1
0
mirror of synced 2024-11-14 18:47: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
- Chart
- Chart List
- Density graph
- format-agnostic chart class
- Song Panel click
- difficulty cycle
- Handling Resolution changes
@ -29,10 +31,15 @@
## TODO
- Add KeyMapping to preferences
### Music Select Screen
- Top Screen Part Handling
- Density graph
- format-agnostic chart class
- async audio load
- Sound
- Black frame
- Fullscreen handling
- Song Panel click
- animation
@ -59,7 +66,7 @@
## FB9 Support
## Music Select Screen
- bound memory usage of Textures::Autoloader
- bound memory usage of Toolkit::Cache
# v1.2.0

View File

@ -25,11 +25,12 @@ sources = [
'include/imgui/imgui_widgets.cpp',
'include/imgui-sfml/imgui-SFML.cpp',
'src/Main.cpp',
'src/Data/Buttons.hpp',
'src/Data/Buttons.cpp',
'src/Data/Chart.hpp',
'src/Data/KeyMapping.hpp',
'src/Data/KeyMapping.cpp',
'src/Data/Note.hpp',
'src/Data/Note.cpp',
'src/Data/Preferences.hpp',
'src/Data/Score.hpp',
'src/Data/SongList.hpp',
@ -37,6 +38,8 @@ sources = [
# 'src/Screens/Gameplay.hpp',
'src/Screens/MusicSelect/ButtonHighlight.hpp',
'src/Screens/MusicSelect/ButtonHighlight.cpp',
# 'src/Screens/MusicSelect/DensityGraph.hpp',
# 'src/Screens/MusicSelect/DensityGraph.cpp',
'src/Screens/MusicSelect/MusicSelect.hpp',
'src/Screens/MusicSelect/MusicSelect.cpp',
'src/Screens/MusicSelect/Panel.hpp',
@ -48,12 +51,13 @@ sources = [
'src/Screens/MusicSelect/Ribbon.hpp',
'src/Screens/MusicSelect/Ribbon.cpp',
# 'src/Screens/Result.hpp',
'src/Resources/Autoloader.hpp',
'src/Resources/Autoloader.cpp',
'src/Resources/TextureCache.cpp',
'src/Resources/TextureCache.hpp',
# 'src/Resources/CoverAtlas.hpp',
# 'src/Resources/CoverAtlas.cpp',
'src/Toolkit/AffineTransform.hpp',
'src/Toolkit/Debuggable.hpp',
'src/Toolkit/Cache.hpp',
'src/Toolkit/EasingFunctions.hpp',
'src/Toolkit/EasingFunctions.cpp',
'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
#include <cstddef>
#include <set>
#include <memon.hpp>
#include "Buttons.hpp"
#include "Note.hpp"
namespace Data {
class Chart {
public:
Chart(/* args */);
struct Chart {
explicit Chart(const stepland::memon& memon, const std::string& chart);
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"
ButtonCoords toCoords(Button button) {
auto num = static_cast<unsigned int>(button);
return {num % 4, num / 4};
}
namespace Data {
KeyMapping::KeyMapping() {
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) {
return static_cast<unsigned int>(button);
}
for (const auto& [key, button] : m_key_to_button) {
m_button_to_key[button] = key;
}
}
std::optional<Button> fromCoords(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> fromIndex(unsigned int index) {
if (index < 16) {
return static_cast<Button>(index);
} else {
return {};
}
}
KeyMapping::KeyMapping() {
m_key_to_button[sf::Keyboard::Num1] = Button::B1;
m_key_to_button[sf::Keyboard::Num2] = Button::B2;
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;
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);
KeyMapping::KeyMapping(std::unordered_map<Button, sf::Keyboard::Key> button_to_key) : m_button_to_key(button_to_key) {
for (auto &&[button, key] : m_button_to_key) {
m_key_to_button[key] = button;
}
}
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>
enum class Button {
B1,
B2,
B3,
B4,
B5,
B6,
B7,
B8,
B9,
B10,
B11,
B12,
B13,
B14,
B15,
B16,
};
#include "Buttons.hpp"
struct ButtonCoords {
unsigned int x;
unsigned int y;
};
ButtonCoords toCoords(Button button);
unsigned int toIndex(Button button);
std::optional<Button> fromCoords(ButtonCoords button_coords);
std::optional<Button> fromIndex(unsigned int index);
class KeyMapping {
public:
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;
};
namespace Data {
class KeyMapping {
public:
KeyMapping();
explicit KeyMapping(std::unordered_map<Button, sf::Keyboard::Key> button_to_key);
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
#include <SFML/System.hpp>
#include "Buttons.hpp"
namespace Data {
class Note {
public:
Note(unsigned int t_position, sf::Time t_timing, sf::Time t_length, unsigned int t_tail_position);
bool operator==(const Note &rhs) const;
bool operator!=(const Note &rhs) const;
bool operator<(const Note &rhs) const;
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;
struct Note {
// Timing is stored as ticks on a 300Hz clock
std::size_t timing;
Button position;
// zero is standard note
std::size_t length;
Button tail;
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 ribbon_x = 8.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_y = 0.017f;
float big_level_x = 656.f / 768.f;
@ -45,7 +45,6 @@ namespace Data {
CEREAL_NVP(panel_spacing),
CEREAL_NVP(ribbon_x),
CEREAL_NVP(ribbon_y),
CEREAL_NVP(big_cover_size),
CEREAL_NVP(upper_part_height)
);
}

View File

@ -32,8 +32,6 @@ namespace Data {
// Path the the audio file
std::optional<fs::path> audio;
// 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::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);
}
void ButtonHighlight::button_pressed(Button button) {
void ButtonHighlight::button_pressed(Data::Button button) {
m_button_presses_history[button].restart();
}
@ -24,7 +24,7 @@ namespace MusicSelect {
auto it = m_button_presses_history.begin();
while (it != m_button_presses_history.end()) {
auto elapsed = it->second.getElapsedTime();
auto coords = toCoords(it->first);
auto coords = Data::button_to_coords(it->first);
if (elapsed > sf::milliseconds(250)) {
it = m_button_presses_history.erase(it);
} else {

View File

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

View File

@ -5,6 +5,7 @@
#include <iostream>
#include "../../Data/Buttons.hpp"
#include "../../Data/KeyMapping.hpp"
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);
auto button_index = toIndex(button);
auto button_index = Data::button_to_index(button);
if (button_index < 12) {
ribbon.click_on(button_index);
// ribbon.at(button_index)->click(ribbon);
} else {
switch (button) {
case Button::B13:
case Data::Button::B13:
ribbon.move_left();
break;
case Button::B14:
case Data::Button::B14:
ribbon.move_right();
default:
break;

View File

@ -35,12 +35,12 @@ namespace MusicSelect {
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
ButtonHighlight button_highlight;
KeyMapping key_mapping;
Data::KeyMapping key_mapping;
// converts a key press into a button press
void handle_key_press(const sf::Event::KeyEvent& key_event);
// converts a mouse click into a button press
void handle_mouse_click(const sf::Event::MouseButtonEvent& mouse_button_event);
// chooses what happens for each button
void press_button(const Button& button);
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 {
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
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) {
auto loaded_texture = m_resources.covers.async_get(m_song.folder/m_song.cover.value());
if (loaded_texture) {
@ -135,12 +135,12 @@ namespace MusicSelect {
chart_dif_badge.setFillColor(sf::Color(128,128,128));
} else {
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);
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{
std::to_string(dif),
m_resources.noto_sans_medium,

View File

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

View File

@ -64,6 +64,7 @@ namespace MusicSelect {
draw_song_title(target, states);
draw_big_level(target, states);
draw_chart_list(target, states);
draw_density_graph(target, states);
}
void SongInfo::draw_song_title(sf::RenderTarget& target, sf::RenderStates states) const {
@ -214,4 +215,20 @@ namespace MusicSelect {
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_big_level(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;
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;
};
}