mirror of
https://gitlab.com/square-game-liberation-front/F.E.I.S.git
synced 2025-02-23 05:30:00 +01:00
Rewriting memon parsing
This commit is contained in:
parent
f32b642fe4
commit
64a952ce83
@ -3,7 +3,7 @@
|
||||
#include <json.hpp>
|
||||
|
||||
namespace better {
|
||||
nlohmann::ordered_json Chart::dump_for_memon_1_0_0(
|
||||
nlohmann::ordered_json Chart::dump_to_memon_1_0_0(
|
||||
const nlohmann::ordered_json& fallback_timing_object
|
||||
) const {
|
||||
nlohmann::ordered_json json_chart;
|
||||
@ -14,9 +14,28 @@ namespace better {
|
||||
if (not chart_timing.empty()) {
|
||||
json_chart["timing"] = chart_timing;
|
||||
}
|
||||
json_chart["notes"] = notes.dump_for_memon_1_0_0();
|
||||
json_chart["notes"] = notes.dump_to_memon_1_0_0();
|
||||
};
|
||||
|
||||
Chart Chart::load_from_memon_legacy(const nlohmann::json& json, const Timing& timing) {
|
||||
Chart chart {
|
||||
.level = Decimal{json["level"].get<int>()},
|
||||
.timing = timing,
|
||||
.hakus = {},
|
||||
.notes = {}
|
||||
};
|
||||
const auto resolution = json["resolution"].get<unsigned int>();
|
||||
for (auto& json_note : json.at("notes")) {
|
||||
try {
|
||||
const auto note = load_legacy_note(json_note, resolution);
|
||||
chart.notes.insert(note);
|
||||
} catch (const std::exception&) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return chart;
|
||||
}
|
||||
|
||||
nlohmann::ordered_json remove_common_keys(
|
||||
const nlohmann::ordered_json& object,
|
||||
const nlohmann::ordered_json& fallback
|
||||
@ -40,7 +59,7 @@ namespace better {
|
||||
const nlohmann::ordered_json& fallback_timing_object
|
||||
) {
|
||||
auto complete_song_timing = nlohmann::ordered_json::object();
|
||||
complete_song_timing.update(timing.dump_for_memon_1_0_0());
|
||||
complete_song_timing.update(timing.dump_to_memon_1_0_0());
|
||||
if (hakus) {
|
||||
complete_song_timing["hakus"] = dump_hakus(*hakus);
|
||||
}
|
||||
|
@ -18,9 +18,14 @@ namespace better {
|
||||
std::optional<Hakus> hakus;
|
||||
Notes notes;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0(
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0(
|
||||
const nlohmann::ordered_json& fallback_timing_object
|
||||
) const;
|
||||
|
||||
static Chart load_from_memon_legacy(
|
||||
const nlohmann::json& json,
|
||||
const Timing& timing
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "better_metadata.hpp"
|
||||
|
||||
namespace better {
|
||||
nlohmann::ordered_json Metadata::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json Metadata::dump_to_memon_1_0_0() const {
|
||||
nlohmann::ordered_json json_metadata;
|
||||
if (not title.empty()) {
|
||||
json_metadata["title"] = title;
|
||||
@ -29,4 +29,13 @@ namespace better {
|
||||
}
|
||||
return json_metadata;
|
||||
};
|
||||
|
||||
Metadata Metadata::load_from_memon_legacy(const nlohmann::json& json) {
|
||||
Metadata metadata;
|
||||
json["song title"].get_to(metadata.title);
|
||||
json["artist"].get_to(metadata.artist);
|
||||
json["music path"].get_to(metadata.audio);
|
||||
json["jacket path"].get_to(metadata.jacket);
|
||||
return metadata;
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ namespace better {
|
||||
std::string preview_file = "";
|
||||
bool use_preview_file = false;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
static Metadata load_from_memon_legacy(const nlohmann::json& json);
|
||||
};
|
||||
}
|
@ -50,7 +50,7 @@ namespace better {
|
||||
return position;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json TapNote::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json TapNote::dump_to_memon_1_0_0() const {
|
||||
return {
|
||||
{"n", position.index()},
|
||||
{"t", beat_to_best_form(time)}
|
||||
@ -135,7 +135,7 @@ namespace better {
|
||||
}
|
||||
};
|
||||
|
||||
nlohmann::ordered_json LongNote::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json LongNote::dump_to_memon_1_0_0() const {
|
||||
return {
|
||||
{"n", position.index()},
|
||||
{"t", beat_to_best_form(time)},
|
||||
@ -151,6 +151,31 @@ namespace better {
|
||||
return 3 + tail_tip.get_y() - int(tail_tip.get_y() > position.get_y());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* legacy long note tail index is given relative to the note position :
|
||||
*
|
||||
* 8
|
||||
* 4
|
||||
* 0
|
||||
* 11 7 3 . 1 5 9
|
||||
* 2
|
||||
* 6
|
||||
* 10
|
||||
*/
|
||||
Position legacy_memon_tail_index_to_position(const Position& pos, unsigned int tail_index) {
|
||||
auto length = (tail_index / 4) + 1;
|
||||
switch (tail_index % 4) {
|
||||
case 0: // up
|
||||
return {pos.get_x(), pos.get_y() - length};
|
||||
case 1: // right
|
||||
return {pos.get_x() + length, pos.get_y()};
|
||||
case 2: // down
|
||||
return {pos.get_x(), pos.get_y() + length};
|
||||
case 3: // left
|
||||
return {pos.get_x() - length, pos.get_y()};
|
||||
}
|
||||
}
|
||||
|
||||
auto _time_bounds = VariantVisitor {
|
||||
[](const TapNote& t) -> std::pair<Fraction, Fraction> { return {t.get_time(), t.get_time()}; },
|
||||
@ -173,8 +198,31 @@ namespace better {
|
||||
return this->get_time_bounds().second;
|
||||
}
|
||||
|
||||
nlohmann::ordered_json Note::dump_for_memon_1_0_0() const {
|
||||
return std::visit([](const auto& n){return n.dump_for_memon_1_0_0();}, this->note);
|
||||
nlohmann::ordered_json Note::dump_to_memon_1_0_0() const {
|
||||
return std::visit([](const auto& n){return n.dump_to_memon_1_0_0();}, this->note);
|
||||
}
|
||||
|
||||
Note Note::load_from_memon_legacy(const nlohmann::json& json, unsigned int resolution) {
|
||||
const auto position = Position{json["n"].get<unsigned int>()};
|
||||
const auto time = Fraction{
|
||||
json["t"].get<unsigned int>(),
|
||||
resolution,
|
||||
};
|
||||
const auto duration = Fraction{
|
||||
json["l"].get<unsigned int>(),
|
||||
resolution,
|
||||
};
|
||||
const auto tail_index = json["n"].get<unsigned int>();
|
||||
if (duration > 0) {
|
||||
return LongNote{
|
||||
time,
|
||||
position,
|
||||
duration,
|
||||
legacy_memon_tail_index_to_position(position, tail_index)
|
||||
};
|
||||
} else {
|
||||
return TapNote{time, position};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ namespace better {
|
||||
|
||||
bool operator==(const TapNote&) const = default;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
private:
|
||||
Fraction time;
|
||||
Position position;
|
||||
@ -68,7 +68,7 @@ namespace better {
|
||||
|
||||
bool operator==(const LongNote&) const = default;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
int tail_as_6_notation() const;
|
||||
private:
|
||||
Fraction time;
|
||||
@ -77,6 +77,8 @@ namespace better {
|
||||
Position tail_tip;
|
||||
};
|
||||
|
||||
Position legacy_memon_tail_index_to_position(const Position& pos, unsigned int tail_index);
|
||||
|
||||
class Note {
|
||||
public:
|
||||
template<typename ...Ts>
|
||||
@ -91,7 +93,12 @@ namespace better {
|
||||
|
||||
bool operator==(const Note&) const = default;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
static Note load_from_memon_legacy(
|
||||
const nlohmann::json& json,
|
||||
unsigned int resolution
|
||||
);
|
||||
private:
|
||||
std::variant<TapNote, LongNote> note;
|
||||
};
|
||||
|
@ -99,10 +99,10 @@ namespace better {
|
||||
return found_collision;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json Notes::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json Notes::dump_to_memon_1_0_0() const {
|
||||
auto json_notes = nlohmann::ordered_json::array();
|
||||
for (const auto& [_, note] : *this) {
|
||||
json_notes.push_back(note.dump_for_memon_1_0_0());
|
||||
json_notes.push_back(note.dump_to_memon_1_0_0());
|
||||
}
|
||||
return json_notes;
|
||||
}
|
||||
|
@ -32,6 +32,6 @@ namespace better {
|
||||
*/
|
||||
bool is_colliding(const better::Note& note, const better::Timing& timing);
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
};
|
||||
}
|
@ -1,10 +1,18 @@
|
||||
#include "better_song.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <SFML/System/Time.hpp>
|
||||
|
||||
#include "better_hakus.hpp"
|
||||
#include "better_timing.hpp"
|
||||
#include "json.hpp"
|
||||
#include "src/better_chart.hpp"
|
||||
#include "src/better_hakus.hpp"
|
||||
#include "src/better_metadata.hpp"
|
||||
#include "src/better_note.hpp"
|
||||
#include "src/special_numeric_types.hpp"
|
||||
#include "std_optional_extras.hpp"
|
||||
#include "variant_visitor.hpp"
|
||||
|
||||
@ -13,10 +21,10 @@ namespace better {
|
||||
return stringify_or(level, "(no level defined)");
|
||||
};
|
||||
|
||||
nlohmann::ordered_json Song::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json Song::dump_to_memon_1_0_0() const {
|
||||
nlohmann::ordered_json memon;
|
||||
memon["version"] = "1.0.0";
|
||||
auto json_metadata = metadata.dump_for_memon_1_0_0();
|
||||
auto json_metadata = metadata.dump_to_memon_1_0_0();
|
||||
if (not json_metadata.empty()) {
|
||||
memon["metadata"] = json_metadata;
|
||||
}
|
||||
@ -30,8 +38,79 @@ namespace better {
|
||||
fallback_timing_object.update(song_timing);
|
||||
auto json_charts = nlohmann::ordered_json::object();
|
||||
for (const auto& [name, chart] : charts) {
|
||||
json_charts[name] = chart.dump_for_memon_1_0_0(fallback_timing_object);
|
||||
json_charts[name] = chart.dump_to_memon_1_0_0(fallback_timing_object);
|
||||
}
|
||||
return memon;
|
||||
}
|
||||
|
||||
Song Song::load_from_memon(const nlohmann::json& memon) {
|
||||
if (not memon.is_object()) {
|
||||
throw std::invalid_argument(
|
||||
"The json file you tried to load does not contain an object "
|
||||
"at the top level. This is required for a json file to be a "
|
||||
"valid memon file."
|
||||
);
|
||||
}
|
||||
if (not memon.contains("version")) {
|
||||
return Song::load_from_memon_legacy(memon);
|
||||
}
|
||||
if (not memon["version"].is_string()) {
|
||||
throw std::invalid_argument(
|
||||
"The json file you tried to load has a 'version' key at the "
|
||||
"top level but its associated value is not a string. This is "
|
||||
"required for a json file to be a valid memon file."
|
||||
);
|
||||
}
|
||||
const auto version = memon["version"].get<std::string>();
|
||||
if (version == "1.0.0") {
|
||||
return Song::load_from_memon_1_0_0(memon);
|
||||
} else if (version == "0.3.0") {
|
||||
return Song::load_from_memon_0_3_0(memon);
|
||||
} else if (version == "0.2.0") {
|
||||
return Song::load_from_memon_0_2_0(memon);
|
||||
} else if (version == "0.1.0") {
|
||||
return Song::load_from_memon_0_1_0(memon);
|
||||
} else {
|
||||
throw std::invalid_argument(fmt::format(
|
||||
"Unknown memon version : {}",
|
||||
version
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Song Song::load_from_memon_1_0_0(const nlohmann::json& memon) {
|
||||
|
||||
};
|
||||
|
||||
Song Song::load_from_memon_0_3_0(const nlohmann::json& memon) {
|
||||
|
||||
};
|
||||
|
||||
Song Song::load_from_memon_0_2_0(const nlohmann::json& memon) {
|
||||
|
||||
};
|
||||
|
||||
Song Song::load_from_memon_0_1_0(const nlohmann::json& memon) {
|
||||
|
||||
};
|
||||
|
||||
Song Song::load_from_memon_legacy(const nlohmann::json& memon) {
|
||||
const auto json_metadata = memon["metadata"];
|
||||
const auto metadata = Metadata::load_from_memon_legacy(memon["metadata"]);
|
||||
const auto bpm = Decimal{json_metadata["BPM"].get<std::string>()};
|
||||
const auto offset = convert_to_fraction(Decimal{json_metadata["offset"].get<std::string>()});
|
||||
const auto timing = Timing::load_from_memon_legacy(bpm, offset);
|
||||
Song song{
|
||||
.charts = {},
|
||||
.metadata = metadata,
|
||||
.timing = timing,
|
||||
.hakus = {}
|
||||
};
|
||||
for (const auto& chart_json : memon["data"]) {
|
||||
const auto dif = chart_json["dif_name"].get<std::string>();
|
||||
const auto chart = Chart::load_from_memon_legacy(chart_json, timing);
|
||||
song.charts[dif] = chart;
|
||||
}
|
||||
return song;
|
||||
};
|
||||
}
|
@ -47,6 +47,51 @@ namespace better {
|
||||
Timing timing;
|
||||
std::optional<Hakus> hakus;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
/*
|
||||
Read the json file as memon by trying to guess version.
|
||||
Throws various exceptions on error
|
||||
*/
|
||||
static Song load_from_memon(const nlohmann::json& memon);
|
||||
|
||||
/*
|
||||
Read the json file as memon v1.0.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v1-0-0
|
||||
*/
|
||||
static Song load_from_memon_1_0_0(const nlohmann::json& memon);
|
||||
|
||||
/*
|
||||
Read the json file as memon v0.3.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-3-0
|
||||
*/
|
||||
static Song load_from_memon_0_3_0(const nlohmann::json& memon);
|
||||
|
||||
/*
|
||||
Read the json file as memon v0.2.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-2-0
|
||||
*/
|
||||
static Song load_from_memon_0_2_0(const nlohmann::json& memon);
|
||||
|
||||
/*
|
||||
Read the json file as memon v0.1.0.
|
||||
https://memon-spec.readthedocs.io/en/latest/changelog.html#v0-1-0
|
||||
*/
|
||||
static Song load_from_memon_0_1_0(const nlohmann::json& memon);
|
||||
|
||||
/*
|
||||
Read the json file as a "legacy" (pre-versionning) memon file.
|
||||
|
||||
Notable quirks of this archaïc schema :
|
||||
- "data" is an array of charts
|
||||
- the difficulty name of a chart is stored as "dif_name" in the chart
|
||||
object
|
||||
- the album cover path field is named "jacket path"
|
||||
*/
|
||||
static Song load_from_memon_legacy(const nlohmann::json& memon);
|
||||
};
|
||||
|
||||
Note load_legacy_note(const nlohmann::json& legacy_note, unsigned int resolution);
|
||||
|
||||
Position legacy_memon_tail_index_to_position(const Position& pos, unsigned int tail_index);
|
||||
}
|
@ -184,7 +184,7 @@ namespace better {
|
||||
return bpm_change->get_beats() + beats_since_previous_event;
|
||||
};
|
||||
|
||||
nlohmann::ordered_json Timing::dump_for_memon_1_0_0() const {
|
||||
nlohmann::ordered_json Timing::dump_to_memon_1_0_0() const {
|
||||
nlohmann::ordered_json j;
|
||||
const auto offset = fractional_seconds_at(0);
|
||||
j["offset"] = convert_to_decimal(offset, 5).format("f");
|
||||
@ -198,4 +198,14 @@ namespace better {
|
||||
j["bpms"] = bpms;
|
||||
return j;
|
||||
}
|
||||
|
||||
/*
|
||||
For this function, offset is the OPPOSITE of the time (in seconds) at which
|
||||
the first beat occurs in the music file
|
||||
*/
|
||||
Timing Timing::load_from_memon_legacy(Decimal bpm, Fraction offset) {
|
||||
const BPMAtBeat bpm_at_beat{0, bpm};
|
||||
const SecondsAtBeat seconds_at_beat{-1 * offset, 0};
|
||||
return Timing{{bpm_at_beat}, seconds_at_beat};
|
||||
}
|
||||
}
|
@ -56,7 +56,9 @@ namespace better {
|
||||
|
||||
Fraction beats_at(sf::Time time) const;
|
||||
|
||||
nlohmann::ordered_json dump_for_memon_1_0_0() const;
|
||||
nlohmann::ordered_json dump_to_memon_1_0_0() const;
|
||||
|
||||
static Timing load_from_memon_legacy(Decimal bpm, Fraction offset);
|
||||
|
||||
private:
|
||||
std::set<BPMEvent, decltype(order_by_beats)> events_by_beats{order_by_beats};
|
||||
|
@ -537,36 +537,62 @@ void EditorState::display_linear_view() {
|
||||
ImGui::PopStyleVar(2);
|
||||
};
|
||||
|
||||
UserWantsToSave EditorState::ask_to_save_if_needed() {
|
||||
if (chart_state and (not chart_state->history.current_state_is_saved())) {
|
||||
int response_code = tinyfd_messageBox(
|
||||
"Warning",
|
||||
"Do you want to save changes ?",
|
||||
"yesnocancel",
|
||||
"warning",
|
||||
1
|
||||
);
|
||||
switch (response_code) {
|
||||
// cancel
|
||||
case 0:
|
||||
return UserWantsToSave::Cancel;
|
||||
// yes
|
||||
case 1:
|
||||
return UserWantsToSave::Yes;
|
||||
// no
|
||||
case 2:
|
||||
return UserWantsToSave::No;
|
||||
default:
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Got unexcpected response code from tinyfd_messageBox : {}",
|
||||
response_code
|
||||
));
|
||||
}
|
||||
bool EditorState::needs_to_save() const {
|
||||
if (chart_state) {
|
||||
return not chart_state->history.current_state_is_saved();
|
||||
} else {
|
||||
return UserWantsToSave::DidNotDisplayDialog;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
EditorState::UserWantsToSave EditorState::ask_if_user_wants_to_save() const {
|
||||
int response_code = tinyfd_messageBox(
|
||||
"Warning",
|
||||
"Do you want to save changes ?",
|
||||
"yesnocancel",
|
||||
"warning",
|
||||
1
|
||||
);
|
||||
switch (response_code) {
|
||||
// cancel
|
||||
case 0:
|
||||
return EditorState::UserWantsToSave::Cancel;
|
||||
// yes
|
||||
case 1:
|
||||
return EditorState::UserWantsToSave::Yes;
|
||||
// no
|
||||
case 2:
|
||||
return EditorState::UserWantsToSave::No;
|
||||
default:
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Got unexcpected response code from tinyfd_messageBox : {}",
|
||||
response_code
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
EditorState::SaveOutcome EditorState::save_if_needed() {
|
||||
if (not needs_to_save()) {
|
||||
return EditorState::SaveOutcome::NoSavingNeeded;
|
||||
}
|
||||
switch (ask_if_user_wants_to_save()) {
|
||||
case EditorState::UserWantsToSave::Yes:
|
||||
{
|
||||
const auto path = ask_for_save_path_if_needed();
|
||||
if (not path) {
|
||||
return EditorState::SaveOutcome::UserCanceled;
|
||||
} else {
|
||||
save(*path);
|
||||
return EditorState::SaveOutcome::UserSaved;
|
||||
}
|
||||
}
|
||||
case EditorState::UserWantsToSave::No:
|
||||
return EditorState::SaveOutcome::UserDeclindedSaving;
|
||||
case EditorState::UserWantsToSave::Cancel:
|
||||
return EditorState::SaveOutcome::UserCanceled;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> EditorState::ask_for_save_path_if_needed() {
|
||||
if (song_path) {
|
||||
return song_path;
|
||||
@ -575,22 +601,6 @@ std::optional<std::filesystem::path> EditorState::ask_for_save_path_if_needed()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Saves if asked and returns false if user canceled
|
||||
*/
|
||||
bool EditorState::save_changes_or_cancel() {
|
||||
switch (ask_to_save_if_needed()) {
|
||||
case UserWantsToSave::Yes:
|
||||
save();
|
||||
case UserWantsToSave::No:
|
||||
case UserWantsToSave::DidNotDisplayDialog:
|
||||
return true;
|
||||
case UserWantsToSave::Cancel:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void EditorState::move_backwards_in_time() {
|
||||
auto beats = current_exact_beats();
|
||||
if (beats % get_snap_step() == 0) {
|
||||
@ -621,7 +631,7 @@ void EditorState::undo(NotificationsQueue& nq) {
|
||||
|
||||
void EditorState::redo(NotificationsQueue& nq) {
|
||||
if (chart_state) {
|
||||
auto next = chart_state->history.gpop_next();
|
||||
auto next = chart_state->history.pop_next();
|
||||
if (next) {
|
||||
nq.push(std::make_shared<RedoNotification>(**next));
|
||||
(*next)->doAction(*this);
|
||||
@ -731,7 +741,7 @@ void EditorState::open_chart(better::Chart& chart, const std::string& name) {
|
||||
};
|
||||
|
||||
void EditorState::save(const std::filesystem::path& path) {
|
||||
const auto memon = song.dump_for_memon_1_0_0();
|
||||
const auto memon = song.dump_to_memon_1_0_0();
|
||||
nowide::ofstream file{path};
|
||||
if (not file) {
|
||||
throw std::runtime_error(
|
||||
@ -750,16 +760,16 @@ void EditorState::save(const std::filesystem::path& path) {
|
||||
}
|
||||
}
|
||||
|
||||
void ESHelper::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
|
||||
void feis::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
|
||||
const char* _filepath =
|
||||
tinyfd_openFileDialog("Open File", nullptr, 0, nullptr, nullptr, false);
|
||||
if (_filepath != nullptr) {
|
||||
auto filepath = std::filesystem::path{_filepath};
|
||||
ESHelper::openFromFile(ed, filepath, assets, settings);
|
||||
feis::openFromFile(ed, filepath, assets, settings);
|
||||
}
|
||||
}
|
||||
|
||||
void ESHelper::openFromFile(
|
||||
void feis::openFromFile(
|
||||
std::optional<EditorState>& ed,
|
||||
std::filesystem::path song_path,
|
||||
std::filesystem::path assets,
|
||||
@ -775,22 +785,10 @@ void ESHelper::openFromFile(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* returns true if user saved or if saving wasn't necessary
|
||||
* returns false if user canceled
|
||||
*/
|
||||
bool ESHelper::saveOrCancel(std::optional<EditorState>& ed) {
|
||||
if (ed) {
|
||||
return ed->save_changes_or_cancel();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the newly created chart if there is one
|
||||
*/
|
||||
std::optional<better::Chart> ESHelper::NewChartDialog::display(EditorState& editorState) {
|
||||
std::optional<better::Chart> feis::NewChartDialog::display(EditorState& editorState) {
|
||||
std::optional<better::Chart> newChart;
|
||||
if (ImGui::Begin(
|
||||
"New Chart",
|
||||
@ -876,7 +874,7 @@ std::optional<better::Chart> ESHelper::NewChartDialog::display(EditorState& edit
|
||||
return newChart;
|
||||
}
|
||||
|
||||
void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::filesystem::path assets) {
|
||||
void feis::ChartPropertiesDialog::display(EditorState& editorState, std::filesystem::path assets) {
|
||||
assert(editorState.chart.has_value());
|
||||
|
||||
if (this->shouldRefreshValues) {
|
||||
|
@ -19,15 +19,6 @@
|
||||
#include "widgets/linear_view.hpp"
|
||||
#include "widgets/playfield.hpp"
|
||||
|
||||
class ActionWithMessage;
|
||||
class OpenChart;
|
||||
|
||||
enum class UserWantsToSave {
|
||||
Yes,
|
||||
No,
|
||||
Cancel,
|
||||
DidNotDisplayDialog
|
||||
};
|
||||
|
||||
/*
|
||||
* The god class, holds everything there is to know about the currently open
|
||||
@ -100,11 +91,24 @@ public:
|
||||
bool showHistory;
|
||||
bool showSoundSettings;
|
||||
|
||||
/*
|
||||
Return ::DidNotDisplayDialog if the current chart state is marked as saved,
|
||||
otherwise ask the user if they want to save and return their answer
|
||||
*/
|
||||
UserWantsToSave ask_to_save_if_needed();
|
||||
enum class SaveOutcome {
|
||||
UserSaved,
|
||||
UserDeclindedSaving,
|
||||
UserCanceled,
|
||||
NoSavingNeeded,
|
||||
};
|
||||
|
||||
SaveOutcome save_if_needed();
|
||||
|
||||
bool needs_to_save() const;
|
||||
|
||||
enum class UserWantsToSave {
|
||||
Yes,
|
||||
No,
|
||||
Cancel,
|
||||
};
|
||||
|
||||
UserWantsToSave ask_if_user_wants_to_save() const;
|
||||
|
||||
/*
|
||||
If the given song already has a dedicated file on disk, returns its path.
|
||||
@ -112,7 +116,6 @@ public:
|
||||
return nothing if the user canceled
|
||||
*/
|
||||
std::optional<std::filesystem::path> ask_for_save_path_if_needed();
|
||||
bool save_changes_or_cancel();
|
||||
|
||||
void toggle_note_at_current_time(const better::Position& pos);
|
||||
|
||||
@ -143,8 +146,6 @@ private:
|
||||
void open_chart(better::Chart& chart, const std::string& name);
|
||||
|
||||
std::filesystem::path assets;
|
||||
|
||||
friend class ESHelper::NewChartDialog;
|
||||
};
|
||||
|
||||
namespace feis {
|
||||
@ -156,8 +157,6 @@ namespace feis {
|
||||
std::filesystem::path settings
|
||||
);
|
||||
|
||||
bool saveOrCancel(std::optional<EditorState>& ed);
|
||||
|
||||
class NewChartDialog {
|
||||
public:
|
||||
std::optional<better::Chart> display(EditorState& editorState);
|
||||
|
@ -95,7 +95,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
bool current_state_is_saved() {
|
||||
bool current_state_is_saved() const {
|
||||
return last_saved_action == previous_actions.front();
|
||||
};
|
||||
|
||||
|
34
src/main.cpp
34
src/main.cpp
@ -77,8 +77,8 @@ int main() {
|
||||
BlankScreen bg{assets_folder};
|
||||
std::optional<EditorState> editor_state;
|
||||
NotificationsQueue notificationsQueue;
|
||||
ESHelper::NewChartDialog newChartDialog;
|
||||
ESHelper::ChartPropertiesDialog chartPropertiesDialog;
|
||||
feis::NewChartDialog newChartDialog;
|
||||
feis::ChartPropertiesDialog chartPropertiesDialog;
|
||||
|
||||
sf::Clock deltaClock;
|
||||
while (window.isOpen()) {
|
||||
@ -89,8 +89,10 @@ int main() {
|
||||
switch (event.type) {
|
||||
case sf::Event::Closed:
|
||||
preferences.save();
|
||||
if (ESHelper::saveOrCancel(editor_state)) {
|
||||
window.close();
|
||||
if (editor_state) {
|
||||
if (editor_state->save_if_needed() != EditorState::SaveOutcome::UserCanceled) {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case sf::Event::Resized:
|
||||
@ -112,8 +114,8 @@ int main() {
|
||||
switch (event.mouseButton.button) {
|
||||
case sf::Mouse::Button::Right:
|
||||
if (editor_state and editor_state->chart_state) {
|
||||
if (editor_state->chart_state->longNoteBeingCreated) {
|
||||
auto pair = *editor_state->chart->longNoteBeingCreated;
|
||||
if (editor_state->chart_state->long_note_being_created) {
|
||||
auto pair = *editor_state->chart_state->long_note_being_created;
|
||||
Note new_note = Note(pair.first, pair.second);
|
||||
std::set<Note> new_note_set = {new_note};
|
||||
editor_state->chart->ref.Notes.insert(new_note);
|
||||
@ -367,8 +369,8 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::O:
|
||||
if (event.key.control) {
|
||||
if (ESHelper::saveOrCancel(editor_state)) {
|
||||
ESHelper::open(editor_state, assets_folder, settings_folder);
|
||||
if (feis::saveOrCancel(editor_state)) {
|
||||
feis::open(editor_state, assets_folder, settings_folder);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -379,7 +381,7 @@ int main() {
|
||||
break;
|
||||
case sf::Keyboard::S:
|
||||
if (event.key.control) {
|
||||
ESHelper::save(*editor_state);
|
||||
feis::save(*editor_state);
|
||||
notificationsQueue.push(std::make_shared<TextNotification>(
|
||||
"Saved file"));
|
||||
}
|
||||
@ -564,7 +566,7 @@ int main() {
|
||||
if (ImGui::MenuItem("New")) {
|
||||
bool user_canceled = false;
|
||||
if (editor_state) {
|
||||
switch (editor_state->ask_to_save_if_needed()) {
|
||||
switch (editor_state->ask_if_user_wants_to_save()) {
|
||||
case UserWantsToSave::Yes:
|
||||
const auto path = editor_state->ask_for_save_path_if_needed();
|
||||
if (path) {
|
||||
@ -587,8 +589,8 @@ int main() {
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Open", "Ctrl+O")) {
|
||||
if (ESHelper::saveOrCancel(editor_state)) {
|
||||
ESHelper::open(editor_state, assets_folder, settings_folder);
|
||||
if (feis::saveOrCancel(editor_state)) {
|
||||
feis::open(editor_state, assets_folder, settings_folder);
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginMenu("Recent Files")) {
|
||||
@ -596,8 +598,8 @@ int main() {
|
||||
for (const auto& file : Toolbox::getRecentFiles(settings_folder)) {
|
||||
ImGui::PushID(i);
|
||||
if (ImGui::MenuItem(file.c_str())) {
|
||||
if (ESHelper::saveOrCancel(editor_state)) {
|
||||
ESHelper::openFromFile(editor_state, file, assets_folder, settings_folder);
|
||||
if (feis::saveOrCancel(editor_state)) {
|
||||
feis::openFromFile(editor_state, file, assets_folder, settings_folder);
|
||||
}
|
||||
}
|
||||
ImGui::PopID();
|
||||
@ -606,13 +608,13 @@ int main() {
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::MenuItem("Close", "", false, editor_state.has_value())) {
|
||||
if (ESHelper::saveOrCancel(editor_state)) {
|
||||
if (feis::saveOrCancel(editor_state)) {
|
||||
editor_state.reset();
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Save", "Ctrl+S", false, editor_state.has_value())) {
|
||||
ESHelper::save(*editor_state);
|
||||
feis::save(*editor_state);
|
||||
}
|
||||
if (ImGui::MenuItem("Save As", "", false, editor_state.has_value())) {
|
||||
char const* options[1] = {"*.memon"};
|
||||
|
@ -37,7 +37,8 @@ const auto floor_beats = [](Fraction beats, unsigned int denominator = 240) {
|
||||
return nearest / Fraction{denominator};
|
||||
};
|
||||
|
||||
// Stolen from : https://github.com/progrock-libraries/kickstart/blob/d62c22efc92006dd76d455cf8f9d4f2a045e9126/source/library/kickstart/main_library/core/ns%E2%96%B8language/operations/intpow.hpp#L36
|
||||
// Stolen from :
|
||||
// https://github.com/progrock-libraries/kickstart/blob/master/source/library/kickstart/main_library/core/ns%E2%96%B8language/operations/intpow.hpp#L36
|
||||
// Essentially this is Horner's rule adapted to calculating a power, so that the
|
||||
// number of floating point multiplications is at worst O(log₂n).
|
||||
template<class Number>
|
||||
|
Loading…
x
Reference in New Issue
Block a user