More things for jujube markers

This commit is contained in:
Stepland 2023-07-11 21:03:37 +02:00
parent 4f7ea878b2
commit 5d873eb8da
11 changed files with 346 additions and 177 deletions

View File

@ -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(

View File

@ -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();

View File

@ -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();

View File

@ -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");
}

View File

@ -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);

View File

@ -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);
}
}
}

View File

@ -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:

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
};
}

View File

@ -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>;
}