1
0
mirror of synced 2025-02-02 20:37:25 +01:00

Add basic marker support

This commit is contained in:
Stepland 2020-03-02 00:16:58 +01:00
parent 11f1cbdb84
commit 1310886609
18 changed files with 989 additions and 217 deletions

View File

@ -0,0 +1,426 @@
/*
Copyright (c) 2016 Jonathan B. Coe
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef JBCOE_POLYMORPHIC_VALUE_H_INCLUDED
#define JBCOE_POLYMORPHIC_VALUE_H_INCLUDED
#include <cassert>
#include <exception>
#include <memory>
#include <type_traits>
#include <typeinfo>
namespace jbcoe
{
namespace detail
{
////////////////////////////////////////////////////////////////////////////
// Implementation detail classes
////////////////////////////////////////////////////////////////////////////
template <class T>
struct default_copy
{
T* operator()(const T& t) const
{
return new T(t);
}
};
template <class T>
struct default_delete
{
void operator()(const T* t) const
{
delete t;
}
};
template <class T>
struct control_block
{
virtual ~control_block() = default;
virtual std::unique_ptr<control_block> clone() const = 0;
virtual T* ptr() = 0;
};
template <class T, class U = T>
class direct_control_block : public control_block<T>
{
static_assert(!std::is_reference<U>::value, "");
U u_;
public:
template <class... Ts>
explicit direct_control_block(Ts&&... ts) : u_(U(std::forward<Ts>(ts)...))
{
}
std::unique_ptr<control_block<T>> clone() const override
{
return std::make_unique<direct_control_block>(*this);
}
T* ptr() override
{
return std::addressof(u_);
}
};
template <class T, class U, class C = default_copy<U>,
class D = default_delete<U>>
class pointer_control_block : public control_block<T>, public C
{
std::unique_ptr<U, D> p_;
public:
explicit pointer_control_block(U* u, C c = C{}, D d = D{})
: C(std::move(c)), p_(u, std::move(d))
{
}
explicit pointer_control_block(std::unique_ptr<U, D> p, C c = C{})
: C(std::move(c)), p_(std::move(p))
{
}
std::unique_ptr<control_block<T>> clone() const override
{
assert(p_);
return std::make_unique<pointer_control_block>(
C::operator()(*p_), static_cast<const C&>(*this), p_.get_deleter());
}
T* ptr() override
{
return p_.get();
}
};
template <class T, class U>
class delegating_control_block : public control_block<T>
{
std::unique_ptr<control_block<U>> delegate_;
public:
explicit delegating_control_block(std::unique_ptr<control_block<U>> b)
: delegate_(std::move(b))
{
}
std::unique_ptr<control_block<T>> clone() const override
{
return std::make_unique<delegating_control_block>(delegate_->clone());
}
T* ptr() override
{
return delegate_->ptr();
}
};
} // end namespace detail
class bad_polymorphic_value_construction : public std::exception
{
public:
bad_polymorphic_value_construction() noexcept = default;
const char* what() const noexcept override
{
return "Dynamic and static type mismatch in polymorphic_value "
"construction";
}
};
template <class T>
class polymorphic_value;
template <class T>
struct is_polymorphic_value : std::false_type
{
};
template <class T>
struct is_polymorphic_value<polymorphic_value<T>> : std::true_type
{
};
////////////////////////////////////////////////////////////////////////////////
// `polymorphic_value` class definition
////////////////////////////////////////////////////////////////////////////////
template <class T>
class polymorphic_value
{
static_assert(!std::is_union<T>::value, "");
static_assert(std::is_class<T>::value, "");
template <class U>
friend class polymorphic_value;
template <class T_, class U, class... Ts>
friend polymorphic_value<T_> make_polymorphic_value(Ts&&... ts);
template <class T_, class... Ts>
friend polymorphic_value<T_> make_polymorphic_value(Ts&&... ts);
T* ptr_ = nullptr;
std::unique_ptr<detail::control_block<T>> cb_;
public:
//
// Destructor
//
~polymorphic_value() = default;
//
// Constructors
//
polymorphic_value()
{
}
template <class U, class C = detail::default_copy<U>,
class D = detail::default_delete<U>,
class V = std::enable_if_t<std::is_convertible<U*, T*>::value>>
explicit polymorphic_value(U* u, C copier = C{}, D deleter = D{})
{
if (!u)
{
return;
}
#ifndef POLYMORPHIC_VALUE_NO_RTTI
if (std::is_same<D, detail::default_delete<U>>::value &&
std::is_same<C, detail::default_copy<U>>::value &&
typeid(*u) != typeid(U))
throw bad_polymorphic_value_construction();
#endif
std::unique_ptr<U, D> p(u, std::move(deleter));
cb_ = std::make_unique<detail::pointer_control_block<T, U, C, D>>(
std::move(p), std::move(copier));
ptr_ = u;
}
//
// Copy-constructors
//
polymorphic_value(const polymorphic_value& p)
{
if (!p)
{
return;
}
auto tmp_cb = p.cb_->clone();
ptr_ = tmp_cb->ptr();
cb_ = std::move(tmp_cb);
}
//
// Move-constructors
//
polymorphic_value(polymorphic_value&& p) noexcept
{
ptr_ = p.ptr_;
cb_ = std::move(p.cb_);
p.ptr_ = nullptr;
}
//
// Converting constructors
//
template <class U,
class V = std::enable_if_t<!std::is_same<T, U>::value &&
std::is_convertible<U*, T*>::value>>
explicit polymorphic_value(const polymorphic_value<U>& p)
{
polymorphic_value<U> tmp(p);
ptr_ = tmp.ptr_;
cb_ = std::make_unique<detail::delegating_control_block<T, U>>(
std::move(tmp.cb_));
}
template <class U,
class V = std::enable_if_t<!std::is_same<T, U>::value &&
std::is_convertible<U*, T*>::value>>
explicit polymorphic_value(polymorphic_value<U>&& p)
{
ptr_ = p.ptr_;
cb_ = std::make_unique<detail::delegating_control_block<T, U>>(
std::move(p.cb_));
p.ptr_ = nullptr;
}
//
// Forwarding constructor
//
template <class U, class V = std::enable_if_t<
std::is_convertible<std::decay_t<U>*, T*>::value &&
!is_polymorphic_value<std::decay_t<U>>::value>>
explicit polymorphic_value(U&& u)
: cb_(std::make_unique<
detail::direct_control_block<T, std::decay_t<U>>>(
std::forward<U>(u)))
{
ptr_ = cb_->ptr();
}
//
// Assignment
//
polymorphic_value& operator=(const polymorphic_value& p)
{
if (std::addressof(p) == this)
{
return *this;
}
if (!p)
{
cb_.reset();
ptr_ = nullptr;
return *this;
}
auto tmp_cb = p.cb_->clone();
ptr_ = tmp_cb->ptr();
cb_ = std::move(tmp_cb);
return *this;
}
//
// Move-assignment
//
polymorphic_value& operator=(polymorphic_value&& p) noexcept
{
if (std::addressof(p) == this)
{
return *this;
}
cb_ = std::move(p.cb_);
ptr_ = p.ptr_;
p.ptr_ = nullptr;
return *this;
}
//
// Modifiers
//
void swap(polymorphic_value& p) noexcept
{
using std::swap;
swap(ptr_, p.ptr_);
swap(cb_, p.cb_);
}
//
// Observers
//
explicit operator bool() const
{
return bool (cb_);
}
const T* operator->() const
{
assert(ptr_);
return ptr_;
}
const T& operator*() const
{
assert(*this);
return *ptr_;
}
T* operator->()
{
assert(*this);
return ptr_;
}
T& operator*()
{
assert(*this);
return *ptr_;
}
};
//
// polymorphic_value creation
//
template <class T, class... Ts>
polymorphic_value<T> make_polymorphic_value(Ts&&... ts)
{
polymorphic_value<T> p;
p.cb_ = std::make_unique<detail::direct_control_block<T, T>>(
std::forward<Ts>(ts)...);
p.ptr_ = p.cb_->ptr();
return p;
}
template <class T, class U, class... Ts>
polymorphic_value<T> make_polymorphic_value(Ts&&... ts)
{
polymorphic_value<T> p;
p.cb_ = std::make_unique<detail::direct_control_block<T, U>>(
std::forward<Ts>(ts)...);
p.ptr_ = p.cb_->ptr();
return p;
}
//
// non-member swap
//
template <class T>
void swap(polymorphic_value<T>& t, polymorphic_value<T>& u) noexcept
{
t.swap(u);
}
} // end namespace jbcoe
#endif

View File

@ -54,10 +54,22 @@ namespace Data {
}
};
struct Options {
std::string marker;
template<class Archive>
void serialize(Archive & archive) {
archive(
CEREAL_NVP(marker),
);
}
};
// RAII style class which loads preferences from the dedicated file when constructed and saves them when destructed
struct Preferences {
Screen screen;
Layout layout;
Options options;
KeyMapping key_mapping;
Preferences() : screen(), layout() {
@ -106,6 +118,7 @@ namespace Data {
archive(
CEREAL_NVP(screen),
CEREAL_NVP(layout),
CEREAL_NVP(options),
cereal::make_nvp("key_mapping", key_mapping.m_button_to_key)
);
}

View File

@ -6,6 +6,7 @@
#include "Data/Song.hpp"
#include "Data/Preferences.hpp"
#include "Resources/Marker.hpp"
// #include "Data/Chart.hpp"
// #include "Data/Score.hpp"
@ -14,7 +15,13 @@
// #include "Screens/Result.hpp"
int main(int argc, char const *argv[]) {
Data::Preferences preferences;
auto markers = Resources::load_markers();
if (markers.find(preferences.options.marker) == markers.end()) {
preferences.options.marker = markers.begin()->first;
}
sf::ContextSettings settings;
settings.antialiasingLevel = 8;
sf::RenderWindow window{
@ -27,7 +34,11 @@ int main(int argc, char const *argv[]) {
settings
};
Data::SongList song_list;
MusicSelect::Screen music_select{song_list, preferences};
MusicSelect::Screen music_select{
song_list,
preferences,
markers,
};
music_select.select_chart(window);
/*

View File

@ -8,17 +8,7 @@
#include <SFML/Graphics.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());
}
};
}
#include "../Toolkit/GHCFilesystemPathHash.hpp"
namespace Textures {

204
src/Resources/Marker.cpp Normal file
View File

@ -0,0 +1,204 @@
#include "Marker.hpp"
#include <algorithm>
#include <cmath>
#include <fstream>
#include <functional>
#include <iostream>
#include <sstream>
#include <cereal/archives/json.hpp>
#include "../Toolkit/GHCFilesystemPathHash.hpp"
namespace fs = ghc::filesystem;
namespace Resources {
Marker::Marker(const fs::path& marker_folder) :
m_folder(marker_folder),
m_metadata(),
m_approach(),
m_miss(),
m_early(),
m_good(),
m_great(),
m_perfect()
{
if (not fs::is_directory(m_folder)) {
throw std::invalid_argument(m_folder.string()+" is not a folder");
}
if (not fs::exists(m_folder/"marker.json")) {
throw std::invalid_argument("Marker folder ( "+m_folder.string()+" ) has no marker.json file");
}
std::ifstream marker_json{m_folder/"marker.json"};
{
cereal::JSONInputArchive archive{marker_json};
archive(m_metadata);
}
load_and_check(m_approach, m_metadata.approach);
load_and_check(m_miss, m_metadata.miss);
load_and_check(m_early, m_metadata.early);
load_and_check(m_good, m_metadata.good);
load_and_check(m_great, m_metadata.great);
load_and_check(m_perfect, m_metadata.perfect);
}
void Marker::load_and_check(sf::Texture& sprite_sheet, const MarkerAnimationMetadata& metadata) {
// File Load & Check
if (not sprite_sheet.loadFromFile(m_folder/metadata.sprite_sheet)) {
throw std::runtime_error(
"Cannot open marker sprite sheet "
+(m_folder/metadata.sprite_sheet).string()
);
}
// Sprite sheet size check
// throw if the texture size does not match what's announced by the metadata
auto sheet_size = sprite_sheet.getSize();
auto expected_size = sf::Vector2u(metadata.columns, metadata.rows) * static_cast<unsigned int>(m_metadata.size);
if (sheet_size != expected_size) {
std::stringstream ss;
ss << "Marker sprite sheet ";
ss << (m_folder/metadata.sprite_sheet).string();
ss << " should be " << expected_size.x << "×" << expected_size.y << " pixels";
ss << " but is " << sheet_size.x << "×" << sheet_size.y;
throw std::invalid_argument(ss.str());
}
// Sprite count check
// throw if the count calls for more sprites than possible according to the 'columns' and 'rows' fields
if (metadata.count > metadata.columns * metadata.rows) {
std::stringstream ss;
ss << "Metadata for marker sprite sheet ";
ss << (m_folder/metadata.sprite_sheet).string();
ss << " indicates that it holds " << metadata.count << " sprites";
ss << " when it can only hold a maximum of " << metadata.columns * metadata.rows;
ss << " according to the 'columns' and 'rows' fields";
throw std::invalid_argument(ss.str());
}
// Duration check
// We do not allow any marker animation to take longer than the jubeat standard of 16 frames at 30 fps
// For that we check that :
// count/fps <= 16/30
// Which is mathematically equivalent to checking that :
// count*30 <= 16*fps
// Which allows us to avoid having to cast to float
if (metadata.count*30 <= 16*m_metadata.fps) {
std::stringstream ss;
ss << "Marker animation for sprite sheet ";
ss << (m_folder/metadata.sprite_sheet).string();
ss << " lasts " << metadata.count/static_cast<float>(m_metadata.fps)*1000.f << "ms";
ss << " (" << metadata.count << "f @ " << m_metadata.fps << "fps)";
ss << " which is more than the maximum of " << 16.f/30.f*1000.f << "ms";
ss << ' (16f @ 30fps)';
throw std::invalid_argument(ss.str());
}
}
sf::Texture& Marker::get_sprite_sheet_from_enum(const MarkerAnimation& state) {
switch (state) {
case MarkerAnimation::APPROACH:
return m_approach;
break;
case MarkerAnimation::MISS:
return m_miss;
break;
case MarkerAnimation::EARLY:
return m_early;
break;
case MarkerAnimation::GOOD:
return m_good;
break;
case MarkerAnimation::GREAT:
return m_great;
break;
case MarkerAnimation::PERFECT:
return m_perfect;
break;
}
}
MarkerAnimationMetadata& Marker::get_metadata_from_enum(const MarkerAnimation& state) {
switch (state) {
case MarkerAnimation::APPROACH:
return m_metadata.approach;
break;
case MarkerAnimation::MISS:
return m_metadata.miss;
break;
case MarkerAnimation::EARLY:
return m_metadata.early;
break;
case MarkerAnimation::GOOD:
return m_metadata.good;
break;
case MarkerAnimation::GREAT:
return m_metadata.great;
break;
case MarkerAnimation::PERFECT:
return m_metadata.perfect;
break;
}
}
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const float& seconds) {
auto raw_frame = static_cast<int>(std::floor(seconds*m_metadata.fps));
if (raw_frame >= 0) {
if (state == MarkerAnimation::APPROACH) {
return get_sprite(MarkerAnimation::MISS, static_cast<std::size_t>(raw_frame));
} else {
return get_sprite(state, static_cast<std::size_t>(raw_frame));
}
} else {
auto approach_frame_count = get_metadata_from_enum(MarkerAnimation::APPROACH).count;
return get_sprite(
MarkerAnimation::APPROACH,
static_cast<std::size_t>(raw_frame+static_cast<int>(approach_frame_count))
);
}
}
std::optional<sf::Sprite> Marker::get_sprite(const MarkerAnimation& state, const std::size_t frame) {
auto& meta = get_metadata_from_enum(state);
if (frame >= meta.count) {
return {};
} else {
auto& tex = get_sprite_sheet_from_enum(state);
sf::Sprite sprite{tex};
sf::IntRect rect{
sf::Vector2i{
static_cast<int>(frame % meta.columns),
static_cast<int>(frame / meta.columns)
} * static_cast<int>(m_metadata.size),
sf::Vector2i{
static_cast<int>(m_metadata.size),
static_cast<int>(m_metadata.size)
}
};
sprite.setTextureRect(rect);
return sprite;
}
}
Markers load_markers() {
Markers res;
for (auto& p : fs::directory_iterator("markers")) {
if (p.is_directory()) {
try {
Marker m{p.path()};
res.emplace(m.m_metadata.name, m);
} catch (const std::exception& e) {
std::cerr << "Unable to load marker folder "
<< p.path().string() << " : "
<< e.what() << std::endl;
}
}
}
if (res.empty()) {
throw std::runtime_error("No markers found in marker folder, jujube needs at least one to operate");
}
return res;
}
}

87
src/Resources/Marker.hpp Normal file
View File

@ -0,0 +1,87 @@
#pragma once
#include <string>
#include <map>
#include <cereal/types/string.hpp>
#include <ghc/filesystem.hpp>
#include <SFML/Graphics.hpp>
#include "../Toolkit/ExtraCerealTypes/GHCFilesystemPath.hpp"
namespace Resources {
enum class MarkerAnimation {
APPROACH,
MISS,
EARLY, // or LATE
GOOD,
GREAT,
PERFECT
};
struct MarkerAnimationMetadata {
ghc::filesystem::path sprite_sheet;
std::size_t count; // how many sprites total on the sheet
std::size_t columns; // how many horizontally
std::size_t rows; // how many vertically
template<class Archive>
void serialize(Archive & archive) const {
archive(
CEREAL_NVP(sprite_sheet),
CEREAL_NVP(count),
CEREAL_NVP(column),
CEREAL_NVP(rows)
);
}
};
// Represents what's held in marker.json
struct MarkerMetadata {
std::string name;
std::size_t size; // the side length in pixels
std::size_t fps; // classic jubeat markers are 30 fps
MarkerAnimationMetadata approach;
MarkerAnimationMetadata miss;
MarkerAnimationMetadata early;
MarkerAnimationMetadata good;
MarkerAnimationMetadata great;
MarkerAnimationMetadata perfect;
template<class Archive>
void serialize(Archive & archive) const {
archive(
CEREAL_NVP(name),
CEREAL_NVP(size),
CEREAL_NVP(fps),
CEREAL_NVP(approach),
CEREAL_NVP(miss),
CEREAL_NVP(early),
CEREAL_NVP(good),
CEREAL_NVP(great),
CEREAL_NVP(perfect)
);
}
};
struct Marker {
explicit Marker(const ghc::filesystem::path& marker_folder);
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const float& seconds);
std::optional<sf::Sprite> get_sprite(const MarkerAnimation& state, const std::size_t frame);
void load_and_check(sf::Texture& spritesheet, const MarkerAnimationMetadata& metadata);
sf::Texture& get_sprite_sheet_from_enum(const MarkerAnimation& state);
MarkerAnimationMetadata& get_metadata_from_enum(const MarkerAnimation& state);
ghc::filesystem::path m_folder;
MarkerMetadata m_metadata;
sf::Texture m_approach;
sf::Texture m_miss;
sf::Texture m_early;
sf::Texture m_good;
sf::Texture m_great;
sf::Texture m_perfect;
};
using Markers = std::multimap<std::string, Marker>;
Markers load_markers();
}

View File

@ -6,18 +6,7 @@
#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());
}
};
}
#include "../Toolkit/GHCFilesystemPathHash.hpp"
namespace Textures {

View File

@ -7,18 +7,23 @@
#include "../../Data/Buttons.hpp"
#include "../../Data/KeyMapping.hpp"
#include "PanelLayout.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,
const Resources::Markers& t_markers
) :
song_list(t_song_list),
resources(t_preferences),
ribbon(resources),
markers(t_markers),
ribbon(PanelLayout::title_sort(t_song_list, resources), resources),
song_info(resources),
selected_panel(),
button_highlight(resources),
black_frame(t_preferences),
key_mapping()
{
ribbon.title_sort(song_list);
std::cout << "loaded MusicSelect::Screen" << std::endl;
}
@ -112,8 +117,7 @@ void MusicSelect::Screen::press_button(const Data::Button& button) {
button_highlight.button_pressed(button);
auto button_index = Data::button_to_index(button);
if (button_index < 12) {
ribbon.click_on(button_index);
// ribbon.at(button_index)->click(ribbon);
ribbon.click_on(button);
} else {
switch (button) {
case Data::Button::B13:

View File

@ -1,6 +1,7 @@
#pragma once
#include <map>
#include <stack>
#include <SFML/Window.hpp>
@ -8,11 +9,13 @@
#include "../../Data/Chart.hpp"
#include "../../Data/KeyMapping.hpp"
#include "../../Drawables/BlackFrame.hpp"
#include "../../Resources/Marker.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "Ribbon.hpp"
#include "SongListRibbon.hpp"
#include "SongInfo.hpp"
#include "SharedResources.hpp"
#include "ButtonHighlight.hpp"
#include "OptionMenu.hpp"
namespace MusicSelect {
@ -20,7 +23,11 @@ namespace MusicSelect {
// it loads a cache of available songs in the song_list attribute
class Screen {
public:
Screen(const Data::SongList& t_song_list, Data::Preferences& t_preferences);
Screen(
const Data::SongList& t_song_list,
Data::Preferences& t_preferences,
const Resources::Markers& t_markers
);
void select_chart(sf::RenderWindow& window);
private:
@ -29,12 +36,14 @@ namespace MusicSelect {
// Resources
SharedResources resources;
Resources::Markers markers;
// State
Ribbon ribbon;
SongListRibbon ribbon;
SongInfo song_info;
std::optional<std::reference_wrapper<SongPanel>> selected_panel;
ButtonHighlight button_highlight;
std::stack<OptionMenu> options_state;
Drawables::BlackFrame black_frame;

View File

@ -0,0 +1,22 @@
#pragma once
#include <SFML/Graphics.hpp>
#include "SharedResources.hpp"
#include "../../Data/Buttons.hpp"
namespace MusicSelect {
class OptionPage : public sf::Drawable, public sf::Transformable, public HoldsSharedResources {
public:
virtual void click(const Data::Button& button) = 0;
virtual ~OptionPage() = default;
};
class MainOptionPage final : public OptionPage {
public:
MainOptionPage();
void click(const Data::Button& button);
private:
void draw(sf::RenderTarget& target, sf::RenderStates states);
}
}

View File

@ -26,8 +26,8 @@ namespace MusicSelect {
target.draw(panel, states);
}
void CategoryPanel::click(Ribbon& ribbon, std::size_t from_button_index) {
ribbon.move_to_next_category(from_button_index);
void CategoryPanel::click(Ribbon& ribbon, const Data::Button& button) {
ribbon.move_to_next_category(button);
}
void CategoryPanel::draw(sf::RenderTarget& target, sf::RenderStates states) const {
@ -66,7 +66,7 @@ namespace MusicSelect {
target.draw(label_text, states);
}
void SongPanel::click(Ribbon& ribbon, std::size_t from_button_index) {
void SongPanel::click(MusicSelectRibbon& ribbon, const Data::Button& button) {
if (selected_chart.has_value()) {
// The song was already selected : look for the next chart in order
auto it = m_song->chart_levels.upper_bound(*selected_chart);

View File

@ -21,7 +21,7 @@ namespace MusicSelect {
public:
explicit Panel(SharedResources& resources);
// What happens when you click on the panel
virtual void click(Ribbon& ribbon, std::size_t from_button_index) = 0;
virtual void click(Ribbon& ribbon, const Data::Button& button) = 0;
virtual ~Panel() = default;
protected:
float get_size() const;
@ -30,7 +30,7 @@ namespace MusicSelect {
class EmptyPanel final : public Panel {
public:
using Panel::Panel;
void click(Ribbon& ribbon, std::size_t from_button_index) override {return;};
void click(Ribbon& ribbon, const Data::Button& button) override {return;};
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override {return;};
};
@ -38,7 +38,7 @@ namespace MusicSelect {
class ColoredMessagePanel final : public Panel {
public:
ColoredMessagePanel(SharedResources& resources, const sf::Color& color, const std::string& message) : Panel(resources), m_color(color), m_message(message) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override {return;};
void click(Ribbon& ribbon, const Data::Button& button) override {return;};
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
const sf::Color m_color;
@ -48,7 +48,7 @@ namespace MusicSelect {
class ColorPanel final : public Panel {
public:
ColorPanel(SharedResources& resources, const sf::Color& t_color) : Panel(resources), m_color(t_color) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override {return;};
void click(Ribbon& ribbon, const Data::Button& button) override {return;};
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
const sf::Color m_color;
@ -57,7 +57,7 @@ namespace MusicSelect {
class CategoryPanel final : public Panel {
public:
explicit CategoryPanel(SharedResources& resources, const std::string& t_label) : Panel(resources), m_label(t_label) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override;
void click(Ribbon& ribbon, const Data::Button& button) override;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
std::string m_label;
@ -74,7 +74,7 @@ namespace MusicSelect {
class SongPanel final : public SelectablePanel {
public:
explicit SongPanel(SharedResources& resources, const std::shared_ptr<const Data::Song>& t_song) : SelectablePanel(resources), m_song(t_song) {};
void click(Ribbon& ribbon, std::size_t from_button_index) override;
void click(Ribbon& ribbon, const Data::Button& button) override;
void unselect() override;
std::optional<SongDifficulty> get_selected_difficulty() const override;
private:

View File

@ -0,0 +1,102 @@
#include "PanelLayout.hpp"
namespace MusicSelect {
PanelLayout::PanelLayout(
const std::map<std::string,std::vector<jbcoe::polymorphic_value<Panel>>>& categories,
SharedResources& resources
) {
for (auto &&[category, panels] : categories) {
if (not panels.empty()) {
std::vector<jbcoe::polymorphic_value<Panel>> current_column;
current_column.emplace_back(CategoryPanel{resources, category});
for (auto &&panel : panels) {
if (current_column.size() == 3) {
push_back({current_column[0], current_column[1], current_column[2]});
current_column.clear();
} else {
current_column.push_back(std::move(panel));
}
}
if (not current_column.empty()) {
while (current_column.size() < 3) {
current_column.emplace_back(EmptyPanel{resources});
}
push_back({current_column[0], current_column[1], current_column[2]});
}
}
}
fill_layout(resources);
}
PanelLayout::PanelLayout(
const std::vector<jbcoe::polymorphic_value<Panel>> panels,
SharedResources& resources
) {
std::vector<jbcoe::polymorphic_value<Panel>> current_column;
for (auto &&panel : panels) {
if (current_column.size() == 3) {
push_back({current_column[0], current_column[1], current_column[2]});
current_column.clear();
} else {
current_column.push_back(std::move(panel));
}
}
if (not current_column.empty()) {
while (current_column.size() < 3) {
current_column.emplace_back(EmptyPanel{resources});
}
push_back({current_column[0], current_column[1], current_column[2]});
}
fill_layout(resources);
}
PanelLayout PanelLayout::red_empty_layout(SharedResources& resources) {
std::vector<jbcoe::polymorphic_value<Panel>> panels;
for (size_t i = 0; i < 3*4; i++) {
panels.emplace_back(ColoredMessagePanel{resources, sf::Color::Red, "- EMPTY -"});
}
return PanelLayout{panels, resources};
}
PanelLayout PanelLayout::title_sort(const Data::SongList& song_list, SharedResources& resources) {
std::vector<std::shared_ptr<const Data::Song>> songs;
for (auto &&song : song_list.songs) {
songs.push_back(song);
}
std::sort(
songs.begin(),
songs.end(),
[](std::shared_ptr<const Data::Song> a, std::shared_ptr<const Data::Song> b){return Data::Song::sort_by_title(*a, *b);}
);
std::map<std::string, std::vector<jbcoe::polymorphic_value<Panel>>> categories;
for (const auto &song : songs) {
if (song->title.size() > 0) {
char letter = song->title[0];
if ('A' <= letter and letter <= 'Z') {
categories
[std::string(1, letter)]
.emplace_back(SongPanel{resources, song});
} else if ('a' <= letter and letter <= 'z') {
categories
[std::string(1, 'A' + (letter - 'a'))]
.emplace_back(SongPanel{resources, song});
} else {
categories["?"].emplace_back(SongPanel{resources, song});
}
} else {
categories["?"].emplace_back(SongPanel{resources, song});
}
}
return PanelLayout{categories, resources};
}
void PanelLayout::fill_layout(SharedResources& resources) {
while (size() < 4) {
push_back({
jbcoe::polymorphic_value<Panel>{EmptyPanel{resources}},
jbcoe::polymorphic_value<Panel>{EmptyPanel{resources}},
jbcoe::polymorphic_value<Panel>{EmptyPanel{resources}}
});
}
}
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <array>
#include <vector>
#include <jbcoe/polymorphic_value.h>
#include "Panel.hpp"
#include "SharedResources.hpp"
#include "../../Data/Song.hpp"
namespace MusicSelect {
// PanelLayout restricts the ways you can create a scrollable grid of panels
class PanelLayout : public std::vector<std::array<jbcoe::polymorphic_value<Panel>,3>> {
public:
explicit PanelLayout(const std::map<std::string,std::vector<jbcoe::polymorphic_value<Panel>>>& categories, SharedResources& resources);
explicit PanelLayout(const std::vector<jbcoe::polymorphic_value<Panel>> panels, SharedResources& resources);
static PanelLayout red_empty_layout(SharedResources& resources);
static PanelLayout title_sort(const Data::SongList& song_list, SharedResources& resources);
private:
void fill_layout(SharedResources& resources);
};
}

View File

@ -1,17 +1,17 @@
#include "Ribbon.hpp"
#include "imgui/imgui.h"
#include "imgui-sfml/imgui-SFML.h"
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <map>
#include <vector>
#include "Panel.hpp"
#include <imgui/imgui.h>
#include <imgui-sfml/imgui-SFML.h>
#include "../../Data/Song.hpp"
#include "../../Toolkit/QuickRNG.hpp"
#include "Panel.hpp"
namespace MusicSelect {
@ -64,126 +64,29 @@ namespace MusicSelect {
return clock.getElapsedTime() / m_time_factor > sf::milliseconds(300);
}
Ribbon::Ribbon(SharedResources& t_resources) : HoldsSharedResources(t_resources) {
Ribbon::Ribbon(PanelLayout layout, SharedResources& resources) :
HoldsSharedResources(resources),
m_layout(layout)
{
std::cout << "Loaded MusicSelect::Ribbon" << std::endl;
}
void Ribbon::title_sort(const Data::SongList &song_list) {
std::vector<std::shared_ptr<const Data::Song>> songs;
for (auto &&song : song_list.songs) {
songs.push_back(song);
}
std::sort(
songs.begin(),
songs.end(),
[](std::shared_ptr<const Data::Song> a, std::shared_ptr<const Data::Song> b){return Data::Song::sort_by_title(*a, *b);}
);
std::map<std::string, std::vector<std::shared_ptr<Panel>>> categories;
for (const auto &song : songs) {
if (song->title.size() > 0) {
char letter = song->title[0];
if ('A' <= letter and letter <= 'Z') {
categories
[std::string(1, letter)]
.push_back(
std::make_shared<SongPanel>(m_resources, song)
);
} else if ('a' <= letter and letter <= 'z') {
categories
[std::string(1, 'A' + (letter - 'a'))]
.push_back(
std::make_shared<SongPanel>(m_resources, song)
);
} else {
categories["?"].push_back(std::make_shared<SongPanel>(m_resources, song));
}
} else {
categories["?"].push_back(std::make_shared<SongPanel>(m_resources, song));
}
}
layout_from_category_map(categories);
std::size_t Ribbon::get_layout_column(const Data::Button& button) const {
return (m_position + (Data::button_to_index(button) % 4)) % m_layout.size();
}
void Ribbon::test_sort() {
m_layout.clear();
m_layout.push_back({
std::make_shared<EmptyPanel>(m_resources),
std::make_shared<CategoryPanel>(m_resources, "A"),
std::make_shared<CategoryPanel>(m_resources, "truc")
});
for (size_t i = 0; i < 3; i++) {
m_layout.push_back({
std::make_shared<EmptyPanel>(m_resources),
std::make_shared<EmptyPanel>(m_resources),
std::make_shared<EmptyPanel>(m_resources)
});
}
fill_layout();
}
void Ribbon::test2_sort() {
std::string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
std::map<std::string, std::vector<std::shared_ptr<Panel>>> categories;
Toolkit::UniformIntRNG category_size_generator{1, 10};
Toolkit::UniformIntRNG panel_hue_generator{0, 255};
for (auto &&letter : alphabet) {
auto category_size = category_size_generator.generate();
for (int i = 0; i < category_size; i++) {
categories[std::string(1, letter)].push_back(
std::make_shared<ColorPanel>(
m_resources,
sf::Color(
panel_hue_generator.generate(),
panel_hue_generator.generate(),
panel_hue_generator.generate()
)
)
);
}
}
layout_from_category_map(categories);
}
std::size_t Ribbon::get_layout_column(const std::size_t& button_index) const {
return (m_position + (button_index % 4)) % m_layout.size();
}
const std::shared_ptr<Panel> &Ribbon::get_panel_under_button(std::size_t button_index) const {
jbcoe::polymorphic_value<Panel>& Ribbon::get_panel_under_button(const Data::Button& button) const {
auto button_index = Data::button_to_index(button);
return (
m_layout
.at(this->get_layout_column(button_index))
.at(this->get_layout_column(button))
.at(button_index / 4)
);
}
void Ribbon::click_on(std::size_t button_index) {
this->get_panel_under_button(button_index)->click(*this, button_index);
}
void Ribbon::layout_from_category_map(const std::map<std::string, std::vector<std::shared_ptr<Panel>>> &categories) {
m_layout.clear();
for (auto &&[category, panels] : categories) {
if (not panels.empty()) {
std::vector<std::shared_ptr<Panel>> current_column;
current_column.push_back(std::make_shared<CategoryPanel>(m_resources, category));
for (auto &&panel : panels) {
if (current_column.size() == 3) {
m_layout.push_back({current_column[0], current_column[1], current_column[2]});
current_column.clear();
} else {
current_column.push_back(std::move(panel));
}
}
if (not current_column.empty()) {
while (current_column.size() < 3) {
current_column.push_back(std::make_shared<EmptyPanel>(m_resources));
}
m_layout.push_back({current_column[0], current_column[1], current_column[2]});
}
}
}
fill_layout();
void Ribbon::click_on(const Data::Button& button) {
get_panel_under_button(button)->click(*this, button);
}
void Ribbon::move_right() {
@ -202,10 +105,8 @@ namespace MusicSelect {
m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Left, m_time_factor);
}
void Ribbon::move_to_next_category(const std::size_t& from_button_index) {
std::size_t old_position = m_position;
std::size_t from_column = this->get_layout_column(from_button_index);
void Ribbon::move_to_next_category(const Data::Button& button) {
std::size_t from_column = this->get_layout_column(button);
bool found = false;
size_t offset = 1;
// Cycle through the whole ribbon once starting on the column next to
@ -226,8 +127,10 @@ namespace MusicSelect {
}
if (found) {
// we want the next category panel to land on the same column we clicked
auto old_position = m_position;
auto button_index = Data::button_to_index(button);
auto next_category_column = from_column + offset;
auto onscreen_clicked_column = (from_button_index % 4);
auto onscreen_clicked_column = (button_index % 4);
m_position = next_category_column - onscreen_clicked_column;
m_move_animation.emplace(old_position, m_position, m_layout.size(), Direction::Right, m_time_factor);
}
@ -285,50 +188,8 @@ namespace MusicSelect {
if (debug) {
ImGui::Begin("Ribbon Debug", &debug); {
ImGui::SliderFloat("Time Slowdown Factor", &m_time_factor, 1.f, 10.f);
/*
if (ImGui::CollapsingHeader("Panels")) {
auto panel_size = static_cast<int>(m_panel_size);
if(ImGui::InputInt("Size", &panel_size)) {
if (panel_size < 0) {
panel_size = 0;
}
m_panel_size = static_cast<std::size_t>(panel_size);
}
auto panel_spacing = static_cast<int>(m_panel_spacing);
if(ImGui::InputInt("Spacing", &panel_spacing)) {
if (panel_spacing < 0) {
panel_spacing = 0;
}
m_panel_spacing = static_cast<std::size_t>(panel_spacing);
}
}
*/
}
ImGui::End();
}
}
// Obligatory steps before the drawing functions can use the layout without crashing
void Ribbon::fill_layout() {
if (m_layout.empty()) {
m_layout.push_back({
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
});
m_layout.push_back({
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
std::make_shared<ColoredMessagePanel>(m_resources, sf::Color::Red, "- EMPTY -"),
});
return;
}
while (m_layout.size() < 4) {
m_layout.push_back({
std::make_shared<EmptyPanel>(m_resources),
std::make_shared<EmptyPanel>(m_resources),
std::make_shared<EmptyPanel>(m_resources),
});
}
}
}

