F.E.I.S/src/editor_state.cpp

1369 lines
46 KiB
C++
Raw Normal View History

2021-12-31 14:59:39 +01:00
#include "editor_state.hpp"
2022-05-11 00:09:02 +02:00
#include <SFML/Audio/SoundSource.hpp>
#include <SFML/Audio/SoundStream.hpp>
#include <SFML/System/Vector2.hpp>
#include <algorithm>
#include <cmath>
2019-01-12 03:13:30 +01:00
#include <filesystem>
#include <fmt/core.h>
#include <imgui-SFML.h>
2021-12-31 14:59:39 +01:00
#include <imgui.h>
#include <imgui_internal.h>
2021-12-31 14:59:39 +01:00
#include <imgui_stdlib.h>
#include <initializer_list>
2022-06-01 00:26:36 +02:00
#include <memory>
#include <nowide/fstream.hpp>
#include <sstream>
#include <SFML/System/Time.hpp>
#include <stdexcept>
#include <string>
2021-12-31 00:57:06 +01:00
#include <tinyfiledialogs.h>
#include <variant>
2022-03-23 02:20:07 +01:00
#include "better_note.hpp"
#include "better_song.hpp"
2022-03-23 02:20:07 +01:00
#include "chart_state.hpp"
2022-04-01 02:30:32 +02:00
#include "file_dialogs.hpp"
2022-04-09 00:54:06 +02:00
#include "history_item.hpp"
#include "imgui_extras.hpp"
#include "json_decimal_handling.hpp"
#include "long_note_dummy.hpp"
#include "notifications_queue.hpp"
#include "special_numeric_types.hpp"
2022-05-11 00:09:02 +02:00
#include "src/custom_sfml_audio/synced_sound_streams.hpp"
2022-03-23 02:20:07 +01:00
#include "variant_visitor.hpp"
2022-03-17 02:50:30 +01:00
2022-04-01 02:30:32 +02:00
EditorState::EditorState(const std::filesystem::path& assets_) :
note_claps(std::make_shared<NoteClaps>(nullptr, nullptr, assets_, 1.f)),
chord_claps(std::make_shared<ChordClaps>(nullptr, nullptr, assets_, 1.f)),
beat_ticks(std::make_shared<BeatTicks>(nullptr, assets_, 1.f)),
2022-04-01 02:30:32 +02:00
playfield(assets_),
linear_view(assets_),
applicable_timing(song.timing),
assets(assets_)
{
reload_music();
reload_jacket();
};
2022-03-17 02:50:30 +01:00
EditorState::EditorState(
const better::Song& song_,
const std::filesystem::path& assets_,
const std::filesystem::path& song_path = {}
) :
2022-04-01 02:30:32 +02:00
song(song_),
2022-03-17 02:50:30 +01:00
song_path(song_path),
note_claps(std::make_shared<NoteClaps>(nullptr, nullptr, assets_, 1.f)),
chord_claps(std::make_shared<ChordClaps>(nullptr, nullptr, assets_, 1.f)),
beat_ticks(std::make_shared<BeatTicks>(nullptr, assets_, 1.f)),
2022-03-17 02:50:30 +01:00
playfield(assets_),
linear_view(assets_),
applicable_timing(song.timing),
assets(assets_)
{
if (not song.charts.empty()) {
auto& [name, _] = *this->song.charts.begin();
open_chart(name);
2022-03-17 02:50:30 +01:00
}
reload_music();
2022-03-23 02:20:07 +01:00
reload_jacket();
2022-03-17 02:50:30 +01:00
};
int EditorState::get_volume() const {
return volume;
}
2022-10-11 01:54:43 +02:00
void EditorState::set_volume(int volume_) {
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
2022-10-11 01:54:43 +02:00
(**music).set_volume(volume_);
volume = (**music).get_volume();
2022-06-01 00:26:36 +02:00
}
}
void EditorState::volume_up() {
set_volume(volume + 1);
}
void EditorState::volume_down() {
set_volume(volume - 1);
}
int EditorState::get_speed() const {
return speed;
}
void EditorState::set_speed(int newMusicSpeed) {
speed = std::clamp(newMusicSpeed, 1, 20);
2022-05-11 00:09:02 +02:00
set_pitch(speed / 10.f);
}
void EditorState::speed_up() {
set_speed(speed + 1);
}
void EditorState::speed_down() {
set_speed(speed - 1);
}
2022-02-28 23:10:22 +01:00
2022-03-26 00:23:13 +01:00
const Interval<sf::Time>& EditorState::get_editable_range() {
2022-03-17 02:50:30 +01:00
reload_editable_range();
2022-03-23 02:20:07 +01:00
return editable_range;
2022-04-06 15:47:04 +02:00
};
2022-10-11 01:54:43 +02:00
bool EditorState::has_any_audio() const {
return not audio.empty();
}
2022-06-09 00:28:21 +02:00
void EditorState::toggle_playback() {
if (get_status() != sf::SoundSource::Playing) {
play();
} else {
pause();
}
}
void EditorState::toggle_note_claps() {
if (
audio.contains_stream(note_clap_stream)
or audio.contains_stream(chord_clap_stream)
) {
audio.update_streams({}, {note_clap_stream, chord_clap_stream});
} else {
note_claps = note_claps->with(
get_pitch(),
not distinct_chord_clap,
clap_on_long_note_ends
);
std::map<std::string, NewStream> streams = {{note_clap_stream, {note_claps, true}}};
if (distinct_chord_clap) {
chord_claps = chord_claps->with_pitch(get_pitch());
streams[chord_clap_stream] = {chord_claps, true};
}
audio.update_streams(streams);
}
}
void EditorState::toggle_clap_on_long_note_ends() {
clap_on_long_note_ends = not clap_on_long_note_ends;
note_claps = note_claps->with(
get_pitch(),
not distinct_chord_clap,
clap_on_long_note_ends
);
audio.update_streams({{note_clap_stream, {note_claps, true}}});
}
void EditorState::toggle_distinct_chord_claps() {
distinct_chord_clap = not distinct_chord_clap;
note_claps = note_claps->with(
get_pitch(),
not distinct_chord_clap,
clap_on_long_note_ends
);
if (distinct_chord_clap) {
chord_claps = chord_claps->with_pitch(get_pitch());
audio.update_streams(
{
{note_clap_stream, {note_claps, true}},
{chord_clap_stream, {chord_claps, true}}
}
);
} else {
audio.update_streams(
{{note_clap_stream, {note_claps, true}}},
{chord_clap_stream}
);
}
}
void EditorState::toggle_beat_ticks() {
if (audio.contains_stream(beat_tick_stream)) {
audio.remove_stream(beat_tick_stream);
} else {
beat_ticks = beat_ticks->with_pitch(get_pitch());
audio.add_stream(beat_tick_stream, {beat_ticks, true});
}
}
2022-05-11 00:09:02 +02:00
void EditorState::play() {
2022-10-11 01:54:43 +02:00
status = sf::SoundSource::Playing;
2022-05-11 00:09:02 +02:00
audio.play();
}
void EditorState::pause() {
2022-10-11 01:54:43 +02:00
status = sf::SoundSource::Paused;
2022-05-11 00:09:02 +02:00
audio.pause();
}
void EditorState::stop() {
2022-10-11 01:54:43 +02:00
status = sf::SoundSource::Stopped;
2022-05-11 00:09:02 +02:00
audio.stop();
}
2022-06-01 00:26:36 +02:00
sf::SoundSource::Status EditorState::get_status() {
2022-10-11 01:54:43 +02:00
if (has_any_audio()) {
return audio.getStatus();
} else {
return status;
}
2022-05-11 00:09:02 +02:00
}
void EditorState::set_pitch(float pitch) {
std::map<std::string, NewStream> update;
if (audio.contains_stream(note_clap_stream)) {
note_claps = note_claps->with_pitch(pitch);
update[note_clap_stream] = {note_claps, true};
}
if (audio.contains_stream(beat_tick_stream)) {
beat_ticks = beat_ticks->with_pitch(pitch);
update[beat_tick_stream] = {beat_ticks, true};
}
if (audio.contains_stream(chord_clap_stream)) {
chord_claps = chord_claps->with_pitch(pitch);
update[chord_clap_stream] = {chord_claps, true};
}
2022-10-08 01:01:49 +02:00
audio.update_streams(update, {}, pitch);
2022-05-11 00:09:02 +02:00
}
float EditorState::get_pitch() const {
return speed / 10.f;
}
void EditorState::set_playback_position(std::variant<sf::Time, Fraction> newPosition) {
const auto clamp_ = VariantVisitor {
[this](const sf::Time& seconds) {
return std::variant<sf::Time, Fraction>(
std::clamp(
seconds,
this->editable_range.start,
this->editable_range.end
)
);
},
[this](const Fraction& beats) {
return std::variant<sf::Time, Fraction>(
std::clamp(
beats,
this->beats_at(this->editable_range.start),
this->beats_at(this->editable_range.end)
)
);
},
};
newPosition = std::visit(clamp_, newPosition);
previous_playback_position = playback_position;
playback_position = newPosition;
2022-05-11 00:09:02 +02:00
const auto now = current_time();
if (now >= sf::Time::Zero and now < editable_range.end) {
audio.setPlayingOffset(now);
} else {
stop();
}
2022-04-06 15:47:04 +02:00
};
sf::Time EditorState::get_precise_playback_position() {
2022-06-11 03:31:33 +02:00
return audio.getPrecisePlayingOffset();
2022-05-11 00:09:02 +02:00
}
2022-03-23 02:20:07 +01:00
Fraction EditorState::current_exact_beats() const {
const auto current_exact_beats_ = VariantVisitor {
[this](const sf::Time& seconds) { return this->beats_at(seconds); },
[](const Fraction& beats) { return beats; },
};
return std::visit(current_exact_beats_, playback_position);
2022-03-17 02:50:30 +01:00
};
2022-03-23 02:20:07 +01:00
Fraction EditorState::current_snaped_beats() const {
const auto exact = current_exact_beats();
return round_beats(exact, snap);
2022-03-17 02:50:30 +01:00
};
Fraction EditorState::previous_exact_beats() const {
const auto current_exact_beats_ = VariantVisitor {
[this](const sf::Time& seconds) { return this->beats_at(seconds); },
[](const Fraction& beats) { return beats; },
};
return std::visit(current_exact_beats_, previous_playback_position);
}
sf::Time EditorState::current_time() const {
const auto current_time_ = VariantVisitor {
[](const sf::Time& seconds) { return seconds; },
[this](const Fraction& beats) { return this->time_at(beats); },
};
return std::visit(current_time_, playback_position);
}
sf::Time EditorState::previous_time() const {
const auto current_time_ = VariantVisitor {
[](const sf::Time& seconds) { return seconds; },
[this](const Fraction& beats) { return this->time_at(beats); },
};
return std::visit(current_time_, previous_playback_position);
}
2022-03-23 02:20:07 +01:00
Fraction EditorState::beats_at(sf::Time time) const {
return applicable_timing.beats_at(time);
2022-03-17 02:50:30 +01:00
};
2022-03-23 02:20:07 +01:00
sf::Time EditorState::time_at(Fraction beat) const {
return applicable_timing.time_at(beat);
};
Fraction EditorState::get_snap_step() const {
2022-03-17 02:50:30 +01:00
return Fraction{1, snap};
};
2022-04-09 00:54:06 +02:00
void EditorState::display_playfield(Marker& marker, Judgement markerEndingState) {
2021-12-31 14:59:39 +01:00
ImGui::SetNextWindowSize(ImVec2(400, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(
ImVec2(0, 0),
ImVec2(FLT_MAX, FLT_MAX),
2022-03-23 02:20:07 +01:00
Toolbox::CustomConstraints::ContentSquare
);
2019-01-14 21:43:56 +01:00
2022-10-11 21:23:29 +02:00
if (ImGui::Begin("Playfield", &show_playfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
2022-03-17 02:50:30 +01:00
if (
not ImGui::IsWindowHovered()
and chart_state
and chart_state->creating_long_note
) {
// cancel long note creation if the mouse is or goes out of the playfield
2022-03-17 02:50:30 +01:00
chart_state->long_note_being_created.reset();
chart_state->creating_long_note = false;
}
2022-03-23 02:20:07 +01:00
2019-01-14 21:43:56 +01:00
float squareSize = ImGui::GetWindowSize().x / 4.f;
float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x;
int ImGuiIndex = 0;
2019-02-13 00:44:46 +01:00
2022-03-17 02:50:30 +01:00
if (chart_state) {
playfield.resize(static_cast<unsigned int>(ImGui::GetWindowSize().x));
2022-03-17 02:50:30 +01:00
if (chart_state->long_note_being_created) {
2022-03-23 02:20:07 +01:00
playfield.draw_tail_and_receptor(
make_playfield_long_note_dummy(
2022-03-23 02:20:07 +01:00
current_exact_beats(),
*chart_state->long_note_being_created,
get_snap_step()
2022-03-23 02:20:07 +01:00
),
current_time(),
2022-03-23 02:20:07 +01:00
applicable_timing
2022-03-17 02:50:30 +01:00
);
}
2022-03-23 02:20:07 +01:00
auto display = VariantVisitor {
[&, this](const better::TapNote& tap_note){
auto note_offset = (this->current_time() - this->time_at(tap_note.get_time()));
2022-04-09 00:54:06 +02:00
auto t = marker.at(markerEndingState, note_offset);
2019-02-09 16:05:46 +01:00
if (t) {
2022-03-23 02:20:07 +01:00
ImGui::SetCursorPos({
tap_note.get_position().get_x() * squareSize,
TitlebarHeight + tap_note.get_position().get_y() * squareSize
});
2019-02-09 16:05:46 +01:00
ImGui::PushID(ImGuiIndex);
ImGui::Image(*t, {squareSize, squareSize});
2019-02-09 16:05:46 +01:00
ImGui::PopID();
2019-02-13 00:44:46 +01:00
++ImGuiIndex;
2019-02-09 16:05:46 +01:00
}
2022-03-23 02:20:07 +01:00
},
[&, this](const better::LongNote& long_note){
this->playfield.draw_long_note(
long_note,
current_time(),
2022-03-23 02:20:07 +01:00
applicable_timing,
2021-12-31 14:59:39 +01:00
marker,
2022-03-23 02:20:07 +01:00
markerEndingState
);
},
};
for (const auto& [_, note] : chart_state->visible_notes) {
2022-03-23 02:20:07 +01:00
note.visit(display);
2019-01-14 21:43:56 +01:00
}
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, TitlebarHeight});
2022-03-23 02:20:07 +01:00
ImGui::Image(playfield.long_note.layer);
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, TitlebarHeight});
2022-03-17 02:50:30 +01:00
ImGui::Image(playfield.marker_layer);
2019-01-14 21:43:56 +01:00
}
2019-02-09 16:05:46 +01:00
// Display button grid
2022-03-23 02:20:07 +01:00
for (unsigned int y = 0; y < 4; ++y) {
for (unsigned int x = 0; x < 4; ++x) {
2021-12-31 14:59:39 +01:00
ImGui::PushID(x + 4 * y);
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4) ImColor::HSV(0, 0, 0, 0));
ImGui::PushStyleColor(
ImGuiCol_ButtonHovered,
(ImVec4) ImColor::HSV(0, 0, 1.f, 0.1f));
ImGui::PushStyleColor(
ImGuiCol_ButtonActive,
(ImVec4) ImColor::HSV(0, 0, 1.f, 0.5f));
if (ImGui::ImageButton(playfield.button, {squareSize, squareSize}, 0)) {
if (chart_state) {
chart_state->toggle_note(
current_time(),
snap,
{x, y},
applicable_timing
);
}
}
2022-03-17 02:50:30 +01:00
if (ImGui::IsItemHovered() and chart_state and chart_state->creating_long_note) {
// Deal with long note creation stuff
2022-03-23 02:20:07 +01:00
if (not chart_state->long_note_being_created) {
better::TapNote current_note{current_snaped_beats(), {x, y}};
chart_state->long_note_being_created.emplace(current_note, current_note);
} else {
2022-03-23 02:20:07 +01:00
chart_state->long_note_being_created->second = better::TapNote{
current_snaped_beats(), {x, y}
};
}
}
2019-01-14 21:43:56 +01:00
ImGui::PopStyleColor(3);
ImGui::PopID();
}
}
2022-03-23 02:20:07 +01:00
if (chart_state) {
// Check for collisions then display them
std::array<bool, 16> collisions = {};
for (const auto& [_, note] : chart_state->visible_notes) {
if (chart_state->chart.notes.is_colliding(note, applicable_timing)) {
2022-03-23 02:20:07 +01:00
collisions[note.get_position().index()] = true;
}
}
for (int i = 0; i < 16; ++i) {
if (collisions.at(i)) {
2021-12-31 14:59:39 +01:00
int x = i % 4;
int y = i / 4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + y * squareSize});
ImGui::PushID(ImGuiIndex);
ImGui::Image(playfield.note_collision, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
}
// Display selected notes
for (const auto& [_, note] : chart_state->visible_notes) {
2022-03-25 02:20:22 +01:00
if (chart_state->selected_notes.contains(note)) {
2022-03-23 02:20:07 +01:00
ImGui::SetCursorPos({
note.get_position().get_x() * squareSize,
TitlebarHeight + note.get_position().get_y() * squareSize
});
ImGui::PushID(ImGuiIndex);
ImGui::Image(playfield.note_selected, {squareSize, squareSize});
ImGui::PopID();
++ImGuiIndex;
}
}
}
2019-01-14 21:43:56 +01:00
}
ImGui::End();
2022-03-23 02:20:07 +01:00
};
2019-01-13 22:29:29 +01:00
/*
2022-03-23 02:20:07 +01:00
Display all metadata in an editable form
*/
2022-10-11 21:23:29 +02:00
void EditorState::display_file_properties() {
2022-04-19 23:13:18 +02:00
if (ImGui::Begin(
2022-10-11 21:23:29 +02:00
"File Properties",
&show_file_properties,
2022-04-19 23:13:18 +02:00
ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_AlwaysAutoResize
)) {
if (jacket) {
2022-03-23 02:20:07 +01:00
if (jacket) {
2022-04-19 23:13:18 +02:00
ImGui::Image(*jacket, sf::Vector2f(300, 300));
2022-03-23 02:20:07 +01:00
}
2022-04-19 23:13:18 +02:00
} else {
ImGui::BeginChild("Album Cover", ImVec2(300, 300), true, ImGuiWindowFlags_NoResize);
ImGui::EndChild();
}
2022-03-23 02:20:07 +01:00
ImGui::InputText("Title", &song.metadata.title);
ImGui::InputText("Artist", &song.metadata.artist);
if (feis::InputTextColored(
2022-03-23 02:20:07 +01:00
"Audio",
&song.metadata.audio,
2022-06-01 00:26:36 +02:00
music.has_value(),
2022-03-23 02:20:07 +01:00
"Invalid Audio Path"
)) {
reload_music();
2019-01-16 13:23:36 +01:00
}
if (feis::InputTextColored(
2022-03-23 02:20:07 +01:00
"Jacket",
&song.metadata.jacket,
2022-03-23 02:20:07 +01:00
jacket.has_value(),
"Invalid Jacket Path"
)) {
reload_jacket();
}
2022-03-23 02:20:07 +01:00
ImGui::Separator();
ImGui::Text("Preview");
ImGui::Checkbox("Use separate preview file", &song.metadata.use_preview_file);
if (song.metadata.use_preview_file) {
if (feis::InputTextColored(
2022-03-23 02:20:07 +01:00
"File",
&song.metadata.preview_file,
2022-03-23 02:20:07 +01:00
preview_audio.has_value(),
"Invalid Path"
)) {
reload_preview_audio();
}
2022-03-23 02:20:07 +01:00
} else {
if (feis::InputDecimal("Start", &song.metadata.preview_loop.start)) {
song.metadata.preview_loop.start = std::max(
Decimal{0},
song.metadata.preview_loop.start
);
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
song.metadata.preview_loop.start = std::min(
2022-06-01 00:26:36 +02:00
Decimal{(**music).getDuration().asMicroseconds()} / 1000000,
song.metadata.preview_loop.start
);
}
}
if (feis::InputDecimal("Duration", &song.metadata.preview_loop.duration)) {
song.metadata.preview_loop.duration = std::max(
Decimal{0},
song.metadata.preview_loop.duration
);
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
song.metadata.preview_loop.start = std::min(
(
Decimal{
2022-06-01 00:26:36 +02:00
(**music)
.getDuration()
.asMicroseconds()
} / 1000000
- song.metadata.preview_loop.start
),
song.metadata.preview_loop.start
);
}
}
}
2022-03-23 02:20:07 +01:00
}
ImGui::End();
2022-03-23 02:20:07 +01:00
};
/*
2022-03-23 02:20:07 +01:00
Display any information that would be useful for the user to troubleshoot the
status of the editor. Will appear in the "Editor Status" window
*/
void EditorState::display_status() {
2022-10-11 21:23:29 +02:00
ImGui::Begin("Status", &show_status, ImGuiWindowFlags_AlwaysAutoResize);
{
2022-06-01 00:26:36 +02:00
if (not music.has_value()) {
if (not song.metadata.audio.empty()) {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid music path : %s",
song.metadata.audio.c_str());
} else {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No music file loaded");
}
}
2022-03-17 02:50:30 +01:00
if (not jacket) {
if (not song.metadata.jacket.empty()) {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
2022-03-23 02:20:07 +01:00
"Invalid jacket path : %s",
song.metadata.jacket.c_str());
} else {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
2022-03-23 02:20:07 +01:00
"No jacket loaded");
}
}
audio.display_debug();
}
ImGui::End();
2022-03-23 02:20:07 +01:00
};
2022-03-23 02:20:07 +01:00
void EditorState::display_playback_status() {
ImGuiIO& io = ImGui::GetIO();
2021-12-31 14:59:39 +01:00
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y - 25),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::Begin(
2021-12-31 14:59:39 +01:00
"Playback Status",
2022-10-11 21:23:29 +02:00
&show_playback_status,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize);
{
if (chart_state) {
ImGui::TextUnformatted(
fmt::format(
"{} {}",
chart_state->difficulty_name,
better::stringify_level(chart_state->chart.level)
).c_str()
);
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
2019-01-16 13:23:36 +01:00
} else {
2021-12-31 14:59:39 +01:00
ImGui::TextDisabled("No chart selected");
ImGui::SameLine();
2019-01-16 13:23:36 +01:00
}
ImGui::TextDisabled("Snap :");
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
ImGui::Text("%s", Toolbox::toOrdinal(snap * 4).c_str());
ImGui::SameLine();
ImGui::TextDisabled("Beats :");
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
ImGui::TextUnformatted(fmt::format("{:.3f}", static_cast<double>(current_exact_beats())).c_str());
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
ImGui::TextDisabled("Music File Offset :");
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
2022-06-01 00:26:36 +02:00
ImGui::TextUnformatted(Toolbox::to_string(audio.getPlayingOffset()).c_str());
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
}
ImGui::TextDisabled("Timeline Position :");
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(current_time()).c_str());
}
ImGui::End();
ImGui::PopStyleVar();
2022-03-23 02:20:07 +01:00
};
2022-03-23 02:20:07 +01:00
void EditorState::display_timeline() {
ImGuiIO& io = ImGui::GetIO();
float raw_height = io.DisplaySize.y * 0.9f;
auto height = static_cast<int>(raw_height);
if (
chart_state.has_value()
and (
chart_state->density_graph.should_recompute
or height != chart_state->density_graph.last_height.value_or(height)
)
) {
chart_state->density_graph.should_recompute = false;
chart_state->density_graph.update(
2022-03-23 02:20:07 +01:00
height,
chart_state->chart,
2022-04-09 00:54:06 +02:00
applicable_timing,
editable_range.start,
editable_range.end
2022-03-23 02:20:07 +01:00
);
}
2021-12-31 14:59:39 +01:00
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x - 35, io.DisplaySize.y * 0.5f),
ImGuiCond_Always,
ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize({45.f, static_cast<float>(height)}, ImGuiCond_Always);
2019-04-06 22:48:18 +02:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
2021-12-31 14:59:39 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0, 1.0, 1.1, 1.0));
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.240f, 0.520f, 0.880f, 0.500f));
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.240f, 0.520f, 0.880f, 0.700f));
ImGui::Begin(
2021-12-31 14:59:39 +01:00
"Timeline",
2022-10-11 21:23:29 +02:00
&show_timeline,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
{
if (chart_state) {
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, 0});
ImGui::Image(chart_state->density_graph.graph);
// The output is reversed because we are repurposing a vertical
// slider, which goes from 0 AT THE BOTTOM to 1 AT THE TOP, which is
// the opposite of what we want
AffineTransform<float> scroll(
editable_range.start.asSeconds(),
editable_range.end.asSeconds(),
1.f,
0.f
);
float slider_pos = scroll.transform(current_time().asSeconds());
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, 0});
if (ImGui::VSliderFloat("TimelineSlider", ImGui::GetContentRegionMax(), &slider_pos, 0.f, 1.f, "")) {
set_playback_position(sf::seconds(scroll.backwards_transform(slider_pos)));
}
}
}
ImGui::End();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar(3);
2022-03-23 02:20:07 +01:00
};
2022-03-23 02:20:07 +01:00
void EditorState::display_chart_list() {
2022-10-11 21:23:29 +02:00
if (ImGui::Begin("Chart List", &show_chart_list, ImGuiWindowFlags_AlwaysAutoResize)) {
if (this->song.charts.empty()) {
2021-12-31 14:59:39 +01:00
ImGui::Dummy({100, 0});
ImGui::SameLine();
ImGui::Text("- no charts -");
ImGui::SameLine();
ImGui::Dummy({100, 0});
} else {
2021-12-31 14:59:39 +01:00
ImGui::Dummy(ImVec2(300, 0));
ImGui::Columns(3, "mycolumns");
2021-12-31 14:59:39 +01:00
ImGui::TextDisabled("Difficulty");
ImGui::NextColumn();
ImGui::TextDisabled("Level");
ImGui::NextColumn();
ImGui::TextDisabled("Note Count");
ImGui::NextColumn();
ImGui::Separator();
for (auto& [name, chart] : song.charts) {
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable(
name.c_str(),
chart_state ? chart_state->difficulty_name == name : false,
ImGuiSelectableFlags_SpanAllColumns
)) {
open_chart(name);
}
ImGui::NextColumn();
ImGui::TextUnformatted(better::stringify_level(chart.level).c_str());
2021-12-31 14:59:39 +01:00
ImGui::NextColumn();
ImGui::Text("%d", static_cast<int>(chart.notes.size()));
2021-12-31 14:59:39 +01:00
ImGui::NextColumn();
}
}
}
ImGui::End();
2022-03-23 02:20:07 +01:00
};
2019-02-09 16:05:46 +01:00
2022-03-23 02:20:07 +01:00
void EditorState::display_linear_view() {
ImGui::SetNextWindowSize(ImVec2(304, 500), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(304, 304), ImVec2(FLT_MAX, FLT_MAX));
2021-12-31 14:59:39 +01:00
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2));
2022-10-11 21:23:29 +02:00
if (ImGui::Begin("Linear View", &show_linear_view, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (chart_state) {
auto header_height = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
ImGui::SetCursorPos({0, header_height});
linear_view.draw(
ImGui::GetWindowDrawList(),
2022-03-25 02:20:22 +01:00
*chart_state,
applicable_timing,
current_exact_beats(),
2022-04-19 12:59:17 +02:00
beats_at(editable_range.end),
get_snap_step(),
ImGui::GetContentRegionMax(),
ImGui::GetCursorScreenPos()
2022-03-26 00:23:13 +01:00
);
2019-03-27 14:41:16 +01:00
} else {
ImGui::TextDisabled("- no chart selected -");
}
2019-03-26 00:04:29 +01:00
}
ImGui::End();
ImGui::PopStyleVar(2);
2022-03-23 02:20:07 +01:00
};
2019-03-26 00:04:29 +01:00
2022-10-11 01:54:43 +02:00
void EditorState::display_sound_settings() {
2022-10-11 21:23:29 +02:00
if (ImGui::Begin("Sound Settings", &show_sound_settings)) {
2022-10-11 01:54:43 +02:00
if (ImGui::TreeNodeEx("Music", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::BeginDisabled(not music.has_value());
{
auto volume = get_volume();
if (ImGui::SliderInt("Volume##Music", &volume, 0, 10)) {
set_volume(volume);
}
}
ImGui::EndDisabled();
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Beat Tick", ImGuiTreeNodeFlags_DefaultOpen)) {
bool beat_tick = beat_ticks_are_on();
if (ImGui::Checkbox("On/Off##Beat Tick", &beat_tick)) {
toggle_beat_ticks();
}
ImGui::BeginDisabled(not beat_tick);
{
auto volume = beat_ticks->get_volume();
if (ImGui::SliderInt("Volume##Beat Tick", &volume, 0, 10)) {
beat_ticks->set_volume(volume);
}
}
ImGui::EndDisabled();
ImGui::TreePop();
}
if (ImGui::TreeNodeEx("Note Clap", ImGuiTreeNodeFlags_DefaultOpen)) {
bool note_clap = note_claps_are_on();
if (ImGui::Checkbox("On/Off##Note Clap", &note_clap)) {
toggle_note_claps();
}
ImGui::BeginDisabled(not note_clap);
{
auto volume = note_claps->get_volume();
if (ImGui::SliderInt("Volume##Note Clap", &volume, 0, 10)) {
note_claps->set_volume(volume);
chord_claps->set_volume(volume);
}
if (ImGui::TreeNode("Advanced##Note Clap")) {
bool long_end = get_clap_on_long_note_ends();
if (ImGui::Checkbox("Clap on long note ends", &long_end)) {
toggle_clap_on_long_note_ends();
}
bool chord_clap = get_distinct_chord_claps();
if (ImGui::Checkbox("Distinct chord clap", &chord_clap)) {
toggle_distinct_chord_claps();
}
ImGui::TreePop();
}
}
ImGui::EndDisabled();
ImGui::TreePop();
}
}
ImGui::End();
}
2022-10-11 21:23:29 +02:00
void EditorState::display_editor_settings() {
if (ImGui::Begin("Editor Settings", &show_editor_settings)) {
static const std::uint64_t step = 1;
if (ImGui::InputScalar("Snap", ImGuiDataType_U64, &snap, &step, nullptr, "%d")) {
snap = std::clamp(snap, 1UL, 1000UL);
};
ImGui::SameLine();
feis::HelpMarker(
"Change the underlying snap value, this allows setting snap "
"values that aren't a divisor of 240. "
"This changes the underlying value that's multiplied "
"by 4 before being shown in the status bar"
);
}
ImGui::End();
}
void EditorState::display_history() {
history.display(show_history);
}
2022-04-02 04:10:09 +02:00
bool EditorState::needs_to_save() const {
return not history.current_state_is_saved();
2022-04-06 15:47:04 +02:00
};
2022-04-02 04:10:09 +02:00
EditorState::UserWantsToSave EditorState::ask_if_user_wants_to_save() const {
int response_code = tinyfd_messageBox(
"Warning",
2022-04-19 23:13:18 +02:00
"Chart has unsaved changes, do you want to save ?",
2022-04-02 04:10:09 +02:00
"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
));
}
2022-03-23 02:20:07 +01:00
};
2022-04-09 00:54:06 +02:00
EditorState::SaveOutcome EditorState::save_if_needed_and_user_wants_to() {
2022-04-02 04:10:09 +02:00
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;
default:
2022-04-02 04:10:09 +02:00
return EditorState::SaveOutcome::UserCanceled;
}
2022-04-06 15:47:04 +02:00
};
EditorState::SaveOutcome EditorState::save_if_needed() {
if (not needs_to_save()) {
return EditorState::SaveOutcome::NoSavingNeeded;
} else {
const auto path = ask_for_save_path_if_needed();
if (not path) {
return EditorState::SaveOutcome::UserCanceled;
} else {
save(*path);
return EditorState::SaveOutcome::UserSaved;
}
}
};
2022-04-02 04:10:09 +02:00
2022-04-01 02:30:32 +02:00
std::optional<std::filesystem::path> EditorState::ask_for_save_path_if_needed() {
if (song_path) {
return song_path;
} else {
2022-04-09 00:54:06 +02:00
return feis::save_file_dialog();
2022-04-01 02:30:32 +02:00
}
2022-04-06 15:47:04 +02:00
};
2022-04-01 02:30:32 +02:00
2022-03-23 02:20:07 +01:00
void EditorState::move_backwards_in_time() {
auto beats = current_snaped_beats();
if (beats >= current_exact_beats()) {
2022-03-23 02:20:07 +01:00
beats -= get_snap_step();
}
set_playback_position(beats);
2022-03-23 02:20:07 +01:00
};
void EditorState::move_forwards_in_time() {
auto beats = current_snaped_beats();
if (beats <= current_exact_beats()) {
beats += get_snap_step();
}
set_playback_position(beats);
2022-03-23 02:20:07 +01:00
};
void EditorState::undo(NotificationsQueue& nq) {
auto previous = history.pop_previous();
if (previous) {
nq.push(std::make_shared<UndoNotification>(**previous));
(*previous)->undo_action(*this);
chart_state->density_graph.should_recompute = true;
2022-03-23 02:20:07 +01:00
}
};
void EditorState::redo(NotificationsQueue& nq) {
auto next = history.pop_next();
if (next) {
nq.push(std::make_shared<RedoNotification>(**next));
(*next)->do_action(*this);
chart_state->density_graph.should_recompute = true;
2022-03-23 02:20:07 +01:00
}
};
void EditorState::open_chart(const std::string& name) {
auto& [name_ref, chart] = *song.charts.find(name);
chart_state.emplace(chart, name_ref, history, assets);
reload_editable_range();
reload_applicable_timing();
2022-06-09 00:28:21 +02:00
note_claps->set_notes_and_timing(&chart.notes, &applicable_timing);
chord_claps->set_notes_and_timing(&chart.notes, &applicable_timing);
beat_ticks->set_timing(&applicable_timing);
};
void EditorState::update_visible_notes() {
if (chart_state) {
chart_state->update_visible_notes(
current_time(),
applicable_timing
);
}
};
2022-03-23 02:20:07 +01:00
void EditorState::reload_editable_range() {
const auto old_range = this->editable_range;
this->editable_range = choose_editable_range();
if (old_range != this->editable_range and this->chart_state.has_value()) {
chart_state->density_graph.should_recompute = true;
2022-03-23 02:20:07 +01:00
}
};
2022-03-23 02:20:07 +01:00
Interval<sf::Time> EditorState::choose_editable_range() {
Interval<sf::Time> new_range{sf::Time::Zero, sf::Time::Zero};
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
// If there is music, allow editing up to the end, but no further
// You've put notes *after* the end of the music ? fuck 'em.
2022-06-01 00:26:36 +02:00
new_range += (**music).getDuration();
return new_range;
} else {
// If there is no music :
// make sure we can edit 10 seconds after the end of the current chart
if (chart_state and not chart_state->chart.notes.empty()) {
const auto beat_of_last_event = chart_state->chart.notes.crbegin()->second.get_end();
new_range += time_at(beat_of_last_event) + sf::seconds(10);
}
// and at at least the first whole minute in any case
2022-03-23 02:20:07 +01:00
new_range += sf::seconds(60);
return new_range;
2022-03-23 02:20:07 +01:00
}
}
2022-03-23 02:20:07 +01:00
/*
* Reloads the album cover from what's indicated in the "album cover path" field
* of the song Resets the album cover state if anything fails
*/
void EditorState::reload_jacket() {
if (not song_path.has_value() or song.metadata.jacket.empty()) {
2022-03-23 02:20:07 +01:00
jacket.reset();
return;
}
jacket.emplace();
auto jacket_path = song_path->parent_path() / song.metadata.jacket;
2022-03-23 02:20:07 +01:00
if (
not std::filesystem::exists(jacket_path)
or not jacket->loadFromFile(jacket_path.string())
) {
jacket.reset();
2022-04-19 23:13:18 +02:00
} else {
jacket->setSmooth(true);
2022-03-23 02:20:07 +01:00
}
};
/*
* Reloads music from what's indicated in the "music path" field of the song
* Resets the music state in case anything fails
* Updates playbackPosition and preview_end as well
*/
void EditorState::reload_music() {
2022-10-11 01:54:43 +02:00
const auto status_before = get_status();
if (not song_path.has_value() or song.metadata.audio.empty()) {
clear_music();
2022-03-23 02:20:07 +01:00
return;
}
const auto absolute_music_path = song_path->parent_path() / song.metadata.audio;
2022-06-01 00:26:36 +02:00
try {
music.emplace(std::make_shared<OpenMusic>(absolute_music_path));
} catch (const std::exception& e) {
clear_music();
2022-06-01 00:26:36 +02:00
}
2022-03-23 02:20:07 +01:00
2022-03-17 02:50:30 +01:00
reload_editable_range();
2022-04-19 23:13:18 +02:00
set_speed(speed);
2022-06-01 00:26:36 +02:00
if (music.has_value()) {
audio.add_stream(music_stream, {*music, false});
2022-06-01 00:26:36 +02:00
} else {
audio.remove_stream(music_stream);
}
2022-10-11 01:54:43 +02:00
pause();
set_playback_position(current_time());
switch (status_before) {
case sf::SoundSource::Playing:
play();
break;
case sf::SoundSource::Paused:
pause();
break;
case sf::SoundSource::Stopped:
stop();
break;
}
2022-03-23 02:20:07 +01:00
};
void EditorState::clear_music() {
audio.remove_stream(music_stream);
music.reset();
}
2022-03-23 02:20:07 +01:00
void EditorState::reload_preview_audio() {
if (not song_path.has_value() or song.metadata.preview_file.empty()) {
2022-03-23 02:20:07 +01:00
preview_audio.reset();
return;
}
const auto path = song_path->parent_path() / song.metadata.preview_file;
preview_audio.emplace();
if (not preview_audio->openFromFile(path.string())) {
2022-03-23 02:20:07 +01:00
preview_audio.reset();
}
};
void EditorState::reload_applicable_timing() {
if (chart_state and chart_state->chart.timing) {
applicable_timing = *chart_state->chart.timing;
} else {
applicable_timing = song.timing;
}
2022-04-06 15:47:04 +02:00
};
2022-03-17 02:50:30 +01:00
void EditorState::save(const std::filesystem::path& path) {
2022-04-02 04:10:09 +02:00
const auto memon = song.dump_to_memon_1_0_0();
nowide::ofstream file{path};
if (not file) {
throw std::runtime_error(
fmt::format("Cannot write to file {}", path.string())
);
}
file << memon.dump(4) << std::endl;
file.close();
if (not file) {
throw std::runtime_error(
fmt::format("Error while closing file {}", path.string())
);
}
2022-04-09 00:54:06 +02:00
song_path = path;
history.mark_as_saved();
2022-04-06 15:47:04 +02:00
};
2022-04-09 00:54:06 +02:00
void feis::save(
std::optional<EditorState>& ed,
NotificationsQueue& nq
) {
if (ed) {
if (ed->save_if_needed() == EditorState::SaveOutcome::UserSaved) {
nq.push(std::make_shared<TextNotification>("Saved file"));
}
}
}
// SAVE if needed and the user asked to, then ASK for a file to opne, then OPEN than file
void feis::save_ask_open(
std::optional<EditorState>& ed,
const std::filesystem::path& assets,
const std::filesystem::path& settings
) {
if (ed and ed->save_if_needed_and_user_wants_to() == EditorState::SaveOutcome::UserCanceled) {
return;
2022-04-06 15:47:04 +02:00
}
2022-04-09 00:54:06 +02:00
if (const auto& filepath = feis::open_file_dialog()) {
feis::open_from_file(ed, *filepath, assets, settings);
}
};
// SAVE if needed and the user asked to, then OPEN the file passed as argument
void feis::save_open(
std::optional<EditorState>& ed,
const std::filesystem::path& song_path,
const std::filesystem::path& assets,
const std::filesystem::path& settings
) {
if (ed and ed->save_if_needed_and_user_wants_to() == EditorState::SaveOutcome::UserCanceled) {
return;
}
2022-04-09 00:54:06 +02:00
feis::open_from_file(ed, song_path, assets, settings);
2022-04-06 15:47:04 +02:00
};
2022-04-09 00:54:06 +02:00
2022-04-03 15:59:05 +02:00
void feis::open_from_file(
std::optional<EditorState>& ed,
2022-04-09 00:54:06 +02:00
const std::filesystem::path& song_path,
const std::filesystem::path& assets,
const std::filesystem::path& settings
) {
try {
2022-04-09 00:54:06 +02:00
// force utf-8 song path on windows
nowide::ifstream f{song_path.string()};
if (not f) {
tinyfd_messageBox(
"Error",
fmt::format("Could not open file {}", song_path.string()).c_str(),
"ok",
"error",
1
);
return;
};
const auto json = load_json_preserving_decimals(f);
auto song = better::Song::load_from_memon(json);
ed.emplace(song, assets, song_path);
Toolbox::pushNewRecentFile(std::filesystem::canonical(song_path), settings);
2021-12-31 14:59:39 +01:00
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
2022-04-06 15:47:04 +02:00
};
2022-04-09 00:54:06 +02:00
void feis::save_close(std::optional<EditorState>& ed) {
if (ed and ed->save_if_needed_and_user_wants_to() == EditorState::SaveOutcome::UserCanceled) {
return;
}
ed.reset();
}
/*
* Returns the newly created chart if there is one
*/
std::optional<std::pair<std::string, better::Chart>> feis::NewChartDialog::display(EditorState& editorState) {
if (ImGui::Begin(
"New Chart",
2022-10-11 21:23:29 +02:00
&editorState.show_new_chart_dialog,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (show_custom_dif_name) {
combo_preview = "Custom";
} else {
if (difficulty.empty()) {
combo_preview = "Choose One";
} else {
combo_preview = difficulty;
}
}
if (ImGui::BeginCombo("Difficulty", combo_preview.c_str())) {
2021-12-31 14:59:39 +01:00
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (not editorState.song.charts.contains(dif_name)) {
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable(dif_name, dif_name == difficulty)) {
show_custom_dif_name = false;
difficulty = dif_name;
}
} else {
2020-05-22 11:02:47 +02:00
ImGui::TextDisabled("%s", dif_name);
}
}
ImGui::Separator();
if (ImGui::Selectable("Custom", &show_custom_dif_name)) {
difficulty = "";
}
ImGui::EndCombo();
}
if (show_custom_dif_name) {
feis::InputTextColored(
2021-12-31 14:59:39 +01:00
"Difficulty Name",
&difficulty,
not editorState.song.charts.contains(difficulty),
"Chart name has to be unique"
);
}
feis::InputDecimal("Level", &level);
2019-01-16 13:23:36 +01:00
ImGui::Separator();
ImGui::BeginDisabled(difficulty.empty() or (editorState.song.charts.contains(difficulty)));
if (ImGui::Button("Create Chart##New Chart")) {
ImGui::EndDisabled();
ImGui::End();
try {
return {{difficulty, {.level = level}}};
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
return {};
}
};
ImGui::EndDisabled();
}
ImGui::End();
return {};
2022-04-06 15:47:04 +02:00
};
void feis::ChartPropertiesDialog::display(EditorState& editor_state) {
assert(editor_state.chart_state.has_value());
if (this->should_refresh_values) {
should_refresh_values = false;
difficulty_names_in_use.clear();
this->level = editor_state.chart_state->chart.level.value_or(0);
this->difficulty_name = editor_state.chart_state->difficulty_name;
this->show_custom_dif_name = (
2022-04-19 23:13:18 +02:00
difficulty_name != "BSC"
and difficulty_name != "ADV"
and difficulty_name != "EXT"
);
for (auto const& [name, _] : editor_state.song.charts) {
if (name != editor_state.chart_state->difficulty_name) {
difficulty_names_in_use.insert(name);
}
}
}
if (ImGui::Begin(
"Chart Properties",
2022-10-11 21:23:29 +02:00
&editor_state.show_chart_properties,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (show_custom_dif_name) {
combo_preview = "Custom";
} else {
if (difficulty_name.empty()) {
combo_preview = "Choose One";
} else {
combo_preview = difficulty_name;
}
}
if (ImGui::BeginCombo("Difficulty", combo_preview.c_str())) {
2021-12-31 14:59:39 +01:00
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (not difficulty_names_in_use.contains(dif_name)) {
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable(dif_name, dif_name == difficulty_name)) {
show_custom_dif_name = false;
difficulty_name = dif_name;
}
} else {
2020-05-22 11:02:47 +02:00
ImGui::TextDisabled("%s", dif_name);
}
}
ImGui::Separator();
if (ImGui::Selectable("Custom", &show_custom_dif_name)) {
difficulty_name = "";
}
ImGui::EndCombo();
}
if (show_custom_dif_name) {
feis::InputTextColored(
2021-12-31 14:59:39 +01:00
"Difficulty Name",
&difficulty_name,
not difficulty_names_in_use.contains(difficulty_name),
"Chart name has to be unique"
);
}
feis::InputDecimal("Level", &level);
ImGui::Separator();
2021-12-31 14:59:39 +01:00
if (difficulty_name.empty()
or (difficulty_names_in_use.find(difficulty_name) != difficulty_names_in_use.end())) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
ImGui::Button("Apply##New Chart");
ImGui::PopItemFlag();
ImGui::PopStyleVar();
} else {
if (ImGui::Button("Apply##New Chart")) {
const auto old_dif_name = editor_state.chart_state->difficulty_name;
const auto old_level = editor_state.chart_state->chart.level;
try {
auto modified_chart = editor_state.song.charts.extract(editor_state.chart_state->difficulty_name);
modified_chart.key() = this->difficulty_name;
modified_chart.mapped().level = this->level;
const auto [_1, inserted, _2] = editor_state.song.charts.insert(std::move(modified_chart));
if (not inserted) {
throw std::runtime_error("Could not insert modified chart in song");
} else {
editor_state.open_chart(this->difficulty_name);
if (old_dif_name != this->difficulty_name) {
editor_state.history.push(
std::make_shared<RenameChart>(
old_dif_name,
this->difficulty_name
)
);
}
if (old_level != this->level) {
editor_state.history.push(
std::make_shared<RerateChart>(
this->difficulty_name,
old_level,
this->level
)
);
}
should_refresh_values = true;
}
} catch (const std::exception& e) {
2021-12-31 14:59:39 +01:00
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
}
ImGui::End();
2022-04-06 15:47:04 +02:00
};