mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2024-11-12 02:00:53 +01:00
More things for jujube markers
This commit is contained in:
parent
4f7ea878b2
commit
5d873eb8da
@ -427,7 +427,7 @@ Fraction EditorState::get_snap_step() const {
|
||||
return Fraction{1, snap};
|
||||
};
|
||||
|
||||
void EditorState::display_playfield(OldMarker& marker, Judgement markerEndingState) {
|
||||
void EditorState::display_playfield(const Marker& marker, Judgement markerEndingState) {
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
|
||||
ImGui::SetNextWindowSizeConstraints(
|
||||
ImVec2(0, 0),
|
||||
@ -467,7 +467,7 @@ void EditorState::display_playfield(OldMarker& marker, Judgement markerEndingSta
|
||||
auto display = VariantVisitor {
|
||||
[&, this](const better::TapNote& tap_note){
|
||||
auto note_offset = (this->current_time() - this->time_at(tap_note.get_time()));
|
||||
auto t = marker.at(markerEndingState, note_offset);
|
||||
const auto t = marker.at(markerEndingState, note_offset);
|
||||
if (t) {
|
||||
ImGui::SetCursorPos({
|
||||
tap_note.get_position().get_x() * squareSize,
|
||||
@ -478,6 +478,7 @@ void EditorState::display_playfield(OldMarker& marker, Judgement markerEndingSta
|
||||
ImGui::PopID();
|
||||
++ImGuiIndex;
|
||||
}
|
||||
|
||||
},
|
||||
[&, this](const better::LongNote& long_note){
|
||||
this->playfield.draw_long_note(
|
||||
|
@ -144,7 +144,7 @@ public:
|
||||
Fraction get_snap_step() const;
|
||||
|
||||
bool show_playfield = true;
|
||||
void display_playfield(OldMarker& marker, Judgement markerEndingState);
|
||||
void display_playfield(const Marker& marker, Judgement markerEndingState);
|
||||
|
||||
bool show_file_properties = false;
|
||||
void display_file_properties();
|
||||
|
216
src/main.cpp
216
src/main.cpp
@ -1,34 +1,34 @@
|
||||
#include <SFML/Window/Keyboard.hpp>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
#include <variant>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <imgui-SFML.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <SFML/Audio/SoundFileFactory.hpp>
|
||||
#include <SFML/Audio/SoundSource.hpp>
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/System/Time.hpp>
|
||||
#include <SFML/Window/Keyboard.hpp>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <fmt/core.h>
|
||||
#include <imgui-SFML.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_stdlib.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tinyfiledialogs.h>
|
||||
#include <variant>
|
||||
#include <whereami++.hpp>
|
||||
|
||||
#include "imgui_extras.hpp"
|
||||
#include "src/custom_sfml_audio/synced_sound_streams.hpp"
|
||||
#include "widgets/blank_screen.hpp"
|
||||
#include "chart_state.hpp"
|
||||
#include "config.hpp"
|
||||
#include "editor_state.hpp"
|
||||
#include "file_dialogs.hpp"
|
||||
#include "history_item.hpp"
|
||||
#include "imgui_extras.hpp"
|
||||
#include "marker.hpp"
|
||||
#include "mp3_reader.hpp"
|
||||
#include "notifications_queue.hpp"
|
||||
#include "src/custom_sfml_audio/synced_sound_streams.hpp"
|
||||
#include "utf8_sfml.hpp"
|
||||
#include "utf8_strings.hpp"
|
||||
#include "widgets/blank_screen.hpp"
|
||||
|
||||
int main() {
|
||||
// TODO : Make the playfield not appear when there's no chart selected
|
||||
@ -40,7 +40,7 @@ int main() {
|
||||
auto assets_folder = executable_folder / "assets";
|
||||
auto settings_folder = executable_folder / "settings";
|
||||
|
||||
config::Config config{settings_folder};
|
||||
config::Config config {settings_folder};
|
||||
|
||||
sf::RenderWindow window(sf::VideoMode(800, 600), "FEIS");
|
||||
window.setVerticalSyncEnabled(true);
|
||||
@ -56,64 +56,61 @@ int main() {
|
||||
if (not std::filesystem::exists(font_path)) {
|
||||
tinyfd_messageBox(
|
||||
"Error",
|
||||
("Could not open "+path_to_utf8_encoded_string(font_path)).c_str(),
|
||||
("Could not open " + path_to_utf8_encoded_string(font_path)).c_str(),
|
||||
"ok",
|
||||
"error",
|
||||
1
|
||||
);
|
||||
1);
|
||||
return -1;
|
||||
}
|
||||
ImGuiIO& IO = ImGui::GetIO();
|
||||
IO.Fonts->Clear();
|
||||
IO.Fonts->AddFontFromFileTTF(
|
||||
path_to_utf8_encoded_string(assets_folder / "fonts" / "NotoSans-Medium.ttf").c_str(),
|
||||
16.f
|
||||
);
|
||||
path_to_utf8_encoded_string(assets_folder / "fonts" / "NotoSans-Medium.ttf")
|
||||
.c_str(),
|
||||
16.f);
|
||||
ImGui::SFML::UpdateFontTexture();
|
||||
|
||||
IO.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||
|
||||
// Loading markers preview
|
||||
std::map<std::filesystem::path, sf::Texture> markerPreviews;
|
||||
std::map<std::filesystem::path, std::shared_ptr<Marker>> markers;
|
||||
for (const auto& folder :
|
||||
std::filesystem::directory_iterator(assets_folder / "textures" / "markers")) {
|
||||
if (folder.is_directory()) {
|
||||
feis::Texture markerPreview;
|
||||
markerPreview.load_from_path(folder.path() / "ma15.png");
|
||||
markerPreview.setSmooth(true);
|
||||
markerPreviews.insert({folder, markerPreview});
|
||||
try {
|
||||
const auto marker = load_marker_from(folder);
|
||||
markers.emplace(folder, marker);
|
||||
} catch (const std::exception& e) {
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<OldMarker> default_marker_opt;
|
||||
try {
|
||||
default_marker_opt = first_available_marker_in(assets_folder);
|
||||
} catch (const std::exception& e) {
|
||||
fmt::print("Couldn't load any marker folder, aborting");
|
||||
if (markers.size() == 0) {
|
||||
fmt::print("Couldn't load any markers, aborting");
|
||||
return -1;
|
||||
}
|
||||
OldMarker& default_marker = *default_marker_opt;
|
||||
|
||||
std::optional<OldMarker> marker_opt;
|
||||
auto marker = markers.begin()->second;
|
||||
if (config.marker.folder) {
|
||||
try {
|
||||
marker_opt = OldMarker(*config.marker.folder);
|
||||
} catch (const std::exception& e) {
|
||||
fmt::print("Failed to load marker from preferences");
|
||||
marker_opt = default_marker_opt;
|
||||
config.marker.folder = marker_opt->get_folder();
|
||||
const auto folder = *config.marker.folder;
|
||||
const auto it = markers.find(folder);
|
||||
if (it != markers.end()) {
|
||||
marker = it->second;
|
||||
} else {
|
||||
try {
|
||||
const auto new_marker = load_marker_from(folder);
|
||||
auto [it, _] = markers.emplace(folder, new_marker);
|
||||
marker = it->second;
|
||||
} catch (const std::exception& e) {
|
||||
fmt::print("Failed to load marker from preferences");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
marker_opt = default_marker_opt;
|
||||
config.marker.folder = marker_opt->get_folder();
|
||||
}
|
||||
OldMarker& marker = *marker_opt;
|
||||
|
||||
if (not config.marker.ending_state) {
|
||||
config.marker.ending_state = Judgement::Perfect;
|
||||
}
|
||||
Judgement& markerEndingState = *config.marker.ending_state;
|
||||
|
||||
BlankScreen bg{assets_folder};
|
||||
BlankScreen bg {assets_folder};
|
||||
std::optional<EditorState> editor_state;
|
||||
NotificationsQueue notificationsQueue;
|
||||
feis::NewChartDialog newChartDialog;
|
||||
@ -132,7 +129,8 @@ int main() {
|
||||
switch (event.type) {
|
||||
case sf::Event::Closed:
|
||||
if (editor_state) {
|
||||
if (editor_state->save_if_needed_and_user_wants_to() != EditorState::SaveOutcome::UserCanceled) {
|
||||
if (editor_state->save_if_needed_and_user_wants_to()
|
||||
!= EditorState::SaveOutcome::UserCanceled) {
|
||||
window.close();
|
||||
}
|
||||
} else {
|
||||
@ -205,8 +203,7 @@ int main() {
|
||||
case sf::Keyboard::Tab:
|
||||
if (editor_state and editor_state->chart_state) {
|
||||
editor_state->chart_state->handle_time_selection_tab(
|
||||
editor_state->current_exact_beats()
|
||||
);
|
||||
editor_state->current_exact_beats());
|
||||
}
|
||||
break;
|
||||
|
||||
@ -226,9 +223,7 @@ int main() {
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Music Volume : {}%",
|
||||
editor_state->get_volume() * 10
|
||||
))
|
||||
);
|
||||
editor_state->get_volume() * 10)));
|
||||
}
|
||||
} else {
|
||||
if (editor_state) {
|
||||
@ -243,9 +238,7 @@ int main() {
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Music Volume : {}%",
|
||||
editor_state->get_volume() * 10
|
||||
))
|
||||
);
|
||||
editor_state->get_volume() * 10)));
|
||||
}
|
||||
} else {
|
||||
if (editor_state) {
|
||||
@ -258,20 +251,19 @@ int main() {
|
||||
if (event.key.shift) {
|
||||
if (editor_state) {
|
||||
editor_state->speed_down();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->get_speed() * 10
|
||||
)));
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->get_speed() * 10)));
|
||||
}
|
||||
} else {
|
||||
if (editor_state and editor_state->chart_state) {
|
||||
editor_state->snap = Toolbox::getPreviousDivisor(240, editor_state->snap);
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Snap : {}",
|
||||
Toolbox::toOrdinal(4 * editor_state->snap)
|
||||
))
|
||||
);
|
||||
editor_state->snap = Toolbox::getPreviousDivisor(
|
||||
240,
|
||||
editor_state->snap);
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Snap : {}",
|
||||
Toolbox::toOrdinal(4 * editor_state->snap))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,20 +273,19 @@ int main() {
|
||||
if (event.key.shift) {
|
||||
if (editor_state) {
|
||||
editor_state->speed_up();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->get_speed() * 10
|
||||
)));
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Speed : {}%",
|
||||
editor_state->get_speed() * 10)));
|
||||
}
|
||||
} else {
|
||||
if (editor_state and editor_state->chart_state) {
|
||||
editor_state->snap = Toolbox::getNextDivisor(240, editor_state->snap);
|
||||
notificationsQueue.push(
|
||||
std::make_shared<TextNotification>(fmt::format(
|
||||
"Snap : {}",
|
||||
Toolbox::toOrdinal(4 * editor_state->snap)
|
||||
))
|
||||
);
|
||||
editor_state->snap = Toolbox::getNextDivisor(
|
||||
240,
|
||||
editor_state->snap);
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(fmt::format(
|
||||
"Snap : {}",
|
||||
Toolbox::toOrdinal(4 * editor_state->snap))));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,13 +341,15 @@ int main() {
|
||||
case sf::Keyboard::Add:
|
||||
if (editor_state) {
|
||||
editor_state->linear_view.zoom_in();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Zoom in"));
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(
|
||||
"Zoom in"));
|
||||
}
|
||||
break;
|
||||
case sf::Keyboard::Subtract:
|
||||
if (editor_state) {
|
||||
editor_state->linear_view.zoom_out();
|
||||
notificationsQueue.push(std::make_shared<TextNotification>("Zoom out"));
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(
|
||||
"Zoom out"));
|
||||
}
|
||||
break;
|
||||
/*
|
||||
@ -374,7 +367,8 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::F:
|
||||
if (editor_state) {
|
||||
config.editor.show_free_buttons = not config.editor.show_free_buttons;
|
||||
config.editor.show_free_buttons =
|
||||
not config.editor.show_free_buttons;
|
||||
}
|
||||
case sf::Keyboard::O:
|
||||
if (event.key.control) {
|
||||
@ -433,7 +427,8 @@ int main() {
|
||||
switch (event.key.code) {
|
||||
case sf::Keyboard::F:
|
||||
if (editor_state) {
|
||||
config.editor.show_free_buttons = not config.editor.show_free_buttons;
|
||||
config.editor.show_free_buttons =
|
||||
not config.editor.show_free_buttons;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
@ -454,11 +449,14 @@ int main() {
|
||||
if (editor_state->get_status() == sf::SoundSource::Playing) {
|
||||
editor_state->previous_playback_position = editor_state->playback_position;
|
||||
if (editor_state->has_any_audio()) {
|
||||
editor_state->playback_position = editor_state->get_precise_playback_position();
|
||||
editor_state->playback_position =
|
||||
editor_state->get_precise_playback_position();
|
||||
} else {
|
||||
editor_state->playback_position = editor_state->current_time() + delta * editor_state->get_pitch();
|
||||
editor_state->playback_position = editor_state->current_time()
|
||||
+ delta * editor_state->get_pitch();
|
||||
}
|
||||
if (editor_state->current_time() > editor_state->get_editable_range().end) {
|
||||
if (editor_state->current_time()
|
||||
> editor_state->get_editable_range().end) {
|
||||
editor_state->pause();
|
||||
}
|
||||
}
|
||||
@ -472,7 +470,7 @@ int main() {
|
||||
editor_state->display_history();
|
||||
}
|
||||
if (editor_state->show_playfield) {
|
||||
editor_state->display_playfield(marker, markerEndingState);
|
||||
editor_state->display_playfield(*marker, markerEndingState);
|
||||
}
|
||||
if (editor_state->show_linear_view) {
|
||||
editor_state->display_linear_view();
|
||||
@ -543,7 +541,8 @@ int main() {
|
||||
if (not editor_state) {
|
||||
editor_state.emplace(assets_folder, config);
|
||||
} else {
|
||||
if (editor_state->save_if_needed_and_user_wants_to() != EditorState::SaveOutcome::UserCanceled) {
|
||||
if (editor_state->save_if_needed_and_user_wants_to()
|
||||
!= EditorState::SaveOutcome::UserCanceled) {
|
||||
editor_state.emplace(assets_folder, config);
|
||||
}
|
||||
}
|
||||
@ -639,7 +638,8 @@ int main() {
|
||||
nullptr,
|
||||
false,
|
||||
editor_state->chart_state.has_value())) {
|
||||
editor_state->erase_chart_and_push_history(editor_state->chart_state->difficulty_name);
|
||||
editor_state->erase_chart_and_push_history(
|
||||
editor_state->chart_state->difficulty_name);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
@ -674,7 +674,8 @@ int main() {
|
||||
}
|
||||
if (ImGui::MenuItem("90° Counter-Clockwise")) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->rotate_selection_90_counter_clockwise(notificationsQueue);
|
||||
editor_state->chart_state->rotate_selection_90_counter_clockwise(
|
||||
notificationsQueue);
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem("180°")) {
|
||||
@ -685,15 +686,23 @@ int main() {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Quantize")) {
|
||||
if (ImGui::MenuItem(fmt::format("To snap ({})", Toolbox::toOrdinal(4 * editor_state->snap)).c_str())) {
|
||||
if (ImGui::MenuItem(fmt::format(
|
||||
"To snap ({})",
|
||||
Toolbox::toOrdinal(4 * editor_state->snap))
|
||||
.c_str())) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->quantize_selection(editor_state->snap, notificationsQueue);
|
||||
editor_state->chart_state->quantize_selection(
|
||||
editor_state->snap,
|
||||
notificationsQueue);
|
||||
}
|
||||
}
|
||||
for (const auto& [snap, color]: config.linear_view.quantization_colors.palette) {
|
||||
for (const auto& [snap, color] :
|
||||
config.linear_view.quantization_colors.palette) {
|
||||
feis::ColorSquare(color);
|
||||
ImGui::SameLine();
|
||||
if (ImGui::MenuItem(fmt::format("To {}##Notes Quantize", Toolbox::toOrdinal(4 * snap)).c_str())) {
|
||||
if (ImGui::MenuItem(
|
||||
fmt::format("To {}##Notes Quantize", Toolbox::toOrdinal(4 * snap))
|
||||
.c_str())) {
|
||||
if (editor_state->chart_state.has_value()) {
|
||||
editor_state->chart_state->quantize_selection(snap, notificationsQueue);
|
||||
}
|
||||
@ -752,22 +761,12 @@ int main() {
|
||||
}
|
||||
if (ImGui::BeginMenu("Marker")) {
|
||||
int i = 0;
|
||||
for (auto& tuple : markerPreviews) {
|
||||
ImGui::PushID(tuple.first.c_str());
|
||||
if (ImGui::ImageButton(tuple.second, {100, 100})) {
|
||||
try {
|
||||
marker = OldMarker(tuple.first);
|
||||
config.marker.folder = marker.get_folder();
|
||||
} catch (const std::exception& e) {
|
||||
tinyfd_messageBox(
|
||||
"Error",
|
||||
e.what(),
|
||||
"ok",
|
||||
"error",
|
||||
1);
|
||||
marker = default_marker;
|
||||
config.marker.folder = marker.get_folder();
|
||||
}
|
||||
for (const auto& [path, marker_ptr] : markers) {
|
||||
ImGui::PushID(path.c_str());
|
||||
const auto preview = marker_ptr->approach_preview();
|
||||
if (ImGui::ImageButton(preview, {100, 100})) {
|
||||
marker = marker_ptr;
|
||||
config.marker.folder = path;
|
||||
}
|
||||
ImGui::PopID();
|
||||
i++;
|
||||
@ -779,7 +778,8 @@ int main() {
|
||||
}
|
||||
if (ImGui::BeginMenu("Marker Ending State")) {
|
||||
for (const auto& [judgement, name] : judgement_to_name) {
|
||||
if (ImGui::ImageButton(marker.preview(judgement), {100, 100})) {
|
||||
const auto preview = marker->judgement_preview(judgement);
|
||||
if (ImGui::ImageButton(preview, {100, 100})) {
|
||||
markerEndingState = judgement;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
131
src/marker.cpp
131
src/marker.cpp
@ -1,20 +1,15 @@
|
||||
#include "marker.hpp"
|
||||
|
||||
#include <SFML/Graphics/Texture.hpp>
|
||||
#include <exception>
|
||||
#include <fmt/core.h>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "sprite_sheet.hpp"
|
||||
#include "utf8_sfml.hpp"
|
||||
#include "utf8_strings.hpp"
|
||||
|
||||
OldMarker first_available_marker_in(const std::filesystem::path& assets_folder) {
|
||||
for (auto& folder : std::filesystem::directory_iterator(assets_folder / "textures" / "markers")) {
|
||||
try {
|
||||
return OldMarker{folder};
|
||||
} catch (const std::runtime_error&) {}
|
||||
}
|
||||
throw std::runtime_error("No valid marker found");
|
||||
}
|
||||
#include "variant_visitor.hpp"
|
||||
|
||||
OldMarker::OldMarker(const std::filesystem::path& folder_):
|
||||
fps(30),
|
||||
@ -59,13 +54,13 @@ OldMarker::OldMarker(const std::filesystem::path& folder_):
|
||||
}
|
||||
};
|
||||
|
||||
OldMarker::optional_texture_reference OldMarker::at(Judgement state, sf::Time offset) const {
|
||||
std::optional<sf::Sprite> OldMarker::at(Judgement state, sf::Time offset) const {
|
||||
const auto frame = static_cast<int>(std::floor(offset.asSeconds() * fps));
|
||||
if (frame < 0) {
|
||||
const auto approach_frames = static_cast<int>(approach.size());
|
||||
const auto index = approach_frames + frame;
|
||||
if (index >= 0 and index < approach_frames) {
|
||||
return approach.at(index);
|
||||
return sf::Sprite{approach.at(index)};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
@ -73,17 +68,21 @@ OldMarker::optional_texture_reference OldMarker::at(Judgement state, sf::Time of
|
||||
|
||||
auto& vec = texture_vector_of(state);
|
||||
if (frame < static_cast<int>(vec.size())) {
|
||||
return std::ref(vec.at(frame));
|
||||
return sf::Sprite{vec.at(frame)};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
OldMarker::texture_reference OldMarker::preview(Judgement state) const {
|
||||
return texture_vector_of(state).at(2);
|
||||
sf::Sprite OldMarker::judgement_preview(Judgement state) const {
|
||||
return sf::Sprite{texture_vector_of(state).at(0)};
|
||||
}
|
||||
|
||||
std::filesystem::path OldMarker::get_folder() const {
|
||||
sf::Sprite OldMarker::approach_preview() const {
|
||||
return sf::Sprite{approach.back()};
|
||||
}
|
||||
|
||||
const std::filesystem::path& OldMarker::get_folder() const {
|
||||
return folder;
|
||||
}
|
||||
|
||||
@ -102,4 +101,106 @@ const OldMarker::texture_vector_type& OldMarker::texture_vector_of(Judgement sta
|
||||
default:
|
||||
throw std::invalid_argument("Unexpected judgement value");
|
||||
}
|
||||
}
|
||||
|
||||
JujubeMarker::JujubeMarker(
|
||||
const std::size_t _fps,
|
||||
const SpriteSheet& _approach,
|
||||
const SpriteSheet& _perfect,
|
||||
const SpriteSheet& _great,
|
||||
const SpriteSheet& _good,
|
||||
const SpriteSheet& _poor,
|
||||
const SpriteSheet& _miss,
|
||||
const std::filesystem::path& _folder
|
||||
) :
|
||||
fps(_fps),
|
||||
approach(_approach),
|
||||
perfect(_perfect),
|
||||
great(_great),
|
||||
good(_good),
|
||||
poor(_poor),
|
||||
miss(_miss),
|
||||
folder(_folder)
|
||||
{}
|
||||
|
||||
|
||||
JujubeMarker JujubeMarker::load_from_folder(const std::filesystem::path& folder) {
|
||||
nowide::ifstream f{path_to_utf8_encoded_string(folder / "marker.json")};
|
||||
const auto j = nlohmann::json::parse(f);
|
||||
return JujubeMarker{
|
||||
j.at("fps").get<std::size_t>(),
|
||||
SpriteSheet::load_from_json(j.at("approach"), folder),
|
||||
SpriteSheet::load_from_json(j.at("perfect"), folder),
|
||||
SpriteSheet::load_from_json(j.at("great"), folder),
|
||||
SpriteSheet::load_from_json(j.at("good"), folder),
|
||||
SpriteSheet::load_from_json(j.at("poor"), folder),
|
||||
SpriteSheet::load_from_json(j.at("miss"), folder),
|
||||
folder
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<sf::Sprite> JujubeMarker::at(Judgement state, sf::Time offset) const {
|
||||
const auto frame = static_cast<int>(std::floor(offset.asSeconds() * fps));
|
||||
if (frame < 0) {
|
||||
const auto approach_frames = static_cast<int>(approach.size());
|
||||
const auto index = approach_frames + frame;
|
||||
if (index >= 0 and index < approach_frames) {
|
||||
return approach.at(index);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
auto& sheet = sprite_sheet_of(state);
|
||||
if (frame < static_cast<int>(sheet.size())) {
|
||||
return sheet.at(frame);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
sf::Sprite JujubeMarker::judgement_preview(Judgement state) const {
|
||||
return sprite_sheet_of(state).at(0);
|
||||
}
|
||||
|
||||
sf::Sprite JujubeMarker::approach_preview() const {
|
||||
return approach.at(approach.size() - 1);
|
||||
}
|
||||
|
||||
const std::filesystem::path& JujubeMarker::get_folder() const {
|
||||
return folder;
|
||||
}
|
||||
|
||||
const SpriteSheet& JujubeMarker::sprite_sheet_of(Judgement state) const {
|
||||
switch (state) {
|
||||
case Judgement::Perfect:
|
||||
return perfect;
|
||||
case Judgement::Great:
|
||||
return great;
|
||||
case Judgement::Good:
|
||||
return good;
|
||||
case Judgement::Poor:
|
||||
return poor;
|
||||
case Judgement::Miss:
|
||||
return miss;
|
||||
default:
|
||||
throw std::invalid_argument("Unexpected judgement value");
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Marker> load_marker_from(const std::filesystem::path& folder) {
|
||||
try {
|
||||
return std::make_shared<OldMarker>(folder);
|
||||
} catch (const std::exception&) {}
|
||||
|
||||
return std::make_shared<JujubeMarker>(std::move(JujubeMarker::load_from_folder(folder)));
|
||||
}
|
||||
|
||||
std::shared_ptr<Marker> first_available_marker_in(const std::filesystem::path& assets_folder) {
|
||||
for (auto& folder : std::filesystem::directory_iterator(assets_folder / "textures" / "markers")) {
|
||||
try {
|
||||
return load_marker_from(folder);
|
||||
} catch (const std::exception&) {}
|
||||
}
|
||||
throw std::runtime_error("No valid marker found");
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Graphics/Sprite.hpp>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
@ -9,9 +7,15 @@
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <SFML/Graphics/Sprite.hpp>
|
||||
|
||||
#include "sprite_sheet.hpp"
|
||||
#include "utf8_sfml_redefinitions.hpp"
|
||||
|
||||
@ -43,21 +47,31 @@ const static std::unordered_map<std::string, Judgement> name_to_judgement = {
|
||||
* Holds the textures associated with a given marker folder from the assets
|
||||
* folder
|
||||
*/
|
||||
class OldMarker {
|
||||
class Marker {
|
||||
public:
|
||||
using texture_type = feis::Texture;
|
||||
using texture_vector_type = std::vector<texture_type>;
|
||||
using texture_reference = std::reference_wrapper<const texture_type>;
|
||||
using optional_texture_reference = std::optional<texture_reference>;
|
||||
|
||||
explicit OldMarker(const std::filesystem::path& folder);
|
||||
optional_texture_reference at(Judgement state, sf::Time offset) const;
|
||||
texture_reference preview(Judgement state) const;
|
||||
virtual std::optional<sf::Sprite> at(Judgement state, sf::Time offset) const = 0;
|
||||
virtual sf::Sprite judgement_preview(Judgement state) const = 0;
|
||||
virtual sf::Sprite approach_preview() const = 0;
|
||||
virtual const std::filesystem::path& get_folder() const = 0;
|
||||
|
||||
std::filesystem::path get_folder() const;
|
||||
virtual ~Marker() = default;
|
||||
};
|
||||
|
||||
class OldMarker : public Marker {
|
||||
public:
|
||||
explicit OldMarker(const std::filesystem::path& folder);
|
||||
|
||||
std::optional<sf::Sprite> at(Judgement state, sf::Time offset) const override;
|
||||
sf::Sprite judgement_preview(Judgement state) const override;
|
||||
sf::Sprite approach_preview() const;
|
||||
|
||||
const std::filesystem::path& get_folder() const override;
|
||||
private:
|
||||
unsigned int fps = 30;
|
||||
|
||||
using texture_type = feis::Texture;
|
||||
using texture_vector_type = std::vector<texture_type>;
|
||||
|
||||
texture_vector_type approach;
|
||||
texture_vector_type perfect;
|
||||
texture_vector_type great;
|
||||
@ -66,16 +80,29 @@ private:
|
||||
texture_vector_type miss;
|
||||
|
||||
const texture_vector_type& texture_vector_of(Judgement state) const;
|
||||
|
||||
std::filesystem::path folder;
|
||||
};
|
||||
|
||||
class JujubeMarker {
|
||||
class JujubeMarker : public Marker {
|
||||
public:
|
||||
explicit JujubeMarker(const std::filesystem::path& folder);
|
||||
std::optional<sf::Sprite> at(Judgement state, sf::Time offset) const;
|
||||
sf::Sprite preview(Judgement state) const;
|
||||
|
||||
std::filesystem::path get_folder() const;
|
||||
explicit JujubeMarker(
|
||||
const std::size_t fps,
|
||||
const SpriteSheet& approach,
|
||||
const SpriteSheet& perfect,
|
||||
const SpriteSheet& great,
|
||||
const SpriteSheet& good,
|
||||
const SpriteSheet& poor,
|
||||
const SpriteSheet& miss,
|
||||
const std::filesystem::path& folder
|
||||
);
|
||||
static JujubeMarker load_from_folder(const std::filesystem::path& folder);
|
||||
|
||||
std::optional<sf::Sprite> at(Judgement state, sf::Time offset) const override;
|
||||
sf::Sprite judgement_preview(Judgement state) const override;
|
||||
sf::Sprite approach_preview() const;
|
||||
|
||||
const std::filesystem::path& get_folder() const override;
|
||||
private:
|
||||
std::size_t fps;
|
||||
|
||||
@ -85,6 +112,11 @@ private:
|
||||
SpriteSheet good;
|
||||
SpriteSheet poor;
|
||||
SpriteSheet miss;
|
||||
|
||||
const SpriteSheet& sprite_sheet_of(Judgement state) const;
|
||||
|
||||
std::filesystem::path folder;
|
||||
};
|
||||
|
||||
OldMarker first_available_marker_in(const std::filesystem::path& assets_folder);
|
||||
std::shared_ptr<Marker> load_marker_from(const std::filesystem::path& folder);
|
||||
std::shared_ptr<Marker> first_available_marker_in(const std::filesystem::path& assets_folder);
|
||||
|
@ -195,8 +195,8 @@ void Playfield::draw_long_note(
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playback_position,
|
||||
const better::Timing& timing,
|
||||
OldMarker& marker,
|
||||
Judgement& markerEndingState
|
||||
const Marker& marker,
|
||||
const Judgement& markerEndingState
|
||||
) {
|
||||
draw_tail_and_receptor(note, playback_position, timing);
|
||||
|
||||
@ -210,28 +210,28 @@ void Playfield::draw_long_note(
|
||||
// Display the beginning marker
|
||||
auto t = marker.at(markerEndingState, note_offset);
|
||||
if (t) {
|
||||
const float scale = square_size / t->get().getSize().x;
|
||||
marker_sprite.setTexture(*t, true);
|
||||
marker_sprite.setScale(scale, scale);
|
||||
marker_sprite.setPosition(
|
||||
const float x_scale = square_size / t->getTextureRect().width;
|
||||
const float y_scale = square_size / t->getTextureRect().height;
|
||||
t->setScale(x_scale, y_scale);
|
||||
t->setPosition(
|
||||
note.get_position().get_x() * square_size,
|
||||
note.get_position().get_y() * square_size
|
||||
);
|
||||
marker_layer.draw(marker_sprite);
|
||||
marker_layer.draw(*t);
|
||||
}
|
||||
|
||||
} else {
|
||||
const auto tail_end_offset = playback_position - tail_end;
|
||||
auto t = marker.at(markerEndingState, tail_end_offset);
|
||||
if (t) {
|
||||
const float scale = square_size / t->get().getSize().x;
|
||||
marker_sprite.setTexture(*t, true);
|
||||
marker_sprite.setScale(scale, scale);
|
||||
marker_sprite.setPosition(
|
||||
const float x_scale = square_size / t->getTextureRect().width;
|
||||
const float y_scale = square_size / t->getTextureRect().height;
|
||||
t->setScale(x_scale, y_scale);
|
||||
t->setPosition(
|
||||
note.get_position().get_x() * square_size,
|
||||
note.get_position().get_y() * square_size
|
||||
);
|
||||
marker_layer.draw(marker_sprite);
|
||||
marker_layer.draw(*t);
|
||||
}
|
||||
}
|
||||
}
|
@ -52,8 +52,8 @@ public:
|
||||
const better::LongNote& note,
|
||||
const sf::Time& playbackPosition,
|
||||
const better::Timing& timing,
|
||||
OldMarker& marker,
|
||||
Judgement& markerEndingState
|
||||
const Marker& marker,
|
||||
const Judgement& markerEndingState
|
||||
);
|
||||
|
||||
private:
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include "utf8_strings.hpp"
|
||||
|
||||
SpriteSheet::SpriteSheet(
|
||||
const std::filesystem::path& texture_path,
|
||||
std::size_t count,
|
||||
@ -61,17 +63,38 @@ SpriteSheet::SpriteSheet(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
{
|
||||
"sprite_sheet": "approach.png",
|
||||
"count": 16,
|
||||
"columns": 4,
|
||||
"rows": 4
|
||||
}
|
||||
*/
|
||||
SpriteSheet SpriteSheet::load_from_json(
|
||||
const nlohmann::json& dict,
|
||||
const nlohmann::json& obj,
|
||||
const std::filesystem::path& parent_folder
|
||||
) {
|
||||
|
||||
auto texture_path = to_path(obj.at("sprite_sheet").get<std::string>());
|
||||
if (texture_path.is_relative()) {
|
||||
texture_path = parent_folder / texture_path;
|
||||
}
|
||||
|
||||
const auto count = obj.at("count").get<std::size_t>();
|
||||
const auto columns = obj.at("columns").get<std::size_t>();
|
||||
const auto rows = obj.at("rows").get<std::size_t>();
|
||||
|
||||
return SpriteSheet{
|
||||
texture_path,
|
||||
count,
|
||||
columns,
|
||||
rows
|
||||
};
|
||||
}
|
||||
|
||||
std::optional<sf::Sprite> SpriteSheet::at(std::size_t frame) const {
|
||||
sf::Sprite SpriteSheet::at(std::size_t frame) const {
|
||||
if (frame >= count) {
|
||||
return {};
|
||||
throw std::out_of_range(fmt::format("frame {} is outside of the SpriteSheet range ({})", frame, count));
|
||||
}
|
||||
|
||||
sf::Sprite sprite{tex};
|
||||
@ -84,4 +107,8 @@ std::optional<sf::Sprite> SpriteSheet::at(std::size_t frame) const {
|
||||
};
|
||||
sprite.setTextureRect(rect);
|
||||
return sprite;
|
||||
}
|
||||
|
||||
std::size_t SpriteSheet::size() const {
|
||||
return count;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <SFML/System/Vector2.hpp>
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
@ -23,7 +24,8 @@ public:
|
||||
const std::filesystem::path& parent_folder
|
||||
);
|
||||
|
||||
std::optional<sf::Sprite> at(std::size_t frame) const;
|
||||
sf::Sprite at(std::size_t frame) const;
|
||||
std::size_t size() const;
|
||||
|
||||
private:
|
||||
feis::Texture tex;
|
||||
|
@ -2,14 +2,12 @@
|
||||
uses nowide under the hood */
|
||||
#pragma once
|
||||
|
||||
#include <SFML/System/FileInputStream.hpp>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <nowide/fstream.hpp>
|
||||
#include <SFML/Audio/Music.hpp>
|
||||
#include <SFML/Graphics/Texture.hpp>
|
||||
#include <SFML/System/FileInputStream.hpp>
|
||||
|
||||
#include "utf8_file_input_stream.hpp"
|
||||
|
||||
@ -19,7 +17,7 @@ namespace feis {
|
||||
load_from_file() and thus don't need the file stream to remain available
|
||||
after the call to load_from_file() */
|
||||
template<class T>
|
||||
class LoadFromPathMixin : public T {
|
||||
class UTF8Loader : public T {
|
||||
public:
|
||||
bool load_from_path(const std::filesystem::path& file) {
|
||||
UTF8FileInputStream f;
|
||||
@ -30,12 +28,21 @@ namespace feis {
|
||||
}
|
||||
};
|
||||
|
||||
class HoldsFileStream {
|
||||
public:
|
||||
virtual ~HoldsFileStream() = default;
|
||||
protected:
|
||||
UTF8FileInputStream file_stream;
|
||||
};
|
||||
|
||||
/* UTF8-aware wrapper around SFML resource classes that "open" files, i.e.
|
||||
resource classes that just store the file stream when open_from_path() is
|
||||
called and stream the file contents on demand and hence require the file
|
||||
stream to remain available for the whole lifetime of the resource */
|
||||
template<class T>
|
||||
class HoldFileStreamMixin : public T {
|
||||
class UTF8Streamer : public HoldsFileStream, public T {
|
||||
/* The file_stream is kept in a base class to make sure it is destroyed
|
||||
AFTER the SFML class that uses it */
|
||||
public:
|
||||
bool open_from_path(const std::filesystem::path& file) {
|
||||
if (not file_stream.open(file)) {
|
||||
@ -43,7 +50,5 @@ namespace feis {
|
||||
};
|
||||
return this->openFromStream(file_stream);
|
||||
}
|
||||
protected:
|
||||
UTF8FileInputStream file_stream;
|
||||
};
|
||||
}
|
@ -3,12 +3,13 @@
|
||||
#include <SFML/Audio.hpp>
|
||||
#include <SFML/Audio/InputSoundFile.hpp>
|
||||
#include <SFML/Audio/SoundBuffer.hpp>
|
||||
#include <SFML/Graphics/Texture.hpp>
|
||||
|
||||
#include "utf8_sfml.hpp"
|
||||
|
||||
namespace feis {
|
||||
using Music = feis::HoldFileStreamMixin<sf::Music>;
|
||||
using InputSoundFile = feis::HoldFileStreamMixin<sf::InputSoundFile>;
|
||||
using SoundBuffer = feis::LoadFromPathMixin<sf::SoundBuffer>;
|
||||
using Texture = feis::LoadFromPathMixin<sf::Texture>;
|
||||
using Music = feis::UTF8Streamer<sf::Music>;
|
||||
using InputSoundFile = feis::UTF8Streamer<sf::InputSoundFile>;
|
||||
using SoundBuffer = feis::UTF8Loader<sf::SoundBuffer>;
|
||||
using Texture = feis::UTF8Loader<sf::Texture>;
|
||||
}
|
Loading…
Reference in New Issue
Block a user