From d57d2f4752ed05f327ec02f4bebcdc61d97c7c7a Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 2 Dec 2025 09:53:03 +0300 Subject: [PATCH] Added text edit right click menu --- .../emscripten_browser_clipboard.h | 10 +- .../DrawingProgramEditToolBase.cpp | 7 ++ .../DrawingProgramEditToolBase.hpp | 1 + src/DrawingProgram/EditTool.cpp | 5 +- src/DrawingProgram/TextBoxEditTool.cpp | 33 +++++- src/DrawingProgram/TextBoxEditTool.hpp | 1 + src/GUIStuff/Elements/Element.hpp | 7 -- src/InputManager.cpp | 103 ++++-------------- src/InputManager.hpp | 23 ++-- src/Toolbar.cpp | 9 +- 10 files changed, 84 insertions(+), 115 deletions(-) diff --git a/include/EmscriptenHelpers/emscripten_browser_clipboard.h b/include/EmscriptenHelpers/emscripten_browser_clipboard.h index 77c42df..460e6b4 100644 --- a/include/EmscriptenHelpers/emscripten_browser_clipboard.h +++ b/include/EmscriptenHelpers/emscripten_browser_clipboard.h @@ -32,9 +32,13 @@ EM_JS_INLINE(void, paste_js, (paste_handler callback, void *callback_data), { /// Register the given callback to handle paste events. Callback data pointer is passed through to the callback. /// Paste handler callback signature is: /// void my_handler(std::string const &paste_data, void *callback_data = nullptr); - document.addEventListener('paste', (event) => { - Module["ccall"]('emscripten_browser_clipboard_detail_paste_return', 'number', ['string', 'number', 'number'], [event.clipboardData.getData('text/plain'), callback, callback_data]); - }); + navigator.clipboard.readText() + .then(text => { + Module["ccall"]('emscripten_browser_clipboard_detail_paste_return', 'number', ['string', 'number', 'number'], [text, callback, callback_data]); + }) + .catch(err => { + console.error('Failed to read clipboard contents: ', err); + }); }); EM_JS_INLINE(void, copy_js, (copy_handler callback, void *callback_data), { diff --git a/src/DrawingProgram/DrawingProgramEditToolBase.cpp b/src/DrawingProgram/DrawingProgramEditToolBase.cpp index 144a40c..24ecefa 100644 --- a/src/DrawingProgram/DrawingProgramEditToolBase.cpp +++ b/src/DrawingProgram/DrawingProgramEditToolBase.cpp @@ -1,7 +1,14 @@ #include "DrawingProgramEditToolBase.hpp" +#include "../MainProgram.hpp" DrawingProgramEditToolBase::DrawingProgramEditToolBase(DrawingProgram& initDrawP): drawP(initDrawP) {} +bool DrawingProgramEditToolBase::right_click_popup_gui(const std::shared_ptr& comp, Vector2f popupPos) { + Toolbar& t = drawP.world.main.toolbar; + t.paint_popup(popupPos); + return true; +} + DrawingProgramEditToolBase::~DrawingProgramEditToolBase() {} diff --git a/src/DrawingProgram/DrawingProgramEditToolBase.hpp b/src/DrawingProgram/DrawingProgramEditToolBase.hpp index 3b0ccae..379a8fa 100644 --- a/src/DrawingProgram/DrawingProgramEditToolBase.hpp +++ b/src/DrawingProgram/DrawingProgramEditToolBase.hpp @@ -13,6 +13,7 @@ class DrawingProgramEditToolBase { virtual void commit_edit_updates(const std::shared_ptr& a, std::any& prevData) = 0; virtual bool edit_update(const std::shared_ptr& a) = 0; virtual bool edit_gui(const std::shared_ptr& a) = 0; + virtual bool right_click_popup_gui(const std::shared_ptr& comp, Vector2f popupPos); virtual ~DrawingProgramEditToolBase(); protected: DrawingProgram& drawP; diff --git a/src/DrawingProgram/EditTool.cpp b/src/DrawingProgram/EditTool.cpp index 4141e8b..96989f9 100644 --- a/src/DrawingProgram/EditTool.cpp +++ b/src/DrawingProgram/EditTool.cpp @@ -42,7 +42,10 @@ void EditTool::gui_toolbox() { } bool EditTool::right_click_popup_gui(Vector2f popupPos) { - return drawP.selection_action_menu(popupPos); + if(controls.isEditing) + return controls.compEditTool->right_click_popup_gui(controls.compToEdit.lock(), popupPos); + else + return drawP.selection_action_menu(popupPos); } void EditTool::add_point_handle(const HandleData& handle) { diff --git a/src/DrawingProgram/TextBoxEditTool.cpp b/src/DrawingProgram/TextBoxEditTool.cpp index 968acd3..9ac0f73 100644 --- a/src/DrawingProgram/TextBoxEditTool.cpp +++ b/src/DrawingProgram/TextBoxEditTool.cpp @@ -152,6 +152,37 @@ bool TextBoxEditTool::edit_gui(const std::shared_ptr& comp) { return oldInputChangedTextBox; } +bool TextBoxEditTool::right_click_popup_gui(const std::shared_ptr& comp, Vector2f popupPos) { + Toolbar& t = drawP.world.main.toolbar; + bool shouldClose = false; + std::shared_ptr a = std::static_pointer_cast(comp); + t.gui.list_popup_menu("Text popup menu", popupPos, [&]() { + t.gui.text_label_light("Text menu"); + InputManager& input = drawP.world.main.input; + if(t.gui.text_button_left_transparent("Paste", "Paste")) { + input.call_text_paste(true); + shouldClose = true; + } + if(t.gui.text_button_left_transparent("Paste without formatting", "Paste without formatting")) { + input.call_text_paste(false); + shouldClose = true; + } + if(a->cursor->selectionBeginPos != a->cursor->selectionEndPos) { + if(t.gui.text_button_left_transparent("Copy", "Copy")) { + input.set_clipboard_plain_and_richtext_pair(a->textBox->process_copy(*a->cursor)); + shouldClose = true; + } + if(t.gui.text_button_left_transparent("Cut", "Cut")) { + input.text.do_textbox_operation_with_undo([&]() { + input.set_clipboard_plain_and_richtext_pair(a->textBox->process_cut(*a->cursor)); + }); + shouldClose = true; + } + } + }); + return !shouldClose; +} + void TextBoxEditTool::hold_undo_data(const std::string& undoName, const std::shared_ptr& a) { auto it = undoHeldData.find(undoName); if(it != undoHeldData.end()) { @@ -286,7 +317,7 @@ bool TextBoxEditTool::edit_update(const std::shared_ptr& comp) { bool collidesWithBox = a->collides_with_cam_coords(drawP.world.drawData.cam.c, mousePointCollection); - input.text.set_rich_text_box_input(a->textBox, a->cursor, currentMods); + input.text.set_rich_text_box_input(a->textBox, a->cursor, true, currentMods); a->textBox->process_mouse_left_button(*a->cursor, a->get_mouse_pos(drawP), (drawP.controls.leftClick && collidesWithBox) ? drawP.world.main.input.mouse.leftClicks : 0, drawP.controls.leftClickHeld, input.key(InputManager::KEY_GENERIC_LSHIFT).held); return true; diff --git a/src/DrawingProgram/TextBoxEditTool.hpp b/src/DrawingProgram/TextBoxEditTool.hpp index 477de24..b5a1c6d 100644 --- a/src/DrawingProgram/TextBoxEditTool.hpp +++ b/src/DrawingProgram/TextBoxEditTool.hpp @@ -14,6 +14,7 @@ class TextBoxEditTool : public DrawingProgramEditToolBase { virtual void commit_edit_updates(const std::shared_ptr& a, std::any& prevData) override; virtual bool edit_update(const std::shared_ptr& a) override; virtual bool edit_gui(const std::shared_ptr& a) override; + virtual bool right_click_popup_gui(const std::shared_ptr& comp, Vector2f popupPos) override; private: struct TextBoxEditToolAllData { DrawTextBox::Data textboxData; diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index 72832c4..5e3c222 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -89,14 +89,7 @@ struct UpdateInputData { bool down = false; bool leftShift = false; bool leftCtrl = false; - bool home = false; - bool del = false; - bool backspace = false; bool enter = false; - bool selectAll = false; - bool copy = false; - bool paste = false; - bool cut = false; bool escape = false; } key; diff --git a/src/InputManager.cpp b/src/InputManager.cpp index eb56785..cdd9f1f 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -48,26 +48,6 @@ InputManager::InputManager() { defaultKeyAssignments[{0, SDLK_SPACE}] = KEY_HOLD_TO_PAN; -#ifdef __EMSCRIPTEN__ - // Without this, SDL eats the CTRL-V event that initiates the paste event - // https://github.com/pthom/hello_imgui/issues/3#issuecomment-1564536870 - EM_ASM({ - window.addEventListener('keydown', function(event) { - if((event.ctrlKey || event.metaKey) && (event.key == 'v' || event.code == 'KeyV')) { - if(Module["ccall"]('is_text_input_happening', 'number', [], []) === 1) - event.stopImmediatePropagation(); - } - }, true); - }); - - emscripten_browser_clipboard::paste([](std::string&& pasteData, void* callbackData){ - InputManager* inMan = (InputManager*)callbackData; - inMan->clipboardPasteEventHappened = true; - inMan->clipboardPasteEventData["text/plain"] = pasteData; - inMan->process_text_paste(); - }, this); -#endif - keyAssignments = defaultKeyAssignments; } @@ -81,11 +61,12 @@ void InputManager::text_input_silence_everything() { text.newInput.clear(); } -void InputManager::Text::set_rich_text_box_input(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, const std::optional& nModMap) { +void InputManager::Text::set_rich_text_box_input(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::optional& nModMap) { set_accepting_input(); newTextBox = nTextBox; newCursor = nCursor; newModMap = nModMap; + newIsRichTextBox = isRichTextBox; } void InputManager::Text::add_text_to_textbox(const std::string& inputText) { @@ -129,41 +110,11 @@ void InputManager::Text::do_textbox_operation_with_undo(const std::functionsecond; -#else +std::string InputManager::get_clipboard_str_SDL() { char* data = SDL_GetClipboardText(); std::string toRet(data); SDL_free(data); return toRet; -#endif -} - -std::string InputManager::get_clipboard_data_for_mimetype(const std::string& mimeType) { -#ifdef __EMSCRIPTEN__ - auto it = clipboardPasteEventData.find(mimeType); - if(it == clipboardPasteEventData.end()) - return ""; - return it->second; -#else - size_t datSize = 0; - void* dat = SDL_GetClipboardData(mimeType.c_str(), &datSize); - if(!dat) - return ""; - return std::string(static_cast(dat), datSize); -#endif } void InputManager::set_clipboard_str(std::string_view s) { @@ -180,30 +131,6 @@ void InputManager::set_clipboard_plain_and_richtext_pair(const std::pair& newClipboardData) { - SDL_ClearClipboardData(); - clipboardData = newClipboardData; - - std::vector mimeTypes; - for(auto& [k, v] : clipboardData) - mimeTypes.emplace_back(k.c_str()); - - SDL_SetClipboardData( - [](void *userdata, const char *mime_type, size_t *size) { - std::unordered_map& clipboardData = *static_cast*>(userdata); - std::string mimeTypeStr(mime_type); - auto it = clipboardData.find(mimeTypeStr); - std::cout << "requesting: " << mime_type << std::endl; - if(it == clipboardData.end()) - return static_cast(nullptr); - *size = it->second.length(); - return static_cast(it->second.c_str()); - }, - [](void *userdata) { - }, - static_cast(&clipboardData), mimeTypes.data(), mimeTypes.size()); -} - std::string InputManager::key_assignment_to_str(const Vector2ui32& k) { std::string toRet; if(k.x() & SDL_KMOD_CTRL) @@ -375,7 +302,7 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { set_key_down(e, KEY_TEXT_PASTE); if(text.textBox && key(KEY_TEXT_PASTE).repeat) text.do_textbox_operation_with_undo([&]() { - process_text_paste(); + call_text_paste(true); }); break; case SDLK_A: @@ -427,11 +354,23 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { set_key_down(e, f->second); } -void InputManager::process_text_paste() { +void InputManager::call_text_paste(bool isRichTextPaste) { + text.isNextPasteRich = isRichTextPaste; +#ifdef __EMSCRIPTEN__ + emscripten_browser_clipboard::paste([](std::string&& pasteData, void* callbackData){ + InputManager* inMan = (InputManager*)callbackData; + std::string pData = pasteData; + inMan->process_text_paste(pData); + }, this); +#else + process_text_paste(get_clipboard_str_SDL()); +#endif +} + +void InputManager::process_text_paste(const std::string& plainClipboardStr) { if(text.textBox) { // Workaround for not being able to copy richtext to system clipboard, this should at least work within the application itself - std::string plainClipboardStr = get_clipboard_str(); - if(lastCopiedRichText.has_value()) { + if(text.isRichTextBox && text.isNextPasteRich && lastCopiedRichText.has_value()) { if(lastCopiedRichText.value().get_plain_text() == plainClipboardStr) text.textBox->process_rich_text_input(*text.cursor, lastCopiedRichText.value()); else @@ -546,8 +485,12 @@ void InputManager::frame_reset(const Vector2i& windowSize) { text.cursor = text.newCursor; text.textBox = text.newTextBox; text.modMap = text.newModMap; + text.isRichTextBox = text.newIsRichTextBox; + text.newCursor = nullptr; text.newTextBox = nullptr; + text.newModMap = std::nullopt; + text.newIsRichTextBox = false; text.newInput.clear(); droppedItems.clear(); diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 4e39162..6312bcf 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -89,17 +89,21 @@ struct InputManager { void set_accepting_input(); bool get_accepting_input(); - void set_rich_text_box_input(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, const std::optional& nModMap = std::nullopt); + void set_rich_text_box_input(const std::shared_ptr& nTextBox, const std::shared_ptr& nCursor, bool isRichTextBox, const std::optional& nModMap = std::nullopt); void add_text_to_textbox(const std::string& inputText); void add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText); void do_textbox_operation_with_undo(const std::function& func); private: + bool isNextPasteRich = false; + + bool isRichTextBox; std::shared_ptr textBox; std::shared_ptr cursor; std::optional modMap; + bool newIsRichTextBox; std::shared_ptr newTextBox; std::shared_ptr newCursor; std::optional newModMap; @@ -201,22 +205,11 @@ struct InputManager { void backend_key_up_update(const SDL_KeyboardEvent& e); void backend_key_down_update(const SDL_KeyboardEvent& e); - bool get_clipboard_paste_happened(); - std::string get_clipboard_str(); - - std::string get_clipboard_data_for_mimetype(const std::string& mimeType); - void set_clipboard_str(std::string_view s); void set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair); - void set_clipboard_data(const std::unordered_map& newClipboardData); - void process_text_paste(); - - std::unordered_map clipboardData; - -#ifdef __EMSCRIPTEN__ - bool clipboardPasteEventHappened = false; - std::unordered_map clipboardPasteEventData; -#endif + std::string get_clipboard_str_SDL(); + void call_text_paste(bool isRichTextPaste); + void process_text_paste(const std::string& plainClipboardStr); std::optional lastCopiedRichText; diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 744b3a4..aa17a9e 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1983,17 +1983,10 @@ void Toolbar::initialize_io_before_update() { io->key.down = main.input.key(InputManager::KEY_TEXT_DOWN).repeat; io->key.leftShift = main.input.key(InputManager::KEY_TEXT_SHIFT).held; io->key.leftCtrl = main.input.key(InputManager::KEY_TEXT_CTRL).held; - io->key.home = main.input.key(InputManager::KEY_TEXT_HOME).repeat; - io->key.del = main.input.key(InputManager::KEY_TEXT_DELETE).repeat; - io->key.backspace = main.input.key(InputManager::KEY_TEXT_BACKSPACE).repeat; io->key.enter = main.input.key(InputManager::KEY_TEXT_ENTER).repeat; - io->key.selectAll = main.input.key(InputManager::KEY_TEXT_SELECTALL).repeat; - io->key.copy = main.input.key(InputManager::KEY_TEXT_COPY).repeat; - io->key.paste = main.input.get_clipboard_paste_happened(); - io->key.cut = main.input.key(InputManager::KEY_TEXT_CUT).repeat; io->key.escape = main.input.key(InputManager::KEY_GENERIC_ESCAPE).repeat; io->richTextBoxEdit = [&](std::shared_ptr t, std::shared_ptr c) { - main.input.text.set_rich_text_box_input(t, c); + main.input.text.set_rich_text_box_input(t, c, false); }; if(io->acceptingTextInput)