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

881 lines
31 KiB
C++
Raw Normal View History

2021-12-31 14:59:39 +01:00
#include "editor_state.hpp"
#include <algorithm>
#include <cmath>
2019-01-12 03:13:30 +01:00
#include <filesystem>
#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>
2021-12-31 00:57:06 +01:00
#include <tinyfiledialogs.h>
EditorState::EditorState(Fumen& fumen, std::filesystem::path assets) :
fumen(fumen),
playfield(assets),
linearView(assets)
{
reloadFromFumen(assets);
}
void EditorState::reloadFromFumen(std::filesystem::path assets) {
if (not this->fumen.Charts.empty()) {
this->chart.emplace(this->fumen.Charts.begin()->second, assets);
} else {
this->chart.reset();
}
reloadMusic();
reloadAlbumCover();
}
/*
* Reloads music from what's indicated in the "music path" field of the fumen
* Resets the music state in case anything fails
2019-03-27 14:41:16 +01:00
* Updates playbackPosition and previewEnd as well
*/
void EditorState::reloadMusic() {
2022-02-28 23:10:22 +01:00
const auto music_path = std::filesystem::path(fumen.path).parent_path() / fumen.musicPath;
try {
music.emplace(music_path);
} catch (const std::exception& e) {
music.reset();
2019-01-12 03:13:30 +01:00
}
2022-02-28 23:10:22 +01:00
reloadPreviewEnd();
2019-01-14 04:20:30 +01:00
auto seconds_position = std::clamp(playbackPosition.asSeconds(), -(fumen.offset), previewEnd.asSeconds());
playbackPosition = sf::seconds(seconds_position);
2019-01-17 03:02:37 +01:00
previousPos = playbackPosition;
2019-04-09 23:07:22 +02:00
}
void EditorState::reloadPreviewEnd() {
auto old_previewEnd = previewEnd;
float music_duration = 0;
2019-01-14 04:20:30 +01:00
if (music) {
music_duration = music->getDuration().asSeconds();
}
float chart_runtime = 0;
if (chart) {
chart_runtime = fumen.getChartRuntime(chart->ref);
2019-01-14 04:20:30 +01:00
}
// Chart end in seconds using the music file "coordinate system"
// (beat 0 is at -offset seconds)
float chart_end = std::max(chart_runtime - fumen.offset, 0.f);
float preview_end_seconds = std::max(music_duration, chart_end) ;
// Add some extra time at the end to allow for more notes to be placed
// after the end of the chart
// TODO: is this really the way to do it ?
preview_end_seconds += 2.f;
previewEnd = sf::seconds(preview_end_seconds);
if (old_previewEnd != previewEnd and chart) {
chart->densityGraph.should_recompute = true;
}
2019-01-12 03:13:30 +01:00
}
/*
2021-12-31 14:59:39 +01:00
* Reloads the album cover from what's indicated in the "album cover path" field
* of the fumen Resets the album cover state if anything fails
*/
void EditorState::reloadAlbumCover() {
albumCover.emplace();
2019-03-27 14:41:16 +01:00
2021-12-31 14:59:39 +01:00
std::filesystem::path album_cover_path =
std::filesystem::path(fumen.path).parent_path() / fumen.albumCoverPath;
2019-03-27 14:41:16 +01:00
2021-12-31 14:59:39 +01:00
if (fumen.albumCoverPath.empty() or not std::filesystem::exists(album_cover_path)
or not albumCover->loadFromFile(album_cover_path.string())) {
albumCover.reset();
}
}
void EditorState::setPlaybackAndMusicPosition(sf::Time newPosition) {
2019-04-09 23:07:22 +02:00
reloadPreviewEnd();
newPosition = sf::seconds(
std::clamp(
newPosition.asSeconds(),
-fumen.offset,
previewEnd.asSeconds()
)
);
2021-12-31 14:59:39 +01:00
previousPos = sf::seconds(newPosition.asSeconds() - 1.f / 60.f);
playbackPosition = newPosition;
if (music) {
if (playbackPosition.asSeconds() >= 0 and playbackPosition < music->getDuration()) {
music->setPlayingOffset(playbackPosition);
} else {
music->stop();
}
}
}
void EditorState::displayPlayfield(Marker& marker, MarkerEndingState 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),
Toolbox::CustomConstraints::ContentSquare);
2019-01-14 21:43:56 +01:00
2021-12-31 14:59:39 +01:00
if (ImGui::Begin("Playfield", &showPlayfield, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
if (not ImGui::IsWindowHovered() and chart and chart->creatingLongNote) {
// cancel long note creation if the mouse is or goes out of the playfield
chart->longNoteBeingCreated.reset();
chart->creatingLongNote = false;
}
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
if (chart) {
playfield.resize(static_cast<unsigned int>(ImGui::GetWindowSize().x));
2021-12-31 14:59:39 +01:00
auto longNoteDummy =
chart->makeLongNoteDummy(static_cast<int>(roundf(getCurrentTick())));
if (longNoteDummy) {
2021-12-31 14:59:39 +01:00
playfield.drawLongNote(
*longNoteDummy,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution());
}
for (auto const& note : visibleNotes) {
2021-12-31 14:59:39 +01:00
float note_offset =
(playbackPosition.asSeconds() - getSecondsAt(note.getTiming()));
// auto frame = static_cast<long long
// int>(std::floor(note_offset * 30.f));
int x = note.getPos() % 4;
int y = note.getPos() / 4;
2019-02-09 16:05:46 +01:00
if (note.getLength() == 0) {
// Display normal note
auto t = marker.getSprite(markerEndingState, note_offset);
2019-02-13 00:44:46 +01:00
2019-02-09 16:05:46 +01:00
if (t) {
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + 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
}
2019-02-13 00:44:46 +01:00
2019-02-09 16:05:46 +01:00
} else {
2021-12-31 14:59:39 +01:00
playfield.drawLongNote(
note,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution(),
marker,
markerEndingState);
2019-01-14 21:43:56 +01:00
}
}
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, TitlebarHeight});
2022-01-05 23:18:13 +01:00
ImGui::Image(playfield.longNoteLayer);
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, TitlebarHeight});
2022-01-05 23:18:13 +01:00
ImGui::Image(playfield.markerLayer);
2019-01-14 21:43:56 +01:00
}
2019-02-09 16:05:46 +01:00
// Display button grid
2019-01-14 21:43:56 +01:00
for (int y = 0; y < 4; ++y) {
for (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)) {
toggleNoteAtCurrentTime(x + 4 * y);
}
if (ImGui::IsItemHovered() and chart and chart->creatingLongNote) {
// Deal with long note creation stuff
if (not chart->longNoteBeingCreated) {
Note current_note =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
chart->longNoteBeingCreated =
std::make_pair(current_note, current_note);
} else {
chart->longNoteBeingCreated->second =
Note(x + 4 * y, static_cast<int>(roundf(getCurrentTick())));
}
}
2019-01-14 21:43:56 +01:00
ImGui::PopStyleColor(3);
ImGui::PopID();
}
}
if (chart) {
// Check for collisions then display them
2021-12-31 14:59:39 +01:00
auto ticks_threshold =
static_cast<int>((1.f / 60.f) * fumen.BPM * getResolution());
std::array<bool, 16> collisions = {};
for (auto const& note : visibleNotes) {
2021-12-31 14:59:39 +01:00
if (chart->ref.is_colliding(note, ticks_threshold)) {
collisions[note.getPos()] = 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 (auto const& note : visibleNotes) {
if (chart->selectedNotes.find(note) != chart->selectedNotes.end()) {
2021-12-31 14:59:39 +01:00
int x = note.getPos() % 4;
int y = note.getPos() / 4;
ImGui::SetCursorPos({x * squareSize, TitlebarHeight + 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();
2019-01-13 22:29:29 +01:00
}
/*
* Display all metadata in an editable form
*/
void EditorState::displayProperties() {
2021-12-31 14:59:39 +01:00
ImGui::SetNextWindowSize(ImVec2(500, 240));
ImGui::Begin("Properties", &showProperties, ImGuiWindowFlags_NoResize);
{
ImGui::Columns(2, nullptr, false);
if (albumCover) {
2021-12-31 14:59:39 +01:00
ImGui::Image(*albumCover, sf::Vector2f(200, 200));
} else {
2021-12-31 14:59:39 +01:00
ImGui::BeginChild("Album Cover", ImVec2(200, 200), true);
ImGui::EndChild();
}
ImGui::NextColumn();
2021-12-31 14:59:39 +01:00
ImGui::InputText("Title", &fumen.songTitle);
ImGui::InputText("Artist", &fumen.artist);
if (Toolbox::InputTextColored(
music.has_value(),
"Invalid Music Path",
"Music",
&fumen.musicPath)) {
reloadMusic();
2019-01-16 13:23:36 +01:00
}
2021-12-31 14:59:39 +01:00
if (Toolbox::InputTextColored(
albumCover.has_value(),
"Invalid Album Cover Path",
"Album Cover",
&fumen.albumCoverPath)) {
reloadAlbumCover();
}
2021-12-31 14:59:39 +01:00
if (ImGui::InputFloat("BPM", &fumen.BPM, 1.0f, 10.0f)) {
if (fumen.BPM <= 0.0f) {
fumen.BPM = 0.0f;
}
}
2021-12-31 14:59:39 +01:00
ImGui::InputFloat("offset", &fumen.offset, 0.01f, 1.f);
}
ImGui::End();
}
/*
2021-12-31 14:59:39 +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::displayStatus() {
2021-12-31 14:59:39 +01:00
ImGui::Begin("Status", &showStatus, ImGuiWindowFlags_AlwaysAutoResize);
{
if (not music) {
if (not fumen.musicPath.empty()) {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid music path : %s",
fumen.musicPath.c_str());
} else {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No music file loaded");
}
}
if (not albumCover) {
if (not fumen.albumCoverPath.empty()) {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"Invalid albumCover path : %s",
fumen.albumCoverPath.c_str());
} else {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(1, 0.42, 0.41, 1),
"No albumCover loaded");
}
}
2021-12-31 14:59:39 +01:00
if (ImGui::SliderInt("Music Volume", &musicVolume, 0, 10)) {
setMusicVolume(musicVolume);
}
}
ImGui::End();
}
void EditorState::displayPlaybackStatus() {
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",
&showPlaybackStatus,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize);
{
if (chart) {
2021-12-31 14:59:39 +01:00
ImGui::Text("%s %d", chart->ref.dif_name.c_str(), chart->ref.level);
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
}
2021-12-31 14:59:39 +01:00
ImGui::TextDisabled("Snap : ");
ImGui::SameLine();
ImGui::Text("%s", Toolbox::toOrdinal(snap * 4).c_str());
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Beats :");
ImGui::SameLine();
ImGui::Text("%02.2f", this->getBeats());
ImGui::SameLine();
if (music) {
2021-12-31 14:59:39 +01:00
ImGui::TextColored(
ImVec4(0.53, 0.53, 0.53, 1),
"Music File Offset :");
ImGui::SameLine();
2022-02-28 23:10:22 +01:00
ImGui::TextUnformatted(Toolbox::to_string(music->getPrecisePlayingOffset()).c_str());
2021-12-31 14:59:39 +01:00
ImGui::SameLine();
}
2021-12-31 14:59:39 +01:00
ImGui::TextColored(ImVec4(0.53, 0.53, 0.53, 1), "Timeline Position :");
ImGui::SameLine();
ImGui::TextUnformatted(Toolbox::to_string(playbackPosition).c_str());
}
ImGui::End();
ImGui::PopStyleVar();
}
void EditorState::displayTimeline() {
ImGuiIO& io = ImGui::GetIO();
float height = io.DisplaySize.y * 0.9f;
if (chart) {
if (chart->densityGraph.should_recompute) {
chart->densityGraph.should_recompute = false;
chart->densityGraph.update(
2021-12-31 14:59:39 +01:00
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
} else {
if (chart->densityGraph.last_height) {
if (static_cast<int>(height) != *(chart->densityGraph.last_height)) {
chart->densityGraph.update(
2021-12-31 14:59:39 +01:00
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
} else {
chart->densityGraph.update(
2021-12-31 14:59:39 +01:00
static_cast<int>(height),
getChartRuntime(),
chart->ref,
fumen.BPM,
getResolution());
}
}
}
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, 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",
&showTimeline,
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove);
{
if (chart) {
2021-12-31 14:59:39 +01:00
ImGui::SetCursorPos({0, 0});
2022-01-05 23:18:13 +01:00
ImGui::Image(chart->densityGraph.graph);
2021-12-31 14:59:39 +01:00
AffineTransform<float> scroll(-fumen.offset, previewEnd.asSeconds(), 1.f, 0.f);
2019-01-14 04:20:30 +01:00
float slider_pos = scroll.transform(playbackPosition.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, "")) {
setPlaybackAndMusicPosition(sf::seconds(scroll.backwards_transform(slider_pos)));
}
}
}
ImGui::End();
ImGui::PopStyleColor(6);
ImGui::PopStyleVar(3);
}
void EditorState::displayChartList(std::filesystem::path assets) {
2021-12-31 14:59:39 +01:00
if (ImGui::Begin("Chart List", &showChartList, ImGuiWindowFlags_AlwaysAutoResize)) {
if (this->fumen.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();
2019-01-18 14:02:55 +01:00
for (auto& tuple : fumen.Charts) {
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable(
tuple.first.c_str(),
chart ? chart->ref == tuple.second : false,
ImGuiSelectableFlags_SpanAllColumns)) {
ESHelper::save(*this);
chart.emplace(tuple.second, assets);
}
ImGui::NextColumn();
2021-12-31 14:59:39 +01:00
ImGui::Text("%d", tuple.second.level);
ImGui::NextColumn();
ImGui::Text("%d", static_cast<int>(tuple.second.Notes.size()));
ImGui::NextColumn();
2019-01-16 13:23:36 +01:00
ImGui::PushID(&tuple);
ImGui::PopID();
}
}
}
ImGui::End();
}
2019-02-09 16:05:46 +01:00
2019-03-26 00:04:29 +01:00
void EditorState::displayLinearView() {
2021-12-31 14:59:39 +01:00
ImGui::SetNextWindowSize(ImVec2(204, 400), ImGuiCond_Once);
ImGui::SetNextWindowSizeConstraints(ImVec2(204, 204), ImVec2(FLT_MAX, FLT_MAX));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2, 2));
if (ImGui::Begin("Linear View", &showLinearView, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
2019-03-27 14:41:16 +01:00
if (chart) {
2021-12-31 14:59:39 +01:00
linearView.update(
chart,
playbackPosition,
getCurrentTick(),
fumen.BPM,
getResolution(),
ImGui::GetContentRegionMax());
2022-01-05 23:18:13 +01:00
auto cursor_y = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.f;
ImGui::SetCursorPos({0, cursor_y});
ImGui::Image(linearView.view);
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);
}
saveChangesResponses EditorState::alertSaveChanges() {
if (chart and (not chart->history.empty())) {
2021-12-31 14:59:39 +01:00
int response = tinyfd_messageBox(
"Warning",
"Do you want to save changes ?",
"yesnocancel",
"warning",
1);
switch (response) {
// cancel
case 0:
return saveChangesCancel;
// yes
case 1:
return saveChangesYes;
// no
case 2:
return saveChangesNo;
default:
std::stringstream ss;
ss << "Got unexcpected result from tinyfd_messageBox : " << response;
throw std::runtime_error(ss.str());
}
} else {
return saveChangesDidNotDisplayDialog;
}
}
/*
* Saves if asked and returns false if user canceled
*/
bool EditorState::saveChangesOrCancel() {
2021-12-31 14:59:39 +01:00
switch (alertSaveChanges()) {
case saveChangesYes:
ESHelper::save(*this);
case saveChangesNo:
case saveChangesDidNotDisplayDialog:
return true;
case saveChangesCancel:
default:
return false;
}
}
2019-02-09 16:05:46 +01:00
/*
* This SCREAAAAMS for optimisation, but in the meantime it works !
*/
void EditorState::updateVisibleNotes() {
2019-01-17 03:02:37 +01:00
visibleNotes.clear();
if (chart) {
2019-02-09 16:05:46 +01:00
float position = playbackPosition.asSeconds();
2019-01-14 21:43:56 +01:00
for (auto const& note : chart->ref.Notes) {
2019-02-09 16:05:46 +01:00
float note_timing_in_seconds = getSecondsAt(note.getTiming());
2021-12-31 14:59:39 +01:00
// we can leave early if the note is happening too far after the
// position
if (position > note_timing_in_seconds - 16.f / 30.f) {
2019-02-09 16:05:46 +01:00
if (note.getLength() == 0) {
2021-12-31 14:59:39 +01:00
if (position < note_timing_in_seconds + 16.f / 30.f) {
2019-02-09 16:05:46 +01:00
visibleNotes.insert(note);
}
} else {
2021-12-31 14:59:39 +01:00
float tail_end_in_seconds =
getSecondsAt(note.getTiming() + note.getLength());
if (position < tail_end_in_seconds + 16.f / 30.f) {
2019-02-09 16:05:46 +01:00
visibleNotes.insert(note);
}
}
}
}
}
}
/*
* If a note is visible for the given pos, delete it
* Otherwise create note at nearest tick
*/
void EditorState::toggleNoteAtCurrentTime(int pos) {
if (chart) {
std::set<Note> toggledNotes = {};
bool deleted_something = false;
for (auto note : visibleNotes) {
if (note.getPos() == pos) {
toggledNotes.insert(note);
chart->ref.Notes.erase(note);
deleted_something = true;
break;
2019-01-14 21:43:56 +01:00
}
}
if (not deleted_something) {
2021-12-31 14:59:39 +01:00
toggledNotes.emplace(pos, static_cast<int>(roundf(getCurrentTick())));
chart->ref.Notes.emplace(pos, static_cast<int>(roundf(getCurrentTick())));
}
chart->history.push(std::make_shared<ToggledNotes>(toggledNotes, not deleted_something));
chart->densityGraph.should_recompute = true;
2019-01-14 21:43:56 +01:00
}
}
void EditorState::setMusicSpeed(int newMusicSpeed) {
2021-12-31 14:59:39 +01:00
musicSpeed = std::clamp(newMusicSpeed, 1, 20);
if (music) {
2021-12-31 14:59:39 +01:00
music->setPitch(musicSpeed / 10.f);
}
}
2022-02-09 02:53:41 +01:00
void EditorState::musicSpeedUp() {
setMusicSpeed(musicSpeed + 1);
}
void EditorState::musicSpeedDown() {
setMusicSpeed(musicSpeed - 1);
}
void EditorState::setMusicVolume(int newMusicVolume) {
2021-12-31 14:59:39 +01:00
musicVolume = std::clamp(newMusicVolume, 0, 10);
if (music) {
2022-02-09 02:53:41 +01:00
music->setVolume(Toolbox::convertVolumeToNormalizedDB(musicVolume)*100.f);
}
}
2022-02-09 02:53:41 +01:00
void EditorState::musicVolumeUp() {
setMusicVolume(musicVolume + 1);
}
void EditorState::musicVolumeDown() {
setMusicVolume(musicVolume -1 );
}
2021-12-31 14:59:39 +01:00
const sf::Time& EditorState::getPreviewEnd() {
2019-04-09 23:07:22 +02:00
reloadPreviewEnd();
return previewEnd;
}
2019-01-13 22:29:29 +01:00
void ESHelper::save(EditorState& ed) {
try {
2019-01-13 22:29:29 +01:00
ed.fumen.autoSaveAsMemon();
} catch (const std::exception& e) {
2021-12-31 14:59:39 +01:00
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
void ESHelper::open(std::optional<EditorState>& ed, std::filesystem::path assets, std::filesystem::path settings) {
2021-12-31 14:59:39 +01:00
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);
}
}
void ESHelper::openFromFile(
std::optional<EditorState>& ed,
std::filesystem::path file,
std::filesystem::path assets,
std::filesystem::path settings
) {
try {
Fumen f(file);
f.autoLoadFromMemon();
ed.emplace(f, assets);
Toolbox::pushNewRecentFile(std::filesystem::canonical(ed->fumen.path), settings);
2021-12-31 14:59:39 +01:00
} catch (const std::exception& e) {
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
/*
* 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->saveChangesOrCancel();
} else {
return true;
}
}
/*
* Returns the newly created chart if there is one
*/
2021-12-31 14:59:39 +01:00
std::optional<Chart> ESHelper::NewChartDialog::display(EditorState& editorState) {
std::optional<Chart> newChart;
if (ImGui::Begin(
"New Chart",
&editorState.showNewChartDialog,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (showCustomDifName) {
comboPreview = "Custom";
} else {
if (difficulty.empty()) {
comboPreview = "Choose One";
} else {
comboPreview = difficulty;
}
}
2021-12-31 14:59:39 +01:00
if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) {
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (editorState.fumen.Charts.find(dif_name)
== editorState.fumen.Charts.end()) {
if (ImGui::Selectable(dif_name, dif_name == difficulty)) {
showCustomDifName = false;
difficulty = dif_name;
}
} else {
2020-05-22 11:02:47 +02:00
ImGui::TextDisabled("%s", dif_name);
}
}
ImGui::Separator();
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable("Custom", &showCustomDifName)) {
difficulty = "";
}
ImGui::EndCombo();
}
if (showCustomDifName) {
2019-01-16 13:23:36 +01:00
Toolbox::InputTextColored(
2021-12-31 14:59:39 +01:00
editorState.fumen.Charts.find(difficulty)
== editorState.fumen.Charts.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty);
}
2021-12-31 14:59:39 +01:00
ImGui::InputInt("Level", &level);
2019-01-16 13:23:36 +01:00
ImGui::Separator();
if (ImGui::TreeNode("Advanced##New Chart")) {
ImGui::Unindent(ImGui::GetTreeNodeToLabelSpacing());
2021-12-31 14:59:39 +01:00
if (ImGui::InputInt("Resolution", &resolution)) {
2019-01-16 13:23:36 +01:00
if (resolution < 1) {
resolution = 1;
}
}
2019-01-16 13:23:36 +01:00
ImGui::SameLine();
ImGui::TextDisabled("(?)");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::TextUnformatted("Number of ticks in a beat");
ImGui::BulletText("Has nothing to do with time signature");
2021-12-31 14:59:39 +01:00
ImGui::BulletText(
"Leave the default unless you know what you're doing");
2019-01-16 13:23:36 +01:00
ImGui::EndTooltip();
}
ImGui::Indent(ImGui::GetTreeNodeToLabelSpacing());
ImGui::TreePop();
}
2019-01-16 13:23:36 +01:00
ImGui::Separator();
2021-12-31 14:59:39 +01:00
if (difficulty.empty()
or (editorState.fumen.Charts.find(difficulty)
!= editorState.fumen.Charts.end())) {
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
ImGui::Button("Create Chart##New Chart");
ImGui::PopItemFlag();
ImGui::PopStyleVar();
} else {
if (ImGui::Button("Create Chart##New Chart")) {
try {
2021-12-31 14:59:39 +01:00
newChart.emplace(difficulty, level, resolution);
} catch (const std::exception& e) {
2021-12-31 14:59:39 +01:00
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
}
ImGui::End();
return newChart;
}
void ESHelper::ChartPropertiesDialog::display(EditorState& editorState, std::filesystem::path assets) {
assert(editorState.chart.has_value());
if (this->shouldRefreshValues) {
shouldRefreshValues = false;
difNamesInUse.clear();
this->level = editorState.chart->ref.level;
this->difficulty_name = editorState.chart->ref.dif_name;
2021-12-31 14:59:39 +01:00
std::set<std::string> difNames {"BSC", "ADV", "EXT"};
showCustomDifName = (difNames.find(difficulty_name) == difNames.end());
for (auto const& tuple : editorState.fumen.Charts) {
if (tuple.second != editorState.chart->ref) {
difNamesInUse.insert(tuple.first);
}
}
}
if (ImGui::Begin(
"Chart Properties",
&editorState.showChartProperties,
2021-12-31 14:59:39 +01:00
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_AlwaysAutoResize)) {
if (showCustomDifName) {
comboPreview = "Custom";
} else {
if (difficulty_name.empty()) {
comboPreview = "Choose One";
} else {
comboPreview = difficulty_name;
}
}
2021-12-31 14:59:39 +01:00
if (ImGui::BeginCombo("Difficulty", comboPreview.c_str())) {
for (auto dif_name : {"BSC", "ADV", "EXT"}) {
if (difNamesInUse.find(dif_name) == difNamesInUse.end()) {
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable(dif_name, dif_name == difficulty_name)) {
showCustomDifName = false;
difficulty_name = dif_name;
}
} else {
2020-05-22 11:02:47 +02:00
ImGui::TextDisabled("%s", dif_name);
}
}
ImGui::Separator();
2021-12-31 14:59:39 +01:00
if (ImGui::Selectable("Custom", &showCustomDifName)) {
difficulty_name = "";
}
ImGui::EndCombo();
}
if (showCustomDifName) {
Toolbox::InputTextColored(
2021-12-31 14:59:39 +01:00
difNamesInUse.find(difficulty_name) == difNamesInUse.end(),
"Chart name has to be unique",
"Difficulty Name",
&difficulty_name);
}
2021-12-31 14:59:39 +01:00
ImGui::InputInt("Level", &level);
ImGui::Separator();
2021-12-31 14:59:39 +01:00
if (difficulty_name.empty()
or (difNamesInUse.find(difficulty_name) != difNamesInUse.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")) {
try {
2021-12-31 14:59:39 +01:00
Chart modified_chart =
editorState.fumen.Charts.at(editorState.chart->ref.dif_name);
editorState.fumen.Charts.erase(editorState.chart->ref.dif_name);
modified_chart.dif_name = this->difficulty_name;
modified_chart.level = this->level;
2021-12-31 14:59:39 +01:00
if (not(editorState.fumen.Charts.emplace(modified_chart.dif_name, modified_chart))
.second) {
throw std::runtime_error(
"Could not insert modified chart in fumen");
} else {
2021-12-31 14:59:39 +01:00
editorState.chart.emplace(
editorState.fumen.Charts.at(modified_chart.dif_name),
assets
);
shouldRefreshValues = true;
}
} catch (const std::exception& e) {
2021-12-31 14:59:39 +01:00
tinyfd_messageBox("Error", e.what(), "ok", "error", 1);
}
}
}
}
ImGui::End();
}