From 262bf23bcee1353925a3b17db8a2b9a0ae762cad Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Fri, 3 Apr 2026 23:02:33 +0300 Subject: [PATCH] Moved textbox input code out of InputManager --- CMakeLists.txt | 1 + .../Tools/EditTools/TextBoxEditTool.cpp | 54 ++-- src/GUIHolder.cpp | 8 + src/GUIHolder.hpp | 3 + src/GUIStuff/Elements/Element.cpp | 2 + src/GUIStuff/Elements/Element.hpp | 2 + src/GUIStuff/Elements/TextBox.decl.hpp | 19 +- src/GUIStuff/Elements/TextBox.impl.hpp | 77 +++-- src/GUIStuff/GUIManager.cpp | 14 + src/GUIStuff/GUIManager.hpp | 2 + src/InputManager.cpp | 285 ++++-------------- src/InputManager.hpp | 42 +-- src/MainProgram.cpp | 8 +- src/MainProgram.hpp | 4 +- src/RichText/TextBox.cpp | 6 - src/RichText/TextBox.hpp | 1 - src/RichTextUserInput.cpp | 123 ++++++++ src/RichTextUserInput.hpp | 19 ++ src/main.cpp | 2 +- 19 files changed, 343 insertions(+), 329 deletions(-) create mode 100644 src/RichTextUserInput.cpp create mode 100644 src/RichTextUserInput.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5f9ecf1..b0745a5 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ set(sources "src/main.cpp" "src/WorldUndoManager.cpp" "src/GUIHolder.cpp" "src/GlobalConfig.cpp" + "src/RichTextUserInput.cpp" "src/Bookmarks/BookmarkListItem.cpp" "src/Bookmarks/BookmarkManager.cpp" "src/ResourceDisplay/ResourceDisplay.cpp" diff --git a/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp b/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp index 6903c90..7bcfd10 100644 --- a/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp +++ b/src/DrawingProgram/Tools/EditTools/TextBoxEditTool.cpp @@ -207,22 +207,22 @@ void TextBoxEditTool::right_click_popup_gui(CanvasComponentContainer::ObjInfo* c gui.element("Text popup menu", popupPos, [&, popupPos] { text_label_light(gui, "Text menu"); InputManager& input = drawP.world.main.input; - drawP.popup_menu_action_button("Paste", "Paste", [&] { - input.call_text_paste(true); - }); - drawP.popup_menu_action_button("Paste without formatting", "Paste without formatting", [&] { - input.call_text_paste(false); - }); - if(a.cursor->selectionBeginPos != a.cursor->selectionEndPos) { - drawP.popup_menu_action_button("Copy", "Copy", [&] { - input.set_clipboard_plain_and_richtext_pair(a.textBox->process_copy(*a.cursor)); - }); - drawP.popup_menu_action_button("Cut", "Cut", [&] { - input.text.do_textbox_operation_with_undo([&]() { - input.set_clipboard_plain_and_richtext_pair(a.textBox->process_cut(*a.cursor)); - }); - }); - } + //drawP.popup_menu_action_button("Paste", "Paste", [&] { + // input.call_text_paste(true); + //}); + //drawP.popup_menu_action_button("Paste without formatting", "Paste without formatting", [&] { + // input.call_text_paste(false); + //}); + //if(a.cursor->selectionBeginPos != a.cursor->selectionEndPos) { + // drawP.popup_menu_action_button("Copy", "Copy", [&] { + // input.set_clipboard_plain_and_richtext_pair(a.textBox->process_copy(*a.cursor)); + // }); + // drawP.popup_menu_action_button("Cut", "Cut", [&] { + // input.text.do_textbox_operation_with_undo([&]() { + // input.set_clipboard_plain_and_richtext_pair(a.textBox->process_cut(*a.cursor)); + // }); + // }); + //} }); } @@ -242,7 +242,7 @@ void TextBoxEditTool::release_undo_data(const std::string& undoName) { if(it != undoHeldData.end()) { std::pair& undoData = it->second; InputManager& input = drawP.world.main.input; - input.text.add_textbox_undo(undoData.first, undoData.second); + //input.text.add_textbox_undo(undoData.first, undoData.second); undoHeldData.erase(it); } } @@ -252,13 +252,13 @@ void TextBoxEditTool::add_undo_if_selecting_area(TextBoxCanvasComponent& a, cons func(); else { InputManager& input = drawP.world.main.input; - input.text.do_textbox_operation_with_undo(func); + //input.text.do_textbox_operation_with_undo(func); } } void TextBoxEditTool::add_undo(const std::function& func) { InputManager& input = drawP.world.main.input; - input.text.do_textbox_operation_with_undo(func); + //input.text.do_textbox_operation_with_undo(func); } uint8_t TextBoxEditTool::get_new_decoration_value() { @@ -299,7 +299,7 @@ void TextBoxEditTool::commit_edit_updates(CanvasComponentContainer::ObjInfo* com auto& a = static_cast(comp->obj->get_comp()); a.d.editing = false; comp->obj->commit_update(drawP); - drawP.world.main.input.remove_rich_text_box_input(a.textBox); + //drawP.world.main.input.remove_rich_text_box_input(a.textBox); } TextBoxEditTool::TextBoxEditToolAllData TextBoxEditTool::get_all_data(const TextBoxCanvasComponent& a) { @@ -330,13 +330,13 @@ void TextBoxEditTool::edit_start(EditTool& editTool, CanvasComponentContainer::O comp->obj->commit_update(drawP); - drawP.world.main.input.set_rich_text_box_input_back(a.textBox, a.cursor, true, nullptr, { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = true, - .multiline = true, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT - }, currentModsPtr); + //drawP.world.main.input.set_rich_text_box_input_back(a.textBox, a.cursor, true, nullptr, { + // .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, + // .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + // .autocorrect = true, + // .multiline = true, + // .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT + //}, currentModsPtr); } bool TextBoxEditTool::edit_update(CanvasComponentContainer::ObjInfo* comp) { diff --git a/src/GUIHolder.cpp b/src/GUIHolder.cpp index aa35678..35a485b 100644 --- a/src/GUIHolder.cpp +++ b/src/GUIHolder.cpp @@ -119,6 +119,14 @@ void GUIHolder::draw(SkCanvas* canvas, bool skiaAA) { canvas->restore(); } +void GUIHolder::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { + gui.input_text_key_callback(key); +} + +void GUIHolder::input_text_callback(const std::string& str) { + gui.input_text_callback(str); +} + void GUIHolder::input_key_callback(const InputManager::KeyCallbackArgs& key) { gui.input_key_callback(key); } diff --git a/src/GUIHolder.hpp b/src/GUIHolder.hpp index e82fc32..69b33ed 100644 --- a/src/GUIHolder.hpp +++ b/src/GUIHolder.hpp @@ -17,6 +17,9 @@ class GUIHolder { void update(); void draw(SkCanvas* canvas, bool skiaAA); + + void input_text_key_callback(const InputManager::KeyCallbackArgs& key); + void input_text_callback(const std::string& str); void input_key_callback(const InputManager::KeyCallbackArgs& key); void input_text_key_callback(); void input_text_input_callback(); diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index 044d5ff..9bdbeff 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -6,6 +6,8 @@ namespace GUIStuff { void Element::tick_update() {} + void Element::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { } + void Element::input_text_callback(const std::string& str) { } void Element::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { } void Element::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { } void Element::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel) { } diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index 64364bd..311b63b 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -25,6 +25,8 @@ namespace GUIStuff { virtual void tick_update(); // Return true when mouse obstructs what's behind it + virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key); + virtual void input_text_callback(const std::string& str); virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button); virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion); virtual void input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel); diff --git a/src/GUIStuff/Elements/TextBox.decl.hpp b/src/GUIStuff/Elements/TextBox.decl.hpp index 6902a7e..03c125b 100644 --- a/src/GUIStuff/Elements/TextBox.decl.hpp +++ b/src/GUIStuff/Elements/TextBox.decl.hpp @@ -5,6 +5,7 @@ #include #include "../../FontData.hpp" #include "../../InputManager.hpp" +#include "../../RichTextUserInput.hpp" namespace GUIStuff { @@ -20,27 +21,39 @@ template struct TextBoxData { std::function onEnter; }; + +struct TextEditData { + TextEditData(unsigned initTextboxInputID, const std::shared_ptr& initTextBox, const std::shared_ptr& initCursor, const std::function& onTextEdit): + textboxInputID(initTextboxInputID), + userInput(initTextBox, initCursor, nullptr, onTextEdit) + {} + unsigned textboxInputID; + RichTextUserInput userInput; +}; + template class TextBox : public Element { public: TextBox(GUIManager& gui); void layout(const Clay_ElementId& id, const TextBoxData& userInfo); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key) override; + virtual void input_text_callback(const std::string& str) override; virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; - virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; ~TextBox(); private: void init_textbox(UpdateInputData& io); void reset_textbox_text(); bool update_data(); + bool is_selected() const; + + std::optional edit; std::optional oldData; - bool isSelected = false; TextBoxData userInfo; std::shared_ptr textbox; std::shared_ptr cur; - std::shared_ptr> rect; }; } diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp index de3b9fd..5de85c2 100644 --- a/src/GUIStuff/Elements/TextBox.impl.hpp +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -28,7 +28,7 @@ template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputDa SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()); canvas->drawRect(r, SkPaint(io.theme->backColor2)); - SkPaint outline(isSelected ? io.theme->fillColor1 : io.theme->backColor2); + SkPaint outline(is_selected() ? io.theme->fillColor1 : io.theme->backColor2); outline.setStroke(true); outline.setStrokeWidth(2.0f); outline.setAntiAlias(skiaAA); @@ -40,7 +40,7 @@ template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputDa yOffset -= textbox->get_height() * 0.5f; RichText::TextBox::PaintOpts paintOpts; - if(isSelected) { + if(is_selected()) { paintOpts.cursor = *cur; SkRect cursorRect = textbox->get_cursor_rect(cur->pos); canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset); @@ -61,45 +61,65 @@ template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputDa canvas->restore(); } +template bool TextBox::is_selected() const { + return edit.has_value(); +} + +template void TextBox::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { + if(is_selected()) { + if(key.key == InputManager::KEY_GENERIC_ENTER && key.down) { + bool success = update_data(); + gui.set_post_callback_func_high_priority([&, success] { + if(success && userInfo.onEdit) userInfo.onEdit(); + if(userInfo.onEnter) userInfo.onEnter(); + reset_textbox_text(); + }); + } + else + edit.value().userInput.input_key_callback(*gui.io.input, key); + } +} + +template void TextBox::input_text_callback(const std::string& str) { + if(is_selected()) + edit.value().userInput.add_text_to_textbox(str); +} + template void TextBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { auto& io = gui.io; if(button.button == InputManager::MouseButton::LEFT && boundingBox.has_value()) { if(button.down) { if(mouseHovering) { - *rect = {(boundingBox.value().min + io.windowPos) * io.guiScaleMultiplier, (boundingBox.value().max + io.windowPos) * io.guiScaleMultiplier}; - gui.io.input->set_rich_text_box_input_front(textbox, cur, false, rect, userInfo.textInputProps, nullptr); - textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, true, gui.io.input->key(InputManager::KEY_TEXT_SHIFT).held); - isSelected = true; + SCollision::AABB rect = {(boundingBox.value().min + io.windowPos) * io.guiScaleMultiplier, (boundingBox.value().max + io.windowPos) * io.guiScaleMultiplier}; + unsigned id = gui.io.input->set_text_box_front(rect, userInfo.textInputProps); + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, true, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); + edit = TextEditData(id, textbox, cur, [&] { + if(userInfo.immutable) + cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, userInfo.toStr(*userInfo.data)); + else { + if(update_data() && userInfo.onEdit) + gui.set_post_callback_func_high_priority(userInfo.onEdit); + } + }); } - else if(isSelected) { - gui.io.input->remove_rich_text_box_input(textbox); + else if(is_selected()) { + gui.io.input->remove_text_box(edit.value().textboxInputID); if(update_data()) gui.set_post_callback_func_high_priority(userInfo.onEdit); reset_textbox_text(); - isSelected = false; + edit = std::nullopt; } } else { - textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, false, gui.io.input->key(InputManager::KEY_TEXT_SHIFT).held); + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, false, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); } } } template void TextBox::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { if(boundingBox.has_value()) - textbox->process_mouse_left_button(*cur, motion.pos - boundingBox.value().min, 0, gui.io.input->mouse.leftDown, gui.io.input->key(InputManager::KEY_TEXT_SHIFT).held); -} - -template void TextBox::input_key_callback(const InputManager::KeyCallbackArgs& key) { - if(key.key == InputManager::KEY_TEXT_ENTER && key.down && isSelected) { - bool success = update_data(); - gui.set_post_callback_func_high_priority([&, success] { - if(success && userInfo.onEdit) userInfo.onEdit(); - if(userInfo.onEnter) userInfo.onEnter(); - reset_textbox_text(); - }); - } + textbox->process_mouse_left_button(*cur, motion.pos - boundingBox.value().min, 0, gui.io.input->mouse.leftDown, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); } template void TextBox::init_textbox(UpdateInputData& io) { @@ -109,17 +129,7 @@ template void TextBox::init_textbox(UpdateInputData& io) { textbox->set_font_data(io.fonts); textbox->set_allow_newlines(false); cur = std::make_shared(); - rect = std::make_shared>(); } - - textbox->onUserTextEdit = [&] { - if(userInfo.immutable) - cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, userInfo.toStr(*userInfo.data)); - else { - if(update_data() && userInfo.onEdit) - gui.set_post_callback_func_high_priority(userInfo.onEdit); - } - }; } template void TextBox::reset_textbox_text() { @@ -138,7 +148,8 @@ template bool TextBox::update_data() { } template TextBox::~TextBox() { - gui.io.input->remove_rich_text_box_input(textbox); + if(is_selected()) + gui.io.input->remove_text_box(edit.value().textboxInputID); } } diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 05dd415..4daa774 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -344,6 +344,20 @@ bool GUIManager::cursor_obstructed() const { return cursorObstructed; } +void GUIManager::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { + for(ElementContainer* e : orderedElements) + e->elem->input_text_key_callback(key); + run_post_callback_func(); + layout_if_necessary(); +} + +void GUIManager::input_text_callback(const std::string& str) { + for(ElementContainer* e : orderedElements) + e->elem->input_text_callback(str); + run_post_callback_func(); + layout_if_necessary(); +} + void GUIManager::input_key_callback(const InputManager::KeyCallbackArgs& key) { for(ElementContainer* e : orderedElements) e->elem->input_key_callback(key); diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index d7223f2..4631f9a 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -85,6 +85,8 @@ class GUIManager { DefaultStringArena strArena; + void input_text_key_callback(const InputManager::KeyCallbackArgs& key); + void input_text_callback(const std::string& str); void input_key_callback(const InputManager::KeyCallbackArgs& key); void input_mouse_button_callback(InputManager::MouseButtonCallbackArgs button); void input_mouse_motion_callback(InputManager::MouseMotionCallbackArgs motion); diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 833263a..b63407e 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -30,13 +30,6 @@ extern "C" { #include "MainProgram.hpp" -template std::optional shared_ptr_to_opt(const std::shared_ptr o) { - if(o) - return *o; - else - return std::nullopt; -} - InputManager::InputManager(MainProgram& initMain): main(initMain) { frame_reset({0, 0}); @@ -95,75 +88,40 @@ InputManager::InputManager(MainProgram& initMain): keyAssignments = defaultKeyAssignments; } -void InputManager::set_rich_text_box_input_front(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::shared_ptr>& textboxRect, const TextInputProperties& inputProps, const std::shared_ptr& nModMap) { +unsigned InputManager::Text::get_id() { + static unsigned nextID = 0; + while(std::find_if(textBoxes.begin(), textBoxes.end(), [&](const TextBoxInfo& info) { return info.id == nextID; }) != textBoxes.end()) + ++nextID; + return nextID; +} + +unsigned InputManager::set_text_box_front(const std::optional>& textboxRect, const TextInputProperties& inputProps) { Text::TextBoxInfo textBoxInfo; - textBoxInfo.textBox = nTextBox; - textBoxInfo.cursor = nCursor; - textBoxInfo.modMap = nModMap; - textBoxInfo.isRichTextBox = isRichTextBox; textBoxInfo.rect = textboxRect; textBoxInfo.textInputProperties = inputProps; + textBoxInfo.id = text.get_id(); text.textBoxes.emplace_front(textBoxInfo); text.update_accepting_input(main.window.sdlWindow); + return textBoxInfo.id; } -void InputManager::set_rich_text_box_input_back(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::shared_ptr>& textboxRect, const TextInputProperties& inputProps, const std::shared_ptr& nModMap) { +unsigned InputManager::set_text_box_back(const std::optional>& textboxRect, const TextInputProperties& inputProps) { Text::TextBoxInfo textBoxInfo; - textBoxInfo.textBox = nTextBox; - textBoxInfo.cursor = nCursor; - textBoxInfo.modMap = nModMap; - textBoxInfo.isRichTextBox = isRichTextBox; textBoxInfo.rect = textboxRect; textBoxInfo.textInputProperties = inputProps; + textBoxInfo.id = text.get_id(); text.textBoxes.emplace_back(textBoxInfo); text.update_accepting_input(main.window.sdlWindow); + return textBoxInfo.id; } -void InputManager::remove_rich_text_box_input(const std::shared_ptr& nTextBox) { - std::erase_if(text.textBoxes, [&nTextBox](Text::TextBoxInfo& info) { - return (info.textBox == nTextBox); +void InputManager::remove_text_box(unsigned id) { + std::erase_if(text.textBoxes, [id](Text::TextBoxInfo& info) { + return (info.id == id); }); text.update_accepting_input(main.window.sdlWindow); } -void InputManager::add_text_to_textbox(const std::string& inputText) { - if(!text.textBoxes.empty() && !inputText.empty()) { - auto& frontTextBox = text.textBoxes.front(); - text.do_textbox_operation_with_undo([&]() { - frontTextBox.textBox->process_text_input(*frontTextBox.cursor, inputText, shared_ptr_to_opt(frontTextBox.modMap)); - }); - main.input_text_input_callback(); - } -} - -void InputManager::Text::add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText) { - if(!textBoxes.empty()) { - auto& frontTextBox = textBoxes.front(); - frontTextBox.textboxUndo.push({[textBox = frontTextBox.textBox, cursor = frontTextBox.cursor, prevCursor = prevCursor, prevRichText = prevRichText]() { - textBox->set_rich_text_data_for_undo_redo(prevRichText); - cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(prevCursor.selectionEndPos, prevCursor.selectionBeginPos); - cursor->previousX = std::nullopt; - return true; - }, - [textBox = frontTextBox.textBox, cursor = frontTextBox.cursor, currentCursor = *frontTextBox.cursor, currentRichText = frontTextBox.textBox->get_rich_text_data()]() { - textBox->set_rich_text_data_for_undo_redo(currentRichText); - cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(currentCursor.selectionEndPos, currentCursor.selectionBeginPos); - cursor->previousX = std::nullopt; - return true; - }}); - } -} - -void InputManager::Text::do_textbox_operation_with_undo(const std::function& func) { - if(!textBoxes.empty()) { - auto& frontTextBox = textBoxes.front(); - auto prevRichText = frontTextBox.textBox->get_rich_text_data(); - auto prevCursor = *frontTextBox.cursor; - func(); - add_textbox_undo(prevCursor, prevRichText); - } -} - bool InputManager::Text::is_accepting_input() { return !textBoxes.empty(); } @@ -369,10 +327,18 @@ const InputManager::KeyData& InputManager::key(KeyCode kCode) { void InputManager::set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode) { auto& k = keys[kCode]; k.held = true; + if(text.is_accepting_input()) + main.input_text_key_callback(KeyCallbackArgs{.key = kCode, .down = e.down, .repeat = e.repeat}); + else + main.input_key_callback(KeyCallbackArgs{.key = kCode, .down = e.down, .repeat = e.repeat}); } void InputManager::set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode) { keys[kCode].held = false; + if(text.is_accepting_input()) + main.input_text_key_callback(KeyCallbackArgs{.key = kCode, .down = e.down, .repeat = e.repeat}); + // Send key up even if text input is happening + main.input_key_callback(KeyCallbackArgs{.key = kCode, .down = e.down, .repeat = e.repeat}); } uint32_t InputManager::make_generic_key_mod(SDL_Keymod m) { @@ -388,6 +354,10 @@ uint32_t InputManager::make_generic_key_mod(SDL_Keymod m) { return toRet; } +void InputManager::backend_input_text_event(const std::string& str) { + main.input_text_callback(str); +} + void InputManager::backend_drop_file_event(const SDL_DropEvent& e) { Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density}; mouse.set_pos(mousePos); @@ -851,184 +821,89 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { return; } - // e.key != SDLK_LMETA && e.key != SDLK_RMETA && - - std::shared_ptr textBox = text.textBoxes.empty() ? nullptr : text.textBoxes.front().textBox; - std::shared_ptr cursor = text.textBoxes.empty() ? nullptr : text.textBoxes.front().cursor; - std::optional modMap = text.textBoxes.empty() ? std::nullopt : shared_ptr_to_opt(text.textBoxes.front().modMap); - switch(kPress) { case SDLK_UP: - set_key_down(e, KEY_TEXT_UP); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::UP, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); + set_key_down(e, KEY_GENERIC_UP); break; case SDLK_DOWN: - set_key_down(e, KEY_TEXT_DOWN); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DOWN, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); + set_key_down(e, KEY_GENERIC_DOWN); break; case SDLK_LEFT: - set_key_down(e, KEY_TEXT_LEFT); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::LEFT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); + set_key_down(e, KEY_GENERIC_LEFT); break; case SDLK_RIGHT: - set_key_down(e, KEY_TEXT_RIGHT); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::RIGHT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); + set_key_down(e, KEY_GENERIC_RIGHT); break; case SDLK_BACKSPACE: set_key_down(e, KEY_TEXT_BACKSPACE); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::BACKSPACE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); - }); break; case SDLK_DELETE: set_key_down(e, KEY_TEXT_DELETE); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DEL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); - }); break; case SDLK_HOME: set_key_down(e, KEY_TEXT_HOME); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::HOME, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); break; case SDLK_END: set_key_down(e, KEY_TEXT_END); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::END, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); break; case SDLK_LSHIFT: - set_key_down(e, KEY_TEXT_SHIFT); set_key_down(e, KEY_GENERIC_LSHIFT); break; case SDLK_LALT: set_key_down(e, KEY_GENERIC_LALT); break; case SDLK_LCTRL: - set_key_down(e, KEY_TEXT_CTRL); set_key_down(e, KEY_GENERIC_LCTRL); break; case SDLK_LMETA: - set_key_down(e, KEY_TEXT_META); set_key_down(e, KEY_GENERIC_LMETA); break; case SDLK_C: // Use either Ctrl or Meta (command for Mac) keys. We do either instead of checking, since checking can get complicated on Emscripten - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_COPY); - if(textBox) - set_clipboard_plain_and_richtext_pair(textBox->process_copy(*cursor)); - } break; case SDLK_X: - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_CUT); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - set_clipboard_plain_and_richtext_pair(textBox->process_cut(*cursor)); - }); - } break; case SDLK_V: - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_PASTE); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - call_text_paste(true); - }); - } break; case SDLK_A: - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_SELECTALL); - if(textBox) - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::SELECT_ALL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); - } break; case SDLK_Z: - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_UNDO); - if(textBox) - text.textBoxes.front().textboxUndo.undo(); - } break; case SDLK_R: - if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) { + if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) set_key_down(e, KEY_TEXT_REDO); - if(textBox) - text.textBoxes.front().textboxUndo.redo(); - } break; - case SDLK_RETURN: { - set_key_down(e, KEY_TEXT_ENTER); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); - }); - main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ENTER, .down = e.down, .repeat = e.repeat}); + case SDLK_RETURN: + set_key_down(e, KEY_GENERIC_ENTER); break; - } - case SDLK_TAB: { + case SDLK_TAB: set_key_down(e, KEY_TEXT_TAB); - if(textBox) - text.do_textbox_operation_with_undo([&]() { - textBox->process_key_input(*cursor, RichText::TextBox::InputKey::TAB, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap); - }); break; - } case SDLK_ESCAPE: - set_key_down(e, KEY_TEXT_ESCAPE); - main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ESCAPE, .down = e.down, .repeat = e.repeat}); + set_key_down(e, KEY_GENERIC_ESCAPE); break; default: break; } - if(text.is_accepting_input()) { - main.input_text_key_callback(); - return; - } - auto f = keyAssignments.find({make_generic_key_mod(kMod), kPress}); - if(f != keyAssignments.end()) { + if(f != keyAssignments.end()) set_key_down(e, f->second); - main.input_key_callback(KeyCallbackArgs{.key = f->second, .down = e.down, .repeat = e.repeat}); - } - switch(kPress) { - case SDLK_UP: - set_key_down(e, KEY_GENERIC_UP); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_UP, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_DOWN: - set_key_down(e, KEY_GENERIC_DOWN); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_DOWN, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_LEFT: - set_key_down(e, KEY_GENERIC_LEFT); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_LEFT, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_RIGHT: - set_key_down(e, KEY_GENERIC_RIGHT); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_RIGHT, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_RETURN: - set_key_down(e, KEY_GENERIC_ENTER); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ENTER, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_ESCAPE: - set_key_down(e, KEY_GENERIC_ESCAPE); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ESCAPE, .down = e.down, .repeat = e.repeat}); - break; - } } -void InputManager::call_text_paste(bool isRichTextPaste) { - text.isNextPasteRich = isRichTextPaste; +void InputManager::call_text_paste(const std::function& pasteCallbackFunc) { + // Workaround for not being able to copy richtext to system clipboard, this should at least work within the application itself + pasteCallback = pasteCallbackFunc; #ifdef __EMSCRIPTEN__ emscripten_browser_clipboard::paste_async([](std::string&& pasteData, void* callbackData){ InputManager* inMan = (InputManager*)callbackData; @@ -1040,17 +915,12 @@ void InputManager::call_text_paste(bool isRichTextPaste) { #endif } -void InputManager::process_text_paste(const std::string& plainClipboardStr) { - if(!text.textBoxes.empty()) { - // Workaround for not being able to copy richtext to system clipboard, this should at least work within the application itself - if(text.textBoxes.front().isRichTextBox && text.isNextPasteRich && lastCopiedRichText.has_value()) { - if(lastCopiedRichText.value().get_plain_text() == remove_carriage_returns_from_str(plainClipboardStr)) - text.textBoxes.front().textBox->process_rich_text_input(*text.textBoxes.front().cursor, lastCopiedRichText.value()); - else - text.textBoxes.front().textBox->process_text_input(*text.textBoxes.front().cursor, plainClipboardStr, shared_ptr_to_opt(text.textBoxes.front().modMap)); - } - else - text.textBoxes.front().textBox->process_text_input(*text.textBoxes.front().cursor, plainClipboardStr, shared_ptr_to_opt(text.textBoxes.front().modMap)); +void InputManager::process_text_paste(const std::string& pasteStr) { + if(lastCopiedRichText.has_value() && lastCopiedRichText.value().get_plain_text() == remove_carriage_returns_from_str(pasteStr)) { + pasteCallback({ + .text = pasteStr, + .richText = lastCopiedRichText + }); } } @@ -1060,20 +930,19 @@ bool InputManager::ctrl_or_meta_held() { void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { auto kPress = e.key; - //auto kMod = e.mod; switch(kPress) { case SDLK_UP: - set_key_up(e, KEY_TEXT_UP); + set_key_up(e, KEY_GENERIC_UP); break; case SDLK_DOWN: - set_key_up(e, KEY_TEXT_DOWN); + set_key_up(e, KEY_GENERIC_DOWN); break; case SDLK_LEFT: - set_key_up(e, KEY_TEXT_LEFT); + set_key_up(e, KEY_GENERIC_LEFT); break; case SDLK_RIGHT: - set_key_up(e, KEY_TEXT_RIGHT); + set_key_up(e, KEY_GENERIC_RIGHT); break; case SDLK_BACKSPACE: set_key_up(e, KEY_TEXT_BACKSPACE); @@ -1088,14 +957,12 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { set_key_up(e, KEY_TEXT_END); break; case SDLK_LSHIFT: - set_key_up(e, KEY_TEXT_SHIFT); set_key_up(e, KEY_GENERIC_LSHIFT); break; case SDLK_LALT: set_key_up(e, KEY_GENERIC_LALT); break; case SDLK_LCTRL: - set_key_up(e, KEY_TEXT_CTRL); set_key_up(e, KEY_GENERIC_LCTRL); break; case SDLK_C: @@ -1117,53 +984,21 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { set_key_up(e, KEY_TEXT_REDO); break; case SDLK_RETURN: - set_key_up(e, KEY_TEXT_ENTER); - main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ENTER, .down = e.down, .repeat = e.repeat}); + set_key_up(e, KEY_GENERIC_ENTER); break; case SDLK_TAB: set_key_up(e, KEY_TEXT_TAB); break; case SDLK_ESCAPE: - set_key_up(e, KEY_TEXT_ESCAPE); - main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ESCAPE, .down = e.down, .repeat = e.repeat}); + set_key_up(e, KEY_GENERIC_ESCAPE); break; default: break; } - for(auto& p : keyAssignments) { - if(p.first.y() == kPress) { // Dont try matching modifiers when setting key to go up + for(auto& p : keyAssignments) + if(p.first.y() == kPress) // Dont try matching modifiers when setting key to go up set_key_up(e, p.second); - main.input_key_callback(KeyCallbackArgs{.key = p.second, .down = e.down, .repeat = e.repeat}); - } - } - - switch(kPress) { - case SDLK_UP: - set_key_up(e, KEY_GENERIC_UP); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_UP, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_DOWN: - set_key_up(e, KEY_GENERIC_DOWN); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_DOWN, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_LEFT: - set_key_up(e, KEY_GENERIC_LEFT); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_LEFT, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_RIGHT: - set_key_up(e, KEY_GENERIC_RIGHT); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_RIGHT, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_RETURN: - set_key_up(e, KEY_GENERIC_ENTER); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ENTER, .down = e.down, .repeat = e.repeat}); - break; - case SDLK_ESCAPE: - set_key_up(e, KEY_GENERIC_ESCAPE); - main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ESCAPE, .down = e.down, .repeat = e.repeat}); - break; - } } void InputManager::Mouse::set_pos(const Vector2f& newPos) { diff --git a/src/InputManager.hpp b/src/InputManager.hpp index d707463..8546462 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -149,34 +149,26 @@ struct InputManager { int androidInputType; }; - void set_rich_text_box_input_front(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::shared_ptr>& textboxRect, const TextInputProperties& inputProps, const std::shared_ptr& nModMap = nullptr); - void set_rich_text_box_input_back(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::shared_ptr>& textboxRect, const TextInputProperties& inputProps, const std::shared_ptr& nModMap = nullptr); - void remove_rich_text_box_input(const std::shared_ptr& nTextBox); + unsigned set_text_box_front(const std::optional>& textboxRect, const TextInputProperties& inputProps); + unsigned set_text_box_back(const std::optional>& textboxRect, const TextInputProperties& inputProps); + void remove_text_box(unsigned textboxID); struct Text { bool is_accepting_input(); - void add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText); - void do_textbox_operation_with_undo(const std::function& func); - private: struct TextBoxInfo { - bool isRichTextBox; - std::shared_ptr textBox; - std::shared_ptr cursor; - std::shared_ptr> rect; - std::shared_ptr modMap; + std::optional> rect; TextInputProperties textInputProperties; - UndoManager textboxUndo; + unsigned id; }; void update_accepting_input(SDL_Window* window); - bool isNextPasteRich = false; - std::optional propID; std::deque textBoxes; + unsigned get_id(); friend struct InputManager; } text; @@ -224,26 +216,17 @@ struct InputManager { KEY_ASSIGNABLE_COUNT, // Not a real key // Unassignable - KEY_TEXT_DOWN, - KEY_TEXT_UP, - KEY_TEXT_LEFT, - KEY_TEXT_RIGHT, KEY_TEXT_BACKSPACE, KEY_TEXT_DELETE, KEY_TEXT_HOME, KEY_TEXT_END, - KEY_TEXT_SHIFT, KEY_TEXT_COPY, KEY_TEXT_CUT, KEY_TEXT_PASTE, - KEY_TEXT_ENTER, KEY_TEXT_TAB, - KEY_TEXT_CTRL, - KEY_TEXT_META, KEY_TEXT_SELECTALL, KEY_TEXT_UNDO, KEY_TEXT_REDO, - KEY_TEXT_ESCAPE, KEY_GENERIC_UP, KEY_GENERIC_DOWN, KEY_GENERIC_LEFT, @@ -254,6 +237,7 @@ struct InputManager { KEY_GENERIC_LALT, KEY_GENERIC_LCTRL, KEY_GENERIC_LMETA, + KEY_COUNT }; @@ -269,8 +253,7 @@ struct InputManager { void touch_finger_do_mouse_up(const SDL_TouchFingerEvent& f); void touch_finger_do_mouse_motion(const SDL_TouchFingerEvent& f); - void add_text_to_textbox(const std::string& inputText); - + void backend_input_text_event(const std::string& str); void backend_drop_file_event(const SDL_DropEvent& e); void backend_drop_text_event(const SDL_DropEvent& e); void backend_mouse_button_up_update(const SDL_MouseButtonEvent& e); @@ -293,9 +276,14 @@ struct InputManager { void set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair); std::string get_clipboard_str_SDL(); void get_clipboard_image_data_SDL(const std::function& callback); - void call_text_paste(bool isRichTextPaste); - void process_text_paste(const std::string& plainClipboardStr); + struct TextPasteCallbackArgs { + std::string text; + std::optional richText; + }; + void call_text_paste(const std::function& pasteCallbackFunc); + void process_text_paste(const std::string& pasteStr); + std::function pasteCallback; std::optional lastCopiedRichText; std::string key_assignment_to_str(const Vector2ui32& k) const; diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index 575d083..e590aa1 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -372,12 +372,12 @@ void MainProgram::input_key_callback(const InputManager::KeyCallbackArgs& key) { world->input_key_callback(key); } -void MainProgram::input_text_key_callback() { - g.input_text_key_callback(); +void MainProgram::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { + g.input_text_key_callback(key); } -void MainProgram::input_text_input_callback() { - g.input_text_input_callback(); +void MainProgram::input_text_callback(const std::string& str) { + g.input_text_callback(str); } void MainProgram::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { diff --git a/src/MainProgram.hpp b/src/MainProgram.hpp index aa5fe47..fed2936 100644 --- a/src/MainProgram.hpp +++ b/src/MainProgram.hpp @@ -126,8 +126,8 @@ class MainProgram { void input_drop_file_callback(const InputManager::DropCallbackArgs& drop); void input_drop_text_callback(const InputManager::DropCallbackArgs& drop); void input_key_callback(const InputManager::KeyCallbackArgs& key); - void input_text_key_callback(); - void input_text_input_callback(); + void input_text_key_callback(const InputManager::KeyCallbackArgs& key); + void input_text_callback(const std::string& str); void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button); void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion); void input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel); diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index 3dd0c38..b1f0267 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -145,14 +145,12 @@ void TextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift, cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::LEFT_WORD : Movement::LEFT, cur.pos)); - if(onUserTextEdit) onUserTextEdit(); break; case InputKey::DEL: if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::RIGHT_WORD : Movement::RIGHT, cur.pos)); - if(onUserTextEdit) onUserTextEdit(); break; case InputKey::ENTER: process_text_input(cur, "\n", inputModMap); @@ -244,7 +242,6 @@ std::pair TextBox::process_cut(Cursor& cur) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.previousX = std::nullopt; if(onChange) onChange(); - if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } return toRet; @@ -257,7 +254,6 @@ void TextBox::process_text_input(Cursor& cur, const std::string& in, const std:: cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert(cur.pos, in, inputModMap); cur.previousX = std::nullopt; if(onChange) onChange(); - if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } } @@ -269,7 +265,6 @@ void TextBox::process_rich_text_input(Cursor& cur, const TextData& richText) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert_rich_text(cur.pos, richText); cur.previousX = std::nullopt; if(onChange) onChange(); - if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } } @@ -502,7 +497,6 @@ void TextBox::set_rich_text_data(const TextData& richText) { void TextBox::set_rich_text_data_for_undo_redo(const TextData& richText) { set_rich_text_data(richText); - if(onUserTextEdit) onUserTextEdit(); } std::string TextBox::get_string() { diff --git a/src/RichText/TextBox.hpp b/src/RichText/TextBox.hpp index 31a5c06..a558116 100644 --- a/src/RichText/TextBox.hpp +++ b/src/RichText/TextBox.hpp @@ -68,7 +68,6 @@ class TextBox { bool inputChangedTextBox = false; std::function onChange; - std::function onUserTextEdit; enum class Movement { NOWHERE, diff --git a/src/RichTextUserInput.cpp b/src/RichTextUserInput.cpp new file mode 100644 index 0000000..2339329 --- /dev/null +++ b/src/RichTextUserInput.cpp @@ -0,0 +1,123 @@ +#include "RichTextUserInput.hpp" + +template std::optional shared_ptr_to_opt(const std::shared_ptr o) { + if(o) + return *o; + else + return std::nullopt; +} + +RichTextUserInput::RichTextUserInput(const std::shared_ptr& initTextBox, const std::shared_ptr& initCursor, const std::shared_ptr& initModMap, const std::function& initOnTextEdit): + textBox(initTextBox), + cursor(initCursor), + modMap(initModMap), + onTextEdit(initOnTextEdit) +{} + +void RichTextUserInput::add_text_to_textbox(const std::string& inputText) { + do_textbox_operation_with_undo([&]() { + textBox->process_text_input(*cursor, inputText, shared_ptr_to_opt(modMap)); + }); + onTextEdit(); +} + +void RichTextUserInput::add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText) { + textboxUndo.push({[&, prevCursor = prevCursor, prevRichText = prevRichText]() { + textBox->set_rich_text_data_for_undo_redo(prevRichText); + cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(prevCursor.selectionEndPos, prevCursor.selectionBeginPos); + cursor->previousX = std::nullopt; + onTextEdit(); + return true; + }, + [&, currentCursor = *cursor, currentRichText = textBox->get_rich_text_data()]() { + textBox->set_rich_text_data_for_undo_redo(currentRichText); + cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(currentCursor.selectionEndPos, currentCursor.selectionBeginPos); + cursor->previousX = std::nullopt; + onTextEdit(); + return true; + }}); +} + +void RichTextUserInput::do_textbox_operation_with_undo(const std::function& func) { + auto prevRichText = textBox->get_rich_text_data(); + auto prevCursor = *cursor; + func(); + add_textbox_undo(prevCursor, prevRichText); + onTextEdit(); +} + +void RichTextUserInput::input_key_callback(InputManager& input, const InputManager::KeyCallbackArgs& key) { + std::optional modMapOpt = shared_ptr_to_opt(modMap); + + if(key.down) { + switch(key.key) { + case InputManager::KEY_GENERIC_UP: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::UP, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_GENERIC_DOWN: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DOWN, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_GENERIC_LEFT: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::LEFT, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_GENERIC_RIGHT: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::RIGHT, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_TEXT_BACKSPACE: + do_textbox_operation_with_undo([&] { + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::BACKSPACE, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + }); + break; + case InputManager::KEY_TEXT_DELETE: + do_textbox_operation_with_undo([&] { + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DEL, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + }); + break; + case InputManager::KEY_TEXT_HOME: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::HOME, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_TEXT_END: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::END, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_TEXT_COPY: + input.set_clipboard_plain_and_richtext_pair(textBox->process_copy(*cursor)); + break; + case InputManager::KEY_TEXT_CUT: + do_textbox_operation_with_undo([&] { + input.set_clipboard_plain_and_richtext_pair(textBox->process_cut(*cursor)); + }); + break; + case InputManager::KEY_TEXT_PASTE: + do_textbox_operation_with_undo([&]() { + input.call_text_paste([textBox = textBox, cursor = cursor, modMapOpt] (const InputManager::TextPasteCallbackArgs& pasteArgs) { + if(pasteArgs.richText.has_value()) + textBox->process_rich_text_input(*cursor, pasteArgs.richText.value()); + else + textBox->process_text_input(*cursor, pasteArgs.text, modMapOpt); + }); + }); + break; + case InputManager::KEY_TEXT_SELECTALL: + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::SELECT_ALL, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + break; + case InputManager::KEY_TEXT_UNDO: + textboxUndo.undo(); + break; + case InputManager::KEY_TEXT_REDO: + textboxUndo.redo(); + break; + case InputManager::KEY_GENERIC_ENTER: + do_textbox_operation_with_undo([&] { + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::ENTER, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + }); + break; + case InputManager::KEY_TEXT_TAB: + do_textbox_operation_with_undo([&] { + textBox->process_key_input(*cursor, RichText::TextBox::InputKey::TAB, input.ctrl_or_meta_held(), input.key(InputManager::KEY_GENERIC_LSHIFT).held, modMapOpt); + }); + break; + default: + break; + } + } +} diff --git a/src/RichTextUserInput.hpp b/src/RichTextUserInput.hpp new file mode 100644 index 0000000..001ad5c --- /dev/null +++ b/src/RichTextUserInput.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "RichText/TextBox.hpp" +#include "UndoManager.hpp" +#include "InputManager.hpp" + +class RichTextUserInput { + public: + RichTextUserInput(const std::shared_ptr& initTextBox, const std::shared_ptr& initCursor, const std::shared_ptr& initModMap, const std::function& initOnTextEdit); + void input_key_callback(InputManager& input, const InputManager::KeyCallbackArgs& key); + void add_text_to_textbox(const std::string& inputText); + private: + void do_textbox_operation_with_undo(const std::function& func); + void add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText); + std::shared_ptr textBox; + std::shared_ptr cursor; + std::shared_ptr modMap; + UndoManager textboxUndo; + std::function onTextEdit; +}; diff --git a/src/main.cpp b/src/main.cpp index f8e537c..82a049e 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -752,7 +752,7 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { mS.m->input.backend_key_up_update(event->key); break; case SDL_EVENT_TEXT_INPUT: - mS.m->input.add_text_to_textbox(event->text.text); + mS.m->input.backend_input_text_event(event->text.text); break; case SDL_EVENT_DROP_FILE: mS.m->update_scale_and_density();