diff --git a/src/Bookmarks/BookmarkManager.cpp b/src/Bookmarks/BookmarkManager.cpp index eb3904c..dd04931 100644 --- a/src/Bookmarks/BookmarkManager.cpp +++ b/src/Bookmarks/BookmarkManager.cpp @@ -355,12 +355,14 @@ void BookmarkManager::setup_list_gui() { }); }); - if(editingBookmark) { - text_label_centered(gui, editingBookmark->is_folder() ? "Edit Bookmark Folder" : "Edit Bookmark"); + auto editingBookmarkLock = editingBookmark.lock(); + if(editingBookmarkLock) { + text_label_centered(gui, editingBookmarkLock->is_folder() ? "Edit Bookmark Folder" : "Edit Bookmark"); input_text_field(gui, "input edit name", "Name", &nameToEdit, { .onEdit = [&] { - if(editingBookmark) { // This can be set to null on the way here (close bookmark popup when textbox is selected) - editingBookmark->set_name(world.delayedUpdateObjectManager, nameToEdit); + auto editingBookmarkLock = editingBookmark.lock(); + if(editingBookmarkLock) { // This can be set to null on the way here (close bookmark popup when textbox is selected) + editingBookmarkLock->set_name(world.delayedUpdateObjectManager, nameToEdit); world.main.g.gui.set_to_layout(); } } @@ -572,18 +574,19 @@ void BookmarkManager::editing_bookmark_check() { WorldUndoManager::UndoObjectID undoID; }; - if(editingBookmark) { - const std::string& currentNameData = editingBookmark->get_name(); + auto editingBookmarkLock = editingBookmark.lock(); + if(editingBookmarkLock) { + const std::string& currentNameData = editingBookmarkLock->get_name(); if(currentNameData != editingBookmarkOldName.value()) world.undo.push(std::make_unique(editingBookmarkOldName.value(), world.undo.get_undoid_from_netid(editingBookmark.get_net_id()))); } if(selectedBookmarkIndices.size() == 1) { editingBookmark = get_bookmark_from_obj_index(*selectedBookmarkIndices.begin()); - editingBookmarkOldName = editingBookmark->get_name(); + editingBookmarkOldName = editingBookmark.lock()->get_name(); } else { - editingBookmark = NetworkingObjects::NetObjTemporaryPtr(); + editingBookmark.reset(); editingBookmarkOldName = std::nullopt; } } diff --git a/src/Bookmarks/BookmarkManager.hpp b/src/Bookmarks/BookmarkManager.hpp index a8b4bb8..3b17ae7 100644 --- a/src/Bookmarks/BookmarkManager.hpp +++ b/src/Bookmarks/BookmarkManager.hpp @@ -33,7 +33,7 @@ class BookmarkManager { std::set selectedBookmarkIndices; - NetworkingObjects::NetObjTemporaryPtr editingBookmark; + NetworkingObjects::NetObjWeakPtr editingBookmark; std::optional editingBookmarkOldName; void editing_bookmark_check(); diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp index b4202c7..5791a20 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.cpp @@ -391,12 +391,14 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { }); }); - if(editingLayer) { - text_label_centered(gui, editingLayer->is_folder() ? "Edit Layer Folder" : "Edit Layer"); + auto editingLayerLock = editingLayer.lock(); + if(editingLayerLock) { + text_label_centered(gui, editingLayerLock->is_folder() ? "Edit Layer Folder" : "Edit Layer"); input_text_field(gui, "input edit name", "Name", &nameToEdit, { .onEdit = [&] { - if(editingLayer) { // This can be set to null on the way here (close layer popup when textbox is selected) - editingLayer->set_name(world.delayedUpdateObjectManager, nameToEdit); + auto editingLayerLock = editingLayer.lock(); + if(editingLayerLock) { // This can be set to null on the way here (close layer popup when textbox is selected) + editingLayerLock->set_name(world.delayedUpdateObjectManager, nameToEdit); world.main.g.gui.set_to_layout(); } } @@ -404,9 +406,10 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { slider_scalar_field(gui, "input alpha slider", "Alpha", &alphaValToEdit, 0.0f, 1.0f, { .decimalPrecision = 2, .onEdit = [&] { - if(editingLayer) { - editingLayer->set_name(world.delayedUpdateObjectManager, nameToEdit); - editingLayer->set_alpha(layerMan, alphaValToEdit); + auto editingLayerLock = editingLayer.lock(); + if(editingLayerLock) { + editingLayerLock->set_name(world.delayedUpdateObjectManager, nameToEdit); + editingLayerLock->set_alpha(layerMan, alphaValToEdit); } } }); @@ -415,8 +418,9 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() { gui.element>("input blend mode", &blendModeValToEdit, get_blend_mode_useful_name_list(), DropdownOptions{ .width = 190.0f, .onClick = [&] { - if(editingLayer) - editingLayer->set_blend_mode(layerMan, get_blend_mode_useful_list()[blendModeValToEdit]); + auto editingLayerLock = editingLayer.lock(); + if(editingLayerLock) + editingLayerLock->set_blend_mode(layerMan, get_blend_mode_useful_list()[blendModeValToEdit]); } }); }); @@ -603,19 +607,19 @@ void DrawingProgramLayerManagerGUI::editing_layer_check() { WorldUndoManager::UndoObjectID undoID; }; - - if(editingLayer) { - const DrawingProgramLayerListItemMetaInfo& currentMetaData = editingLayer->get_metainfo(); + auto editingLayerLock = editingLayer.lock(); + if(editingLayerLock) { + const DrawingProgramLayerListItemMetaInfo& currentMetaData = editingLayerLock->get_metainfo(); if(currentMetaData != editingLayerOldMetainfo.value()) world.undo.push(std::make_unique(std::make_unique(editingLayerOldMetainfo.value()), world.undo.get_undoid_from_netid(editingLayer.get_net_id()))); } if(selectedLayerIndices.size() == 1) { editingLayer = get_layer_from_obj_index(*selectedLayerIndices.begin()); - editingLayerOldMetainfo = editingLayer->get_metainfo(); + editingLayerOldMetainfo = editingLayer.lock()->get_metainfo(); } else { - editingLayer = NetworkingObjects::NetObjTemporaryPtr(); + editingLayer.reset(); editingLayerOldMetainfo = std::nullopt; } } diff --git a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.hpp b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.hpp index e148f30..dbb75f5 100644 --- a/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.hpp +++ b/src/DrawingProgram/Layers/DrawingProgramLayerManagerGUI.hpp @@ -29,7 +29,7 @@ class DrawingProgramLayerManagerGUI { size_t blendModeValToEdit = 0; std::set selectedLayerIndices; - NetworkingObjects::NetObjTemporaryPtr editingLayer; + NetworkingObjects::NetObjWeakPtr editingLayer; std::optional editingLayerOldMetainfo; diff --git a/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp b/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp index ed76f77..09f46b7 100644 --- a/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp +++ b/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp @@ -12,6 +12,7 @@ template SelectableButton::Data selectable_button_options_to_dat SelectableButton::Data toRet; toRet.onClick = options.onClick; toRet.isSelected = options.isSelected; + toRet.instantResponse = options.instantResponse; toRet.drawType = options.drawType; toRet.onClickButton = options.onClickButton; return toRet; @@ -20,6 +21,7 @@ template SelectableButton::Data selectable_button_options_to_dat struct TextButtonOptions { SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; bool isSelected = false; + bool instantResponse = false; bool wide = false; bool centered = true; @@ -30,6 +32,7 @@ struct TextButtonOptions { struct SVGButtonOptions { SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; bool isSelected = false; + bool instantResponse = false; float size = BIG_BUTTON_SIZE; std::function onClick; @@ -39,6 +42,7 @@ struct SVGButtonOptions { struct FixedSizeColorButtonOptions { SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; bool isSelected = false; + bool instantResponse = false; bool hasAlpha = true; float size = BIG_BUTTON_SIZE; diff --git a/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp b/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp index 920655b..1c8ba27 100644 --- a/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp +++ b/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp @@ -8,7 +8,6 @@ namespace GUIStuff { namespace ElementHelpers { template TextBoxData textbox_options_to_data(const OptT& options) { TextBoxData toRet; - toRet.updateEveryEdit = options.updateEveryEdit; toRet.onEnter = options.onEnter; toRet.onEdit = options.onEdit; toRet.immutable = options.immutable; @@ -18,7 +17,6 @@ template TextBoxData textbox_options_to_data(cons } struct TextBoxOptions { - bool updateEveryEdit = true; bool immutable = false; std::function onEnter; std::function onEdit; @@ -28,7 +26,6 @@ struct TextBoxOptions { struct TextBoxScalarOptions { int decimalPrecision = 0; - bool updateEveryEdit = true; bool immutable = false; std::function onEnter; std::function onEdit; @@ -38,7 +35,6 @@ struct TextBoxScalarOptions { struct TextBoxScalarsOptions { int decimalPrecision = 0; - bool updateEveryEdit = true; bool immutable = false; std::function onEnter; std::function onEdit; @@ -48,7 +44,6 @@ struct TextBoxScalarsOptions { struct TextBoxPathOptions { std::filesystem::file_type fileTypeRestriction; - bool updateEveryEdit = true; bool immutable = false; std::function onEnter; std::function onEdit; @@ -58,7 +53,6 @@ struct TextBoxPathOptions { struct TextBoxHexColorOptions { bool hasAlpha = true; - bool updateEveryEdit = true; bool immutable = false; std::function onEnter; std::function onEdit; @@ -122,7 +116,6 @@ template void input_scalars_field(GUIManager& for(size_t i = 0; i < elemCount; i++) { gui.new_id(i, [&] { input_scalar(gui, "field", &(*val)[i], minVal, maxVal, TextBoxScalarOptions { .decimalPrecision = options.decimalPrecision, - .updateEveryEdit = options.updateEveryEdit, .immutable = options.immutable, .onEnter = [f = options.onEnter, i] { if(f) f(i); }, .onEdit = [f = options.onEdit, i] { if(f) f(i); }, diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index e41e6ae..ff94cb2 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -6,6 +6,7 @@ namespace GUIStuff { gui(initGUI) {} void Element::update() {} + void Element::deselect() {} void Element::input_paste_callback(const CustomEvents::PasteEventData& paste) { } void Element::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { } diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index aaa5ca4..dc506eb 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -23,6 +23,7 @@ namespace GUIStuff { Element* parent = nullptr; virtual void update(); + virtual void deselect(); virtual void input_paste_callback(const CustomEvents::PasteEventData& paste); virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key); diff --git a/src/GUIStuff/Elements/SelectableButton.cpp b/src/GUIStuff/Elements/SelectableButton.cpp index ec7f14c..f8835a9 100644 --- a/src/GUIStuff/Elements/SelectableButton.cpp +++ b/src/GUIStuff/Elements/SelectableButton.cpp @@ -14,6 +14,7 @@ void SelectableButton::layout(const Clay_ElementId& id, const Data& d) { SkColor4f backgroundColorHighlight; SkColor4f backgroundColor; + instantResponse = d.instantResponse; onClick = [&, this, d] { if(d.onClickButton) d.onClickButton(this); @@ -70,10 +71,16 @@ void SelectableButton::layout(const Clay_ElementId& id, const Data& d) { void SelectableButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { bool oldIsHeld = isHeld; isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down; - if(isHeld) + if(isHeld) { + if(instantResponse) { + std::cout << "INSTANT" << std::endl; + gui.set_post_callback_func(onClick); + } gui.set_to_layout(); + } else if(mouseHovering && oldIsHeld && button.button == InputManager::MouseButton::LEFT && !button.down) { - gui.set_post_callback_func(onClick); + if(!instantResponse) + gui.set_post_callback_func(onClick); gui.set_to_layout(); } else if(mouseHovering != oldIsHovering || isHeld != oldIsHeld) diff --git a/src/GUIStuff/Elements/SelectableButton.hpp b/src/GUIStuff/Elements/SelectableButton.hpp index 9745345..6ae4d73 100644 --- a/src/GUIStuff/Elements/SelectableButton.hpp +++ b/src/GUIStuff/Elements/SelectableButton.hpp @@ -21,6 +21,7 @@ class SelectableButton : public Element { struct Data { DrawType drawType = DrawType::TRANSPARENT_ALL; bool isSelected = false; + bool instantResponse = false; std::function onClick; std::function onClickButton; std::function innerContent; @@ -32,6 +33,7 @@ class SelectableButton : public Element { virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; private: + bool instantResponse = false; bool isHeld = false; bool oldIsHovering = false; std::function onClick; diff --git a/src/GUIStuff/Elements/TextBox.decl.hpp b/src/GUIStuff/Elements/TextBox.decl.hpp index 9c4fcf2..f0ae563 100644 --- a/src/GUIStuff/Elements/TextBox.decl.hpp +++ b/src/GUIStuff/Elements/TextBox.decl.hpp @@ -14,7 +14,6 @@ template struct TextBoxData { std::function(const std::string&)> fromStr; std::function toStr; bool singleLine = true; - bool updateEveryEdit = true; bool immutable = false; InputManager::TextInputProperties textInputProps; std::function onEdit; @@ -38,7 +37,9 @@ template class TextBox : public Element { TextBox(GUIManager& gui); void layout(const Clay_ElementId& id, const TextBoxData& userInfo); - virtual void update(); + void select(); + virtual void update() override; + virtual void deselect() override; virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; virtual void input_paste_callback(const CustomEvents::PasteEventData& paste) override; virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key) override; diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp index e1354d1..88e9862 100644 --- a/src/GUIStuff/Elements/TextBox.impl.hpp +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -71,6 +71,23 @@ template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputDa canvas->restore(); } +template void TextBox::select() { + if(!is_selected()) { + SCollision::AABB rect = {(boundingBox.value().min + gui.io.windowPos) * gui.io.guiScaleMultiplier, (boundingBox.value().max + gui.io.windowPos) * gui.io.guiScaleMultiplier}; + unsigned id = gui.io.input->set_text_box_front(rect, userInfo.textInputProps); + edit = TextEditData(id, textbox, cur); + } +} + +template void TextBox::deselect() { + if(is_selected()) { + gui.io.input->remove_text_box(edit.value().textboxInputID); + if(userInfo.onDeselect) userInfo.onDeselect(); + reset_textbox_text(); + edit = std::nullopt; + } +} + template bool TextBox::is_selected() const { return edit.has_value(); } @@ -101,7 +118,7 @@ template void TextBox::after_text_input_callback() { if(userInfo.immutable) cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, userInfo.toStr(*userInfo.data)); else { - if(userInfo.updateEveryEdit && update_data() && userInfo.onEdit) + if(update_data() && userInfo.onEdit) gui.set_post_callback_func_high_priority(userInfo.onEdit); } } @@ -114,37 +131,22 @@ template void TextBox::input_text_callback(const InputManager::T } template void TextBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { - auto& io = gui.io; - - isHeld = false; - if(button.button == InputManager::MouseButton::LEFT && boundingBox.has_value()) { if(button.down) { if(mouseHovering) { if(!is_selected()) { - 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); - edit = TextEditData(id, textbox, cur); + select(); gui.set_post_callback_func_high_priority([&] { if(userInfo.onSelect) userInfo.onSelect(); }); } - isHeld = true; - textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, isHeld, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); - } - else if(is_selected()) { - gui.io.input->remove_text_box(edit.value().textboxInputID); - gui.set_post_callback_func_high_priority([&] { - if(update_data()) - if(userInfo.onEdit) userInfo.onEdit(); - if(userInfo.onDeselect) userInfo.onDeselect(); - }); - reset_textbox_text(); - edit = std::nullopt; + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, true, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); } + else if(is_selected()) + deselect(); } else { - textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, isHeld, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, false, gui.io.input->key(InputManager::KEY_GENERIC_LSHIFT).held); } } } diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 9141a93..00e1419 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -482,6 +482,11 @@ bool GUIManager::cursor_obstructed() const { return cursorObstructed; } +void GUIManager::deselect_all() { + for(ElementContainer* e : orderedElements) + e->elem->deselect(); +} + void GUIManager::input_paste_callback(const CustomEvents::PasteEventData& paste) { for(ElementContainer* e : orderedElements) e->elem->input_paste_callback(paste); diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index e7cf63e..27c54a2 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -43,8 +43,8 @@ class GUIManager { template ElementType* element(const char* id, const Args&... a) { push_id(id); - Element* oldParent = parentElement; ElementType* elem = insert_element(); + Element* oldParent = parentElement; elem->parent = parentElement; parentElement = elem; @@ -61,9 +61,8 @@ class GUIManager { template ElementType* clipping_element(const char* id, const Args&... a) { push_id(id); - - Element* oldParent = parentElement; ElementType* elem = insert_element(); + Element* oldParent = parentElement; elem->parent = parentElement; parentElement = elem; @@ -83,11 +82,12 @@ class GUIManager { clippingRegion = oldClippingRegion; parentElement = oldParent; - pop_id(); return elem; } + void deselect_all(); + DefaultStringArena strArena; void input_paste_callback(const CustomEvents::PasteEventData& paste); diff --git a/src/InputManager.cpp b/src/InputManager.cpp index c5972b1..e2afb73 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -332,18 +332,18 @@ const InputManager::KeyData& InputManager::key(KeyCode kCode) { return keys[kCode]; } -void InputManager::set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode) { +void InputManager::set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode, bool acceptingTextInput) { auto& k = keys[kCode]; k.held = true; - if(text.is_accepting_input()) + if(acceptingTextInput) 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) { +void InputManager::set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode, bool acceptingTextInput) { keys[kCode].held = false; - if(text.is_accepting_input()) + if(acceptingTextInput) 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}); @@ -852,76 +852,78 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { return; } + bool acceptingTextInput = text.is_accepting_input(); + switch(kPress) { case SDLK_UP: - set_key_down(e, KEY_GENERIC_UP); + set_key_down(e, KEY_GENERIC_UP, acceptingTextInput); break; case SDLK_DOWN: - set_key_down(e, KEY_GENERIC_DOWN); + set_key_down(e, KEY_GENERIC_DOWN, acceptingTextInput); break; case SDLK_LEFT: - set_key_down(e, KEY_GENERIC_LEFT); + set_key_down(e, KEY_GENERIC_LEFT, acceptingTextInput); break; case SDLK_RIGHT: - set_key_down(e, KEY_GENERIC_RIGHT); + set_key_down(e, KEY_GENERIC_RIGHT, acceptingTextInput); break; case SDLK_BACKSPACE: - set_key_down(e, KEY_TEXT_BACKSPACE); + set_key_down(e, KEY_TEXT_BACKSPACE, acceptingTextInput); break; case SDLK_DELETE: - set_key_down(e, KEY_TEXT_DELETE); + set_key_down(e, KEY_TEXT_DELETE, acceptingTextInput); break; case SDLK_HOME: - set_key_down(e, KEY_TEXT_HOME); + set_key_down(e, KEY_TEXT_HOME, acceptingTextInput); break; case SDLK_END: - set_key_down(e, KEY_TEXT_END); + set_key_down(e, KEY_TEXT_END, acceptingTextInput); break; case SDLK_LSHIFT: - set_key_down(e, KEY_GENERIC_LSHIFT); + set_key_down(e, KEY_GENERIC_LSHIFT, acceptingTextInput); break; case SDLK_LALT: - set_key_down(e, KEY_GENERIC_LALT); + set_key_down(e, KEY_GENERIC_LALT, acceptingTextInput); break; case SDLK_LCTRL: - set_key_down(e, KEY_GENERIC_LCTRL); + set_key_down(e, KEY_GENERIC_LCTRL, acceptingTextInput); break; case SDLK_LMETA: - set_key_down(e, KEY_GENERIC_LMETA); + set_key_down(e, KEY_GENERIC_LMETA, acceptingTextInput); 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)) - set_key_down(e, KEY_TEXT_COPY); + set_key_down(e, KEY_TEXT_COPY, acceptingTextInput); break; case SDLK_X: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) - set_key_down(e, KEY_TEXT_CUT); + set_key_down(e, KEY_TEXT_CUT, acceptingTextInput); break; case SDLK_V: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) - set_key_down(e, KEY_TEXT_PASTE); + set_key_down(e, KEY_TEXT_PASTE, acceptingTextInput); break; case SDLK_A: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) - set_key_down(e, KEY_TEXT_SELECTALL); + set_key_down(e, KEY_TEXT_SELECTALL, acceptingTextInput); break; case SDLK_Z: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) - set_key_down(e, KEY_TEXT_UNDO); + set_key_down(e, KEY_TEXT_UNDO, acceptingTextInput); break; case SDLK_R: if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL)) - set_key_down(e, KEY_TEXT_REDO); + set_key_down(e, KEY_TEXT_REDO, acceptingTextInput); break; case SDLK_RETURN: - set_key_down(e, KEY_GENERIC_ENTER); + set_key_down(e, KEY_GENERIC_ENTER, acceptingTextInput); break; case SDLK_TAB: - set_key_down(e, KEY_TEXT_TAB); + set_key_down(e, KEY_TEXT_TAB, acceptingTextInput); break; case SDLK_ESCAPE: - set_key_down(e, KEY_GENERIC_ESCAPE); + set_key_down(e, KEY_GENERIC_ESCAPE, acceptingTextInput); break; default: break; @@ -929,7 +931,7 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) { auto f = keyAssignments.find({make_generic_key_mod(kMod), kPress}); if(f != keyAssignments.end()) - set_key_down(e, f->second); + set_key_down(e, f->second, acceptingTextInput); } void InputManager::call_paste(CustomEvents::PasteEventDataType type, const InputManagerCallPasteInfo& info) { @@ -968,66 +970,68 @@ bool InputManager::ctrl_or_meta_held() { void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { auto kPress = e.key; + bool acceptingTextInput = text.is_accepting_input(); + switch(kPress) { case SDLK_UP: - set_key_up(e, KEY_GENERIC_UP); + set_key_up(e, KEY_GENERIC_UP, acceptingTextInput); break; case SDLK_DOWN: - set_key_up(e, KEY_GENERIC_DOWN); + set_key_up(e, KEY_GENERIC_DOWN, acceptingTextInput); break; case SDLK_LEFT: - set_key_up(e, KEY_GENERIC_LEFT); + set_key_up(e, KEY_GENERIC_LEFT, acceptingTextInput); break; case SDLK_RIGHT: - set_key_up(e, KEY_GENERIC_RIGHT); + set_key_up(e, KEY_GENERIC_RIGHT, acceptingTextInput); break; case SDLK_BACKSPACE: - set_key_up(e, KEY_TEXT_BACKSPACE); + set_key_up(e, KEY_TEXT_BACKSPACE, acceptingTextInput); break; case SDLK_DELETE: - set_key_up(e, KEY_TEXT_DELETE); + set_key_up(e, KEY_TEXT_DELETE, acceptingTextInput); break; case SDLK_HOME: - set_key_up(e, KEY_TEXT_HOME); + set_key_up(e, KEY_TEXT_HOME, acceptingTextInput); break; case SDLK_END: - set_key_up(e, KEY_TEXT_END); + set_key_up(e, KEY_TEXT_END, acceptingTextInput); break; case SDLK_LSHIFT: - set_key_up(e, KEY_GENERIC_LSHIFT); + set_key_up(e, KEY_GENERIC_LSHIFT, acceptingTextInput); break; case SDLK_LALT: - set_key_up(e, KEY_GENERIC_LALT); + set_key_up(e, KEY_GENERIC_LALT, acceptingTextInput); break; case SDLK_LCTRL: - set_key_up(e, KEY_GENERIC_LCTRL); + set_key_up(e, KEY_GENERIC_LCTRL, acceptingTextInput); break; case SDLK_C: - set_key_up(e, KEY_TEXT_COPY); + set_key_up(e, KEY_TEXT_COPY, acceptingTextInput); break; case SDLK_X: - set_key_up(e, KEY_TEXT_CUT); + set_key_up(e, KEY_TEXT_CUT, acceptingTextInput); break; case SDLK_V: - set_key_up(e, KEY_TEXT_PASTE); + set_key_up(e, KEY_TEXT_PASTE, acceptingTextInput); break; case SDLK_A: - set_key_up(e, KEY_TEXT_SELECTALL); + set_key_up(e, KEY_TEXT_SELECTALL, acceptingTextInput); break; case SDLK_Z: - set_key_up(e, KEY_TEXT_UNDO); + set_key_up(e, KEY_TEXT_UNDO, acceptingTextInput); break; case SDLK_R: - set_key_up(e, KEY_TEXT_REDO); + set_key_up(e, KEY_TEXT_REDO, acceptingTextInput); break; case SDLK_RETURN: - set_key_up(e, KEY_GENERIC_ENTER); + set_key_up(e, KEY_GENERIC_ENTER, acceptingTextInput); break; case SDLK_TAB: - set_key_up(e, KEY_TEXT_TAB); + set_key_up(e, KEY_TEXT_TAB, acceptingTextInput); break; case SDLK_ESCAPE: - set_key_up(e, KEY_GENERIC_ESCAPE); + set_key_up(e, KEY_GENERIC_ESCAPE, acceptingTextInput); break; default: break; @@ -1035,7 +1039,7 @@ void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) { 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); + set_key_up(e, p.second, acceptingTextInput); } void InputManager::Mouse::set_pos(const Vector2f& newPos) { diff --git a/src/InputManager.hpp b/src/InputManager.hpp index 5618e66..eaeda74 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -252,8 +252,8 @@ struct InputManager { bool ctrl_or_meta_held(); uint32_t make_generic_key_mod(SDL_Keymod m); - void set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode); - void set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode); + void set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode, bool acceptingTextInput); + void set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode, bool acceptingTextInput); std::vector get_multiple_finger_positions(); std::vector get_multiple_finger_motions(); void touch_finger_do_mouse_down(); diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index 9e02a75..3624b64 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -370,6 +370,11 @@ void MainProgram::input_key_callback(const InputManager::KeyCallbackArgs& key) { toolbar.save_as_func(); break; } + case InputManager::KEY_OPEN_CHAT: { + if(key.down && !key.repeat) + toolbar.open_chatbox(); + break; + } } g.input_key_callback(key); if(world) @@ -378,6 +383,13 @@ void MainProgram::input_key_callback(const InputManager::KeyCallbackArgs& key) { } void MainProgram::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { + switch(key.key) { + case InputManager::KEY_GENERIC_ESCAPE: { + if(key.down && !key.repeat) + toolbar.close_chatbox(); + break; + } + } g.input_text_key_callback(key); if(world) world->input_text_key_callback(key); diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index eef8cc9..ef1ed79 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -16,6 +16,7 @@ #include "MainProgram.hpp" #include "InputManager.hpp" #include "ResourceDisplay/ImageResourceDisplay.hpp" +#include "RichText/TextStyleModifier.hpp" #include "VersionConstants.hpp" #include "World.hpp" #include @@ -52,6 +53,7 @@ #include "GUIStuff/Elements/DropDown.hpp" #include "GUIStuff/Elements/SVGIcon.hpp" #include "GUIStuff/Elements/MovableTabList.hpp" +#include "GUIStuff/Elements/TextParagraph.hpp" #define UPDATE_DOWNLOAD_URL "https://infinipaint.com/download.html" #define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersion.txt" @@ -257,6 +259,31 @@ void Toolbar::update() { } return false; }); + if(!chatboxOpen) { + for(auto& chatMessage : main.world->chatMessages) { + bool wasShown = chatMessage.time < ChatMessage::DISPLAY_TIME; + chatMessage.time.update_time_since(); + bool isShown = chatMessage.time < ChatMessage::DISPLAY_TIME; + bool isFading = chatMessage.time >= ChatMessage::FADE_START_TIME; + if((isFading && isShown) || (wasShown && !isShown)) + main.g.gui.set_to_layout(); + } + } +} + +void Toolbar::open_chatbox() { + if(!chatboxOpen) { + chatMessageInput.clear(); + chatboxOpen = true; + main.g.gui.set_to_layout(); + } +} + +void Toolbar::close_chatbox() { + if(chatboxOpen) { + chatboxOpen = false; + main.g.gui.set_to_layout(); + } } void Toolbar::layout_run() { @@ -1021,149 +1048,139 @@ void Toolbar::layer_menu(Element* layerMenuButton) { }); } -std::unique_ptr Toolbar::build_paragraph_from_chat_message(const ChatMessage& message, float alpha) { +RichText::TextData Toolbar::build_paragraph_from_chat_message(const ChatMessage& message, float alpha) { auto& gui = main.g.gui; auto& io = gui.io; - skia::textlayout::ParagraphStyle pStyle; - pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); - skia::textlayout::TextStyle tStyle; - tStyle.setFontSize(io.fontSize); - tStyle.setFontFamilies(main.fonts->get_default_font_families()); - tStyle.setFontStyle(SkFontStyle::Bold()); - tStyle.setForegroundColor(SkPaint{color_mul_alpha(message.type == ChatMessage::JOIN ? io.theme->warningColor : io.theme->frontColor1, alpha)}); - pStyle.setTextStyle(tStyle); + RichText::TextData toRet; - skia::textlayout::ParagraphBuilderImpl a(pStyle, io.fonts->collection, SkUnicodes::ICU::Make()); - if(message.type == ChatMessage::JOIN) { - std::string messageName = message.name + " "; - a.addText(messageName.c_str(), messageName.length()); - } - else { - std::string messageName = "[" + message.name + "] "; - a.addText(messageName.c_str(), messageName.length()); - } + auto& par = toRet.paragraphs.emplace_back(); - tStyle.setFontStyle(SkFontStyle::Normal()); - a.pushStyle(tStyle); - a.addText(message.message.c_str(), message.message.length()); + RichText::PositionedTextStyleMod& positionedModInit = toRet.tStyleMods.emplace_back(); + positionedModInit.pos = {0, 0}; + positionedModInit.mods[RichText::TextStyleModifier::ModifierType::WEIGHT] = std::make_shared(SkFontStyle::Weight::kBold_Weight); + positionedModInit.mods[RichText::TextStyleModifier::ModifierType::COLOR] = std::make_shared(convert_vec4(color_mul_alpha(message.type == ChatMessage::JOIN ? io.theme->warningColor : io.theme->frontColor1, alpha))); + if(message.type == ChatMessage::JOIN) + par.text += message.name + " "; + else + par.text += "[" + message.name + "] "; - return a.Build(); + RichText::PositionedTextStyleMod& positionedModMessage = toRet.tStyleMods.emplace_back(); + positionedModMessage.pos = {0, par.text.size()}; + positionedModMessage.mods[RichText::TextStyleModifier::ModifierType::WEIGHT] = std::make_shared(SkFontStyle::Weight::kNormal_Weight); + par.text += message.message; + + return toRet; } void Toolbar::chat_box() { - //constexpr float CHATBOX_WIDTH = 700; - //if(main.world->netObjMan.is_connected()) { - // Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT CHAT BOX OPEN BUTTON"); - // CLAY(localId, { - // .layout = { - // .layoutDirection = CLAY_LEFT_TO_RIGHT - // }, - // .floating = {.offset = {static_cast(io.theme->padding1), -static_cast(io.theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - // }) { - // gui.obstructing_window(localId); - // gui.push_id("chat box open button"); - // if(gui.svg_icon_button("Chat Open Button", "data/icons/chat.svg", chatBoxState != CHATBOXSTATE_CLOSE)) { - // if(chatBoxState == CHATBOXSTATE_CLOSE) - // chatBoxState = CHATBOXSTATE_JUSTOPEN; - // else - // chatBoxState = CHATBOXSTATE_CLOSE; - // } - // gui.pop_id(); - // } - //} - //Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT CHAT BOX OPEN"); - //CLAY(localId, { - // .layout = { - // .sizing = {.width = CLAY_SIZING_FIXED(CHATBOX_WIDTH), .height = CLAY_SIZING_FIT(0) }, - // .childGap = 0, - // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM}, - // .layoutDirection = CLAY_TOP_TO_BOTTOM - // }, - // .floating = {.offset = {60 + static_cast(io.theme->padding1), -static_cast(io.theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - //}) { - // gui.obstructing_window(localId); - // gui.push_id("chat box"); - // if(chatBoxState == CHATBOXSTATE_JUSTOPEN || chatBoxState == CHATBOXSTATE_OPEN) { - // gui.push_id("messages"); - // CLAY_AUTO_ID({ - // .layout = { - // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - // .padding = CLAY_PADDING_ALL(0), - // .childGap = 0, - // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM}, - // .layoutDirection = CLAY_TOP_TO_BOTTOM - // }, - // .backgroundColor = convert_vec4(io.theme->backColor1) - // }) { - // int id = 0; - // for(auto& chatMessage : main.world->chatMessages | std::views::reverse) { - // gui.push_id(id); - // gui.text_paragraph("text", build_paragraph_from_chat_message(chatMessage, 1.0f), CHATBOX_WIDTH); - // gui.pop_id(); - // id++; - // } - // } - // gui.pop_id(); + auto& gui = main.g.gui; + auto& io = gui.io; - // gui.left_to_right_line_layout([&]() { - // gui.input_text("message input", &chatMessageInput); - // if(gui.text_button("send button", "Send", false, true)) { - // if(!chatMessageInput.empty()) - // main.world->send_chat_message(chatMessageInput); - // } - // }); - // gui.push_id("message input"); - // GUIStuff::GUIManager::ElementContainer& a = gui.elements[gui.idStack]; - // GUIStuff::TextBox& t = *(GUIStuff::TextBox*)a.elem.get(); - // gui.pop_id(); - // if(chatBoxState == CHATBOXSTATE_JUSTOPEN) { - // t.selection.selected = true; - // chatBoxState = CHATBOXSTATE_OPEN; - // } - // if(io.key.escape) - // t.selection.selected = false; - // if(io.key.enter) { - // main.world->send_chat_message(chatMessageInput); - // t.selection.selected = false; - // } - // if(!t.selection.selected) { - // chatMessageInput.clear(); - // chatBoxState = CHATBOXSTATE_CLOSE; - // } - // } - // else { - // constexpr float DISPLAY_TIME = 8.0f; - // constexpr float FADE_START_TIME = 7.0f; - // gui.push_id("messages popup"); - // int id = 0; - // for(auto& chatMessage : main.world->chatMessages | std::views::reverse) { - // chatMessage.time.update_time_since(); - // if(chatMessage.time < DISPLAY_TIME) { - // float a = 1.0f - lerp_time(chatMessage.time, DISPLAY_TIME, FADE_START_TIME); - // Clay_ElementId elemId = CLAY_IDI_LOCAL("CHAT MESSAGE ID", id); - // CLAY(elemId, { - // .layout = { - // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - // .padding = CLAY_PADDING_ALL(0), - // .childGap = 0, - // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - // .layoutDirection = CLAY_TOP_TO_BOTTOM - // }, - // .backgroundColor = convert_vec4(color_mul_alpha(io.theme->backColor1, a)), - // }) { - // gui.push_id(id); - // gui.obstructing_window(elemId); - // gui.text_paragraph("text", build_paragraph_from_chat_message(chatMessage, a), CHATBOX_WIDTH); - // gui.pop_id(); - // } - // } - // id++; - // } - // gui.pop_id(); - // } - // gui.pop_id(); - //} + constexpr float CHATBOX_WIDTH = 700; + if(main.world->netObjMan.is_connected()) { + gui.element("Infinipaint chat box open button", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .floating = {.offset = {static_cast(io.theme->padding1), -static_cast(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + }) { + svg_icon_button(gui, "Chat open button", "data/icons/chat.svg", { + .onClick = [&] { + chatboxOpen = !chatboxOpen; + chatMessageInput.clear(); + } + }); + } + }); + } + gui.element("Infinipaint chat box", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(CHATBOX_WIDTH), .height = CLAY_SIZING_FIT(0) }, + .childGap = 0, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .floating = {.offset = {60 + static_cast(io.theme->padding1), -static_cast(io.theme->padding1)}, .zIndex = gui.get_z_index(), .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_BOTTOM, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + }) { + if(chatboxOpen) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_BOTTOM}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(io.theme->backColor1) + }) { + int id = 0; + for(auto& chatMessage : main.world->chatMessages | std::views::reverse) { + gui.new_id(id++, [&] { + gui.element("text", TextParagraph::Data{ + .text = build_paragraph_from_chat_message(chatMessage, 1.0f), + .width = CHATBOX_WIDTH + }); + }); + } + } + + left_to_right_line_layout(gui, [&] { + input_text(gui, "message input", &chatMessageInput, { + .onEnter = [&] { + if(!chatMessageInput.empty()) + main.world->send_chat_message(chatMessageInput); + chatboxOpen = false; + }, + .onDeselect = [&] { + chatboxOpen = false; + gui.set_to_layout(); + } + })->select(); + text_button(gui, "send button", "Send", { + .instantResponse = true, + .onClick = [&] { + if(!chatMessageInput.empty()) + main.world->send_chat_message(chatMessageInput); + chatboxOpen = false; + } + }); + }); + } + else { + gui.new_id("Message popups", [&] { + int id = 0; + for(auto& chatMessage : main.world->chatMessages | std::views::reverse) { + chatMessage.time.update_time_since(); + if(chatMessage.time < ChatMessage::DISPLAY_TIME) { + gui.new_id(id++, [&] { + float a = 1.0f - lerp_time(chatMessage.time, ChatMessage::DISPLAY_TIME, ChatMessage::FADE_START_TIME); + gui.element("Chat message", [&](LayoutElement*, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(color_mul_alpha(io.theme->backColor1, a)), + }) { + gui.element("message", TextParagraph::Data{ + .text = build_paragraph_from_chat_message(chatMessage, a), + .width = CHATBOX_WIDTH + }); + } + }); + }); + } + } + }); + } + } + }); } void Toolbar::global_log() { @@ -2136,7 +2153,7 @@ void Toolbar::file_picker_gui() { } }); input_path_field(gui, "file picker path", "Path", &filePicker.currentSearchPath, { - .updateEveryEdit = false, + .fileTypeRestriction = std::filesystem::file_type::directory, .onEdit = [&] { file_picker_gui_refresh_entries(); } diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index d93b165..3c3553d 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -1,5 +1,6 @@ #pragma once #include "GUIStuff/Elements/ScrollArea.hpp" +#include "GUIStuff/Elements/TextParagraph.hpp" #include "GUIStuff/GUIManager.hpp" #include "DrawData.hpp" #include "Helpers/FileDownloader.hpp" @@ -26,6 +27,8 @@ class Toolbar { }; struct ChatMessage { + static constexpr float DISPLAY_TIME = 8.0f; + static constexpr float FADE_START_TIME = 7.0f; std::string name; std::string message; enum Type { @@ -73,6 +76,9 @@ class Toolbar { void load_palettes(); void load_licenses(); + void open_chatbox(); + void close_chatbox(); + GUIStuff::Element* colorLeftButton; Vector4f* colorLeft = nullptr; ColorSelectorData colorLeftData; @@ -171,11 +177,7 @@ class Toolbar { float finalCalculatedGuiScale = 1.0f; - enum { - CHATBOXSTATE_OPEN, - CHATBOXSTATE_JUSTOPEN, - CHATBOXSTATE_CLOSE - } chatBoxState = CHATBOXSTATE_CLOSE; + bool chatboxOpen = false; enum OptionsMenuType { HOST_MENU, @@ -226,7 +228,7 @@ class Toolbar { }; static NativeFilePicker nativeFilePicker; - std::unique_ptr build_paragraph_from_chat_message(const ChatMessage& message, float alpha); + RichText::TextData build_paragraph_from_chat_message(const ChatMessage& message, float alpha); void load_icons_at(const std::filesystem::path& pathToLoad);