More Music Select Preparation
This commit is contained in:
parent
6a4f704856
commit
a111820c9d
13
meson.build
13
meson.build
@ -15,20 +15,23 @@ filesystem = cpp.find_library('stdc++fs')
|
||||
|
||||
sources = [
|
||||
'src/Main.cpp',
|
||||
'src/Data/Chart.hpp',
|
||||
'src/Data/Note.hpp',
|
||||
'src/Data/Note.cpp',
|
||||
'srx/Data/Score.hpp',
|
||||
'src/Data/SongList.hpp',
|
||||
'src/Data/SongList.cpp',
|
||||
'src/Screens/Gameplay.hpp',
|
||||
'src/Screens/MusicSelect.hpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.hpp',
|
||||
'src/Screens/MusicSelect/MusicSelect.cpp',
|
||||
'src/Screens/Result.hpp',
|
||||
'src/Resources/CoverAtlas.hpp',
|
||||
'src/Resources/CoverAtlas.cpp',
|
||||
]
|
||||
|
||||
executable(
|
||||
'jujube',
|
||||
'src/Test.cpp',
|
||||
'src/Textures/CoverPack.hpp',
|
||||
'src/Textures/CoverPack.cpp',
|
||||
dependencies: [sfml, filesystem]
|
||||
sources,
|
||||
dependencies: [sfml, filesystem],
|
||||
include_directories : include_directories('include')
|
||||
)
|
54
src/Data/KeyMaping.cpp
Normal file
54
src/Data/KeyMaping.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include "KeyMaping.hpp"
|
||||
|
||||
PanelCoords toCoord(PanelEnum panel) {
|
||||
auto num = static_cast<int>(panel);
|
||||
return {num % 4, num / 4};
|
||||
}
|
||||
|
||||
KeyMaping::KeyMaping() {
|
||||
m_key_to_panel[sf::Keyboard::Num1] = PanelEnum::P1;
|
||||
m_key_to_panel[sf::Keyboard::Num2] = PanelEnum::P2;
|
||||
m_key_to_panel[sf::Keyboard::Num3] = PanelEnum::P3;
|
||||
m_key_to_panel[sf::Keyboard::Num4] = PanelEnum::P4;
|
||||
m_key_to_panel[sf::Keyboard::A] = PanelEnum::P5;
|
||||
m_key_to_panel[sf::Keyboard::Z] = PanelEnum::P6;
|
||||
m_key_to_panel[sf::Keyboard::E] = PanelEnum::P7;
|
||||
m_key_to_panel[sf::Keyboard::R] = PanelEnum::P8;
|
||||
m_key_to_panel[sf::Keyboard::Q] = PanelEnum::P9;
|
||||
m_key_to_panel[sf::Keyboard::S] = PanelEnum::P10;
|
||||
m_key_to_panel[sf::Keyboard::D] = PanelEnum::P11;
|
||||
m_key_to_panel[sf::Keyboard::F] = PanelEnum::P12;
|
||||
m_key_to_panel[sf::Keyboard::W] = PanelEnum::P13;
|
||||
m_key_to_panel[sf::Keyboard::X] = PanelEnum::P14;
|
||||
m_key_to_panel[sf::Keyboard::C] = PanelEnum::P15;
|
||||
m_key_to_panel[sf::Keyboard::V] = PanelEnum::P16;
|
||||
|
||||
for (const auto& [key, panel] : m_key_to_panel) {
|
||||
m_panel_to_key[panel] = key;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyMaping::setPanelToKey(const PanelEnum& panel, const sf::Keyboard::Key& key) {
|
||||
if (m_key_to_panel.find(key) != m_key_to_panel.end()) {
|
||||
m_panel_to_key.erase(m_key_to_panel[key]);
|
||||
m_key_to_panel.erase(key);
|
||||
}
|
||||
m_panel_to_key[panel] = key;
|
||||
m_key_to_panel[key] = panel;
|
||||
}
|
||||
|
||||
std::optional<PanelEnum> KeyMaping::key_to_panel(const sf::Keyboard::Key& key) {
|
||||
try {
|
||||
return m_key_to_panel.at(key);
|
||||
} catch(const std::exception& e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<sf::Keyboard::Key> KeyMaping::panel_to_key(const PanelEnum& panel) {
|
||||
try {
|
||||
return m_panel_to_key.at(panel);
|
||||
} catch(const std::exception& e) {
|
||||
return {};
|
||||
}
|
||||
}
|
43
src/Data/KeyMaping.hpp
Normal file
43
src/Data/KeyMaping.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <SFML/Window.hpp>
|
||||
|
||||
enum class PanelEnum {
|
||||
P1,
|
||||
P2,
|
||||
P3,
|
||||
P4,
|
||||
P5,
|
||||
P6,
|
||||
P7,
|
||||
P8,
|
||||
P9,
|
||||
P10,
|
||||
P11,
|
||||
P12,
|
||||
P13,
|
||||
P14,
|
||||
P15,
|
||||
P16,
|
||||
};
|
||||
|
||||
struct PanelCoords {
|
||||
unsigned int x;
|
||||
unsigned int y;
|
||||
};
|
||||
|
||||
PanelCoords toCoord(PanelEnum panel);
|
||||
|
||||
class KeyMaping {
|
||||
public:
|
||||
KeyMaping();
|
||||
void setPanelToKey(const PanelEnum& panel, const sf::Keyboard::Key& key);
|
||||
std::optional<PanelEnum> key_to_panel(const sf::Keyboard::Key& key);
|
||||
std::optional<sf::Keyboard::Key> panel_to_key(const PanelEnum& panel);
|
||||
private:
|
||||
std::unordered_map<sf::Keyboard::Key, PanelEnum> m_key_to_panel;
|
||||
std::unordered_map<PanelEnum, sf::Keyboard::Key> m_panel_to_key;
|
||||
};
|
@ -1,9 +1,12 @@
|
||||
#include "SongList.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
|
||||
#include <memon.hpp>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
Data::SongList::SongList::SongList() {
|
||||
@ -12,25 +15,12 @@ Data::SongList::SongList::SongList() {
|
||||
for (const auto& folder : getSongFolders()) {
|
||||
try {
|
||||
songs.emplace_back(folder);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception while parsing song folder "
|
||||
<< folder.string() << " : " << e.what() << '\n';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Loading all cover previews
|
||||
for (const auto& song : songs) {
|
||||
if (song.cover) {
|
||||
try {
|
||||
cover_previews.emplace_back(*song.cover);
|
||||
} catch (const std::invalid_argument& e) {
|
||||
std::cerr << "Exception while loading song cover "
|
||||
<< song.cover->string() << " : " << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -82,13 +72,26 @@ Data::Song::Song(fs::path song_folder) {
|
||||
if (memon_files.size() > 1) {
|
||||
throw std::invalid_argument("Multiple .memon files");
|
||||
} else {
|
||||
|
||||
stepland::memon m;
|
||||
std::ifstream file(memon_files.back());
|
||||
file >> m;
|
||||
this->title = m.song_title;
|
||||
this->artist = m.artist;
|
||||
if (not m.album_cover_path.empty()) {
|
||||
this->cover.emplace(m.album_cover_path);
|
||||
}
|
||||
if (not m.music_path.empty()) {
|
||||
this->audio.emplace(m.music_path);
|
||||
}
|
||||
for (const auto& [difficulty, chart] : m.charts) {
|
||||
this->chart_levels[difficulty] = chart.level;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// .memo ?
|
||||
auto memo_files = getMemoFiles(song_folder);
|
||||
if (not memo_files.empty()) {
|
||||
|
||||
throw std::exception("jujube does not support .memo files for now ...");
|
||||
} else {
|
||||
throw std::invalid_argument("No valid file found in song folder");
|
||||
}
|
||||
|
@ -1,12 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../Textures/CoverPack.hpp"
|
||||
#include "../Resources/CoverAtlas.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
@ -26,15 +27,17 @@ namespace Data {
|
||||
// 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;
|
||||
|
||||
static bool sort_by_title(const Data::Song& a, const Data::Song& b) {
|
||||
return a.title < b.title;
|
||||
}
|
||||
};
|
||||
|
||||
// Class holding all the necessary data to run the Music Select screen
|
||||
// Class holding all the necessary song data to run the Music Select screen
|
||||
class SongList {
|
||||
public:
|
||||
SongList();
|
||||
private:
|
||||
Textures::CoverPack cover_previews;
|
||||
std::vector<const Song> songs;
|
||||
std::vector<Song> songs;
|
||||
};
|
||||
|
||||
// Returns the folders conscidered to contain a valid song
|
||||
|
@ -1,10 +1,9 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "Model/Chart.hpp"
|
||||
#include "Model/MusicList.hpp"
|
||||
#include "Model/Score.hpp"
|
||||
#include "Data/Chart.hpp"
|
||||
#include "Data/Score.hpp"
|
||||
|
||||
#include "Screens/MusicSelect.hpp"
|
||||
#include "Screens/MusicSelect/MusicSelect.hpp"
|
||||
#include "Screens/Gameplay.hpp"
|
||||
#include "Screens/Result.hpp"
|
||||
|
||||
@ -13,7 +12,7 @@ int main(int argc, char const *argv[]) {
|
||||
sf::RenderWindow window(sf::VideoMode(800,600), "jujube");
|
||||
window.setVerticalSyncEnabled(true);
|
||||
|
||||
Screen::MusicSelect music_select;
|
||||
MusicSelect::Screen music_select;
|
||||
|
||||
while (true) {
|
||||
|
||||
|
93
src/Resources/CoverAtlas.cpp
Normal file
93
src/Resources/CoverAtlas.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include "CoverAtlas.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
Textures::CoverAltas::CoverAltas() :
|
||||
path_to_index(),
|
||||
index_to_path(),
|
||||
textures()
|
||||
{
|
||||
this->emplace_back("assets/textures/fallback_cover.png");
|
||||
}
|
||||
|
||||
sf::Sprite Textures::CoverAltas::at(const fs::path& path) const {
|
||||
|
||||
unsigned int index;
|
||||
if (path_to_index.find(path) != path_to_index.end()) {
|
||||
index = path_to_index.at(path);
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
|
||||
auto location = get_detailed_location(path_to_index.at(path));
|
||||
sf::IntRect rect(location.column*256, location.row*256, 256, 256);
|
||||
sf::Sprite cover;
|
||||
cover.setTexture(textures.at(location.texture_index)->getTexture());
|
||||
cover.setTextureRect(rect);
|
||||
return cover;
|
||||
}
|
||||
|
||||
void Textures::CoverAltas::emplace_back(const fs::path& cover) {
|
||||
|
||||
if (path_to_index.find(cover) != path_to_index.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
sf::Texture cover_texture;
|
||||
if (!cover_texture.loadFromFile(cover)) {
|
||||
throw std::invalid_argument("Unable to load cover image : "+cover.string());
|
||||
}
|
||||
unsigned int next_index = next_available_index();
|
||||
auto size = cover_texture.getSize();
|
||||
auto location = get_detailed_location(next_index);
|
||||
sf::Sprite new_cover;
|
||||
new_cover.setTexture(cover_texture);
|
||||
new_cover.setScale(256.0f/size.x, 256.0f/size.y);
|
||||
new_cover.setPosition(256.0f * location.column, 256.0f * location.row);
|
||||
if (location.column == 0 and location.row == 0) {
|
||||
// first cover means it's a new texture
|
||||
textures.push_back(std::make_shared<sf::RenderTexture>());
|
||||
textures.back()->create(1024, 1024);
|
||||
}
|
||||
textures.at(location.texture_index)->draw(new_cover);
|
||||
textures.at(location.texture_index)->display();
|
||||
path_to_index[cover] = next_index;
|
||||
index_to_path[next_index] = cover;
|
||||
}
|
||||
|
||||
void Textures::CoverAltas::efficient_reload(const std::vector<fs::path>& covers) {
|
||||
|
||||
// Remove the paths that are not present in covers
|
||||
auto it = path_to_index.begin();
|
||||
while (it != path_to_index.end()) {
|
||||
if (std::find(covers.begin(), covers.end(), it->first) == covers.end()) {
|
||||
index_to_path.erase(it->second);
|
||||
it = path_to_index.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the covers that are not already in the altas
|
||||
for (const auto& cover : covers) {
|
||||
if (path_to_index.find(cover) == path_to_index.end()) {
|
||||
this->emplace_back(cover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) {
|
||||
return {location / 16, (location % 16) / 4, location % 4};
|
||||
}
|
||||
|
||||
unsigned int Textures::CoverAltas::next_available_index() {
|
||||
unsigned int index = 0;
|
||||
while (index_to_path.find(index) != index_to_path.end()) {
|
||||
++index;
|
||||
}
|
||||
return index;
|
||||
}
|
53
src/Resources/CoverAtlas.hpp
Normal file
53
src/Resources/CoverAtlas.hpp
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
namespace fs = std::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 {
|
||||
|
||||
/*
|
||||
A CoverAltas stores 256x256 cover previews in a vector of 1024x1024 textures
|
||||
*/
|
||||
class CoverAltas {
|
||||
public:
|
||||
CoverAltas();
|
||||
// returns the sprite corresponding to the path
|
||||
// returns a fallback texture if the path is unknown
|
||||
sf::Sprite at(const fs::path& path) const;
|
||||
void emplace_back(const fs::path& cover);
|
||||
// only loads the images not already present in the atlas
|
||||
// empties the slots not present in the vector
|
||||
void efficient_reload(const std::vector<fs::path>& covers);
|
||||
|
||||
private:
|
||||
std::unordered_map<fs::path, unsigned int> path_to_index;
|
||||
std::unordered_map<unsigned int, fs::path> index_to_path;
|
||||
std::vector<std::shared_ptr<sf::RenderTexture>> textures;
|
||||
unsigned int next_available_index();
|
||||
};
|
||||
|
||||
struct DetailedLocation {
|
||||
unsigned int texture_index;
|
||||
unsigned int row;
|
||||
unsigned int column;
|
||||
};
|
||||
|
||||
DetailedLocation get_detailed_location(unsigned int location);
|
||||
};
|
@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Window.hpp>
|
||||
#include "../Data/SongList.hpp"
|
||||
#include "../Data/Chart.hpp"
|
||||
|
||||
namespace Screen {
|
||||
|
||||
// The music select screen is created only once and loads a cache of available songs
|
||||
// in the music_list attribute
|
||||
class MusicSelect {
|
||||
Data::SongList music_list;
|
||||
public:
|
||||
MusicSelect() = default;
|
||||
Chart& select_chart(sf::Window& window) const;
|
||||
};
|
||||
};
|
53
src/Screens/MusicSelect/MusicSelect.cpp
Normal file
53
src/Screens/MusicSelect/MusicSelect.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "MusicSelect.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "../../Data/KeyMaping.hpp"
|
||||
|
||||
MusicSelect::Screen::Screen(const Data::SongList& t_song_list) :
|
||||
song_list(t_song_list),
|
||||
resources(),
|
||||
state(t_song_list)
|
||||
{
|
||||
for (const auto& song : song_list.songs) {
|
||||
if (song.cover) {
|
||||
try {
|
||||
resources.cover_previews.emplace_back(song.cover.value());
|
||||
} catch(const std::exception& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Chart& MusicSelect::Screen::select_chart(sf::Window& window) {
|
||||
bool chart_selected = false;
|
||||
while (not chart_selected) {
|
||||
sf::Event event;
|
||||
while (window.pollEvent(event)) {
|
||||
switch (event.type) {
|
||||
case sf::Event::KeyPressed:
|
||||
handle_key(event.key);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MusicSelect::Screen::handle_key(const sf::Event::KeyEvent& key_event) {
|
||||
auto panel = key_mapping.key_to_panel(key_event.code);
|
||||
if (panel) {
|
||||
if (PanelEnum::P2 <= *panel and *panel <= PanelEnum::P12) {
|
||||
auto coord = toCoord(*panel);
|
||||
auto ribbon_size = state.ribbon.get_layout().size();
|
||||
state
|
||||
.ribbon
|
||||
.get_layout()
|
||||
.at((state.ribbon_position + coord.x) % ribbon_size)
|
||||
.at(coord.y)
|
||||
->click(state);
|
||||
}
|
||||
}
|
||||
}
|
28
src/Screens/MusicSelect/MusicSelect.hpp
Normal file
28
src/Screens/MusicSelect/MusicSelect.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <SFML/Window.hpp>
|
||||
|
||||
#include "../../Data/SongList.hpp"
|
||||
#include "../../Data/Chart.hpp"
|
||||
#include "../../Data/KeyMaping.hpp"
|
||||
#include "State.hpp"
|
||||
#include "Resources.hpp"
|
||||
|
||||
namespace MusicSelect {
|
||||
|
||||
// The music select screen is created only once
|
||||
// it loads a cache of available songs in the song_list attribute
|
||||
class Screen {
|
||||
public:
|
||||
Screen(const Data::SongList& t_song_list);
|
||||
Chart& select_chart(sf::Window& window);
|
||||
private:
|
||||
const Data::SongList& song_list;
|
||||
Resources resources;
|
||||
State state;
|
||||
KeyMaping key_mapping;
|
||||
void handle_key(const sf::Event::KeyEvent& key_event);
|
||||
};
|
||||
};
|
6
src/Screens/MusicSelect/Panel.cpp
Normal file
6
src/Screens/MusicSelect/Panel.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
#include "Panel.hpp"
|
||||
|
||||
|
||||
void MusicSelect::CategoryPanel::display(State& state, sf::Window& window, sf::FloatRect area) {
|
||||
|
||||
}
|
42
src/Screens/MusicSelect/Panel.hpp
Normal file
42
src/Screens/MusicSelect/Panel.hpp
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Window.hpp>
|
||||
#include "State.hpp"
|
||||
|
||||
namespace MusicSelect {
|
||||
// A Panel holds anything that can go under a panel on the moving part
|
||||
// of the music select screen, be it nothing, a sort indicator, or a song
|
||||
class Panel {
|
||||
public:
|
||||
// What happens when you click on the panel
|
||||
virtual void click(State& state) = 0;
|
||||
// How the panel should be displayed
|
||||
virtual void display(State& state, sf::Window& window, sf::FloatRect area) = 0;
|
||||
virtual ~Panel() = default;
|
||||
};
|
||||
|
||||
class EmptyPanel final : public Panel {
|
||||
public:
|
||||
void click(State& state) override {return;};
|
||||
void display(State& state, sf::Window& window, sf::FloatRect area) override {return;};
|
||||
};
|
||||
|
||||
class CategoryPanel final : public Panel {
|
||||
public:
|
||||
CategoryPanel(const std::string& t_label) : label(t_label) {};
|
||||
void click(State& state) override;
|
||||
void display(State& state, sf::Window& window, sf::FloatRect area) override;
|
||||
private:
|
||||
std::string label;
|
||||
};
|
||||
|
||||
class SongPanel final : public Panel {
|
||||
public:
|
||||
SongPanel(const Data::Song& t_song) : song(t_song) {};
|
||||
void click(State& state) override;
|
||||
void display(State& state, sf::Window& window, sf::FloatRect area) override;
|
||||
private:
|
||||
const Data::Song& song;
|
||||
};
|
||||
}
|
15
src/Screens/MusicSelect/Resources.hpp
Normal file
15
src/Screens/MusicSelect/Resources.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
#include "../../Resources/CoverAtlas.hpp"
|
||||
|
||||
namespace MusicSelect {
|
||||
// Holds the fonts, graphics and sounds needed by all of the MusicSelect Screen
|
||||
struct Resources {
|
||||
Textures::CoverAltas cover_previews;
|
||||
std::unordered_map<std::string, sf::Font> fonts;
|
||||
};
|
||||
}
|
44
src/Screens/MusicSelect/Ribbon.cpp
Normal file
44
src/Screens/MusicSelect/Ribbon.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "Ribbon.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "Panel.hpp"
|
||||
|
||||
MusicSelect::Ribbon MusicSelect::Ribbon::title_sort(const Data::SongList& song_list) {
|
||||
std::map<char,std::vector<Data::Song>> categories;
|
||||
for (const auto& song : song_list.songs) {
|
||||
if (song.title.size > 0) {
|
||||
char letter = song.title[0];
|
||||
if ('A' <= letter and letter <= 'Z') {
|
||||
categories[letter].push_back(song);
|
||||
} else if ('a' <= letter and letter <= 'z') {
|
||||
categories['A' + (letter - 'a')].push_back(song);
|
||||
} else {
|
||||
categories['?'].push_back(song);
|
||||
}
|
||||
} else {
|
||||
categories['?'].push_back(song);
|
||||
}
|
||||
}
|
||||
Ribbon ribbon;
|
||||
for (const auto& [letter, songs] : categories) {
|
||||
std::vector<std::unique_ptr<Panel>> panels = {std::make_unique<CategoryPanel>(letter)};
|
||||
std::sort(songs.begin(), songs.end(), Data::Song::sort_by_title);
|
||||
for (const auto& song : songs) {
|
||||
panels.push_back(std::make_unique<SongPanel>(song));
|
||||
}
|
||||
while (panels.size % 3 != 0) {
|
||||
panels.push_back(std::make_unique<EmptyPanel>());
|
||||
}
|
||||
for (size_t i = 0; i < panels.size; i += 3) {
|
||||
std::array<std::unique_ptr<Panel>,3> column = {
|
||||
std::move(panels[i]),
|
||||
std::move(panels[i+1]),
|
||||
std::move(panels[i+2])
|
||||
};
|
||||
ribbon.layout.push_back(column);
|
||||
}
|
||||
}
|
||||
return ribbon;
|
||||
}
|
17
src/Screens/MusicSelect/Ribbon.hpp
Normal file
17
src/Screens/MusicSelect/Ribbon.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.hpp"
|
||||
#include "../../Data/SongList.hpp"
|
||||
|
||||
namespace MusicSelect {
|
||||
// The Ribbon is the moving part of the Music Select Screen
|
||||
// It can be sorted in a number of ways
|
||||
class Ribbon {
|
||||
public:
|
||||
Ribbon();
|
||||
static Ribbon title_sort(const Data::SongList& song_list);
|
||||
const auto& get_layout() {return layout;};
|
||||
private:
|
||||
std::vector<std::array<std::unique_ptr<Panel>,3>> layout;
|
||||
};
|
||||
}
|
8
src/Screens/MusicSelect/State.cpp
Normal file
8
src/Screens/MusicSelect/State.cpp
Normal file
@ -0,0 +1,8 @@
|
||||
#include "State.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
MusicSelect::State::State(const Data::SongList& song_list) {
|
||||
ribbon = MusicSelect::Ribbon::title_sort(song_list);
|
||||
}
|
22
src/Screens/MusicSelect/State.hpp
Normal file
22
src/Screens/MusicSelect/State.hpp
Normal file
@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "../Data/SongList.hpp"
|
||||
#include "Panel.hpp"
|
||||
#include "Ribbon.hpp"
|
||||
|
||||
|
||||
namespace MusicSelect {
|
||||
// Holds everything related to the displaying state of the MusicSelect screen
|
||||
struct State {
|
||||
State(const Data::SongList& song_list);
|
||||
// The ribbon is the moving part or the music select screen
|
||||
Ribbon ribbon;
|
||||
unsigned int ribbon_position;
|
||||
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
|
||||
};
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
#include "CoverPack.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
|
||||
sf::Sprite Textures::CoverPack::at(const std::filesystem::path& path) const{
|
||||
auto location = get_detailed_location(locations.at(path));
|
||||
sf::IntRect rect(location.column, location.row, 128, 128);
|
||||
sf::Sprite cover;
|
||||
cover.setTexture(textures.at(location.texture_index)->getTexture());
|
||||
cover.setTextureRect(rect);
|
||||
return cover;
|
||||
}
|
||||
|
||||
void Textures::CoverPack::emplace_back(const std::filesystem::path& cover) {
|
||||
|
||||
sf::Texture cover_texture;
|
||||
if (!cover_texture.loadFromFile(cover)) {
|
||||
std::stringstream ss;
|
||||
ss << "Unable to load cover image : " << cover;
|
||||
throw std::invalid_argument(ss.str());
|
||||
} else {
|
||||
locations[cover] = next_location;
|
||||
}
|
||||
auto size = cover_texture.getSize();
|
||||
auto location = get_detailed_location(next_location);
|
||||
sf::Sprite new_cover;
|
||||
new_cover.setTexture(cover_texture);
|
||||
new_cover.setScale(128.0f/size.x, 128.0f/size.y);
|
||||
new_cover.setPosition(128.0f * location.column, 128.0f * location.row);
|
||||
if (location.column == 0 and location.row == 0) {
|
||||
// first cover means it's a new texture
|
||||
textures.push_back(std::make_shared<sf::RenderTexture>());
|
||||
textures.back()->create(1024, 1024);
|
||||
}
|
||||
textures.at(location.texture_index)->draw(new_cover);
|
||||
textures.at(location.texture_index)->display();
|
||||
next_location++;
|
||||
}
|
||||
|
||||
Textures::DetailedLocation Textures::get_detailed_location(unsigned int location) {
|
||||
return {location / 64, (location % 64) / 8, location % 8};
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<sf::RenderTexture>>& Textures::CoverPack::getTextures() const {
|
||||
return textures;
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
|
||||
// Define the way we hash std::filesystem::path for use in unordered maps
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<std::filesystem::path> {
|
||||
std::size_t operator()(const std::filesystem::path& p) const {
|
||||
return std::hash<std::string>()(p.string());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
namespace Textures {
|
||||
|
||||
/*
|
||||
A CoverPack stores 128x128 cover previews in a vector of 1024x1024 textures
|
||||
*/
|
||||
class CoverPack {
|
||||
public:
|
||||
CoverPack() = default;
|
||||
sf::Sprite at(const std::filesystem::path& path) const;
|
||||
void emplace_back(const std::filesystem::path& cover);
|
||||
const std::vector<std::shared_ptr<sf::RenderTexture>>& getTextures() const;
|
||||
private:
|
||||
std::unordered_map<std::filesystem::path, unsigned int> locations;
|
||||
std::vector<std::shared_ptr<sf::RenderTexture>> textures;
|
||||
unsigned int next_location = 0;
|
||||
};
|
||||
|
||||
struct DetailedLocation {
|
||||
unsigned int texture_index;
|
||||
unsigned int row;
|
||||
unsigned int column;
|
||||
};
|
||||
|
||||
DetailedLocation get_detailed_location(unsigned int location);
|
||||
};
|
Loading…
Reference in New Issue
Block a user