diff --git a/.gitignore b/.gitignore index 3dd540f..51c18c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ build*/ -.vscode/ \ No newline at end of file +.vscode/ +include/imgui* \ No newline at end of file diff --git a/meson.build b/meson.build index 982c449..b92abf1 100644 --- a/meson.build +++ b/meson.build @@ -10,9 +10,13 @@ foreach module : ['system', 'window', 'graphics', 'audio'] sfml += [dependency('sfml-'+module, version : '>=2.5.1')] endforeach -add_project_link_arguments(['-lstdc++', '-lstdc++fs', '-lm'], language : 'cpp') +add_project_link_arguments(['-lstdc++', '-lstdc++fs', '-lm', '-lGL'], language : 'cpp') sources = [ + 'include/imgui/imgui.cpp', + 'include/imgui/imgui_draw.cpp', + 'include/imgui/imgui_widgets.cpp', + 'include/imgui-sfml/imgui-SFML.cpp', 'src/Main.cpp', 'src/Data/Chart.hpp', 'src/Data/KeyMapping.hpp', @@ -37,6 +41,7 @@ sources = [ 'src/Resources/CoverAtlas.hpp', 'src/Resources/CoverAtlas.cpp', 'src/Toolkit/AffineTransform.hpp', + 'src/Toolkit/Debuggable.hpp', 'src/Toolkit/EasingFunctions.hpp', 'src/Toolkit/EasingFunctions.cpp', 'src/Toolkit/QuickRNG.hpp', @@ -47,7 +52,7 @@ executable( 'jujube', sources, dependencies: [sfml], - include_directories : include_directories('include'), + include_directories : include_directories('include', 'include/imgui', 'include/imgui-sfml'), cpp_args : [ '-Wall', '-Wextra', diff --git a/src/Resources/CoverAtlas.cpp b/src/Resources/CoverAtlas.cpp index 2f139a8..d825672 100644 --- a/src/Resources/CoverAtlas.cpp +++ b/src/Resources/CoverAtlas.cpp @@ -16,7 +16,7 @@ Textures::CoverAltas::CoverAltas() : sf::Sprite Textures::CoverAltas::at(const fs::path& path) const { - unsigned int index; + std::size_t index; if (path_to_index.find(path) != path_to_index.end()) { index = path_to_index.at(path); } else { @@ -41,7 +41,7 @@ void Textures::CoverAltas::emplace_back(const fs::path& cover) { if (!cover_texture.loadFromFile(cover)) { throw std::invalid_argument("Unable to load cover image : "+cover.string()); } - unsigned int next_index = next_available_index(); + std::size_t next_index = next_available_index(); auto size = cover_texture.getSize(); auto location = get_detailed_location(next_index); sf::Sprite new_cover; @@ -80,12 +80,12 @@ void Textures::CoverAltas::efficient_reload(const std::vector& covers) } } -Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) { +Textures::DetailedLocation Textures::get_detailed_location(std::size_t location) { return {location / 16, (location % 16) / 4, location % 4}; } -unsigned int Textures::CoverAltas::next_available_index() { - unsigned int index = 0; +std::size_t Textures::CoverAltas::next_available_index() { + std::size_t index = 0; while (index_to_path.find(index) != index_to_path.end()) { ++index; } diff --git a/src/Resources/CoverAtlas.hpp b/src/Resources/CoverAtlas.hpp index 2341afd..1aa6e0c 100644 --- a/src/Resources/CoverAtlas.hpp +++ b/src/Resources/CoverAtlas.hpp @@ -37,17 +37,17 @@ namespace Textures { void efficient_reload(const std::vector& covers); private: - std::unordered_map path_to_index; - std::unordered_map index_to_path; + std::unordered_map path_to_index; + std::unordered_map index_to_path; std::vector> textures; - unsigned int next_available_index(); + std::size_t next_available_index(); }; struct DetailedLocation { - unsigned int texture_index; - unsigned int row; - unsigned int column; + std::size_t texture_index; + std::size_t row; + std::size_t column; }; - DetailedLocation get_detailed_location(unsigned int location); + DetailedLocation get_detailed_location(std::size_t location); } diff --git a/src/Screens/MusicSelect/ButtonHighlight.cpp b/src/Screens/MusicSelect/ButtonHighlight.cpp index e9685f5..62a159a 100644 --- a/src/Screens/MusicSelect/ButtonHighlight.cpp +++ b/src/Screens/MusicSelect/ButtonHighlight.cpp @@ -1,6 +1,6 @@ #include "ButtonHighlight.hpp" -MusicSelect::ButtonHighlight::ButtonHighlight(unsigned int t_panel_size) : +MusicSelect::ButtonHighlight::ButtonHighlight(std::size_t t_panel_size) : panel_size(t_panel_size), highlight({static_cast(t_panel_size-3), static_cast(t_panel_size-3)}), time_to_alpha(0.f, 0.25f, 255.f, 0.f) @@ -23,7 +23,7 @@ void MusicSelect::ButtonHighlight::draw(sf::RenderTarget& target, sf::RenderStat it = button_presses_history.erase(it); } else { auto alpha = time_to_alpha.transform(elapsed.asSeconds()); - highlight.setOutlineColor(sf::Color(255,255,0,static_cast(alpha))); + highlight.setOutlineColor(sf::Color(255,255,0,static_cast(alpha))); highlight.setPosition({ static_cast(coords.x * panel_size) + panel_size/2.f, static_cast(coords.y * panel_size) + panel_size/2.f diff --git a/src/Screens/MusicSelect/ButtonHighlight.hpp b/src/Screens/MusicSelect/ButtonHighlight.hpp index 5a43b9b..7c51427 100644 --- a/src/Screens/MusicSelect/ButtonHighlight.hpp +++ b/src/Screens/MusicSelect/ButtonHighlight.hpp @@ -11,11 +11,11 @@ namespace MusicSelect { class ButtonHighlight : public sf::Drawable { public: - explicit ButtonHighlight(unsigned int t_panel_size); + explicit ButtonHighlight(std::size_t t_panel_size); void button_pressed(Button button); private: virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; - unsigned int panel_size; + std::size_t panel_size; mutable sf::RectangleShape highlight; mutable std::map button_presses_history; Toolkit::AffineTransform time_to_alpha; diff --git a/src/Screens/MusicSelect/MusicSelect.cpp b/src/Screens/MusicSelect/MusicSelect.cpp index accfd4d..749c35d 100644 --- a/src/Screens/MusicSelect/MusicSelect.cpp +++ b/src/Screens/MusicSelect/MusicSelect.cpp @@ -1,5 +1,8 @@ #include "MusicSelect.hpp" +#include "imgui/imgui.h" +#include "imgui-sfml/imgui-SFML.h" + #include #include "../../Data/KeyMapping.hpp" @@ -25,10 +28,13 @@ void MusicSelect::Screen::select_chart(sf::RenderWindow& window) { window.create(sf::VideoMode(panel_size*4, panel_size*4), "jujube", sf::Style::Titlebar); window.setFramerateLimit(60); + ImGui::SFML::Init(window); bool chart_selected = false; + sf::Clock imguiClock; while (not chart_selected) { sf::Event event; while (window.pollEvent(event)) { + ImGui::SFML::ProcessEvent(event); switch (event.type) { case sf::Event::KeyPressed: handle_key_press(event.key); @@ -39,17 +45,30 @@ void MusicSelect::Screen::select_chart(sf::RenderWindow& window) { break; } } + + ImGui::SFML::Update(window, imguiClock.restart()); + window.clear(sf::Color::Black); + ribbon.draw_debug(); window.draw(ribbon); window.draw(button_highlight); + ImGui::SFML::Render(window); window.display(); - window.clear(sf::Color::Black); } + ImGui::SFML::Shutdown(); } 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); + } else { + switch (key_event.code){ + case sf::Keyboard::F12: + ribbon.debug = not ribbon.debug; + break; + default: + break; + } } } diff --git a/src/Screens/MusicSelect/MusicSelect.hpp b/src/Screens/MusicSelect/MusicSelect.hpp index c2ecd77..65c31c6 100644 --- a/src/Screens/MusicSelect/MusicSelect.hpp +++ b/src/Screens/MusicSelect/MusicSelect.hpp @@ -24,7 +24,7 @@ namespace MusicSelect { private: // Data const Data::SongList& song_list; - unsigned int panel_size = 150; + std::size_t panel_size = 150; // Resources Resources resources; diff --git a/src/Screens/MusicSelect/Ribbon.cpp b/src/Screens/MusicSelect/Ribbon.cpp index f423e0b..abbc25e 100644 --- a/src/Screens/MusicSelect/Ribbon.cpp +++ b/src/Screens/MusicSelect/Ribbon.cpp @@ -1,161 +1,182 @@ #include "Ribbon.hpp" +#include "imgui/imgui.h" +#include "imgui-sfml/imgui-SFML.h" + +#include #include +#include #include #include #include "Panel.hpp" #include "../../Toolkit/QuickRNG.hpp" -MusicSelect::MoveAnimation::MoveAnimation(unsigned int previous_pos, unsigned int next_pos, size_t ribbon_size, Direction direction) : +MusicSelect::MoveAnimation::MoveAnimation(int previous_pos, int next_pos, size_t ribbon_size, Direction direction, float &t_time_factor) : normalized_to_pos(create_transform(previous_pos, next_pos, ribbon_size, direction)), seconds_to_normalized(0.f, 0.3f, 0.f, 1.f), + time_factor(t_time_factor), clock(), ease_expo(-7.f) { - } -Toolkit::AffineTransform MusicSelect::MoveAnimation::create_transform(unsigned int previous_pos, unsigned int next_pos, size_t ribbon_size, Direction direction) { +Toolkit::AffineTransform MusicSelect::MoveAnimation::create_transform(int previous_pos, int next_pos, size_t ribbon_size, Direction direction) +{ // We first deal with cases were we cross the end of the ribbon - if (direction == Direction::Left and next_pos > previous_pos) { + if (direction == Direction::Left and next_pos > previous_pos) + { return Toolkit::AffineTransform( 0.f, 1.f, static_cast(previous_pos), - static_cast(next_pos-ribbon_size) - ); - } else if (direction == Direction::Right and next_pos < previous_pos) { + static_cast(next_pos) - ribbon_size); + } + else if (direction == Direction::Right and next_pos < previous_pos) + { return Toolkit::AffineTransform( 0.f, 1.f, static_cast(previous_pos), - static_cast(next_pos+ribbon_size) - ); - } else { + static_cast(next_pos) + ribbon_size); + } + else + { return Toolkit::AffineTransform( 0.f, 1.f, static_cast(previous_pos), - static_cast(next_pos) - ); + static_cast(next_pos)); } } -float MusicSelect::MoveAnimation::get_position() { +float MusicSelect::MoveAnimation::get_position() +{ return normalized_to_pos.transform( ease_expo.transform( seconds_to_normalized.clampedTransform( - clock.getElapsedTime().asSeconds() - ) - ) - ); + clock.getElapsedTime().asSeconds() / time_factor))); } -bool MusicSelect::MoveAnimation::ended() { - return clock.getElapsedTime() > sf::milliseconds(300); +bool MusicSelect::MoveAnimation::ended() +{ + return clock.getElapsedTime() / time_factor > sf::milliseconds(300); } -void MusicSelect::Ribbon::title_sort(const Data::SongList& song_list) { +void MusicSelect::Ribbon::title_sort(const Data::SongList &song_list) +{ std::vector> songs; - for (auto &&song : song_list.songs) { + for (auto &&song : song_list.songs) + { songs.push_back(std::cref(song)); } - + (song_list.songs.begin(), song_list.songs.end()); std::sort(songs.begin(), songs.end(), Data::Song::sort_by_title); - std::map>> categories; - for (const auto& song : songs) { - if (song.get().title.size() > 0) { + std::map>> categories; + for (const auto &song : songs) + { + if (song.get().title.size() > 0) + { char letter = song.get().title[0]; - if ('A' <= letter and letter <= 'Z') { + if ('A' <= letter and letter <= 'Z') + { categories [std::string(1, letter)] - .push_back( - std::make_shared(song) - ); - } else if ('a' <= letter and letter <= 'z') { + .push_back( + std::make_shared(song)); + } + else if ('a' <= letter and letter <= 'z') + { categories [std::string(1, 'A' + (letter - 'a'))] - .push_back( - std::make_shared(song) - ); - } else { + .push_back( + std::make_shared(song)); + } + else + { categories["?"].push_back(std::make_shared(song)); } - } else { + } + else + { categories["?"].push_back(std::make_shared(song)); } } layout_from_category_map(categories); } -void MusicSelect::Ribbon::test_sort() { +void MusicSelect::Ribbon::test_sort() +{ layout.clear(); layout.push_back( - { - std::make_shared(), - std::make_shared("A"), - std::make_shared("truc") - } - ); - for (size_t i = 0; i < 3; i++) { + {std::make_shared(), + std::make_shared("A"), + std::make_shared("truc")}); + for (size_t i = 0; i < 3; i++) + { layout.push_back( - { - std::make_shared(), - std::make_shared(), - std::make_shared() - } - ); + {std::make_shared(), + std::make_shared(), + std::make_shared()}); } } -void MusicSelect::Ribbon::test2_sort() { +void MusicSelect::Ribbon::test2_sort() +{ std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; std::map>> categories; - Toolkit::UniformIntRNG category_size_generator{1,10}; - Toolkit::UniformIntRNG panel_hue_generator{0,255}; - for (auto &&letter : alphabet) { + 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++) { + for (int i = 0; i < category_size; i++) + { categories[std::string(1, letter)].push_back( std::make_shared( sf::Color( panel_hue_generator.generate(), panel_hue_generator.generate(), - panel_hue_generator.generate() - ) - ) - ); + panel_hue_generator.generate()))); } } layout_from_category_map(categories); } -const std::shared_ptr& MusicSelect::Ribbon::at(unsigned int button_index) const { +const std::shared_ptr &MusicSelect::Ribbon::at(std::size_t button_index) const +{ return ( layout - .at((position + (button_index % 4)) % layout.size()) - .at(button_index / 4) - ); + .at((position + (button_index % 4)) % layout.size()) + .at(button_index / 4)); } -void MusicSelect::Ribbon::layout_from_category_map(const std::map>>& categories) { +void MusicSelect::Ribbon::layout_from_category_map(const std::map>> &categories) +{ layout.clear(); - for (auto &&[category, panels] : categories) { - if (not panels.empty()) { + for (auto &&[category, panels] : categories) + { + if (not panels.empty()) + { std::vector> current_column; current_column.push_back(std::make_shared(category)); - for (auto &&panel : panels) { - if (current_column.size() == 3) { + for (auto &&panel : panels) + { + if (current_column.size() == 3) + { layout.push_back({current_column[0], current_column[1], current_column[2]}); current_column.clear(); - } else { + } + else + { current_column.push_back(std::move(panel)); } } - if (not current_column.empty()) { - while (current_column.size() < 3) { + if (not current_column.empty()) + { + while (current_column.size() < 3) + { current_column.push_back(std::make_shared()); } layout.push_back({current_column[0], current_column[1], current_column[2]}); @@ -164,43 +185,71 @@ void MusicSelect::Ribbon::layout_from_category_map(const std::map(position)-1, layout.size(), Direction::Left); - if (position == 0) { +void MusicSelect::Ribbon::move_left() +{ + std::size_t old_position = position; + if (position == 0) + { position = layout.size() - 1; - } else { + } + else + { position--; } + move_animation.emplace(old_position, position, layout.size(), Direction::Left, time_factor); } -void MusicSelect::Ribbon::draw(sf::RenderTarget& target, sf::RenderStates states) const { - if (move_animation) { - if (not move_animation->ended()) { +void MusicSelect::Ribbon::draw(sf::RenderTarget &target, sf::RenderStates states) const +{ + if (move_animation) + { + if (not move_animation->ended()) + { return draw_with_animation(target, states); - } else { + } + else + { move_animation.reset(); } } draw_without_animation(target, states); } -void MusicSelect::Ribbon::draw_with_animation(sf::RenderTarget& target, sf::RenderStates states) const { +void MusicSelect::Ribbon::draw_with_animation(sf::RenderTarget &target, sf::RenderStates states) const +{ auto float_position = move_animation->get_position(); - unsigned int column_zero = (static_cast(float_position) + layout.size()) % layout.size(); - for (int column_offset = -1; column_offset <= 4; column_offset++) { - unsigned int actual_column = (column_zero + column_offset + layout.size()) % layout.size(); - for (int row = 0; row < 3; row++) { - layout.at(actual_column).at(row)->draw( + int relative_column_zero = static_cast(std::floor(float_position)); + std::size_t column_zero = (relative_column_zero + layout.size()) % layout.size(); + + if (debug) { + ImGui::Begin("Ribbon Debug"); { + ImGui::Text("float position : %f", float_position); + ImGui::Text("zeroth column : %lu", column_zero); + } + ImGui::End(); + } + + for (int column_offset = -1; column_offset <= 4; column_offset++) + { + std::size_t actual_column = (column_zero + column_offset + layout.size()) % layout.size(); + for (int row = 0; row < 3; row++) + { + layout + .at(actual_column) + .at(row) + ->draw( resources, target, sf::FloatRect( - (static_cast(column_zero + column_offset)-float_position)*150.f, - row*150.f, + (static_cast(relative_column_zero + column_offset) - float_position) * 150.f, + row * 150.f, 150.f, 150.f ) @@ -209,16 +258,22 @@ void MusicSelect::Ribbon::draw_with_animation(sf::RenderTarget& target, sf::Rend } } -void MusicSelect::Ribbon::draw_without_animation(sf::RenderTarget& target, sf::RenderStates states) const { - for (int column = -1; column <= 4; column++) { +void MusicSelect::Ribbon::draw_without_animation(sf::RenderTarget &target, sf::RenderStates states) const +{ + for (int column = -1; column <= 4; column++) + { int actual_column_index = (column + position + layout.size()) % layout.size(); - for (int row = 0; row < 3; row++) { - layout.at(actual_column_index).at(row)->draw( + for (int row = 0; row < 3; row++) + { + layout + .at(actual_column_index) + .at(row) + ->draw( resources, target, sf::FloatRect( - column*150.f, - row*150.f, + column * 150.f, + row * 150.f, 150.f, 150.f ) @@ -226,3 +281,14 @@ void MusicSelect::Ribbon::draw_without_animation(sf::RenderTarget& target, sf::R } } } + +void MusicSelect::Ribbon::draw_debug() +{ + if (debug) { + ImGui::Begin("Ribbon Debug"); + { + ImGui::SliderFloat("Time Slowdown Factor", &time_factor, 1.f, 10.f); + } + ImGui::End(); + } +} diff --git a/src/Screens/MusicSelect/Ribbon.hpp b/src/Screens/MusicSelect/Ribbon.hpp index 9ea5c26..dbc683a 100644 --- a/src/Screens/MusicSelect/Ribbon.hpp +++ b/src/Screens/MusicSelect/Ribbon.hpp @@ -5,6 +5,7 @@ #include "Panel.hpp" #include "../../Data/SongList.hpp" #include "../../Toolkit/AffineTransform.hpp" +#include "../../Toolkit/Debuggable.hpp" #include "../../Toolkit/EasingFunctions.hpp" namespace MusicSelect { @@ -15,37 +16,40 @@ namespace MusicSelect { }; struct MoveAnimation { - MoveAnimation(unsigned int previous_pos, unsigned int next_pos, size_t ribbon_size, Direction direction); + MoveAnimation(int previous_pos, int next_pos, size_t ribbon_size, Direction direction, float& t_time_factor); Toolkit::AffineTransform normalized_to_pos; Toolkit::AffineTransform seconds_to_normalized; + float& time_factor; sf::Clock clock; Toolkit::EaseExponential ease_expo; float get_position(); bool ended(); private: - Toolkit::AffineTransform create_transform(unsigned int previous_pos, unsigned int next_pos, size_t ribbon_size, Direction direction); + Toolkit::AffineTransform create_transform(int previous_pos, int next_pos, size_t ribbon_size, Direction direction); }; // The Ribbon is the moving part of the Music Select Screen // It can be sorted in a number of ways - class Ribbon final : public sf::Drawable { + class Ribbon final : public sf::Drawable, public Toolkit::Debuggable { public: Ribbon(const Resources& t_resources) : resources(t_resources) {}; void title_sort(const Data::SongList& song_list); void test_sort(); void test2_sort(); const auto& get_layout() {return layout;}; - const std::shared_ptr& at(unsigned int button_index) const; + const std::shared_ptr& at(std::size_t button_index) const; void move_right(); void move_left(); + void draw_debug() override; private: void draw(sf::RenderTarget& target, sf::RenderStates states) const override; void draw_with_animation(sf::RenderTarget& target, sf::RenderStates states) const; void draw_without_animation(sf::RenderTarget& target, sf::RenderStates states) const; void layout_from_category_map(const std::map>>& categories); std::vector,3>> layout; - unsigned int position = 0; + std::size_t position = 0; mutable std::optional move_animation; const Resources& resources; + float time_factor = 1.f; }; } \ No newline at end of file diff --git a/src/Toolkit/Debuggable.hpp b/src/Toolkit/Debuggable.hpp new file mode 100644 index 0000000..c5ee599 --- /dev/null +++ b/src/Toolkit/Debuggable.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace Toolkit { + // Classes that can show a debug interface toggled by the boolean + class Debuggable { + public: + virtual ~Debuggable() = default; + virtual void draw_debug() = 0; + bool debug; + }; +} \ No newline at end of file