From d623ff74ae37e5373dad964f1a4071cde552ef4d Mon Sep 17 00:00:00 2001 From: Stepland Date: Sat, 2 Mar 2019 13:47:26 +0100 Subject: [PATCH] Undo/Redo feature done ! - History cannot give back its last previous state - HistoryAction.cpp has everything you need to define actions stored in history - Added a History dialog showing the contents of the history --- CMakeLists.txt | 41 ++++++++++------- EditorState.cpp | 81 ++++++++++++++++++++-------------- EditorState.h | 19 ++++++-- History.h | 74 ++++++++++++++++++++++++------- HistoryActions.cpp | 66 +++++++++++++++++++++++++++ HistoryActions.h | 65 +++++++++++++++++++++++++++ include/imgui/imgui_stdlib.cpp | 65 +++++++++++++++++++++++++++ include/imgui/imgui_stdlib.h | 22 +++++++++ main.cpp | 52 ++++++++++++++++------ 9 files changed, 404 insertions(+), 81 deletions(-) create mode 100644 HistoryActions.cpp create mode 100644 HistoryActions.h create mode 100644 include/imgui/imgui_stdlib.cpp create mode 100644 include/imgui/imgui_stdlib.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ee0f1fa..6fe9773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,21 +3,27 @@ project(FEIS) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_CXX_EXTENSIONS ON) + +set(OpenGL_GL_PREFERENCE GLVND) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3") -SET(GCC_COVERAGE_COMPILE_FLAGS "-mwindows") -SET(GCC_COVERAGE_LINK_FLAGS "-mwindows") -SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}" ) -SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS} -static-libgcc -static-libstdc++") +if (WIN32) + message("Compiling for windows !") + SET(GCC_COVERAGE_COMPILE_FLAGS "${GCC_COVERAGE_COMPILE_FLAGS} -mwindows") + SET(GCC_COVERAGE_LINK_FLAGS "${GCC_COVERAGE_LINK_FLAGS} -mwindows") +endif(WIN32) -set(imgui imgui/imgui.cpp - imgui/imgui_draw.cpp - imgui/imgui_widgets.cpp - imgui/imgui-SFML.cpp - imgui/imgui_stdlib.cpp - imgui/imgui_demo.cpp) +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}" ) +SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${GCC_COVERAGE_LINK_FLAGS}") + +set(imgui include/imgui/imgui.cpp + include/imgui/imgui_draw.cpp + include/imgui/imgui_widgets.cpp + include/imgui/imgui-SFML.cpp + include/imgui/imgui_stdlib.cpp + include/imgui/imgui_demo.cpp) set(SOURCE_FILES main.cpp @@ -28,7 +34,7 @@ set(SOURCE_FILES Chart.cpp EditorState.cpp ${imgui} - tinyfiledialogs.c Toolbox.cpp Toolbox.h LNMarker.cpp LNMarker.h History.h) + tinyfiledialogs.c Toolbox.cpp Toolbox.h LNMarker.cpp LNMarker.h History.h HistoryActions.cpp HistoryActions.h) #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${FEIS_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}) @@ -37,12 +43,15 @@ find_library (WinMM libwinmm.a) find_package (SFML 2.5.1 COMPONENTS system window graphics network audio REQUIRED) find_package (OpenGL REQUIRED) -include_directories(${OPENGL_INCLUDE_DIR} imgui/) +include_directories(${OPENGL_INCLUDE_DIR} include/imgui/) add_executable(FEIS ${SOURCE_FILES}) -target_link_libraries(FEIS stdc++fs ${OPENGL_LIBRARIES} sfml-system sfml-window sfml-graphics sfml-network sfml-audio WinMM) -add_executable(ImGuiDemo ImGuiDemoMain.cpp ${imgui}) +set(TARGET_LINK_LIBS ${OPENGL_LIBRARIES} stdc++ stdc++fs sfml-system sfml-window sfml-graphics sfml-network sfml-audio) -target_link_libraries(ImGuiDemo ${OPENGL_LIBRARIES} sfml-system sfml-window sfml-graphics sfml-network sfml-audio WinMM) +if(WIN32) + set(TARGET_LINK_LIBS ${TARGET_LINK_LIBS} WinMM) +endif(WIN32) + +target_link_libraries(FEIS ${TARGET_LINK_LIBS}) diff --git a/EditorState.cpp b/EditorState.cpp index d33eda1..bd4dc88 100644 --- a/EditorState.cpp +++ b/EditorState.cpp @@ -18,9 +18,9 @@ EditorState::EditorState(Fumen &fumen) : fumen(fumen) { void EditorState::reloadFromFumen() { if (not this->fumen.Charts.empty()) { - this->selectedChart = this->fumen.Charts.begin()->second; + this->chart.emplace(this->fumen.Charts.begin()->second); } else { - this->selectedChart.reset(); + this->chart.reset(); } reloadMusic(); reloadJacket(); @@ -50,14 +50,14 @@ void EditorState::reloadPlaybackPositionAndChartRuntime() { playbackPosition = sf::seconds(-(fumen.offset)); previousPos = playbackPosition; if (music) { - if (selectedChart) { - chartRuntime = sf::seconds(std::max(music->getDuration().asSeconds(),fumen.getChartRuntime(*selectedChart)-fumen.offset)+2.f); + if (chart) { + chartRuntime = sf::seconds(std::max(music->getDuration().asSeconds(),fumen.getChartRuntime(chart->ref)-fumen.offset)+2.f); } else { chartRuntime = sf::seconds(std::max(-fumen.offset,music->getDuration().asSeconds())); } } else { - if (selectedChart) { - chartRuntime = sf::seconds(std::max(fumen.getChartRuntime(*selectedChart)-fumen.offset,2.f)); + if (chart) { + chartRuntime = sf::seconds(std::max(fumen.getChartRuntime(chart->ref)-fumen.offset,2.f)); } else { chartRuntime = sf::seconds(std::max(-fumen.offset,2.f)); } @@ -106,7 +106,7 @@ void EditorState::displayPlayfield(Marker& marker, MarkerEndingState markerEndin float squareSize = ImGui::GetWindowSize().x / 4.f; float TitlebarHeight = ImGui::GetWindowSize().y - ImGui::GetWindowSize().x; - if (selectedChart) { + if (chart) { int ImGuiIndex = 0; @@ -403,8 +403,8 @@ void EditorState::displayPlaybackStatus() { |ImGuiWindowFlags_AlwaysAutoResize ); { - if (selectedChart) { - ImGui::Text("%s %d",selectedChart->get().dif_name.c_str(),selectedChart->get().level); ImGui::SameLine(); + if (chart) { + ImGui::Text("%s %d",chart->ref.dif_name.c_str(),chart->ref.level); ImGui::SameLine(); } else { ImGui::TextDisabled("No chart selected"); ImGui::SameLine(); } @@ -469,8 +469,8 @@ void EditorState::displayChartList() { ImGui::TextDisabled("Note Count"); ImGui::NextColumn(); ImGui::Separator(); for (auto& tuple : fumen.Charts) { - if (ImGui::Selectable(tuple.first.c_str(), selectedChart ? selectedChart->get()==tuple.second : false , ImGuiSelectableFlags_SpanAllColumns)) { - selectedChart = tuple.second; + if (ImGui::Selectable(tuple.first.c_str(), chart ? chart->ref==tuple.second : false , ImGuiSelectableFlags_SpanAllColumns)) { + chart.emplace(tuple.second); } ImGui::NextColumn(); ImGui::Text("%d",tuple.second.level); ImGui::NextColumn(); @@ -490,11 +490,11 @@ void EditorState::updateVisibleNotes() { visibleNotes.clear(); - if (selectedChart) { + if (chart) { float position = playbackPosition.asSeconds(); - for (auto const& note : selectedChart->get().Notes) { + for (auto const& note : chart->ref.Notes) { float note_timing_in_seconds = getSecondsAt(note.getTiming()); @@ -520,18 +520,27 @@ void EditorState::updateVisibleNotes() { * Otherwise create note at nearest tick */ void EditorState::toggleNoteAtCurrentTime(int pos) { - if (selectedChart) { + + if (chart) { + + std::set toggledNotes = {}; + bool deleted_something = false; for (auto note : visibleNotes) { if (note.getPos() == pos) { - selectedChart->get().Notes.erase(note); + toggledNotes.insert(note); + chart->ref.Notes.erase(note); deleted_something = true; } } if (not deleted_something) { - selectedChart->get().Notes.emplace(pos,static_cast(roundf(getTicks()))); + toggledNotes.emplace(pos,static_cast(roundf(getTicks()))); + chart->ref.Notes.emplace(pos,static_cast(roundf(getTicks()))); } + + chart->history.push(std::make_shared(toggledNotes, not deleted_something)); } + } void ESHelper::save(EditorState& ed) { @@ -651,20 +660,20 @@ std::optional ESHelper::NewChartDialog::display(EditorState &editorState) void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) { - assert(editorState.selectedChart.has_value()); + assert(editorState.chart.has_value()); if (this->shouldRefreshValues) { shouldRefreshValues = false; difNamesInUse.clear(); - this->level = editorState.selectedChart->get().level; - this->difficulty = editorState.selectedChart->get().dif_name; + this->level = editorState.chart->ref.level; + this->difficulty_name = editorState.chart->ref.dif_name; std::set difNames{"BSC","ADV","EXT"}; - showCustomDifName = (difNames.find(difficulty) == difNames.end()); + showCustomDifName = (difNames.find(difficulty_name) == difNames.end()); for (auto const& tuple : editorState.fumen.Charts) { - if (tuple.second != editorState.selectedChart) { + if (tuple.second != editorState.chart->ref) { difNamesInUse.insert(tuple.first); } } @@ -680,18 +689,18 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) { if (showCustomDifName) { comboPreview = "Custom"; } else { - if (difficulty.empty()) { + if (difficulty_name.empty()) { comboPreview = "Choose One"; } else { - comboPreview = difficulty; + comboPreview = difficulty_name; } } if(ImGui::BeginCombo("Difficulty",comboPreview.c_str())) { for (auto dif_name : {"BSC","ADV","EXT"}) { if (difNamesInUse.find(dif_name) == difNamesInUse.end()) { - if(ImGui::Selectable(dif_name,dif_name == difficulty)) { + if(ImGui::Selectable(dif_name,dif_name == difficulty_name)) { showCustomDifName = false; - difficulty = dif_name; + difficulty_name = dif_name; } } else { ImGui::TextDisabled(dif_name); @@ -699,21 +708,21 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) { } ImGui::Separator(); if (ImGui::Selectable("Custom",&showCustomDifName)) { - difficulty = ""; + difficulty_name = ""; } ImGui::EndCombo(); } if (showCustomDifName) { Toolbox::InputTextColored( - difNamesInUse.find(difficulty) == difNamesInUse.end(), + difNamesInUse.find(difficulty_name) == difNamesInUse.end(), "Chart name has to be unique", "Difficulty Name", - &difficulty + &difficulty_name ); } ImGui::InputInt("Level",&level); ImGui::Separator(); - if (difficulty.empty() or (difNamesInUse.find(difficulty) != difNamesInUse.end())) { + 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"); @@ -722,12 +731,14 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) { } else { if (ImGui::Button("Apply##New Chart")) { try { - editorState.fumen.Charts.erase(editorState.selectedChart->get().dif_name); - editorState.selectedChart->get().dif_name = this->difficulty; - editorState.selectedChart->get().level = this->level; - if (not (editorState.fumen.Charts.emplace(this->difficulty,editorState.selectedChart.value())).second) { + 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; + 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 { + editorState.chart.emplace(editorState.fumen.Charts.at(modified_chart.dif_name)); shouldRefreshValues = true; } } catch (const std::exception& e) { @@ -739,3 +750,7 @@ void ESHelper::ChartPropertiesDialog::display(EditorState &editorState) { ImGui::End(); } + +EditorState::Chart_with_History::Chart_with_History(Chart &c) : ref(c) { + history.push(std::make_shared(c)); +} diff --git a/EditorState.h b/EditorState.h index 2d2ce81..12ecbd5 100644 --- a/EditorState.h +++ b/EditorState.h @@ -11,15 +11,25 @@ #include "Fumen.h" #include "Marker.h" #include "Widgets.h" +#include "History.h" +#include "HistoryActions.h" + +class ActionWithMessage; +class OpenChart; class EditorState { - public: + struct Chart_with_History { + explicit Chart_with_History(Chart& c); + Chart& ref; + History> history; + }; + explicit EditorState(Fumen& fumen); Fumen fumen; - std::optional> selectedChart; + std::optional chart; Widgets::Playfield playfield; int snap = 1; @@ -43,7 +53,7 @@ public: float getTicks () {return getTicksAt(playbackPosition.asSeconds());}; float getTicksAt (float seconds) {return getBeatsAt(seconds) * getResolution();} float getSecondsAt (int tick) {return (60.f * tick)/(fumen.BPM * getResolution()) - fumen.offset;}; - int getResolution () {return selectedChart ? selectedChart->get().getResolution() : 240;}; + int getResolution () {return chart ? chart->ref.getResolution() : 240;}; int getSnapStep () {return getResolution() / snap;}; float ticksToSeconds (int ticks) {return (60.f * ticks)/(fumen.BPM * getResolution());}; @@ -61,6 +71,7 @@ public: bool showChartList; bool showNewChartDialog; bool showChartProperties; + bool showHistory; void displayPlayfield(Marker& marker, MarkerEndingState markerEndingState); void displayProperties(); @@ -107,7 +118,7 @@ namespace ESHelper { private: int level; - std::string difficulty; + std::string difficulty_name; std::string comboPreview; std::set difNamesInUse; bool showCustomDifName = false; diff --git a/History.h b/History.h index 45ffc43..57c22f9 100644 --- a/History.h +++ b/History.h @@ -8,43 +8,87 @@ #include #include +/* + * History implemented this way : + * + * last action -> * <- back of next_actions + * * + * * + * * <- front of next_actions + * + * given state of stuff -> x + * + * cause of current state -> * <- front of previous_actions + * * + * * + * first action ever done -> * <- back of previous_actions + * + * + */ template class History { public: + /* + * we cannot undo the very first action, which in my case corresponds to opening a chart + */ std::optional get_previous() { - if (previous_states.empty()) { + if (previous_actions.size() == 1) { return {}; } else { - T value = previous_states.back(); - previous_states.pop_back(); - next_states.push_back(value); - return value; + T elt = previous_actions.front(); + next_actions.push_front(elt); + previous_actions.pop_front(); + return elt; } } std::optional get_next() { - if (next_states.empty()) { + if (next_actions.empty()) { return {}; } else { - T value = next_states.back(); - next_states.pop_back(); - previous_states.push_back(value); - return value; + T elt = next_actions.front(); + previous_actions.push_front(elt); + next_actions.pop_front(); + return elt; } } void push(const T& elt) { - previous_states.push_back(elt); - if (not next_states.empty()) { - next_states.clear(); + previous_actions.push_front(elt); + if (not next_actions.empty()) { + next_actions.clear(); } } + void display(std::function printer) { + if (ImGui::Begin("History")) { + ImGui::Indent(); + for (auto it = next_actions.crbegin(); it != next_actions.crend(); ++it) { + ImGui::TextUnformatted(printer(*it).c_str()); + } + ImGui::Unindent(); + if (previous_actions.empty()) { + ImGui::Bullet(); ImGui::TextDisabled("(empty)"); + } else { + auto it = previous_actions.cbegin(); + ImGui::Bullet(); ImGui::TextUnformatted(printer(*it).c_str()); + ImGui::Indent(); + ++it; + while (it != previous_actions.cend()) { + ImGui::TextUnformatted(printer(*it).c_str()); + ++it; + } + ImGui::Unindent(); + } + } + ImGui::End(); + } + private: - std::deque previous_states; - std::deque next_states; + std::deque previous_actions; + std::deque next_actions; }; diff --git a/HistoryActions.cpp b/HistoryActions.cpp new file mode 100644 index 0000000..56529b8 --- /dev/null +++ b/HistoryActions.cpp @@ -0,0 +1,66 @@ +// +// Created by Syméon on 02/03/2019. +// + +#include +#include "HistoryActions.h" +#include "EditorState.h" + +const std::string &ActionWithMessage::getMessage() const { + return message; +} + +OpenChart::OpenChart(Chart c) : notes(c.Notes) { + std::stringstream ss; + ss << "Opened Chart " << c.dif_name << " (Level " << c.level << ")"; + message = ss.str(); +} + +void OpenChart::doAction(EditorState &ed) { + ed.chart->ref.Notes = notes; +} + +ToggledNotes::ToggledNotes(std::set n, bool have_been_added) : notes(n), have_been_added(have_been_added) { + std::stringstream ss; + if (have_been_added) { + ss << "Added Note"; + } else { + ss << "Removed Note"; + } + if (n.size() > 1) { + ss << "s"; + } + message = ss.str(); +} + +void ToggledNotes::doAction(EditorState &ed) { + if (have_been_added) { + for (auto note : notes) { + if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) { + ed.chart->ref.Notes.insert(note); + } + } + } else { + for (auto note : notes) { + if (ed.chart->ref.Notes.find(note) != ed.chart->ref.Notes.end()) { + ed.chart->ref.Notes.erase(note); + } + } + } +} + +void ToggledNotes::undoAction(EditorState &ed) { + if (not have_been_added) { + for (auto note : notes) { + if (ed.chart->ref.Notes.find(note) == ed.chart->ref.Notes.end()) { + ed.chart->ref.Notes.insert(note); + } + } + } else { + for (auto note : notes) { + if (ed.chart->ref.Notes.find(note) != ed.chart->ref.Notes.end()) { + ed.chart->ref.Notes.erase(note); + } + } + } +} diff --git a/HistoryActions.h b/HistoryActions.h new file mode 100644 index 0000000..a98f83c --- /dev/null +++ b/HistoryActions.h @@ -0,0 +1,65 @@ +#include + +// +// Created by Syméon on 02/03/2019. +// + +#ifndef FEIS_HISTORYSTATE_H +#define FEIS_HISTORYSTATE_H + + +#include +#include +#include +#include "Chart.h" + +class EditorState; + +class ActionWithMessage { +public: + explicit ActionWithMessage(std::string message = "") : message(std::move(message)) {}; + + const std::string &getMessage() const; + virtual void doAction(EditorState &ed) {}; + virtual void undoAction(EditorState &ed) {}; + + virtual ~ActionWithMessage() = default; + +protected: + std::string message; +}; + +/* + * A history action replacing every note, typically the first state in history + */ +class OpenChart : public ActionWithMessage { +public: + explicit OpenChart(Chart c); + + void doAction(EditorState &ed) override; + +protected: + std::set notes; +}; + +/* + * Some notes have been toggled, either on or off depending on have_been_added + */ +class ToggledNotes : public ActionWithMessage { +public: + ToggledNotes(std::set notes, bool have_been_added); + + void doAction(EditorState &ed) override; + void undoAction(EditorState &ed) override; + +protected: + bool have_been_added; + std::set notes; +}; + +auto print_history_message = [](std::shared_ptr hs) -> std::string { + return (*hs).getMessage(); +}; + + +#endif //FEIS_HISTORYSTATE_H diff --git a/include/imgui/imgui_stdlib.cpp b/include/imgui/imgui_stdlib.cpp new file mode 100644 index 0000000..790d8d8 --- /dev/null +++ b/include/imgui/imgui_stdlib.cpp @@ -0,0 +1,65 @@ +// imgui_stdlib.cpp +// Wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Compatibility: +// - std::string support is only guaranteed to work from C++11. +// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +#include "imgui.h" +#include "imgui_stdlib.h" + +struct InputTextCallback_UserData +{ + std::string* Str; + ImGuiInputTextCallback ChainCallback; + void* ChainCallbackUserData; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) +{ + InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) + { + // Resize string callback + // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. + std::string* str = user_data->Str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } + else if (user_data->ChainCallback) + { + // Forward to user callback, if any + data->UserData = user_data->ChainCallbackUserData; + return user_data->ChainCallback(data); + } + return 0; +} + +bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); +} + +bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); +} diff --git a/include/imgui/imgui_stdlib.h b/include/imgui/imgui_stdlib.h new file mode 100644 index 0000000..200c45a --- /dev/null +++ b/include/imgui/imgui_stdlib.h @@ -0,0 +1,22 @@ +// imgui_stdlib.h +// Wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Compatibility: +// - std::string support is only guaranteed to work from C++11. +// If you try to use it pre-C++11, please share your findings (w/ info about compiler/architecture) + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +#pragma once + +#include + +namespace ImGui +{ + // ImGui::InputText() with std::string + // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity + IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL); +} diff --git a/main.cpp b/main.cpp index 311da9d..c0d28fb 100644 --- a/main.cpp +++ b/main.cpp @@ -10,7 +10,7 @@ int main(int argc, char** argv) { // TODO : Highlight des notes qui s'entrechoquent - // TODO : Undo / Redo + // TODO : scroll to modifed note when undoing/redoing // TODO : Debug Log // TODO : Bruit différent si clap simple ou chord // TODO : Density graph sur la timeline @@ -98,7 +98,7 @@ int main(int argc, char** argv) { } } } else { - if (editorState and editorState->selectedChart) { + if (editorState and editorState->chart) { float floatTicks = editorState->getTicks(); int prevTick = static_cast(floorf(floatTicks)); int step = editorState->getSnapStep(); @@ -121,7 +121,7 @@ int main(int argc, char** argv) { } } } else { - if (editorState and editorState->selectedChart) { + if (editorState and editorState->chart) { float floatTicks = editorState->getTicks(); int nextTick = static_cast(ceilf(floatTicks)); int step = editorState->getSnapStep(); @@ -131,13 +131,13 @@ int main(int argc, char** argv) { } break; case sf::Keyboard::Left: - if (editorState and editorState->selectedChart) { - editorState->snap = Toolbox::getPreviousDivisor(editorState->selectedChart->get().getResolution(),editorState->snap); + if (editorState and editorState->chart) { + editorState->snap = Toolbox::getPreviousDivisor(editorState->chart->ref.getResolution(),editorState->snap); } break; case sf::Keyboard::Right: - if (editorState and editorState->selectedChart) { - editorState->snap = Toolbox::getNextDivisor(editorState->selectedChart->get().getResolution(),editorState->snap); + if (editorState and editorState->chart) { + editorState->snap = Toolbox::getNextDivisor(editorState->chart->ref.getResolution(),editorState->snap); } break; case sf::Keyboard::F3: @@ -168,6 +168,26 @@ int main(int argc, char** argv) { ESHelper::save(*editorState); } break; + case sf::Keyboard::Y: + if (event.key.control) { + if (editorState and editorState->chart) { + auto next = editorState->chart->history.get_next(); + if (next) { + (*next)->doAction(*editorState); + } + } + } + break; + case sf::Keyboard::Z: + if (event.key.control) { + if (editorState and editorState->chart) { + auto previous = editorState->chart->history.get_previous(); + if (previous) { + (*previous)->undoAction(*editorState); + } + } + } + break; default: break; } @@ -238,11 +258,14 @@ int main(int argc, char** argv) { } } - // Dessin du fond + // Drawing if (editorState) { window.clear(sf::Color(0, 0, 0)); + if (editorState->showHistory) { + editorState->chart->history.display(print_history_message); + } if (editorState->showPlayfield) { editorState->displayPlayfield(marker,markerEndingState); } @@ -278,7 +301,7 @@ int main(int argc, char** argv) { if (c) { editorState->showNewChartDialog = false; if(editorState->fumen.Charts.try_emplace(c->dif_name,*c).second) { - editorState->selectedChart = editorState->fumen.Charts[c->dif_name]; + editorState->chart->ref = editorState->fumen.Charts[c->dif_name]; } } } else { @@ -359,7 +382,7 @@ int main(int argc, char** argv) { if (ImGui::MenuItem("Chart List")) { editorState->showChartList = true; } - if (ImGui::MenuItem("Properties##Chart",nullptr,false,editorState->selectedChart.has_value())) { + if (ImGui::MenuItem("Properties##Chart",nullptr,false,editorState->chart.has_value())) { editorState->showChartProperties = true; } ImGui::Separator(); @@ -367,9 +390,9 @@ int main(int argc, char** argv) { editorState->showNewChartDialog = true; } ImGui::Separator(); - if (ImGui::MenuItem("Delete Chart",nullptr,false,editorState->selectedChart.has_value())) { - editorState->fumen.Charts.erase(editorState->selectedChart->get().dif_name); - editorState->selectedChart.reset(); + if (ImGui::MenuItem("Delete Chart",nullptr,false,editorState->chart.has_value())) { + editorState->fumen.Charts.erase(editorState->chart->ref.dif_name); + editorState->chart.reset(); } ImGui::EndMenu(); } @@ -386,6 +409,9 @@ int main(int argc, char** argv) { if (ImGui::MenuItem("Editor Status",nullptr,editorState->showStatus)) { editorState->showStatus = not editorState->showStatus; } + if (ImGui::MenuItem("History",nullptr,editorState->showHistory)) { + editorState->showHistory = not editorState->showHistory; + } ImGui::EndMenu(); } if (ImGui::BeginMenu("Settings",editorState.has_value())) {