View File

@ -5,13 +5,15 @@
#include <SFML/Graphics/Drawable.hpp>
#include <SFML/Graphics/Transformable.hpp>
#include "../../Data/Buttons.hpp"
#include "../../Data/Preferences.hpp"
#include "../../Data/Song.hpp"
#include "../../Toolkit/AffineTransform.hpp"
#include "../../Toolkit/Debuggable.hpp"
#include "../../Toolkit/EasingFunctions.hpp"
#include "Panel.hpp"
#include "SharedResources.hpp"
#include "Panel.hpp"
#include "PanelLayout.hpp"
namespace MusicSelect {
@ -33,29 +35,23 @@ namespace MusicSelect {
Toolkit::AffineTransform<float> create_transform(int previous_pos, int next_pos, size_t ribbon_size, Direction direction);
};
// The Ribbon is the moving part of the Music Select Screen
// It can be sorted in a number of ways
class Ribbon final : public sf::Drawable, public sf::Transformable, public HoldsSharedResources, public Toolkit::Debuggable {
// A Ribbon is a visual representation of a PanelLayout,
// You can scroll it using the left and right buttons
class Ribbon : public sf::Drawable, public sf::Transformable, public HoldsSharedResources, public Toolkit::Debuggable {
public:
Ribbon(SharedResources& t_resources);
void title_sort(const Data::SongList& song_list);
void test_sort();
void test2_sort();
void test_song_cover_sort();
const std::shared_ptr<MusicSelect::Panel>& get_panel_under_button(std::size_t button_index) const;
void click_on(std::size_t button_index);
Ribbon(PanelLayout layout, SharedResources& t_resources);
jbcoe::polymorphic_value<Panel>& get_panel_under_button(const Data::Button& button) const;
void click_on(const Data::Button& button);
void move_right();
void move_left();
void move_to_next_category(const std::size_t& from_button_index);
void move_to_next_category(const Data::Button& button);
void draw_debug() override;
private:
void draw(sf::RenderTarget& target, sf::RenderStates states) const override;
void draw_with_animation(sf::RenderTarget& target, sf::RenderStates states) const;
void draw_without_animation(sf::RenderTarget& target, sf::RenderStates states) const;
void layout_from_category_map(const std::map<std::string,std::vector<std::shared_ptr<Panel>>>& categories);
void fill_layout();
std::size_t get_layout_column(const std::size_t& button_index) const;
std::vector<std::array<std::shared_ptr<Panel>,3>> m_layout;
std::size_t get_layout_column(const Data::Button& button) const;
PanelLayout m_layout;
std::size_t m_position = 0;
mutable std::optional<MoveAnimation> m_move_animation;
float m_time_factor = 1.f;

View File

@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <cereal/cereal.hpp>
#include <cereal/types/string.hpp>
#include <ghc/filesystem.hpp>
template <class Archive>
std::string save_minimal(const Archive &, const ghc::filesystem::path & p) {
return p.string();
}
template <class Archive>
void load_minimal(const Archive &, ghc::filesystem::path& p, const std::string& value) {
p = ghc::filesystem::path{value};
}

View File

@ -0,0 +1,17 @@
#pragma once
#include <functional>
#include <ghc/filesystem.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());
}
};
}