1
0
mirror of synced 2025-02-08 22:59:41 +01:00

WIP Density Graph

This commit is contained in:
Stepland 2020-02-17 00:19:51 +01:00
parent 426e2ede47
commit 94d303cbbe
25 changed files with 416 additions and 363 deletions

View File

@ -21,6 +21,8 @@
- Artist - Artist
- Chart - Chart
- Chart List - Chart List
- Density graph
- format-agnostic chart class
- Song Panel click - Song Panel click
- difficulty cycle - difficulty cycle
- Handling Resolution changes - Handling Resolution changes
@ -29,10 +31,15 @@
## TODO ## TODO
- Add KeyMapping to preferences
### Music Select Screen ### Music Select Screen
- Top Screen Part Handling - Top Screen Part Handling
- Density graph - Density graph
- format-agnostic chart class
- async audio load
- Sound - Sound
- Black frame
- Fullscreen handling - Fullscreen handling
- Song Panel click - Song Panel click
- animation - animation
@ -59,7 +66,7 @@
## FB9 Support ## FB9 Support
## Music Select Screen ## Music Select Screen
- bound memory usage of Textures::Autoloader - bound memory usage of Toolkit::Cache
# v1.2.0 # v1.2.0

View File

@ -25,11 +25,12 @@ sources = [
'include/imgui/imgui_widgets.cpp', 'include/imgui/imgui_widgets.cpp',
'include/imgui-sfml/imgui-SFML.cpp', 'include/imgui-sfml/imgui-SFML.cpp',
'src/Main.cpp', 'src/Main.cpp',
'src/Data/Buttons.hpp',
'src/Data/Buttons.cpp',
'src/Data/Chart.hpp', 'src/Data/Chart.hpp',
'src/Data/KeyMapping.hpp', 'src/Data/KeyMapping.hpp',
'src/Data/KeyMapping.cpp', 'src/Data/KeyMapping.cpp',
'src/Data/Note.hpp', 'src/Data/Note.hpp',
'src/Data/Note.cpp',
'src/Data/Preferences.hpp', 'src/Data/Preferences.hpp',
'src/Data/Score.hpp', 'src/Data/Score.hpp',
'src/Data/SongList.hpp', 'src/Data/SongList.hpp',
@ -37,6 +38,8 @@ sources = [
# 'src/Screens/Gameplay.hpp', # 'src/Screens/Gameplay.hpp',
'src/Screens/MusicSelect/ButtonHighlight.hpp', 'src/Screens/MusicSelect/ButtonHighlight.hpp',
'src/Screens/MusicSelect/ButtonHighlight.cpp', 'src/Screens/MusicSelect/ButtonHighlight.cpp',
# 'src/Screens/MusicSelect/DensityGraph.hpp',
# 'src/Screens/MusicSelect/DensityGraph.cpp',
'src/Screens/MusicSelect/MusicSelect.hpp', 'src/Screens/MusicSelect/MusicSelect.hpp',
'src/Screens/MusicSelect/MusicSelect.cpp', 'src/Screens/MusicSelect/MusicSelect.cpp',
'src/Screens/MusicSelect/Panel.hpp', 'src/Screens/MusicSelect/Panel.hpp',
@ -48,12 +51,13 @@ sources = [
'src/Screens/MusicSelect/Ribbon.hpp', 'src/Screens/MusicSelect/Ribbon.hpp',
'src/Screens/MusicSelect/Ribbon.cpp', 'src/Screens/MusicSelect/Ribbon.cpp',
# 'src/Screens/Result.hpp', # 'src/Screens/Result.hpp',
'src/Resources/Autoloader.hpp', 'src/Resources/TextureCache.cpp',
'src/Resources/Autoloader.cpp', 'src/Resources/TextureCache.hpp',
# 'src/Resources/CoverAtlas.hpp', # 'src/Resources/CoverAtlas.hpp',
# 'src/Resources/CoverAtlas.cpp', # 'src/Resources/CoverAtlas.cpp',
'src/Toolkit/AffineTransform.hpp', 'src/Toolkit/AffineTransform.hpp',
'src/Toolkit/Debuggable.hpp', 'src/Toolkit/Debuggable.hpp',
'src/Toolkit/Cache.hpp',
'src/Toolkit/EasingFunctions.hpp', 'src/Toolkit/EasingFunctions.hpp',
'src/Toolkit/EasingFunctions.cpp', 'src/Toolkit/EasingFunctions.cpp',
'src/Toolkit/HSL.hpp', 'src/Toolkit/HSL.hpp',

31
src/Data/Buttons.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "Buttons.hpp"
namespace Data {
ButtonCoords button_to_coords(Button button) {
auto num = static_cast<std::size_t>(button);
return {num % 4, num / 4};
}
std::size_t button_to_index(Button button) {
return static_cast<std::size_t>(button);
}
std::optional<Button> coords_to_button(ButtonCoords button_coords) {
if (
button_coords.x < 4 and
button_coords.y < 4
) {
return static_cast<Button>(button_coords.x + 4*button_coords.y);
} else {
return {};
}
}
std::optional<Button> index_to_button(std::size_t index) {
if (index < 16) {
return static_cast<Button>(index);
} else {
return {};
}
}
}

35
src/Data/Buttons.hpp Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <cstddef>
#include <optional>
namespace Data {
enum class Button : std::size_t {
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
B9,
B10,
B11,
B12,
B13,
B14,
B15,
B16,
};
struct ButtonCoords {
std::size_t x;
std::size_t y;
};
ButtonCoords button_to_coords(Button button);
std::size_t button_to_index(Button button);
std::optional<Button> coords_to_button(ButtonCoords button_coords);
std::optional<Button> index_to_button(std::size_t index);
}

62
src/Data/Chart.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "Chart.hpp"
#include "../Toolkit/AffineTransform.hpp"
#include "Buttons.hpp"
namespace Data {
Chart::Chart(const stepland::memon& memon, const std::string& chart_name) {
auto it = memon.charts.find(chart_name);
if (it == memon.charts.end()) {
throw std::invalid_argument("Memon file has no "+chart_name+" chart");
}
auto [_, chart] = *it;
level = static_cast<unsigned int>(chart.level);
resolution = static_cast<std::size_t>(chart.resolution);
Toolkit::AffineTransform<float> memon_timing_to_300Hz(
0.f, static_cast<float>(chart.resolution),
-memon.offset*300.f, (-memon.offset+60.f/memon.BPM)*300.f
);
for (auto &&note : chart.notes) {
auto timing = static_cast<std::size_t>(memon_timing_to_300Hz.transform(note.get_timing()));
auto position = static_cast<Button>(note.get_pos());
auto length = static_cast<std::size_t>(note.get_length());
auto tail = convert_memon_tail(position, note.get_tail_pos());
notes.emplace(timing, position, length, tail);
}
}
Button convert_memon_tail(Button note, unsigned int tail_position) {
auto note_position = button_to_index(note);
assert((note_position >= 0 and note_position <= 15));
assert((tail_position >= 0 and tail_position <= 11));
int x = note_position%4;
int y = note_position/4;
int dx = 0;
int dy = 0;
// Vertical
if (tail_position%2 == 0) {
// Going up
if ((tail_position/2)%2 == 0) {
dy = -(tail_position/4 + 1);
// Going down
} else {
dy = tail_position/4 +1;
}
// Horizontal
} else {
// Going right
if ((tail_position/2)%2 == 0) {
dx = tail_position/4 + 1;
// Going left
} else {
dx = -(tail_position/4 + 1);
}
}
if (auto tail = coords_to_button({x+dx, y+dy})) {
return *tail;
} else {
throw std::runtime_error("Invalid tail_position : "+tail_position);
}
}
}

View File

@ -1,8 +1,19 @@
#pragma once #pragma once
#include <cstddef>
#include <set>
#include <memon.hpp>
#include "Buttons.hpp"
#include "Note.hpp"
namespace Data { namespace Data {
class Chart { struct Chart {
public: explicit Chart(const stepland::memon& memon, const std::string& chart);
Chart(/* args */); unsigned int level;
std::set<Note> notes;
std::size_t resolution;
}; };
Button convert_memon_tail(Button note, unsigned int tail_position);
} }

View File

@ -1,77 +1,58 @@
#include "KeyMapping.hpp" #include "KeyMapping.hpp"
ButtonCoords toCoords(Button button) { namespace Data {
auto num = static_cast<unsigned int>(button); KeyMapping::KeyMapping() {
return {num % 4, num / 4}; m_key_to_button[sf::Keyboard::Num1] = Button::B1;
} m_key_to_button[sf::Keyboard::Num2] = Button::B2;
m_key_to_button[sf::Keyboard::Num3] = Button::B3;
m_key_to_button[sf::Keyboard::Num4] = Button::B4;
m_key_to_button[sf::Keyboard::A] = Button::B5;
m_key_to_button[sf::Keyboard::Z] = Button::B6;
m_key_to_button[sf::Keyboard::E] = Button::B7;
m_key_to_button[sf::Keyboard::R] = Button::B8;
m_key_to_button[sf::Keyboard::Q] = Button::B9;
m_key_to_button[sf::Keyboard::S] = Button::B10;
m_key_to_button[sf::Keyboard::D] = Button::B11;
m_key_to_button[sf::Keyboard::F] = Button::B12;
m_key_to_button[sf::Keyboard::W] = Button::B13;
m_key_to_button[sf::Keyboard::X] = Button::B14;
m_key_to_button[sf::Keyboard::C] = Button::B15;
m_key_to_button[sf::Keyboard::V] = Button::B16;
unsigned int toIndex(Button button) { for (const auto& [key, button] : m_key_to_button) {
return static_cast<unsigned int>(button); m_button_to_key[button] = key;
} }
}
std::optional<Button> fromCoords(ButtonCoords button_coords) { KeyMapping::KeyMapping(std::unordered_map<Button, sf::Keyboard::Key> button_to_key) : m_button_to_key(button_to_key) {
if ( for (auto &&[button, key] : m_button_to_key) {
button_coords.x < 4 and m_key_to_button[key] = button;
button_coords.y < 4 }
) {
return static_cast<Button>(button_coords.x + 4*button_coords.y); }
} else {
return {}; void KeyMapping::set_button_to_key(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);
std::optional<Button> fromIndex(unsigned int index) { }
if (index < 16) { m_button_to_key[button] = key;
return static_cast<Button>(index); m_key_to_button[key] = button;
} else { }
return {};
} std::optional<Button> KeyMapping::key_to_button(const sf::Keyboard::Key& key) {
} if (m_key_to_button.find(key) == m_key_to_button.end()) {
return {};
KeyMapping::KeyMapping() { } else {
m_key_to_button[sf::Keyboard::Num1] = Button::B1; return m_key_to_button.at(key);
m_key_to_button[sf::Keyboard::Num2] = Button::B2; }
m_key_to_button[sf::Keyboard::Num3] = Button::B3; }
m_key_to_button[sf::Keyboard::Num4] = Button::B4;
m_key_to_button[sf::Keyboard::A] = Button::B5; std::optional<sf::Keyboard::Key> KeyMapping::button_to_key(const Button& button) {
m_key_to_button[sf::Keyboard::Z] = Button::B6; if (m_button_to_key.find(button) == m_button_to_key.end()) {
m_key_to_button[sf::Keyboard::E] = Button::B7; return {};
m_key_to_button[sf::Keyboard::R] = Button::B8; } else {
m_key_to_button[sf::Keyboard::Q] = Button::B9; return m_button_to_key.at(button);
m_key_to_button[sf::Keyboard::S] = Button::B10; }
m_key_to_button[sf::Keyboard::D] = Button::B11;
m_key_to_button[sf::Keyboard::F] = Button::B12;
m_key_to_button[sf::Keyboard::W] = Button::B13;
m_key_to_button[sf::Keyboard::X] = Button::B14;
m_key_to_button[sf::Keyboard::C] = Button::B15;
m_key_to_button[sf::Keyboard::V] = Button::B16;
for (const auto& [key, panel] : m_key_to_button) {
m_button_to_key[panel] = key;
}
}
void KeyMapping::set_button_to_key(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[button] = key;
m_key_to_button[key] = button;
}
std::optional<Button> KeyMapping::key_to_button(const sf::Keyboard::Key& key) {
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& button) {
if (m_button_to_key.find(button) == m_button_to_key.end()) {
return {};
} else {
return m_button_to_key.at(button);
} }
} }

View File

@ -5,42 +5,18 @@
#include <SFML/Window.hpp> #include <SFML/Window.hpp>
enum class Button { #include "Buttons.hpp"
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
B9,
B10,
B11,
B12,
B13,
B14,
B15,
B16,
};
struct ButtonCoords { namespace Data {
unsigned int x; class KeyMapping {
unsigned int y; public:
}; KeyMapping();
explicit KeyMapping(std::unordered_map<Button, sf::Keyboard::Key> button_to_key);
ButtonCoords toCoords(Button button); void set_button_to_key(const Button& button, const sf::Keyboard::Key& key);
unsigned int toIndex(Button button); std::optional<Button> key_to_button(const sf::Keyboard::Key& key);
std::optional<Button> fromCoords(ButtonCoords button_coords); std::optional<sf::Keyboard::Key> button_to_key(const Button& button);
std::optional<Button> fromIndex(unsigned int index); private:
std::unordered_map<sf::Keyboard::Key, Button> m_key_to_button;
class KeyMapping { std::unordered_map<Button, sf::Keyboard::Key> m_button_to_key;
public: };
KeyMapping(); }
void set_button_to_key(const Button& button, const sf::Keyboard::Key& key);
std::optional<Button> key_to_button(const sf::Keyboard::Key& key);
std::optional<sf::Keyboard::Key> button_to_key(const Button& button);
private:
std::unordered_map<sf::Keyboard::Key, Button> m_key_to_button;
std::unordered_map<Button, sf::Keyboard::Key> m_button_to_key;
};

View File

@ -1,83 +0,0 @@
#include "Note.hpp"
namespace Data {
Note::Note(
unsigned int t_position,
sf::Time t_timing,
sf::Time t_length,
unsigned int t_tail_position
) :
position(t_position),
timing(t_timing),
length(t_length),
tail_position(t_tail_position)
{
if (t_position > 15) {
throw std::out_of_range(
"Tried creating a note with invalid position : "
+std::to_string(t_position)
);
}
if (t_length.asMicroseconds() < 0) {
throw std::out_of_range(
"Tried creating a long note with negative length : "
+std::to_string(t_length.asSeconds())+"s"
);
}
if (t_length.asMicroseconds() > 0) {
if (t_tail_position > 5) {
throw std::out_of_range(
"Tried creating a long note with invalid tail position : "
+std::to_string(t_tail_position)
);
}
}
}
bool Note::operator==(const Note &rhs) const {
return timing == rhs.timing && position == rhs.position;
}
bool Note::operator!=(const Note &rhs) const {
return !(rhs == *this);
}
bool Note::operator<(const Note &rhs) const {
if (timing < rhs.timing) {
return true;
}
if (rhs.timing < timing) {
return false;
}
return position < rhs.position;
}
bool Note::operator>(const Note &rhs) const {
return rhs < *this;
}
bool Note::operator<=(const Note &rhs) const {
return !(rhs < *this);
}
bool Note::operator>=(const Note &rhs) const {
return !(*this < rhs);
}
unsigned int Note::getPosition() const {
return position;
}
const sf::Time& Note::getTiming() const {
return timing;
}
const sf::Time& Note::getLength() const {
return length;
}
unsigned int Note::getTailPosition() const {
return tail_position;
}
}

View File

@ -1,30 +1,39 @@
#pragma once #pragma once
#include <SFML/System.hpp> #include "Buttons.hpp"
namespace Data { namespace Data {
class Note { struct Note {
public: // Timing is stored as ticks on a 300Hz clock
Note(unsigned int t_position, sf::Time t_timing, sf::Time t_length, unsigned int t_tail_position); std::size_t timing;
Button position;
bool operator==(const Note &rhs) const; // zero is standard note
bool operator!=(const Note &rhs) const; std::size_t length;
bool operator<(const Note &rhs) const; Button tail;
bool operator>(const Note &rhs) const;
bool operator<=(const Note &rhs) const;
bool operator>=(const Note &rhs) const;
unsigned int getPosition() const;
const sf::Time& getTiming() const;
const sf::Time& getLength() const;
unsigned int getTailPosition() const;
private:
const unsigned int position;
const sf::Time timing;
const sf::Time length = sf::Time{};
const unsigned int tail_position = 0;
bool operator==(const Note &rhs) const {
return timing == rhs.timing && position == rhs.position;
};
bool operator!=(const Note &rhs) const {
return not(rhs == *this);
};
bool operator<(const Note &rhs) const {
if (timing < rhs.timing) {
return true;
}
if (rhs.timing < timing) {
return false;
}
return position < rhs.position;
};
bool operator>(const Note &rhs) const {
return rhs < *this;
};
bool operator<=(const Note &rhs) const {
return !(rhs < *this);
};
bool operator>=(const Note &rhs) const {
return !(*this < rhs);
};
}; };
} }

View File

@ -31,7 +31,7 @@ namespace Data {
float panel_step() const {return panel_size+panel_spacing;}; float panel_step() const {return panel_size+panel_spacing;};
float ribbon_x = 8.f / 768.f; float ribbon_x = 8.f / 768.f;
float ribbon_y = 602.f / 768.f; float ribbon_y = 602.f / 768.f;
float big_cover_size = 320.f / 768.f; float big_cover_size = 300.f / 768.f;
float big_cover_x = 0.5f; float big_cover_x = 0.5f;
float big_cover_y = 0.017f; float big_cover_y = 0.017f;
float big_level_x = 656.f / 768.f; float big_level_x = 656.f / 768.f;
@ -45,7 +45,6 @@ namespace Data {
CEREAL_NVP(panel_spacing), CEREAL_NVP(panel_spacing),
CEREAL_NVP(ribbon_x), CEREAL_NVP(ribbon_x),
CEREAL_NVP(ribbon_y), CEREAL_NVP(ribbon_y),
CEREAL_NVP(big_cover_size),
CEREAL_NVP(upper_part_height) CEREAL_NVP(upper_part_height)
); );
} }

View File

@ -32,8 +32,6 @@ namespace Data {
// Path the the audio file // Path the the audio file
std::optional<fs::path> audio; std::optional<fs::path> audio;
// Mapping from chart difficulty (BSC, ADV, EXT ...) to the numeric level, // 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::map<std::string, unsigned int, cmp_dif_name> chart_levels; std::map<std::string, unsigned int, cmp_dif_name> chart_levels;
std::optional<fs::path> full_cover_path() const; std::optional<fs::path> full_cover_path() const;

View File

@ -1,66 +0,0 @@
#include "Autoloader.hpp"
#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>
namespace Textures {
void Autoloader::load(const fs::path& path) {
if (has(path) or is_loading(path)) {
return;
}
{
std::unique_lock lock{m_is_loading_mutex};
m_is_loading.insert(path);
}
auto texture = std::make_shared<sf::Texture>();
if (!texture->loadFromFile(path.string())) {
throw std::invalid_argument("Unable to load cover image : "+path.string());
}
texture->setSmooth(true);
{
std::unique_lock<std::shared_mutex> lock_mapping{m_mapping_mutex, std::defer_lock};
std::unique_lock<std::shared_mutex> lock_is_loading{m_is_loading_mutex, std::defer_lock};
std::lock(lock_mapping, lock_is_loading);
m_mapping.emplace(path, texture);
m_is_loading.erase(path);
}
}
void Autoloader::async_load(const fs::path& path) {
std::thread t(&Autoloader::load, this, path);
t.detach();
}
std::optional<AutoloadedTexture> Autoloader::async_get(const fs::path& path) {
if (not has(path)) {
if (not is_loading(path)) {
async_load(path);
}
return {};
} else {
return get(path);
}
}
std::optional<AutoloadedTexture> Autoloader::get(const fs::path& path) {
std::shared_lock lock{m_mapping_mutex};
if (has(path)) {
return m_mapping.at(path);
} else {
return {};
}
}
bool Autoloader::has(const fs::path& path) {
std::shared_lock lock{m_mapping_mutex};
return m_mapping.find(path) != m_mapping.end();
}
bool Autoloader::is_loading(const fs::path& path) {
std::shared_lock lock{m_is_loading_mutex};
return m_is_loading.find(path) != m_is_loading.end();
}
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <ghc/filesystem.hpp>
#include <memory>
#include <optional>
#include <shared_mutex>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <SFML/Graphics.hpp>
#include <SFML/System.hpp>
namespace fs = ghc::filesystem;
// Define the way we hash fs::path for use in unordered maps
namespace std {
template <>
struct hash<fs::path> {
std::size_t operator()(const fs::path& p) const {
return std::hash<std::string>()(p.string());
}
};
}
namespace Textures {
// Hold time elapsed since loaded
struct AutoloadedTexture {
AutoloadedTexture(std::shared_ptr<sf::Texture> t_texture) : texture(t_texture), loaded_since() {};
std::shared_ptr<sf::Texture> texture;
sf::Clock loaded_since;
};
// Loads textures asynchronously (in the background) on demand and stores them in a map for easy path-based access
class Autoloader {
public:
Autoloader() = default;
// Triggers async loading and returns empty if not already loaded
std::optional<AutoloadedTexture> async_get(const fs::path& path);
// Does not trigger loading
std::optional<AutoloadedTexture> get(const fs::path& path);
void load(const fs::path& path);
void async_load(const fs::path& path);
bool has(const fs::path& path);
bool is_loading(const fs::path& path);
private:
std::unordered_map<fs::path, AutoloadedTexture> m_mapping;
std::shared_mutex m_mapping_mutex;
std::unordered_set<fs::path> m_is_loading;
std::shared_mutex m_is_loading_mutex;
};
}

View File

@ -0,0 +1,12 @@
#include "TextureCache.hpp"
namespace Textures {
AutoloadedTexture load_texture_from_path(const fs::path& path) {
auto texture = std::make_shared<sf::Texture>();
if (!texture->loadFromFile(path.string())) {
throw std::invalid_argument("Unable to load cover image : "+path.string());
}
texture->setSmooth(true);
return AutoloadedTexture(texture);
}
}

View File

@ -0,0 +1,34 @@
#pragma once
#include <memory>
#include <ghc/filesystem.hpp>
#include <SFML/Graphics.hpp>
#include "../Toolkit/Cache.hpp"
namespace fs = ghc::filesystem;
// Define the way we hash fs::path for use in unordered maps
namespace std {
template <>
struct hash<fs::path> {
std::size_t operator()(const fs::path& p) const {
return std::hash<std::string>()(p.string());
}
};
}
namespace Textures {
// Hold time elapsed since loaded
struct AutoloadedTexture {
explicit AutoloadedTexture(std::shared_ptr<sf::Texture> t_texture) : texture(t_texture), loaded_since() {};
std::shared_ptr<sf::Texture> texture;
sf::Clock loaded_since;
};
AutoloadedTexture load_texture_from_path(const fs::path& path);
using TextureCache = Toolkit::Cache<fs::path, AutoloadedTexture, &load_texture_from_path>;
}

View File

@ -10,7 +10,7 @@ namespace MusicSelect {
m_highlight.setOutlineThickness(1.f); m_highlight.setOutlineThickness(1.f);
} }
void ButtonHighlight::button_pressed(Button button) { void ButtonHighlight::button_pressed(Data::Button button) {
m_button_presses_history[button].restart(); m_button_presses_history[button].restart();
} }
@ -24,7 +24,7 @@ namespace MusicSelect {
auto it = m_button_presses_history.begin(); auto it = m_button_presses_history.begin();
while (it != m_button_presses_history.end()) { while (it != m_button_presses_history.end()) {
auto elapsed = it->second.getElapsedTime(); auto elapsed = it->second.getElapsedTime();
auto coords = toCoords(it->first); auto coords = Data::button_to_coords(it->first);
if (elapsed > sf::milliseconds(250)) { if (elapsed > sf::milliseconds(250)) {
it = m_button_presses_history.erase(it); it = m_button_presses_history.erase(it);
} else { } else {

View File

@ -5,7 +5,7 @@
#include <SFML/System.hpp> #include <SFML/System.hpp>
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include "../../Data/KeyMapping.hpp" #include "../../Data/Buttons.hpp"
#include "../../Toolkit/AffineTransform.hpp" #include "../../Toolkit/AffineTransform.hpp"
#include "SharedResources.hpp" #include "SharedResources.hpp"
@ -14,11 +14,11 @@ namespace MusicSelect {
class ButtonHighlight : public sf::Drawable, public sf::Transformable, public HoldsSharedResources { class ButtonHighlight : public sf::Drawable, public sf::Transformable, public HoldsSharedResources {
public: public:
ButtonHighlight(SharedResources& resources); ButtonHighlight(SharedResources& resources);
void button_pressed(Button button); void button_pressed(Data::Button button);
private: private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const;
mutable sf::RectangleShape m_highlight; mutable sf::RectangleShape m_highlight;
mutable std::map<Button, sf::Clock> m_button_presses_history; mutable std::map<Data::Button, sf::Clock> m_button_presses_history;
Toolkit::AffineTransform<float> m_time_to_alpha; Toolkit::AffineTransform<float> m_time_to_alpha;
}; };
} }

View File

@ -5,6 +5,7 @@
#include <iostream> #include <iostream>
#include "../../Data/Buttons.hpp"
#include "../../Data/KeyMapping.hpp" #include "../../Data/KeyMapping.hpp"
MusicSelect::Screen::Screen(const Data::SongList& t_song_list, Data::Preferences& t_preferences) : MusicSelect::Screen::Screen(const Data::SongList& t_song_list, Data::Preferences& t_preferences) :
@ -104,18 +105,18 @@ void MusicSelect::Screen::handle_mouse_click(const sf::Event::MouseButtonEvent&
*/ */
} }
void MusicSelect::Screen::press_button(const Button& button) { void MusicSelect::Screen::press_button(const Data::Button& button) {
button_highlight.button_pressed(button); button_highlight.button_pressed(button);
auto button_index = toIndex(button); auto button_index = Data::button_to_index(button);
if (button_index < 12) { if (button_index < 12) {
ribbon.click_on(button_index); ribbon.click_on(button_index);
// ribbon.at(button_index)->click(ribbon); // ribbon.at(button_index)->click(ribbon);
} else { } else {
switch (button) { switch (button) {
case Button::B13: case Data::Button::B13:
ribbon.move_left(); ribbon.move_left();
break; break;
case Button::B14: case Data::Button::B14:
ribbon.move_right(); ribbon.move_right();
default: default:
break; break;

View File

@ -35,12 +35,12 @@ namespace MusicSelect {
std::optional<std::reference_wrapper<SongPanel>> selected_panel; std::optional<std::reference_wrapper<SongPanel>> selected_panel;
ButtonHighlight button_highlight; ButtonHighlight button_highlight;
KeyMapping key_mapping; Data::KeyMapping key_mapping;
// converts a key press into a button press // converts a key press into a button press
void handle_key_press(const sf::Event::KeyEvent& key_event); void handle_key_press(const sf::Event::KeyEvent& key_event);
// converts a mouse click into a button press // converts a mouse click into a button press
void handle_mouse_click(const sf::Event::MouseButtonEvent& mouse_button_event); void handle_mouse_click(const sf::Event::MouseButtonEvent& mouse_button_event);
// chooses what happens for each button // chooses what happens for each button
void press_button(const Button& button); void press_button(const Data::Button& button);
}; };
} }

View File

@ -108,9 +108,9 @@ namespace MusicSelect {
void SongPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const { void SongPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const {
states.transform *= getTransform(); states.transform *= getTransform();
auto selected_chart = m_resources.get_last_selected_chart(); auto last_selected_chart = m_resources.get_last_selected_chart();
// We should gray out the panel if the currently selected difficulty doesn't exist for this song // We should gray out the panel if the currently selected difficulty doesn't exist for this song
bool should_be_grayed_out = m_song.chart_levels.find(selected_chart) == m_song.chart_levels.end(); bool should_be_grayed_out = m_song.chart_levels.find(last_selected_chart) == m_song.chart_levels.end();
if (m_song.cover) { if (m_song.cover) {
auto loaded_texture = m_resources.covers.async_get(m_song.folder/m_song.cover.value()); auto loaded_texture = m_resources.covers.async_get(m_song.folder/m_song.cover.value());
if (loaded_texture) { if (loaded_texture) {
@ -135,12 +135,12 @@ namespace MusicSelect {
chart_dif_badge.setFillColor(sf::Color(128,128,128)); chart_dif_badge.setFillColor(sf::Color(128,128,128));
} else { } else {
chart_dif_badge.setFillColor( chart_dif_badge.setFillColor(
m_resources.get_chart_color(selected_chart) m_resources.get_chart_color(last_selected_chart)
); );
} }
target.draw(chart_dif_badge, states); target.draw(chart_dif_badge, states);
if (not should_be_grayed_out) { if (not should_be_grayed_out) {
auto dif = m_song.chart_levels.at(selected_chart); auto dif = m_song.chart_levels.at(last_selected_chart);
sf::Text dif_label{ sf::Text dif_label{
std::to_string(dif), std::to_string(dif),
m_resources.noto_sans_medium, m_resources.noto_sans_medium,

View File

@ -6,7 +6,7 @@
#include <SFML/Graphics.hpp> #include <SFML/Graphics.hpp>
#include <SFML/System.hpp> #include <SFML/System.hpp>
#include "../../Resources/Autoloader.hpp" #include "../../Resources/TextureCache.hpp"
#include "../../Data/Preferences.hpp" #include "../../Data/Preferences.hpp"
#include "../../Data/SongList.hpp" #include "../../Data/SongList.hpp"
@ -27,7 +27,7 @@ namespace MusicSelect {
Data::Preferences& preferences; Data::Preferences& preferences;
Textures::Autoloader covers; Textures::TextureCache covers;
sf::Texture fallback_cover; sf::Texture fallback_cover;
sf::Font noto_sans_medium; sf::Font noto_sans_medium;

View File

@ -64,6 +64,7 @@ namespace MusicSelect {
draw_song_title(target, states); draw_song_title(target, states);
draw_big_level(target, states); draw_big_level(target, states);
draw_chart_list(target, states); draw_chart_list(target, states);
draw_density_graph(target, states);
} }
void SongInfo::draw_song_title(sf::RenderTarget& target, sf::RenderStates states) const { void SongInfo::draw_song_title(sf::RenderTarget& target, sf::RenderStates states) const {
@ -214,4 +215,20 @@ namespace MusicSelect {
dif_index++; dif_index++;
} }
} }
void SongInfo::draw_density_graph(sf::RenderTarget& target, sf::RenderStates states) const {
sf::RectangleShape line{{get_screen_width()*1.1f,2.f/768.f*get_screen_width()}};
Toolkit::set_origin_normalized(line, 0.5f, 0.5f);
line.setFillColor(sf::Color::White);
line.setPosition(get_screen_width()*0.5f,425.f/768.f*get_screen_width());
target.draw(line, 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;
}
}
} }

View File

@ -28,6 +28,7 @@ namespace MusicSelect {
void draw_song_title(sf::RenderTarget& target, sf::RenderStates states) const; void draw_song_title(sf::RenderTarget& target, sf::RenderStates states) const;
void draw_big_level(sf::RenderTarget& target, sf::RenderStates states) const; void draw_big_level(sf::RenderTarget& target, sf::RenderStates states) const;
void draw_chart_list(sf::RenderTarget& target, sf::RenderStates states) const; void draw_chart_list(sf::RenderTarget& target, sf::RenderStates states) const;
void draw_density_graph(sf::RenderTarget& target, sf::RenderStates states) const;
mutable BigCover m_big_cover; mutable BigCover m_big_cover;
const Toolkit::AffineTransform<float> m_seconds_to_badge_anim{0.f, 0.15f, 0.f, 1.f}; const Toolkit::AffineTransform<float> m_seconds_to_badge_anim{0.f, 0.15f, 0.f, 1.f};
}; };

78
src/Toolkit/Cache.hpp Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include <mutex>
#include <thread>
#include <optional>
#include <shared_mutex>
#include <unordered_map>
#include <unordered_set>
namespace Toolkit {
// Loads resources (audio, textures, etc ...) asynchronously in another thread and stores them for later reuse
template <class Key, class Value, Value(*load_resource)(const Key&)>
class Cache {
public:
Cache() = default;
// Triggers async loading and returns empty if not already loaded
std::optional<Value> async_get(const Key& key) {
if (not has(key)) {
if (not is_loading(key)) {
async_load(key);
}
return {};
} else {
return get(key);
}
}
// Does not trigger loading
std::optional<Value> get(const Key& key) {
std::shared_lock lock{m_mapping_mutex};
if (has(key)) {
return m_mapping.at(key);
} else {
return {};
}
}
void load(const Key& key) {
if (has(key) or is_loading(key)) {
return;
}
{
std::unique_lock lock{m_is_loading_mutex};
m_is_loading.insert(key);
}
Value resource = load_resource(key);
{
std::unique_lock<std::shared_mutex> lock_mapping{m_mapping_mutex, std::defer_lock};
std::unique_lock<std::shared_mutex> lock_is_loading{m_is_loading_mutex, std::defer_lock};
std::lock(lock_mapping, lock_is_loading);
m_mapping.emplace(key, resource);
m_is_loading.erase(key);
}
}
void async_load(const Key& key) {
std::thread t(&Cache::load, this, key);
t.detach();
}
bool has(const Key& key) {
std::shared_lock lock{m_mapping_mutex};
return m_mapping.find(key) != m_mapping.end();
}
bool is_loading(const Key& key) {
std::shared_lock lock{m_is_loading_mutex};
return m_is_loading.find(key) != m_is_loading.end();
}
private:
std::unordered_map<Key, Value> m_mapping;
std::shared_mutex m_mapping_mutex;
std::unordered_set<Key> m_is_loading;
std::shared_mutex m_is_loading_mutex;
};
}