Copy paragraph styles in rich text, and make plain text clipboard work in web version again

This commit is contained in:
Yousef Khadadeh 2025-11-11 22:21:25 +03:00
parent b8fb00e218
commit 8830386dc0
4 changed files with 37 additions and 24 deletions

View file

@ -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) {

View file

@ -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<std::string, std::string>& plainAndRichtextPair) {
#ifdef __EMSCRIPTEN__
set_clipboard_str(plainAndRichtextPair.first);
#else
std::unordered_map<std::string, std::string> 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<std::string, std::string>& 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;
}

View file

@ -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<std::string, std::string>& plainAndRichtextPair);
void set_clipboard_data(const std::unordered_map<std::string, std::string>& newClipboardData);
std::string get_infpnt_richtext_mimetype();
void process_text_paste();
static std::string get_infpnt_richtext_mimetype();
std::unordered_map<std::string, std::string> clipboardData;

View file

@ -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;