1
0
mirror of synced 2024-11-12 01:40:47 +01:00

Show song cover on HUD

This commit is contained in:
Stepland 2020-02-15 16:52:45 +01:00
parent 3dd50d463b
commit c61b470f55
16 changed files with 247 additions and 52 deletions

View File

@ -41,8 +41,10 @@ sources = [
'src/Screens/MusicSelect/MusicSelect.cpp',
'src/Screens/MusicSelect/Panel.hpp',
'src/Screens/MusicSelect/Panel.cpp',
'src/Screens/MusicSelect/Resources.hpp',
'src/Screens/MusicSelect/Resources.cpp',
'src/Screens/MusicSelect/SharedResources.hpp',
'src/Screens/MusicSelect/SharedResources.cpp',
'src/Screens/MusicSelect/SongInfo.hpp',
'src/Screens/MusicSelect/SongInfo.cpp',
'src/Screens/MusicSelect/Ribbon.hpp',
'src/Screens/MusicSelect/Ribbon.cpp',
# 'src/Screens/Result.hpp',

View File

@ -8,7 +8,8 @@
#include <SFML/System.hpp>
namespace Data {
// By convention all axis-independant lengths are expressed as a ratio of the screen WIDTH (see panel_size and spacing)
// By convention all axis-independant lengths are expressed as a ratio of the screen WIDTH
// see panel_size and panel_spacing for example
struct Screen {
std::size_t width = 768;
std::size_t height = 1360;
@ -47,35 +48,43 @@ namespace Data {
Layout layout;
Preferences() : screen(), layout() {
load();
}
~Preferences() {
save();
}
void load() {
auto path = std::filesystem::path("data/preferences.json");
if (std::filesystem::exists(path)) {
std::ifstream prefs_file;
prefs_file.open(path);
try {
cereal::JSONInputArchive archive{prefs_file};
this->serialize(archive);
serialize(archive);
} catch(const std::exception& e) {
std::cerr << "Error while loading data/preferences.json : " << e.what() << std::endl;
std::cerr << "Using fallback preferences instead" << std::endl;
}
}
}
~Preferences() {
};
void save() {
auto data_folder = std::filesystem::path("data");
if (not std::filesystem::exists(data_folder)) {
std::filesystem::create_directory(data_folder);
}
if (not std::filesystem::is_directory(data_folder)) {
std::cerr << "Could not save preferences : can't create data folder, a file named 'data' exists" << std::endl;
std::cerr << "Can't create data folder to save preferences, a file named 'data' exists" << std::endl;
}
std::ofstream preferences_file;
preferences_file.open(data_folder / "preferences.json", std::ofstream::trunc | std::ofstream::out);
{
cereal::JSONOutputArchive archive{preferences_file};
serialize(archive);
}
}
}
};
template<class Archive>
void serialize(Archive & archive) {

View File

@ -11,6 +11,22 @@ namespace fs = std::filesystem;
namespace Data {
bool cmp_dif_name::operator()(const std::string &a, const std::string &b) const {
if (dif_names.find(a) != dif_names.end()) {
if (dif_names.find(b) != dif_names.end()) {
return dif_names.find(a)->second < dif_names.find(b)->second;
} else {
return true;
}
} else {
if (dif_names.find(b) != dif_names.end()) {
return false;
} else {
return a < b;
}
}
}
std::optional<fs::path> Song::full_cover_path() const {
if (cover) {
return folder/cover.value();

View File

@ -3,15 +3,23 @@
#include <filesystem>
#include <iterator>
#include <list>
#include <map>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
namespace fs = std::filesystem;
namespace Data {
/*
* Difficulty name ordering : BSC > ADV > EXT > anything else in lexicographical order
*/
struct cmp_dif_name {
std::map<std::string,int> dif_names = {{"BSC",1},{"ADV",2},{"EXT",3}};
bool operator()(const std::string& a, const std::string& b) const;
};
// Basic metadata about a song
struct Song {
Song() = default;
@ -26,7 +34,7 @@ namespace Data {
// Mapping from chart difficulty (BSC, ADV, EXT ...) to the numeric level,
// the level is stored multiplied by 10 and displayed divided by 10
// to allow for decimal levels (introduced in jubeat ... festo ?)
std::unordered_map<std::string, unsigned int> chart_levels;
std::map<std::string, unsigned int, cmp_dif_name> chart_levels;
std::optional<fs::path> full_cover_path() const;

View File

@ -28,9 +28,9 @@ namespace Textures {
// Hold time elapsed since loaded
struct AutoloadedTexture {
AutoloadedTexture(std::shared_ptr<sf::Texture> t_texture) : texture(t_texture), clock() {};
AutoloadedTexture(std::shared_ptr<sf::Texture> t_texture) : texture(t_texture), loaded_since() {};
std::shared_ptr<sf::Texture> texture;
sf::Clock clock;
sf::Clock loaded_since;
};
// Loads textures asynchronously (in the background) on demand and stores them in a map for easy path-based access

View File

@ -11,6 +11,7 @@ MusicSelect::Screen::Screen(const Data::SongList& t_song_list) :
song_list(t_song_list),
resources(),
ribbon(resources, m_panel_size, m_panel_spacing),
song_info(resources, m_upper_part_width, m_upper_part_height),
selected_panel(),
button_highlight(m_panel_size, m_panel_spacing),
key_mapping()
@ -47,17 +48,20 @@ void MusicSelect::Screen::select_chart(sf::RenderWindow& window) {
break;
case sf::Event::MouseButtonPressed:
handle_mouse_click(event.mouseButton);
break;
case sf::Event::Closed:
window.close();
break;
default:
break;
}
}
ImGui::SFML::Update(window, imguiClock.restart());
window.clear(sf::Color(252, 201, 0, 255));
window.clear(sf::Color::Black);
ribbon.draw_debug();
window.draw(ribbon);
window.draw(button_highlight);
window.draw(song_info);
ImGui::SFML::Render(window);
window.display();
}

View File

@ -9,7 +9,8 @@
#include "../../Data/KeyMapping.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "Ribbon.hpp"
#include "Resources.hpp"
#include "SongInfo.hpp"
#include "SharedResources.hpp"
#include "ButtonHighlight.hpp"
namespace MusicSelect {
@ -23,15 +24,18 @@ namespace MusicSelect {
private:
// Data
const Data::SongList& song_list;
const Data::SongList song_list;
float m_panel_size = 160.0f;
float m_panel_spacing = 112.0f / 3.0f;
float m_upper_part_width = 768.0f;
float m_upper_part_height = 464.0f;
// Resources
Resources resources;
SharedResources resources;
// State
Ribbon ribbon;
SongInfo song_info;
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
ButtonHighlight button_highlight;

View File

@ -1,8 +1,11 @@
#include "Panel.hpp"
#include <functional>
#include <SFML/Graphics.hpp>
#include "MusicSelect.hpp"
#include "SharedResources.hpp"
namespace MusicSelect {
void ColorPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const {
@ -64,7 +67,41 @@ namespace MusicSelect {
}
void SongPanel::click(Ribbon& ribbon, std::size_t from_button_index) {
if (selected_chart.has_value()) {
// The song was already selected : look for the next chart in order
auto it = m_song.chart_levels.upper_bound(*selected_chart);
if (it != m_song.chart_levels.cend()) {
selected_chart = it->first;
} else {
selected_chart = m_song.chart_levels.cbegin()->first;
}
} else {
// The song was not selected before : unselect the last one
if (m_resources.selected_panel.has_value()) {
m_resources.selected_panel->panel.unselect();
}
// Look for the first chart with dif greater or equal to the last select one
// or else select the first chart
auto it = m_song.chart_levels.lower_bound(ribbon.m_global_chart_dif);
if (it != m_song.chart_levels.cend()) {
selected_chart = it->first;
} else {
selected_chart = m_song.chart_levels.cbegin()->first;
}
}
m_resources.selected_panel.emplace(TimedSelectedPanel{*this});
}
void SongPanel::unselect() {
selected_chart.reset();
}
std::optional<ChartSelection> SongPanel::get_selected_chart() const {
if (selected_chart) {
return ChartSelection{m_song, *selected_chart};
} else {
return {};
}
}
void SongPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const {
@ -75,7 +112,7 @@ namespace MusicSelect {
sf::Sprite cover{*(loaded_texture->texture)};
auto alpha = static_cast<std::uint8_t>(
m_seconds_to_alpha.clampedTransform(
loaded_texture->clock.getElapsedTime().asSeconds()
loaded_texture->loaded_since.getElapsedTime().asSeconds()
)
);
cover.setColor(sf::Color(255, 255, 255, alpha));
@ -89,7 +126,7 @@ namespace MusicSelect {
song_title.setFont(m_resources.noto_sans_medium);
song_title.setString(m_song.title);
song_title.setCharacterSize(static_cast<unsigned int>(0.06875f*m_size));
song_title.setFillColor(sf::Color::Black);
song_title.setFillColor(sf::Color::White);
auto song_title_bounds = song_title.getLocalBounds();
// text is too long : scale it
if (song_title_bounds.width > 0.88f * m_size) {
@ -97,7 +134,6 @@ namespace MusicSelect {
}
song_title.setPosition(18.f/160.f,9.f/160.f);
target.draw(song_title, states);
}
void set_to_global_bounds(sf::RectangleShape& rect, const sf::Text& text) {
@ -130,4 +166,4 @@ namespace MusicSelect {
message.setPosition(m_size*0.5f, m_size*0.5f);
target.draw(message, states);
}
}
}

View File

@ -1,27 +1,31 @@
#pragma once
#include <optional>
#include <tuple>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include "../../Data/SongList.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "Resources.hpp"
#include "SharedResources.hpp"
namespace MusicSelect {
class Ribbon;
class SharedResources;
// A Panel holds anything that can go under a button on the moving part
// of the music select screen, be it nothing, a category indicator, or a song
class Panel : public sf::Drawable, public sf::Transformable {
public:
Panel(const float& size, Resources& resources) : m_size(size), m_resources(resources) {};
Panel(const float& size, SharedResources& resources) : m_size(size), m_resources(resources) {};
// What happens when you click on the panel
virtual void click(Ribbon& ribbon, std::size_t from_button_index) = 0;
virtual ~Panel() = default;
protected:
const float& m_size;
Resources& m_resources;
SharedResources& m_resources;
};
class EmptyPanel final : public Panel {
@ -34,7 +38,7 @@ namespace MusicSelect {
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) {};
ColoredMessagePanel(const float& size, SharedResources& 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;
@ -44,7 +48,7 @@ namespace MusicSelect {
class ColorPanel final : public Panel {
public:
ColorPanel(const float& size, Resources& resources, const sf::Color& t_color) : Panel(size, resources), m_color(t_color) {};
ColorPanel(const float& size, SharedResources& resources, const sf::Color& t_color) : Panel(size, resources), m_color(t_color) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override {return;};
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
@ -53,22 +57,37 @@ namespace MusicSelect {
class CategoryPanel final : public Panel {
public:
explicit CategoryPanel(const float& size, Resources& resources, const std::string& t_label) : Panel(size, resources), m_label(t_label) {};
explicit CategoryPanel(const float& size, SharedResources& resources, const std::string& t_label) : Panel(size, resources), m_label(t_label) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::string m_label;
};
class SongPanel final : public Panel {
struct ChartSelection {
const Data::Song& song;
const std::string& chart;
};
class SelectablePanel : public Panel {
public:
explicit SongPanel(const float& size, Resources& resources, const Data::Song& t_song) : Panel(size, resources), m_song(t_song) {};
using Panel::Panel;
virtual ~SelectablePanel() = default;
virtual void unselect() = 0;
virtual std::optional<ChartSelection> get_selected_chart() const = 0;
};
class SongPanel final : public SelectablePanel {
public:
explicit SongPanel(const float& size, SharedResources& resources, const Data::Song& t_song) : SelectablePanel(size, resources), m_song(t_song) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override;
void unselect() override;
std::optional<ChartSelection> get_selected_chart() const override;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
const Data::Song& m_song;
const Toolkit::AffineTransform<float> m_seconds_to_alpha{0.0f, 0.15f, 0.f, 255.f};
std::optional<std::reference_wrapper<std::string>> selected_chart;
std::optional<std::string> selected_chart;
};
void set_to_global_bounds(sf::RectangleShape& rect, const sf::Text& text);

View File

@ -1,14 +0,0 @@
#pragma once
#include <SFML/Graphics.hpp>
#include "../../Resources/Autoloader.hpp"
namespace MusicSelect {
struct Resources {
Resources();
Textures::Autoloader covers;
sf::Texture fallback_cover;
sf::Font noto_sans_medium;
};
}

View File

@ -64,7 +64,7 @@ namespace MusicSelect {
return clock.getElapsedTime() / m_time_factor > sf::milliseconds(300);
}
Ribbon::Ribbon(Resources& t_resources, float& panel_size, float& panel_spacing) :
Ribbon::Ribbon(SharedResources& t_resources, float& panel_size, float& panel_spacing) :
m_layout(),
m_move_animation(),
m_resources(t_resources),

View File

@ -3,11 +3,12 @@
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Transformable.hpp>
#include "Panel.hpp"
#include "../../Data/SongList.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "../../Toolkit/Debuggable.hpp"
#include "../../Toolkit/EasingFunctions.hpp"
#include "Panel.hpp"
#include "SharedResources.hpp"
namespace MusicSelect {
@ -33,7 +34,7 @@ namespace MusicSelect {
// It can be sorted in a number of ways
class Ribbon final : public sf::Drawable, public sf::Transformable, public Toolkit::Debuggable {
public:
Ribbon(Resources& t_resources, float& panel_size, float& panel_spacing);
Ribbon(SharedResources& t_resources, float& panel_size, float& panel_spacing);
void title_sort(const Data::SongList& song_list);
void test_sort();
void test2_sort();
@ -44,6 +45,7 @@ namespace MusicSelect {
void move_left();
void move_to_next_category(const std::size_t& from_button_index);
void draw_debug() override;
std::string m_global_chart_dif;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
void draw_with_animation(sf::RenderTarget& target, sf::RenderStates states) const;
@ -54,7 +56,7 @@ namespace MusicSelect {
std::vector<std::array<std::shared_ptr<Panel>,3>> m_layout;
std::size_t m_position = 0;
mutable std::optional<MoveAnimation> m_move_animation;
Resources& m_resources;
SharedResources& m_resources;
float m_time_factor = 1.f;
Data::Song empty_song;
float& m_panel_size;

View File

@ -1,8 +1,8 @@
#include "Resources.hpp"
#include "SharedResources.hpp"
#include <iostream>
MusicSelect::Resources::Resources() :
MusicSelect::SharedResources::SharedResources() :
covers(),
fallback_cover(),
noto_sans_medium()

View File

@ -0,0 +1,31 @@
#pragma once
#include <cstddef>
#include <optional>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "../../Resources/Autoloader.hpp"
#include "../../Data/SongList.hpp"
#include "Panel.hpp"
namespace MusicSelect {
class SelectablePanel;
struct TimedSelectedPanel {
TimedSelectedPanel(SelectablePanel& s) : panel(s), selected_since() {};
SelectablePanel& panel;
sf::Clock selected_since;
};
struct SharedResources {
SharedResources();
Textures::Autoloader covers;
sf::Texture fallback_cover;
sf::Font noto_sans_medium;
std::optional<TimedSelectedPanel> selected_panel;
};
}

View File

@ -0,0 +1,54 @@
#include "SongInfo.hpp"
#include <SFML/Graphics.hpp>
namespace MusicSelect {
SongInfo::SongInfo(SharedResources& resources, const float& width, const float& height) :
m_resources(resources),
m_width(width),
m_height(height),
m_cover_fallback({height*0.8f, height*0.8f})
{
m_cover_fallback.setFillColor(sf::Color::Black);
m_cover_fallback.setOutlineThickness(1.f);
m_cover_fallback.setOutlineColor(sf::Color::White);
m_cover_fallback.setPosition(height*0.1f, height*0.1f);
}
void SongInfo::draw(sf::RenderTarget& target, sf::RenderStates states) const {
draw_cover(target, states);
}
}
void MusicSelect::SongInfo::draw_cover(sf::RenderTarget& target, sf::RenderStates states) const {
states.transform *= getTransform();
target.draw(m_cover_fallback, states);
auto selected_panel = m_resources.selected_panel;
if (not selected_panel.has_value()) {
return;
}
auto selected_chart = selected_panel->panel.get_selected_chart();
if (not selected_chart.has_value()) {
return;
}
auto cover_path = selected_chart->song.full_cover_path();
if (not cover_path.has_value()) {
return;
}
auto cover_texture = m_resources.covers.async_get(*cover_path);
if (not cover_texture.has_value()) {
return;
}
sf::Sprite cover{*(cover_texture->texture)};
auto bounds = cover.getGlobalBounds();
cover.setScale(m_height*0.8f/bounds.width, m_height*0.8f/bounds.height);
cover.setPosition(m_height*0.1f, m_height*0.1f);
auto alpha = static_cast<std::uint8_t>(
m_seconds_to_alpha.clampedTransform(
selected_panel->selected_since.getElapsedTime().asSeconds()
)
);
cover.setColor(sf::Color(255, 255, 255, alpha));
target.draw(cover, states);
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
#include "../../Toolkit/AffineTransform.hpp"
#include "SharedResources.hpp"
namespace MusicSelect {
// SongInfo displays the song info on the top part of the screen
class SongInfo : public sf::Drawable, public sf::Transformable {
public:
SongInfo(SharedResources& resources, const float& width, const float& height);
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
void draw_cover(sf::RenderTarget& target, sf::RenderStates states) const;
SharedResources& m_resources;
const float& m_width;
const float& m_height;
sf::RectangleShape m_cover_fallback;
const Toolkit::AffineTransform<float> m_seconds_to_alpha{0.0f, 0.3f, 0.f, 255.f};
};
}