1
0
mirror of synced 2024-09-24 19:38:25 +02:00

Basic category layout

This commit is contained in:
Stepland 2020-01-26 17:55:20 +01:00
parent 59960de2bb
commit 12b5283a71
14 changed files with 318 additions and 34 deletions

View File

@ -1,7 +1,7 @@
project(
'jujube',
'cpp',
default_options : ['cpp_std=c++17'],
default_options : ['cpp_std=c++20'],
version : '0.1.0-alpha',
)
@ -23,6 +23,8 @@ sources = [
'src/Data/SongList.hpp',
'src/Data/SongList.cpp',
# 'src/Screens/Gameplay.hpp',
'src/Screens/MusicSelect/ButtonHighlight.hpp',
'src/Screens/MusicSelect/ButtonHighlight.cpp',
'src/Screens/MusicSelect/MusicSelect.hpp',
'src/Screens/MusicSelect/MusicSelect.cpp',
'src/Screens/MusicSelect/Panel.hpp',
@ -34,6 +36,9 @@ sources = [
# 'src/Screens/Result.hpp',
'src/Resources/CoverAtlas.hpp',
'src/Resources/CoverAtlas.cpp',
'src/Toolkit/QuickRNG.hpp',
'src/Toolkit/QuickRNG.cpp',
'src/Toolkit/AffineTransform.hpp',
]
executable(

View File

@ -1,10 +1,35 @@
#include "KeyMapping.hpp"
ButtonCoords toCoord(Button panel) {
auto num = static_cast<int>(panel);
ButtonCoords toCoords(Button button) {
auto num = static_cast<unsigned int>(button);
return {num % 4, num / 4};
}
unsigned int toIndex(Button button) {
return static_cast<unsigned int>(button);
}
std::optional<Button> fromCoords(ButtonCoords button_coords) {
if (
button_coords.x >= 0 and
button_coords.x < 4 and
button_coords.y >= 0 and
button_coords.y < 4
) {
return static_cast<Button>(button_coords.x + 4*button_coords.y);
} else {
return {};
}
}
std::optional<Button> fromIndex(unsigned int index) {
if (index >= 0 and index < 16) {
return static_cast<Button>(index);
} else {
return {};
}
}
KeyMapping::KeyMapping() {
m_key_to_button[sf::Keyboard::Num1] = Button::B1;
m_key_to_button[sf::Keyboard::Num2] = Button::B2;
@ -28,27 +53,27 @@ KeyMapping::KeyMapping() {
}
}
void KeyMapping::setPanelToKey(const Button& panel, const sf::Keyboard::Key& key) {
void KeyMapping::setPanelToKey(const Button& button, const sf::Keyboard::Key& key) {
if (m_key_to_button.find(key) != m_key_to_button.end()) {
m_button_to_key.erase(m_key_to_button[key]);
m_key_to_button.erase(key);
}
m_button_to_key[panel] = key;
m_key_to_button[key] = panel;
m_button_to_key[button] = key;
m_key_to_button[key] = button;
}
std::optional<Button> KeyMapping::key_to_button(const sf::Keyboard::Key& key) {
try {
return m_key_to_button.at(key);
} catch(const std::exception& e) {
if (m_key_to_button.find(key) == m_key_to_button.end()) {
return {};
} else {
return m_key_to_button.at(key);
}
}
std::optional<sf::Keyboard::Key> KeyMapping::button_to_key(const Button& panel) {
try {
return m_button_to_key.at(panel);
} catch(const std::exception& e) {
std::optional<sf::Keyboard::Key> KeyMapping::button_to_key(const Button& button) {
if (m_button_to_key.find(button) == m_button_to_key.end()) {
return {};
} else {
return m_button_to_key.at(button);
}
}

View File

@ -29,7 +29,10 @@ struct ButtonCoords {
unsigned int y;
};
ButtonCoords toCoord(Button button);
ButtonCoords toCoords(Button button);
unsigned int toIndex(Button button);
std::optional<Button> fromCoords(ButtonCoords button_coords);
std::optional<Button> fromIndex(unsigned int index);
class KeyMapping {
public:

View File

@ -0,0 +1,35 @@
#include "ButtonHighlight.hpp"
MusicSelect::ButtonHighlight::ButtonHighlight(unsigned int t_panel_size) :
panel_size(t_panel_size),
highlight({static_cast<float>(t_panel_size-3), static_cast<float>(t_panel_size-3)}),
time_to_alpha(0.f, 0.25f, 255.f, 0.f)
{
highlight.setFillColor(sf::Color::Transparent);
highlight.setOutlineThickness(1.f);
highlight.setOrigin(highlight.getSize().x / 2.f, highlight.getSize().y / 2.f);
}
void MusicSelect::ButtonHighlight::button_pressed(Button button) {
button_presses_history[button].restart();
}
void MusicSelect::ButtonHighlight::draw(sf::RenderTarget& target, sf::RenderStates states) const {
auto it = button_presses_history.begin();
while (it != button_presses_history.end()) {
auto elapsed = it->second.getElapsedTime();
auto coords = toCoords(it->first);
if (elapsed > sf::milliseconds(250)) {
it = button_presses_history.erase(it);
} else {
auto alpha = time_to_alpha.transform(elapsed.asSeconds());
highlight.setOutlineColor(sf::Color(255,255,0,static_cast<unsigned int>(alpha)));
highlight.setPosition({
static_cast<float>(coords.x * panel_size) + panel_size/2.f,
static_cast<float>(coords.y * panel_size) + panel_size/2.f
});
target.draw(highlight, states);
++it;
}
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <map>
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include "../../Data/KeyMapping.hpp"
#include "../../Toolkit/AffineTransform.hpp"
namespace MusicSelect {
class ButtonHighlight : public sf::Drawable {
public:
explicit ButtonHighlight(unsigned int t_panel_size);
void button_pressed(Button button);
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
unsigned int panel_size;
mutable sf::RectangleShape highlight;
mutable std::map<Button, sf::Clock> button_presses_history;
Toolkit::AffineTransform<float> time_to_alpha;
};
}

View File

@ -6,7 +6,8 @@
MusicSelect::Screen::Screen(const Data::SongList& t_song_list) :
song_list(t_song_list),
ribbon(MusicSelect::Ribbon::test_sort())
ribbon(MusicSelect::Ribbon::test2_sort()),
button_highlight(panel_size)
{
for (const auto& song : song_list.songs) {
if (song.cover) {
@ -21,7 +22,7 @@ MusicSelect::Screen::Screen(const Data::SongList& t_song_list) :
void MusicSelect::Screen::select_chart(sf::RenderWindow& window) {
window.create(sf::VideoMode(600,600), "jujube", sf::Style::None);
window.create(sf::VideoMode(panel_size*4, panel_size*4), "jujube", sf::Style::Titlebar);
window.setFramerateLimit(60);
bool chart_selected = false;
while (not chart_selected) {
@ -29,13 +30,16 @@ void MusicSelect::Screen::select_chart(sf::RenderWindow& window) {
while (window.pollEvent(event)) {
switch (event.type) {
case sf::Event::KeyPressed:
handle_key(event.key);
handle_key_press(event.key);
break;
case sf::Event::MouseButtonPressed:
handle_mouse_click(event.mouseButton);
default:
break;
}
}
// drawing the ribbon
// draw the ribbon
for (size_t panel = 0; panel < 12; panel++) {
ribbon.at(panel)->draw(
resources,
@ -48,10 +52,43 @@ void MusicSelect::Screen::select_chart(sf::RenderWindow& window) {
)
);
}
window.draw(button_highlight);
window.display();
window.clear(sf::Color::Black);
}
}
void MusicSelect::Screen::handle_key(const sf::Event::KeyEvent& key_event) {
void MusicSelect::Screen::handle_key_press(const sf::Event::KeyEvent& key_event) {
auto button = key_mapping.key_to_button(key_event.code);
if (button) {
press_button(*button);
}
}
void MusicSelect::Screen::handle_mouse_click(const sf::Event::MouseButtonEvent& mouse_button_event) {
if (mouse_button_event.button == sf::Mouse::Left) {
int clicked_panel_index = (mouse_button_event.x / panel_size) + 4 * (mouse_button_event.y / panel_size);
auto button = fromIndex(clicked_panel_index);
if (button) {
press_button(*button);
}
}
}
void MusicSelect::Screen::press_button(const Button& button) {
button_highlight.button_pressed(button);
auto index = toIndex(button);
if (index < 12) {
ribbon.at(index)->click(*this);
} else {
switch (button) {
case Button::B13:
ribbon.move_left();
break;
case Button::B14:
ribbon.move_right();
default:
break;
}
}
}

View File

@ -7,8 +7,10 @@
#include "../../Data/SongList.hpp"
#include "../../Data/Chart.hpp"
#include "../../Data/KeyMapping.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "Ribbon.hpp"
#include "Resources.hpp"
#include "ButtonHighlight.hpp"
namespace MusicSelect {
@ -22,6 +24,7 @@ namespace MusicSelect {
private:
// Data
const Data::SongList& song_list;
unsigned int panel_size = 150;
// Resources
Resources resources;
@ -29,8 +32,14 @@ namespace MusicSelect {
// State
Ribbon ribbon;
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
ButtonHighlight button_highlight;
KeyMapping key_mapping;
void handle_key(const sf::Event::KeyEvent& key_event);
// converts a key press into a button press
void handle_key_press(const sf::Event::KeyEvent& key_event);
// converts a mouse click into a button press
void handle_mouse_click(const sf::Event::MouseButtonEvent& mouse_button_event);
// chooses what happens for each button
void press_button(const Button& button);
};
}

View File

@ -4,6 +4,14 @@
#include "MusicSelect.hpp"
void MusicSelect::ColorPanel::draw(Resources& resources, sf::RenderTarget& target, sf::FloatRect area) {
sf::RectangleShape panel{{area.width*0.9f, area.height*0.9f}};
panel.setFillColor(this->color);
panel.setOrigin(panel.getSize().x / 2.f, panel.getSize().y / 2.f);
panel.setPosition(area.left+area.width/2.f, area.top+area.height/2.f);
target.draw(panel);
}
void MusicSelect::CategoryPanel::click(Screen& screen) {
}

View File

@ -11,7 +11,7 @@ namespace MusicSelect {
class Screen;
// A Panel holds anything that can go under a button on the moving part
// of the music select screen, be it nothing, a sort indicator, or a song
// of the music select screen, be it nothing, a category indicator, or a song
class Panel {
public:
// What happens when you click on the panel
@ -27,6 +27,15 @@ namespace MusicSelect {
void draw(Resources& resources, sf::RenderTarget& target, sf::FloatRect area) override {return;};
};
class ColorPanel final : public Panel {
public:
explicit ColorPanel(const sf::Color& t_color) : color(t_color) {};
void click(Screen& screen) override {return;};
void draw(Resources& resources, sf::RenderTarget& target, sf::FloatRect area) override;
private:
const sf::Color color;
};
class CategoryPanel final : public Panel {
public:
explicit CategoryPanel(const std::string& t_label) : label(t_label) {};

View File

@ -1,9 +1,11 @@
#include "Ribbon.hpp"
#include <cstdlib>
#include <map>
#include <vector>
#include "Panel.hpp"
#include "../../Toolkit/QuickRNG.hpp"
MusicSelect::Ribbon MusicSelect::Ribbon::title_sort(const Data::SongList& song_list) {
std::map<char,std::vector<Data::Song>> categories;
@ -23,18 +25,18 @@ MusicSelect::Ribbon MusicSelect::Ribbon::title_sort(const Data::SongList& song_l
}
Ribbon ribbon;
for (auto& [letter, songs] : categories) {
std::vector<std::unique_ptr<Panel>> panels;
std::vector<std::shared_ptr<Panel>> panels;
panels.emplace_back(
std::make_unique<CategoryPanel>(
std::make_shared<CategoryPanel>(
std::string(1, letter)
)
);
std::sort(songs.begin(), songs.end(), Data::Song::sort_by_title);
for (const auto& song : songs) {
panels.push_back(std::make_unique<SongPanel>(song));
panels.push_back(std::make_shared<SongPanel>(song));
}
while (panels.size() % 3 != 0) {
panels.push_back(std::make_unique<EmptyPanel>());
panels.push_back(std::make_shared<EmptyPanel>());
}
for (size_t i = 0; i < panels.size(); i += 3) {
ribbon.layout.emplace_back();
@ -50,27 +52,86 @@ MusicSelect::Ribbon MusicSelect::Ribbon::test_sort() {
Ribbon ribbon;
ribbon.layout.push_back(
{
std::make_unique<EmptyPanel>(),
std::make_unique<CategoryPanel>("A"),
std::make_unique<CategoryPanel>("truc")
std::make_shared<EmptyPanel>(),
std::make_shared<CategoryPanel>("A"),
std::make_shared<CategoryPanel>("truc")
}
);
for (size_t i = 0; i < 3; i++) {
ribbon.layout.push_back(
{
std::make_unique<EmptyPanel>(),
std::make_unique<EmptyPanel>(),
std::make_unique<EmptyPanel>()
std::make_shared<EmptyPanel>(),
std::make_shared<EmptyPanel>(),
std::make_shared<EmptyPanel>()
}
);
}
return ribbon;
}
const std::unique_ptr<MusicSelect::Panel>& MusicSelect::Ribbon::at(unsigned int button_index) const {
MusicSelect::Ribbon MusicSelect::Ribbon::test2_sort() {
std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::map<std::string, std::list<std::shared_ptr<Panel>>> categories;
Toolkit::UniformIntRNG category_size_generator{1,10};
Toolkit::UniformIntRNG panel_hue_generator{0,255};
for (auto &&letter : alphabet) {
auto category_size = category_size_generator.generate();
for (int i = 0; i < category_size; i++) {
categories[std::string(1, letter)].push_back(
std::make_shared<MusicSelect::ColorPanel>(
sf::Color(
panel_hue_generator.generate(),
panel_hue_generator.generate(),
panel_hue_generator.generate()
)
)
);
}
}
return Ribbon::layout_from_map(categories);
}
const std::shared_ptr<MusicSelect::Panel>& MusicSelect::Ribbon::at(unsigned int button_index) const {
return (
layout
.at((position + (button_index % 4)) % layout.size())
.at(button_index / 4)
);
}
MusicSelect::Ribbon MusicSelect::Ribbon::layout_from_map(const std::map<std::string, std::list<std::shared_ptr<MusicSelect::Panel>>>& categories) {
Ribbon ribbon;
for (auto &&[category, panels] : categories) {
if (not panels.empty()) {
std::vector<std::shared_ptr<Panel>> current_column;
current_column.push_back(std::make_shared<CategoryPanel>(category));
for (auto &&panel : panels) {
if (current_column.size() == 3) {
ribbon.layout.push_back({current_column[0], current_column[1], current_column[2]});
current_column.clear();
} else {
current_column.push_back(std::move(panel));
}
}
if (not current_column.empty()) {
while (current_column.size() < 3) {
current_column.push_back(std::make_shared<EmptyPanel>());
}
ribbon.layout.push_back({current_column[0], current_column[1], current_column[2]});
}
}
}
return ribbon;
}
void MusicSelect::Ribbon::move_right() {
position = (position + 1) % layout.size();
}
void MusicSelect::Ribbon::move_left() {
if (position == 0) {
position = layout.size() - 1;
} else {
position--;
}
}

View File

@ -11,10 +11,14 @@ namespace MusicSelect {
Ribbon() = default;
static Ribbon title_sort(const Data::SongList& song_list);
static Ribbon test_sort();
static Ribbon test2_sort();
const auto& get_layout() {return layout;};
const std::unique_ptr<MusicSelect::Panel>& at(unsigned int button_index) const;
const std::shared_ptr<MusicSelect::Panel>& at(unsigned int button_index) const;
void move_right();
void move_left();
private:
std::vector<std::array<std::unique_ptr<Panel>,3>> layout;
static Ribbon layout_from_map(const std::map<std::string,std::list<std::shared_ptr<Panel>>>& categories);
std::vector<std::array<std::shared_ptr<Panel>,3>> layout;
unsigned int position = 0;
};
}

View File

@ -0,0 +1,40 @@
#pragma once
namespace Toolkit {
template<typename T>
class AffineTransform {
public:
AffineTransform(T low_input, T high_input, T low_output, T high_output):
m_low_input(low_input),
m_high_input(high_input),
m_low_output(low_output),
m_high_output(high_output)
{
if (low_input == high_input) {
throw std::invalid_argument("low and high input values for affine transform must be different !");
}
m_a = (high_output-low_output)/(high_input-low_input);
m_b = (high_input*low_output - high_output*low_input)/(high_input-low_input);
};
T transform(T val) const {return m_a*val + m_b;};
T clampedTransform(T val) const { return transform(std::clamp(val,m_low_input,m_high_input));};
T backwards_transform(T val) const {
// if we're too close to zero
if (std::abs(m_a) < 10e-10) {
throw std::runtime_error("Can't apply backwards transformation, coefficient is too close to zero");
} else {
return (val-m_b)/m_a;
}
};
void setB(T b) {
m_b = b;
}
private:
T m_a;
T m_b;
T m_low_input;
T m_high_input;
T m_low_output;
T m_high_output;
};
}

11
src/Toolkit/QuickRNG.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "QuickRNG.hpp"
Toolkit::UniformIntRNG::UniformIntRNG(int min, int max) : m_distribution(min, max) {
std::random_device rd;
m_generator = std::mt19937{rd()};
}
int Toolkit::UniformIntRNG::generate() {
return m_distribution(m_generator);
}

14
src/Toolkit/QuickRNG.hpp Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <random>
namespace Toolkit {
class UniformIntRNG {
public:
UniformIntRNG(int min, int max);
int generate();
private:
std::mt19937 m_generator;
std::uniform_int_distribution<> m_distribution;
};
}