From 903914c7ef947294da693f7bf5ce8b92d679adc5 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Mon, 1 Dec 2025 13:05:59 +0300 Subject: [PATCH] Dropdown reacts to window size, and color picker always fits in window --- src/GUIStuff/GUIManager.cpp | 48 +++++++++---- src/GUIStuff/GUIManager.hpp | 35 +++++++++- src/Toolbar.cpp | 133 +++++++++++++++++++++--------------- 3 files changed, 145 insertions(+), 71 deletions(-) diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index b5db3db..f802fc1 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include "../FontData.hpp" namespace GUIStuff { @@ -536,6 +535,11 @@ bool GUIManager::input_scalar(const std::string& id, uint8_t* val, uint8_t min, return isUpdating; } +std::string GUIManager::get_clay_unique_id() { + std::string toRet = "CLAY_UNIQUE_ID_" + std::to_string(clayUniqueIDCounter++); + return toRet; +} + void GUIManager::input_path(const std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate) { push_id(id); insert_element>()->update(*io, val, @@ -564,9 +568,17 @@ bool GUIManager::font_picker(const std::string& id, std::string* fontName) { return toRet; } -void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::vector& selections, float width, float maxHeight, const std::function& hoverboxElemUpdate) { +void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::vector& selections, float width, const std::function& hoverboxElemUpdate) { push_id(id); - bool& dropDownOpen = insert_any_with_id(0, false); + struct DropdownData { + std::string uniqueID; + bool isOpen = false; + }; + DropdownData& dropdownData = insert_any_with_id_with_function(0, [&]() { + return DropdownData { + .uniqueID = get_clay_unique_id() + }; + }); left_to_right_layout(CLAY_SIZING_FIXED(width), CLAY_SIZING_FIT(0), [&]() { bool click = selectable_button(id, [&](SelectionHelper& s, bool iS) { CLAY({ @@ -585,14 +597,20 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std:: .sizing = {.width = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE)} } }) { - svg_icon("dropico", "data/icons/droparrow.svg", dropDownOpen); + svg_icon("dropico", "data/icons/droparrow.svg", dropdownData.isOpen); } } - }, SelectableButton::DrawType::FILLED, dropDownOpen); - if(dropDownOpen) { + }, SelectableButton::DrawType::FILLED, dropdownData.isOpen); + if(dropdownData.isOpen) { + Clay_ElementId clayID = Clay_GetElementId(strArena.std_str_to_clay_str(dropdownData.uniqueID)); + Clay_ElementData dropdownElemData = Clay_GetElementData(clayID); + float calculatedDropdownMaxHeight = 0.0f; + if(dropdownElemData.found) + calculatedDropdownMaxHeight = std::max(windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f); CLAY({ + .id = clayID, .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0, maxHeight)}, + .sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)}, .childGap = 0 }, .backgroundColor = convert_vec4(io->theme->backColor1), @@ -644,7 +662,7 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std:: text_label(selections[i]); if(io->mouse.leftClick && Clay_Hovered()) { *val = i; - dropDownOpen = false; + dropdownData.isOpen = false; } } } @@ -654,12 +672,13 @@ void GUIManager::dropdown_select(const std::string& id, size_t* val, const std:: } } if(click) - dropDownOpen = !dropDownOpen; + dropdownData.isOpen = !dropdownData.isOpen; }); pop_id(); } void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, const std::function& elemUpdate) { + push_id(id); CLAY({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, @@ -679,11 +698,11 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con std::string uniqueID; }; - push_id(id); - ScrollAreaData& sD = insert_any(ScrollAreaData{ - .uniqueID = Random::get().alphanumeric_str(30) + ScrollAreaData& sD = insert_any_with_id_with_function(0, [&](){ + return ScrollAreaData{ + .uniqueID = get_clay_unique_id() + }; }); - pop_id(); Clay_ElementId clayID = Clay_GetElementId(strArena.std_str_to_clay_str(sD.uniqueID)); @@ -696,7 +715,9 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con }, .clip = {.horizontal = clipHorizontal, .vertical = true, .childOffset = {.x = 0, .y = Clay_GetScrollOffset().y}} }) { + push_id(1); elemUpdate(sD.contentDimensions, sD.containerDimensions, sD.currentScrollPos); + pop_id(); } // Make sure you are accessing this data after the element has been created @@ -770,6 +791,7 @@ void GUIManager::scroll_bar_area(const std::string& id, bool clipHorizontal, con else sD.currentScrollPos = 0.0f; } + pop_id(); } diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index a9454ff..fc852bb 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -96,7 +96,7 @@ class GUIManager { void input_path(const std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate = nullptr); void input_path_field(const std::string& id, const std::string& name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate = nullptr); - void dropdown_select(const std::string& id, size_t* val, const std::vector& selections, float width = 200.0f, float maxHeight = 0.0f, const std::function& hoverboxElemUpdate = nullptr); + void dropdown_select(const std::string& id, size_t* val, const std::vector& selections, float width = 200.0f, const std::function& hoverboxElemUpdate = nullptr); void paint_circle_popup_menu(const std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function& elemUpdate = nullptr); @@ -126,6 +126,22 @@ class GUIManager { pop_id(); return toRet; } + + template T& insert_any_with_function(std::function funcToRun) { + auto [it, inserted] = elements.emplace(idStack, ElementContainer()); + auto& container = it->second; + if(inserted) + it->second.extra = funcToRun(); + container.isUsedThisFrame = true; + return std::any_cast(it->second.extra); + } + + template T& insert_any_with_id_with_function(int64_t id, std::function funcToRun) { + push_id(id); + T& toRet = insert_any_with_function(funcToRun); + pop_id(); + return toRet; + } template bool input_generic(const std::string& id, T* val, const std::function(const std::string&)>& fromStr, const std::function& toStr, bool singleLine, bool updateEveryEdit, const std::function& elemUpdate = nullptr) { bool isUpdating = false; @@ -307,10 +323,20 @@ class GUIManager { bool font_picker(const std::string& id, std::string* fontName); - template bool color_picker_items(const std::string& id, T* val, bool selectAlpha) { + template bool color_picker_items(const std::string& id, T* val, bool selectAlpha, float fixedPickerWidth = 0.0f) { bool isUpdating = false; push_id(id); - isUpdating |= color_picker("c", val, selectAlpha); + if(fixedPickerWidth == 0.0f) + isUpdating |= color_picker("c", val, selectAlpha); + else { + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(fixedPickerWidth), .height = CLAY_SIZING_FIXED(fixedPickerWidth)} + } + }) { + isUpdating |= color_picker("c", val, selectAlpha); + } + } left_to_right_line_layout([&]() { text_label("R"); isUpdating |= input_color_component_255("r", &(*val)[0]); @@ -341,6 +367,9 @@ class GUIManager { } private: + std::string get_clay_unique_id(); + uint64_t clayUniqueIDCounter = 0; + static void clay_error_handler(Clay_ErrorData errorData); static Clay_Dimensions clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData); diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 8706963..49cf69c 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1062,21 +1062,27 @@ void Toolbar::drawing_program_gui() { if(colorLeft) { CLAY({ .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)}, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io->theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) + .padding = {.top = 40, .bottom = 40} + } }) { - gui.obstructing_window(); - isUpdatingColorLeft |= gui.color_picker_items("colorpickerleft", colorLeft, true); - bool hoveringOnDropdown = false; - isUpdatingColorLeft |= color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown); - if(!Clay_Hovered() && !justAssignedColorLeft && !hoveringOnDropdown && io->mouse.leftClick) - colorLeft = nullptr; + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)}, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = io->theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) + }) { + gui.obstructing_window(); + isUpdatingColorLeft |= gui.color_picker_items("colorpickerleft", colorLeft, true, 300.0f - io->theme->padding1 * 2.0f); + bool hoveringOnDropdown = false; + isUpdatingColorLeft |= color_palette("colorpickerleftpalette", colorLeft, hoveringOnDropdown); + if(!Clay_Hovered() && !justAssignedColorLeft && !hoveringOnDropdown && io->mouse.leftClick) + colorLeft = nullptr; + } } } CLAY({ @@ -1087,21 +1093,27 @@ void Toolbar::drawing_program_gui() { if(colorRight) { CLAY({ .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)}, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io->theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) + .padding = {.top = 40, .bottom = 40} + } }) { - gui.obstructing_window(); - isUpdatingColorRight |= gui.color_picker_items("colorpickerright", colorRight, true); - bool hoveringOnDropdown = false; - isUpdatingColorRight |= color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown); - if(!Clay_Hovered() && !justAssignedColorRight && !hoveringOnDropdown && io->mouse.leftClick) - colorRight = nullptr; + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0)}, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = io->theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) + }) { + gui.obstructing_window(); + isUpdatingColorRight |= gui.color_picker_items("colorpickerright", colorRight, true, 300.0f - io->theme->padding1 * 2.0f); + bool hoveringOnDropdown = false; + isUpdatingColorRight |= color_palette("colorpickerrightpalette", colorRight, hoveringOnDropdown); + if(!Clay_Hovered() && !justAssignedColorRight && !hoveringOnDropdown && io->mouse.leftClick) + colorRight = nullptr; + } } } main.world->drawProg.tool_options_gui(); @@ -1112,42 +1124,53 @@ bool Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri bool isUpdating = false; gui.push_id(id); auto& palette = paletteData.palettes[paletteData.selectedPalette].colors; - size_t i = 0; constexpr float COLOR_BUTTON_SIZE = GUIStuff::GUIManager::BIG_BUTTON_SIZE; - while(i < palette.size()) { + + gui.scroll_bar_area("color palette scroll area", false, [&](float, float, float &) { CLAY({ .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)}, - .padding = {.top = 3, .bottom = 3}, + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT + .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + size_t i = 0; while(i < palette.size()) { CLAY({ .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)}, + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)}, + .childGap = io->theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT } }) { - Vector4f newC = {palette[i].x(), palette[i].y(), palette[i].z(), 1.0f}; - if(gui.color_button("c" + std::to_string(i), &newC, (paletteData.selectedColor == (int)i))) { - paletteData.selectedColor = (int)i; - // We want to keep the old color's alpha - color->x() = newC.x(); - color->y() = newC.y(); - color->z() = newC.z(); - isUpdating = true; + while(i < palette.size()) { + CLAY({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(COLOR_BUTTON_SIZE)} + } + }) { + Vector4f newC = {palette[i].x(), palette[i].y(), palette[i].z(), 1.0f}; + if(gui.color_button("c" + std::to_string(i), &newC, (paletteData.selectedColor == (int)i))) { + paletteData.selectedColor = (int)i; + // We want to keep the old color's alpha + color->x() = newC.x(); + color->y() = newC.y(); + color->z() = newC.z(); + isUpdating = true; + } + if(paletteData.selectedColor == (int)i && (newC.x() != color->x() || newC.y() != color->y() || newC.z() != color->z())) + paletteData.selectedColor = -1; + } + i++; + if(i % 6 == 0) + break; } - if(paletteData.selectedColor == (int)i && (newC.x() != color->x() || newC.y() != color->y() || newC.z() != color->z())) - paletteData.selectedColor = -1; } - i++; - if(i % 6 == 0) - break; } } - } + }); + if(paletteData.selectedPalette != 0) { CLAY({ .layout = { @@ -1182,7 +1205,7 @@ bool Toolbar::color_palette(const std::string& id, Vector4f* color, bool& hoveri std::vector paletteNames; for(auto& p : paletteData.palettes) paletteNames.emplace_back(p.name); - gui.dropdown_select("paletteselector", &paletteData.selectedPalette, paletteNames, 200.0f, 90.0f, [&]() { + gui.dropdown_select("paletteselector", &paletteData.selectedPalette, paletteNames, 200.0f, [&]() { hoveringOnDropdown = Clay_Hovered(); }); if(gui.svg_icon_button("paletteadd", "data/icons/plus.svg", paletteData.addingPalette, 25.0f)) paletteData.addingPalette = !paletteData.addingPalette; @@ -1442,14 +1465,14 @@ void Toolbar::options_menu() { switch(generalSettingsOptions) { case GSETTINGS_GENERAL: { gui.push_id("general settings"); - gui.input_text_field("display name input", "Display Name", &main.displayName); + gui.input_text_field("display name input", "Display name", &main.displayName); main.update_display_names(); SkColor4f newBackColor{main.defaultCanvasBackgroundColor.x(), main.defaultCanvasBackgroundColor.y(), main.defaultCanvasBackgroundColor.z(), 1.0f}; gui.color_picker_button_field("defaultCanvasBackgroundColor", "Default canvas background color", &newBackColor, false); main.defaultCanvasBackgroundColor = convert_vec3(newBackColor); #ifndef __EMSCRIPTEN__ - gui.checkbox_field("native file pick", "Use Native File Picker", &useNativeFilePicker); - gui.checkbox_field("update notifications enable", "Check For Updates on Startup", &updateCheckerData.checkForUpdates); + gui.checkbox_field("native file pick", "Use native file picker", &useNativeFilePicker); + gui.checkbox_field("update notifications enable", "Check for updates on startup", &updateCheckerData.checkForUpdates); #endif gui.slider_scalar_field("drag zoom slider", "Drag zoom speed", &dragZoomSpeed, 0.0, 1.0, 3); gui.slider_scalar_field("scroll zoom slider", "Scroll zoom speed", &scrollZoomSpeed, 0.0, 1.0, 3); @@ -1597,8 +1620,8 @@ void Toolbar::options_menu() { } case GSETTINGS_DEBUG: { gui.push_id("debug settings menu"); - gui.checkbox_field("show performance metrics", "Show Metrics", &showPerformance); - gui.input_scalar_fields("jump transition easing", "Jump Easing", &jumpTransitionEasing, 4, -10.0f, 10.0f, 2); + gui.checkbox_field("show performance metrics", "Show metrics", &showPerformance); + gui.input_scalar_fields("jump transition easing", "Jump easing", &jumpTransitionEasing, 4, -10.0f, 10.0f, 2); #ifndef __EMSCRIPTEN__ gui.input_scalar_field("fps cap slider", "FPS cap", &main.fpsLimit, 3.0f, 10000.0f); #endif