diff --git a/src/GUIStuff/Elements/ScrollArea.cpp b/src/GUIStuff/Elements/ScrollArea.cpp index fd8a681..66b7c16 100644 --- a/src/GUIStuff/Elements/ScrollArea.cpp +++ b/src/GUIStuff/Elements/ScrollArea.cpp @@ -1,5 +1,6 @@ #include "ScrollArea.hpp" #include "../GUIManager.hpp" +#include "LayoutElement.hpp" namespace GUIStuff { @@ -13,12 +14,18 @@ void ScrollArea::layout(const Clay_ElementId& id, const Options& options) { Clay_ElementId localID = CLAY_ID_LOCAL("SCROLL_AREA"); Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(localID); + Clay_ElementData elemData = Clay_GetElementData(localID); + + Vector2f bbPos = {0.0f, 0.0f}; if(scrollData.found) { contentDimensions = {scrollData.contentDimensions.width, scrollData.contentDimensions.height}; containerDimensions = {scrollData.scrollContainerDimensions.width, scrollData.scrollContainerDimensions.height}; } + if(elemData.found) + bbPos = {elemData.boundingBox.x, elemData.boundingBox.y}; + clamp_scroll(); CLAY(localID, { @@ -32,68 +39,98 @@ void ScrollArea::layout(const Clay_ElementId& id, const Options& options) { opts.innerContent({.contentDimensions = contentDimensions, .containerDimensions = containerDimensions, .scrollOffset = scrollOffset}); } - //if(scrollData.contentDimensions.height > scrollData.scrollContainerDimensions.height) { - // float sAreaDim = scrollData.scrollContainerDimensions.height; - // float contDim = scrollData.contentDimensions.height; - // float scrollerSize = (sAreaDim / contDim) * sAreaDim; - // float scrollPosMax = contDim - sAreaDim; - // float scrollerPos = std::fabs(scrollData.scrollPosition->y / scrollPosMax); - // float areaAboveScrollerSize = scrollerPos * (sAreaDim - scrollerSize); + if(scrollData.contentDimensions.height > scrollData.scrollContainerDimensions.height) { + float sAreaDim = containerDimensions.y(); + float sAreaStart = bbPos.y(); + float contDim = contentDimensions.y(); + float scrollerSize = (sAreaDim / contDim) * sAreaDim; + float scrollPosMax = contDim - sAreaDim; + float scrollerPos = std::fabs(scrollOffset.y() / scrollPosMax); + float areaAboveScrollerSize = scrollerPos * (sAreaDim - scrollerSize); - // currentScrollPos = scrollData.scrollPosition->y; + gui.element("scroll bar", [&] (const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(12), .height = CLAY_SIZING_GROW(0)}, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(gui.io.theme->backColor2) + }) { + SkColor4f scrollerColor; + if(isScrollbarHeldY) + scrollerColor = gui.io.theme->fillColor1; + else if(isScrollbarHoveredY) + scrollerColor = gui.io.theme->fillColor1; + else + scrollerColor = gui.io.theme->fillColor2; - // CLAY_AUTO_ID({ - // .layout = { - // .sizing = {.width = CLAY_SIZING_FIXED(12), .height = CLAY_SIZING_GROW(0)}, - // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - // .layoutDirection = CLAY_TOP_TO_BOTTOM - // }, - // .backgroundColor = convert_vec4(io.theme->backColor2) - // }) { - // SkColor4f scrollerColor; - // if(sD.isMoving) - // scrollerColor = io.theme->fillColor1; - // else if(Clay_Hovered()) - // scrollerColor = io.theme->fillColor1; - // else - // scrollerColor = io.theme->fillColor2; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(areaAboveScrollerSize)} + } + }) {} - // bool isHoveringOverScroller = false; - // CLAY_AUTO_ID({ - // .layout = { - // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(areaAboveScrollerSize)} - // } - // }) {} - // CLAY_AUTO_ID({ - // .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(scrollerSize)}}, - // .backgroundColor = convert_vec4(scrollerColor), - // .cornerRadius = CLAY_CORNER_RADIUS(3), - // }) { - // if(Clay_Hovered()) - // isHoveringOverScroller = true; - // } - // CLAY_AUTO_ID({ .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} - // if(Clay_Hovered() && io.mouse.leftClick) { - // isMoving = true; - // if(isHoveringOverScroller) - // scrollerStartPos = scrollAreaBB.y + areaAboveScrollerSize + scrollerSize * 0.5f; - // else - // scrollerStartPos = std::clamp(io.mouse.pos.y(), scrollAreaBB.y + scrollerSize * 0.5f, scrollAreaBB.y + scrollAreaBB.height - scrollerSize * 0.5f); - // mouseStartPos = io.mouse.pos.y(); - // } - // if(!io.mouse.leftHeld) - // isMoving = false; - // if(isMoving) { - // float newScrollPosFrac; - // newScrollPosFrac = std::clamp((scrollerStartPos - (mouseStartPos - io.mouse.pos.y()) - scrollAreaBB.y - scrollerSize * 0.5f) / (scrollAreaBB.height - scrollerSize), 0.0f, 1.0f); - // currentScrollPos = newScrollPosFrac * (-scrollPosMax); - // } - // currentScrollPos = std::clamp(currentScrollPos, -scrollPosMax, 0.0f); - // scrollData.scrollPosition->y = currentScrollPos; - // } - //} - //else - // sD.currentScrollPos = 0.0f; + gui.element("scroller", [&] (const Clay_ElementId& lId2) { + CLAY(lId2, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(scrollerSize)}}, + .backgroundColor = convert_vec4(scrollerColor), + .cornerRadius = CLAY_CORNER_RADIUS(3), + }) {} + }, LayoutElement::Callbacks{ + .mouseMotion = [&] (const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + if(isHoveringOverScrollerY != mouseHovering) { + isHoveringOverScrollerY = mouseHovering; + gui.set_to_layout(); + } + return mouseHovering; + } + }); + + CLAY_AUTO_ID({ .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} + } + }, LayoutElement::Callbacks{ + .mouseButton = [&, scrollPosMax, sAreaStart, sAreaDim, scrollerSize, areaAboveScrollerSize](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + if(button.button == InputManager::MouseButton::LEFT) { + if(button.down && mouseHovering) { + isScrollbarHeldY = true; + if(isHoveringOverScrollerY) + scrollerStartPosY = sAreaStart + areaAboveScrollerSize + scrollerSize * 0.5f; + else + scrollerStartPosY = std::clamp(button.pos.y(), sAreaStart + scrollerSize * 0.5f, sAreaStart + sAreaDim - scrollerSize * 0.5f); + mouseStartPosY = button.pos.y(); + + float newScrollPosFrac; + newScrollPosFrac = std::clamp((scrollerStartPosY - (mouseStartPosY - button.pos.y()) - sAreaStart - scrollerSize * 0.5f) / (sAreaDim - scrollerSize), 0.0f, 1.0f); + scrollOffset.y() = newScrollPosFrac * (-scrollPosMax); + + clamp_scroll(); + gui.set_to_layout(); + } + else if(isScrollbarHeldY) { + isScrollbarHeldY = false; + gui.set_to_layout(); + } + } + return mouseHovering; + }, + .mouseMotion = [&, scrollPosMax, sAreaStart, sAreaDim, scrollerSize](const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + if(isScrollbarHoveredY != mouseHovering) { + isScrollbarHoveredY = mouseHovering; + gui.set_to_layout(); + } + if(isScrollbarHeldY) { + float newScrollPosFrac; + newScrollPosFrac = std::clamp((scrollerStartPosY - (mouseStartPosY - motion.pos.y()) - sAreaStart - scrollerSize * 0.5f) / (sAreaDim - scrollerSize), 0.0f, 1.0f); + scrollOffset.y() = newScrollPosFrac * (-scrollPosMax); + + clamp_scroll(); + gui.set_to_layout(); + } + return mouseHovering; + } + }); + } } } diff --git a/src/GUIStuff/Elements/ScrollArea.hpp b/src/GUIStuff/Elements/ScrollArea.hpp index 9b75986..805b985 100644 --- a/src/GUIStuff/Elements/ScrollArea.hpp +++ b/src/GUIStuff/Elements/ScrollArea.hpp @@ -32,8 +32,12 @@ class ScrollArea : public Element { virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) override; private: Options opts; - bool isScrollbarXHeld = false; - bool isScrollbarYHeld = false; + bool isScrollbarHeldY = false; + bool isScrollbarHoveredY = false; + bool isHoveringOverScrollerY = false; + float scrollerStartPosY; + float mouseStartPosY; + Vector2f contentDimensions = {0.0f, 0.0f}; Vector2f containerDimensions = {0.0f, 0.0f}; Vector2f scrollOffset = {0.0f, 0.0f}; diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index a439cea..23f88b8 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -242,7 +242,7 @@ void GUIManager::layout_if_necessary() { } void GUIManager::layout() { - constexpr int LAYOUT_RUN_COUNT = 2; + constexpr int LAYOUT_RUN_COUNT = 3; for(int i = 0; i < LAYOUT_RUN_COUNT; i++) single_layout_run(); } diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 7170293..3705e46 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -1679,223 +1679,228 @@ void Toolbar::general_settings_inner_gui() { .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - gui.element("general settings scroll area", ScrollArea::Options{ - .scrollVertical = true, - .clipHorizontal = false, - .clipVertical = true, - .showScrollbarY = true, - .innerContent = [&](const ScrollArea::InnerContentParameters&) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, - .padding = CLAY_PADDING_ALL(io.theme->padding1), - .childGap = io.theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - switch(generalSettingsOptions) { - case GSETTINGS_GENERAL: { - gui.new_id("general settings", [&] { - input_text_field(gui, "display name input", "Display name", &main.conf.displayName); - main.update_display_names(); - color_picker_button_field(gui, "defaultCanvasBackgroundColor", "Default canvas background color", &main.conf.defaultCanvasBackgroundColor, { .hasAlpha = false }); - #ifndef __EMSCRIPTEN__ - checkbox_boolean_field(gui, "native file pick", "Use native file picker", &main.conf.useNativeFilePicker); - checkbox_boolean_field(gui, "update notifications enable", "Check for updates on startup", &main.conf.checkForUpdates); - #endif - slider_scalar_field(gui, "drag zoom slider", "Drag zoom speed", &main.conf.dragZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); - slider_scalar_field(gui, "scroll zoom slider", "Scroll zoom speed", &main.conf.scrollZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); - checkbox_boolean_field(gui, "flip zoom tool direction", "Flip zoom tool direction", &main.conf.flipZoomToolDirection); - checkbox_boolean_field(gui, "make all tools share same size", "Make all tools share size", &main.toolConfig.globalConf.useGlobalRelativeWidth); - #ifndef __EMSCRIPTEN__ - checkbox_boolean_field(gui, "disable graphics driver workarounds", "Disable graphics driver workarounds (enabling or disabling this might fix some graphical glitches, requires restart)", &main.conf.disableGraphicsDriverWorkarounds); - #endif - input_scalar_field(gui, "jump transition time", "Jump transition time", &main.conf.jumpTransitionTime, 0.01f, 1000.0f, {.decimalPrecision = 2}); - input_scalar_field(gui, "Max GUI Scale", "Max GUI Scale", &main.conf.guiScale, 0.5f, 5.0f, {.decimalPrecision = 1}); - text_label(gui, "Anti-aliasing:"); - radio_button_selector(gui, "Antialiasing selector", &main.conf.antialiasing, { - {"None", GlobalConfig::AntiAliasing::NONE}, - {"Skia", GlobalConfig::AntiAliasing::SKIA}, - {"Dynamic MSAA", GlobalConfig::AntiAliasing::DYNAMIC_MSAA} - }, [&] { - main.refresh_draw_surfaces(); - }); - text_label(gui, "VSync:"); - radio_button_selector(gui, "VSync selector", &main.conf.vsyncValue, { - {"On", 1}, - {"Off", 0}, - {"Adaptive", -1} - }, [&] { - main.set_vsync_value(main.conf.vsyncValue); - }); - - #ifndef __EMSCRIPTEN__ - checkbox_boolean_field(gui, "apply display scale", "Apply display scale", &main.conf.applyDisplayScale); - #endif - }); - break; - } - case GSETTINGS_TABLET: { - gui.new_id("tablet settings", [&] { - checkbox_boolean_field(gui, "pen pressure width", "Pen pressure affects brush size", &main.conf.tabletOptions.pressureAffectsBrushWidth); - slider_scalar_field(gui, "smoothing time", "Smoothing sampling time", &main.conf.tabletOptions.smoothingSamplingTime, 0.001f, 1.0f, {.decimalPrecision = 3}); - input_scalar_field(gui, "middle click", "Middle click pen button", &main.conf.tabletOptions.middleClickButton, 1, 255); - input_scalar_field(gui, "right click", "Right click pen button", &main.conf.tabletOptions.rightClickButton, 1, 255); - slider_scalar_field(gui, "tablet brush minimum size", "Brush relative minimum size", &main.conf.tabletOptions.brushMinimumSize, 0.0f, 1.0f, {.decimalPrecision = 3}); - checkbox_boolean_field(gui, "tablet zoom with button method", "Zoom when pen touching tablet and pen button assigned to middle click is held", &main.conf.tabletOptions.zoomWhilePenDownAndButtonHeld); - #ifdef _WIN32 - checkbox_boolean_field(gui, "mouse ignore when pen proximity", "Ignore mouse movement when pen in proximity", &tabletOptions.ignoreMouseMovementWhenPenInProximity); - #endif - }); - break; - } - case GSETTINGS_THEME: { - gui.new_id("theme", [&] { - if(!themeData.selectedThemeIndex) - reload_theme_list(); - - CLAY_AUTO_ID({.layout = { - .childGap = io.theme->childGap1, - .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT, - } - }) { - text_label(gui, "Theme: "); - gui.element>("dropdownSelectThemes", &themeData.selectedThemeIndex.value(), themeData.themeDirList, DropdownOptions{ - .onClick = [&]() { - main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - reload_theme_list(); - } - }); - } - left_to_right_line_layout(gui, [&]() { - if(themeData.selectedThemeIndex != 0) { - text_button_wide("savethemebutton", "Save", [&] { - main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - main.g.save_theme(main.configPath, main.conf.themeCurrentlyLoaded); - reload_theme_list(); - }); - } - text_button_wide("saveasthemebutton", "Save As", [&]() { - themeData.openedSaveAsMenu = !themeData.openedSaveAsMenu; - }); - text_button_wide("reloadthemebutton", "Reload", [&] { - main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - reload_theme_list(); - }); - if(themeData.selectedThemeIndex != 0) { - text_button_wide("deletethemebutton", "Delete", [&] { - try { std::filesystem::remove(main.configPath / "themes" / (themeData.themeDirList[themeData.selectedThemeIndex.value()] + ".json")); } catch(...) { } - main.conf.themeCurrentlyLoaded = "Default"; - reload_theme_list(); - }); - } - }); - if(themeData.openedSaveAsMenu) { - input_text_field(gui, "Theme name:", "Theme name: ", &main.conf.themeCurrentlyLoaded); - left_to_right_line_layout(gui, [&]() { - text_button_wide("saveasdone", "Done", [&] { - main.g.save_theme(main.configPath, main.conf.themeCurrentlyLoaded); - reload_theme_list(); - }); - text_button_wide("saveascancel", "Cancel", [&] { - themeData.openedSaveAsMenu = false; - }); - }); - } - text_label(gui, "Edit theme:"); - text_label_light(gui, "Note: Changes only remain if theme is saved"); - color_picker_button_field(gui, "fillColor1", "Fill Color 1", &io.theme->fillColor1); - color_picker_button_field(gui, "fillColor2", "Fill Color 2", &io.theme->fillColor2); - color_picker_button_field(gui, "backColor1", "Back Color 1", &io.theme->backColor1); - color_picker_button_field(gui, "backColor2", "Back Color 2", &io.theme->backColor2); - color_picker_button_field(gui, "frontColor1", "Front Color 1", &io.theme->frontColor1); - color_picker_button_field(gui, "frontColor2", "Front Color 2", &io.theme->frontColor2); - color_picker_button_field(gui, "warningColor", "Warning Color", &io.theme->warningColor); - color_picker_button_field(gui, "errorColor", "Error Color", &io.theme->errorColor); - //gui.slider_scalar_field("hoverExpandTime", "Hover Expand Time", &io.theme->hoverExpandTime, 0.001f, 1.0f); - input_scalar_field(gui, "childGap1", "Gap between child elements", &io.theme->childGap1, 0, 30); - input_scalar_field(gui, "padding1", "Window padding", &io.theme->padding1, 0, 30); - slider_scalar_field(gui, "windowCorners1", "Window corner radius", &io.theme->windowCorners1, 0, 30); - }); - break; - } - case GSETTINGS_KEYBINDS: { - if(keybindWaiting.has_value()) { - main.input.stop_key_input(); - if(main.input.lastPressedKeybind) { - unsigned v = keybindWaiting.value(); - - Vector2ui32 newKey = main.input.lastPressedKeybind.value(); - main.input.keyAssignments.erase(newKey); - auto f = std::find_if(main.input.keyAssignments.begin(), main.input.keyAssignments.end(), [&](auto& p) { - return p.second == v; - }); - if(f != main.input.keyAssignments.end()) - main.input.keyAssignments.erase(f); - main.input.keyAssignments.emplace(newKey, v); - keybindWaiting = std::nullopt; - } - } - - gui.new_id("keybind entries", [&] { - for(unsigned i = 0; i < InputManager::KEY_ASSIGNABLE_COUNT; i++) { - gui.new_id(i, [&] { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(0), - .childGap = io.theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - text_label(gui, std::string(nlohmann::json(static_cast(i)))); - auto f = std::find_if(main.input.keyAssignments.begin(), main.input.keyAssignments.end(), [&](auto& p) { - return p.second == i; - }); - std::string assignedKeystrokeStr = f != main.input.keyAssignments.end() ? main.input.key_assignment_to_str(f->first) : ""; - text_button(gui, "keybind button", assignedKeystrokeStr, { - .isSelected = keybindWaiting.has_value() && keybindWaiting.value() == i, - .wide = true, - .onClick = [&] { - keybindWaiting = i; - } - }); - } - }); - } - }); - break; - } - case GSETTINGS_DEBUG: { - gui.new_id("debug settings menu", [&] { - checkbox_boolean_field(gui, "show performance metrics", "Show metrics", &showPerformance); - input_scalars_field(gui, "jump transition easing", "Jump easing", &main.conf.jumpTransitionEasing, 4, -10.0f, 10.0f, { .decimalPrecision = 2 }); - #ifndef __EMSCRIPTEN__ - input_scalar_field(gui, "fps cap slider", "FPS cap", &main.conf.fpsLimit, 3.0f, 10000.0f); - #endif - input_scalar_field(gui, "image load max threads", "Maximum image loading threads", &ImageResourceDisplay::IMAGE_LOAD_THREAD_COUNT_MAX, 1, 10000); - text_label_light(gui, "Cache related settings"); - input_scalar_field(gui, "cache node resolution", "Cache node resolution", &DrawingProgramCache::CACHE_NODE_RESOLUTION, 256, 8192); - input_scalar_field(gui, "max cache nodes", "Maximum cached nodes", &DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES, 2, 10000); - size_t cacheVRAMConsumptionInMB = ( DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES // Number of surfaces - * DrawingProgramCache::CACHE_NODE_RESOLUTION * DrawingProgramCache::CACHE_NODE_RESOLUTION // Number of pixels per cache surface - * 4) // 4 Channels per pixel (RGBA) - / (1024 * 1024); // Bytes -> Megabytes conversion - text_label_light(gui, "Cache max VRAM consumption (MB): " + std::to_string(cacheVRAMConsumptionInMB)); - input_scalar_field(gui, "max components in node", "Maximum components in single node", &DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE, 2, 10000); - input_scalar_field(gui, "components to force cache rebuild", "Number of components to force cache rebuild", &DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD, 1, 1000000); - input_scalar_field(gui, "maximum frame time to force cache rebuild", "Maximum frame time to force cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH, 1, 1000000); - input_scalar_field(gui, "minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000); - }); - break; + auto general_scroll_area = [&](const char* id, const std::function& innerContent) { + gui.element("general settings scroll area", ScrollArea::Options{ + .scrollVertical = true, + .clipHorizontal = false, + .clipVertical = true, + .showScrollbarY = true, + .innerContent = [&, innerContent](const ScrollArea::InnerContentParameters&) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, + .padding = CLAY_PADDING_ALL(io.theme->padding1), + .childGap = io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM } + }) { + innerContent(); } } + }); + }; + switch(generalSettingsOptions) { + case GSETTINGS_GENERAL: { + general_scroll_area("general settings", [&] { + input_text_field(gui, "display name input", "Display name", &main.conf.displayName); + main.update_display_names(); + color_picker_button_field(gui, "defaultCanvasBackgroundColor", "Default canvas background color", &main.conf.defaultCanvasBackgroundColor, { .hasAlpha = false }); + #ifndef __EMSCRIPTEN__ + checkbox_boolean_field(gui, "native file pick", "Use native file picker", &main.conf.useNativeFilePicker); + checkbox_boolean_field(gui, "update notifications enable", "Check for updates on startup", &main.conf.checkForUpdates); + #endif + slider_scalar_field(gui, "drag zoom slider", "Drag zoom speed", &main.conf.dragZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); + slider_scalar_field(gui, "scroll zoom slider", "Scroll zoom speed", &main.conf.scrollZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); + checkbox_boolean_field(gui, "flip zoom tool direction", "Flip zoom tool direction", &main.conf.flipZoomToolDirection); + checkbox_boolean_field(gui, "make all tools share same size", "Make all tools share size", &main.toolConfig.globalConf.useGlobalRelativeWidth); + #ifndef __EMSCRIPTEN__ + checkbox_boolean_field(gui, "disable graphics driver workarounds", "Disable graphics driver workarounds (enabling or disabling this might fix some graphical glitches, requires restart)", &main.conf.disableGraphicsDriverWorkarounds); + #endif + input_scalar_field(gui, "jump transition time", "Jump transition time", &main.conf.jumpTransitionTime, 0.01f, 1000.0f, {.decimalPrecision = 2}); + input_scalar_field(gui, "Max GUI Scale", "Max GUI Scale", &main.conf.guiScale, 0.5f, 5.0f, {.decimalPrecision = 1}); + text_label(gui, "Anti-aliasing:"); + radio_button_selector(gui, "Antialiasing selector", &main.conf.antialiasing, { + {"None", GlobalConfig::AntiAliasing::NONE}, + {"Skia", GlobalConfig::AntiAliasing::SKIA}, + {"Dynamic MSAA", GlobalConfig::AntiAliasing::DYNAMIC_MSAA} + }, [&] { + main.refresh_draw_surfaces(); + }); + text_label(gui, "VSync:"); + radio_button_selector(gui, "VSync selector", &main.conf.vsyncValue, { + {"On", 1}, + {"Off", 0}, + {"Adaptive", -1} + }, [&] { + main.set_vsync_value(main.conf.vsyncValue); + }); + + #ifndef __EMSCRIPTEN__ + checkbox_boolean_field(gui, "apply display scale", "Apply display scale", &main.conf.applyDisplayScale); + #endif + }); + break; } - }); + case GSETTINGS_TABLET: { + general_scroll_area("tablet settings", [&] { + checkbox_boolean_field(gui, "pen pressure width", "Pen pressure affects brush size", &main.conf.tabletOptions.pressureAffectsBrushWidth); + slider_scalar_field(gui, "smoothing time", "Smoothing sampling time", &main.conf.tabletOptions.smoothingSamplingTime, 0.001f, 1.0f, {.decimalPrecision = 3}); + input_scalar_field(gui, "middle click", "Middle click pen button", &main.conf.tabletOptions.middleClickButton, 1, 255); + input_scalar_field(gui, "right click", "Right click pen button", &main.conf.tabletOptions.rightClickButton, 1, 255); + slider_scalar_field(gui, "tablet brush minimum size", "Brush relative minimum size", &main.conf.tabletOptions.brushMinimumSize, 0.0f, 1.0f, {.decimalPrecision = 3}); + checkbox_boolean_field(gui, "tablet zoom with button method", "Zoom when pen touching tablet and pen button assigned to middle click is held", &main.conf.tabletOptions.zoomWhilePenDownAndButtonHeld); + #ifdef _WIN32 + checkbox_boolean_field(gui, "mouse ignore when pen proximity", "Ignore mouse movement when pen in proximity", &tabletOptions.ignoreMouseMovementWhenPenInProximity); + #endif + }); + break; + } + case GSETTINGS_THEME: { + general_scroll_area("theme", [&] { + if(!themeData.selectedThemeIndex) + reload_theme_list(); + + CLAY_AUTO_ID({.layout = { + .childGap = io.theme->childGap1, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + text_label(gui, "Theme: "); + gui.element>("dropdownSelectThemes", &themeData.selectedThemeIndex.value(), themeData.themeDirList, DropdownOptions{ + .onClick = [&]() { + main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; + reload_theme_list(); + } + }); + } + left_to_right_line_layout(gui, [&]() { + if(themeData.selectedThemeIndex != 0) { + text_button_wide("savethemebutton", "Save", [&] { + main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; + main.g.save_theme(main.configPath, main.conf.themeCurrentlyLoaded); + reload_theme_list(); + }); + } + text_button_wide("saveasthemebutton", "Save As", [&]() { + themeData.openedSaveAsMenu = !themeData.openedSaveAsMenu; + }); + text_button_wide("reloadthemebutton", "Reload", [&] { + main.conf.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; + reload_theme_list(); + }); + if(themeData.selectedThemeIndex != 0) { + text_button_wide("deletethemebutton", "Delete", [&] { + try { std::filesystem::remove(main.configPath / "themes" / (themeData.themeDirList[themeData.selectedThemeIndex.value()] + ".json")); } catch(...) { } + main.conf.themeCurrentlyLoaded = "Default"; + reload_theme_list(); + }); + } + }); + if(themeData.openedSaveAsMenu) { + input_text_field(gui, "Theme name:", "Theme name: ", &main.conf.themeCurrentlyLoaded); + left_to_right_line_layout(gui, [&]() { + text_button_wide("saveasdone", "Done", [&] { + main.g.save_theme(main.configPath, main.conf.themeCurrentlyLoaded); + reload_theme_list(); + }); + text_button_wide("saveascancel", "Cancel", [&] { + themeData.openedSaveAsMenu = false; + }); + }); + } + text_label(gui, "Edit theme:"); + text_label_light(gui, "Note: Changes only remain if theme is saved"); + auto theme_color_field = [&](const char* id, const char* name, SkColor4f* c) { + color_picker_button_field(gui, id, name, c, {}); + }; + theme_color_field("fillColor2", "Fill Color 2", &io.theme->fillColor2); + theme_color_field("backColor1", "Back Color 1", &io.theme->backColor1); + theme_color_field("backColor2", "Back Color 2", &io.theme->backColor2); + theme_color_field("frontColor1", "Front Color 1", &io.theme->frontColor1); + theme_color_field("frontColor2", "Front Color 2", &io.theme->frontColor2); + theme_color_field("warningColor", "Warning Color", &io.theme->warningColor); + theme_color_field("errorColor", "Error Color", &io.theme->errorColor); + //gui.slider_scalar_field("hoverExpandTime", "Hover Expand Time", &io.theme->hoverExpandTime, 0.001f, 1.0f); + input_scalar_field(gui, "childGap1", "Gap between child elements", &io.theme->childGap1, 0, 30); + input_scalar_field(gui, "padding1", "Window padding", &io.theme->padding1, 0, 30); + slider_scalar_field(gui, "windowCorners1", "Window corner radius", &io.theme->windowCorners1, 0, 30); + }); + break; + } + case GSETTINGS_KEYBINDS: { + if(keybindWaiting.has_value()) { + main.input.stop_key_input(); + if(main.input.lastPressedKeybind) { + unsigned v = keybindWaiting.value(); + + Vector2ui32 newKey = main.input.lastPressedKeybind.value(); + main.input.keyAssignments.erase(newKey); + auto f = std::find_if(main.input.keyAssignments.begin(), main.input.keyAssignments.end(), [&](auto& p) { + return p.second == v; + }); + if(f != main.input.keyAssignments.end()) + main.input.keyAssignments.erase(f); + main.input.keyAssignments.emplace(newKey, v); + keybindWaiting = std::nullopt; + } + } + + general_scroll_area("keybind entries", [&] { + for(unsigned i = 0; i < InputManager::KEY_ASSIGNABLE_COUNT; i++) { + gui.new_id(i, [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(0), + .childGap = io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + text_label(gui, std::string(nlohmann::json(static_cast(i)))); + auto f = std::find_if(main.input.keyAssignments.begin(), main.input.keyAssignments.end(), [&](auto& p) { + return p.second == i; + }); + std::string assignedKeystrokeStr = f != main.input.keyAssignments.end() ? main.input.key_assignment_to_str(f->first) : ""; + text_button(gui, "keybind button", assignedKeystrokeStr, { + .isSelected = keybindWaiting.has_value() && keybindWaiting.value() == i, + .wide = true, + .onClick = [&] { + keybindWaiting = i; + } + }); + } + }); + } + }); + break; + } + case GSETTINGS_DEBUG: { + general_scroll_area("debug settings menu", [&] { + checkbox_boolean_field(gui, "show performance metrics", "Show metrics", &showPerformance); + input_scalars_field(gui, "jump transition easing", "Jump easing", &main.conf.jumpTransitionEasing, 4, -10.0f, 10.0f, { .decimalPrecision = 2 }); + #ifndef __EMSCRIPTEN__ + input_scalar_field(gui, "fps cap slider", "FPS cap", &main.conf.fpsLimit, 3.0f, 10000.0f); + #endif + input_scalar_field(gui, "image load max threads", "Maximum image loading threads", &ImageResourceDisplay::IMAGE_LOAD_THREAD_COUNT_MAX, 1, 10000); + text_label_light(gui, "Cache related settings"); + input_scalar_field(gui, "cache node resolution", "Cache node resolution", &DrawingProgramCache::CACHE_NODE_RESOLUTION, 256, 8192); + input_scalar_field(gui, "max cache nodes", "Maximum cached nodes", &DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES, 2, 10000); + size_t cacheVRAMConsumptionInMB = ( DrawingProgramCache::MAXIMUM_DRAW_CACHE_SURFACES // Number of surfaces + * DrawingProgramCache::CACHE_NODE_RESOLUTION * DrawingProgramCache::CACHE_NODE_RESOLUTION // Number of pixels per cache surface + * 4) // 4 Channels per pixel (RGBA) + / (1024 * 1024); // Bytes -> Megabytes conversion + text_label_light(gui, "Cache max VRAM consumption (MB): " + std::to_string(cacheVRAMConsumptionInMB)); + input_scalar_field(gui, "max components in node", "Maximum components in single node", &DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE, 2, 10000); + input_scalar_field(gui, "components to force cache rebuild", "Number of components to force cache rebuild", &DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD, 1, 1000000); + input_scalar_field(gui, "maximum frame time to force cache rebuild", "Maximum frame time to force cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_FRAME_TIME_TO_FORCE_CACHE_REFRESH, 1, 1000000); + input_scalar_field(gui, "minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000); + }); + break; + } + } text_button_wide("done menu", "Done", [&] { main.save_config(); main.g.load_theme(main.configPath, main.conf.themeCurrentlyLoaded);