From 94438c7b971f887d943b7899bc6e06bc1cf539ee Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Wed, 12 Nov 2025 11:36:14 +0300 Subject: [PATCH] Added undo and redo to textbox --- src/InputManager.cpp | 76 +++++++++++++++++++++++++++++++++++++++----- src/InputManager.hpp | 8 +++++ src/UndoManager.cpp | 1 - 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 2d8e54f..88fb794 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -89,8 +89,10 @@ void InputManager::Text::set_rich_text_box_input(const std::shared_ptrprocess_text_input(*cursor, inputText, modMap); + if(textBox && !inputText.empty()) + do_textbox_operation_with_undo([&]() { + textBox->process_text_input(*cursor, inputText, modMap); + }); } void InputManager::Text::set_accepting_input() { @@ -101,6 +103,32 @@ bool InputManager::Text::get_accepting_input() { return acceptingInput; } +void InputManager::Text::add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText) { + if(textBox) { + textboxUndo.push({[textBox = textBox, cursor = cursor, prevCursor = prevCursor, prevRichText = prevRichText]() { + textBox->set_rich_text_data(prevRichText); + *cursor = prevCursor; + cursor->previousX = std::nullopt; + return true; + }, + [textBox = textBox, cursor = cursor, currentCursor = *cursor, currentRichText = textBox->get_rich_text_data()]() { + textBox->set_rich_text_data(currentRichText); + *cursor = currentCursor; + cursor->previousX = std::nullopt; + return true; + }}); + } +} + +void InputManager::Text::do_textbox_operation_with_undo(const std::function& func) { + if(textBox) { + auto prevRichText = textBox->get_rich_text_data(); + auto prevCursor = *cursor; + func(); + add_textbox_undo(prevCursor, prevRichText); + } +} + bool InputManager::get_clipboard_paste_happened() { #ifdef __EMSCRIPTEN__ return clipboardPasteEventHappened; @@ -291,12 +319,16 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { case SDLK_BACKSPACE: set_key_down(e, KEY_TEXT_BACKSPACE); if(text.textBox && key(KEY_TEXT_BACKSPACE).repeat) - text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::BACKSPACE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + text.do_textbox_operation_with_undo([&]() { + text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::BACKSPACE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + }); break; case SDLK_DELETE: set_key_down(e, KEY_TEXT_DELETE); if(text.textBox && key(KEY_TEXT_DELETE).repeat) - text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::DELETE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + text.do_textbox_operation_with_undo([&]() { + text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::DELETE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + }); break; case SDLK_HOME: set_key_down(e, KEY_TEXT_HOME); @@ -334,13 +366,17 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_CUT); if(text.textBox && key(KEY_TEXT_CUT).repeat) - set_clipboard_plain_and_richtext_pair(text.textBox->process_cut(*text.cursor)); + text.do_textbox_operation_with_undo([&]() { + set_clipboard_plain_and_richtext_pair(text.textBox->process_cut(*text.cursor)); + }); break; case SDLK_V: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_PASTE); if(text.textBox && key(KEY_TEXT_PASTE).repeat) - process_text_paste(); + text.do_textbox_operation_with_undo([&]() { + process_text_paste(); + }); break; case SDLK_A: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) @@ -348,16 +384,32 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { if(text.textBox && key(KEY_TEXT_SELECTALL).repeat) text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::SELECT_ALL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); break; + case SDLK_Z: + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) + set_key_down(e, KEY_TEXT_UNDO); + if(text.textBox && key(KEY_TEXT_UNDO).repeat) + text.textboxUndo.undo(); + break; + case SDLK_R: + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) + set_key_down(e, KEY_TEXT_REDO); + if(text.textBox && key(KEY_TEXT_REDO).repeat) + text.textboxUndo.redo(); + break; case SDLK_RETURN: { set_key_down(e, KEY_TEXT_ENTER); if(text.textBox && key(KEY_TEXT_ENTER).repeat) - text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + text.do_textbox_operation_with_undo([&]() { + text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + }); break; } case SDLK_TAB: { set_key_down(e, KEY_TEXT_TAB); if(text.textBox && key(KEY_TEXT_TAB).repeat) - text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::TAB, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + text.do_textbox_operation_with_undo([&]() { + text.textBox->process_key_input(*text.cursor, RichText::TextBox::InputKey::TAB, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, text.modMap); + }); break; } case SDLK_ESCAPE: @@ -446,6 +498,12 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { case SDLK_A: set_key_up(e, KEY_TEXT_SELECTALL); break; + case SDLK_Z: + set_key_up(e, KEY_TEXT_UNDO); + break; + case SDLK_R: + set_key_up(e, KEY_TEXT_REDO); + break; case SDLK_RETURN: set_key_up(e, KEY_TEXT_ENTER); break; @@ -483,6 +541,8 @@ void InputManager::frame_reset(const Vector2i& windowSize) { mouse.middleClicks = 0; text.acceptingInput = text.acceptingInputNew; text.acceptingInputNew = false; + if(text.cursor != text.newCursor || text.textBox != text.newTextBox) + text.textboxUndo.clear(); text.cursor = text.newCursor; text.textBox = text.newTextBox; text.modMap = text.newModMap; diff --git a/src/InputManager.hpp b/src/InputManager.hpp index fe1292b..cb62c4f 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include "UndoManager.hpp" #include #include @@ -92,6 +93,9 @@ struct InputManager { void add_text_to_textbox(const std::string& inputText); private: + void add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText); + void do_textbox_operation_with_undo(const std::function& func); + std::shared_ptr textBox; std::shared_ptr cursor; std::optional modMap; @@ -100,6 +104,8 @@ struct InputManager { std::shared_ptr newCursor; std::optional newModMap; + UndoManager textboxUndo; + bool acceptingInput = false; bool acceptingInputNew = false; @@ -172,6 +178,8 @@ struct InputManager { KEY_TEXT_CTRL, KEY_TEXT_META, KEY_TEXT_SELECTALL, + KEY_TEXT_UNDO, + KEY_TEXT_REDO, KEY_GENERIC_ESCAPE, KEY_GENERIC_LSHIFT, KEY_GENERIC_LALT, diff --git a/src/UndoManager.cpp b/src/UndoManager.cpp index a29b00e..4528354 100644 --- a/src/UndoManager.cpp +++ b/src/UndoManager.cpp @@ -51,7 +51,6 @@ void UndoManager::redo() { } void UndoManager::clear() { - std::cout << "[UndoManager::clear] Clearing undo queue" << std::endl; redoQueue.clear(); undoQueue.clear(); }