From 10217e97f46c5c46152e0b6d65ab3af407764ec2 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Wed, 1 Apr 2026 21:49:44 +0300 Subject: [PATCH] Fixed file picker and scroll area clipping region --- src/FileHelpers.cpp | 3 +++ .../ElementHelpers/ScrollAreaHelpers.cpp | 2 +- src/GUIStuff/Elements/Element.cpp | 7 +++++- src/GUIStuff/Elements/Element.hpp | 2 ++ src/GUIStuff/Elements/SelectableButton.cpp | 2 +- src/GUIStuff/Elements/TextBox.decl.hpp | 3 ++- src/GUIStuff/Elements/TextBox.impl.hpp | 21 ++++++++++-------- src/GUIStuff/GUIManager.cpp | 9 +++++--- src/GUIStuff/GUIManager.hpp | 22 +++++++++++++++++++ src/RichText/TextBox.cpp | 1 + src/Toolbar.cpp | 17 +++++++++----- src/Toolbar.hpp | 3 ++- 12 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/FileHelpers.cpp b/src/FileHelpers.cpp index 2aed12b..9455f36 100644 --- a/src/FileHelpers.cpp +++ b/src/FileHelpers.cpp @@ -4,6 +4,9 @@ std::filesystem::path force_extension_on_path(std::filesystem::path p, const std::string& extensions) { std::vector extensionList = split_string_by_token(extensions, ";"); + std::erase(extensionList, "*"); + if(extensionList.empty()) + return p; for(std::string& s : extensionList) { std::transform(s.begin(), s.end(), s.begin(), ::tolower); s.insert(0, "."); diff --git a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp index 37291f0..787ff5e 100644 --- a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp +++ b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp @@ -4,7 +4,7 @@ namespace GUIStuff { namespace ElementHelpers { void scroll_area_many_entries(GUIManager& gui, const char* id, const ScrollBarManyEntriesOptions& options) { - gui.element(id, ScrollArea::Options{ + gui.clipping_element(id, ScrollArea::Options{ .scrollVertical = true, .clipHorizontal = options.clipHorizontal, .clipVertical = true, diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index 59df8fc..e9651a1 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -20,6 +20,10 @@ namespace GUIStuff { boundingBox = std::nullopt; } + void Element::set_parent_clipping_region(const std::optional>& bb) { + parentClippingRegion = bb; + } + const std::optional>& Element::get_bb() const { return boundingBox; } @@ -27,7 +31,8 @@ namespace GUIStuff { bool Element::collides_with_point(const Vector2f& p) const { if(!boundingBox.has_value()) return false; - return SCollision::collide(p, boundingBox.value()); + bool inClippingRegion = !parentClippingRegion.has_value() || SCollision::collide(p, parentClippingRegion.value()); + return inClippingRegion && SCollision::collide(p, boundingBox.value()); } SCollision::AABB Element::get_bb_from_command(Clay_RenderCommand* command) { diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index ba2a6ca..15217a0 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -12,6 +12,7 @@ namespace GUIStuff { virtual bool collides_with_point(const Vector2f& p) const; void set_bounding_box_from_elem_data(const Clay_ElementData& elemData); + void set_parent_clipping_region(const std::optional>& bb); const std::optional>& get_bb() const; virtual ~Element() = default; @@ -27,6 +28,7 @@ namespace GUIStuff { protected: static SCollision::AABB get_bb_from_command(Clay_RenderCommand* command); std::optional> boundingBox; + std::optional> parentClippingRegion; GUIManager& gui; }; diff --git a/src/GUIStuff/Elements/SelectableButton.cpp b/src/GUIStuff/Elements/SelectableButton.cpp index f86c160..7c794da 100644 --- a/src/GUIStuff/Elements/SelectableButton.cpp +++ b/src/GUIStuff/Elements/SelectableButton.cpp @@ -69,7 +69,7 @@ bool SelectableButton::input_mouse_button_callback(const InputManager::MouseButt isHovering = mouseHovering; isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down; if(isHeld) { - gui.set_post_callback_func([&]{onClick();}); + gui.set_post_callback_func([&]{ if(onClick) onClick(); }); gui.set_to_layout(); } else if(isHovering != oldIsHovering || isHeld != oldIsHeld) diff --git a/src/GUIStuff/Elements/TextBox.decl.hpp b/src/GUIStuff/Elements/TextBox.decl.hpp index 2ac1172..94a6514 100644 --- a/src/GUIStuff/Elements/TextBox.decl.hpp +++ b/src/GUIStuff/Elements/TextBox.decl.hpp @@ -31,7 +31,8 @@ template class TextBox : public Element { virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; ~TextBox(); private: - void init_textbox(UpdateInputData& io, bool forceTextUpdate); + void init_textbox(UpdateInputData& io); + void reset_textbox_text(); bool update_data(); std::optional oldData; diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp index 33401df..cd81486 100644 --- a/src/GUIStuff/Elements/TextBox.impl.hpp +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -8,7 +8,10 @@ template TextBox::TextBox(GUIManager& gui): Element(gui) {} template void TextBox::layout(const Clay_ElementId& id, const TextBoxData& userInfo) { auto& io = gui.io; this->userInfo = userInfo; - init_textbox(io, false); + init_textbox(io); + if(!oldData.has_value() || oldData.value() != *userInfo.data) + reset_textbox_text(); + CLAY(id, { .layout = { .sizing = {.width = CLAY_SIZING_GROW(static_cast(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast(io.fontSize * 1.25f))} @@ -73,7 +76,7 @@ template bool TextBox::input_mouse_button_callback(const InputMa gui.io.input->remove_rich_text_box_input(textbox); if(update_data()) gui.set_post_callback_func_high_priority(userInfo.onEdit); - init_textbox(gui.io, true); + reset_textbox_text(); isSelected = false; } } @@ -96,12 +99,12 @@ template void TextBox::input_key_callback(const InputManager::Ke gui.set_post_callback_func_high_priority([&, success] { if(success && userInfo.onEdit) userInfo.onEdit(); if(userInfo.onEnter) userInfo.onEnter(); - init_textbox(gui.io, true); + reset_textbox_text(); }); } } -template void TextBox::init_textbox(UpdateInputData& io, bool forceTextUpdate) { +template void TextBox::init_textbox(UpdateInputData& io) { if(!textbox) { textbox = std::make_shared(); textbox->set_width(std::numeric_limits::max()); @@ -111,11 +114,6 @@ template void TextBox::init_textbox(UpdateInputData& io, bool fo rect = std::make_shared>(); } - if(forceTextUpdate || !oldData.has_value() || oldData.value() != *userInfo.data) { - textbox->clear_text(); - cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, userInfo.toStr(*userInfo.data)); - } - textbox->onUserTextEdit = [&] { if(userInfo.immutable) cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, userInfo.toStr(*userInfo.data)); @@ -126,6 +124,11 @@ template void TextBox::init_textbox(UpdateInputData& io, bool fo }; } +template void TextBox::reset_textbox_text() { + textbox->set_string(userInfo.toStr(*userInfo.data)); + cur->pos = cur->selectionBeginPos = cur->selectionEndPos = {0, 0}; +} + template bool TextBox::update_data() { std::optional dataToAssign = userInfo.fromStr(textbox->get_string()); if(dataToAssign.has_value()) { diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index adc0120..a6e1226 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -243,7 +243,7 @@ void GUIManager::layout_if_necessary() { } void GUIManager::layout() { - constexpr int LAYOUT_RUN_COUNT = 3; + constexpr int LAYOUT_RUN_COUNT = 4; for(int i = 0; i < LAYOUT_RUN_COUNT; i++) single_layout_run(); } @@ -297,10 +297,13 @@ void GUIManager::new_id(int64_t id, const std::function& f) { } void GUIManager::set_z_index(int16_t z, const std::function& f) { + auto oldClippingRegion = clippingRegion; + clippingRegion = std::nullopt; int16_t oldZIndex = zIndex; zIndex = z; f(); zIndex = oldZIndex; + clippingRegion = oldClippingRegion; } int16_t GUIManager::get_z_index() { @@ -333,8 +336,8 @@ void GUIManager::run_post_callback_func() { if(postCallbackFunc) { postCallbackFunc(); postCallbackFunc = nullptr; - postCallbackFuncIsHighPriority = false; } + postCallbackFuncIsHighPriority = false; } bool GUIManager::cursor_obstructed() const { @@ -366,7 +369,7 @@ void GUIManager::mouse_callback(const Vector2f& mousePos, const std::functionelem->zIndex) && e->elem->get_bb().has_value() && SCollision::collide(e->elem->get_bb().value(), mousePos)) { + if((!cursorObstructed || zIndexObstructed == e->elem->zIndex) && e->elem->collides_with_point(mousePos)) { zIndexObstructed = e->elem->zIndex; cursorObstructed = true; f(e, true); diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index f48d748..16ae6a6 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -39,12 +39,32 @@ class GUIManager { push_id(id); ElementType* elem = insert_element(); Clay_ElementId clayId = strArena.elem_id_from_id_stack(idStack); + elem->set_parent_clipping_region(clippingRegion); elem->set_bounding_box_from_elem_data(Clay_GetElementData(clayId)); elem->layout(clayId, a...); pop_id(); return elem; } + template ElementType* clipping_element(const char* id, const Args&... a) { + push_id(id); + ElementType* elem = insert_element(); + Clay_ElementId clayId = strArena.elem_id_from_id_stack(idStack); + elem->set_bounding_box_from_elem_data(Clay_GetElementData(clayId)); + elem->set_parent_clipping_region(clippingRegion); + auto oldClippingRegion = clippingRegion; + if(elem->get_bb().has_value()) { + if(clippingRegion.has_value()) + clippingRegion = clippingRegion.value().get_intersection_between_aabbs(elem->get_bb().value()); + else + clippingRegion = elem->get_bb(); + } + elem->layout(clayId, a...); + clippingRegion = oldClippingRegion; + pop_id(); + return elem; + } + DefaultStringArena strArena; void input_key_callback(const InputManager::KeyCallbackArgs& key); @@ -84,6 +104,8 @@ class GUIManager { void pop_id(); int16_t zIndex = 0; + std::optional> clippingRegion; + std::function postCallbackFunc; bool postCallbackFuncIsHighPriority; Clay_Context* clayInstance; diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index fd97557..8fe1777 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -477,6 +477,7 @@ void TextBox::clear_text() { paragraphs.clear(); tStyleMods.clear(); paragraphs.emplace_back(); + needsRebuild = true; } void TextBox::set_string(const std::string& str) { diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 3705e46..3e4c9e0 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -150,6 +150,7 @@ void Toolbar::open_file_selector(const std::string& filePickerName, const std::v filePicker.filePickerWindowName = filePickerName; filePicker.postSelectionFunc = postSelectionFunc; filePicker.fileName = ""; + filePicker.isSaving = isSaving; file_picker_gui_refresh_entries(); } } @@ -299,7 +300,7 @@ void Toolbar::close_popup_gui() { }); center_obstructing_window_gui("Close program popup GUI", CLAY_SIZING_FIT(0), CLAY_SIZING_FIT(0, 600), [&] { text_label(gui, "Files may contain unsaved changes"); - gui.element("close file popup gui scroll area", ScrollArea::Options{ + gui.clipping_element("close file popup gui scroll area", ScrollArea::Options{ .scrollVertical = true, .clipVertical = true, .showScrollbarY = true, @@ -1680,7 +1681,7 @@ void Toolbar::general_settings_inner_gui() { } }) { auto general_scroll_area = [&](const char* id, const std::function& innerContent) { - gui.element("general settings scroll area", ScrollArea::Options{ + gui.clipping_element("general settings scroll area", ScrollArea::Options{ .scrollVertical = true, .clipHorizontal = false, .clipVertical = true, @@ -1925,7 +1926,7 @@ void Toolbar::about_menu_inner_gui() { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - gui.element("About Menu Selector Scroll Area", ScrollArea::Options{ + gui.clipping_element("About Menu Selector Scroll Area", ScrollArea::Options{ .scrollVertical = true, .clipHorizontal = false, .clipVertical = true, @@ -1968,7 +1969,7 @@ void Toolbar::about_menu_inner_gui() { } }); - gui.element("About Menu Text Scroll Area", ScrollArea::Options{ + gui.clipping_element("About Menu Text Scroll Area", ScrollArea::Options{ .scrollVertical = true, .clipHorizontal = false, .clipVertical = true, @@ -2060,7 +2061,9 @@ void Toolbar::file_picker_gui_refresh_entries() { void Toolbar::file_picker_gui_done() { if(!filePicker.fileName.empty()) { std::filesystem::path pathToRet = filePicker.currentSearchPath / filePicker.fileName; - filePicker.postSelectionFunc(force_extension_on_path(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected].extensions), filePicker.extensionFiltersComplete[filePicker.extensionSelected]); + if(filePicker.isSaving) + pathToRet = force_extension_on_path(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected].extensions); + filePicker.postSelectionFunc(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected]); } filePicker.isOpen = false; } @@ -2127,8 +2130,10 @@ void Toolbar::file_picker_gui() { if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) { gui.set_post_callback_func([&, button, selectedEntry, entry] { if(selectedEntry && button.clicks >= 2) { - if(std::filesystem::is_directory(entry)) + if(std::filesystem::is_directory(entry)) { filePicker.currentSearchPath = entry; + file_picker_gui_refresh_entries(); + } else if(std::filesystem::is_regular_file(entry)) file_picker_gui_done(); } diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index e881978..333c7da 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -193,7 +193,8 @@ class Toolbar { std::vector entries; std::filesystem::path currentSearchPath; std::filesystem::path currentSelectedPath; - std::string fileName; + std::string fileName = ""; + bool isSaving = false; size_t extensionSelected; OpenFileSelectorCallback postSelectionFunc; } filePicker;