From f9922c341ad1a30d615722f139a2c1ff8f4c741e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sym=C3=A9on=20CARLE?= <16676308+Stepland@users.noreply.github.com> Date: Mon, 10 Feb 2020 22:59:53 +0100 Subject: [PATCH] Fix empty song folder crashing music select screen --- TODO.md | 1 + src/Screens/MusicSelect/Panel.cpp | 25 ++ src/Screens/MusicSelect/Panel.hpp | 10 + src/Screens/MusicSelect/Ribbon.cpp | 545 +++++++++++++++-------------- src/Screens/MusicSelect/Ribbon.hpp | 1 + 5 files changed, 321 insertions(+), 261 deletions(-) diff --git a/TODO.md b/TODO.md index 333410b..8f34364 100644 --- a/TODO.md +++ b/TODO.md @@ -11,6 +11,7 @@ - Handling Resolution changes - Make Panels Drawable and Transformable - Make Ribbon Transformable +- Fix jujube crashing on empty layout ## TODO - Preference persistency system diff --git a/src/Screens/MusicSelect/Panel.cpp b/src/Screens/MusicSelect/Panel.cpp index 3d4a761..a695311 100644 --- a/src/Screens/MusicSelect/Panel.cpp +++ b/src/Screens/MusicSelect/Panel.cpp @@ -105,4 +105,29 @@ namespace MusicSelect { rect.setSize({bounds.width, bounds.height}); rect.setPosition({bounds.left, bounds.top}); } + + void ColoredMessagePanel::draw(sf::RenderTarget& target, sf::RenderStates states) const { + states.transform *= getTransform(); + sf::RectangleShape frame{{m_size*0.9f, m_size*0.9f}}; + frame.setFillColor(sf::Color::Black); + frame.setOutlineThickness(1.f); + frame.setOutlineColor(m_color); + frame.setOrigin(frame.getSize().x / 2.f, frame.getSize().y / 2.f); + frame.setPosition(m_size/2.f, m_size/2.f); + target.draw(frame, states); + + sf::Text message; + message.setFont(m_resources.noto_sans_medium); + message.setString(m_message); + message.setCharacterSize(static_cast(0.1f*m_size)); + message.setFillColor(m_color); + auto bounds = message.getLocalBounds(); + message.setOrigin(bounds.left+bounds.width*0.5f, bounds.top+bounds.height*0.5f); + auto biggest_side = std::max(bounds.width, bounds.height); + if (biggest_side > m_size*0.8f) { + message.setScale(m_size*0.8f / biggest_side, m_size*0.8f / biggest_side); + } + message.setPosition(m_size*0.5f, m_size*0.5f); + target.draw(message, states); + } } \ No newline at end of file diff --git a/src/Screens/MusicSelect/Panel.hpp b/src/Screens/MusicSelect/Panel.hpp index cd930f6..cc07d4d 100644 --- a/src/Screens/MusicSelect/Panel.hpp +++ b/src/Screens/MusicSelect/Panel.hpp @@ -32,6 +32,16 @@ namespace MusicSelect { void draw(sf::RenderTarget& target, sf::RenderStates states) const override {return;}; }; + class ColoredMessagePanel final : public Panel { + public: + ColoredMessagePanel(const float& size, Resources& resources, const sf::Color& color, const std::string& message) : Panel(size, resources), m_color(color), m_message(message) {}; + void click(Ribbon& ribbon, std::size_t from_button_index) override {return;}; + private: + void draw(sf::RenderTarget& target, sf::RenderStates states) const override; + const sf::Color m_color; + const std::string m_message; + }; + class ColorPanel final : public Panel { public: ColorPanel(const float& size, Resources& resources, const sf::Color& t_color) : Panel(size, resources), m_color(t_color) {}; diff --git a/src/Screens/MusicSelect/Ribbon.cpp b/src/Screens/MusicSelect/Ribbon.cpp index 578ae40..a59f617 100644 --- a/src/Screens/MusicSelect/Ribbon.cpp +++ b/src/Screens/MusicSelect/Ribbon.cpp @@ -13,310 +13,333 @@ #include "../../Data/SongList.hpp" #include "../../Toolkit/QuickRNG.hpp" -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), - m_time_factor(t_time_factor), - clock(), - ease_expo(-7.f) -{ -} -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) { - return Toolkit::AffineTransform( - 0.f, - 1.f, - static_cast(previous_pos), - static_cast(next_pos) - ribbon_size); +namespace 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), + m_time_factor(t_time_factor), + clock(), + ease_expo(-7.f) + { } - 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 { - return Toolkit::AffineTransform( - 0.f, - 1.f, - static_cast(previous_pos), - static_cast(next_pos) - ); - } -} -float MusicSelect::MoveAnimation::get_position() { - return normalized_to_pos.transform( - ease_expo.transform( - seconds_to_normalized.clampedTransform( - clock.getElapsedTime().asSeconds() / m_time_factor + Toolkit::AffineTransform 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) { + 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) { + return Toolkit::AffineTransform( + 0.f, + 1.f, + static_cast(previous_pos), + static_cast(next_pos) + ribbon_size + ); + } else { + return Toolkit::AffineTransform( + 0.f, + 1.f, + static_cast(previous_pos), + static_cast(next_pos) + ); + } + } + + float MoveAnimation::get_position() { + return normalized_to_pos.transform( + ease_expo.transform( + seconds_to_normalized.clampedTransform( + clock.getElapsedTime().asSeconds() / m_time_factor + ) ) - ) - ); -} - -bool MusicSelect::MoveAnimation::ended() { - return clock.getElapsedTime() / m_time_factor > sf::milliseconds(300); -} - -MusicSelect::Ribbon::Ribbon(Resources& t_resources, float& panel_size, float& panel_spacing) : - m_layout(), - m_move_animation(), - m_resources(t_resources), - empty_song(), - m_panel_size(panel_size), - m_panel_spacing(panel_spacing) -{ - std::cout << "Loaded MusicSelect::Ribbon" << std::endl; -} - -void MusicSelect::Ribbon::title_sort(const Data::SongList &song_list) { - std::vector> songs; - for (auto &&song : song_list.songs) { - songs.push_back(std::cref(song)); + ); } - 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) { - char letter = song.get().title[0]; - if ('A' <= letter and letter <= 'Z') { - categories - [std::string(1, letter)] - .push_back( - std::make_shared(m_panel_size, m_resources, song) - ); - } else if ('a' <= letter and letter <= 'z') { - categories - [std::string(1, 'A' + (letter - 'a'))] - .push_back( - std::make_shared(m_panel_size, m_resources, song) - ); + + bool MoveAnimation::ended() { + return clock.getElapsedTime() / m_time_factor > sf::milliseconds(300); + } + + Ribbon::Ribbon(Resources& t_resources, float& panel_size, float& panel_spacing) : + m_layout(), + m_move_animation(), + m_resources(t_resources), + empty_song(), + m_panel_size(panel_size), + m_panel_spacing(panel_spacing) + { + std::cout << "Loaded Ribbon" << std::endl; + } + + void Ribbon::title_sort(const Data::SongList &song_list) { + std::vector> songs; + for (auto &&song : song_list.songs) { + songs.push_back(std::cref(song)); + } + 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) { + char letter = song.get().title[0]; + if ('A' <= letter and letter <= 'Z') { + categories + [std::string(1, letter)] + .push_back( + std::make_shared(m_panel_size, m_resources, song) + ); + } else if ('a' <= letter and letter <= 'z') { + categories + [std::string(1, 'A' + (letter - 'a'))] + .push_back( + std::make_shared(m_panel_size, m_resources, song) + ); + } else { + categories["?"].push_back(std::make_shared(m_panel_size, m_resources, song)); + } } else { categories["?"].push_back(std::make_shared(m_panel_size, m_resources, song)); } - } else { - categories["?"].push_back(std::make_shared(m_panel_size, m_resources, song)); } + layout_from_category_map(categories); } - layout_from_category_map(categories); -} -void MusicSelect::Ribbon::test_sort() { - m_layout.clear(); - m_layout.push_back({ - std::make_shared(m_panel_size, m_resources), - std::make_shared(m_panel_size, m_resources, "A"), - std::make_shared(m_panel_size, m_resources, "truc") - }); - for (size_t i = 0; i < 3; i++) { + void Ribbon::test_sort() { + m_layout.clear(); m_layout.push_back({ std::make_shared(m_panel_size, m_resources), - std::make_shared(m_panel_size, m_resources), - std::make_shared(m_panel_size, m_resources) + std::make_shared(m_panel_size, m_resources, "A"), + std::make_shared(m_panel_size, m_resources, "truc") }); + for (size_t i = 0; i < 3; i++) { + m_layout.push_back({ + std::make_shared(m_panel_size, m_resources), + std::make_shared(m_panel_size, m_resources), + std::make_shared(m_panel_size, m_resources) + }); + } + fill_layout(); } -} -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) { - 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( - m_panel_size, m_resources, - sf::Color( - panel_hue_generator.generate(), - panel_hue_generator.generate(), - panel_hue_generator.generate() + void 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) { + 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( + m_panel_size, m_resources, + sf::Color( + panel_hue_generator.generate(), + panel_hue_generator.generate(), + panel_hue_generator.generate() + ) ) - ) - ); + ); + } } + layout_from_category_map(categories); } - layout_from_category_map(categories); -} -void MusicSelect::Ribbon::test_song_cover_sort() { - std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - std::map>> categories; - Toolkit::UniformIntRNG category_size_generator{1, 10}; - 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(m_panel_size, m_resources, this->empty_song) - ); + void Ribbon::test_song_cover_sort() { + std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::map>> categories; + Toolkit::UniformIntRNG category_size_generator{1, 10}; + 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(m_panel_size, m_resources, this->empty_song) + ); + } } + layout_from_category_map(categories); } - layout_from_category_map(categories); -} -std::size_t MusicSelect::Ribbon::get_layout_column(const std::size_t& button_index) const { - return (m_position + (button_index % 4)) % m_layout.size(); -} + std::size_t Ribbon::get_layout_column(const std::size_t& button_index) const { + return (m_position + (button_index % 4)) % m_layout.size(); + } -const std::shared_ptr &MusicSelect::Ribbon::get_panel_under_button(std::size_t button_index) const { - return ( - m_layout - .at(this->get_layout_column(button_index)) - .at(button_index / 4) - ); -} + const std::shared_ptr &Ribbon::get_panel_under_button(std::size_t button_index) const { + return ( + m_layout + .at(this->get_layout_column(button_index)) + .at(button_index / 4) + ); + } -void MusicSelect::Ribbon::click_on(std::size_t button_index) { - this->get_panel_under_button(button_index)->click(*this, button_index); -} + void Ribbon::click_on(std::size_t button_index) { + this->get_panel_under_button(button_index)->click(*this, button_index); + } -void MusicSelect::Ribbon::layout_from_category_map(const std::map>> &categories) { - m_layout.clear(); - for (auto &&[category, panels] : categories) { - if (not panels.empty()) { - std::vector> current_column; - current_column.push_back(std::make_shared(m_panel_size, m_resources, category)); - for (auto &&panel : panels) { - if (current_column.size() == 3) { + void Ribbon::layout_from_category_map(const std::map>> &categories) { + m_layout.clear(); + for (auto &&[category, panels] : categories) { + if (not panels.empty()) { + std::vector> current_column; + current_column.push_back(std::make_shared(m_panel_size, m_resources, category)); + for (auto &&panel : panels) { + if (current_column.size() == 3) { + m_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(m_panel_size, m_resources)); + } m_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(m_panel_size, m_resources)); - } - m_layout.push_back({current_column[0], current_column[1], current_column[2]}); - } } + fill_layout(); } -} -void MusicSelect::Ribbon::move_right() { - std::size_t old_position = m_position; - m_position = (m_position + 1) % m_layout.size(); - m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Right, m_time_factor); -} - -void MusicSelect::Ribbon::move_left() { - std::size_t old_position = m_position; - if (m_position == 0) { - m_position = m_layout.size() - 1; - } else { - m_position--; - } - m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Left, m_time_factor); -} - -void MusicSelect::Ribbon::move_to_next_category(const std::size_t& from_button_index) { - std::size_t old_position = m_position; - std::size_t from_column = this->get_layout_column(from_button_index); - - bool found = false; - size_t offset = 1; - // Cycle through the whole ribbon once starting on the column next to - // the one that was just clicked, possibly wrapping around - while(offset < m_layout.size()) { - const auto& column = m_layout.at((from_column + offset) % m_layout.size()); - if (std::any_of( - column.begin(), - column.end(), - [](std::shared_ptr panel) -> bool { - return (std::dynamic_pointer_cast(panel).get() != nullptr); - } - )) { - found = true; - break; - } - offset++; - } - if (found) { - // we want the next category panel to land on the same column we clicked - auto next_category_column = from_column + offset; - auto onscreen_clicked_column = (from_button_index % 4); - m_position = next_category_column - onscreen_clicked_column; + void Ribbon::move_right() { + std::size_t old_position = m_position; + m_position = (m_position + 1) % m_layout.size(); m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Right, m_time_factor); } -} -void MusicSelect::Ribbon::draw(sf::RenderTarget &target, sf::RenderStates states) const { - states.transform *= getTransform(); - if (m_move_animation) { - if (not m_move_animation->ended()) { - return draw_with_animation(target, states); + void Ribbon::move_left() { + std::size_t old_position = m_position; + if (m_position == 0) { + m_position = m_layout.size() - 1; } else { - m_move_animation.reset(); + m_position--; } - } - draw_without_animation(target, states); -} - -void MusicSelect::Ribbon::draw_with_animation(sf::RenderTarget &target, sf::RenderStates states) const { - auto float_position = m_move_animation->get_position(); - int relative_column_zero = static_cast(std::floor(float_position)); - std::size_t column_zero = (relative_column_zero + m_layout.size()) % m_layout.size(); - - if (debug) { - if (ImGui::Begin("Ribbon Debug")) { - ImGui::Text("float position : %f", float_position); - ImGui::Text("zeroth column : %lu", column_zero); - } - ImGui::End(); + m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Left, m_time_factor); } - for (int column_offset = -1; column_offset <= 4; column_offset++) { - std::size_t actual_column = (column_zero + column_offset + m_layout.size()) % m_layout.size(); - for (int row = 0; row < 3; row++) { - auto panel = m_layout.at(actual_column).at(row); - panel->setPosition( - (static_cast(relative_column_zero + column_offset) - float_position) * (m_panel_size+m_panel_spacing), - row * (m_panel_size+m_panel_spacing) - ); - target.draw(*panel, states); - } - } -} + void Ribbon::move_to_next_category(const std::size_t& from_button_index) { + std::size_t old_position = m_position; + std::size_t from_column = this->get_layout_column(from_button_index); -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 + m_position + m_layout.size()) % m_layout.size(); - for (int row = 0; row < 3; row++) { - auto panel = m_layout.at(actual_column_index).at(row); - panel->setPosition(column * (m_panel_size+m_panel_spacing), row * (m_panel_size+m_panel_spacing)); - target.draw(*panel, states); - } - } -} - -void MusicSelect::Ribbon::draw_debug() { - if (debug) { - ImGui::Begin("Ribbon Debug", &debug); { - ImGui::SliderFloat("Time Slowdown Factor", &m_time_factor, 1.f, 10.f); - if (ImGui::CollapsingHeader("Panels")) { - auto panel_size = static_cast(m_panel_size); - if(ImGui::InputInt("Size", &panel_size)) { - if (panel_size < 0) { - panel_size = 0; - } - m_panel_size = static_cast(panel_size); - } - auto panel_spacing = static_cast(m_panel_spacing); - if(ImGui::InputInt("Spacing", &panel_spacing)) { - if (panel_spacing < 0) { - panel_spacing = 0; - } - m_panel_spacing = static_cast(panel_spacing); + bool found = false; + size_t offset = 1; + // Cycle through the whole ribbon once starting on the column next to + // the one that was just clicked, possibly wrapping around + while(offset < m_layout.size()) { + const auto& column = m_layout.at((from_column + offset) % m_layout.size()); + if (std::any_of( + column.begin(), + column.end(), + [](std::shared_ptr panel) -> bool { + return (std::dynamic_pointer_cast(panel).get() != nullptr); } + )) { + found = true; + break; + } + offset++; + } + if (found) { + // we want the next category panel to land on the same column we clicked + auto next_category_column = from_column + offset; + auto onscreen_clicked_column = (from_button_index % 4); + m_position = next_category_column - onscreen_clicked_column; + m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Right, m_time_factor); + } + } + + void Ribbon::draw(sf::RenderTarget &target, sf::RenderStates states) const { + states.transform *= getTransform(); + if (m_move_animation) { + if (not m_move_animation->ended()) { + return draw_with_animation(target, states); + } else { + m_move_animation.reset(); } } - ImGui::End(); + draw_without_animation(target, states); + } + + void Ribbon::draw_with_animation(sf::RenderTarget &target, sf::RenderStates states) const { + auto float_position = m_move_animation->get_position(); + int relative_column_zero = static_cast(std::floor(float_position)); + std::size_t column_zero = (relative_column_zero + m_layout.size()) % m_layout.size(); + + if (debug) { + if (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 + m_layout.size()) % m_layout.size(); + for (int row = 0; row < 3; row++) { + auto panel = m_layout.at(actual_column).at(row); + panel->setPosition( + (static_cast(relative_column_zero + column_offset) - float_position) * (m_panel_size+m_panel_spacing), + row * (m_panel_size+m_panel_spacing) + ); + target.draw(*panel, states); + } + } + } + + void Ribbon::draw_without_animation(sf::RenderTarget &target, sf::RenderStates states) const { + for (int column = -1; column <= 4; column++) { + int actual_column_index = (column + m_position + m_layout.size()) % m_layout.size(); + for (int row = 0; row < 3; row++) { + auto panel = m_layout.at(actual_column_index).at(row); + panel->setPosition(column * (m_panel_size+m_panel_spacing), row * (m_panel_size+m_panel_spacing)); + target.draw(*panel, states); + } + } + } + + void Ribbon::draw_debug() { + if (debug) { + ImGui::Begin("Ribbon Debug", &debug); { + ImGui::SliderFloat("Time Slowdown Factor", &m_time_factor, 1.f, 10.f); + if (ImGui::CollapsingHeader("Panels")) { + auto panel_size = static_cast(m_panel_size); + if(ImGui::InputInt("Size", &panel_size)) { + if (panel_size < 0) { + panel_size = 0; + } + m_panel_size = static_cast(panel_size); + } + auto panel_spacing = static_cast(m_panel_spacing); + if(ImGui::InputInt("Spacing", &panel_spacing)) { + if (panel_spacing < 0) { + panel_spacing = 0; + } + m_panel_spacing = static_cast(panel_spacing); + } + } + } + ImGui::End(); + } + } + + // Obligatory steps before the drawing functions can use the layout without crashing + void Ribbon::fill_layout() { + if (m_layout.empty()) { + m_layout.push_back({ + std::make_shared(m_panel_size, m_resources, sf::Color::Red, "- EMPTY -"), + std::make_shared(m_panel_size, m_resources), + std::make_shared(m_panel_size, m_resources), + }); + } + while (m_layout.size() < 4) { + m_layout.push_back({ + std::make_shared(m_panel_size, m_resources), + std::make_shared(m_panel_size, m_resources), + std::make_shared(m_panel_size, m_resources), + }); + } } } diff --git a/src/Screens/MusicSelect/Ribbon.hpp b/src/Screens/MusicSelect/Ribbon.hpp index 2bcff73..789b037 100644 --- a/src/Screens/MusicSelect/Ribbon.hpp +++ b/src/Screens/MusicSelect/Ribbon.hpp @@ -49,6 +49,7 @@ namespace MusicSelect { 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); + void fill_layout(); std::size_t get_layout_column(const std::size_t& button_index) const; std::vector,3>> m_layout; std::size_t m_position = 0;