From 8830386dc0d5068eecad6e69dd7a168771afa750 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Tue, 11 Nov 2025 22:21:25 +0300 Subject: [PATCH] Copy paragraph styles in rich text, and make plain text clipboard work in web version again --- .../emscripten_browser_clipboard.h | 14 ++++---- src/InputManager.cpp | 35 +++++++++++-------- src/InputManager.hpp | 6 ++-- src/RichText/TextBox.cpp | 6 ++++ 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/include/EmscriptenHelpers/emscripten_browser_clipboard.h b/include/EmscriptenHelpers/emscripten_browser_clipboard.h index 8674121..4a70fa5 100644 --- a/include/EmscriptenHelpers/emscripten_browser_clipboard.h +++ b/include/EmscriptenHelpers/emscripten_browser_clipboard.h @@ -28,16 +28,16 @@ inline void copy(std::string const &content); namespace detail { -EM_JS_INLINE(void, paste_js, (paste_handler callback, const char* mimeType, void *callback_data), { +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(mimeType), callback, callback_data]); + Module["ccall"]('emscripten_browser_clipboard_detail_paste_return', 'number', ['string', 'number', 'number'], [event.clipboardData.getData('text/plain'), callback, callback_data]); }); }); -EM_JS_INLINE(void, copy_js, (copy_handler callback, const char* mimeType, void *callback_data), { +EM_JS_INLINE(void, copy_js, (copy_handler callback, void *callback_data), { /// Register the given callback to handle copy events. Callback data pointer is passed through to the callback. /// Copy handler callback signature is: /// char const *my_handler(void *callback_data = nullptr); @@ -55,14 +55,14 @@ EM_JS_INLINE(void, copy_async_js, (char const *content_ptr), { } // namespace detail -inline void paste(paste_handler callback, const char* mimeType, void *callback_data) { +inline void paste(paste_handler callback, void *callback_data) { /// C++ wrapper for javascript paste call - detail::paste_js(callback, mimeType, callback_data); + detail::paste_js(callback, callback_data); } -inline void copy(copy_handler callback, const char* mimeType, void *callback_data) { +inline void copy(copy_handler callback, void *callback_data) { /// C++ wrapper for javascript copy call - detail::copy_js(callback, mimeType, callback_data); + detail::copy_js(callback, callback_data); } inline void copy(std::string const &content) { diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 357558c..5e37b92 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -47,6 +47,7 @@ InputManager::InputManager() { defaultKeyAssignments[{0, SDLK_F2}] = KEY_SHOW_PLAYER_LIST; 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 @@ -63,13 +64,8 @@ InputManager::InputManager() { InputManager* inMan = (InputManager*)callbackData; inMan->clipboardPasteEventHappened = true; inMan->clipboardPasteEventData["text/plain"] = pasteData; - }, "text/plain", this); - - emscripten_browser_clipboard::paste([](std::string&& pasteData, void* callbackData){ - InputManager* inMan = (InputManager*)callbackData; - inMan->clipboardPasteEventHappened = true; - inMan->clipboardPasteEventData[inMan->get_infpnt_richtext_mimetype()] = pasteData; - }, get_infpnt_richtext_mimetype().c_str(), this); + inMan->process_text_paste(); + }, this); #endif keyAssignments = defaultKeyAssignments; @@ -151,14 +147,18 @@ void InputManager::set_clipboard_str(std::string_view s) { } std::string InputManager::get_infpnt_richtext_mimetype() { - return "infpnt/richtext" + std::string(VERSION_STRING); + return "web text/infpnt"; } void InputManager::set_clipboard_plain_and_richtext_pair(const std::pair& plainAndRichtextPair) { +#ifdef __EMSCRIPTEN__ + set_clipboard_str(plainAndRichtextPair.first); +#else std::unordered_map clipboardData; clipboardData["text/plain"] = plainAndRichtextPair.first; clipboardData[get_infpnt_richtext_mimetype()] = plainAndRichtextPair.second; set_clipboard_data(clipboardData); +#endif } void InputManager::set_clipboard_data(const std::unordered_map& newClipboardData) { @@ -347,13 +347,8 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { 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) { - std::string richTextClipboard = get_clipboard_data_for_mimetype(get_infpnt_richtext_mimetype()); - if(!richTextClipboard.empty()) - text.textBox->process_rich_text_input(*text.cursor, richTextClipboard); - else - text.textBox->process_text_input(*text.cursor, get_clipboard_data_for_mimetype("text/plain"), text.modMap); - } + if(text.textBox && key(KEY_TEXT_PASTE).repeat) + process_text_paste(); break; case SDLK_A: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) @@ -388,6 +383,16 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { set_key_down(e, f->second); } +void InputManager::process_text_paste() { + if(text.textBox) { + std::string richTextClipboard = get_clipboard_data_for_mimetype(get_infpnt_richtext_mimetype()); + if(!richTextClipboard.empty()) + text.textBox->process_rich_text_input(*text.cursor, richTextClipboard); + else + text.textBox->process_text_input(*text.cursor, get_clipboard_data_for_mimetype("text/plain"), text.modMap); + } +} + bool InputManager::ctrl_or_meta_held() { return key(KEY_GENERIC_LCTRL).held || key(KEY_GENERIC_LMETA).held; } diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 550f327..8a1c479 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -19,7 +19,7 @@ struct InputManager { InputManager(); void frame_reset(const Vector2i& windowSize); - + // Just renamed SDL3 cursor types, but if we need to change to a different backend we can // translate this enum to different commands in main.cpp enum class SystemCursorType : unsigned { @@ -201,7 +201,9 @@ struct InputManager { 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); - std::string get_infpnt_richtext_mimetype(); + void process_text_paste(); + + static std::string get_infpnt_richtext_mimetype(); std::unordered_map clipboardData; diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index f179030..9d38002 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -376,6 +376,12 @@ TextPosition TextBox::insert_rich_text(TextPosition p, const TextData& richText) set_text_style_modifier_between(shiftedModPos, lastPos, modifier); } + for(size_t pIndex = p.fParagraphIndex; pIndex <= lastPos.fParagraphIndex; pIndex++) { + // If the first line was empty before pasting (it's not empty right now, which is why the comparison is required), we will apply the paragraph style to the first line as well + if((pIndex == p.fParagraphIndex && paragraphs[pIndex].text.size() == richText.paragraphs[0].text.size()) || pIndex != p.fParagraphIndex) + paragraphs[pIndex].pStyleData = richText.paragraphs[pIndex - p.fParagraphIndex].pStyleData; + } + inputChangedTextBox = true; needsRebuild = true;