WIP Density Graph
This commit is contained in:
parent
426e2ede47
commit
94d303cbbe
9
TODO.md
9
TODO.md
@ -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
|
||||
|
||||
|
10
meson.build
10
meson.build
@ -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
31
src/Data/Buttons.cpp
Normal 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
35
src/Data/Buttons.hpp
Normal 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
62
src/Data/Chart.cpp
Normal 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 &¬e : 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
||||
};
|
||||
}
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
||||
}
|
12
src/Resources/TextureCache.cpp
Normal file
12
src/Resources/TextureCache.cpp
Normal 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);
|
||||
}
|
||||
}
|
34
src/Resources/TextureCache.hpp
Normal file
34
src/Resources/TextureCache.hpp
Normal 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>;
|
||||
}
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
78
src/Toolkit/Cache.hpp
Normal 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;
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user