diff --git a/CMakeLists.txt b/CMakeLists.txt index 51c887e..8361bf7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -101,6 +101,7 @@ set(sources "src/main.cpp" "src/GUIStuff/GUIManager.cpp" "src/GUIStuff/GUIManagerID.cpp" "src/GUIStuff/ClayImplement.cpp" + "src/GUIStuff/Elements/GUIStuffHelpers.cpp" "src/GUIStuff/Elements/Element.cpp" "src/GUIStuff/Elements/SVGIcon.cpp" "src/GUIStuff/Elements/RadioButton.cpp" @@ -112,6 +113,19 @@ set(sources "src/main.cpp" "src/GUIStuff/Elements/TextParagraph.cpp" "src/GUIStuff/Elements/FontPicker.cpp" "src/GUIStuff/Elements/TreeListing.cpp" + "src/GUIStuff/Elements/LayoutElement.cpp" + "src/GUIStuff/Elements/ColorRectangleDisplay.cpp" + "src/GUIStuff/Elements/ScrollArea.cpp" + "src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp" + "src/GUIStuff/Elements/ColorPickerShaders.cpp" + "src/GUIStuff/ElementHelpers/PopupHelpers.cpp" + "src/GUIStuff/ElementHelpers/ButtonHelpers.cpp" + "src/GUIStuff/ElementHelpers/LayoutHelpers.cpp" + "src/GUIStuff/ElementHelpers/TextBoxHelpers.cpp" + "src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp" + "src/GUIStuff/ElementHelpers/RadioButtonHelpers.cpp" + "src/GUIStuff/ElementHelpers/CheckBoxHelpers.cpp" + "src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp" "include/Helpers/Networking/NetLibrary.cpp" "include/Helpers/Networking/NetServer.cpp" "include/Helpers/Networking/NetClient.cpp" diff --git a/src/GUIStuff/ElementHelpers/ButtonHelpers.cpp b/src/GUIStuff/ElementHelpers/ButtonHelpers.cpp new file mode 100644 index 0000000..4bb696d --- /dev/null +++ b/src/GUIStuff/ElementHelpers/ButtonHelpers.cpp @@ -0,0 +1,47 @@ +#include "ButtonHelpers.hpp" +#include "../Elements/SVGIcon.hpp" +#include "TextLabelHelpers.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void text_button(GUIManager& gui, const char* id, std::string_view text, const TextButtonOptions& options) { + SelectableButton::Data d = selectable_button_options_to_data(options); + d.innerContent = [&gui, text] (const SelectableButton::InnerContentCallbackParameters&) { + text_label(gui, text); + }; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = options.wide ? CLAY_SIZING_GROW(0) : CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) }, + .childAlignment = {.x = options.centered ? CLAY_ALIGN_X_CENTER : CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + } + }) { + gui.element(id, options); + } +} + +void text_button_sized(GUIManager& gui, const char* id, std::string_view text, Clay_SizingAxis x, Clay_SizingAxis y, const TextButtonOptions& options) { + SelectableButton::Data d = selectable_button_options_to_data(options); + d.innerContent = [&gui, text] (const SelectableButton::InnerContentCallbackParameters&) { + text_label(gui, text); + }; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = x, .height = y }, + .childAlignment = {.x = options.centered ? CLAY_ALIGN_X_CENTER : CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + } + }) { + gui.element(id, options); + } +} + +void svg_icon_button(GUIManager& gui, const char* id, const std::string& svgPath, const SVGButtonOptions& options) { + SelectableButton::Data d = selectable_button_options_to_data(options); + d.innerContent = [&gui, &svgPath] (const SelectableButton::InnerContentCallbackParameters& p) { + gui.element("icon", svgPath, p.isSelected || p.isHovering || p.isHeld); + }; + CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(options.size), .height = CLAY_SIZING_FIXED(options.size) } } }) { + gui.element(id, options); + } +} + +}} diff --git a/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp b/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp new file mode 100644 index 0000000..e6e1251 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/ButtonHelpers.hpp @@ -0,0 +1,64 @@ +#pragma once +#include "../GUIManager.hpp" +#include "../Elements/SelectableButton.hpp" +#include "../Elements/ColorRectangleDisplay.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +constexpr static float BIG_BUTTON_SIZE = 30; +constexpr static float SMALL_BUTTON_SIZE = 20; + +template SelectableButton::Data selectable_button_options_to_data(const OptT& options) { + SelectableButton::Data toRet; + toRet.onClick = options.onClick; + toRet.isSelected = options.isSelected; + toRet.drawType = options.drawType; + return toRet; +} + +struct TextButtonOptions { + SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; + bool isSelected = false; + bool wide = false; + bool centered = true; + + std::function onClick; +}; + +struct SVGButtonOptions { + SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; + bool isSelected = false; + float size = BIG_BUTTON_SIZE; + + std::function onClick; +}; + +struct FixedSizeColorButtonOptions { + SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED; + bool isSelected = false; + bool hasAlpha = true; + float size = BIG_BUTTON_SIZE; + + std::function onClick; +}; + +void text_button(GUIManager& gui, const char* id, std::string_view text, const TextButtonOptions& options); +void text_button_sized(GUIManager& gui, const char* id, std::string_view text, Clay_SizingAxis x, Clay_SizingAxis y, const TextButtonOptions& options); +void svg_icon_button(GUIManager& gui, const char* id, const std::string& svgPath, const SVGButtonOptions& options); + +template void color_button(GUIManager& gui, const char* id, T* val, const FixedSizeColorButtonOptions& options) { + SelectableButton::Data d = selectable_button_options_to_data(options); + d.innerContent = [&] (const SelectableButton::InnerContentCallbackParameters&) { + gui.element([val, hasAlpha = options.hasAlpha] { + if(hasAlpha) + return SkColor4f{(*val)[0], (*val)[1], (*val)[2], 1.0f}; + else + return SkColor4f{(*val)[0], (*val)[1], (*val)[2], (*val)[3]}; + }); + }; + CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(options.size), .height = CLAY_SIZING_FIXED(options.size) } } }) { + gui.element(id, options); + } +} + +}} diff --git a/src/GUIStuff/ElementHelpers/CheckBoxHelpers.cpp b/src/GUIStuff/ElementHelpers/CheckBoxHelpers.cpp new file mode 100644 index 0000000..161cbfb --- /dev/null +++ b/src/GUIStuff/ElementHelpers/CheckBoxHelpers.cpp @@ -0,0 +1,30 @@ +#include "CheckBoxHelpers.hpp" +#include "LayoutHelpers.hpp" +#include "TextLabelHelpers.hpp" +#include "../Elements/CheckBox.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void checkbox_boolean(GUIManager& gui, const char* id, bool* d, const std::function& onClick) { + gui.element(id, [d]{ return *d; }, [d, onClick] { + *d = !(*d); + if(onClick) + onClick(); + }); +} + +void checkbox_field(GUIManager& gui, const char* id, std::string_view name, const std::function& isTicked, const std::function& onClick) { + left_to_right_line_layout(gui, [&]() { + gui.element(id, isTicked, onClick); + text_label(gui, name); + }); +} + +void checkbox_boolean_field(GUIManager& gui, const char* id, std::string_view name, bool* d, const std::function& onClick) { + left_to_right_line_layout(gui, [&]() { + checkbox_boolean(gui, id, d, onClick); + text_label(gui, name); + }); +} + +}} diff --git a/src/GUIStuff/ElementHelpers/CheckBoxHelpers.hpp b/src/GUIStuff/ElementHelpers/CheckBoxHelpers.hpp new file mode 100644 index 0000000..33fc304 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/CheckBoxHelpers.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "../GUIManager.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void checkbox_boolean(GUIManager& gui, const char* id, bool* d, const std::function& onClick = nullptr); +void checkbox_field(GUIManager& gui, const char* id, std::string_view name, const std::function& isTicked, const std::function& onClick); +void checkbox_boolean_field(GUIManager& gui, const char* id, std::string_view name, bool* d, const std::function& onClick = nullptr); + +}} diff --git a/src/GUIStuff/ElementHelpers/ColorPickerHelpers.hpp b/src/GUIStuff/ElementHelpers/ColorPickerHelpers.hpp new file mode 100644 index 0000000..a9d4d4d --- /dev/null +++ b/src/GUIStuff/ElementHelpers/ColorPickerHelpers.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "../GUIManager.hpp" +#include "TextBoxHelpers.hpp" +#include "../Elements/ColorPicker.hpp" +#include "../Elements/ColorPickerButton.decl.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +struct ColorPickerItemsOptions { + bool hasAlpha = true; + std::function onEdit; +}; + +template void color_picker_items(GUIManager& gui, const char* id, T* val, const ColorPickerItemsOptions& options = {}) { + gui.push_id(id); + auto fullOnEdit = [&gui, oE = options.onEdit, val]() { + oE(); + gui.set_to_layout(); + }; + gui.element("c", val, options.hasAlpha, fullOnEdit); + left_to_right_line_layout([&]() { + text_label(gui, "R"); + input_color_component_255("r", &(*val)[0], { .onEdit = fullOnEdit }); + text_label(gui, "G"); + input_color_component_255("g", &(*val)[1], { .onEdit = fullOnEdit }); + text_label(gui, "B"); + input_color_component_255("b", &(*val)[2], { .onEdit = fullOnEdit }); + if(options.hasAlpha) { + text_label(gui, "A"); + input_color_component_255("a", &(*val)[3], { .onEdit = fullOnEdit }); + } + }); + left_to_right_line_layout([&]() { + text_label(gui, "Hex"); + input_color_hex("h", val, { .onEdit = fullOnEdit, .hasAlpha = options.hasAlpha }); + }); + gui.pop_id(); +} + +template void color_picker_button_field(GUIManager& gui, const char* id, std::string_view name, T* val, const ColorPickerButtonData& options = {}) { + left_to_right_line_layout([&]() { + gui.element>(id, val, options); + text_label(gui, name); + }); +} + +} } diff --git a/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp b/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp new file mode 100644 index 0000000..ae53dd8 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/LayoutHelpers.cpp @@ -0,0 +1,53 @@ +#include "LayoutHelpers.hpp" +#include "../Elements/LayoutElement.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void top_to_bottom_window_popup_layout(GUIManager& gui, const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent, const LayoutElement::Callbacks& callbacks) { + gui.element(id, [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = x, .height = y }, + .padding = CLAY_PADDING_ALL(gui.io->theme->padding1), + .childGap = gui.io->theme->childGap1, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(gui.io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(10), + .floating = { .attachTo = CLAY_ATTACH_TO_PARENT }, + .border = {.color = convert_vec4(gui.io->theme->fillColor2), .width = CLAY_BORDER_OUTSIDE(1)} + }) { + innerContent(); + } + }, callbacks); +} + +void left_to_right_layout(GUIManager& gui, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent) { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = x, .height = y }, + .childGap = gui.io->theme->childGap1, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + innerContent(); + } +} + +void left_to_right_line_layout(GUIManager& gui, const std::function& innerContent) { + left_to_right_layout(gui, CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0), innerContent); +} + +void left_to_right_line_centered_layout(GUIManager& gui, const std::function& innerContent) { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .childGap = gui.io->theme->childGap1, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + innerContent(); + } +} + +}} diff --git a/src/GUIStuff/ElementHelpers/LayoutHelpers.hpp b/src/GUIStuff/ElementHelpers/LayoutHelpers.hpp new file mode 100644 index 0000000..851895c --- /dev/null +++ b/src/GUIStuff/ElementHelpers/LayoutHelpers.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../GUIManager.hpp" +#include "../Elements/LayoutElement.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void top_to_bottom_window_popup_layout(GUIManager& gui, const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent, const LayoutElement::Callbacks& callbacks); +void left_to_right_layout(GUIManager& gui, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent); +void left_to_right_line_layout(GUIManager& gui, const std::function& innerContent); +void left_to_right_line_centered_layout(GUIManager& gui, const std::function& innerContent); + +}} diff --git a/src/GUIStuff/ElementHelpers/NumberSliderHelpers.hpp b/src/GUIStuff/ElementHelpers/NumberSliderHelpers.hpp new file mode 100644 index 0000000..5451847 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/NumberSliderHelpers.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "../GUIManager.hpp" +#include "TextBoxHelpers.hpp" +#include "../Elements/NumberSlider.hpp" + +namespace GUIStuff { namespace ElementHelpers { + template bool slider_scalar_field(GUIManager& gui, const char* id, std::string_view name, T* val, T min, T max, TextBoxScalarOptions options = {}) { + gui.push_id(id); + options.onEdit = [&, oE = options.onEdit] { + oE(); + gui.set_to_layout(); + }; + left_to_right_line_layout([&]() { + text_label(gui, name); + input_scalar("textbox", val, min, max, options); + }); + gui.element>("slider", val, min, max, options.onEdit); + gui.pop_id(); + } +}} diff --git a/src/GUIStuff/ElementHelpers/PopupHelpers.cpp b/src/GUIStuff/ElementHelpers/PopupHelpers.cpp new file mode 100644 index 0000000..aa51bf8 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/PopupHelpers.cpp @@ -0,0 +1,23 @@ +#include "PopupHelpers.hpp" +#include "../Elements/LayoutElement.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void paint_circle_popup_menu(GUIManager& gui, const char* id, const Vector2f& centerPos, const PaintCircleMenu::Data& val) { + constexpr float size = 300.0f; + gui.element(id, [&] { + CLAY_AUTO_ID({.layout = { + .sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size)} + }, + .floating = { + .offset = {centerPos.x() - size * 0.5f, centerPos.y() - size * 0.5f}, + .zIndex = gui.zIndex, + .attachTo = CLAY_ATTACH_TO_ROOT + } + }) { + gui.element("paint circle menu", val); + } + }); +} + +}} diff --git a/src/GUIStuff/ElementHelpers/PopupHelpers.hpp b/src/GUIStuff/ElementHelpers/PopupHelpers.hpp new file mode 100644 index 0000000..7fdc614 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/PopupHelpers.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "../GUIManager.hpp" +#include "../Elements/PaintCircleMenu.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void paint_circle_popup_menu(GUIManager& gui, const char* id, const Vector2f& centerPos, const PaintCircleMenu::Data& val); + +}} diff --git a/src/GUIStuff/ElementHelpers/RadioButtonHelpers.cpp b/src/GUIStuff/ElementHelpers/RadioButtonHelpers.cpp new file mode 100644 index 0000000..51dfdf4 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/RadioButtonHelpers.cpp @@ -0,0 +1,15 @@ +#include "RadioButtonHelpers.hpp" +#include "../Elements/RadioButton.hpp" +#include "LayoutHelpers.hpp" +#include "TextLabelHelpers.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void radio_button_field(GUIManager& gui, const char* id, std::string_view name, const std::function& isTicked, const std::function& onClick) { + left_to_right_line_layout(gui, [&]() { + gui.element(id, isTicked, onClick); + text_label(gui, name); + }); +} + +}} diff --git a/src/GUIStuff/ElementHelpers/RadioButtonHelpers.hpp b/src/GUIStuff/ElementHelpers/RadioButtonHelpers.hpp new file mode 100644 index 0000000..46361e8 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/RadioButtonHelpers.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "../GUIManager.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void radio_button_field(GUIManager& gui, const char* id, std::string_view name, const std::function& isTicked, const std::function& onClick); + +template void radio_button_selector(GUIManager& gui, const char* id, T* data, const std::vector>& options, const std::function& onClick = nullptr) { + gui.push_id(id); + for(size_t i = 0; i < options.size(); i++) { + gui.push_id(i); + auto& option = options[i]; + radio_button_field(gui, "Option", option.first, [data, optionVal = option.second]{ return *data == optionVal; }, [data, optionVal = option.second, onClick]{ *data = optionVal; if(onClick) onClick(); }); + gui.pop_id(); + } + gui.pop_id(); +} + +}} diff --git a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp new file mode 100644 index 0000000..1cab205 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.cpp @@ -0,0 +1,45 @@ +#include "ScrollAreaHelpers.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void scroll_area_many_entries(GUIManager& gui, const char* id, const ScrollBarManyEntriesOptions& options) { + gui.element(id, ScrollArea::Options{ + .scrollVertical = true, + .clipHorizontal = options.clipHorizontal, + .clipVertical = true, + .showScrollbarY = true, + .innerContent = [&, options](const ScrollArea::InnerContentParameters& params) { + options.innerContentExtraCallback(params); + + float absScrollAmount = std::fabs(params.scrollOffset.y()); + size_t startPoint = absScrollAmount / options.entryHeight; + size_t elementsContainable = (params.containerDimensions.y() / options.entryHeight) + 2; // Displaying 2 more elements ensures that there isn't any empty space in the container + size_t endPoint = std::min(options.entryCount, startPoint + elementsContainable); + + if(startPoint != 0) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(startPoint * options.entryHeight)}, + } + }) { } + } + + for(size_t i = startPoint; i < endPoint; i++) { + gui.push_id(i); + options.elementContent(i); + gui.pop_id(); + } + + if(endPoint != options.entryCount) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((options.entryCount - endPoint) * options.entryHeight)}, + } + }) { } + } + } + }); +} + +}} diff --git a/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp new file mode 100644 index 0000000..5bf07c0 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "../Elements/ScrollArea.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +struct ScrollBarManyEntriesOptions { + float entryHeight; + size_t entryCount; + bool clipHorizontal = false; + std::function elementContent; + std::function innerContentExtraCallback; +}; + +void scroll_area_many_entries(GUIManager& gui, const char* id, const ScrollBarManyEntriesOptions& options); + +}} diff --git a/src/GUIStuff/ElementHelpers/TextBoxHelpers.cpp b/src/GUIStuff/ElementHelpers/TextBoxHelpers.cpp new file mode 100644 index 0000000..81abf6b --- /dev/null +++ b/src/GUIStuff/ElementHelpers/TextBoxHelpers.cpp @@ -0,0 +1,123 @@ +#include "TextBoxHelpers.hpp" +#include "../Elements/TextBox.impl.hpp" +#include "LayoutHelpers.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void input_color_component_255(GUIManager& gui, const char* id, float* val, const TextBoxOptions& options) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER + }; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [](const std::string& str) { + int roundTo255; + std::stringstream ss; + ss << str; + ss >> roundTo255; + return std::clamp(static_cast(roundTo255) / 255.0f, 0.0f, 1.0f); + }; + d.toStr = [](const float& a) { + int convertTo255 = a * 255; + std::stringstream ss; + ss << convertTo255; + return ss.str(); + }; + + gui.element>(id, d); +} + +void input_text(GUIManager& gui, const char* id, std::string* val, const TextBoxOptions& options) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT + }; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [](const std::string& a) { + return a; + }; + d.toStr = [](const std::string& a) { + return a; + }; + + gui.element>(id, d); +} + +void input_text_field(GUIManager& gui, const char* id, std::string_view name, std::string* val, const TextBoxOptions& options) { + left_to_right_line_layout(gui, [&]() { + text_label(gui, name); + input_text(gui, id, val, options); + }); +} + +void input_scalar(GUIManager& gui, const char* id, uint8_t* val, uint8_t minVal, uint8_t maxVal, const TextBoxScalarOptions& options) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER + }; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [minVal, maxVal](const std::string& a) { + if(a.empty()) + return minVal; + uint8_t toRet; + std::stringstream ss; + ss << a; + ss >> toRet; + if(ss.fail()) + return minVal; + return std::clamp(toRet, minVal, maxVal); + }; + d.toStr = [options](const uint8_t& a) { + return std::to_string(static_cast(a)); + }; + + gui.element>(id, d); +} + +void input_path(GUIManager& gui, const char* id, std::filesystem::path* val, const TextBoxPathOptions& options) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT + }; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [fileTypeRestriction = options.fileTypeRestriction](const std::string& a) { + std::filesystem::path toRet = std::filesystem::path(std::u8string_view(reinterpret_cast(a.c_str()), a.length())); + if(fileTypeRestriction != std::filesystem::file_type::none && std::filesystem::status(toRet).type() != fileTypeRestriction) + return std::optional(std::nullopt); + return std::optional(toRet); + }; + d.toStr = [](const std::filesystem::path& a) { + return a.string(); + }; + + gui.element>(id, d); +} + +void input_path_field(GUIManager& gui, const char* id, std::string_view name, std::filesystem::path* val, const TextBoxPathOptions& options) { + left_to_right_line_layout(gui, [&]() { + text_label(gui, name); + input_path(gui, id, val, options); + }); +} + +} } diff --git a/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp b/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp new file mode 100644 index 0000000..9000bcd --- /dev/null +++ b/src/GUIStuff/ElementHelpers/TextBoxHelpers.hpp @@ -0,0 +1,163 @@ +#pragma once +#include "../GUIManager.hpp" +#include "../Elements/TextBox.decl.hpp" +#include "TextLabelHelpers.hpp" +#include "LayoutHelpers.hpp" + +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; + return toRet; +} + +struct TextBoxOptions { + bool updateEveryEdit = false; + bool immutable = false; + std::function onEnter; + std::function onEdit; +}; + +struct TextBoxScalarOptions { + int decimalPrecision = 0; + bool updateEveryEdit = false; + bool immutable = false; + std::function onEnter; + std::function onEdit; +}; + +struct TextBoxPathOptions { + std::filesystem::file_type fileTypeRestriction; + bool updateEveryEdit = false; + bool immutable = false; + std::function onEnter; + std::function onEdit; +}; + +struct TextBoxHexColorOptions { + bool hasAlpha = true; + bool updateEveryEdit = false; + bool immutable = false; + std::function onEnter; + std::function onEdit; +}; + +void input_color_component_255(GUIManager& gui, const char* id, float* val, const TextBoxOptions& options = {}); +void input_text(GUIManager& gui, const char* id, std::string* val, const TextBoxOptions& options = {}); +void input_text_field(GUIManager& gui, const char* id, std::string_view name, std::string* val, const TextBoxOptions& options = {}); +void input_scalar(GUIManager& gui, const char* id, uint8_t* val, uint8_t minVal, uint8_t maxVal, const TextBoxScalarOptions& options = {}); +void input_path(GUIManager& gui, const char* id, std::filesystem::path* val, const TextBoxPathOptions& options = {}); +void input_path_field(GUIManager& gui, const char* id, std::string_view name, std::filesystem::path* val, const TextBoxPathOptions& options = {}); + +template void input_scalar(GUIManager& gui, const char* id, T* val, T minVal, T maxVal, const TextBoxScalarOptions& options = {}) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER + }; + if(std::is_signed()) + textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_SIGNED; + if(std::is_floating_point()) + textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_DECIMAL; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [minVal, maxVal](const std::string& a) { + if(a.empty()) + return minVal; + T toRet; + std::stringstream ss; + ss << a; + ss >> toRet; + if(ss.fail()) + return minVal; + return std::clamp(toRet, minVal, maxVal); + }; + d.toStr = [decimalPrecision = options.decimalPrecision, minVal, maxVal](const T& a) { + std::stringstream ss; + if(std::is_floating_point()) { + ss.precision(decimalPrecision); + ss << std::fixed << *a; + } + else + ss << *a; + return ss.str(); + }; + + gui.element>(id, d); +} + +template void input_scalars_field(GUIManager& gui, const char* id, std::string_view name, TContainer* val, size_t elemCount, T minVal, T maxVal, const TextBoxScalarOptions& options = {}) { + gui.push_id(id); + left_to_right_line_layout([&]() { + text_label(gui, name); + for(size_t i = 0; i < elemCount; i++) { + gui.push_id(i); + input_scalar(gui, "field", &(*val)[i], minVal, maxVal, options); + gui.pop_id(); + } + }); + gui.pop_id(); +} + +template void input_scalar_field(GUIManager& gui, const char* id, std::string_view name, T* val, T minVal, T maxVal, const TextBoxScalarOptions& options = {}) { + left_to_right_line_layout([&]() { + text_label(gui, name); + input_scalar(gui, id, val, minVal, maxVal, options); + }); +} + +template void input_color_hex(GUIManager& gui, const char* id, T* val, const TextBoxHexColorOptions& options = {}) { + InputManager::TextInputProperties textInputProps { + .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, + .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, + .autocorrect = false, + .multiline = false, + .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT + }; + + TextBoxData d = textbox_options_to_data(options); + d.textInputProps = textInputProps; + d.fromStr = [hasAlpha = options.hasAlpha](const std::string& a) { + T def = {0.0f, 0.0f, 0.0f, 1.0f}; + unsigned startIndex = 0; + if(a.empty()) + return def; + + if(a[0] == '#') + startIndex++; + + for(unsigned i = 0; i < (hasAlpha ? 4 : 3); i++) { + int byteColor; + std::stringstream ss; + size_t startingAt = startIndex + i * 2; + if(startingAt + 1 >= a.size()) + break; + ss << a.substr(startingAt, 2); + ss >> std::hex >> byteColor; + if(ss.fail()) + break; + def[i] = byteColor / 255.0f; + } + return def; + }; + d.toStr = [hasAlpha = options.hasAlpha](const T& a) { + std::stringstream ss; + ss << "#" << std::setfill('0') << std::setw(2) << std::hex; + for(size_t i = 0; i < (hasAlpha ? 4 : 3); i++) { + int convertTo255 = static_cast(std::clamp(a[i], 0, 1) * 255); + ss << std::setfill('0') << std::setw(2) << std::hex << convertTo255; + } + return ss.str(); + }; + + gui.element>(id, d); +} + +} } diff --git a/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp b/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp new file mode 100644 index 0000000..23029d4 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/TextLabelHelpers.cpp @@ -0,0 +1,43 @@ +#include "TextLabelHelpers.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void text_label_size(GUIManager& gui, std::string_view val, float modifier) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io->theme->frontColor1), .fontSize = static_cast(gui.io->fontSize * modifier)})); +} + +void text_label_color(GUIManager& gui, std::string_view val, const SkColor4f& color) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(color), .fontSize = gui.io->fontSize })); +} + +void text_label_light(GUIManager& gui, std::string_view val) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io->theme->frontColor2), .fontSize = gui.io->fontSize })); +} + +void text_label(GUIManager& gui, std::string_view val) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io->theme->frontColor1), .fontSize = gui.io->fontSize })); +} + +void text_label_centered(GUIManager& gui, std::string_view val) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + } + }) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io->theme->frontColor1), .fontSize = gui.io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); + } +} + +void text_label_light_centered(GUIManager& gui, std::string_view val) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + } + }) { + CLAY_TEXT(gui.strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(gui.io->theme->frontColor2), .fontSize = gui.io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); + } +} + +}} diff --git a/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp b/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp new file mode 100644 index 0000000..9a58d98 --- /dev/null +++ b/src/GUIStuff/ElementHelpers/TextLabelHelpers.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "../GUIManager.hpp" + +namespace GUIStuff { namespace ElementHelpers { + +void text_label_color(GUIManager& gui, std::string_view val, const SkColor4f& color); +void text_label_size(GUIManager& gui, std::string_view val, float modifier); +void text_label_light(GUIManager& gui, std::string_view val); +void text_label(GUIManager& gui, std::string_view val); +void text_label_light_centered(GUIManager& gui, std::string_view val); +void text_label_centered(GUIManager& gui, std::string_view val); + +}} diff --git a/src/GUIStuff/Elements/CheckBox.cpp b/src/GUIStuff/Elements/CheckBox.cpp index 224e874..9edff5e 100644 --- a/src/GUIStuff/Elements/CheckBox.cpp +++ b/src/GUIStuff/Elements/CheckBox.cpp @@ -1,28 +1,39 @@ #include "CheckBox.hpp" #include #include +#include "../GUIManager.hpp" namespace GUIStuff { -void CheckBox::update(UpdateInputData& io, bool newIsTicked, const std::function& elemUpdate) { +CheckBox::CheckBox(GUIManager& gui): + Element(gui) {} + +void CheckBox::layout(const std::function& isTicked, const std::function& onClick) { + this->isTicked = isTicked; + this->onClick = onClick; + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(15), .height = CLAY_SIZING_FIXED(15)} }, .custom = { .customData = this } - }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(isTicked != newIsTicked) { - hoverAnimation2 = selection.hovered ? CHECKBOX_ANIMATION_TIME : 0.0f; - isTicked = newIsTicked; - } - if(elemUpdate) - elemUpdate(); - } + }) {} +} + +bool CheckBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + isHovering = mouseHovering; + if(isHovering && button.button == InputManager::MouseButton::LEFT && button.down) + gui.set_post_callback_func([&](){if(onClick) onClick();}); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool CheckBox::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + isHovering = mouseHovering; + return Element::input_mouse_motion_callback(motion, mouseHovering); } void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - auto bb = get_bb(command); + auto& bb = boundingBox.value(); canvas->save(); canvas->translate(bb.min.x(), bb.min.y()); @@ -31,7 +42,7 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma SkPaint p; p.setAntiAlias(skiaAA); - if(isTicked) { + if(isTicked()) { SkRect checkBox = SkRect::MakeLTRB(-0.5f, -0.5f, 0.5f, 0.5f); p.setColor4f(convert_vec4(io.theme->fillColor1)); p.setStyle(SkPaint::kFill_Style); @@ -39,14 +50,13 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma } else { SkRect checkBox = SkRect::MakeLTRB(-0.45f, -0.45f, 0.45f, 0.45f); - p.setColor4f(convert_vec4(selection.hovered ? io.theme->fillColor1 : io.theme->fillColor2)); + p.setColor4f(convert_vec4(isHovering ? io.theme->fillColor1 : io.theme->fillColor2)); p.setStyle(SkPaint::kStroke_Style); p.setStrokeWidth(0.15f); canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p); } - - if(isTicked) { + if(isTicked()) { SkPaint checkP; checkP.setAntiAlias(skiaAA); checkP.setColor4f(io.theme->backColor1); @@ -67,7 +77,7 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma static BezierEasing anim{0.445, -0.733, 0.575, 1.627}; - float lerpTime2 = anim(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, CHECKBOX_ANIMATION_TIME)); + float lerpTime2 = anim(smooth_two_way_time(hoverAnimation2, io.deltaTime, isHovering, CHECKBOX_ANIMATION_TIME)); std::array points; diff --git a/src/GUIStuff/Elements/CheckBox.hpp b/src/GUIStuff/Elements/CheckBox.hpp index 601fc1f..ea65df3 100644 --- a/src/GUIStuff/Elements/CheckBox.hpp +++ b/src/GUIStuff/Elements/CheckBox.hpp @@ -5,13 +5,19 @@ namespace GUIStuff { class CheckBox : public Element { public: - void update(UpdateInputData& io, bool newIsTicked, const std::function& elemUpdate); + CheckBox(GUIManager& gui); + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; - SelectionHelper selection; + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + + void layout(const std::function& isTicked, const std::function& onClick); private: static constexpr float CHECKBOX_ANIMATION_TIME = 0.3; - bool isTicked = false; + bool isHovering = false; float hoverAnimation2 = 0.0; + std::function isTicked; + std::function onClick; }; } diff --git a/src/GUIStuff/Elements/ColorPicker.decl.hpp b/src/GUIStuff/Elements/ColorPicker.decl.hpp new file mode 100644 index 0000000..25d1c54 --- /dev/null +++ b/src/GUIStuff/Elements/ColorPicker.decl.hpp @@ -0,0 +1,102 @@ +#pragma once +#include "Element.hpp" +#include +#include +#include +#include +#include +#include + +namespace GUIStuff { + +namespace ColorPickerShaders { + // HSV to RGB func from: https://github.com/hughsk/glsl-hsv2rgb?tab=readme-ov-file + static constexpr const char* hueBarSkSl = + R"V( + vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + } + + vec4 main(vec2 fragCoord) { + vec3 a = hsv2rgb(vec3(1.0 - fragCoord.y, 1.0, 1.0)); + return vec4(a.xyz, 1.0); + })V"; + + static constexpr const char* svSelectionAreaSkSl = + R"V( + uniform float hue; + vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + } + + vec4 main(vec2 fragCoord) { + vec3 a = hsv2rgb(vec3(hue, fragCoord.x, 1.0 - fragCoord.y)); + return vec4(a.xyz, 1.0); + })V"; + + static constexpr const char* alphaBarSksl = + R"V( + uniform vec3 mainColor; + uniform float horizontalResolution; + + vec4 main(vec2 fragcoord) { + vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0); + vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0); + + vec2 flooredCoords = floor(fragcoord / 10); + bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5; + + return mix(isDark ? alphaBoard2 : alphaBoard1, vec4(mainColor, 1.0), fragcoord.x / horizontalResolution); + })V"; + + sk_sp get_hue_shader(); + sk_sp get_sv_selection_shader(float hue); + sk_sp get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution); +} + +template class ColorPicker : public Element { + public: + ColorPicker(GUIManager& gui); + void layout(T* data, bool selectAlpha, const std::function& onChange); + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA); + bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + private: + void update_color_picker_pos(const Vector2f& p); + float get_sv_selection_area_size(); + Vector2f get_hue_bar_pos(); + Vector2f get_hue_bar_dim(); + Vector2f get_alpha_bar_pos(); + Vector2f get_alpha_bar_dim(); + void set_hsv(const Vector3f& hsv); + + static constexpr float BAR_WIDTH = 30.0; + static constexpr float BAR_GAP = 5.0; + + + static sk_sp hueBarShader; + static sk_sp svSelectionAreaEffect; + static sk_sp alphaBarEffect; + + static sk_sp get_hue_shader(); + static sk_sp get_sv_selection_shader(float hue); + static sk_sp get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution); + + T* data; + Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI + std::function onChange; + bool selectAlpha = false; + enum class HeldBar { + NONE, + SV_HELD, + HUE_HELD, + ALPHA_HELD + }; + HeldBar held = HeldBar::NONE; +}; + +} diff --git a/src/GUIStuff/Elements/ColorPicker.hpp b/src/GUIStuff/Elements/ColorPicker.hpp index 0a3de65..d45c611 100644 --- a/src/GUIStuff/Elements/ColorPicker.hpp +++ b/src/GUIStuff/Elements/ColorPicker.hpp @@ -1,291 +1,3 @@ #pragma once -#include "Element.hpp" -#include -#include -#include -#include -#include -#include - -namespace GUIStuff { - -template class ColorPicker : public Element { - public: - bool update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function& elemUpdate) { - bool isChanged = false; - - T* data = newData; - selectAlpha = newSelectAlpha; - - if(!data) - return false; - - force_update_colorpicker(data); - - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } - }, - .aspectRatio = {1.0f}, - .custom = { .customData = this } - }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(selection.clicked && data) { - float svSelectionAreaSize = get_sv_selection_area_size(); - modifyingSv = SCollision::collide(SCollision::AABB(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), io.mouse.pos); - Vector2f hueBarPos = get_hue_bar_pos(); - Vector2f hueBarDim = get_hue_bar_dim(); - modifyingHue = SCollision::collide(SCollision::AABB(hueBarPos, hueBarPos + hueBarDim), io.mouse.pos); - if(selectAlpha) { - Vector2f alphaBarPos = get_alpha_bar_pos(); - Vector2f alphaBarDim = get_alpha_bar_dim(); - modifyingAlpha = SCollision::collide(SCollision::AABB(alphaBarPos, alphaBarPos + alphaBarDim), io.mouse.pos); - } - isChanged = true; - } - if(selection.held && modifyingSv) { - float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH); - Vector2f newSv = cwise_vec_clamp((io.mouse.pos - bb.min) / svSelectionAreaSize, Vector2f{0.0f, 0.0f}, Vector2f{1.0f, 1.0f}); - savedHsv.y() = newSv.x(); - savedHsv.z() = 1.0f - newSv.y(); - set_hsv(data, savedHsv); - isChanged = true; - } - if(selection.held && modifyingHue) { - Vector2f huePos = get_hue_bar_pos(); - Vector2f hueDim = get_hue_bar_dim(); - savedHsv.x() = (1.0f - std::clamp((io.mouse.pos.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f; - set_hsv(data, savedHsv); - isChanged = true; - } - if(selection.held && modifyingAlpha) { - Vector2f alphaPos = get_alpha_bar_pos(); - Vector2f alphaDim = get_alpha_bar_dim(); - (*data)[3] = std::clamp((io.mouse.pos.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f); - oldData = *data; - isChanged = true; - } - if(!selection.held) { - modifyingSv = false; - modifyingHue = false; - modifyingAlpha = false; - } - if(elemUpdate) - elemUpdate(); - } - - return isChanged; - } - - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - bb = get_bb(command); - - canvas->save(); - canvas->translate(bb.min.x(), bb.min.y()); - float svSelectionAreaSize = get_sv_selection_area_size(); - canvas->scale(svSelectionAreaSize, svSelectionAreaSize); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); - - SkPaint svSelectionAreaPaint; - svSelectionAreaPaint.setShader(get_sv_selection_shader(savedHsv.x() / 360.0f)); - canvas->drawPaint(svSelectionAreaPaint); - - SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f}); - selectionLinePaint.setAntiAlias(skiaAA); - selectionLinePaint.setStrokeWidth(2.0f / svSelectionAreaSize); - canvas->drawLine(0.0f, 1.0f - savedHsv.z(), 1.0f, 1.0f - savedHsv.z(), selectionLinePaint); - canvas->drawLine(savedHsv.y(), 0.0f, savedHsv.y(), 1.0f, selectionLinePaint); - - canvas->restore(); - - float normalizedHue = savedHsv.x() / 360.0f; - - Vector2f hueBarPos = get_hue_bar_pos(); - Vector2f hueBarDim = get_hue_bar_dim(); - canvas->save(); - canvas->translate(hueBarPos.x(), hueBarPos.y()); - canvas->scale(hueBarDim.x(), hueBarDim.y()); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); - - SkPaint hueBarPaint; - hueBarPaint.setShader(get_hue_shader()); - canvas->drawPaint(hueBarPaint); - canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint); - - canvas->restore(); - - Vector2f alphaBarPos = get_alpha_bar_pos(); - Vector2f alphaBarDim = get_alpha_bar_dim(); - canvas->save(); - canvas->translate(alphaBarPos.x(), alphaBarPos.y()); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()), skiaAA); - - SkPaint alphaBarPaint; - if(selectAlpha) - alphaBarPaint.setShader(get_alpha_bar_shader({oldData[0], oldData[1], oldData[2]}, alphaBarDim.x())); - else - alphaBarPaint.setColor4f(SkColor4f{oldData[0], oldData[1], oldData[2], 1.0f}); - canvas->drawPaint(alphaBarPaint); - - if(selectAlpha) { - canvas->scale(alphaBarDim.x(), alphaBarDim.y()); - canvas->drawLine(oldData[3], 0.0f, oldData[3], 1.0f, selectionLinePaint); - } - - canvas->restore(); - } - private: - void force_update_colorpicker(T* data) { - if(data && (*data == oldData)) - return; - - if(data) { - savedHsv = rgb_to_hsv(*data); - oldData = *data; - } - else - oldData = T{0, 0, 0, 0}; - } - - float get_sv_selection_area_size() { - return bb.width() - (BAR_GAP + BAR_WIDTH); - } - - Vector2f get_hue_bar_pos() { - return {bb.min.x() + get_sv_selection_area_size() + BAR_GAP, bb.min.y()}; - } - - Vector2f get_hue_bar_dim() { - return {BAR_WIDTH, get_sv_selection_area_size()}; - } - - Vector2f get_alpha_bar_pos() { - return {bb.min.x(), bb.min.y() + get_sv_selection_area_size() + BAR_GAP}; - } - - Vector2f get_alpha_bar_dim() { - return {bb.width(), BAR_WIDTH}; - } - - void set_hsv(T* data, const Vector3f& hsv) { - Vector3f a = hsv_to_rgb(hsv); - (*data)[0] = a.x(); - (*data)[1] = a.y(); - (*data)[2] = a.z(); - oldData = *data; - } - - static constexpr float BAR_WIDTH = 30.0; - static constexpr float BAR_GAP = 5.0; - - // HSV to RGB func from: https://github.com/hughsk/glsl-hsv2rgb?tab=readme-ov-file - static constexpr const char* hueBarSkSl = - R"V( - vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); - } - - vec4 main(vec2 fragCoord) { - vec3 a = hsv2rgb(vec3(1.0 - fragCoord.y, 1.0, 1.0)); - return vec4(a.xyz, 1.0); - })V"; - - static constexpr const char* svSelectionAreaSkSl = - R"V(uniform float hue; - vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); - } - - vec4 main(vec2 fragCoord) { - vec3 a = hsv2rgb(vec3(hue, fragCoord.x, 1.0 - fragCoord.y)); - return vec4(a.xyz, 1.0); - })V"; - - static constexpr const char* alphaBarSksl = - R"V(uniform vec3 mainColor; -uniform float horizontalResolution; - -vec4 main(vec2 fragcoord) { - vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0); - vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0); - - vec2 flooredCoords = floor(fragcoord / 10); - bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5; - - return mix(isDark ? alphaBoard2 : alphaBoard1, vec4(mainColor, 1.0), fragcoord.x / horizontalResolution); -} - )V"; - - static sk_sp hueBarShader; - static sk_sp svSelectionAreaEffect; - static sk_sp alphaBarEffect; - - static sk_sp get_hue_shader(); - static sk_sp get_sv_selection_shader(float hue); - static sk_sp get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution); - - SCollision::AABB bb; - SelectionHelper selection; - T oldData; - Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI - bool selectAlpha; - bool modifyingSv = false; - bool modifyingHue = false; - bool modifyingAlpha = false; -}; - -template sk_sp ColorPicker::hueBarShader; -template sk_sp ColorPicker::svSelectionAreaEffect; -template sk_sp ColorPicker::alphaBarEffect; - -template sk_sp ColorPicker::get_hue_shader(){ - if(!hueBarShader) { - auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(hueBarSkSl)); - if(!err.isEmpty()) { - std::cout << "[ColorPicker::get_hue_shader] Hue shader construction error\n"; - std::cout << err.c_str() << std::endl; - } - else - hueBarShader = effect->makeShader(nullptr, {nullptr, 0}); - } - return hueBarShader; -} - -template sk_sp ColorPicker::get_sv_selection_shader(float hue) { - if(!svSelectionAreaEffect) { - auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(svSelectionAreaSkSl)); - if(!err.isEmpty()) { - std::cout << "[ColorPicker::get_sv_selection_shader] SV Selection shader construction error\n"; - std::cout << err.c_str() << std::endl; - return nullptr; - } - else - svSelectionAreaEffect = effect; - } - SkRuntimeShaderBuilder builder(svSelectionAreaEffect); - builder.uniform("hue") = hue; - return builder.makeShader(); -} - -template sk_sp ColorPicker::get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) { - if(!alphaBarEffect) { - auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBarSksl)); - if(!err.isEmpty()) { - std::cout << "[ColorPicker::get_alpha_bar_shader] Alpha bar shader construction error\n"; - std::cout << err.c_str() << std::endl; - return nullptr; - } - else - alphaBarEffect = effect; - } - SkRuntimeShaderBuilder builder(alphaBarEffect); - builder.uniform("mainColor") = SkV3{mainColor.x(), mainColor.y(), mainColor.z()}; - builder.uniform("horizontalResolution") = horizontalResolution; - return builder.makeShader(); -} - -} +#include "ColorPicker.decl.hpp" +#include "ColorPicker.impl.hpp" diff --git a/src/GUIStuff/Elements/ColorPicker.impl.hpp b/src/GUIStuff/Elements/ColorPicker.impl.hpp new file mode 100644 index 0000000..c728df3 --- /dev/null +++ b/src/GUIStuff/Elements/ColorPicker.impl.hpp @@ -0,0 +1,170 @@ +#pragma once +#include "ColorPicker.decl.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { + +template void ColorPicker::layout(T* data, bool selectAlpha, const std::function& onChange) { + this->data = data; + savedHsv = rgb_to_hsv(*data); + this->selectAlpha = selectAlpha; + this->onChange = onChange; + + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } + }, + .aspectRatio = {1.0f}, + .custom = { .customData = this } + }) {} +} + +template void ColorPicker::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + float svSelectionAreaSize = get_sv_selection_area_size(); + canvas->scale(svSelectionAreaSize, svSelectionAreaSize); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); + + SkPaint svSelectionAreaPaint; + svSelectionAreaPaint.setShader(get_sv_selection_shader(savedHsv.x() / 360.0f)); + canvas->drawPaint(svSelectionAreaPaint); + + SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f}); + selectionLinePaint.setAntiAlias(skiaAA); + selectionLinePaint.setStrokeWidth(2.0f / svSelectionAreaSize); + canvas->drawLine(0.0f, 1.0f - savedHsv.z(), 1.0f, 1.0f - savedHsv.z(), selectionLinePaint); + canvas->drawLine(savedHsv.y(), 0.0f, savedHsv.y(), 1.0f, selectionLinePaint); + + canvas->restore(); + + float normalizedHue = savedHsv.x() / 360.0f; + + Vector2f hueBarPos = get_hue_bar_pos(); + Vector2f hueBarDim = get_hue_bar_dim(); + canvas->save(); + canvas->translate(hueBarPos.x(), hueBarPos.y()); + canvas->scale(hueBarDim.x(), hueBarDim.y()); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); + + SkPaint hueBarPaint; + hueBarPaint.setShader(get_hue_shader()); + canvas->drawPaint(hueBarPaint); + canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint); + + canvas->restore(); + + Vector2f alphaBarPos = get_alpha_bar_pos(); + Vector2f alphaBarDim = get_alpha_bar_dim(); + canvas->save(); + canvas->translate(alphaBarPos.x(), alphaBarPos.y()); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()), skiaAA); + + SkPaint alphaBarPaint; + if(selectAlpha) + alphaBarPaint.setShader(get_alpha_bar_shader({(*data)[0], (*data)[1], (*data)[2]}, alphaBarDim.x())); + else + alphaBarPaint.setColor4f(SkColor4f{(*data)[0], (*data)[1], (*data)[2], 1.0f}); + canvas->drawPaint(alphaBarPaint); + + if(selectAlpha) { + canvas->scale(alphaBarDim.x(), alphaBarDim.y()); + canvas->drawLine((*data)[3], 0.0f, (*data)[3], 1.0f, selectionLinePaint); + } + + canvas->restore(); +} + +template bool ColorPicker::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + held = HeldBar::NONE; + if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down && boundingBox.has_value()) { + auto& bb = boundingBox.value(); + float svSelectionAreaSize = get_sv_selection_area_size(); + if(SCollision::collide(SCollision::AABB(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), button.pos)) + held = HeldBar::SV_HELD; + else { + Vector2f hueBarPos = get_hue_bar_pos(); + Vector2f hueBarDim = get_hue_bar_dim(); + if(SCollision::collide(SCollision::AABB(hueBarPos, hueBarPos + hueBarDim), button.pos)) + held = HeldBar::HUE_HELD; + else if(selectAlpha) { + Vector2f alphaBarPos = get_alpha_bar_pos(); + Vector2f alphaBarDim = get_alpha_bar_dim(); + if(SCollision::collide(SCollision::AABB(alphaBarPos, alphaBarPos + alphaBarDim), button.pos)) + held = HeldBar::ALPHA_HELD; + } + } + } + update_color_picker_pos(button.pos); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +template bool ColorPicker::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + update_color_picker_pos(motion.pos); + return Element::input_mouse_button_callback(motion, mouseHovering); +} + +template void ColorPicker::update_color_picker_pos(const Vector2f& p) { + if(held != HeldBar::NONE && boundingBox.has_value()) { + gui.set_post_callback_func([&, p]() { + auto& bb = boundingBox.value(); + switch(held) { + case HeldBar::SV_HELD: { + float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH); + Vector2f newSv = cwise_vec_clamp((p - bb.min) / svSelectionAreaSize, Vector2f{0.0f, 0.0f}, Vector2f{1.0f, 1.0f}); + savedHsv.y() = newSv.x(); + savedHsv.z() = 1.0f - newSv.y(); + set_hsv(savedHsv); + gui.set_post_callback_func([&]{if(onChange) onChange();}); + break; + } + case HeldBar::HUE_HELD: { + Vector2f huePos = get_hue_bar_pos(); + Vector2f hueDim = get_hue_bar_dim(); + savedHsv.x() = (1.0f - std::clamp((p.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f; + set_hsv(savedHsv); + gui.set_post_callback_func([&]{if(onChange) onChange();}); + break; + } + case HeldBar::ALPHA_HELD: { + Vector2f alphaPos = get_alpha_bar_pos(); + Vector2f alphaDim = get_alpha_bar_dim(); + (*data)[3] = std::clamp((p.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f); + gui.set_post_callback_func([&]{if(onChange) onChange();}); + break; + } + } + }); + } +} + +template float ColorPicker::get_sv_selection_area_size() { + return boundingBox.value().width() - (BAR_GAP + BAR_WIDTH); +} + +template Vector2f ColorPicker::get_hue_bar_pos() { + return {boundingBox.value().min.x() + get_sv_selection_area_size() + BAR_GAP, boundingBox.value().min.y()}; +} + +template Vector2f ColorPicker::get_hue_bar_dim() { + return {BAR_WIDTH, get_sv_selection_area_size()}; +} + +template Vector2f ColorPicker::get_alpha_bar_pos() { + return {boundingBox.value().min.x(), boundingBox.value().min.y() + get_sv_selection_area_size() + BAR_GAP}; +} + +template Vector2f ColorPicker::get_alpha_bar_dim() { + return {boundingBox.value().width(), BAR_WIDTH}; +} + +template void ColorPicker::set_hsv(const Vector3f& hsv) { + Vector3f a = hsv_to_rgb(hsv); + (*data)[0] = a.x(); + (*data)[1] = a.y(); + (*data)[2] = a.z(); +} + +} diff --git a/src/GUIStuff/Elements/ColorPickerButton.decl.hpp b/src/GUIStuff/Elements/ColorPickerButton.decl.hpp new file mode 100644 index 0000000..de921de --- /dev/null +++ b/src/GUIStuff/Elements/ColorPickerButton.decl.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +struct ColorPickerButtonData { + bool hasAlpha = false; + std::function onEdit; +}; + +template class ColorPickerButton : public Element { + public: + ColorPickerButton(GUIManager& gui); + void layout(T* val, ColorPickerButtonData& d); + private: + ColorPickerButtonData data; + bool isOpen = false; +}; + +} diff --git a/src/GUIStuff/Elements/ColorPickerButton.hpp b/src/GUIStuff/Elements/ColorPickerButton.hpp new file mode 100644 index 0000000..52564ed --- /dev/null +++ b/src/GUIStuff/Elements/ColorPickerButton.hpp @@ -0,0 +1,3 @@ +#pragma once +#include "ColorPickerButton.decl.hpp" +#include "ColorPickerButton.impl.hpp" diff --git a/src/GUIStuff/Elements/ColorPickerButton.impl.hpp b/src/GUIStuff/Elements/ColorPickerButton.impl.hpp new file mode 100644 index 0000000..cfae679 --- /dev/null +++ b/src/GUIStuff/Elements/ColorPickerButton.impl.hpp @@ -0,0 +1,35 @@ +#include "ColorPickerButton.decl.hpp" +#include "../ElementHelpers/ColorPickerHelpers.hpp" +#include "../ElementHelpers/ButtonHelpers.hpp" + +namespace GUIStuff { + +template ColorPickerButton::ColorPickerButton(GUIManager& gui): Element(gui) {} + +template void ColorPickerButton::layout(T* val, ColorPickerButtonData& d) { + this->data = d; + + GUIStuff::ElementHelpers::color_button(gui, "Color Picker Button", val, { + .isSelected = isOpen, + .onClick = [&]{ + isOpen = !isOpen; + gui.set_to_layout(); + } + }); + if(isOpen) { + top_to_bottom_window_popup_layout("Color Picker", CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&, val, data = data]() { + ElementHelpers::color_picker_items(gui, "c", val, { + .hasAlpha = data.hasAlpha, + .onEdit = data.onEdit + }); + }, { + .mouseButton = [&](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + if(!mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) + isOpen = false; + return mouseHovering; + } + }); + } +} + +} diff --git a/src/GUIStuff/Elements/ColorPickerShaders.cpp b/src/GUIStuff/Elements/ColorPickerShaders.cpp new file mode 100644 index 0000000..950ce4f --- /dev/null +++ b/src/GUIStuff/Elements/ColorPickerShaders.cpp @@ -0,0 +1,55 @@ +#include "ColorPicker.decl.hpp" +#include + +namespace GUIStuff { namespace ColorPickerShaders { + +sk_sp get_hue_shader() { + static sk_sp hueBarShader; + if(!hueBarShader) { + auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(hueBarSkSl)); + if(!err.isEmpty()) { + std::cout << "[ColorPicker::get_hue_shader] Hue shader construction error\n"; + std::cout << err.c_str() << std::endl; + } + else + hueBarShader = effect->makeShader(nullptr, {nullptr, 0}); + } + return hueBarShader; +} + +sk_sp get_sv_selection_shader(float hue) { + static sk_sp svSelectionAreaEffect; + if(!svSelectionAreaEffect) { + auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(svSelectionAreaSkSl)); + if(!err.isEmpty()) { + std::cout << "[ColorPicker::get_sv_selection_shader] SV Selection shader construction error\n"; + std::cout << err.c_str() << std::endl; + return nullptr; + } + else + svSelectionAreaEffect = effect; + } + SkRuntimeShaderBuilder builder(svSelectionAreaEffect); + builder.uniform("hue") = hue; + return builder.makeShader(); +} + +sk_sp get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) { + static sk_sp alphaBarEffect; + if(!alphaBarEffect) { + auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBarSksl)); + if(!err.isEmpty()) { + std::cout << "[ColorPicker::get_alpha_bar_shader] Alpha bar shader construction error\n"; + std::cout << err.c_str() << std::endl; + return nullptr; + } + else + alphaBarEffect = effect; + } + SkRuntimeShaderBuilder builder(alphaBarEffect); + builder.uniform("mainColor") = SkV3{mainColor.x(), mainColor.y(), mainColor.z()}; + builder.uniform("horizontalResolution") = horizontalResolution; + return builder.makeShader(); +} + +}} diff --git a/src/GUIStuff/Elements/ColorRectangleDisplay.cpp b/src/GUIStuff/Elements/ColorRectangleDisplay.cpp new file mode 100644 index 0000000..be78ba7 --- /dev/null +++ b/src/GUIStuff/Elements/ColorRectangleDisplay.cpp @@ -0,0 +1,49 @@ +#include "ColorRectangleDisplay.hpp" + +namespace GUIStuff { + +ColorRectangleDisplay::ColorRectangleDisplay(GUIManager& gui): Element(gui) {} + +void ColorRectangleDisplay::layout(const std::function& colorFunc) { + this->colorFunc = colorFunc; +} + +void ColorRectangleDisplay::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + SkColor4f color = colorFunc(); + SkRect r = SkRect::MakeLTRB(bb.min.x(), bb.min.y(), bb.max.x(), bb.max.y()); + + if(color.fA == 1.0f) { + SkPaint p; + p.setColor4f(color); + p.setAntiAlias(skiaAA); + canvas->drawRect(r, p); + } + else { + canvas->save(); + canvas->clipRect(r, skiaAA); + SkPaint alphaPaint; + alphaPaint.setShader(get_alpha_background_shader()); + canvas->drawPaint(alphaPaint); + canvas->drawPaint(SkPaint{color}); + canvas->restore(); + } +} + +sk_sp ColorRectangleDisplay::get_alpha_background_shader() { + if(!alphaBackgroundEffect) { + auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBackgroundSksl)); + if(!err.isEmpty()) { + std::cout << "[ColorRectangleDisplay::get_alpha_background_shader] Alpha background shader construction error\n"; + std::cout << err.c_str() << std::endl; + return nullptr; + } + else + alphaBackgroundEffect = effect; + } + SkRuntimeShaderBuilder builder(alphaBackgroundEffect); + return builder.makeShader(); +} + +} diff --git a/src/GUIStuff/Elements/ColorRectangleDisplay.hpp b/src/GUIStuff/Elements/ColorRectangleDisplay.hpp new file mode 100644 index 0000000..9fb7fec --- /dev/null +++ b/src/GUIStuff/Elements/ColorRectangleDisplay.hpp @@ -0,0 +1,31 @@ +#pragma once +#include "Element.hpp" +#include + +namespace GUIStuff { + +class ColorRectangleDisplay : public Element { + public: + ColorRectangleDisplay(GUIManager& gui); + void layout(const std::function& colorFunc); + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + private: + static constexpr const char* alphaBackgroundSksl = + R"V( +vec4 main(vec2 fragcoord) { + vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0); + vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0); + + vec2 flooredCoords = floor(fragcoord / 10); + bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5; + + return isDark ? alphaBoard2 : alphaBoard1; +} + )V"; + std::function colorFunc; + + static sk_sp alphaBackgroundEffect; + static sk_sp get_alpha_background_shader(); +}; + +} diff --git a/src/GUIStuff/Elements/DropDown.hpp b/src/GUIStuff/Elements/DropDown.hpp new file mode 100644 index 0000000..6ee9541 --- /dev/null +++ b/src/GUIStuff/Elements/DropDown.hpp @@ -0,0 +1,152 @@ +#pragma once +#include "Element.hpp" +#include "SelectableButton.hpp" +#include "../ElementHelpers/ScrollAreaHelpers.hpp" +#include "../ElementHelpers/LayoutHelpers.hpp" +#include "../ElementHelpers/TextLabelHelpers.hpp" +#include "../ElementHelpers/ButtonHelpers.hpp" +#include "SVGIcon.hpp" + +namespace GUIStuff { + +struct DropdownOptions { + float width = 200.0f; + std::function onClick; +}; + +template class DropDown : public Element { + public: + DropDown(GUIManager& gui): Element(gui) {} + void layout(T* data, const std::vector& selections, const DropdownOptions& options = {}) { + opts = options; + ElementHelpers::left_to_right_layout(gui, CLAY_SIZING_FIXED(opts.width), CLAY_SIZING_FIT(0), [&] { + gui.element("dropdown", SelectableButton::Data{ + .drawType = SelectableButton::DrawType::FILLED, + .isSelected = isOpen, + .onClick = [&] { + isOpen = !isOpen; + gui.set_to_layout(); + }, + .innerContent = [&](const SelectableButton::InnerContentCallbackParameters&) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, + .padding = {.left = 4, .right = 4}, + .childGap = gui.io->theme->childGap1, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT, + } + }) { + ElementHelpers::text_label(gui, selections[*data]); + CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(ElementHelpers::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIT(ElementHelpers::SMALL_BUTTON_SIZE)} + } + }) { + gui.element("dropico", "data/icons/droparrow.svg", isOpen); + } + } + } + }); + + if(isOpen) { + Clay_ElementId localID = CLAY_ID_LOCAL("DROPDOWN"); + Clay_ElementData dropdownElemData = Clay_GetElementData(localID); + float calculatedDropdownMaxHeight = 0.0f; + if(dropdownElemData.found) + calculatedDropdownMaxHeight = std::max(gui.io->windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f); + CLAY(localID, { + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(opts.width), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)}, + .childGap = 0 + }, + .backgroundColor = convert_vec4(gui.io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(4), + .floating = { + .offset = { + .y = 4 + }, + .attachPoints = { + .element = CLAY_ATTACH_POINT_LEFT_TOP, + .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM + }, + .attachTo = CLAY_ATTACH_TO_PARENT + }, + .border = { + .color = convert_vec4(gui.io->theme->fillColor2), + .width = CLAY_BORDER_OUTSIDE(1) + } + }) { + ElementHelpers::scroll_area_many_entries(gui, "dropdown scroll area", { + .entryHeight = 18.0f, + .entryCount = selections.size(), + .clipHorizontal = true, + .elementContent = [&](size_t i) { + bool selectedEntry = static_cast(*data) == i; + gui.element("elem", [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(18)}, + .padding = CLAY_PADDING_ALL(0), + .childGap = 0, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + SkColor4f entryColor; + if(selectedEntry) + entryColor = gui.io->theme->backColor2; + else if(hoveringOver.has_value() && hoveringOver.value() == i) + entryColor = gui.io->theme->backColor2; + else + entryColor = gui.io->theme->backColor1; + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .padding = {.left = 2, .right = 2, .top = 0, .bottom = 0}, + .childGap = 0, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + }, + .backgroundColor = convert_vec4(entryColor) + }) { + ElementHelpers::text_label(gui, selections[i]); + } + } + }, LayoutElement::Callbacks { + .mouseButton = [&, i](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + update_hovering_over(i, mouseHovering); + if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) { + gui.set_post_callback_func([&] { opts.onClick(); }); + gui.set_to_layout(); + } + return mouseHovering; + }, + .mouseMotion = [&, i](const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + update_hovering_over(i, mouseHovering); + return mouseHovering; + } + }); + } + }); + } + } + }); + } + private: + void update_hovering_over(size_t i, bool mouseHovering) { + auto oldHoveringOver = hoveringOver; + if(mouseHovering) + hoveringOver = i; + else if(hoveringOver.has_value() && hoveringOver.value() == i) + hoveringOver = std::nullopt; + if(oldHoveringOver != hoveringOver) + gui.set_to_layout(); + } + T* d; + DropdownOptions opts; + bool isOpen = false; + std::optional hoveringOver; +}; + +} diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index 38da295..115da88 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -1,68 +1,37 @@ #include "Element.hpp" -#include "Helpers/MathExtras.hpp" -#include namespace GUIStuff { - SkFont get_setup_skfont() { - SkFont font; - font.setLinearMetrics(true); - font.setHinting(SkFontHinting::kNormal); - //font.setForceAutoHinting(true); - font.setSubpixel(true); - font.setBaselineSnap(true); - font.setEdging(SkFont::Edging::kSubpixelAntiAlias); - //paint.setAntiAlias(true); - return font; + Element::Element(GUIManager& initGUI): + gui(initGUI) {} + + void Element::tick_update() {} + + bool Element::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { return mouseHovering; } + bool Element::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { return mouseHovering; } + bool Element::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) { return mouseHovering; } + void Element::input_key_callback(const InputManager::KeyCallbackArgs& key) {} + + void Element::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {} + + void Element::update_bounding_box(Clay_RenderCommand* command) { + boundingBox = get_bb_from_command(command); } - SkFont UpdateInputData::get_font(float fSize) const { - SkFont f = get_setup_skfont(); - f.setTypeface(textTypeface); - f.setSize(fSize); - return f; + void Element::clear_bounding_box() { + boundingBox = std::nullopt; } - std::shared_ptr get_default_dark_mode() { - std::shared_ptr theme(std::make_shared()); - theme->fillColor1 = {0.65f, 0.64f, 1.0f, 1.0f}; - theme->fillColor2 = {0.6f, 0.6f, 0.785f, 1.0f}; - theme->backColor1 = {0.156f, 0.156f, 0.18f, 1.0f}; - theme->backColor2 = {0.24f, 0.24f, 0.29f, 1.0f}; - theme->frontColor1 = {0.87f, 0.87f, 0.87f, 1.0f}; - theme->frontColor2 = {0.64f, 0.64f, 0.64f, 1.0f}; - return theme; + const std::optional>& Element::get_bb() const { + return boundingBox; } - SCollision::AABB Element::get_bb(Clay_RenderCommand* command) { + bool Element::collides_with_point(const Vector2f& p) const { + if(!boundingBox.has_value()) + return false; + return SCollision::collide(p, boundingBox.value()); + } + + SCollision::AABB Element::get_bb_from_command(Clay_RenderCommand* command) { return {{command->boundingBox.x, command->boundingBox.y}, {command->boundingBox.x + command->boundingBox.width, command->boundingBox.y + command->boundingBox.height}}; } - - void SelectionHelper::update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos) { - hovered = isHovering; - - clicked = false; - tapped = false; - justUnselected = false; - bool oldSelected = selected; - - if(isLeftClick) { - if(hovered) { - held = true; - clicked = true; - selected = true; - timeClicked = std::chrono::steady_clock::now(); - clickPos = cursorPos; - } - else - selected = false; - } - else if(!isLeftHeld) { - if(held && hovered && (std::chrono::steady_clock::now() - timeClicked) < std::chrono::milliseconds(250) && vec_distance_sqrd(clickPos, cursorPos) < (25 * 25)) - tapped = true; - held = false; - } - - if(!selected && oldSelected) - justUnselected = true; - } } diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index 11d854c..02fd199 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -1,149 +1,34 @@ #pragma once -#include "Helpers/ConvertVec.hpp" -#include -#include "../../TimePoint.hpp" -#include -#include -#include -#include -#include "../../FontData.hpp" -#include -#include -#include -#include -#include -#include "../../RichText/TextBox.hpp" -#include "../../InputManager.hpp" - -using namespace Eigen; +#include "GUIStuffHelpers.hpp" namespace GUIStuff { + class GUIManager; -// Taken from https://github.com/TimothyHoytBSME/ClayMan (rewritten to be a separate struct, and use templates to change size) -template class StringArena { - public: - void reset() { - stringArenaIndex = 0; - } - const char* insert_string_into_arena(std::string_view str) { - size_t strSize = str.size(); - if(stringArenaIndex + strSize > S) - throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string"); - char* startPtr = &stringArena[stringArenaIndex]; - std::copy(str.begin(), str.end(), startPtr); - stringArenaIndex += strSize; - return startPtr; - } - Clay_String std_str_to_clay_str(std::string_view str) { - return Clay_String{.length = static_cast(str.size()), .chars = insert_string_into_arena(str)}; - } - private: - std::array stringArena; - size_t stringArenaIndex; -}; + class Element { + public: + Element(GUIManager& initGUI); -typedef StringArena<4000000> DefaultStringArena; + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA); + virtual bool collides_with_point(const Vector2f& p) const; -SkFont get_setup_skfont(); + void update_bounding_box(Clay_RenderCommand* command); + void clear_bounding_box(); + const std::optional>& get_bb() const; + virtual ~Element() = default; -struct Theme { - SkColor4f fillColor1 = {0.8f, 0.785f, 1.0f, 1.0f}; - SkColor4f fillColor2 = {0.3f, 0.3f, 0.477f, 1.0f}; - SkColor4f backColor1 = {0.0f, 0.0f, 0.0f, 1.0f}; - SkColor4f backColor2 = {1.0f, 1.0f, 1.0f, 1.0f}; - SkColor4f frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f}; - SkColor4f frontColor2 = {0.9f, 0.9f, 0.9f, 1.0f}; + int16_t zIndex; - SkColor4f errorColor = {1.0f, 0.0f, 0.0f, 1.0f}; - SkColor4f warningColor = {1.0f, 1.0f, 0.0f, 1.0f}; + virtual void tick_update(); - float hoverExpandTime = 0.1f; - uint16_t childGap1 = 10; - uint16_t padding1 = 8; - float windowCorners1 = 8; - - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, backColor1, backColor2, frontColor1, frontColor2, errorColor, warningColor, hoverExpandTime, childGap1, padding1, windowCorners1) -}; - -std::shared_ptr get_default_dark_mode(); - -struct UpdateInputData { - struct { - int leftClick = 0; - int rightClick = 0; - bool leftDoubleClick = false; - bool leftHeld = false; - bool rightHeld = false; - Vector2f globalPos{0, 0}; - Vector2f pos{0, 0}; // Position relative to window - Vector2f scroll; - } mouse; - - struct { - bool left = false; - bool right = false; - bool up = false; - bool down = false; - bool leftShift = false; - bool leftCtrl = false; - bool enter = false; - bool escape = false; - } key; - - struct { - std::function textInFunc; - std::optional textOut; - } clipboard; - - std::shared_ptr previousRichTextBoxToEdit; - std::shared_ptr richTextBoxToEdit; - std::shared_ptr richTextBoxToEditCursor; - InputManager::TextInputProperties richTextInputProperties; - std::shared_ptr> richTextBoxToEditRectangle; - - SkFont get_font(float fSize) const; - - uint16_t fontSize = 16; - - std::string textInput; - bool hoverObstructed = false; - std::vector> hoverObstructingAABBs; - std::vector> hoverObstructingCircles; - bool acceptingTextInput = false; - float deltaTime = 0.0f; - bool isTouch = true; - - float guiScaleMultiplier = 1.0f; - Vector2f windowPos = {0.0f, 0.0f}; - - std::shared_ptr fonts; - sk_sp textTypeface; - - DefaultStringArena* strArena; - std::shared_ptr theme; - std::unordered_map> svgData; -}; - -class SelectionHelper { - public: - void update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos); - bool held = false; - bool hovered = false; - bool selected = false; - bool clicked = false; - bool justUnselected = false; - bool tapped = false; - Vector2f clickPos; - std::chrono::steady_clock::time_point timeClicked; -}; - -class Element { - public: - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) = 0; - virtual ~Element() = default; - - protected: - static SCollision::AABB get_bb(Clay_RenderCommand* command); -}; + // Return true when mouse obstructs what's behind it + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering); + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering); + virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering); + virtual void input_key_callback(const InputManager::KeyCallbackArgs& key); + protected: + static SCollision::AABB get_bb_from_command(Clay_RenderCommand* command); + std::optional> boundingBox; + GUIManager& gui; + }; } diff --git a/src/GUIStuff/Elements/FontPicker.cpp b/src/GUIStuff/Elements/FontPicker.cpp index 702237c..a76bfcf 100644 --- a/src/GUIStuff/Elements/FontPicker.cpp +++ b/src/GUIStuff/Elements/FontPicker.cpp @@ -6,182 +6,184 @@ #include #include #include +#include "../GUIManager.hpp" -bool GUIStuff::FontPicker::update(UpdateInputData& io, std::string* fontName, GUIManager* gui) { - if(sortedFontList.empty()) { - auto icu = SkUnicodes::ICU::Make(); - std::set sortedFontSet; +namespace GUIStuff { - if(io.fonts->localFontMgr) { - for(int i = 0; i < io.fonts->localFontMgr->countFamilies(); i++) { - SkString familyNameSkString; - io.fonts->localFontMgr->getFamilyName(i, &familyNameSkString); - if(familyNameSkString.isEmpty()) - continue; - std::string familyName(familyNameSkString.c_str(), familyNameSkString.size()); - sortedFontSet.emplace(familyName); - } - } - for(int i = 0; i < io.fonts->defaultFontMgr->countFamilies(); i++) { - SkString familyNameSkString; - io.fonts->defaultFontMgr->getFamilyName(i, &familyNameSkString); - if(familyNameSkString.isEmpty()) - continue; - std::string familyName(familyNameSkString.c_str(), familyNameSkString.size()); - sortedFontSet.emplace(familyName); - } - sortedFontList = std::vector(sortedFontSet.begin(), sortedFontSet.end()); - sortedFontListLowercase = sortedFontList; - for(std::string& s : sortedFontListLowercase) - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - } +FontPicker::FontPicker(GUIManager& gui): Element(gui) {} - if(!fontName) - return false; +void FontPicker::layout(const Data& data) { + d = data; + //auto& io = *gui.io; - bool fontChangedFromTextbox = false; - bool fontChangedFromSelection = false; + //if(sortedFontList.empty()) { + // auto icu = SkUnicodes::ICU::Make(); + // std::set sortedFontSet; - bool clickedOut = io.mouse.leftClick; + // if(io.fonts->localFontMgr) { + // for(int i = 0; i < io.fonts->localFontMgr->countFamilies(); i++) { + // SkString familyNameSkString; + // io.fonts->localFontMgr->getFamilyName(i, &familyNameSkString); + // if(familyNameSkString.isEmpty()) + // continue; + // std::string familyName(familyNameSkString.c_str(), familyNameSkString.size()); + // sortedFontSet.emplace(familyName); + // } + // } + // for(int i = 0; i < io.fonts->defaultFontMgr->countFamilies(); i++) { + // SkString familyNameSkString; + // io.fonts->defaultFontMgr->getFamilyName(i, &familyNameSkString); + // if(familyNameSkString.isEmpty()) + // continue; + // std::string familyName(familyNameSkString.c_str(), familyNameSkString.size()); + // sortedFontSet.emplace(familyName); + // } + // sortedFontList = std::vector(sortedFontSet.begin(), sortedFontSet.end()); + // sortedFontListLowercase = sortedFontList; + // for(std::string& s : sortedFontListLowercase) + // std::transform(s.begin(), s.end(), s.begin(), ::tolower); + //} - const float ENTRY_HEIGHT = 25.0f; + //bool fontChangedFromTextbox = false; + //bool fontChangedFromSelection = false; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0)}, - .childGap = 4 - } - }) { - fontChangedFromTextbox = gui->input_text("Font textbox", fontName, true, [&](SelectionHelper& s) { - if(s.selected) { - dropdownOpen = true; - clickedOut = false; - } - }); + //bool clickedOut = io.mouse.leftClick; - auto it = std::find(sortedFontList.begin(), sortedFontList.end(), *fontName); - size_t val = std::numeric_limits::max(); - if(it == sortedFontList.end()) { - std::string lowerFamilyName = *fontName; - std::transform(lowerFamilyName.begin(), lowerFamilyName.end(), lowerFamilyName.begin(), ::tolower); - auto itLower = std::find(sortedFontListLowercase.begin(), sortedFontListLowercase.end(), lowerFamilyName); - if(itLower != sortedFontListLowercase.end()) { - val = itLower - sortedFontListLowercase.begin(); - *fontName = sortedFontList[val]; - } - } - else - val = it - sortedFontList.begin(); + //const float ENTRY_HEIGHT = 25.0f; - if(gui->svg_icon_button("Open font dropdown", "data/icons/droparrowbold.svg", dropdownOpen, GUIManager::SMALL_BUTTON_SIZE)) { - dropdownOpen = !dropdownOpen; - clickedOut = false; - } - if(dropdownOpen) { - Clay_ElementId localID = CLAY_ID_LOCAL("Font picker floating element"); - CLAY(localID, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, 300)}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io.theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(4), - .floating = { - .offset = { - .y = 2 - }, - .attachPoints = { - .element = CLAY_ATTACH_POINT_LEFT_TOP, - .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM - }, - .attachTo = CLAY_ATTACH_TO_PARENT - }, - .border = { - .color = convert_vec4(io.theme->fillColor2), - .width = CLAY_BORDER_OUTSIDE(1) - } - }) { - gui->obstructing_window(localID); - if(Clay_Hovered()) - clickedOut = false; - gui->scroll_bar_many_entries_area("Font Selector", ENTRY_HEIGHT, sortedFontList.size(), true, [fontName, gui, &fontChangedFromSelection, &sortedFontList = sortedFontList, val, &io = io](size_t i, bool listHovered) { - bool selectedEntry = val == i; - - skia::textlayout::ParagraphStyle pStyle; - pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); - pStyle.setTextHeightBehavior(skia::textlayout::kDisableAll); - - skia::textlayout::StrutStyle strutStyle; - strutStyle.setStrutEnabled(true); - strutStyle.setForceStrutHeight(true); - pStyle.setStrutStyle(strutStyle); - - skia::textlayout::TextStyle tStyle; - tStyle.setFontSize(io.fontSize); - tStyle.setFontFamilies({SkString{sortedFontList[i].c_str()}, SkString{"Roboto"}}); - tStyle.setForegroundColor(SkPaint{io.theme->frontColor1}); - pStyle.setTextStyle(tStyle); - - skia::textlayout::ParagraphBuilderImpl pBuilder(pStyle, io.fonts->collection, SkUnicodes::ICU::Make()); - tStyle.setFontStyle(SkFontStyle::Normal()); - - pBuilder.pushStyle(tStyle); - pBuilder.addText(sortedFontList[i].c_str(), sortedFontList[i].size()); - - auto p = pBuilder.Build(); - - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(25)}, - .childGap = 1, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - SkColor4f entryColor; - if(selectedEntry) - entryColor = io.theme->backColor2; - else if(Clay_Hovered()) - entryColor = io.theme->backColor2; - else - entryColor = io.theme->backColor1; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(25)}, - .padding = {.left = 2, .right = 2}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }, - .backgroundColor = convert_vec4(entryColor) - }) { - gui->text_paragraph("font name", std::move(p), 10000.0f); // Setting width to numeric_limits::max will lead to multiplying it by any number greater than 1 to = infinity, which will cause issues - - if(io.mouse.leftClick && Clay_Hovered()) { - *fontName = sortedFontList[i]; - fontChangedFromSelection = true; - } - } - } - }, [ENTRY_HEIGHT, fontChangedFromTextbox, io = &io, val](float scrollContentHeight, float containerHeight, float& scrollAmount) { - if(fontChangedFromTextbox && !io->mouse.leftClick && val != std::numeric_limits::max()) - scrollAmount = -static_cast(val) * ENTRY_HEIGHT; - }); - #ifdef __EMSCRIPTEN__ - CLAY_AUTO_ID({ - .layout = { - .padding = CLAY_PADDING_ALL(io.theme->padding1) - }}) { - gui->text_label_light_centered("Local fonts not usable in web version"); - } - #endif - } - } - } + //CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0)}, + // .childGap = 4 + // } + //}) { + // fontChangedFromTextbox = gui.input_text("Font textbox", fontName, true, [&](SelectionHelper& s) { + // if(s.selected) { + // dropdownOpen = true; + // clickedOut = false; + // } + // }); - if(clickedOut) - dropdownOpen = false; - - return fontChangedFromSelection | fontChangedFromTextbox; + // auto it = std::find(sortedFontList.begin(), sortedFontList.end(), *fontName); + // size_t val = std::numeric_limits::max(); + // if(it == sortedFontList.end()) { + // std::string lowerFamilyName = *fontName; + // std::transform(lowerFamilyName.begin(), lowerFamilyName.end(), lowerFamilyName.begin(), ::tolower); + // auto itLower = std::find(sortedFontListLowercase.begin(), sortedFontListLowercase.end(), lowerFamilyName); + // if(itLower != sortedFontListLowercase.end()) { + // val = itLower - sortedFontListLowercase.begin(); + // *fontName = sortedFontList[val]; + // } + // } + // else + // val = it - sortedFontList.begin(); + + // if(gui.svg_icon_button("Open font dropdown", "data/icons/droparrowbold.svg", dropdownOpen, GUIManager::SMALL_BUTTON_SIZE)) { + // dropdownOpen = !dropdownOpen; + // clickedOut = false; + // } + // if(dropdownOpen) { + // Clay_ElementId localID = CLAY_ID_LOCAL("Font picker floating element"); + // CLAY(localID, { + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, 300)}, + // .layoutDirection = CLAY_TOP_TO_BOTTOM + // }, + // .backgroundColor = convert_vec4(io.theme->backColor1), + // .cornerRadius = CLAY_CORNER_RADIUS(4), + // .floating = { + // .offset = { + // .y = 2 + // }, + // .attachPoints = { + // .element = CLAY_ATTACH_POINT_LEFT_TOP, + // .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM + // }, + // .attachTo = CLAY_ATTACH_TO_PARENT + // }, + // .border = { + // .color = convert_vec4(io.theme->fillColor2), + // .width = CLAY_BORDER_OUTSIDE(1) + // } + // }) { + // gui->obstructing_window(localID); + // if(Clay_Hovered()) + // clickedOut = false; + // gui->scroll_bar_many_entries_area("Font Selector", ENTRY_HEIGHT, sortedFontList.size(), true, [fontName, gui, &fontChangedFromSelection, &sortedFontList = sortedFontList, val, &io = io](size_t i, bool listHovered) { + // bool selectedEntry = val == i; + // + // skia::textlayout::ParagraphStyle pStyle; + // pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft); + // pStyle.setTextHeightBehavior(skia::textlayout::kDisableAll); + // + // skia::textlayout::StrutStyle strutStyle; + // strutStyle.setStrutEnabled(true); + // strutStyle.setForceStrutHeight(true); + // pStyle.setStrutStyle(strutStyle); + // + // skia::textlayout::TextStyle tStyle; + // tStyle.setFontSize(io.fontSize); + // tStyle.setFontFamilies({SkString{sortedFontList[i].c_str()}, SkString{"Roboto"}}); + // tStyle.setForegroundColor(SkPaint{io.theme->frontColor1}); + // pStyle.setTextStyle(tStyle); + // + // skia::textlayout::ParagraphBuilderImpl pBuilder(pStyle, io.fonts->collection, SkUnicodes::ICU::Make()); + // tStyle.setFontStyle(SkFontStyle::Normal()); + // + // pBuilder.pushStyle(tStyle); + // pBuilder.addText(sortedFontList[i].c_str(), sortedFontList[i].size()); + // + // auto p = pBuilder.Build(); + // + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(25)}, + // .childGap = 1, + // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + // .layoutDirection = CLAY_TOP_TO_BOTTOM + // } + // }) { + // SkColor4f entryColor; + // if(selectedEntry) + // entryColor = io.theme->backColor2; + // else if(Clay_Hovered()) + // entryColor = io.theme->backColor2; + // else + // entryColor = io.theme->backColor1; + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(25)}, + // .padding = {.left = 2, .right = 2}, + // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + // }, + // .backgroundColor = convert_vec4(entryColor) + // }) { + // gui->text_paragraph("font name", std::move(p), 10000.0f); // Setting width to numeric_limits::max will lead to multiplying it by any number greater than 1 to = infinity, which will cause issues + // + // if(io.mouse.leftClick && Clay_Hovered()) { + // *fontName = sortedFontList[i]; + // fontChangedFromSelection = true; + // } + // } + // } + // }, [ENTRY_HEIGHT, fontChangedFromTextbox, io = &io, val](float scrollContentHeight, float containerHeight, float& scrollAmount) { + // if(fontChangedFromTextbox && !io->mouse.leftClick && val != std::numeric_limits::max()) + // scrollAmount = -static_cast(val) * ENTRY_HEIGHT; + // }); + // #ifdef __EMSCRIPTEN__ + // CLAY_AUTO_ID({ + // .layout = { + // .padding = CLAY_PADDING_ALL(io.theme->padding1) + // }}) { + // gui->text_label_light_centered("Local fonts not usable in web version"); + // } + // #endif + // } + // } + //} + + //if(clickedOut) + // dropdownOpen = false; } -void GUIStuff::FontPicker::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { } diff --git a/src/GUIStuff/Elements/FontPicker.hpp b/src/GUIStuff/Elements/FontPicker.hpp index 935bf0c..15f92c7 100644 --- a/src/GUIStuff/Elements/FontPicker.hpp +++ b/src/GUIStuff/Elements/FontPicker.hpp @@ -1,15 +1,19 @@ #pragma once #include "Element.hpp" -#include "../GUIManager.hpp" namespace GUIStuff { class FontPicker : public Element { public: - bool update(UpdateInputData& io, std::string* fontName, GUIManager* gui); - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; - SelectionHelper selection; + struct Data { + std::string* fontName = nullptr; + std::function onFontChange; + }; + FontPicker(GUIManager& gui); + void layout(const Data& data); private: + Data d; + std::vector sortedFontList; std::vector sortedFontListLowercase; bool dropdownOpen = false; diff --git a/src/GUIStuff/Elements/GUIStuffHelpers.cpp b/src/GUIStuff/Elements/GUIStuffHelpers.cpp new file mode 100644 index 0000000..b37f688 --- /dev/null +++ b/src/GUIStuff/Elements/GUIStuffHelpers.cpp @@ -0,0 +1,64 @@ +#include "Element.hpp" +#include "Helpers/MathExtras.hpp" +#include + +namespace GUIStuff { + SkFont get_setup_skfont() { + SkFont font; + font.setLinearMetrics(true); + font.setHinting(SkFontHinting::kNormal); + //font.setForceAutoHinting(true); + font.setSubpixel(true); + font.setBaselineSnap(true); + font.setEdging(SkFont::Edging::kSubpixelAntiAlias); + //paint.setAntiAlias(true); + return font; + } + + SkFont UpdateInputData::get_font(float fSize) const { + SkFont f = get_setup_skfont(); + f.setTypeface(textTypeface); + f.setSize(fSize); + return f; + } + + std::shared_ptr get_default_dark_mode() { + std::shared_ptr theme(std::make_shared()); + theme->fillColor1 = {0.65f, 0.64f, 1.0f, 1.0f}; + theme->fillColor2 = {0.6f, 0.6f, 0.785f, 1.0f}; + theme->backColor1 = {0.156f, 0.156f, 0.18f, 1.0f}; + theme->backColor2 = {0.24f, 0.24f, 0.29f, 1.0f}; + theme->frontColor1 = {0.87f, 0.87f, 0.87f, 1.0f}; + theme->frontColor2 = {0.64f, 0.64f, 0.64f, 1.0f}; + return theme; + } + + void SelectionHelper::update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos) { + hovered = isHovering; + + clicked = false; + tapped = false; + justUnselected = false; + bool oldSelected = selected; + + if(isLeftClick) { + if(hovered) { + held = true; + clicked = true; + selected = true; + timeClicked = std::chrono::steady_clock::now(); + clickPos = cursorPos; + } + else + selected = false; + } + else if(!isLeftHeld) { + if(held && hovered && (std::chrono::steady_clock::now() - timeClicked) < std::chrono::milliseconds(250) && vec_distance_sqrd(clickPos, cursorPos) < (25 * 25)) + tapped = true; + held = false; + } + + if(!selected && oldSelected) + justUnselected = true; + } +} diff --git a/src/GUIStuff/Elements/GUIStuffHelpers.hpp b/src/GUIStuff/Elements/GUIStuffHelpers.hpp new file mode 100644 index 0000000..3b115e4 --- /dev/null +++ b/src/GUIStuff/Elements/GUIStuffHelpers.hpp @@ -0,0 +1,105 @@ +#pragma once +#include "Helpers/ConvertVec.hpp" +#include +#include "../../TimePoint.hpp" +#include +#include +#include +#include +#include "../../FontData.hpp" +#include +#include +#include +#include +#include +#include "../../RichText/TextBox.hpp" +#include "../../InputManager.hpp" + +using namespace Eigen; + +namespace GUIStuff { + +// Taken from https://github.com/TimothyHoytBSME/ClayMan (rewritten to be a separate struct, and use templates to change size) +template class StringArena { + public: + void reset() { + stringArenaIndex = 0; + } + const char* insert_string_into_arena(std::string_view str) { + size_t strSize = str.size(); + if(stringArenaIndex + strSize > S) + throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string"); + char* startPtr = &stringArena[stringArenaIndex]; + std::copy(str.begin(), str.end(), startPtr); + stringArenaIndex += strSize; + return startPtr; + } + Clay_String std_str_to_clay_str(std::string_view str) { + return Clay_String{.length = static_cast(str.size()), .chars = insert_string_into_arena(str)}; + } + private: + std::array stringArena; + size_t stringArenaIndex; +}; + +typedef StringArena<4000000> DefaultStringArena; + +SkFont get_setup_skfont(); + +struct Theme { + SkColor4f fillColor1 = {0.8f, 0.785f, 1.0f, 1.0f}; + SkColor4f fillColor2 = {0.3f, 0.3f, 0.477f, 1.0f}; + SkColor4f backColor1 = {0.0f, 0.0f, 0.0f, 1.0f}; + SkColor4f backColor2 = {1.0f, 1.0f, 1.0f, 1.0f}; + SkColor4f frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f}; + SkColor4f frontColor2 = {0.9f, 0.9f, 0.9f, 1.0f}; + + SkColor4f errorColor = {1.0f, 0.0f, 0.0f, 1.0f}; + SkColor4f warningColor = {1.0f, 1.0f, 0.0f, 1.0f}; + + float hoverExpandTime = 0.1f; + uint16_t childGap1 = 10; + uint16_t padding1 = 8; + float windowCorners1 = 8; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, backColor1, backColor2, frontColor1, frontColor2, errorColor, warningColor, hoverExpandTime, childGap1, padding1, windowCorners1) +}; + +std::shared_ptr get_default_dark_mode(); + +struct UpdateInputData { + InputManager* input; + std::function layoutRun; + + SkFont get_font(float fSize) const; + + uint16_t fontSize = 16; + + float deltaTime = 0.0f; + + float guiScaleMultiplier = 1.0f; + Vector2f windowPos = {0.0f, 0.0f}; + Vector2f windowSize = {0.0f, 0.0f}; + + std::shared_ptr fonts; + sk_sp textTypeface; + + DefaultStringArena* strArena; + std::shared_ptr theme; + std::unordered_map> svgData; +}; + +class SelectionHelper { + public: + void update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos); + bool held = false; + bool hovered = false; + bool selected = false; + bool clicked = false; + bool justUnselected = false; + bool tapped = false; + Vector2f clickPos; + std::chrono::steady_clock::time_point timeClicked; +}; + +} diff --git a/src/GUIStuff/Elements/LayoutElement.cpp b/src/GUIStuff/Elements/LayoutElement.cpp new file mode 100644 index 0000000..12de50b --- /dev/null +++ b/src/GUIStuff/Elements/LayoutElement.cpp @@ -0,0 +1,34 @@ +#include "LayoutElement.hpp" + +namespace GUIStuff { + +LayoutElement::LayoutElement(GUIManager& gui): Element(gui) {} + +void LayoutElement::layout(const std::function& layout, const Callbacks& c) { + this->c = c; + layout(); +} + +bool LayoutElement::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + if(c.mouseButton) + return c.mouseButton(button, mouseHovering); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool LayoutElement::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + if(c.mouseMotion) + return c.mouseMotion(motion, mouseHovering); + return Element::input_mouse_motion_callback(motion, mouseHovering); +} + +bool LayoutElement::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) { + if(c.mouseWheel) + return c.mouseWheel(wheel, mouseHovering); + return Element::input_mouse_wheel_callback(wheel, mouseHovering); +} + +void LayoutElement::input_key_callback(const InputManager::KeyCallbackArgs& key) { + if(c.key) c.key(key); +} + +} diff --git a/src/GUIStuff/Elements/LayoutElement.hpp b/src/GUIStuff/Elements/LayoutElement.hpp new file mode 100644 index 0000000..10b7041 --- /dev/null +++ b/src/GUIStuff/Elements/LayoutElement.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +class LayoutElement : public Element { + public: + LayoutElement(GUIManager& gui); + struct Callbacks { + std::function mouseButton; + std::function mouseMotion; + std::function mouseWheel; + std::function key; + }; + void layout(const std::function& layout, const Callbacks& c = {}); + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) override; + virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; + private: + Callbacks c; +}; + +} diff --git a/src/GUIStuff/Elements/MovableTabList.cpp b/src/GUIStuff/Elements/MovableTabList.cpp index c6a70ed..864ebcc 100644 --- a/src/GUIStuff/Elements/MovableTabList.cpp +++ b/src/GUIStuff/Elements/MovableTabList.cpp @@ -5,76 +5,63 @@ namespace GUIStuff { -void MovableTabList::update(UpdateInputData& io, const std::vector>& tabNames, size_t& selectedTab, std::optional& closedTab, const std::function& elemUpdate) { - size_t currentSelectedTab = selectedTab; - if(tabNames != oldTabNames) { - buttons = std::vector(tabNames.size()); - closeButtons = std::vector(tabNames.size()); - closeIcons = std::vector(tabNames.size()); - tabIcons = std::vector(tabNames.size()); - oldTabNames = tabNames; - } +MovableTabList::MovableTabList(GUIManager& gui): Element(gui) {} - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)}, - .padding = CLAY_PADDING_ALL(0), - .childGap = 4, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT - }, - .clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()} - }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(elemUpdate) - elemUpdate(); - for(size_t i = 0; i < buttons.size(); i++) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)}, - } - }) { - buttons[i].update(io, SelectableButton::DrawType::FILLED, [&](SelectionHelper& s, bool iS) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .padding = CLAY_PADDING_ALL(0), - .childGap = 4, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - if(!tabNames[i].first.empty()) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - tabIcons[i].update(io, tabNames[i].first, iS, nullptr); - } - } - CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io.theme->frontColor1), .fontSize = io.fontSize })); - if(s.clicked) - selectedTab = i; - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - closeButtons[i].update(io, SelectableButton::DrawType::TRANSPARENT_ALL, [&](SelectionHelper& s, bool iS) { - if(s.clicked) - closedTab = i; - closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr); - }, false); - } - } - }, currentSelectedTab == i); - } - } - } -} - -void MovableTabList::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { +void MovableTabList::layout(const Data& d) { + //CLAY_AUTO_ID({.layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)}, + // .padding = CLAY_PADDING_ALL(0), + // .childGap = 4, + // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER }, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // }, + // .clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()} + //}) { + // for(size_t i = 0; i < d.tabNames.size(); i++) { + // CLAY_AUTO_ID({.layout = { + // .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)}, + // } + // }) { + // buttons[i].update(io, SelectableButton::DrawType::FILLED, [&](SelectionHelper& s, bool iS) { + // CLAY_AUTO_ID({.layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + // .padding = CLAY_PADDING_ALL(0), + // .childGap = 4, + // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + // }) { + // if(!tabNames[i].first.empty()) { + // CLAY_AUTO_ID({.layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, + // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + // }) { + // tabIcons[i].update(io, tabNames[i].first, iS, nullptr); + // } + // } + // CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io.theme->frontColor1), .fontSize = io.fontSize })); + // if(s.clicked) + // selectedTab = i; + // CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} + // CLAY_AUTO_ID({.layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)}, + // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + // }) { + // closeButtons[i].update(io, SelectableButton::DrawType::TRANSPARENT_ALL, [&](SelectionHelper& s, bool iS) { + // if(s.clicked) + // closedTab = i; + // closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr); + // }, false); + // } + // } + // }, currentSelectedTab == i); + // } + // } + //} } } diff --git a/src/GUIStuff/Elements/MovableTabList.hpp b/src/GUIStuff/Elements/MovableTabList.hpp index 2b1dd82..9545311 100644 --- a/src/GUIStuff/Elements/MovableTabList.hpp +++ b/src/GUIStuff/Elements/MovableTabList.hpp @@ -7,15 +7,15 @@ namespace GUIStuff { class MovableTabList : public Element { public: - void update(UpdateInputData& io, const std::vector>& tabNames, size_t& selectedTab, std::optional& closedTab, const std::function& elemUpdate); - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; - SelectionHelper selection; - private: - std::vector> oldTabNames; - std::vector buttons; - std::vector closeButtons; - std::vector closeIcons; - std::vector tabIcons; + struct Data { + std::vector> tabNames; + size_t selectedTab = 0; + std::function changeSelectedTab; + std::function closeTab; + }; + + MovableTabList(GUIManager& gui); + void layout(const Data& d); }; } diff --git a/src/GUIStuff/Elements/NumberSlider.hpp b/src/GUIStuff/Elements/NumberSlider.hpp index 3f1d47c..ef7cbc2 100644 --- a/src/GUIStuff/Elements/NumberSlider.hpp +++ b/src/GUIStuff/Elements/NumberSlider.hpp @@ -1,15 +1,19 @@ #pragma once #include "Element.hpp" +#include "../GUIManager.hpp" namespace GUIStuff { template class NumberSlider : public Element { public: - bool update(UpdateInputData& io, T* data, T minNew, T maxNew, const std::function& elemUpdate) { - bool isUpdating = false; + NumberSlider(GUIManager& gui): + Element(gui) {} - min = minNew; - max = maxNew; + void layout(T* data, T minData, T maxData, const std::function& onChange) { + this->data = data; + this->minData = minData; + this->maxData = maxData; + this->onChange = onChange; CLAY_AUTO_ID({ .layout = { @@ -17,43 +21,27 @@ template class NumberSlider : public Element { }, .custom = { .customData = this } }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(selection.held && data) { - float fracPosOnSlider = (io.mouse.pos.x() - bb.min.x()) / bb.width(); - *data = static_cast(std::clamp(std::lerp(min, max, fracPosOnSlider), min, max)); // Clamp as double then cast so that unsigned types dont wrap on clamp - isUpdating = true; - } - if(elemUpdate) - elemUpdate(); } - - if(data) - cachedData = *data; - else - cachedData = 0.0; - - return isUpdating; } virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override { - bb = get_bb(command); + auto& bb = boundingBox.value(); canvas->save(); canvas->translate(bb.min.x(), bb.min.y()); - float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, selection.hovered, io.theme->hoverExpandTime); + float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, isHovering, io.theme->hoverExpandTime); - //static BezierEasing easeHeight(0.75, 0.25, 0.25, 0.75); static BezierEasing easeHeight(0.68, -1.55, 0.265, 2.55); - float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, selection.held, 0.3)); + float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, isHeld, 0.3)); float holderRadius = lerp_vec(4.0, 5.0, lerpTimeHover); float holderHeight = lerp_vec(4.0, 10.0, lerpTimeHeld); const float yChange = bb.height() * 0.5f - holderRadius * 0.5f; - float holderPos = lerp_time(cachedData, max, min) * bb.width(); + float holderPos = lerp_time(*data, maxData, minData) * bb.width(); SkRect barFull = SkRect::MakeXYWH(0.0f, yChange, holderPos, holderRadius); SkRect barEmpty = SkRect::MakeXYWH(holderPos, yChange, bb.width() - holderPos, holderRadius); @@ -85,14 +73,35 @@ template class NumberSlider : public Element { canvas->restore(); } + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override { + isHovering = mouseHovering; + isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down; + update_slider_pos(button.pos); + return Element::input_mouse_button_callback(button, mouseHovering); + } + + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override { + update_slider_pos(motion.pos); + return Element::input_mouse_motion_callback(motion, mouseHovering); + } private: - SCollision::AABB bb; - SelectionHelper selection; + void update_slider_pos(const Vector2f& p) { + gui.set_post_callback_func([&, p] { + if(isHeld && boundingBox.has_value()) { + float fracPosOnSlider = (p.x() - boundingBox.value().min.x()) / boundingBox.value().width(); + *data = static_cast(std::clamp(std::lerp(minData, maxData, fracPosOnSlider), minData, maxData)); // Clamp as double then cast so that unsigned types dont wrap on clamp + onChange(); + } + }); + } - T min = 0.0; - T max = 1.0; - T cachedData = 0.0; + bool isHeld = false; + bool isHovering = false; + T* data = nullptr; + T minData = 0.0; + T maxData = 1.0; + std::function onChange; float hoverAnimation = 0.0; float holdAnimation = 0.0; diff --git a/src/GUIStuff/Elements/PaintCircleMenu.cpp b/src/GUIStuff/Elements/PaintCircleMenu.cpp index 38805d2..5ad8e77 100644 --- a/src/GUIStuff/Elements/PaintCircleMenu.cpp +++ b/src/GUIStuff/Elements/PaintCircleMenu.cpp @@ -11,61 +11,84 @@ constexpr float CIRCLE_END = 150.0f; constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.2; constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25; -void PaintCircleMenu::update(GUIManager& gui, const Data& data, const std::function& elemUpdate) { - auto& io = *gui.io; - CLAY_AUTO_ID({.layout = { +PaintCircleMenu::PaintCircleMenu(GUIManager& gui): + Element(gui) {} + +void PaintCircleMenu::layout(const Data& data, const std::function& onChange) { + d = data; + this->onChange = onChange; + + CLAY_AUTO_ID({ + .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} }, .custom = { .customData = this } }) { - bool isHoveredNoObstruction = Clay_Hovered() && !io.hoverObstructed; - Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized(); - float distFromCenter = vec_distance(io.mouse.pos, bb.center()); - if(data.newRotationAngle) { - rotateBarSelect.update(isHoveredNoObstruction && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END, io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(rotateBarSelect.held) { - double& newRotationAngle = *data.newRotationAngle; - newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; - if(rotateBarSelect.hovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles + } +} + +bool PaintCircleMenu::collides_with_point(const Vector2f& p) const { + return boundingBox.has_value() && SCollision::collide(SCollision::Circle(boundingBox.value().center(), CIRCLE_END), p); +} + +bool PaintCircleMenu::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + isHovering = mouseHovering; + isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down; + update_paint_circle_menu_mouse(button.pos, button.button == InputManager::MouseButton::LEFT && button.down); + return mouseHovering && collides_with_point(button.pos); +} + +bool PaintCircleMenu::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + update_paint_circle_menu_mouse(motion.pos, false); + return mouseHovering && collides_with_point(motion.pos); +} + +void PaintCircleMenu::update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked) { + if(boundingBox.has_value()) { + Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized(); + float distFromCenter = vec_distance(p, boundingBox.value().center()); + isRotateBarHovered = isHovering && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END; + isRotateBarHeld = isRotateBarHovered && isHeld; + isColorBarHovered = isHovering && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START; + + if(isColorBarHovered) { + double selectionAngle = std::atan2(-vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; + colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * d.palette.size(); + colorSelectionIndex = std::clamp(colorSelectionIndex, 0, d.palette.size() - 1); + } + + if(isRotateBarHeld) { + gui.set_post_callback_func([&, vecFromCenter] { + auto& rotationAngle = *d.rotationAngle; + rotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; + if(isRotateBarHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { - if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) { - newRotationAngle = snapPos; - if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0 - newRotationAngle = 0; + if(std::fabs(rotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) { + rotationAngle = snapPos; + if(rotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0 + rotationAngle = 0; break; } } + d.onRotate(); } - } - else - *data.newRotationAngle = data.currentRotationAngle; + }); } - if(!data.palette.empty() && data.selectedColor) { - colorBarSelect.update(isHoveredNoObstruction && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START, io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(colorBarSelect.hovered) { - Vector4f& selectedColor = *data.selectedColor; - double selectionAngle = std::atan2(-vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; - colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * data.palette.size(); - colorSelectionIndex = std::clamp(colorSelectionIndex, 0, data.palette.size() - 1); - if(colorBarSelect.clicked) { - selectedColor.x() = data.palette[colorSelectionIndex].x(); - selectedColor.y() = data.palette[colorSelectionIndex].y(); - selectedColor.z() = data.palette[colorSelectionIndex].z(); - } - } + else if(isColorBarHovered && leftClicked) { + gui.set_post_callback_func([&] { + Vector4f& selectedColor = *d.selectedColor; + selectedColor.x() = d.palette[colorSelectionIndex].x(); + selectedColor.y() = d.palette[colorSelectionIndex].y(); + selectedColor.z() = d.palette[colorSelectionIndex].z(); + d.onPaletteClick(); + }); } - io.hoverObstructingCircles.emplace_back(bb.center() + gui.windowPos, CIRCLE_END); - if(SCollision::collide(io.hoverObstructingCircles.back(), io.mouse.pos)) - io.hoverObstructed = true; - if(elemUpdate) - elemUpdate(); } - - d = data; } void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - bb = get_bb(command); + auto& bb = boundingBox.value(); + canvas->save(); canvas->translate(bb.center().x(), bb.center().y()); @@ -80,10 +103,7 @@ void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_Rend } void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA) { - if(!d.newRotationAngle) - return; - - double rotationAngle = d.currentRotationAngle; + double rotationAngle = *d.rotationAngle; float rotateBarMiddleRadius = (CIRCLE_END + ROTATE_START) / 2.0f; SkPaint rotateBarFill(io.theme->backColor2); rotateBarFill.setAntiAlias(skiaAA); @@ -91,7 +111,7 @@ void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, boo rotateBarFill.setStrokeWidth(CIRCLE_END - ROTATE_START); canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill); - if(rotateBarSelect.hovered) { + if(isRotateBarHovered) { for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)}; SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2); @@ -103,9 +123,9 @@ void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, boo SkPaint rotateBarHolderFill; rotateBarHolderFill.setAntiAlias(skiaAA); - if(rotateBarSelect.held) + if(isRotateBarHeld) rotateBarHolderFill.setColor4f(io.theme->fillColor1); - else if(rotateBarSelect.hovered) + else if(isRotateBarHovered) rotateBarHolderFill.setColor4f(io.theme->fillColor2); else rotateBarHolderFill.setColor4f(io.theme->frontColor1); @@ -161,7 +181,7 @@ void PaintCircleMenu::draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bo SkRect startOval = SkRect::MakeLTRB(-PALETTE_START, -PALETTE_START, PALETTE_START, PALETTE_START); SkRect endOval = SkRect::MakeLTRB(-ROTATE_START, -ROTATE_START, ROTATE_START, ROTATE_START); - if(colorBarSelect.hovered) { + if(isColorBarHovered) { SkPathBuilder selectionPathBuilder; double angleStart = colorSelectionIndex * colorDistribution; double angleEnd = (colorSelectionIndex + 1) * colorDistribution; diff --git a/src/GUIStuff/Elements/PaintCircleMenu.hpp b/src/GUIStuff/Elements/PaintCircleMenu.hpp index 6af9dca..c90d066 100644 --- a/src/GUIStuff/Elements/PaintCircleMenu.hpp +++ b/src/GUIStuff/Elements/PaintCircleMenu.hpp @@ -3,26 +3,34 @@ namespace GUIStuff { - class GUIManager; - class PaintCircleMenu : public Element { public: + PaintCircleMenu(GUIManager& gui); struct Data { - double currentRotationAngle = 0.0; - double* newRotationAngle = nullptr; + double* rotationAngle = nullptr; Vector4f* selectedColor = nullptr; std::vector palette; + std::function onRotate; + std::function onPaletteClick; }; - void update(GUIManager& gui, const Data& data, const std::function& elemUpdate); + void layout(const Data& data, const std::function& onChange); + virtual bool collides_with_point(const Vector2f& p) const override; virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; private: void draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA); void draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA); Data d; - SCollision::AABB bb; - SelectionHelper rotateBarSelect; - SelectionHelper colorBarSelect; + std::function onChange; unsigned colorSelectionIndex = 0; + bool isHovering = false; + bool isRotateBarHeld = false; + bool isRotateBarHovered = false; + bool isColorBarHovered = false; + bool isHeld = false; + + void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked); }; } diff --git a/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp new file mode 100644 index 0000000..1217658 --- /dev/null +++ b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp @@ -0,0 +1,39 @@ +#include "PositionAdjustingPopupMenu.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { + +PositionAdjustingPopupMenu::PositionAdjustingPopupMenu(GUIManager& gui): Element(gui) {} + +void PositionAdjustingPopupMenu::layout(Vector2f popupPos, const std::function& innerContent) { + if(layoutElement && layoutElement->get_bb().has_value()) { + auto& bb = layoutElement->get_bb().value(); + if((popupPos.y() + bb.height()) > gui.io->windowSize.y()) + popupPos.y() -= bb.height(); + if((popupPos.x() + bb.width()) > gui.io->windowSize.x()) + popupPos.x() -= bb.width(); + } + + layoutElement = gui.element("popup", [&]{ + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0)}, + .padding = CLAY_PADDING_ALL(gui.io->theme->padding1), + .childGap = 1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + .backgroundColor = convert_vec4(gui.io->theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(gui.io->theme->windowCorners1), + .floating = { + .offset = {popupPos.x(), popupPos.y()}, + .zIndex = gui.zIndex, + .attachTo = CLAY_ATTACH_TO_ROOT + } + }) { + innerContent(); + } + }); +} + +} diff --git a/src/GUIStuff/Elements/PositionAdjustingPopupMenu.hpp b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.hpp new file mode 100644 index 0000000..9177968 --- /dev/null +++ b/src/GUIStuff/Elements/PositionAdjustingPopupMenu.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "Element.hpp" +#include "LayoutElement.hpp" + +namespace GUIStuff { + +class PositionAdjustingPopupMenu : public Element { + public: + PositionAdjustingPopupMenu(GUIManager& gui); + void layout(Vector2f popupPos, const std::function& innerContent); + private: + LayoutElement* layoutElement = nullptr; +}; + +} + diff --git a/src/GUIStuff/Elements/RadioButton.cpp b/src/GUIStuff/Elements/RadioButton.cpp index 4795a2f..cd67abf 100644 --- a/src/GUIStuff/Elements/RadioButton.cpp +++ b/src/GUIStuff/Elements/RadioButton.cpp @@ -1,33 +1,44 @@ #include "RadioButton.hpp" +#include "../GUIManager.hpp" namespace GUIStuff { -void RadioButton::update(UpdateInputData& io, bool newIsTicked, const std::function& elemUpdate) { +RadioButton::RadioButton(GUIManager& gui): + Element(gui) {} + +void RadioButton::layout(const std::function& isTicked, const std::function& onClick) { + this->onClick = onClick; + this->isTicked = isTicked; + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} }, .custom = { .customData = this } - }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(isTicked != newIsTicked) { - hoverAnimation2 = RADIOBUTTON_ANIMATION_TIME; - isTicked = newIsTicked; - } - if(elemUpdate) - elemUpdate(); - } + }) {} +} + +bool RadioButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + isHovering = mouseHovering; + if(isHovering && button.button == InputManager::MouseButton::LEFT && button.down) + gui.set_post_callback_func([&] { if(onClick) onClick(); }); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool RadioButton::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + isHovering = mouseHovering; + return Element::input_mouse_motion_callback(motion, mouseHovering); } void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - auto bb = get_bb(command); + auto& bb = boundingBox.value(); canvas->save(); canvas->translate(bb.min.x(), bb.min.y()); canvas->scale(bb.width(), bb.height()); canvas->translate(0.5f, 0.5f); - if(isTicked) { + if(isTicked()) { SkPaint p; p.setAntiAlias(skiaAA); p.setColor4f(convert_vec4(io.theme->fillColor1)); @@ -40,7 +51,7 @@ void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCo innerCircleP.setStyle(SkPaint::kFill_Style); static BezierEasing easeRadius(0.68, -2.55, 0.265, 3.55); - float lerpTime2 = easeRadius(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, RADIOBUTTON_ANIMATION_TIME)); + float lerpTime2 = easeRadius(smooth_two_way_time(hoverAnimation2, io.deltaTime, isHovering, RADIOBUTTON_ANIMATION_TIME)); float innerCircleRadius = lerp_vec(0.3f, 0.2f, lerpTime2); canvas->drawCircle(0.0f, 0.0f, innerCircleRadius, innerCircleP); @@ -48,7 +59,7 @@ void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCo else { SkPaint p; p.setAntiAlias(skiaAA); - p.setColor4f(convert_vec4(selection.hovered ? io.theme->fillColor1 : io.theme->backColor2)); + p.setColor4f(convert_vec4(isHovering ? io.theme->fillColor1 : io.theme->backColor2)); p.setStyle(SkPaint::kStroke_Style); p.setStrokeWidth(0.15f); canvas->drawCircle(0.0f, 0.0f, 0.5f, p); diff --git a/src/GUIStuff/Elements/RadioButton.hpp b/src/GUIStuff/Elements/RadioButton.hpp index e513c6f..2d78e0d 100644 --- a/src/GUIStuff/Elements/RadioButton.hpp +++ b/src/GUIStuff/Elements/RadioButton.hpp @@ -5,15 +5,18 @@ namespace GUIStuff { class RadioButton : public Element { public: - void update(UpdateInputData& io, bool newIsTicked, const std::function& elemUpdate); + RadioButton(GUIManager& gui); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; - SelectionHelper selection; + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + + void layout(const std::function& isTicked, const std::function& onClick); private: static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3; - - bool isTicked = false; - + bool isHovering = false; float hoverAnimation2 = 0.0; + std::function isTicked; + std::function onClick; }; } diff --git a/src/GUIStuff/Elements/RotateWheel.cpp b/src/GUIStuff/Elements/RotateWheel.cpp index 8fc9a66..80fa6b8 100644 --- a/src/GUIStuff/Elements/RotateWheel.cpp +++ b/src/GUIStuff/Elements/RotateWheel.cpp @@ -1,5 +1,6 @@ #include "RotateWheel.hpp" #include "Helpers/ConvertVec.hpp" +#include "../GUIManager.hpp" namespace GUIStuff { @@ -8,39 +9,20 @@ constexpr float WHEEL_WIDTH = 13.0f; constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.3; constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25; -void RotateWheel::update(UpdateInputData& io, double* newRotationAnglePtr, const std::function& elemUpdate) { - rotateAngle = 0.0f; - if(!newRotationAnglePtr) - return; +RotateWheel::RotateWheel(GUIManager& gui): + Element(gui) {} - CLAY_AUTO_ID({.layout = { +void RotateWheel::layout(double* rotateAngle, const std::function& onChange) { + this->rotateAngle = rotateAngle; + this->onChange = onChange; + + CLAY_AUTO_ID({ + .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} }, .custom = { .customData = this } }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized(); - float distFromCenter = vec_distance(io.mouse.pos, bb.center()); - wheelHovered = selection.hovered && distFromCenter > wheel_start() && distFromCenter < wheel_end(); - if(selection.held) { - double& newRotationAngle = *newRotationAnglePtr; - newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; - if(wheelHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles - for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { - if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) { - newRotationAngle = snapPos; - if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0 - newRotationAngle = 0; - break; - } - } - } - } - if(elemUpdate) - elemUpdate(); } - - rotateAngle = *newRotationAnglePtr; } float RotateWheel::wheel_start() { @@ -48,11 +30,50 @@ float RotateWheel::wheel_start() { } float RotateWheel::wheel_end() { - return bb.width() * 0.5f; + return boundingBox.value().width() * 0.5f; +} + +bool RotateWheel::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + isHovering = mouseHovering; + isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down; + update_paint_circle_menu_mouse(button.pos, button.button == InputManager::MouseButton::LEFT && button.down); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool RotateWheel::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + update_paint_circle_menu_mouse(motion.pos, false); + return Element::input_mouse_motion_callback(motion, mouseHovering); +} + +void RotateWheel::update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked) { + if(boundingBox.has_value()) { + Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized(); + float distFromCenter = vec_distance(p, boundingBox.value().center()); + isRotateBarHovered = isHovering && distFromCenter > wheel_start() && distFromCenter < wheel_end(); + isRotateBarHeld = isRotateBarHovered && isHeld; + + if(isRotateBarHeld) { + gui.set_post_callback_func([&, vecFromCenter] { + auto& rotationAngle = *rotateAngle; + rotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; + if(isRotateBarHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles + for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { + if(std::fabs(rotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) { + rotationAngle = snapPos; + if(rotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0 + rotationAngle = 0; + break; + } + } + } + }); + } + } } void RotateWheel::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - bb = get_bb(command); + auto& bb = boundingBox.value(); + canvas->save(); canvas->translate(bb.center().x(), bb.center().y()); @@ -71,7 +92,7 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool rotateBarFill.setStrokeWidth(wheelEnd - wheelStart); canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill); - if(wheelHovered) { + if(isRotateBarHovered) { for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)}; SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2); @@ -90,7 +111,7 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool else rotateBarHolderFill.setColor4f(io.theme->frontColor2); float rotateBarHolderRadius = (wheelEnd - wheelStart) / 2.0f; - Vector2f rotateBarHolderPos{std::cos(rotateAngle) * rotateBarMiddleRadius, -std::sin(rotateAngle) * rotateBarMiddleRadius}; + Vector2f rotateBarHolderPos{std::cos(*rotateAngle) * rotateBarMiddleRadius, -std::sin(*rotateAngle) * rotateBarMiddleRadius}; canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill); SkPaint rotateBarHolderOutline(io.theme->frontColor1); rotateBarHolderOutline.setAntiAlias(skiaAA); diff --git a/src/GUIStuff/Elements/RotateWheel.hpp b/src/GUIStuff/Elements/RotateWheel.hpp index 377ed1a..ee0d2f4 100644 --- a/src/GUIStuff/Elements/RotateWheel.hpp +++ b/src/GUIStuff/Elements/RotateWheel.hpp @@ -5,17 +5,25 @@ namespace GUIStuff { class RotateWheel : public Element { public: - void update(UpdateInputData& io, double* newRotationAnglePtr, const std::function& elemUpdate); + RotateWheel(GUIManager& gui); + void layout(double* rotateAngle, const std::function& onChange); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; SelectionHelper selection; private: float wheel_start(); float wheel_end(); void draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool skiaAA); - double rotateAngle = 0.0f; - SCollision::AABB bb; - bool wheelHovered; + double* rotateAngle = nullptr; + bool isHeld = false; + bool isHovering = false; + bool isRotateBarHeld = false; + bool isRotateBarHovered = false; + std::function onChange; + + void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked); }; } diff --git a/src/GUIStuff/Elements/SVGIcon.cpp b/src/GUIStuff/Elements/SVGIcon.cpp index 3861fab..8b4d24d 100644 --- a/src/GUIStuff/Elements/SVGIcon.cpp +++ b/src/GUIStuff/Elements/SVGIcon.cpp @@ -3,10 +3,15 @@ #include #include #include +#include "../GUIManager.hpp" namespace GUIStuff { -void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function& elemUpdate) { +SVGIcon::SVGIcon(GUIManager& gui): + Element(gui) {} + +void SVGIcon::layout(const std::string& newSvgPath, bool newIsHighlighted) { + auto& io = *gui.io; auto findSVGData = io.svgData.find(newSvgPath); if(findSVGData == io.svgData.end()) throw std::runtime_error("[SVGIcon::update] Could not load icon " + newSvgPath); @@ -20,17 +25,14 @@ void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool ne .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .custom = { .customData = this } - }) { - if(elemUpdate) - elemUpdate(); - } + }) {} } void SVGIcon::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { if(!svgDom) return; - auto bb = get_bb(command); + auto& bb = boundingBox.value(); canvas->save(); canvas->translate(bb.min.x(), bb.min.y()); diff --git a/src/GUIStuff/Elements/SVGIcon.hpp b/src/GUIStuff/Elements/SVGIcon.hpp index b151ecf..5941c75 100644 --- a/src/GUIStuff/Elements/SVGIcon.hpp +++ b/src/GUIStuff/Elements/SVGIcon.hpp @@ -5,7 +5,8 @@ namespace GUIStuff { class SVGIcon : public Element { public: - void update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function& elemUpdate); + SVGIcon(GUIManager& gui); + void layout(const std::string& newSvgPath, bool newIsHighlighted); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; private: bool highlighted; diff --git a/src/GUIStuff/Elements/ScrollArea.cpp b/src/GUIStuff/Elements/ScrollArea.cpp new file mode 100644 index 0000000..0942ed9 --- /dev/null +++ b/src/GUIStuff/Elements/ScrollArea.cpp @@ -0,0 +1,117 @@ +#include "ScrollArea.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { + +ScrollArea::ScrollArea(GUIManager& gui): Element(gui) {} + +void ScrollArea::layout(const Options& options) { + opts = options; + CLAY_AUTO_ID({ + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} + }) { + Clay_ElementId localID = CLAY_ID_LOCAL("SCROLL_AREA"); + + Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(localID); + + if(scrollData.found) { + contentDimensions = {scrollData.contentDimensions.width, scrollData.contentDimensions.height}; + containerDimensions = {scrollData.scrollContainerDimensions.width, scrollData.scrollContainerDimensions.height}; + } + + CLAY(localID, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = opts.layoutDirection + }, + .clip = {.horizontal = opts.clipHorizontal, .vertical = opts.clipVertical, .childOffset = {.x = scrollOffset.x(), .y = scrollOffset.y()}} + }) { + 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); + + // currentScrollPos = scrollData.scrollPosition->y; + + // 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; + + // 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; + } +} + +bool ScrollArea::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool ScrollArea::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + return Element::input_mouse_motion_callback(motion, mouseHovering); +} + +bool ScrollArea::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) { + if(mouseHovering) { + if(opts.scrollVertical) + scrollOffset.y() += wheel.amount.y(); + if(opts.scrollHorizontal) + scrollOffset.x() += wheel.amount.x(); + gui.set_to_layout(); + } + return Element::input_mouse_wheel_callback(wheel, mouseHovering); +} + +} diff --git a/src/GUIStuff/Elements/ScrollArea.hpp b/src/GUIStuff/Elements/ScrollArea.hpp new file mode 100644 index 0000000..8916b4b --- /dev/null +++ b/src/GUIStuff/Elements/ScrollArea.hpp @@ -0,0 +1,42 @@ +#pragma once +#include "Element.hpp" +#include "Helpers/ConvertVec.hpp" + +namespace GUIStuff { + +class ScrollArea : public Element { + public: + ScrollArea(GUIManager& gui); + + struct InnerContentParameters { + Vector2f contentDimensions; + Vector2f containerDimensions; + Vector2f scrollOffset; + }; + + struct Options { + bool scrollVertical = false; + bool scrollHorizontal = false; + bool clipHorizontal = false; + bool clipVertical = false; + bool showScrollbarX = false; + bool showScrollbarY = false; + Clay_LayoutDirection layoutDirection = CLAY_TOP_TO_BOTTOM; + std::function innerContent; + }; + + void layout(const Options& options); + + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) override; + private: + Options opts; + bool isScrollbarXHeld = false; + bool isScrollbarYHeld = false; + Vector2f contentDimensions = {0.0f, 0.0f}; + Vector2f containerDimensions = {0.0f, 0.0f}; + Vector2f scrollOffset = {0.0f, 0.0f}; +}; + +} diff --git a/src/GUIStuff/Elements/SelectableButton.cpp b/src/GUIStuff/Elements/SelectableButton.cpp index b9f5fe5..9ee43ef 100644 --- a/src/GUIStuff/Elements/SelectableButton.cpp +++ b/src/GUIStuff/Elements/SelectableButton.cpp @@ -1,30 +1,38 @@ #include "SelectableButton.hpp" #include "Helpers/ConvertVec.hpp" +#include "../GUIManager.hpp" namespace GUIStuff { -void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std::function& elemUpdate, bool isSelected) { +SelectableButton::SelectableButton(GUIManager& gui): + Element(gui) {} + +void SelectableButton::layout(const Data& d) { + auto& io = *gui.io; + SkColor4f borderColor; SkColor4f backgroundColorHighlight; SkColor4f backgroundColor; - if(selection.held || ((isSelected || selection.hovered) && drawType == DrawType::TRANSPARENT_BORDER)) + onClick = d.onClick; + + if(isHeld || ((d.isSelected || isHovering) && d.drawType == DrawType::TRANSPARENT_BORDER)) borderColor = io.theme->fillColor1; - else if(drawType == DrawType::TRANSPARENT_BORDER) + else if(d.drawType == DrawType::TRANSPARENT_BORDER) borderColor = io.theme->backColor2; else borderColor = SkColor4f{0.0f, 0.0f, 0.0f, 0.0f}; - if(isSelected) + if(d.isSelected) backgroundColorHighlight = color_mul_alpha(io.theme->fillColor1, 0.4f); - else if(selection.hovered || selection.held) + else if(isHovering || isHeld) backgroundColorHighlight = color_mul_alpha(io.theme->fillColor1, 0.2f); else backgroundColorHighlight = {0.0f, 0.0f, 0.0f, 0.0f}; - if(drawType == DrawType::TRANSPARENT_ALL || drawType == DrawType::TRANSPARENT_BORDER) + if(d.drawType == DrawType::TRANSPARENT_ALL || d.drawType == DrawType::TRANSPARENT_BORDER) backgroundColor = {0.0f, 0.0f, 0.0f, 0.0f}; - else if(drawType == DrawType::FILLED) + else if(d.drawType == DrawType::FILLED) backgroundColor = io.theme->backColor2; else backgroundColor = io.theme->fillColor2; @@ -43,19 +51,38 @@ void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std: }) { CLAY_AUTO_ID({.layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .padding = (drawType == DrawType::TRANSPARENT_BORDER) ? CLAY_PADDING_ALL(0) : CLAY_PADDING_ALL(4), + .padding = (d.drawType == DrawType::TRANSPARENT_BORDER) ? CLAY_PADDING_ALL(0) : CLAY_PADDING_ALL(4), .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, }, .backgroundColor = convert_vec4(backgroundColorHighlight), .cornerRadius = CLAY_CORNER_RADIUS(4) }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - elemUpdate(selection, isSelected); + if(d.innerContent) + d.innerContent({.isSelected = d.isSelected, .isHovering = isHovering, .isHeld = isHeld}); } } } -void SelectableButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { +bool SelectableButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + bool oldIsHovering = isHovering; + bool oldIsHeld = isHeld; + isHovering = mouseHovering; + isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down; + if(isHeld) { + gui.set_post_callback_func([&]{onClick();}); + gui.set_to_layout(); + } + else if(isHovering != oldIsHovering || isHeld != oldIsHeld) + gui.set_to_layout(); + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool SelectableButton::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + bool oldIsHovering = isHovering; + isHovering = mouseHovering; + if(isHovering != oldIsHovering) + gui.set_to_layout(); + return Element::input_mouse_motion_callback(motion, mouseHovering); } } diff --git a/src/GUIStuff/Elements/SelectableButton.hpp b/src/GUIStuff/Elements/SelectableButton.hpp index 241e05d..c244ead 100644 --- a/src/GUIStuff/Elements/SelectableButton.hpp +++ b/src/GUIStuff/Elements/SelectableButton.hpp @@ -12,9 +12,28 @@ class SelectableButton : public Element { TRANSPARENT_BORDER }; - void update(UpdateInputData& io, DrawType drawType, const std::function& elemUpdate, bool isSelected); - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; - SelectionHelper selection; + struct InnerContentCallbackParameters { + bool isSelected; + bool isHovering; + bool isHeld; + }; + + struct Data { + DrawType drawType = DrawType::TRANSPARENT_ALL; + bool isSelected = false; + std::function onClick; + std::function innerContent; + }; + + SelectableButton(GUIManager& gui); + void layout(const Data& d); + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + + private: + bool isHovering = false; + bool isHeld = false; + std::function onClick; }; } diff --git a/src/GUIStuff/Elements/TextBox.decl.hpp b/src/GUIStuff/Elements/TextBox.decl.hpp new file mode 100644 index 0000000..a6e53ae --- /dev/null +++ b/src/GUIStuff/Elements/TextBox.decl.hpp @@ -0,0 +1,44 @@ +#pragma once +#include "Element.hpp" +#include "../../RichText/TextBox.hpp" +#include +#include +#include "../../FontData.hpp" +#include "../../InputManager.hpp" + +namespace GUIStuff { + +template struct TextBoxData { + T* data = nullptr; + std::function(const std::string&)> fromStr; + std::function toStr; + bool singleLine = true; + bool updateEveryEdit = false; + bool immutable = false; + InputManager::TextInputProperties textInputProps; + std::function onEdit; + std::function onEnter; +}; + +template class TextBox : public Element { + public: + TextBox(GUIManager& gui); + + void layout(const TextBoxData& userInfo); + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; + ~TextBox(); + private: + void init_textbox(UpdateInputData& io); + bool update_data(); + + bool isSelected = false; + TextBoxData userInfo; + std::shared_ptr textbox; + std::shared_ptr cur; + std::shared_ptr> rect; +}; + +} diff --git a/src/GUIStuff/Elements/TextBox.hpp b/src/GUIStuff/Elements/TextBox.hpp index 4156b12..45d390a 100644 --- a/src/GUIStuff/Elements/TextBox.hpp +++ b/src/GUIStuff/Elements/TextBox.hpp @@ -1,147 +1,3 @@ #pragma once -#include "Element.hpp" -#include "../../RichText/TextBox.hpp" -#include -#include -#include "../../FontData.hpp" -#include "../../InputManager.hpp" - -namespace GUIStuff { - -template class TextBox : public Element { - public: - bool update(UpdateInputData& io, T* newData, const std::function(const std::string&)>& newFromStr, const std::function newToStr, bool newSingleLine, bool updateEveryEdit, const InputManager::TextInputProperties& textInputProps, const std::function& elemUpdate) { - bool isUpdating = false; - - if(!textbox || !cur) - force_update_textbox(io, true); - - data = newData; - fromStr = newFromStr; - toStr = newToStr; - force_update_textbox(io, false); - - textbox->set_width(std::numeric_limits::max()); - //textbox->set_text_direction_between(0, 0, textbox->get_suggested_direction(0)); - - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(static_cast(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast(io.fontSize * 1.25f))} - }, - .custom = { .customData = this } - }) { - selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos); - if(data && selection.selected) { - io.richTextBoxToEdit = textbox; - io.richTextBoxToEditCursor = cur; - io.richTextBoxToEditRectangle = rect; - io.richTextInputProperties = textInputProps; - textbox->process_mouse_left_button(*cur, io.mouse.pos - bb.min, io.mouse.leftClick, io.mouse.leftHeld, io.key.leftShift); - if((updateEveryEdit && textbox->inputChangedTextBox) || io.key.enter) { - update_on_edit(); - isUpdating = true; - } - } - if(data && selection.justUnselected) { - std::optional dataToAssign = fromStr(textbox->get_string()); - if(dataToAssign.has_value()) - *data = dataToAssign.value(); - force_update_textbox(io, true); - isUpdating = true; - } - if(elemUpdate) - elemUpdate(selection); - } - - if(textbox) - textbox->inputChangedTextBox = false; - - *rect = {(bb.min + io.windowPos) * io.guiScaleMultiplier, (bb.max + io.windowPos) * io.guiScaleMultiplier}; - - return isUpdating; - } - - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override { - if(!data || !textbox || !cur) - return; - - bb = get_bb(command); - - canvas->save(); - SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()); - - canvas->drawRect(r, SkPaint(io.theme->backColor2)); - SkPaint outline(selection.selected ? io.theme->fillColor1 : io.theme->backColor2); - outline.setStroke(true); - outline.setStrokeWidth(2.0f); - outline.setAntiAlias(skiaAA); - canvas->drawRoundRect(r, 2.0f, 2.0f, outline); - - canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height())); - - float yOffset = bb.height() * 0.5f; - yOffset -= textbox->get_height() * 0.5f; - - RichText::TextBox::PaintOpts paintOpts; - if(selection.selected) { - paintOpts.cursor = *cur; - SkRect cursorRect = textbox->get_cursor_rect(cur->pos); - canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset); - } - else - canvas->translate(bb.min.x() + 2.0f, bb.min.y() + yOffset); - paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB}; - paintOpts.skiaAA = skiaAA; - - skia::textlayout::TextStyle tStyle; - tStyle.setFontFamilies(io.fonts->get_default_font_families()); - tStyle.setFontSize(io.fontSize); - tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1}); - textbox->set_initial_text_style(tStyle); - - textbox->paint(canvas, paintOpts); - - canvas->restore(); - } - - SelectionHelper selection; - private: - void force_update_textbox(UpdateInputData& io, bool reallyForce) { - if(data && oldData.has_value() && (*data == oldData.value()) && !reallyForce && textbox && cur) - return; - - textbox = std::make_shared(); - textbox->set_font_data(io.fonts); - textbox->set_allow_newlines(false); - - cur = std::make_shared(); - rect = std::make_shared>(); - - if(data) { - cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*data)); - oldData = *data; - } - } - - void update_on_edit() { - std::optional dataToAssign = fromStr(textbox->get_string()); - if(dataToAssign.has_value()) { - *data = dataToAssign.value(); - oldData = *data; - } - } - - T* data = nullptr; - std::optional oldData; - - std::function(const std::string&)> fromStr; - std::function toStr; - - std::shared_ptr textbox; - std::shared_ptr cur; - std::shared_ptr> rect; - - SCollision::AABB bb = {{1.0f, 1.0f}, {2.0f, 2.0f}}; -}; - -} +#include "TextBox.decl.hpp" +#include "TextBox.impl.hpp" diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp new file mode 100644 index 0000000..9e53ddd --- /dev/null +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -0,0 +1,134 @@ +#include "TextBox.decl.hpp" +#include "../GUIManager.hpp" + +namespace GUIStuff { + +template void TextBox::layout(const TextBoxData& userInfo) { + auto& io = *gui.io; + this->userInfo = userInfo; + textbox->onUserTextEdit = [&] { + if(userInfo.immutable) + cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*userInfo.data)); + else { + if(update_data() && userInfo.onEdit) + gui.set_post_callback_func(userInfo.onEdit); + } + }; + init_textbox(io); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(static_cast(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast(io.fontSize * 1.25f))} + }, + .custom = { .customData = this } + }) { + } +} + +template void TextBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + canvas->save(); + SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()); + + canvas->drawRect(r, SkPaint(io.theme->backColor2)); + SkPaint outline(isSelected ? io.theme->fillColor1 : io.theme->backColor2); + outline.setStroke(true); + outline.setStrokeWidth(2.0f); + outline.setAntiAlias(skiaAA); + canvas->drawRoundRect(r, 2.0f, 2.0f, outline); + + canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height())); + + float yOffset = bb.height() * 0.5f; + yOffset -= textbox->get_height() * 0.5f; + + RichText::TextBox::PaintOpts paintOpts; + if(isSelected) { + paintOpts.cursor = *cur; + SkRect cursorRect = textbox->get_cursor_rect(cur->pos); + canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset); + } + else + canvas->translate(bb.min.x() + 2.0f, bb.min.y() + yOffset); + paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB}; + paintOpts.skiaAA = skiaAA; + + skia::textlayout::TextStyle tStyle; + tStyle.setFontFamilies(io.fonts->get_default_font_families()); + tStyle.setFontSize(io.fontSize); + tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1}); + textbox->set_initial_text_style(tStyle); + + textbox->paint(canvas, paintOpts); + + canvas->restore(); +} + +template bool TextBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + auto& io = *gui.io; + + if(button.button == InputManager::MouseButton::LEFT && boundingBox.has_value()) { + if(button.down) { + if(mouseHovering) { + *rect = {(boundingBox.value().min + io.windowPos) * io.guiScaleMultiplier, (boundingBox.value().max + io.windowPos) * io.guiScaleMultiplier}; + gui.io->input->set_rich_text_box_input_front(textbox, cur, false, rect, userInfo.textInputProps, nullptr); + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, true, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held); + isSelected = true; + } + else { + gui.io->input->remove_rich_text_box_input(textbox); + if(update_data()) + gui.set_post_callback_func(userInfo.onEdit); + update_textbox(io); + isSelected = false; + } + } + else { + textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, false, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held); + } + } + return Element::input_mouse_button_callback(button, mouseHovering); +} + +template bool TextBox::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + if(boundingBox.has_value()) + textbox->process_mouse_left_button(*cur, motion.pos - boundingBox.value().min, 0, gui.io->input->mouse.leftDown, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held); + return Element::input_mouse_motion_callback(motion, mouseHovering); +} + +template void TextBox::input_key_callback(const InputManager::KeyCallbackArgs& key) { + if(key.key == InputManager::KEY_TEXT_ENTER && key.down && userInfo.onEnter) { + bool success = update_data(); + gui.set_post_callback_func([&, success] { + if(success && userInfo.onEdit) userInfo.onEdit(); + userInfo.onEnter(); + }); + } +} + +template void TextBox::init_textbox(UpdateInputData& io) { + textbox = std::make_shared(); + textbox->set_width(std::numeric_limits::max()); + textbox->set_font_data(io.fonts); + textbox->set_allow_newlines(false); + + cur = std::make_shared(); + rect = std::make_shared>(); + + cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*userInfo.data)); +} + +template bool TextBox::update_data() { + std::optional dataToAssign = userInfo.fromStr(textbox->get_string()); + if(dataToAssign.has_value()) { + *userInfo.data = dataToAssign.value(); + return true; + } + return false; +} + +template TextBox::~TextBox() { + gui.io->input->remove_rich_text_box_input(textbox); +} + +} diff --git a/src/GUIStuff/Elements/TextParagraph.cpp b/src/GUIStuff/Elements/TextParagraph.cpp index 5278537..211b159 100644 --- a/src/GUIStuff/Elements/TextParagraph.cpp +++ b/src/GUIStuff/Elements/TextParagraph.cpp @@ -4,7 +4,10 @@ namespace GUIStuff { -void TextParagraph::update(UpdateInputData& io, std::unique_ptr paragraph, float width, const std::function& elemUpdate) { +TextParagraph::TextParagraph(GUIManager& gui): + Element(gui) {} + +void TextParagraph::layout(std::unique_ptr paragraph, float width) { data = std::move(paragraph); data->layout(width); CLAY_AUTO_ID({ @@ -12,17 +15,14 @@ void TextParagraph::update(UpdateInputData& io, std::unique_ptrgetHeight())} }, .custom = { .customData = this } - }) { - if(elemUpdate) - elemUpdate(); - } + }) {} } void TextParagraph::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { - bb = get_bb(command); - - if(data) + if(data) { + auto& bb = boundingBox.value(); data->paint(canvas, bb.min.x(), bb.min.y()); + } } } diff --git a/src/GUIStuff/Elements/TextParagraph.hpp b/src/GUIStuff/Elements/TextParagraph.hpp index ca554f7..fa0ac6b 100644 --- a/src/GUIStuff/Elements/TextParagraph.hpp +++ b/src/GUIStuff/Elements/TextParagraph.hpp @@ -6,10 +6,10 @@ namespace GUIStuff { class TextParagraph : public Element { public: - void update(UpdateInputData& io, std::unique_ptr paragraph, float width, const std::function& elemUpdate); + TextParagraph(GUIManager& gui); + void layout(std::unique_ptr paragraph, float width); virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; private: - SCollision::AABB bb = SCollision::AABB({0.0f, 0.0f}, {0.0f, 0.0f}); std::unique_ptr data; }; diff --git a/src/GUIStuff/Elements/TreeListing.cpp b/src/GUIStuff/Elements/TreeListing.cpp index bb41c48..ab34240 100644 --- a/src/GUIStuff/Elements/TreeListing.cpp +++ b/src/GUIStuff/Elements/TreeListing.cpp @@ -3,279 +3,293 @@ #include "Helpers/ConvertVec.hpp" #include #include "../../TimePoint.hpp" +#include "../GUIManager.hpp" #define TIME_TO_HOVER_TO_OPEN_DIRECTORY std::chrono::milliseconds(700) namespace GUIStuff { -void TreeListing::update(UpdateInputData& io, GUIManager& gui, NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData) { - if(!io.mouse.leftHeld) - dragHoldSelected = false; +TreeListing::TreeListing(GUIManager& gui): + Element(gui) {} - selectionData.orderedObjsSelected.clear(); - std::optional hoveredObject; +void TreeListing::layout(NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData) { + //auto& io = *gui.io; - CLAY_AUTO_ID({ - .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} - }) { - Clay_ElementId localID = CLAY_ID_LOCAL("scroll area"); - float setScrollAmount; - float setScrollContentHeight; - CLAY(localID, { - .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} - }) { - gui.scroll_bar_area("scroll area", true, [&](float scrollContentHeight, float containerHeight, float& scrollAmount) { - setScrollAmount = scrollAmount; - setScrollContentHeight = scrollContentHeight; - size_t itemCount = 0; - size_t itemCountToStartAt = -scrollAmount / ENTRY_HEIGHT; - size_t itemCountToEndAt = (containerHeight - scrollAmount) / ENTRY_HEIGHT; - itemCountToEndAt++; - std::vector parentIDStack; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * itemCountToStartAt)} - } - }) {} - recursive_gui(io, gui, displayData, selectionData, rootObjID, itemCount, itemCountToStartAt, itemCountToEndAt, 0, hoveredObject, parentIDStack, false); - if(itemCount > itemCountToEndAt) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * (itemCount - itemCountToEndAt))} - } - }) {} - } - }, false); - } - Clay_ElementData scrollAreaBB = Clay_GetElementData(localID); - if(scrollAreaBB.found) { - // Clamp by fabs(scrollcontentheight - 3), small offset is added to account for error (when youre at the very edge it might assume youre at the top of an element) - float mouseRelativeYPos = std::clamp(std::fabs(io.mouse.pos.y() - scrollAreaBB.boundingBox.y - setScrollAmount), 0.0f, std::fabs(setScrollContentHeight - 3.0f)); - topHalfOfHovered = std::fmod(mouseRelativeYPos, ENTRY_HEIGHT) < (ENTRY_HEIGHT / 2.0f); - } - } + //if(!io.mouse.leftHeld) + // dragHoldSelected = false; - // Any objects that were part of a collapsed directory will be unselected - selectionData.objsSelected.clear(); - for(auto& [parent, object] : selectionData.orderedObjsSelected) - selectionData.objsSelected.emplace(object); + //selectionData.orderedObjsSelected.clear(); + //std::optional hoveredObject; - if(objDragged.has_value()) { - if(hoveredObject.has_value() && !selectionData.objsSelected.contains(hoveredObject.value().object) && (std::chrono::steady_clock::now() - timeStartedHoveringOverObject) >= TIME_TO_HOVER_TO_OPEN_DIRECTORY) { - ParentObjectIDStack& hoveredObjVal = hoveredObject.value(); - size_t hoveredObjIndex = displayData.getIndexOfObjInList({hoveredObjVal.parents.back(), hoveredObjVal.object}); - std::optional objHoveringOver = displayData.getObjInListAtIndex(hoveredObjVal.parents.back(), hoveredObjIndex); - if(objHoveringOver.value().isDirectory && !objHoveringOver.value().isDirectoryOpen) - displayData.setDirectoryOpen(hoveredObjVal.object, true); - } + //CLAY_AUTO_ID({ + // .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} + //}) { + // Clay_ElementId localID = CLAY_ID_LOCAL("scroll area"); + // float setScrollAmount; + // float setScrollContentHeight; + // CLAY(localID, { + // .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}} + // }) { + // gui.scroll_bar_area("scroll area", true, [&](float scrollContentHeight, float containerHeight, float& scrollAmount) { + // setScrollAmount = scrollAmount; + // setScrollContentHeight = scrollContentHeight; + // size_t itemCount = 0; + // size_t itemCountToStartAt = -scrollAmount / ENTRY_HEIGHT; + // size_t itemCountToEndAt = (containerHeight - scrollAmount) / ENTRY_HEIGHT; + // itemCountToEndAt++; + // std::vector parentIDStack; + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * itemCountToStartAt)} + // } + // }) {} + // recursive_gui(io, gui, displayData, selectionData, rootObjID, itemCount, itemCountToStartAt, itemCountToEndAt, 0, hoveredObject, parentIDStack, false); + // if(itemCount > itemCountToEndAt) { + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * (itemCount - itemCountToEndAt))} + // } + // }) {} + // } + // }, false); + // } + // Clay_ElementData scrollAreaBB = Clay_GetElementData(localID); + // if(scrollAreaBB.found) { + // // Clamp by fabs(scrollcontentheight - 3), small offset is added to account for error (when youre at the very edge it might assume youre at the top of an element) + // float mouseRelativeYPos = std::clamp(std::fabs(io.mouse.pos.y() - scrollAreaBB.boundingBox.y - setScrollAmount), 0.0f, std::fabs(setScrollContentHeight - 3.0f)); + // topHalfOfHovered = std::fmod(mouseRelativeYPos, ENTRY_HEIGHT) < (ENTRY_HEIGHT / 2.0f); + // } + //} - ParentObjectIDPair& objDraggedVal = objDragged.value(); - if(!selectionData.objsSelected.contains(objDraggedVal.object)) - objDragged = std::nullopt; - else if(!io.mouse.leftHeld) { - if(!hoveredObject.has_value()) - set_single_selected_object(selectionData); - else { - ParentObjectIDStack hoveredObjValToUse = hoveredObject.value(); // By value since it'll be edited - size_t indexToMoveFrom = displayData.getIndexOfObjInList(objDraggedVal); - size_t indexToMoveTo = displayData.getIndexOfObjInList({hoveredObjValToUse.parents.back(), hoveredObjValToUse.object}); - std::optional objHoveringOver = displayData.getObjInListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo); - if(objHoveringOver.value().isDirectoryOpen && !topHalfOfHovered) { // Move to front of the directory object - hoveredObjValToUse.parents.emplace_back(hoveredObjValToUse.object); - indexToMoveTo = 0; - } - else if(!topHalfOfHovered) - indexToMoveTo++; - if((indexToMoveFrom == indexToMoveTo || (indexToMoveFrom + 1) == indexToMoveTo) && objDraggedVal.parent == hoveredObjValToUse.parents.back()) - set_single_selected_object(selectionData); - else { - bool wrongMovement = false; - // Check if object is about to move into itself - for(const NetworkingObjects::NetObjID& parent : hoveredObjValToUse.parents) { - if(selectionData.objsSelected.contains(parent)) { - wrongMovement = true; - break; - } - } - if(!wrongMovement) { - displayData.moveObjectsToListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo, selectionData.orderedObjsSelected); - selectionData.objsSelected.clear(); - selectionData.orderedObjsSelected.clear(); - } - else - set_single_selected_object(selectionData); - } - } - objDragged = std::nullopt; - } - } + //// Any objects that were part of a collapsed directory will be unselected + //selectionData.objsSelected.clear(); + //for(auto& [parent, object] : selectionData.orderedObjsSelected) + // selectionData.objsSelected.emplace(object); - oldHoveredObject = hoveredObject; + //if(objDragged.has_value()) { + // if(hoveredObject.has_value() && !selectionData.objsSelected.contains(hoveredObject.value().object) && (std::chrono::steady_clock::now() - timeStartedHoveringOverObject) >= TIME_TO_HOVER_TO_OPEN_DIRECTORY) { + // ParentObjectIDStack& hoveredObjVal = hoveredObject.value(); + // size_t hoveredObjIndex = displayData.getIndexOfObjInList({hoveredObjVal.parents.back(), hoveredObjVal.object}); + // std::optional objHoveringOver = displayData.getObjInListAtIndex(hoveredObjVal.parents.back(), hoveredObjIndex); + // if(objHoveringOver.value().isDirectory && !objHoveringOver.value().isDirectoryOpen) + // displayData.setDirectoryOpen(hoveredObjVal.object, true); + // } + + // ParentObjectIDPair& objDraggedVal = objDragged.value(); + // if(!selectionData.objsSelected.contains(objDraggedVal.object)) + // objDragged = std::nullopt; + // else if(!io.mouse.leftHeld) { + // if(!hoveredObject.has_value()) + // set_single_selected_object(selectionData); + // else { + // ParentObjectIDStack hoveredObjValToUse = hoveredObject.value(); // By value since it'll be edited + // size_t indexToMoveFrom = displayData.getIndexOfObjInList(objDraggedVal); + // size_t indexToMoveTo = displayData.getIndexOfObjInList({hoveredObjValToUse.parents.back(), hoveredObjValToUse.object}); + // std::optional objHoveringOver = displayData.getObjInListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo); + // if(objHoveringOver.value().isDirectoryOpen && !topHalfOfHovered) { // Move to front of the directory object + // hoveredObjValToUse.parents.emplace_back(hoveredObjValToUse.object); + // indexToMoveTo = 0; + // } + // else if(!topHalfOfHovered) + // indexToMoveTo++; + // if((indexToMoveFrom == indexToMoveTo || (indexToMoveFrom + 1) == indexToMoveTo) && objDraggedVal.parent == hoveredObjValToUse.parents.back()) + // set_single_selected_object(selectionData); + // else { + // bool wrongMovement = false; + // // Check if object is about to move into itself + // for(const NetworkingObjects::NetObjID& parent : hoveredObjValToUse.parents) { + // if(selectionData.objsSelected.contains(parent)) { + // wrongMovement = true; + // break; + // } + // } + // if(!wrongMovement) { + // displayData.moveObjectsToListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo, selectionData.orderedObjsSelected); + // selectionData.objsSelected.clear(); + // selectionData.orderedObjsSelected.clear(); + // } + // else + // set_single_selected_object(selectionData); + // } + // } + // objDragged = std::nullopt; + // } + //} + + //oldHoveredObject = hoveredObject; } void TreeListing::set_single_selected_object(SelectionData& selectionData) { - selectionData.objsSelected.clear(); - selectionData.orderedObjsSelected.clear(); - selectionData.objsSelected.emplace(objDragged.value().object); - selectionData.orderedObjsSelected.emplace_back(objDragged.value()); + //selectionData.objsSelected.clear(); + //selectionData.orderedObjsSelected.clear(); + //selectionData.objsSelected.emplace(objDragged.value().object); + //selectionData.orderedObjsSelected.emplace_back(objDragged.value()); } void TreeListing::recursive_gui(UpdateInputData& io, GUIManager& gui, const DisplayData& displayData, SelectionData& selectionData, NetworkingObjects::NetObjID objID, size_t& itemCount, size_t itemCountToStartAt, size_t itemCountToEndAt, size_t listDepth, std::optional& hoveredObject, std::vector& parentIDStack, bool parentJustSelected) { - parentIDStack.emplace_back(objID); - for(size_t i = 0;; i++) { - std::optional objInListOptional = displayData.getObjInListAtIndex(objID, i); - if(!objInListOptional.has_value()) - break; - else { - auto& objInList = objInListOptional.value(); - bool selfJustSelected = false; - if(itemCount >= itemCountToStartAt && itemCount < itemCountToEndAt) { - gui.push_id(itemCount); - bool buttonClicked = false; - uint16_t topOutlineWidth = 0; - uint16_t bottomOutlineWidth = 0; + //parentIDStack.emplace_back(objID); + //for(size_t i = 0;; i++) { + // std::optional objInListOptional = displayData.getObjInListAtIndex(objID, i); + // if(!objInListOptional.has_value()) + // break; + // else { + // auto& objInList = objInListOptional.value(); + // bool selfJustSelected = false; + // if(itemCount >= itemCountToStartAt && itemCount < itemCountToEndAt) { + // gui.push_id(itemCount); + // bool buttonClicked = false; + // uint16_t topOutlineWidth = 0; + // uint16_t bottomOutlineWidth = 0; - bool isOldHoveredObject = oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id; - SkColor4f backgroundColor; - if(selectionData.objsSelected.contains(objInList.id)) - backgroundColor = io.theme->backColor1; - else if(isOldHoveredObject && objInList.isDirectory && !objInList.isDirectoryOpen && objDragged.has_value()) - backgroundColor = lerp_vec(io.theme->backColor2, io.theme->fillColor1, std::clamp(std::chrono::duration(std::chrono::steady_clock::now() - timeStartedHoveringOverObject) / TIME_TO_HOVER_TO_OPEN_DIRECTORY, 0.0f, 1.0f)); - else - backgroundColor = io.theme->backColor2; + // bool isOldHoveredObject = oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id; + // SkColor4f backgroundColor; + // if(selectionData.objsSelected.contains(objInList.id)) + // backgroundColor = io.theme->backColor1; + // else if(isOldHoveredObject && objInList.isDirectory && !objInList.isDirectoryOpen && objDragged.has_value()) + // backgroundColor = lerp_vec(io.theme->backColor2, io.theme->fillColor1, std::clamp(std::chrono::duration(std::chrono::steady_clock::now() - timeStartedHoveringOverObject) / TIME_TO_HOVER_TO_OPEN_DIRECTORY, 0.0f, 1.0f)); + // else + // backgroundColor = io.theme->backColor2; - if(oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id) { - if(objDragged.has_value()) { - ParentObjectIDPair& objDraggedVal = objDragged.value(); - if(objDraggedVal.object != objInList.id) { - if(topHalfOfHovered) - topOutlineWidth = 1; - else - bottomOutlineWidth = 1; - } - } - } - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }, - .backgroundColor = convert_vec4(backgroundColor) - }) { - if(listDepth != 0) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT * listDepth), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)} - }, - }) {} - } - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} - }, - .border = { - .color = convert_vec4(io.theme->frontColor1), - .width = { - .left = 0, - .right = 0, - .top = topOutlineWidth, - .bottom = bottomOutlineWidth, - .betweenChildren = 0 - } - } - }) { - if(objInList.isDirectory) { - if(gui.svg_icon_button_transparent("ico", objInList.isDirectoryOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", false, ENTRY_HEIGHT)) { - displayData.setDirectoryOpen(objInList.id, !objInList.isDirectoryOpen); - buttonClicked = true; - } - } - else { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }, - }) { - buttonClicked |= displayData.drawNonDirectoryObjIconGUI(objInList.id); - } - } - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - } - }) { - buttonClicked |= displayData.drawObjGUI({objID, objInList.id}); - } - } - - if(Clay_Hovered()) { - hoveredObject = {parentIDStack, objInList.id}; - if(!isOldHoveredObject) - timeStartedHoveringOverObject = std::chrono::steady_clock::now(); - if(io.mouse.leftClick && !buttonClicked && !parentJustSelected) { - if(io.key.leftShift) - dragHoldSelected = true; - else if(io.key.leftCtrl) { - bool alreadySelected = selectionData.objsSelected.contains(objInList.id); - if(alreadySelected) - selectionData.objsSelected.erase(objInList.id); - else { - select_and_unselect_parents(parentIDStack, objInList.id, selectionData); - selfJustSelected = true; - } - } - else { - if(!selectionData.objsSelected.contains(objInList.id)) { - selectionData.objsSelected.clear(); - selectionData.orderedObjsSelected.clear(); - selectionData.objsSelected.emplace(objInList.id); - selfJustSelected = true; - } - objDragged = {objID, objInList.id}; - } - } - if(io.key.leftShift && dragHoldSelected && !parentJustSelected) { - select_and_unselect_parents(parentIDStack, objInList.id, selectionData); - selfJustSelected = true; - } - } - } - gui.pop_id(); - } - if(!parentJustSelected) { - if(selectionData.objsSelected.contains(objInList.id)) - selectionData.orderedObjsSelected.emplace_back(objID, objInList.id); - } - else - unselect_object(objInList.id, selectionData); - itemCount++; - if(objInList.isDirectoryOpen) - recursive_gui(io, gui, displayData, selectionData, objInList.id, itemCount, itemCountToStartAt, itemCountToEndAt, listDepth + 1, hoveredObject, parentIDStack, selfJustSelected | parentJustSelected); - } - } - parentIDStack.pop_back(); + // if(oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id) { + // if(objDragged.has_value()) { + // ParentObjectIDPair& objDraggedVal = objDragged.value(); + // if(objDraggedVal.object != objInList.id) { + // if(topHalfOfHovered) + // topOutlineWidth = 1; + // else + // bottomOutlineWidth = 1; + // } + // } + // } + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}, + // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + // }, + // .backgroundColor = convert_vec4(backgroundColor) + // }) { + // if(listDepth != 0) { + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT * listDepth), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)} + // }, + // }) {} + // } + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} + // }, + // .border = { + // .color = convert_vec4(io.theme->frontColor1), + // .width = { + // .left = 0, + // .right = 0, + // .top = topOutlineWidth, + // .bottom = bottomOutlineWidth, + // .betweenChildren = 0 + // } + // } + // }) { + // if(objInList.isDirectory) { + // if(gui.svg_icon_button_transparent("ico", objInList.isDirectoryOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", false, ENTRY_HEIGHT)) { + // displayData.setDirectoryOpen(objInList.id, !objInList.isDirectoryOpen); + // buttonClicked = true; + // } + // } + // else { + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}, + // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + // }, + // }) { + // buttonClicked |= displayData.drawNonDirectoryObjIconGUI(objInList.id); + // } + // } + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + // .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} + // } + // }) { + // buttonClicked |= displayData.drawObjGUI({objID, objInList.id}); + // } + // } + // + // if(Clay_Hovered()) { + // hoveredObject = {parentIDStack, objInList.id}; + // if(!isOldHoveredObject) + // timeStartedHoveringOverObject = std::chrono::steady_clock::now(); + // if(io.mouse.leftClick && !buttonClicked && !parentJustSelected) { + // if(io.key.leftShift) + // dragHoldSelected = true; + // else if(io.key.leftCtrl) { + // bool alreadySelected = selectionData.objsSelected.contains(objInList.id); + // if(alreadySelected) + // selectionData.objsSelected.erase(objInList.id); + // else { + // select_and_unselect_parents(parentIDStack, objInList.id, selectionData); + // selfJustSelected = true; + // } + // } + // else { + // if(!selectionData.objsSelected.contains(objInList.id)) { + // selectionData.objsSelected.clear(); + // selectionData.orderedObjsSelected.clear(); + // selectionData.objsSelected.emplace(objInList.id); + // selfJustSelected = true; + // } + // objDragged = {objID, objInList.id}; + // } + // } + // if(io.key.leftShift && dragHoldSelected && !parentJustSelected) { + // select_and_unselect_parents(parentIDStack, objInList.id, selectionData); + // selfJustSelected = true; + // } + // } + // } + // gui.pop_id(); + // } + // if(!parentJustSelected) { + // if(selectionData.objsSelected.contains(objInList.id)) + // selectionData.orderedObjsSelected.emplace_back(objID, objInList.id); + // } + // else + // unselect_object(objInList.id, selectionData); + // itemCount++; + // if(objInList.isDirectoryOpen) + // recursive_gui(io, gui, displayData, selectionData, objInList.id, itemCount, itemCountToStartAt, itemCountToEndAt, listDepth + 1, hoveredObject, parentIDStack, selfJustSelected | parentJustSelected); + // } + //} + //parentIDStack.pop_back(); } void TreeListing::unselect_object(NetworkingObjects::NetObjID id, SelectionData& selectionData) { - selectionData.objsSelected.erase(id); - std::erase_if(selectionData.orderedObjsSelected, [&id](const auto& p) { - return p.object == id; - }); + //selectionData.objsSelected.erase(id); + //std::erase_if(selectionData.orderedObjsSelected, [&id](const auto& p) { + // return p.object == id; + //}); } void TreeListing::select_and_unselect_parents(const std::vector& parentIDStack, NetworkingObjects::NetObjID newSelectedObj, SelectionData& selectionData) { - for(const NetworkingObjects::NetObjID& parent : parentIDStack) { - if(selectionData.objsSelected.contains(parent)) { - unselect_object(parent, selectionData); - break; - } - } - selectionData.objsSelected.emplace(newSelectedObj); + //for(const NetworkingObjects::NetObjID& parent : parentIDStack) { + // if(selectionData.objsSelected.contains(parent)) { + // unselect_object(parent, selectionData); + // break; + // } + //} + //selectionData.objsSelected.emplace(newSelectedObj); } -void TreeListing::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { +bool TreeListing::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + return Element::input_mouse_button_callback(button, mouseHovering); +} + +bool TreeListing::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { + return Element::input_mouse_motion_callback(motion, mouseHovering); +} + +void TreeListing::input_key_callback(const InputManager::KeyCallbackArgs& key) { } } diff --git a/src/GUIStuff/Elements/TreeListing.hpp b/src/GUIStuff/Elements/TreeListing.hpp index 28d5529..036ea13 100644 --- a/src/GUIStuff/Elements/TreeListing.hpp +++ b/src/GUIStuff/Elements/TreeListing.hpp @@ -40,8 +40,11 @@ class TreeListing : public Element { std::unordered_set objsSelected; }; - void update(UpdateInputData& io, GUIManager& gui, NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData); - virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + TreeListing(GUIManager& gui); + void layout(NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData); + virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override; + virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override; + virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override; private: bool dragHoldSelected = false; diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 357eac6..c904fd3 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -35,7 +35,6 @@ GUIManager::GUIManager() clayArena = Clay_CreateArenaWithCapacityAndMemory(Clay_MinMemorySize(), malloc(Clay_MinMemorySize())); clayInstance = Clay_Initialize(clayArena, Clay_Dimensions(1.0f, 1.0f), (Clay_ErrorHandler)clay_error_handler); Clay_SetMeasureTextFunction(clay_skia_measure_text, this); - //Clay_SetDebugModeEnabled(true); } Clay_Dimensions GUIManager::clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData) { @@ -56,35 +55,9 @@ Clay_Dimensions GUIManager::clay_skia_measure_text(Clay_StringSlice str, Clay_Te return Clay_Dimensions(p->getMaxIntrinsicWidth(), p->getHeight()); } -void GUIManager::begin() { - for(auto& [id, e] : elements) - e.isUsedThisFrame = false; - - io->mouse.pos = io->mouse.globalPos - windowPos; - io->strArena = &strArena; - Clay_SetLayoutDimensions(Clay_Dimensions(windowSize.x(), windowSize.y())); - Clay_SetPointerState(Clay_Vector2((float)io->mouse.pos.x(), (float)io->mouse.pos.y()), io->mouse.leftHeld); - // NOTE: Using y scroll for x axis, since horizontal scrolling containers will just use the normal y scroll wheel as well - Clay_UpdateScrollContainers(false, Clay_Vector2(io->mouse.scroll.y() * 2.0f, io->mouse.scroll.y() * 2.0f), io->deltaTime); - Clay_SetCurrentContext(clayInstance); - - strArena.reset(); - - Clay_BeginLayout(); -} - -void GUIManager::end() { - renderCommands = Clay_EndLayout(); - if(!idStack.empty()) - throw std::runtime_error("[GUIManager::end] ID Stack is not empty on end (push_id and pop_id calls not equal)"); - std::erase_if(elements, [](auto& p) { - return !p.second.isUsedThisFrame; - }); -} - void GUIManager::draw(SkCanvas* canvas, bool skiaAA) { canvas->save(); - canvas->translate(windowPos.x(), windowPos.y()); + canvas->translate(io->windowPos.x(), io->windowPos.y()); for(size_t i = 0; i < static_cast(renderCommands.length); i++) { Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i); @@ -236,7 +209,7 @@ void GUIManager::draw(SkCanvas* canvas, bool skiaAA) { break; } case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { - Element* customElement = (Element*)command->renderData.custom.customData; + Element* customElement = static_cast(command->renderData.custom.customData); customElement->clay_draw(canvas, *io, command, skiaAA); break; } @@ -248,702 +221,70 @@ void GUIManager::draw(SkCanvas* canvas, bool skiaAA) { canvas->restore(); } -void GUIManager::top_to_bottom_window_popup_layout(const Clay_ElementId& windowLocalID, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate) { - CLAY(windowLocalID, {.layout = { - .sizing = {.width = x, .height = y }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = io->theme->childGap1, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io->theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(10), - .floating = { .attachTo = CLAY_ATTACH_TO_PARENT }, - .border = {.color = convert_vec4(io->theme->fillColor2), .width = CLAY_BORDER_OUTSIDE(1)} - }) { - obstructing_window(windowLocalID); - elemUpdate(); +void GUIManager::update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier) { + if(io->windowPos != windowPos || io->windowSize != windowSize || io->guiScaleMultiplier != guiScaleMultiplier) + set_to_layout(); + io->windowPos = windowPos; + io->windowSize = windowSize; + io->guiScaleMultiplier = guiScaleMultiplier; +} + +void GUIManager::set_to_layout() { + setToLayout = true; +} + +void GUIManager::layout_if_necessary() { + if(setToLayout) { + layout(); + setToLayout = false; } } -void GUIManager::left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = x, .height = y }, - .childGap = io->theme->childGap1, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT, - } - }) { - elemUpdate(); - } +void GUIManager::layout() { + constexpr int LAYOUT_RUN_COUNT = 2; + for(int i = 0; i < LAYOUT_RUN_COUNT; i++) + single_layout_run(); } -void GUIManager::left_to_right_line_layout(const std::function& elemUpdate) { - left_to_right_layout(CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0), elemUpdate); +void GUIManager::layout_begin() { + for(auto& [id, e] : elements) + e.isUsedThisFrame = false; + + io->strArena = &strArena; + Clay_SetLayoutDimensions(Clay_Dimensions(io->windowSize.x(), io->windowSize.y())); + Clay_SetCurrentContext(clayInstance); + + strArena.reset(); + + Clay_BeginLayout(); } -void GUIManager::left_to_right_line_centered_layout(const std::function& elemUpdate) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childGap = io->theme->childGap1, - .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT, - } - }) { - elemUpdate(); - } -} - -void GUIManager::text_label_size(std::string_view val, float modifier) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor1), .fontSize = static_cast(io->fontSize * modifier)})); -} - -void GUIManager::text_label_color(std::string_view val, const SkColor4f& color) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(color), .fontSize = io->fontSize })); -} - -void GUIManager::text_label_light(std::string_view val) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor2), .fontSize = io->fontSize })); -} - -void GUIManager::text_label(std::string_view val) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor1), .fontSize = io->fontSize })); -} - -void GUIManager::text_label_centered(std::string_view val) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - } - }) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor1), .fontSize = io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); - } -} - -void GUIManager::text_label_light_centered(std::string_view val) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - } - }) { - CLAY_TEXT(strArena.std_str_to_clay_str(val), CLAY_TEXT_CONFIG({.textColor = convert_vec4(io->theme->frontColor2), .fontSize = io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER})); - } -} - -bool GUIManager::radio_button(const char* id, bool val, const std::function& elemUpdate) { - push_id(id); - RadioButton* e = insert_element(); - e->update(*io, val, elemUpdate); - pop_id(); - return e->selection.clicked; -} - -void GUIManager::checkbox(const char* id, bool* val, const std::function& elemUpdate) { - push_id(id); - CheckBox* e = insert_element(); - e->update(*io, *val, elemUpdate); - if(e->selection.clicked) - *val = !(*val); - pop_id(); -} - -bool GUIManager::input_text_field(const char* id, std::string_view name, std::string* val, bool updateEveryEdit, const std::function& elemUpdate) { - bool toRet = false; - left_to_right_line_layout([&]() { - text_label(name); - toRet = input_text(id, val, updateEveryEdit, elemUpdate); +void GUIManager::layout_end() { + renderCommands = Clay_EndLayout(); + if(!idStack.empty()) + throw std::runtime_error("[GUIManager::end] ID Stack is not empty on end (push_id and pop_id calls not equal)"); + std::erase_if(elements, [](auto& p) { + return !p.second.isUsedThisFrame; }); - return toRet; + update_element_bounding_boxes(); } -void GUIManager::input_path_field(const char* id, std::string_view name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate) { - left_to_right_line_layout([&]() { - text_label(name); - input_path(id, val, fileTypeRestriction, elemUpdate); - }); -} - -void GUIManager::checkbox_field(const char* id, std::string_view name, bool* val, const std::function& elemUpdate) { - left_to_right_line_layout([&]() { - checkbox(id, val, elemUpdate); - text_label(name); - }); -} - -bool GUIManager::radio_button_field(const char* id, std::string_view name, bool val, const std::function& elemUpdate) { - bool toRet; - left_to_right_line_layout([&]() { - toRet = radio_button(id, val, elemUpdate); - text_label(name); - }); - return toRet; -} - -bool GUIManager::input_color_component_255(const char* id, float* val, const std::function& elemUpdate) { - push_id(id); - bool isUpdating = insert_element>()->update(*io, val, - [&](const std::string& str) { - int roundTo255; - std::stringstream ss; - ss << str; - ss >> roundTo255; - return std::clamp(static_cast(roundTo255) / 255.0f, 0.0f, 1.0f); - }, - [&](const float& colorFloat) { - int convertTo255 = colorFloat * 255; - std::stringstream ss; - ss << convertTo255; - return ss.str(); - }, true, false, - { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER - }, - elemUpdate); - pop_id(); - return isUpdating; -} - -bool GUIManager::input_text(const char* id, std::string* val, bool updateEveryEdit, const std::function& elemUpdate) { - push_id(id); - bool toRet = insert_element>()->update(*io, val, - [&](const std::string& str) { - return str; - }, - [&](const std::string& str) { - return str; - }, true, updateEveryEdit, - { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT - }, - elemUpdate); - pop_id(); - return toRet; -} - -bool GUIManager::selectable_button(const char* id, const std::function& elemUpdate, GUIStuff::SelectableButton::DrawType drawType, bool isSelected, bool onClick) { - push_id(id); - SelectableButton* e = insert_element(); - e->update(*io, drawType, elemUpdate, isSelected); - //bool toRet = onClick ? e->selection.clicked : e->selection.tapped; - bool toRet = e->selection.clicked; - pop_id(); - return toRet; -} - -void GUIManager::svg_icon(const char* id, const std::string& svgPath, bool isHighlighted, const std::function& elemUpdate) { - push_id(id); - insert_element()->update(*io, svgPath, isHighlighted, elemUpdate); - pop_id(); -} - -bool GUIManager::svg_icon_button(const char* id, const std::string& svgPath, bool isSelected, float size, bool hasBorder, const std::function& elemUpdate) { - bool toRet = false; - push_id(id); - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) } } }) { - toRet = selectable_button("0", [&](SelectionHelper& s, bool is) { - svg_icon("1", svgPath, is || s.held || s.hovered); - if(elemUpdate) - elemUpdate(); - }, SelectableButton::DrawType::FILLED, isSelected); - } - pop_id(); - return toRet; -} - -bool GUIManager::svg_icon_button_transparent(const char* id, const std::string& svgPath, bool isSelected, float size, bool hasBorder, bool onClick, const std::function& elemUpdate) { - bool toRet = false; - push_id(id); - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) } } }) { - toRet = selectable_button("0", [&](SelectionHelper& s, bool is) { - svg_icon("1", svgPath, is || s.held || s.hovered); - if(elemUpdate) - elemUpdate(); - }, SelectableButton::DrawType::TRANSPARENT_ALL, isSelected, onClick); - } - pop_id(); - return toRet; -} - -bool GUIManager::text_button_sized(const char* id, std::string_view text, Clay_SizingAxis x, Clay_SizingAxis y, bool isSelected, const std::function& elemUpdate) { - bool toRet = false; - CLAY_AUTO_ID({.layout = {.sizing = {.width = x, .height = y } } }) { - toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) { - text_label(text); - if(elemUpdate) - elemUpdate(); - }, SelectableButton::DrawType::FILLED, isSelected); - } - return toRet; -} - -bool GUIManager::text_button(const char* id, std::string_view text, bool isSelected, bool onClick, const std::function& elemUpdate) { - bool toRet = false; - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) } } }) { - toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) { - text_label(text); - if(elemUpdate) - elemUpdate(); - }, SelectableButton::DrawType::FILLED, isSelected, onClick); - } - return toRet; -} - -bool GUIManager::text_button_wide(const char* id, std::string_view text, bool isSelected, const std::function& elemUpdate) { - bool toRet = false; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - }}) { - toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) { - text_label(text); - if(elemUpdate) - elemUpdate(); - }, SelectableButton::DrawType::FILLED, isSelected); - } - return toRet; -} - -bool GUIManager::text_button_left_transparent(const char* id, std::string_view text, bool isSelected, const std::function& elemUpdate) { - bool toRet = false; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - }}) { - toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }}) { - text_label(text); - if(elemUpdate) - elemUpdate(); - } - }, SelectableButton::DrawType::TRANSPARENT_ALL, isSelected); - } - return toRet; -} - -bool GUIManager::input_scalar(const char* id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function& elemUpdate) { - bool isUpdating = false; - push_id(id); - isUpdating = insert_element>()->update(*io, val, - [&](const std::string& a) { - if(a.empty()) - return min; - uint32_t toRet; - std::stringstream ss; - ss << a; - ss >> toRet; - if(ss.fail()) - return min; - return std::clamp(toRet, min, max); - }, - [&](const uint8_t& a) { - return std::to_string(static_cast(a)); - }, true, false, - { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER - }, - elemUpdate); - pop_id(); - return isUpdating; -} - -void GUIManager::input_path(const char* id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate) { - push_id(id); - insert_element>()->update(*io, val, - [&](const std::string& a) { - std::filesystem::path toRet = std::filesystem::path(std::u8string_view(reinterpret_cast(a.c_str()), a.length())); - if(fileTypeRestriction != std::filesystem::file_type::none && std::filesystem::status(toRet).type() != fileTypeRestriction) - return std::optional(std::nullopt); - return std::optional(toRet); - }, - [&](const std::filesystem::path& a) { - return a.string(); - }, true, false, - { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT - }, - elemUpdate); - pop_id(); -} - -void GUIManager::tab_list(const char* id, const std::vector>& tabNames, size_t& selectedTab, std::optional& closedTab, const std::function& elemUpdate) { - push_id(id); - insert_element()->update(*io, tabNames, selectedTab, closedTab, elemUpdate); - pop_id(); -} - -bool GUIManager::font_picker(const char* id, std::string* fontName) { - push_id(id); - bool toRet = insert_element()->update(*io, fontName, this); - pop_id(); - return toRet; -} - -void GUIManager::dropdown_select(const char* id, size_t* val, const std::vector& selections, float width, const std::function& hoverboxElemUpdate) { - push_id(id); - struct DropdownData { - bool isOpen = false; - }; - DropdownData& dropdownData = insert_any_with_id(0, {}); - left_to_right_layout(CLAY_SIZING_FIXED(width), CLAY_SIZING_FIT(0), [&]() { - bool click = selectable_button(id, [&](SelectionHelper& s, bool iS) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, - .padding = {.left = 4, .right = 4}, - .childGap = io->theme->childGap1, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT, - } - }) { - text_label(selections[*val]); - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIT(SMALL_BUTTON_SIZE)} - } - }) { - svg_icon("dropico", "data/icons/droparrow.svg", dropdownData.isOpen); - } - } - }, SelectableButton::DrawType::FILLED, dropdownData.isOpen); - if(dropdownData.isOpen) { - Clay_ElementId localID = CLAY_ID_LOCAL("DROPDOWN"); - Clay_ElementData dropdownElemData = Clay_GetElementData(localID); - float calculatedDropdownMaxHeight = 0.0f; - if(dropdownElemData.found) - calculatedDropdownMaxHeight = std::max(windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f); - CLAY(localID, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)}, - .childGap = 0 - }, - .backgroundColor = convert_vec4(io->theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(4), - .floating = { - .offset = { - .y = 4 - }, - .attachPoints = { - .element = CLAY_ATTACH_POINT_LEFT_TOP, - .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM - }, - .attachTo = CLAY_ATTACH_TO_PARENT - }, - .border = { - .color = convert_vec4(io->theme->fillColor2), - .width = CLAY_BORDER_OUTSIDE(1) - } - }) { - obstructing_window(localID); - scroll_bar_many_entries_area("dropdown scroll area", 18.0f, selections.size(), true, [&](size_t i, bool) { - bool selectedEntry = *val == i; - - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(18)}, - .padding = CLAY_PADDING_ALL(0), - .childGap = 0, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - SkColor4f entryColor; - if(selectedEntry) - entryColor = io->theme->backColor2; - else if(Clay_Hovered()) - entryColor = io->theme->backColor2; - else - entryColor = io->theme->backColor1; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .padding = {.left = 2, .right = 2, .top = 0, .bottom = 0}, - .childGap = 0, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER} - }, - .backgroundColor = convert_vec4(entryColor) - }) { - text_label(selections[i]); - if(io->mouse.leftClick && Clay_Hovered()) { - *val = i; - dropdownData.isOpen = false; - } - } - } - }); - if(hoverboxElemUpdate) - hoverboxElemUpdate(); - } +void GUIManager::update_element_bounding_boxes() { + for(auto& e : elements) + e.second.elem->clear_bounding_box(); + for(size_t i = 0; i < static_cast(renderCommands.length); i++) { + Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i); + if(command->commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM) { + Element* customElement = static_cast(command->renderData.custom.customData); + customElement->update_bounding_box(command); } - if(click) - dropdownData.isOpen = !dropdownData.isOpen; - }); - pop_id(); -} - -void GUIManager::scroll_bar_area(const char* id, bool clipHorizontal, const std::function& elemUpdate, bool maxScrollBugWorkaround) { - push_id(id); - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - struct ScrollAreaData { - float currentScrollPos = 0.0f; - bool isMoving = false; - - float scrollerStartPos; - float mouseStartPos; - - float contentDimensions = 100.0f; - float containerDimensions = 100.0f; - }; - - ScrollAreaData& sD = insert_any_with_id(0, {}); - - Clay_ElementId localID = CLAY_ID_LOCAL("SCROLL_AREA"); - - CLAY(localID, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .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 - Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(localID); - Clay_BoundingBox scrollAreaBB = Clay_GetElementData(localID).boundingBox; - - sD.contentDimensions = scrollData.contentDimensions.height; - sD.containerDimensions = scrollData.scrollContainerDimensions.height; - - 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) + (maxScrollBugWorkaround ? 3.0f : 0.0f); //NOTE: Adding + 3.0f to prevent bug with scroller that pushes everything under it when its at the bottom of the scroll area. Only happens with some scroll areas - float scrollerPos = std::fabs(scrollData.scrollPosition->y / scrollPosMax); - float areaAboveScrollerSize = scrollerPos * (sAreaDim - scrollerSize); - - sD.currentScrollPos = scrollData.scrollPosition->y; - - 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; - - 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) { - sD.isMoving = true; - if(isHoveringOverScroller) - sD.scrollerStartPos = scrollAreaBB.y + areaAboveScrollerSize + scrollerSize * 0.5f; - else - sD.scrollerStartPos = std::clamp(io->mouse.pos.y(), scrollAreaBB.y + scrollerSize * 0.5f, scrollAreaBB.y + scrollAreaBB.height - scrollerSize * 0.5f); - sD.mouseStartPos = io->mouse.pos.y(); - } - if(!io->mouse.leftHeld) - sD.isMoving = false; - if(sD.isMoving) { - float newScrollPosFrac; - newScrollPosFrac = std::clamp((sD.scrollerStartPos - (sD.mouseStartPos - io->mouse.pos.y()) - scrollAreaBB.y - scrollerSize * 0.5f) / (scrollAreaBB.height - scrollerSize), 0.0f, 1.0f); - sD.currentScrollPos = newScrollPosFrac * (-scrollPosMax); - } - sD.currentScrollPos = std::clamp(sD.currentScrollPos, -scrollPosMax, 0.0f); - scrollData.scrollPosition->y = sD.currentScrollPos; - } - } - else - sD.currentScrollPos = 0.0f; - } - pop_id(); -} - - -void GUIManager::scroll_bar_many_entries_area(const char* id, float entryHeight, size_t entryCount, bool clipHorizontal, const std::function& entryUpdate, const std::function& elemUpdate) { - push_id(id); - scroll_bar_area(id, clipHorizontal, [&](float scrollContentHeight, float containerHeight, float& scrollAmount) { - if(elemUpdate) - elemUpdate(scrollContentHeight, containerHeight, scrollAmount); - - bool listHovered = Clay_Hovered(); - - float absScrollAmount = std::fabs(scrollAmount); - size_t startPoint = absScrollAmount / entryHeight; - size_t elementsContainable = (containerHeight / entryHeight) + 2; // Displaying 2 more elements ensures that there isn't any empty space in the container - size_t endPoint = std::min(entryCount, startPoint + elementsContainable); - - if(startPoint != 0) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(startPoint * entryHeight)}, - } - }) { } - } - - for(size_t i = startPoint; i < endPoint; i++) { - push_id(i); - entryUpdate(i, listHovered); - pop_id(); - } - - if(endPoint != entryCount) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((entryCount - endPoint) * entryHeight)}, - } - }) { } - } - }); - pop_id(); -} - -bool GUIManager::rotate_wheel(const char* id, double* angle, float size, const std::function& elemUpdate) { - bool toRet = false; - push_id(id); - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) } } }) { - RotateWheel* e = insert_element(); - e->update(*io, angle, elemUpdate); - toRet = e->selection.clicked; - } - pop_id(); - return toRet; -} - -void GUIManager::text_paragraph(const char* id, std::unique_ptr paragraph, float width, const std::function& elemUpdate) { - push_id(id); - TextParagraph* e = insert_element(); - e->update(*io, std::move(paragraph), width, elemUpdate); - pop_id(); -} - -void GUIManager::paint_circle_popup_menu(const char* id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function& elemUpdate) { - float size = 300.0f; - push_id(id); - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size)} - }, - .floating = { - .offset = {centerPos.x() - size * 0.5f, centerPos.y() - size * 0.5f}, - .zIndex = -1, - .attachTo = CLAY_ATTACH_TO_ROOT - } - }) { - PaintCircleMenu* e = insert_element(); - e->update(*this, val, elemUpdate); - } - pop_id(); -} - -void GUIManager::list_popup_menu(const char* id, Vector2f popupPos, const std::function& elemUpdate) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} - } - }) { - push_id(id); - - Clay_ElementId localID = CLAY_ID_LOCAL("LIST_POPUP"); - - Clay_ElementData actionListElemData = Clay_GetElementData(localID); - - if(actionListElemData.found) { - if((popupPos.y() + actionListElemData.boundingBox.height) > windowSize.y()) { - popupPos.y() -= actionListElemData.boundingBox.height; - } - } - - CLAY(localID, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0)}, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = 1, - .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), - .floating = { - .offset = {popupPos.x(), popupPos.y()}, - .zIndex = -1, - .attachTo = CLAY_ATTACH_TO_ROOT - } - }) { - obstructing_window(localID); - push_id(1); - elemUpdate(); - pop_id(); - } - pop_id(); } } -void GUIManager::tree_listing(const char* id, NetworkingObjects::NetObjID rootObjID, const TreeListing::DisplayData& displayData, TreeListing::SelectionData& selectionData) { - push_id(id); - insert_element()->update(*io, *this, rootObjID, displayData, selectionData); - pop_id(); -} - -void GUIManager::obstructing_window(const Clay_ElementId& elementID) { - if(Clay_Hovered()) - io->hoverObstructed = true; - const Clay_ElementData& elemData = Clay_GetElementData(elementID); - io->hoverObstructingAABBs.emplace_back(SCollision::AABB{Vector2f{elemData.boundingBox.x, elemData.boundingBox.y} + windowPos, Vector2f{elemData.boundingBox.x + elemData.boundingBox.width, elemData.boundingBox.y + elemData.boundingBox.height} + windowPos}); +void GUIManager::single_layout_run() { + layout_begin(); + io->layoutRun(); + layout_end(); } void GUIManager::clay_error_handler(Clay_ErrorData errorData) { @@ -962,4 +303,8 @@ void GUIManager::pop_id() { idStack.pop_back(); } +void GUIManager::set_post_callback_func(const std::function& f) { + postCallbackFunc = f; +} + } diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index 975e071..5bc43f2 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -1,22 +1,9 @@ #pragma once #include #include -#include "Elements/Element.hpp" -#include "Elements/NumberSlider.hpp" -#include "Elements/CheckBox.hpp" -#include "Elements/PaintCircleMenu.hpp" -#include "Elements/RadioButton.hpp" -#include "Elements/ColorPicker.hpp" -#include "Elements/SelectableButton.hpp" -#include "Elements/TextBox.hpp" -#include "Elements/TreeListing.hpp" -#include #include "GUIManagerID.hpp" -#include -#include #include - -#include +#include "Elements/Element.hpp" using namespace Eigen; @@ -24,383 +11,65 @@ namespace GUIStuff { class GUIManager { public: - constexpr static float BIG_BUTTON_SIZE = 30; - constexpr static float SMALL_BUTTON_SIZE = 20; - struct ElementContainer { std::unique_ptr elem; - std::any extra; bool isUsedThisFrame; }; GUIManager(); - void begin(); - void end(); void draw(SkCanvas* canvas, bool skiaAA); + void set_to_layout(); + void layout_if_necessary(); + + void update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier); - Vector2f windowPos = Vector2f{0.0f, 0.0f}; - Vector2f windowSize = Vector2f{0.0f, 0.0f}; std::shared_ptr io; GUIManagerIDStack idStack; std::unordered_map elements; - Vector2f screen_pos_to_window_pos(const Vector2f& screenPos); - Vector2f get_mouse_pos(); - - bool input_text_field(const char* id, std::string_view name, std::string* val, bool updateEveryEdit = true, const std::function& elemUpdate = nullptr); - - void text_paragraph(const char* id, std::unique_ptr paragraph, float width, const std::function& elemUpdate = nullptr); - void text_label_color(std::string_view val, const SkColor4f& color); - void text_label_size(std::string_view val, float modifier); - void text_label_light(std::string_view val); - void text_label(std::string_view val); - void text_label_light_centered(std::string_view val); - void text_label_centered(std::string_view val); - bool text_button_left_transparent(const char* id, std::string_view text, bool isSelected = false, const std::function& elemUpdate = nullptr); - bool text_button(const char* id, std::string_view text, bool isSelected = false, bool onClick = false, const std::function& elemUpdate = nullptr); - bool text_button_wide(const char* id, std::string_view text, bool isSelected = false, const std::function& elemUpdate = nullptr); - bool text_button_sized(const char* id, std::string_view text, Clay_SizingAxis x, Clay_SizingAxis y, bool isSelected = false, const std::function& elemUpdate = nullptr); - - void svg_icon(const char* id, const std::string& svgPath, bool isHighlighted = false, const std::function& elemUpdate = nullptr); - bool svg_icon_button(const char* id, const std::string& svgPath, bool isSelected = false, float size = BIG_BUTTON_SIZE, bool hasBorder = true, const std::function& elemUpdate = nullptr); - - bool svg_icon_button_transparent(const char* id, const std::string& svgPath, bool isSelected = false, float size = BIG_BUTTON_SIZE, bool hasBorder = true, bool onClick = false, const std::function& elemUpdate = nullptr); - - bool rotate_wheel(const char* id, double* angle, float size = BIG_BUTTON_SIZE, const std::function& elemUpdate = nullptr); - - void top_to_bottom_window_popup_layout(const Clay_ElementId& localID, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); - void left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); - void left_to_right_line_layout(const std::function& elemUpdate); - void left_to_right_line_centered_layout(const std::function& elemUpdate); - - bool selectable_button(const char* id, const std::function& elemUpdate, GUIStuff::SelectableButton::DrawType drawType, bool isSelected, bool onClick = false); - void push_id(int64_t id); void push_id(const char* id); void pop_id(); - void scroll_bar_area(const char* id, bool clipHorizontal, const std::function& elemUpdate, bool maxScrollBugWorkaround = true); - void scroll_bar_many_entries_area(const char* id, float entryHeight, size_t entryCount, bool clipHorizontal, const std::function& entryUpdate, const std::function& elemUpdate = nullptr); + void set_post_callback_func(const std::function& f); - bool input_color_component_255(const char* id, float* val, const std::function& elemUpdate = nullptr); - bool input_text(const char* id, std::string* val, bool updateEveryEdit = true, const std::function& elemUpdate = nullptr); + int16_t zIndex = 0; + template ElementType* element(const char* id, const Args&... a) { + push_id(id); + ElementType* elem = insert_element(); + elem->layout(a...); + pop_id(); + return elem; + } - bool radio_button(const char* id, bool val, const std::function& elemUpdate = nullptr); - bool radio_button_field(const char* id, std::string_view name, bool val, const std::function& elemUpdate = nullptr); + DefaultStringArena strArena; - void checkbox_field(const char* id, std::string_view name, bool* val, const std::function& elemUpdate = nullptr); - void checkbox(const char* id, bool* val, const std::function& elemUpdate = nullptr); - - void tab_list(const char* id, const std::vector>& tabNames, size_t& selectedTab, std::optional& closedTab, const std::function& elemUpdate = nullptr); - - void input_path(const char* id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate = nullptr); - void input_path_field(const char* id, std::string_view name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function& elemUpdate = nullptr); - - void dropdown_select(const char* id, size_t* val, const std::vector& selections, float width = 200.0f, const std::function& hoverboxElemUpdate = nullptr); - - void paint_circle_popup_menu(const char* id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function& elemUpdate = nullptr); - void list_popup_menu(const char* id, Vector2f popupPos, const std::function& elemUpdate); - - void tree_listing(const char* id, NetworkingObjects::NetObjID rootObjID, const TreeListing::DisplayData& displayData, TreeListing::SelectionData& selectionData); - - void obstructing_window(const Clay_ElementId& elementID); + private: + void layout(); + void update_element_bounding_boxes(); + void layout_begin(); + void layout_end(); + void single_layout_run(); template NewElement* insert_element() { auto [it, inserted] = elements.emplace(idStack, ElementContainer()); auto& container = it->second; if(inserted) - container = {std::make_unique()}; + container = {std::make_unique(*this)}; + container.elem->zIndex = zIndex; container.isUsedThisFrame = true; return static_cast(it->second.elem.get()); } - template T& insert_any(const T& def) { - auto [it, inserted] = elements.emplace(idStack, ElementContainer()); - auto& container = it->second; - if(inserted) - it->second.extra = def; - container.isUsedThisFrame = true; - return std::any_cast(it->second.extra); - } - - template T& insert_any_with_id(int64_t id, const T& def) { - push_id(id); - T& toRet = insert_any(def); - 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 char* id, T* val, const std::function(const std::string&)>& fromStr, const std::function& toStr, bool singleLine, bool updateEveryEdit, const InputManager::TextInputProperties& inputProps, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - push_id(id); - isUpdating = insert_element>()->update(*io, val, fromStr, toStr, singleLine, updateEveryEdit, inputProps, elemUpdate); - pop_id(); - return isUpdating; - } - - template bool input_scalar(const char* id, T* val, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { - InputManager::TextInputProperties textInputProps { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_NUMBER, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_NUMBER - }; - if(std::is_signed()) - textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_SIGNED; - if(std::is_floating_point()) - textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_DECIMAL; - return input_generic(id, val, - [&](const std::string& a) { - if(a.empty()) - return min; - T toRet; - std::stringstream ss; - ss << a; - ss >> toRet; - if(ss.fail()) - return min; - return std::clamp(toRet, min, max); - }, - [&](const T& a) { - std::stringstream ss; - if(std::is_floating_point()) { - ss.precision(decimalPrecision); - ss << std::fixed << *val; - } - else - ss << *val; - return ss.str(); - }, true, false, textInputProps, elemUpdate); - } - - bool input_scalar(const char* id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function& elemUpdate); - - template bool input_scalar_fields(const char* id, std::string_view name, TContainer* val, size_t elemCount, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - push_id(id); - left_to_right_line_layout([&]() { - text_label(name); - for(size_t i = 0; i < elemCount; i++) { - push_id(i); - isUpdating |= input_scalar("field", &(*val)[i], min, max, decimalPrecision, elemUpdate); - pop_id(); - } - }); - pop_id(); - return isUpdating; - } - - template bool input_color_hex(const char* id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { - return input_generic(id, val, - [&](const std::string& str) { - T def = {0.0f, 0.0f, 0.0f, 1.0f}; - unsigned startIndex = 0; - if(str.empty()) - return def; - - if(str[0] == '#') - startIndex++; - - for(unsigned i = 0; i < (selectAlpha ? 4 : 3); i++) { - int byteColor; - std::stringstream ss; - size_t startingAt = startIndex + i * 2; - if(startingAt + 1 >= str.size()) - break; - ss << str.substr(startingAt, 2); - ss >> std::hex >> byteColor; - if(ss.fail()) - break; - def[i] = byteColor / 255.0f; - } - return def; - }, - [&](const T& color) { - std::stringstream ss; - ss << "#" << std::setfill('0') << std::setw(2) << std::hex; - for(size_t i = 0; i < (selectAlpha ? 4 : 3); i++) { - int convertTo255 = static_cast(std::clamp(color[i], 0, 1) * 255); - ss << std::setfill('0') << std::setw(2) << std::hex << convertTo255; - } - return ss.str(); - }, - true, false, - { - .inputType = SDL_TextInputType::SDL_TEXTINPUT_TYPE_TEXT, - .capitalization = SDL_Capitalization::SDL_CAPITALIZE_NONE, - .autocorrect = false, - .multiline = false, - .androidInputType = InputManager::AndroidInputType::ANDROIDTEXT_TYPE_CLASS_TEXT - }, - elemUpdate); - } - - template bool color_picker(const char* id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { - push_id(id); - bool isUpdating = insert_element>()->update(*io, val, selectAlpha, elemUpdate); - pop_id(); - return isUpdating; - } - - template bool slider_scalar(const char* id, T* val, T min, T max, const std::function& elemUpdate = nullptr) { - push_id(id); - bool isUpdating = insert_element>()->update(*io, val, min, max, elemUpdate); - pop_id(); - return isUpdating; - } - - template bool input_scalar_field(const char* id, std::string_view name, T* val, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - left_to_right_line_layout([&]() { - text_label(name); - isUpdating = input_scalar(id, val, min, max, decimalPrecision, elemUpdate); - }); - return isUpdating; - } - - template bool slider_scalar_field(const char* id, std::string_view name, T* val, T min, T max, int decimalPrecision = 0, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - push_id(id); - left_to_right_line_layout([&]() { - text_label(name); - isUpdating |= input_scalar("0", val, min, max, decimalPrecision, nullptr); - }); - isUpdating |= slider_scalar("1", val, min, max, elemUpdate); - pop_id(); - return isUpdating; - } - - template bool big_color_button(const char* id, T* val, bool isSelected = false, const std::function& elemUpdate = nullptr) { - bool toRet = false; - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)}}}) { - toRet = color_button(id, val, isSelected, elemUpdate); - } - return toRet; - } - - template bool color_button(const char* id, T* val, bool isSelected = false, const std::function& elemUpdate = nullptr) { - return selectable_button(id, [&](SelectionHelper& s, bool iS) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - }, - .backgroundColor = convert_vec4(*val), - .cornerRadius = CLAY_CORNER_RADIUS(5), - }) {} - if(elemUpdate) - elemUpdate(); - }, GUIStuff::SelectableButton::DrawType::TRANSPARENT_BORDER, isSelected); - } - - template bool color_button_big(const char* id, T* val, bool isSelected = false, const std::function& elemUpdate = nullptr) { - bool toRet = false; - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE)}}}) { - toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) { - CLAY_AUTO_ID({.layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - }, - .backgroundColor = convert_vec4(*val), - .cornerRadius = CLAY_CORNER_RADIUS(5), - }) {} - if(elemUpdate) - elemUpdate(); - }, GUIStuff::SelectableButton::DrawType::TRANSPARENT_BORDER, isSelected); - } - return toRet; - } - - template bool color_picker_button(const char* id, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - push_id(id); - bool& isOpen = insert_any_with_id(0, false); - bool clicked; - CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE) } } }) { - clicked = color_button("0", val, isOpen, elemUpdate); - if(clicked) - isOpen = true; - if(isOpen) { - top_to_bottom_window_popup_layout(CLAY_ID_LOCAL("COLOR PICKER"), CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&]() { - isUpdating = color_picker_items("c", val, selectAlpha); - if(io->mouse.leftClick && !Clay_Hovered() && !clicked) - isOpen = false; - }); - } - } - pop_id(); - return isUpdating; - } - - bool font_picker(const char* id, std::string* fontName); - - template bool color_picker_items(const char* id, T* val, bool selectAlpha, float fixedPickerWidth = 0.0f) { - bool isUpdating = false; - push_id(id); - if(fixedPickerWidth == 0.0f) - isUpdating |= color_picker("c", val, selectAlpha); - else { - CLAY_AUTO_ID({ - .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]); - text_label("G"); - isUpdating |= input_color_component_255("g", &(*val)[1]); - text_label("B"); - isUpdating |= input_color_component_255("b", &(*val)[2]); - if(selectAlpha) { - text_label("A"); - isUpdating |= input_color_component_255("a", &(*val)[3]); - } - }); - left_to_right_line_layout([&]() { - text_label("Hex"); - isUpdating |= input_color_hex("h", val, selectAlpha); - }); - pop_id(); - return isUpdating; - } - - template bool color_picker_button_field(const char* id, std::string_view name, T* val, bool selectAlpha, const std::function& elemUpdate = nullptr) { - bool isUpdating = false; - left_to_right_line_layout([&]() { - isUpdating = color_picker_button(id, val, selectAlpha, elemUpdate); - text_label(name); - }); - return isUpdating; - } - - private: static void clay_error_handler(Clay_ErrorData errorData); static Clay_Dimensions clay_skia_measure_text(Clay_StringSlice str, Clay_TextElementConfig* config, void* userData); - DefaultStringArena strArena; - + std::function postCallbackFunc; Clay_Context* clayInstance; Clay_Arena clayArena; Clay_RenderCommandArray renderCommands; + bool setToLayout; }; } diff --git a/src/RichText/TextBox.cpp b/src/RichText/TextBox.cpp index be7542d..fd97557 100644 --- a/src/RichText/TextBox.cpp +++ b/src/RichText/TextBox.cpp @@ -145,12 +145,14 @@ void TextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift, cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::LEFT_WORD : Movement::LEFT, cur.pos)); + if(onUserTextEdit) onUserTextEdit(); break; case InputKey::DEL: if(cur.selectionBeginPos != cur.selectionEndPos) cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); else cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.pos, move(ctrl ? Movement::RIGHT_WORD : Movement::RIGHT, cur.pos)); + if(onUserTextEdit) onUserTextEdit(); break; case InputKey::ENTER: process_text_input(cur, "\n", inputModMap); @@ -170,6 +172,7 @@ void TextBox::process_key_input(Cursor& cur, InputKey in, bool ctrl, bool shift, if(in != InputKey::UP && in != InputKey::DOWN) cur.previousX = std::nullopt; + if(onChange) onChange(); inputChangedTextBox = true; } @@ -224,6 +227,7 @@ void TextBox::process_mouse_left_button(Cursor& cur, const Vector2f& pos, int cl cur.previousX = std::nullopt; + if((oldCursor != cur) && onChange) onChange(); inputChangedTextBox |= (oldCursor != cur); } else @@ -239,6 +243,8 @@ std::pair TextBox::process_cut(Cursor& cur) { if(cur.selectionBeginPos != cur.selectionEndPos) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.previousX = std::nullopt; + if(onChange) onChange(); + if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } return toRet; @@ -250,6 +256,8 @@ void TextBox::process_text_input(Cursor& cur, const std::string& in, const std:: cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert(cur.pos, in, inputModMap); cur.previousX = std::nullopt; + if(onChange) onChange(); + if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } } @@ -260,6 +268,8 @@ void TextBox::process_rich_text_input(Cursor& cur, const TextData& richText) { cur.selectionEndPos = cur.selectionBeginPos = cur.pos = remove(cur.selectionBeginPos, cur.selectionEndPos); cur.selectionEndPos = cur.selectionBeginPos = cur.pos = insert_rich_text(cur.pos, richText); cur.previousX = std::nullopt; + if(onChange) onChange(); + if(onUserTextEdit) onUserTextEdit(); inputChangedTextBox = true; } } @@ -456,6 +466,7 @@ TextPosition TextBox::insert_rich_text(TextPosition p, const TextData& richText) paragraphs[pIndex].pStyleData = richText.paragraphs[pIndex - p.fParagraphIndex].pStyleData; } + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; @@ -483,6 +494,7 @@ void TextBox::set_rich_text_data(const TextData& richText) { if(richText.paragraphs.empty()) paragraphs.emplace_back(); tStyleMods = richText.tStyleMods; + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -514,6 +526,7 @@ std::string TextBox::get_text_between(TextPosition p1, TextPosition p2) { void TextBox::set_initial_text_style(const skia::textlayout::TextStyle& tStyle) { if(!tStyle.equals(initialTStyle)) { initialTStyle = tStyle; + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -585,6 +598,7 @@ void TextBox::set_text_style_modifier_between(TextPosition p1, TextPosition p2, insert_style_at_pos(start, modifier); insert_style_at_pos(end, lastModOfThisTypeBeforeEnd); // Even if the end is the literal end of the text (which wont be seen), the end style must still be placed at the end so that it can merge with and delete the start style when the styled text is erased remove_duplicate_text_style_mods(); + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -600,6 +614,7 @@ void TextBox::set_text_alignment_between(size_t paragraphIndex1, size_t paragrap } } if(anythingChanged) { + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -615,6 +630,7 @@ void TextBox::set_text_direction_between(size_t paragraphIndex1, size_t paragrap } } if(anythingChanged) { + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -851,6 +867,7 @@ int TextBox::get_line_number_at_from_byte_text_pos(TextPosition pos) { void TextBox::set_width(float newWidth) { if(width != newWidth) { width = std::max(newWidth, 4.0f); + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -868,6 +885,7 @@ void TextBox::set_allow_newlines(bool allow) { void TextBox::set_font_data(const std::shared_ptr& fD) { if(fontData != fD) { fontData = fD; + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -1037,6 +1055,7 @@ TextPosition TextBox::insert(TextPosition pos, std::string_view textToInsert, co for(const auto& [modType, modifier] : inputModMap.value()) set_text_style_modifier_between(oldPos, pos, modifier); } + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; } @@ -1086,6 +1105,7 @@ TextPosition TextBox::remove(TextPosition p1, TextPosition p2) { } remove_duplicate_text_style_mods(); + if(onChange) onChange(); inputChangedTextBox = true; needsRebuild = true; diff --git a/src/RichText/TextBox.hpp b/src/RichText/TextBox.hpp index 97d1c98..4da44f6 100644 --- a/src/RichText/TextBox.hpp +++ b/src/RichText/TextBox.hpp @@ -67,6 +67,8 @@ class TextBox { TextBox(); bool inputChangedTextBox = false; + std::function onChange; + std::function onUserTextEdit; enum class Movement { NOWHERE, diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index 5493fd6..f3c6974 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -2,14 +2,17 @@ #include "DrawingProgram/Tools/DrawingProgramToolBase.hpp" #include "DrawingProgram/Tools/ScreenshotTool.hpp" #include "FileHelpers.hpp" +#include "GUIStuff/ElementHelpers/CheckBoxHelpers.hpp" +#include "GUIStuff/ElementHelpers/LayoutHelpers.hpp" +#include "GUIStuff/ElementHelpers/PopupHelpers.hpp" +#include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp" +#include "GUIStuff/ElementHelpers/ScrollAreaHelpers.hpp" #include "Helpers/ConvertVec.hpp" #include "Helpers/FileDownloader.hpp" #include "Helpers/MathExtras.hpp" #include "Helpers/Networking/NetLibrary.hpp" #include "Helpers/NetworkingObjects/NetObjGenericSerializedClass.hpp" #include "MainProgram.hpp" -#include "GUIStuff/Elements/Element.hpp" -#include "GUIStuff/GUIManager.hpp" #include "InputManager.hpp" #include "ResourceDisplay/ImageResourceDisplay.hpp" #include "VersionConstants.hpp" @@ -35,6 +38,19 @@ #include #include +#include "GUIStuff/GUIManager.hpp" +#include "GUIStuff/ElementHelpers/TextLabelHelpers.hpp" +#include "GUIStuff/ElementHelpers/ButtonHelpers.hpp" +#include "GUIStuff/ElementHelpers/PopupHelpers.hpp" +#include "GUIStuff/ElementHelpers/TextBoxHelpers.hpp" +#include "GUIStuff/ElementHelpers/ColorPickerHelpers.hpp" +#include "GUIStuff/ElementHelpers/RadioButtonHelpers.hpp" +#include "GUIStuff/ElementHelpers/NumberSliderHelpers.hpp" +#include "GUIStuff/Elements/ScrollArea.hpp" +#include "GUIStuff/Elements/LayoutElement.hpp" +#include "GUIStuff/Elements/DropDown.hpp" +#include "GUIStuff/Elements/SVGIcon.hpp" + #define UPDATE_DOWNLOAD_URL "https://infinipaint.com/download.html" #define UPDATE_NOTIFICATION_URL "https://infinipaint.com/updateNotificationVersion.txt" @@ -44,6 +60,9 @@ Toolbar::NativeFilePicker Toolbar::nativeFilePicker; +using namespace GUIStuff; +using namespace ElementHelpers; + Toolbar::Toolbar(MainProgram& initMain): io(std::make_shared()), main(initMain) @@ -392,16 +411,18 @@ void Toolbar::set_config_json(const nlohmann::json& j, VersionNumber version) { } void Toolbar::update() { + calculate_final_gui_scale(); +} + +void Toolbar::layout_run() { #ifndef __EMSCRIPTEN__ update_notification_check(); #endif - start_gui(); - if(main.drawGui) { CLAY_AUTO_ID({ .layout = { - .sizing = {.width = CLAY_SIZING_FIT(gui.windowSize.x()), .height = CLAY_SIZING_FIT(gui.windowSize.y())}, + .sizing = {.width = CLAY_SIZING_FIT(gui.io->windowSize.x()), .height = CLAY_SIZING_FIT(gui.io->windowSize.y())}, .padding = CLAY_PADDING_ALL(io->theme->padding1), .childGap = io->theme->childGap1, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, @@ -442,20 +463,18 @@ void Toolbar::update() { } if(!main.world->clientStillConnecting) { - if(io->hoverObstructed && (io->mouse.leftClick || io->mouse.rightClick)) - rightClickPopupLocation = std::nullopt; + //if(io->hoverObstructed && (io->mouse.leftClick || io->mouse.rightClick)) + // rightClickPopupLocation = std::nullopt; - if(rightClickPopupLocation.has_value()) { - if(!main.world->drawProg.right_click_popup_gui(rightClickPopupLocation.value())) - rightClickPopupLocation = std::nullopt; - } + //if(rightClickPopupLocation.has_value()) { + // if(!main.world->drawProg.right_click_popup_gui(rightClickPopupLocation.value())) + // rightClickPopupLocation = std::nullopt; + //} - if(io->hoverObstructed && io->mouse.rightClick) // This runs specifically if the paint popup has the cursor - rightClickPopupLocation = std::nullopt; + //if(io->hoverObstructed && io->mouse.rightClick) // This runs specifically if the paint popup has the cursor + // rightClickPopupLocation = std::nullopt; } - end_gui(); - justAssignedColorLeft = false; justAssignedColorRight = false; @@ -486,8 +505,7 @@ void Toolbar::close_popup_gui() { std::erase_if(closePopupData.worldsToClose, [](auto& wPair) { return wPair.w.expired(); }); - Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT CLOSE POPUP"); - CLAY(localId, { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0, 600) }, .padding = CLAY_PADDING_ALL(io->theme->padding1), @@ -499,76 +517,89 @@ void Toolbar::close_popup_gui() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.zIndex = 1, .attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.obstructing_window(localId); gui.push_id("Close single file popup gui"); - gui.text_label("Files may contain unsaved changes"); - gui.scroll_bar_area("close file popup gui scroll area", false, [&](float, float, float &) { - CLAY_AUTO_ID({ - .layout = { - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - size_t i = 0; - for(auto& [w, setToSave] : closePopupData.worldsToClose) { - auto wLock = w.lock(); - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - }, - .backgroundColor = convert_vec4(io->theme->backColor2), - .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), - }) { - gui.push_id(i); - gui.checkbox("set to save checkbox", &setToSave); + text_label(gui, "Files may contain unsaved changes"); + gui.element("close file popup gui scroll area", ScrollArea::Options{ + .scrollVertical = true, + .clipVertical = true, + .showScrollbarY = true, + .innerContent = [&](const ScrollArea::InnerContentParameters&) { + CLAY_AUTO_ID({ + .layout = { + .childGap = io->theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + size_t i = 0; + for(auto& [w, setToSave] : closePopupData.worldsToClose) { + auto wLock = w.lock(); CLAY_AUTO_ID({ .layout = { - .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) }, - .childGap = 0, + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = io->theme->childGap1, .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .backgroundColor = convert_vec4(io->theme->backColor2), + .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), }) { - gui.text_label(wLock->name); - gui.text_label_light(wLock->filePath.empty() ? "Autosave in " + main.documentsPath.string() : wLock->filePath.string()); + gui.push_id(i); + checkbox_boolean(gui, "set to save checkbox", &setToSave); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) }, + .childGap = 0, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + text_label(gui, wLock->name); + text_label_light(gui, wLock->filePath.empty() ? "Autosave in " + main.documentsPath.string() : wLock->filePath.string()); + } + gui.pop_id(); } - gui.pop_id(); + ++i; } - ++i; } } }); - if(gui.text_button_wide("Save", "Save")) { - for(auto& [w, setToSave] : closePopupData.worldsToClose) { - auto wLock = w.lock(); - if(setToSave) { - if(wLock->filePath.empty()) - wLock->autosave_to_directory(main.documentsPath); - else - wLock->save_to_file(wLock->filePath); + text_button(gui, "Save", "Save", { + .wide = true, + .onClick = [&] { + for(auto& [w, setToSave] : closePopupData.worldsToClose) { + auto wLock = w.lock(); + if(setToSave) { + if(wLock->filePath.empty()) + wLock->autosave_to_directory(main.documentsPath); + else + wLock->save_to_file(wLock->filePath); + } + main.set_tab_to_close(w); } - main.set_tab_to_close(w); + closePopupData.worldsToClose.clear(); + if(closePopupData.closeAppWhenDone) + main.setToQuit = true; } - closePopupData.worldsToClose.clear(); - if(closePopupData.closeAppWhenDone) - main.setToQuit = true; - } - if(gui.text_button_wide("Discard All", "Discard All")) { - for(auto& [w, setToSave] : closePopupData.worldsToClose) - main.set_tab_to_close(w); - closePopupData.worldsToClose.clear(); - if(closePopupData.closeAppWhenDone) - main.setToQuit = true; - } - if(gui.text_button_wide("Cancel", "Cancel")) { - closePopupData.worldsToClose.clear(); - closePopupData.closeAppWhenDone = false; - } + }); + text_button(gui, "Discard All", "Discard All", { + .wide = true, + .onClick = [&] { + for(auto& [w, setToSave] : closePopupData.worldsToClose) + main.set_tab_to_close(w); + closePopupData.worldsToClose.clear(); + if(closePopupData.closeAppWhenDone) + main.setToQuit = true; + } + }); + text_button(gui, "Cancel", "Cancel", { + .wide = true, + .onClick = [&] { + closePopupData.worldsToClose.clear(); + closePopupData.closeAppWhenDone = false; + } + }); gui.pop_id(); } } @@ -595,20 +626,21 @@ void Toolbar::save_as_func() { void Toolbar::paint_popup(Vector2f popupPos) { double newRotationAngle = 0.0; - gui.paint_circle_popup_menu("paint circle popup", popupPos, { - .currentRotationAngle = main.world->drawData.cam.c.rotation, - .newRotationAngle = &newRotationAngle, + paint_circle_popup_menu(gui, "paint circle popup", popupPos, { + .rotationAngle = &main.world->drawData.cam.c.rotation, .selectedColor = main.world->drawProg.get_foreground_color_ptr(), - .palette = paletteData.palettes[paletteData.selectedPalette].colors + .palette = paletteData.palettes[paletteData.selectedPalette].colors, + .onRotate = [&] { + main.world->drawData.cam.c.rotate_about(main.world->drawData.cam.c.from_space(main.window.size.cast() * 0.5f), newRotationAngle - main.world->drawData.cam.c.rotation); + }, + .onPaletteClick = [&] { + gui.set_to_layout(); + } }); - if(newRotationAngle != main.world->drawData.cam.c.rotation) { - main.world->drawData.cam.c.rotate_about(main.world->drawData.cam.c.from_space(main.window.size.cast() * 0.5f), newRotationAngle - main.world->drawData.cam.c.rotation); - } } void Toolbar::top_toolbar() { - Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT TOP TOOLBAR"); - CLAY(localId, { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) }, .padding = CLAY_PADDING_ALL(static_cast(io->theme->padding1 / 2)), @@ -619,54 +651,64 @@ void Toolbar::top_toolbar() { .backgroundColor = convert_vec4(io->theme->backColor1), .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) }) { - gui.obstructing_window(localId); gui.push_id("menu top toolbar"); global_log(); bool menuPopUpJustOpen = false; bool bookmarkMenuPopUpJustOpen = false; bool gridMenuPopUpJustOpen = false; bool layerMenuPopUpJustOpen = false; - if(gui.svg_icon_button_transparent("Main Menu Button", "data/icons/menu.svg", menuPopUpOpen, GUIStuff::GUIManager::BIG_BUTTON_SIZE, true, true)) { + auto icon_button_top_toolbar = [&](const char* id, const std::string& svgPath, bool isSelected, const std::function& onClick) { + svg_icon_button(gui, id, svgPath, { + .isSelected = isSelected, + .onClick = onClick + }); + }; + icon_button_top_toolbar("Main Menu Button", "data/icons/menu.svg", menuPopUpOpen, [&] { if(menuPopUpOpen) menuPopUpOpen = false; else { menuPopUpOpen = true; menuPopUpJustOpen = true; } - } - std::vector> tabNames; - for(size_t i = 0; i < main.worlds.size(); i++) { - auto& w = main.worlds[i]; - bool shouldAddStarNextToName = w->should_ask_before_closing() && !w->netServer && !w->netClient; - tabNames.emplace_back(w->netObjMan.is_connected() ? "data/icons/network.svg" : "", w->name + (shouldAddStarNextToName ? "*" : "")); - } + }); + //std::vector> tabNames; + //for(size_t i = 0; i < main.worlds.size(); i++) { + // auto& w = main.worlds[i]; + // bool shouldAddStarNextToName = w->should_ask_before_closing() && !w->netServer && !w->netClient; + // tabNames.emplace_back(w->netObjMan.is_connected() ? "data/icons/network.svg" : "", w->name + (shouldAddStarNextToName ? "*" : "")); + //} - std::optional closedTab; - gui.tab_list("file tab list", tabNames, main.worldIndex, closedTab); - if(closedTab) { - auto& closedWorldPtr = main.worlds[closedTab.value()]; - if(closedWorldPtr->should_ask_before_closing()) - add_world_to_close_popup_data(closedWorldPtr); - else - main.set_tab_to_close(closedWorldPtr); - } + //std::optional closedTab; + //gui.tab_list("file tab list", tabNames, main.worldIndex, closedTab); + //if(closedTab) { + // auto& closedWorldPtr = main.worlds[closedTab.value()]; + // if(closedWorldPtr->should_ask_before_closing()) + // add_world_to_close_popup_data(closedWorldPtr); + // else + // main.set_tab_to_close(closedWorldPtr); + //} if(!main.world->clientStillConnecting) { - if(main.world->netObjMan.is_connected() && gui.svg_icon_button_transparent("Player List Toggle Button", "data/icons/list.svg", playerMenuOpen)) - playerMenuOpen = !playerMenuOpen; - if(gui.svg_icon_button_transparent("Menu Undo Button", "data/icons/undo.svg")) + if(main.world->netObjMan.is_connected()) { + icon_button_top_toolbar("Player List Toggle Button", "data/icons/list.svg", playerMenuOpen, [&] { + playerMenuOpen = !playerMenuOpen; + }); + } + icon_button_top_toolbar("Menu Undo Button", "data/icons/undo.svg", false, [&] { main.world->undo_with_checks(); - if(gui.svg_icon_button_transparent("Menu Redo Button", "data/icons/redo.svg")) + }); + icon_button_top_toolbar("Menu Redo Button", "data/icons/redo.svg", false, [&] { main.world->redo_with_checks(); - if(gui.svg_icon_button_transparent("Grids Button", "data/icons/grid.svg", gridMenu.popupOpen, GUIStuff::GUIManager::BIG_BUTTON_SIZE, true, true)) { + }); + icon_button_top_toolbar("Grids Button", "data/icons/grid.svg", gridMenu.popupOpen, [&] { if(gridMenu.popupOpen) stop_displaying_grid_menu(); else { gridMenu.popupOpen = true; gridMenuPopUpJustOpen = true; } - } - if(gui.svg_icon_button_transparent("Layer Menu Button", "data/icons/layer.svg", layerMenuPopupOpen, GUIStuff::GUIManager::BIG_BUTTON_SIZE, true, true)) { + }); + icon_button_top_toolbar("Layer Menu Button", "data/icons/layer.svg", layerMenuPopupOpen, [&] { if(layerMenuPopupOpen) { main.world->drawProg.layerMan.listGUI.refresh_gui_data(); layerMenuPopupOpen = false; @@ -675,8 +717,8 @@ void Toolbar::top_toolbar() { layerMenuPopupOpen = true; layerMenuPopUpJustOpen = true; } - } - if(gui.svg_icon_button_transparent("Bookmark Menu Button", "data/icons/bookmark.svg", bookmarkMenuPopupOpen, GUIStuff::GUIManager::BIG_BUTTON_SIZE, true, true)) { + }); + icon_button_top_toolbar("Bookmark Menu Button", "data/icons/bookmark.svg", bookmarkMenuPopupOpen, [&] { if(bookmarkMenuPopupOpen) { main.world->bMan.refresh_gui_data(); bookmarkMenuPopupOpen = false; @@ -685,7 +727,7 @@ void Toolbar::top_toolbar() { bookmarkMenuPopupOpen = true; bookmarkMenuPopUpJustOpen = true; } - } + }); if(gridMenu.popupOpen) grid_menu(gridMenuPopUpJustOpen); @@ -695,121 +737,115 @@ void Toolbar::top_toolbar() { layer_menu(layerMenuPopUpJustOpen); } if(menuPopUpOpen) { - Clay_ElementId menuPopupId = CLAY_ID_LOCAL("Menu Popup"); - CLAY(menuPopupId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = 1, - .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), - .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_TOP, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(menuPopupId); - if(gui.text_button_left_transparent("new file local", "New File")) { - main.new_tab({ - .isClient = false - }, true); - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("open file", "Open")) { - open_world_file(false, "", ""); - menuPopUpOpen = false; - } - if(!main.world->clientStillConnecting) { - if(gui.text_button_left_transparent("save file", "Save")) { - save_func(); - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("save as file", "Save As")) { - save_as_func(); - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("screenshot", "Take Screenshot")) { - main.world->drawProg.switch_to_tool(DrawingProgramToolType::SCREENSHOT); - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("add image or file to canvas", "Add Image/File to Canvas")) { - #ifdef __EMSCRIPTEN__ - static std::weak_ptr worldWeakPtr; - worldWeakPtr = make_weak_ptr(main.world); - emscripten_browser_file::upload("*", [](std::string const& fileName, std::string const& mimeType, std::string_view buffer, void* callbackData) { - if(!buffer.empty()) { - auto world = ((std::weak_ptr*)callbackData)->lock(); - if(world) - world->drawProg.add_file_to_canvas_by_data(fileName, buffer, world->main.window.size.cast() / 2.0f); + gui.element("main menu popup", [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = 1, + .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), + .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_LEFT_TOP, .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + }) { + auto menu_popup_text_button = [&](const char* id, const char* str, const std::function& onClick) { + text_button(gui, id, str, { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .wide = true, + .onClick = [&]{ + onClick(); + menuPopUpOpen = false; + } + }); + }; + menu_popup_text_button("new file local", "New File", [&] { + main.new_tab({ + .isClient = false + }, true); + }); + menu_popup_text_button("open file", "Open", [&] { open_world_file(false, "", ""); }); + if(!main.world->clientStillConnecting) { + menu_popup_text_button("save file", "Save", [&] { save_func(); }); + menu_popup_text_button("save as file", "Save As", [&] { save_as_func(); }); + menu_popup_text_button("screenshot", "Screenshot", [&] { main.world->drawProg.switch_to_tool(DrawingProgramToolType::SCREENSHOT); }); + menu_popup_text_button("add image or file to canvas", "Add Image/File to Canvas", [&] { + #ifdef __EMSCRIPTEN__ + static std::weak_ptr worldWeakPtr; + worldWeakPtr = make_weak_ptr(main.world); + emscripten_browser_file::upload("*", [](std::string const& fileName, std::string const& mimeType, std::string_view buffer, void* callbackData) { + if(!buffer.empty()) { + auto world = ((std::weak_ptr*)callbackData)->lock(); + if(world) + world->drawProg.add_file_to_canvas_by_data(fileName, buffer, world->main.window.size.cast() / 2.0f); + else + Logger::get().log("INFO", "Loading image to canvas that has been destroyed"); + } + }, &worldWeakPtr); + #else + open_file_selector("Open File", {{"Any File", "*"}}, [w = make_weak_ptr(main.world)](const std::filesystem::path& p, const auto& e) { + auto wLock = w.lock(); + if(wLock) + wLock->drawProg.add_file_to_canvas_by_path(p, wLock->main.window.size.cast() / 2.0f, false); else Logger::get().log("INFO", "Loading image to canvas that has been destroyed"); - } - }, &worldWeakPtr); - #else - open_file_selector("Open File", {{"Any File", "*"}}, [w = make_weak_ptr(main.world)](const std::filesystem::path& p, const auto& e) { - auto wLock = w.lock(); - if(wLock) - wLock->drawProg.add_file_to_canvas_by_path(p, wLock->main.window.size.cast() / 2.0f, false); - else - Logger::get().log("INFO", "Loading image to canvas that has been destroyed"); + }); + #endif + }); + if(main.world->netObjMan.is_connected()) { + menu_popup_text_button("lobby info", "Lobby Info", [&] { + optionsMenuOpen = true; + optionsMenuType = LOBBY_INFO_MENU; }); - #endif - menuPopUpOpen = false; - } - if(main.world->netObjMan.is_connected()) { - if(gui.text_button_left_transparent("lobby info", "Lobby Info")) { - optionsMenuOpen = true; - optionsMenuType = LOBBY_INFO_MENU; - menuPopUpOpen = false; } + menu_popup_text_button("start hosting", "Host", [&] { + serverLocalID = NetLibrary::get_random_server_local_id(); + serverToConnectTo = NetLibrary::get_global_id() + serverLocalID; + optionsMenuOpen = true; + optionsMenuType = HOST_MENU; + }); + menu_popup_text_button("canvas specific settings", "Canvas Settings", [&] { + optionsMenuOpen = true; + optionsMenuType = CANVAS_SETTINGS_MENU; + }); } - else if(gui.text_button_left_transparent("start hosting", "Host")) { - serverLocalID = NetLibrary::get_random_server_local_id(); - serverToConnectTo = NetLibrary::get_global_id() + serverLocalID; + menu_popup_text_button("start connecting", "Connect", [&] { + serverToConnectTo.clear(); optionsMenuOpen = true; - optionsMenuType = HOST_MENU; - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("canvas specific settings", "Canvas Settings")) { + optionsMenuType = CONNECT_MENU; + }); + menu_popup_text_button("open options", "Settings", [&] { optionsMenuOpen = true; - optionsMenuType = CANVAS_SETTINGS_MENU; + optionsMenuType = GENERAL_SETTINGS_MENU; + }); + menu_popup_text_button("about menu button", "About", [&] { + optionsMenuOpen = true; + optionsMenuType = ABOUT_MENU; + }); + #ifndef __EMSCRIPTEN__ + menu_popup_text_button("quit button", "Quit", [&] { + if(main.app_close_requested()) + main.setToQuit = true; + }); + #endif + } + }, LayoutElement::Callbacks { + .mouseButton = [&](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + if(!mouseHovering && button.down) { menuPopUpOpen = false; + gui.set_to_layout(); } + return mouseHovering; } - if(gui.text_button_left_transparent("start connecting", "Connect")) { - serverToConnectTo.clear(); - optionsMenuOpen = true; - optionsMenuType = CONNECT_MENU; - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("open options", "Settings")) { - optionsMenuOpen = true; - optionsMenuType = GENERAL_SETTINGS_MENU; - menuPopUpOpen = false; - } - if(gui.text_button_left_transparent("about menu button", "About")) { - optionsMenuOpen = true; - optionsMenuType = ABOUT_MENU; - menuPopUpOpen = false; - } - #ifndef __EMSCRIPTEN__ - if(gui.text_button_left_transparent("quit button", "Quit")) { - if(main.app_close_requested()) - main.setToQuit = true; - menuPopUpOpen = false; - } - #endif - if(io->mouse.leftClick && !Clay_Hovered() && !menuPopUpJustOpen) - menuPopUpOpen = false; - } + }); } gui.pop_id(); } } void Toolbar::web_version_welcome() { - Clay_ElementId localId = CLAY_ID_LOCAL("Web version welcome"); - CLAY(localId, { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIT(0) }, .padding = CLAY_PADDING_ALL(io->theme->padding1), @@ -821,10 +857,9 @@ void Toolbar::web_version_welcome() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.obstructing_window(localId); gui.push_id("web version welcome gui"); - gui.text_label_centered("Welcome to the web version of InfiniPaint!"); - gui.text_label( + text_label_centered(gui, "Welcome to the web version of InfiniPaint!"); + text_label(gui, R"(This version contains more known issues than the native version of the app. This includes: - Rare crashes - If this browser tab is unfocused, or the window is minimized, any InfiniPaint tabs connected online (whether host or client) will be disconnected @@ -833,8 +868,12 @@ R"(This version contains more known issues than the native version of the app. T - Can't access local fonts If you like this app, consider downloading the native version for your system)"); - if(gui.text_button_wide("got it", "Got It")) - viewWebVersionWelcome = false; + text_button(gui, "got it", "Got It", { + .wide = true, + .onClick = [&] { + viewWebVersionWelcome = false; + } + }); gui.pop_id(); } } @@ -852,7 +891,7 @@ void Toolbar::still_connecting_center_message() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.text_label("Connecting to server..."); + text_label(gui, "Connecting to server..."); } } @@ -869,7 +908,7 @@ void Toolbar::no_layers_being_edited_message() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.text_label("Select a layer to edit"); + text_label(gui, "Select a layer to edit"); } } @@ -918,8 +957,7 @@ void Toolbar::update_notification_check() { } void Toolbar::update_notification_gui() { - Clay_ElementId localId = CLAY_ID_LOCAL("UPDATE NOTIFICATION GUI"); - CLAY(localId, { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIT(0) }, .padding = CLAY_PADDING_ALL(io->theme->padding1), @@ -931,19 +969,28 @@ void Toolbar::update_notification_gui() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.obstructing_window(localId); gui.push_id("update notification gui"); - gui.text_label_centered("Update v" + updateCheckerData.newVersionStr + " available!"); - if(gui.text_button_wide("download", "Open download page in web browser")) { - SDL_OpenURL(UPDATE_DOWNLOAD_URL); - updateCheckerData.showGui = false; - } - if(gui.text_button_wide("ignore forever", "Ignore and don't notify again (can be changed in settings)")) { - updateCheckerData.checkForUpdates = false; - updateCheckerData.showGui = false; - } - if(gui.text_button_wide("ignore for now", "Ignore for now")) - updateCheckerData.showGui = false; + text_label_centered(gui, "Update v" + updateCheckerData.newVersionStr + " available!"); + text_button(gui, "download", "Open download page in web browser", { + .wide = true, + .onClick = [&]{ + SDL_OpenURL(UPDATE_DOWNLOAD_URL); + updateCheckerData.showGui = false; + } + }); + text_button(gui, "ignore forever", "Ignore and don't notify again (can be changed in settings)", { + .wide = true, + .onClick = [&]{ + updateCheckerData.checkForUpdates = false; + updateCheckerData.showGui = false; + } + }); + text_button(gui, "ignore for now", "Ignore for now", { + .wide = true, + .onClick = [&]{ + updateCheckerData.showGui = false; + } + }); gui.pop_id(); } } @@ -953,88 +1000,88 @@ void Toolbar::grid_menu(bool justOpened) { if(main.world->gridMan.grids) { gui.push_id("grid menu"); Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT GRID MENU"); - CLAY(localId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, - .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), - .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(localId); - gui.text_label_centered("Grids"); - float entryHeight = 25.0f; - if(main.world->gridMan.grids->empty()) - gui.text_label_centered("No grids yet..."); - uint32_t toDelete = std::numeric_limits::max(); - gui.scroll_bar_many_entries_area("grid menu entries", entryHeight, main.world->gridMan.grids->size(), true, [&](size_t i, bool isListHovered) { - auto& grid = main.world->gridMan.grids->at(i)->obj; - bool selectedEntry = gridMenu.selectedGrid == i; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)}, - .childGap = 2, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - }, - .backgroundColor = selectedEntry ? convert_vec4(io->theme->backColor1) : convert_vec4(io->theme->backColor2) - }) { - gui.text_label(grid->get_display_name()); - bool miniButtonClicked = false; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childGap = 1, - .childAlignment = {.x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - if(gui.svg_icon_button_transparent("visibility eye", grid->visible ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", false, entryHeight, false)) { - miniButtonClicked = true; - grid->visible = !grid->visible; - NetworkingObjects::generic_serialized_class_send_update_to_all(grid); - } - if(gui.svg_icon_button_transparent("edit pencil", "data/icons/pencil.svg", false, entryHeight, false)) { - miniButtonClicked = true; - main.world->drawProg.modify_grid(grid); - stop_displaying_grid_menu(); - } - if(gui.svg_icon_button_transparent("delete trash", "data/icons/trash.svg", false, entryHeight, false)) { - miniButtonClicked = true; - toDelete = i; - } - } - if(Clay_Hovered() && io->mouse.leftClick && isListHovered && !miniButtonClicked) { - gridMenu.selectedGrid = i; - if(io->mouse.leftClick >= 2) { - main.world->drawProg.modify_grid(grid); - stop_displaying_grid_menu(); - } - } - } - }); - if(toDelete != std::numeric_limits::max()) - main.world->gridMan.remove_grid(toDelete); - gui.left_to_right_line_layout([&]() { - bool addByEnter = false; - gui.input_text("grid text input", &gridMenu.newName, true, [&](GUIStuff::SelectionHelper& s) { - addByEnter = s.selected && io->key.enter; - }); - if(gui.svg_icon_button("grid add button", "data/icons/plusbold.svg", false, GUIStuff::GUIManager::SMALL_BUTTON_SIZE) || (addByEnter && !gridMenu.newName.empty())) { - main.world->gridMan.add_default_grid(gridMenu.newName); - main.world->drawProg.modify_grid(main.world->gridMan.grids->at(main.world->gridMan.grids->size() - 1)->obj); - stop_displaying_grid_menu(); - } - }); + //CLAY(localId, { + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, + // .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), + // .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + //}) { + // gui.obstructing_window(localId); + // text_label_centered(gui, "Grids"); + // float entryHeight = 25.0f; + // if(main.world->gridMan.grids->empty()) + // text_label_centered(gui, "No grids yet..."); + // uint32_t toDelete = std::numeric_limits::max(); + // gui.scroll_bar_many_entries_area("grid menu entries", entryHeight, main.world->gridMan.grids->size(), true, [&](size_t i, bool isListHovered) { + // auto& grid = main.world->gridMan.grids->at(i)->obj; + // bool selectedEntry = gridMenu.selectedGrid == i; + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)}, + // .childGap = 2, + // .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // }, + // .backgroundColor = selectedEntry ? convert_vec4(io->theme->backColor1) : convert_vec4(io->theme->backColor2) + // }) { + // text_label(gui, grid->get_display_name()); + // bool miniButtonClicked = false; + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + // .childGap = 1, + // .childAlignment = {.x = CLAY_ALIGN_X_RIGHT, .y = CLAY_ALIGN_Y_CENTER}, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + // }) { + // if(gui.svg_icon_button_transparent("visibility eye", grid->visible ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", false, entryHeight, false)) { + // miniButtonClicked = true; + // grid->visible = !grid->visible; + // NetworkingObjects::generic_serialized_class_send_update_to_all(grid); + // } + // if(gui.svg_icon_button_transparent("edit pencil", "data/icons/pencil.svg", false, entryHeight, false)) { + // miniButtonClicked = true; + // main.world->drawProg.modify_grid(grid); + // stop_displaying_grid_menu(); + // } + // if(gui.svg_icon_button_transparent("delete trash", "data/icons/trash.svg", false, entryHeight, false)) { + // miniButtonClicked = true; + // toDelete = i; + // } + // } + // if(Clay_Hovered() && io->mouse.leftClick && isListHovered && !miniButtonClicked) { + // gridMenu.selectedGrid = i; + // if(io->mouse.leftClick >= 2) { + // main.world->drawProg.modify_grid(grid); + // stop_displaying_grid_menu(); + // } + // } + // } + // }); + // if(toDelete != std::numeric_limits::max()) + // main.world->gridMan.remove_grid(toDelete); + // gui.left_to_right_line_layout([&]() { + // bool addByEnter = false; + // gui.input_text("grid text input", &gridMenu.newName, true, [&](GUIStuff::SelectionHelper& s) { + // addByEnter = s.selected && io->key.enter; + // }); + // if(gui.svg_icon_button("grid add button", "data/icons/plusbold.svg", false, GUIStuff::GUIManager::SMALL_BUTTON_SIZE) || (addByEnter && !gridMenu.newName.empty())) { + // main.world->gridMan.add_default_grid(gridMenu.newName); + // main.world->drawProg.modify_grid(main.world->gridMan.grids->at(main.world->gridMan.grids->size() - 1)->obj); + // stop_displaying_grid_menu(); + // } + // }); - bool dropdownHover = false; - if(io->mouse.leftClick && !Clay_Hovered() && !justOpened && !dropdownHover) - stop_displaying_grid_menu(); - } + // bool dropdownHover = false; + // if(io->mouse.leftClick && !Clay_Hovered() && !justOpened && !dropdownHover) + // stop_displaying_grid_menu(); + //} gui.pop_id(); } } @@ -1047,54 +1094,54 @@ void Toolbar::stop_displaying_grid_menu() { void Toolbar::bookmark_menu(bool justOpened) { gui.push_id("bookmark menu"); - Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT BOOKMARK MENU"); - CLAY(localId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, - .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), - .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(localId); - gui.text_label_centered("Bookmarks"); - main.world->bMan.setup_list_gui("bookmark menu list"); - if(io->mouse.leftClick && !Clay_Hovered() && !justOpened) { - bookmarkMenuPopupOpen = false; - main.world->bMan.refresh_gui_data(); - } - } + //Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT BOOKMARK MENU"); + //CLAY(localId, { + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, + // .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), + // .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + //}) { + // gui.obstructing_window(localId); + // text_label_centered(gui, "Bookmarks"); + // main.world->bMan.setup_list_gui("bookmark menu list"); + // if(io->mouse.leftClick && !Clay_Hovered() && !justOpened) { + // bookmarkMenuPopupOpen = false; + // main.world->bMan.refresh_gui_data(); + // } + //} gui.pop_id(); } void Toolbar::layer_menu(bool justOpened) { gui.push_id("layer menu"); - Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT LAYER MENU"); - CLAY(localId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, - .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), - .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(localId); - gui.text_label_centered("Layers"); - bool hoveringOverDropdown = false; - main.world->drawProg.layerMan.listGUI.setup_list_gui("layer menu list", hoveringOverDropdown); - if(io->mouse.leftClick && !Clay_Hovered() && !justOpened && !hoveringOverDropdown) { - layerMenuPopupOpen = false; - main.world->drawProg.layerMan.listGUI.refresh_gui_data(); - } - } + //Clay_ElementId localId = CLAY_ID_LOCAL("INFINIPAINT LAYER MENU"); + //CLAY(localId, { + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0, 600) }, + // .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), + // .floating = {.offset = {.x = 0, .y = static_cast(io->theme->padding1)}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} + //}) { + // gui.obstructing_window(localId); + // text_label_centered(gui, "Layers"); + // bool hoveringOverDropdown = false; + // main.world->drawProg.layerMan.listGUI.setup_list_gui("layer menu list", hoveringOverDropdown); + // if(io->mouse.leftClick && !Clay_Hovered() && !justOpened && !hoveringOverDropdown) { + // layerMenuPopupOpen = false; + // main.world->drawProg.layerMan.listGUI.refresh_gui_data(); + // } + //} gui.pop_id(); } @@ -1126,118 +1173,118 @@ std::unique_ptr Toolbar::build_paragraph_from_chat_ } 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(); + //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(); - 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(); - } + // 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(); + //} } void Toolbar::global_log() { @@ -1250,41 +1297,41 @@ void Toolbar::global_log() { }, .floating = {.offset = {0, 10}, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_BOTTOM}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - constexpr float DISPLAY_TIME = 8.0f; - constexpr float FADE_START_TIME = 7.0f; - int i = 0; - for(auto& logM : main.logMessages) { - logM.time.update_time_since(); - if(logM.time < DISPLAY_TIME) { - float a = 1.0f - lerp_time(logM.time, DISPLAY_TIME, FADE_START_TIME); - Clay_ElementId elemId = CLAY_IDI_LOCAL("GLOBAL LOG", i++); - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .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)), - .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) - }) { - gui.obstructing_window(elemId); - SkColor4f c{0, 0, 0, 0}; - switch(logM.color) { - case LogMessage::COLOR_NORMAL: - c = io->theme->frontColor1; - break; - case LogMessage::COLOR_ERROR: - c = io->theme->errorColor; - break; - } - gui.text_label_color(logM.text, color_mul_alpha(c, a)); - } - } - else - break; - } + // constexpr float DISPLAY_TIME = 8.0f; + // constexpr float FADE_START_TIME = 7.0f; + // int i = 0; + // for(auto& logM : main.logMessages) { + // logM.time.update_time_since(); + // if(logM.time < DISPLAY_TIME) { + // float a = 1.0f - lerp_time(logM.time, DISPLAY_TIME, FADE_START_TIME); + // Clay_ElementId elemId = CLAY_IDI_LOCAL("GLOBAL LOG", i++); + // CLAY(elemId, { + // .layout = { + // .sizing = {.width = CLAY_SIZING_FIT(300), .height = CLAY_SIZING_FIT(0) }, + // .padding = CLAY_PADDING_ALL(io->theme->padding1), + // .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)), + // .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1) + // }) { + // gui.obstructing_window(elemId); + // SkColor4f c{0, 0, 0, 0}; + // switch(logM.color) { + // case LogMessage::COLOR_NORMAL: + // c = io->theme->frontColor1; + // break; + // case LogMessage::COLOR_ERROR: + // c = io->theme->errorColor; + // break; + // } + // gui.text_label_color(logM.text, color_mul_alpha(c, a)); + // } + // } + // else + // break; + // } } } @@ -1299,65 +1346,65 @@ void Toolbar::drawing_program_gui() { }) { main.world->drawProg.toolbar_gui(); isUpdatingColorLeft = isUpdatingColorRight = false; - if(colorLeft) { - CLAY_AUTO_ID({ - .layout = { - .padding = {.top = 40, .bottom = 40} - } - }) { - auto localId = CLAY_ID_LOCAL("Drawing program gui color picker left"); - CLAY(localId, { - .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(localId); - 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; - } - } - } + //if(colorLeft) { + // CLAY_AUTO_ID({ + // .layout = { + // .padding = {.top = 40, .bottom = 40} + // } + // }) { + // auto localId = CLAY_ID_LOCAL("Drawing program gui color picker left"); + // CLAY(localId, { + // .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(localId); + // 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_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} } }) {} - if(colorRight) { - CLAY_AUTO_ID({ - .layout = { - .padding = {.top = 40, .bottom = 40} - } - }) { - auto localID = CLAY_ID_LOCAL("Drawing program gui color picker right"); - CLAY(localID, { - .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(localID); - 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; - } - } - } + //if(colorRight) { + // CLAY_AUTO_ID({ + // .layout = { + // .padding = {.top = 40, .bottom = 40} + // } + // }) { + // auto localID = CLAY_ID_LOCAL("Drawing program gui color picker right"); + // CLAY(localID, { + // .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(localID); + // 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(); } } @@ -1365,112 +1412,112 @@ void Toolbar::drawing_program_gui() { bool Toolbar::color_palette(const char* id, Vector4f* color, bool& hoveringOnDropdown) { bool isUpdating = false; gui.push_id(id); - auto& palette = paletteData.palettes[paletteData.selectedPalette].colors; - constexpr float COLOR_BUTTON_SIZE = GUIStuff::GUIManager::BIG_BUTTON_SIZE; + //auto& palette = paletteData.palettes[paletteData.selectedPalette].colors; + //constexpr float COLOR_BUTTON_SIZE = GUIStuff::GUIManager::BIG_BUTTON_SIZE; - size_t nextID = 0; + //size_t nextID = 0; - gui.scroll_bar_area("color palette scroll area", false, [&](float, float, float &) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, - .childGap = io->theme->childGap1, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - size_t i = 0; - while(i < palette.size()) { - CLAY_AUTO_ID({ - .layout = { - .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 - } - }) { - while(i < palette.size()) { - CLAY_AUTO_ID({ - .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}; - gui.push_id(nextID++); - if(gui.color_button("c", &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; - } - gui.pop_id(); - 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; - } - } - } - } - }); + //gui.scroll_bar_area("color palette scroll area", false, [&](float, float, float &) { + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + // .childGap = io->theme->childGap1, + // .layoutDirection = CLAY_TOP_TO_BOTTOM + // } + // }) { + // size_t i = 0; + // while(i < palette.size()) { + // CLAY_AUTO_ID({ + // .layout = { + // .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 + // } + // }) { + // while(i < palette.size()) { + // CLAY_AUTO_ID({ + // .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}; + // gui.push_id(nextID++); + // if(gui.color_button("c", &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; + // } + // gui.pop_id(); + // 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_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, - .padding = {.top = 3, .bottom = 3}, - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - if(gui.svg_icon_button("addcolor", "data/icons/plus.svg", false, COLOR_BUTTON_SIZE)) { - palette.emplace_back(color->x(), color->y(), color->z()); - paletteData.selectedColor = palette.size() - 1; - } - if(gui.svg_icon_button("deletecolor", "data/icons/close.svg", false, COLOR_BUTTON_SIZE)) { - if(paletteData.selectedColor >= 0 && paletteData.selectedColor < (int)palette.size()) { - palette.erase(palette.begin() + paletteData.selectedColor); - paletteData.selectedColor = -1; - } - } - } - } - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, - .padding = {.top = 3, .bottom = 3}, - .childGap = io->theme->childGap1, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - } - }) { - std::vector paletteNames; - for(auto& p : paletteData.palettes) - paletteNames.emplace_back(p.name); - 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; - if(paletteData.selectedPalette != 0) { - if(gui.svg_icon_button("paletteremove", "data/icons/close.svg", false, 25.0f)) { - paletteData.palettes.erase(paletteData.palettes.begin() + paletteData.selectedPalette); - paletteData.selectedPalette = 0; - } - } - } - if(paletteData.addingPalette) { - gui.input_text_field("paletteinputname", "Name", &paletteData.newPaletteStr); - if(gui.text_button_wide("addpalettebutton", "Create") && !paletteData.newPaletteStr.empty()) { - paletteData.palettes.emplace_back(); - paletteData.palettes.back().name = paletteData.newPaletteStr; - paletteData.selectedPalette = paletteData.palettes.size() - 1; - paletteData.addingPalette = false; - } - } + //if(paletteData.selectedPalette != 0) { + // CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + // .padding = {.top = 3, .bottom = 3}, + // .childGap = io->theme->childGap1, + // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + // }) { + // if(gui.svg_icon_button("addcolor", "data/icons/plus.svg", false, COLOR_BUTTON_SIZE)) { + // palette.emplace_back(color->x(), color->y(), color->z()); + // paletteData.selectedColor = palette.size() - 1; + // } + // if(gui.svg_icon_button("deletecolor", "data/icons/close.svg", false, COLOR_BUTTON_SIZE)) { + // if(paletteData.selectedColor >= 0 && paletteData.selectedColor < (int)palette.size()) { + // palette.erase(palette.begin() + paletteData.selectedColor); + // paletteData.selectedColor = -1; + // } + // } + // } + //} + //CLAY_AUTO_ID({ + // .layout = { + // .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + // .padding = {.top = 3, .bottom = 3}, + // .childGap = io->theme->childGap1, + // .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + // .layoutDirection = CLAY_LEFT_TO_RIGHT + // } + //}) { + // std::vector paletteNames; + // for(auto& p : paletteData.palettes) + // paletteNames.emplace_back(p.name); + // 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; + // if(paletteData.selectedPalette != 0) { + // if(gui.svg_icon_button("paletteremove", "data/icons/close.svg", false, 25.0f)) { + // paletteData.palettes.erase(paletteData.palettes.begin() + paletteData.selectedPalette); + // paletteData.selectedPalette = 0; + // } + // } + //} + //if(paletteData.addingPalette) { + // gui.input_text_field("paletteinputname", "Name", &paletteData.newPaletteStr); + // if(gui.text_button_wide("addpalettebutton", "Create") && !paletteData.newPaletteStr.empty()) { + // paletteData.palettes.emplace_back(); + // paletteData.palettes.back().name = paletteData.newPaletteStr; + // paletteData.selectedPalette = paletteData.palettes.size() - 1; + // paletteData.addingPalette = false; + // } + //} gui.pop_id(); return isUpdating; @@ -1497,10 +1544,10 @@ void Toolbar::performance_metrics() { }, .backgroundColor = convert_vec4(color_mul_alpha(io->theme->backColor1, 0.7f)), }) { - gui.text_label("Undo queue"); + text_label(gui, "Undo queue"); std::vector undoList = main.world->undo.get_front_undo_queue_names(10); for(const std::string& u : undoList) - gui.text_label(u); + text_label(gui, u); } CLAY_AUTO_ID({ .layout = { @@ -1514,24 +1561,23 @@ void Toolbar::performance_metrics() { }) { std::stringstream a; a << "FPS: " << std::fixed << std::setprecision(0) << (1.0 / main.deltaTime); - gui.text_label(a.str()); - gui.text_label("Item Count: " + std::to_string(main.world->drawProg.layerMan.total_component_count())); + text_label(gui, a.str()); + text_label(gui, "Item Count: " + std::to_string(main.world->drawProg.layerMan.total_component_count())); std::stringstream b; b << "Coord: " << main.world->drawData.cam.c.pos.x().display_int_str(5) << ", " << main.world->drawData.cam.c.pos.y().display_int_str(5); - gui.text_label(b.str()); + text_label(gui, b.str()); std::stringstream c; c << "Zoom: " << main.world->drawData.cam.c.inverseScale.display_int_str(5); - gui.text_label(c.str()); + text_label(gui, c.str()); std::stringstream d; d << "Rotation: " << main.world->drawData.cam.c.rotation; - gui.text_label(d.str()); + text_label(gui, d.str()); } } } void Toolbar::player_list() { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT PLAYER LIST"); - CLAY(elemId, { + CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIT(500), .height = CLAY_SIZING_FIT(0) }, .padding = CLAY_PADDING_ALL(io->theme->padding1), @@ -1543,11 +1589,10 @@ void Toolbar::player_list() { .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} }) { - gui.obstructing_window(elemId); gui.push_id("client list"); - gui.text_label_centered("Player List"); + text_label_centered(gui, "Player List"); if(!main.world->clientStillConnecting) { - gui.left_to_right_line_layout([&]() { + left_to_right_line_layout(gui, [&]() { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} @@ -1555,13 +1600,13 @@ void Toolbar::player_list() { .backgroundColor = convert_vec4(SkColor4f{main.world->ownClientData->get_cursor_color().x(), main.world->ownClientData->get_cursor_color().y(), main.world->ownClientData->get_cursor_color().z(), 1.0f}), .cornerRadius = CLAY_CORNER_RADIUS(3) }) {} - gui.text_label(main.world->ownClientData->get_display_name()); + text_label(gui, main.world->ownClientData->get_display_name()); }); size_t num = 0; for(auto& client : main.world->clients->get_data()) { if(client != main.world->ownClientData) { gui.push_id(num++); - gui.left_to_right_line_layout([&]() { + left_to_right_line_layout(gui, [&]() { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} @@ -1569,17 +1614,19 @@ void Toolbar::player_list() { .backgroundColor = convert_vec4(SkColor4f{client->get_cursor_color().x(), client->get_cursor_color().y(), client->get_cursor_color().z(), 1.0f}), .cornerRadius = CLAY_CORNER_RADIUS(3) }) {} - gui.text_label(client->get_display_name()); + text_label(gui, client->get_display_name()); CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {} - if(gui.text_button("teleport button", "Jump To")) + text_button(gui, "teleport button", "Jump To", { .onClick = [&] { main.world->drawData.cam.smooth_move_to(*main.world, client->get_cam_coords(), client->get_window_size()); + }}); }); gui.pop_id(); } } } - if(gui.text_button_wide("close list", "Done")) + text_button(gui, "close list", "Done", { .wide = true, .onClick = [&] { playerMenuOpen = false; + }}); gui.pop_id(); } } @@ -1620,462 +1667,433 @@ void Toolbar::open_world_file(bool isClient, const std::string& netSource, const #endif } +void Toolbar::center_obstructing_window_gui(const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent) { + gui.element(id, [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = x, .height = y }, + .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), + .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} + }) { + innerContent(); + } + }); +} + +void Toolbar::text_button_wide(const char* id, const char* str, const std::function& onClick) { + text_button(gui, id, str, { + .wide = true, + .onClick = onClick + }); +} + void Toolbar::options_menu() { switch(optionsMenuType) { - case HOST_MENU: + case HOST_MENU: { + center_obstructing_window_gui("host menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] { + input_text_field(gui, "lobby", "Lobby", &serverToConnectTo); + left_to_right_line_layout(gui, [&]() { + text_button_wide("copy lobby address", "Copy Lobby Address", [&] { + main.input.set_clipboard_str(serverToConnectTo); + }); + text_button_wide("host file", "Host", [&] { + main.world->start_hosting(serverToConnectTo, serverLocalID); + optionsMenuOpen = false; + }); + text_button_wide("cancel", "Cancel", [&] { + optionsMenuOpen = false; + }); + }); + }); + break; + } case CONNECT_MENU: { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT CONNECT MENU"); - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(650), .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(elemId); - if(optionsMenuType == HOST_MENU) { - gui.push_id("host menu"); - std::string oldS = serverToConnectTo; - gui.input_text_field("lobby", "Lobby", &serverToConnectTo); - serverToConnectTo = oldS; - gui.left_to_right_line_layout([&]() { - if(gui.text_button_wide("copy lobby address", "Copy Lobby Address")) - main.input.set_clipboard_str(serverToConnectTo); - if(gui.text_button_wide("host file", "Host")) { - main.world->start_hosting(serverToConnectTo, serverLocalID); + center_obstructing_window_gui("connect menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] { + input_text_field(gui, "lobby", "Lobby", &serverToConnectTo); + left_to_right_line_layout(gui, [&]() { + text_button_wide("connect", "Connect", [&] { + if(serverToConnectTo.length() != (NetLibrary::LOCALID_LEN + NetLibrary::GLOBALID_LEN)) + Logger::get().log("USERINFO", "Connect issue: Incorrect address length"); + else if(serverToConnectTo.substr(0, NetLibrary::GLOBALID_LEN) == NetLibrary::get_global_id()) + Logger::get().log("USERINFO", "Connect issue: Can't connect to your own address"); + else { + main.new_tab({ + .isClient = true, + .netSource = serverToConnectTo + }, true); optionsMenuOpen = false; } - if(gui.text_button_wide("cancel", "Cancel")) - optionsMenuOpen = false; }); - gui.pop_id(); - } - else if(optionsMenuType == CONNECT_MENU) { - gui.push_id("connect menu"); - gui.input_text_field("lobby", "Lobby", &serverToConnectTo); - gui.left_to_right_line_layout([&]() { - if(gui.text_button_wide("connect", "Connect")) { - if(serverToConnectTo.length() != (NetLibrary::LOCALID_LEN + NetLibrary::GLOBALID_LEN)) - Logger::get().log("USERINFO", "Connect issue: Incorrect address length"); - else if(serverToConnectTo.substr(0, NetLibrary::GLOBALID_LEN) == NetLibrary::get_global_id()) - Logger::get().log("USERINFO", "Connect issue: Can't connect to your own address"); - else { - main.new_tab({ - .isClient = true, - .netSource = serverToConnectTo - }, true); - optionsMenuOpen = false; - } - } - if(gui.text_button_wide("cancel", "Cancel")) - optionsMenuOpen = false; + text_button_wide("cancel", "Cancel", [&] { + optionsMenuOpen = false; }); - gui.pop_id(); - } - } + }); + }); break; } case GENERAL_SETTINGS_MENU: { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(600), .height = CLAY_SIZING_FIXED(500) }, - .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.push_id("gsettings"); - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT GENERAL SETTINGS MENU"); - CLAY(elemId, { - .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_LEFT_TO_RIGHT - } - }) { - gui.obstructing_window(elemId); - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(150), .height = CLAY_SIZING_GROW(0) }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childGap = 2, - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - auto oldOption = generalSettingsOptions; - if(gui.text_button_left_transparent("Generalbutton", "General", generalSettingsOptions == GSETTINGS_GENERAL)) generalSettingsOptions = GSETTINGS_GENERAL; - if(gui.text_button_left_transparent("Tabletbutton", "Tablet", generalSettingsOptions == GSETTINGS_TABLET)) generalSettingsOptions = GSETTINGS_TABLET; - if(gui.text_button_left_transparent("Themebutton", "Theme", generalSettingsOptions == GSETTINGS_THEME)) generalSettingsOptions = GSETTINGS_THEME; - if(gui.text_button_left_transparent("Keybindsbutton", "Keybinds", generalSettingsOptions == GSETTINGS_KEYBINDS)) generalSettingsOptions = GSETTINGS_KEYBINDS; - if(gui.text_button_left_transparent("Debugbutton", "Debug", generalSettingsOptions == GSETTINGS_DEBUG)) generalSettingsOptions = GSETTINGS_DEBUG; - if(oldOption != generalSettingsOptions) { - load_theme(); - themeData.selectedThemeIndex = std::nullopt; - } - } - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - } - }) { - gui.scroll_bar_area("general settings scroll area", false, [&](float, float, float) { - 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.push_id("general settings"); - 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); - #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); - gui.checkbox_field("flip zoom tool direction", "Flip zoom tool direction", &flipZoomToolDirection); - gui.checkbox_field("make all tools share same size", "Make all tools share size", &main.toolConfig.globalConf.useGlobalRelativeWidth); - #ifndef __EMSCRIPTEN__ - gui.checkbox_field("disable graphics driver workarounds", "Disable graphics driver workarounds (enabling or disabling this might fix some graphical glitches, requires restart)", &main.window.disableGraphicsDriverWorkarounds); - #endif - gui.input_scalar_field("jump transition time", "Jump transition time", &jumpTransitionTime, 0.01f, 1000.0f, 2); - gui.input_scalar_field("Max GUI Scale", "Max GUI Scale", &guiScale, 0.5f, 5.0f, 1); - gui.text_label("Anti-aliasing:"); - MainProgram::AntiAliasing aa = main.antialiasing; - if(gui.radio_button_field("AntiAliasing None", "None", aa == MainProgram::AntiAliasing::NONE)) { main.antialiasing = MainProgram::AntiAliasing::NONE; } - if(gui.radio_button_field("AntiAliasing Skia", "Skia", aa == MainProgram::AntiAliasing::SKIA)) { main.antialiasing = MainProgram::AntiAliasing::SKIA; } - if(gui.radio_button_field("AntiAliasing Dynamic MSAA", "Dynamic MSAA", aa == MainProgram::AntiAliasing::DYNAMIC_MSAA)) { main.antialiasing = MainProgram::AntiAliasing::DYNAMIC_MSAA; } - if(main.antialiasing != aa) - main.refresh_draw_surfaces(); - gui.text_label("VSync:"); - if(gui.radio_button_field("Vsync On", "On", main.window.vsyncValue == 1)) - main.set_vsync_value(1); - if(gui.radio_button_field("Vsync Off", "Off", main.window.vsyncValue == 0)) - main.set_vsync_value(0); - if(gui.radio_button_field("Vsync Adaptive", "Adaptive", main.window.vsyncValue == -1)) - main.set_vsync_value(-1); - - #ifndef __EMSCRIPTEN__ - gui.checkbox_field("apply display scale", "Apply display scale", &main.window.applyDisplayScale); - #endif - - gui.pop_id(); - break; - } - case GSETTINGS_TABLET: { - gui.push_id("tablet settings"); - gui.checkbox_field("pen pressure width", "Pen pressure affects brush size", &tabletOptions.pressureAffectsBrushWidth); - gui.slider_scalar_field("smoothing time", "Smoothing sampling time", &tabletOptions.smoothingSamplingTime, 0.001f, 1.0f, 3); - gui.input_scalar_field("middle click", "Middle click pen button", &tabletOptions.middleClickButton, 1, 255); - gui.input_scalar_field("right click", "Right click pen button", &tabletOptions.rightClickButton, 1, 255); - gui.slider_scalar_field("tablet brush minimum size", "Brush relative minimum size", &tabletOptions.brushMinimumSize, 0.0f, 1.0f, 3); - gui.checkbox_field("tablet zoom with button method", "Zoom when pen touching tablet and pen button assigned to middle click is held", &tabletOptions.zoomWhilePenDownAndButtonHeld); - #ifdef _WIN32 - gui.checkbox_field("mouse ignore when pen proximity", "Ignore mouse movement when pen in proximity", &tabletOptions.ignoreMouseMovementWhenPenInProximity); - #endif - gui.pop_id(); - break; - } - case GSETTINGS_THEME: { - gui.push_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, - } - }) { - gui.text_label("Theme: "); - size_t changedVal = themeData.selectedThemeIndex.value(); - gui.dropdown_select("dropdownSelectThemes", &themeData.selectedThemeIndex.value(), themeData.themeDirList); - if(changedVal != themeData.selectedThemeIndex.value()) { - themeData.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - reload_theme_list(); - } - } - gui.left_to_right_line_layout([&]() { - if(themeData.selectedThemeIndex != 0 && gui.text_button_wide("savethemebutton", "Save")) { - themeData.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - save_theme(); - reload_theme_list(); - } - if(gui.text_button_wide("saveasthemebutton", "Save As", themeData.openedSaveAsMenu)) themeData.openedSaveAsMenu = !themeData.openedSaveAsMenu; - if(gui.text_button_wide("reloadthemebutton", "Reload")) { - themeData.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; - reload_theme_list(); - } - if(themeData.selectedThemeIndex != 0 && gui.text_button_wide("deletethemebutton", "Delete")) { - try { std::filesystem::remove(main.configPath / "themes" / (themeData.themeDirList[themeData.selectedThemeIndex.value()] + ".json")); } catch(...) { } - themeData.themeCurrentlyLoaded = "Default"; - reload_theme_list(); - } - }); - if(themeData.openedSaveAsMenu) { - gui.left_to_right_line_layout([&]() { - gui.text_label("Theme name: "); - gui.input_text("saveasnewthemename", &themeData.themeCurrentlyLoaded); - }); - gui.left_to_right_line_layout([&]() { - if(gui.text_button_wide("saveasdone", "Done")) { - save_theme(); - reload_theme_list(); - } - if(gui.text_button_wide("saveascancel", "Cancel")) - themeData.openedSaveAsMenu = false; - }); - } - gui.text_label("Edit theme:"); - gui.text_label_light("Note: Changes only remain if theme is saved"); - gui.color_picker_button_field("fillColor1", "Fill Color 1", &io->theme->fillColor1, true); - gui.color_picker_button_field("fillColor2", "Fill Color 2", &io->theme->fillColor2, true); - gui.color_picker_button_field("backColor1", "Back Color 1", &io->theme->backColor1, true); - gui.color_picker_button_field("backColor2", "Back Color 2", &io->theme->backColor2, true); - gui.color_picker_button_field("frontColor1", "Front Color 1", &io->theme->frontColor1, true); - gui.color_picker_button_field("frontColor2", "Front Color 2", &io->theme->frontColor2, true); - gui.color_picker_button_field("warningColor", "Warning Color", &io->theme->warningColor, true); - gui.color_picker_button_field("errorColor", "Error Color", &io->theme->errorColor, true); - //gui.slider_scalar_field("hoverExpandTime", "Hover Expand Time", &io->theme->hoverExpandTime, 0.001f, 1.0f); - gui.input_scalar_field("childGap1", "Gap between child elements", &io->theme->childGap1, 0, 30); - gui.input_scalar_field("padding1", "Window padding", &io->theme->padding1, 0, 30); - gui.slider_scalar_field("windowCorners1", "Window corner radius", &io->theme->windowCorners1, 0, 30); - gui.pop_id(); - 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.push_id("keybind entries"); - for(unsigned i = 0; i < InputManager::KEY_ASSIGNABLE_COUNT; i++) { - gui.push_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 - } - }) { - gui.text_label(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) : ""; - if(gui.text_button_wide("keybind button", assignedKeystrokeStr, keybindWaiting.has_value() && keybindWaiting.value() == i)) - keybindWaiting = i; - } - gui.pop_id(); - } - gui.pop_id(); - break; - } - 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); - #ifndef __EMSCRIPTEN__ - gui.input_scalar_field("fps cap slider", "FPS cap", &main.fpsLimit, 3.0f, 10000.0f); - #endif - gui.input_scalar_field("image load max threads", "Maximum image loading threads", &ImageResourceDisplay::IMAGE_LOAD_THREAD_COUNT_MAX, 1, 10000); - gui.text_label_light("Cache related settings"); - gui.input_scalar_field("cache node resolution", "Cache node resolution", &DrawingProgramCache::CACHE_NODE_RESOLUTION, 256, 8192); - gui.input_scalar_field("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 - gui.text_label_light("Cache max VRAM consumption (MB): " + std::to_string(cacheVRAMConsumptionInMB)); - gui.input_scalar_field("max components in node", "Maximum components in single node", &DrawingProgramCache::MAXIMUM_COMPONENTS_IN_SINGLE_NODE, 2, 10000); - gui.input_scalar_field("components to force cache rebuild", "Number of components to force cache rebuild", &DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD, 1, 1000000); - gui.input_scalar_field("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); - gui.input_scalar_field("minimum time to force cache rebuild", "Minimum time to check cache rebuild (ms)", &DrawingProgramCache::MILLISECOND_MINIMUM_TIME_TO_CHECK_FORCE_REFRESH, 1, 1000000); - gui.pop_id(); - break; - } - } - } - }); - if(gui.text_button_wide("done menu", "Done")) { - main.save_config(); - load_theme(); - themeData.selectedThemeIndex = std::nullopt; - optionsMenuOpen = false; - } - } - } - gui.pop_id(); - } + center_obstructing_window_gui("gsettings", CLAY_SIZING_FIXED(600), CLAY_SIZING_FIXED(500), [&] { + general_settings_inner_gui(); + }); break; } case LOBBY_INFO_MENU: { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT LOBBY INFO MENU"); - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(650), .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.push_id("lobby info menu"); - gui.obstructing_window(elemId); - if(!main.world || !main.world->netObjMan.is_connected()) - optionsMenuOpen = false; - else { - std::string oldS = main.world->netSource; - gui.input_text_field("lobby", "Lobby", &main.world->netSource); - main.world->netSource = oldS; - gui.left_to_right_line_layout([&]() { - if(gui.text_button_wide("copy lobby address", "Copy Lobby Address")) - main.input.set_clipboard_str(main.world->netSource); - if(gui.text_button_wide("done", "Done")) - optionsMenuOpen = false; + center_obstructing_window_gui("lobby info menu", CLAY_SIZING_FIT(650), CLAY_SIZING_FIT(0), [&] { + input_text_field(gui, "lobby", "Lobby", &main.world->netSource); + left_to_right_line_layout(gui, [&]() { + text_button_wide("copy lobby address", "Copy Lobby Address", [&] { + main.input.set_clipboard_str(main.world->netSource); }); - } - gui.pop_id(); - } + text_button_wide("done", "Done", [&] { + optionsMenuOpen = false; + }); + }); + }); break; } case CANVAS_SETTINGS_MENU: { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT CANVAS SETTINGS MENU"); - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(500), .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.push_id("canvas settings menu"); - gui.obstructing_window(elemId); - SkColor4f newColorToSet = main.world->canvasTheme.get_back_color(); - gui.color_picker_button_field("canvasColor", "Canvas Color", &newColorToSet, false); - main.world->canvasTheme.set_back_color(convert_vec3(newColorToSet)); - if(gui.text_button_wide("done", "Done")) + center_obstructing_window_gui("canvas settings menu", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] { + auto newColorToSet = std::make_shared(main.world->canvasTheme.get_back_color()); + color_picker_button_field(gui, "canvasColor", "Canvas Color", newColorToSet.get(), { + .hasAlpha = false, + .onEdit = [&, newColorToSet] { + main.world->canvasTheme.set_back_color(convert_vec3(newColorToSet)); + } + }); + text_button_wide("done", "Done", [&] { optionsMenuOpen = false; - gui.pop_id(); - } + }); + }); break; } case SET_DOWNLOAD_NAME: { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT DOWNLOAD NAME MENU"); - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(500), .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.push_id("set download name menu"); - gui.obstructing_window(elemId); - if(!main.world) - optionsMenuOpen = false; - gui.input_text_field("file name", "File Name", &downloadNameSet); - gui.left_to_right_line_layout([&]() { - if(gui.text_button_wide("download save button", "Save")) { + center_obstructing_window_gui("set download name menu", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] { + input_text_field(gui, "file name", "File Name", &downloadNameSet); + left_to_right_line_layout(gui, [&]() { + text_button_wide("download save button", "Save", [&] { main.world->save_to_file(downloadNameSet); optionsMenuOpen = false; - } - if(gui.text_button_wide("cancel", "Cancel")) + }); + text_button_wide("cancel", "Cancel", [&] { optionsMenuOpen = false; + }); }); - gui.pop_id(); - } + }); break; } case ABOUT_MENU: { - about_menu_gui(); + center_obstructing_window_gui("about menu", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] { + about_menu_inner_gui(); + }); break; } } } -void Toolbar::about_menu_gui() { - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT ABOUT MENU"); - CLAY(elemId, { +void Toolbar::general_settings_inner_gui() { + CLAY_AUTO_ID({ .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIXED(600) }, + .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_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM - }, - .backgroundColor = convert_vec4(io->theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } }) { - gui.push_id("about menu popup"); - gui.obstructing_window(elemId); - gui.left_to_right_line_layout([&]() { - gui.push_id("Menu Selector"); - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_FIT(0) }, - .padding = CLAY_PADDING_ALL(io->theme->padding1), - .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, - .layoutDirection = CLAY_TOP_TO_BOTTOM + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(150), .height = CLAY_SIZING_GROW(0) }, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childGap = 2, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + auto category_button = [&](const char* id, const char* str, GeneralSettingsOptions opt) { + text_button(gui, id, str, { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .isSelected = generalSettingsOptions == opt, + .wide = true, + .onClick = [&] { + load_theme(); + themeData.selectedThemeIndex = std::nullopt; + generalSettingsOptions = opt; + } + }); + }; + category_button("Generalbutton", "General", GSETTINGS_GENERAL); + category_button("Tabletbutton", "Tablet", GSETTINGS_TABLET); + category_button("Themebutton", "Theme", GSETTINGS_THEME); + category_button("Keybindsbutton", "Keybinds", GSETTINGS_KEYBINDS); + category_button("Debugbutton", "Debug", GSETTINGS_DEBUG); + } + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + gui.element("general settings scroll area", ScrollArea::Options{ + .scrollVertical = true, + .clipHorizontal = true, + .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.push_id("general settings"); + input_text_field(gui, "display name input", "Display name", &main.displayName); + main.update_display_names(); + color_picker_button_field(gui, "defaultCanvasBackgroundColor", "Default canvas background color", &main.defaultCanvasBackgroundColor, { .hasAlpha = false }); + #ifndef __EMSCRIPTEN__ + checkbox_boolean_field(gui, "native file pick", "Use native file picker", &useNativeFilePicker); + checkbox_boolean_field(gui, "update notifications enable", "Check for updates on startup", &updateCheckerData.checkForUpdates); + #endif + slider_scalar_field(gui, "drag zoom slider", "Drag zoom speed", &dragZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); + slider_scalar_field(gui, "scroll zoom slider", "Scroll zoom speed", &scrollZoomSpeed, 0.0, 1.0, {.decimalPrecision = 3}); + checkbox_boolean_field(gui, "flip zoom tool direction", "Flip zoom tool direction", &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.window.disableGraphicsDriverWorkarounds); + #endif + input_scalar_field(gui, "jump transition time", "Jump transition time", &jumpTransitionTime, 0.01f, 1000.0f, {.decimalPrecision = 2}); + input_scalar_field(gui, "Max GUI Scale", "Max GUI Scale", &guiScale, 0.5f, 5.0f, {.decimalPrecision = 1}); + text_label(gui, "Anti-aliasing:"); + radio_button_selector(gui, "Antialiasing selector", &main.antialiasing, { + {"None", MainProgram::AntiAliasing::NONE}, + {"Skia", MainProgram::AntiAliasing::SKIA}, + {"Dynamic MSAA", MainProgram::AntiAliasing::DYNAMIC_MSAA} + }, [&] { + main.refresh_draw_surfaces(); + }); + text_label(gui, "VSync:"); + radio_button_selector(gui, "VSync selector", &main.window.vsyncValue, { + {"On", 1}, + {"Off", 0}, + {"Adaptive", -1} + }, [&] { + main.set_vsync_value(main.window.vsyncValue); + }); + + #ifndef __EMSCRIPTEN__ + checkbox_boolean_field(gui, "apply display scale", "Apply display scale", &main.window.applyDisplayScale); + #endif + + gui.pop_id(); + break; + } + case GSETTINGS_TABLET: { + gui.push_id("tablet settings"); + checkbox_boolean_field(gui, "pen pressure width", "Pen pressure affects brush size", &tabletOptions.pressureAffectsBrushWidth); + slider_scalar_field(gui, "smoothing time", "Smoothing sampling time", &tabletOptions.smoothingSamplingTime, 0.001f, 1.0f, {.decimalPrecision = 3}); + input_scalar_field(gui, "middle click", "Middle click pen button", &tabletOptions.middleClickButton, 1, 255); + input_scalar_field(gui, "right click", "Right click pen button", &tabletOptions.rightClickButton, 1, 255); + slider_scalar_field(gui, "tablet brush minimum size", "Brush relative minimum size", &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", &tabletOptions.zoomWhilePenDownAndButtonHeld); + #ifdef _WIN32 + checkbox_boolean_field(gui, "mouse ignore when pen proximity", "Ignore mouse movement when pen in proximity", &tabletOptions.ignoreMouseMovementWhenPenInProximity); + #endif + gui.pop_id(); + break; + } + case GSETTINGS_THEME: { + gui.push_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 = [&]() { + themeData.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; + reload_theme_list(); + } + }); + } + left_to_right_line_layout(gui, [&]() { + if(themeData.selectedThemeIndex != 0) { + text_button_wide("savethemebutton", "Save", [&] { + themeData.themeCurrentlyLoaded = themeData.themeDirList[themeData.selectedThemeIndex.value()]; + save_theme(); + reload_theme_list(); + }); + } + text_button_wide("saveasthemebutton", "Save As", [&]() { + themeData.openedSaveAsMenu = !themeData.openedSaveAsMenu; + }); + text_button_wide("reloadthemebutton", "Reload", [&] { + themeData.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(...) { } + themeData.themeCurrentlyLoaded = "Default"; + reload_theme_list(); + }); + } + }); + if(themeData.openedSaveAsMenu) { + input_text_field(gui, "Theme name:", "Theme name: ", &themeData.themeCurrentlyLoaded); + left_to_right_line_layout(gui, [&]() { + text_button_wide("saveasdone", "Done", [&] { + save_theme(); + 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); + gui.pop_id(); + 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.push_id("keybind entries"); + for(unsigned i = 0; i < InputManager::KEY_ASSIGNABLE_COUNT; i++) { + gui.push_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; + } + }); + } + gui.pop_id(); + } + gui.pop_id(); + break; + } + case GSETTINGS_DEBUG: { + gui.push_id("debug settings menu"); + checkbox_boolean_field(gui, "show performance metrics", "Show metrics", &showPerformance); + input_scalars_field(gui, "jump transition easing", "Jump easing", &jumpTransitionEasing, 4, -10.0f, 10.0f, { .decimalPrecision = 2 }); + #ifndef __EMSCRIPTEN__ + input_scalar_field(gui, "fps cap slider", "FPS cap", &main.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); + gui.pop_id(); + break; + } + } + } } - }) { - gui.scroll_bar_area("About Menu Selector Scroll Area", true, [&](float, float, float) { + }); + text_button_wide("done menu", "Done", [&] { + main.save_config(); + load_theme(); + themeData.selectedThemeIndex = std::nullopt; + optionsMenuOpen = false; + }); + } + } +} + +void Toolbar::about_menu_inner_gui() { + left_to_right_line_layout(gui, [&]() { + gui.push_id("Menu Selector"); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_FIT(0) }, + .padding = CLAY_PADDING_ALL(io->theme->padding1), + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP}, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + gui.element("About Menu Selector Scroll Area", ScrollArea::Options{ + .innerContent = [&](const ScrollArea::InnerContentParameters&) { CLAY_AUTO_ID({ .layout = { .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)} } }) { - if(gui.text_button_left_transparent("infinipaintnoticebutton", "InfiniPaint", selectedLicense == -1)) selectedLicense = -1; + text_button(gui, "infinipaintnoticebutton", "InfiniPaint", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .isSelected = selectedLicense == -1, + .wide = true, + .onClick = [&] { selectedLicense = -1; } + }); } - gui.text_label_light_centered("Third Party Components"); + text_label_light_centered(gui, "Third Party Components"); for(int i = 0; i < static_cast(thirdPartyLicenses.size()); i++) { CLAY_AUTO_ID({ .layout = { @@ -2084,21 +2102,32 @@ void Toolbar::about_menu_gui() { } }) { gui.push_id(i); - if(gui.text_button_left_transparent("noticebutton", thirdPartyLicenses[i].first, selectedLicense == i)) selectedLicense = i; - gui.pop_id(); + text_button(gui, "noticebutton", thirdPartyLicenses[i].first, { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .isSelected = selectedLicense == i, + .wide = true, + .onClick = [&, i] { selectedLicense = i; } + }); } } - }); - } - gui.pop_id(); - gui.scroll_bar_area("About Menu Text Scroll Area", false, [&](float, float, float) { - gui.text_label_size((selectedLicense == -1) ? ownLicenseText : thirdPartyLicenses[selectedLicense].second, 0.8f); + } }); - }); - if(gui.text_button_wide("done", "Done")) - optionsMenuOpen = false; + } gui.pop_id(); - } + + gui.element("About Menu Text Scroll Area", ScrollArea::Options{ + .scrollVertical = true, + .clipHorizontal = true, + .clipVertical = true, + .showScrollbarY = true, + .innerContent = [&](const ScrollArea::InnerContentParameters&) { + text_label_size(gui, (selectedLicense == -1) ? ownLicenseText : thirdPartyLicenses[selectedLicense].second, 0.8f); + } + }); + }); + text_button_wide("done", "Done", [&] { + optionsMenuOpen = false; + }); } void Toolbar::reload_theme_list() { @@ -2123,33 +2152,80 @@ void Toolbar::reload_theme_list() { themeData.openedSaveAsMenu = false; } +void Toolbar::file_picker_gui_refresh_entries() { + filePicker.entries.clear(); + for(;;) { + try { + for(const std::filesystem::path& entry : std::filesystem::directory_iterator(filePicker.currentSearchPath)) + filePicker.entries.emplace_back(entry); + break; + } + catch(const std::exception& e) { + Logger::get().log("INFO", e.what()); + if(filePicker.currentSearchPath == main.homePath) // The home path must exist. If we get errors on the home path, we have a real problem + throw e; + filePicker.currentSearchPath = main.homePath; + } + } + + std::vector extensionList = split_string_by_token(filePicker.extensionFilters[filePicker.extensionSelected], ";"); + for(std::string& s : extensionList) { + std::transform(s.begin(), s.end(), s.begin(), ::tolower); + s.insert(0, "."); + } + + std::erase_if(filePicker.entries, [&](const std::filesystem::path& a) { + if(extensionList.empty()) + return false; + if(extensionList[0] == ".*") + return false; + std::string fExtension; + if(a.has_extension()) { + fExtension = a.extension().string(); + std::transform(fExtension.begin(), fExtension.end(), fExtension.begin(), ::tolower); + } + return std::filesystem::is_regular_file(a) && (fExtension.empty() || !std::ranges::contains(extensionList, fExtension)); + }); + + std::sort(filePicker.entries.begin(), filePicker.entries.end(), [&](const std::filesystem::path& a, const std::filesystem::path& b) { + bool aDir = std::filesystem::is_directory(a); + bool bDir = std::filesystem::is_directory(b); + const std::string& aStr = a.string(); + const std::string& bStr = b.string(); + if(aDir && !bDir) + return true; + if(!aDir && bDir) + return false; + return std::lexicographical_compare(aStr.begin(), aStr.end(), bStr.begin(), bStr.end()); + }); + + gui.set_to_layout(); +} + +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]); + } + filePicker.isOpen = false; +} + void Toolbar::file_picker_gui() { - gui.push_id("filepicker"); - Clay_ElementId elemId = CLAY_ID_LOCAL("INFINIPAINT FILE PICKER MENU"); bool isDoneByDoubleClick = false; - CLAY(elemId, { - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(700), .height = CLAY_SIZING_FIXED(500) }, - .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), - .floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT} - }) { - gui.obstructing_window(elemId); - gui.text_label_centered(filePicker.filePickerWindowName); - gui.left_to_right_line_layout([&]() { - if(gui.svg_icon_button("file picker back button", "data/icons/backarrow.svg", false, 30.0f)) { - filePicker.currentSearchPath = filePicker.currentSearchPath.parent_path(); - filePicker.refreshEntries = true; - } - std::filesystem::path pathDiff = filePicker.currentSearchPath; - gui.input_path_field("file picker path", "Path", &filePicker.currentSearchPath, std::filesystem::file_type::directory); - if(pathDiff != filePicker.currentSearchPath) - filePicker.refreshEntries = true; + center_obstructing_window_gui("file picker gui window", CLAY_SIZING_FIXED(700), CLAY_SIZING_FIXED(500), [&] { + text_label_centered(gui, filePicker.filePickerWindowName); + left_to_right_line_layout(gui, [&]() { + svg_icon_button(gui, "file picker back button", "data/icons/backarrow.svg", { + .onClick = [&] { + filePicker.currentSearchPath = filePicker.currentSearchPath.parent_path(); + file_picker_gui_refresh_entries(); + } + }); + input_path_field(gui, "file picker path", "Path", &filePicker.currentSearchPath, { + .onEdit = [&] { + file_picker_gui_refresh_entries(); + } + }); }); CLAY_AUTO_ID({ .layout = { @@ -2159,179 +2235,76 @@ void Toolbar::file_picker_gui() { }, .backgroundColor = convert_vec4(io->theme->backColor2) }) { - if(filePicker.refreshEntries) { - filePicker.entries.clear(); - for(;;) { - try { - for(const std::filesystem::path& entry : std::filesystem::directory_iterator(filePicker.currentSearchPath)) - filePicker.entries.emplace_back(entry); - break; - } - catch(const std::exception& e) { - Logger::get().log("INFO", e.what()); - if(filePicker.currentSearchPath == main.homePath) // The home path must exist. If we get errors on the home path, we have a real problem - throw e; - filePicker.currentSearchPath = main.homePath; - } - } - - std::vector extensionList = split_string_by_token(filePicker.extensionFilters[filePicker.extensionSelected], ";"); - for(std::string& s : extensionList) { - std::transform(s.begin(), s.end(), s.begin(), ::tolower); - s.insert(0, "."); - } - - std::erase_if(filePicker.entries, [&](const std::filesystem::path& a) { - if(extensionList.empty()) - return false; - if(extensionList[0] == ".*") - return false; - std::string fExtension; - if(a.has_extension()) { - fExtension = a.extension().string(); - std::transform(fExtension.begin(), fExtension.end(), fExtension.begin(), ::tolower); - } - return std::filesystem::is_regular_file(a) && (fExtension.empty() || !std::ranges::contains(extensionList, fExtension)); - }); - - std::sort(filePicker.entries.begin(), filePicker.entries.end(), [&](const std::filesystem::path& a, const std::filesystem::path& b) { - bool aDir = std::filesystem::is_directory(a); - bool bDir = std::filesystem::is_directory(b); - const std::string& aStr = a.string(); - const std::string& bStr = b.string(); - if(aDir && !bDir) - return true; - if(!aDir && bDir) - return false; - return std::lexicographical_compare(aStr.begin(), aStr.end(), bStr.begin(), bStr.end()); - }); - filePicker.refreshEntries = false; - } - float entryHeight = 25.0f; - gui.scroll_bar_many_entries_area("file picker entries", entryHeight, filePicker.entries.size(), true, [&](size_t i, bool isListHovered) { - const std::filesystem::path& entry = filePicker.entries[i]; - bool selectedEntry = filePicker.currentSelectedPath == entry; - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)}, - .childGap = 1, - .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, - .layoutDirection = CLAY_LEFT_TO_RIGHT - }, - .backgroundColor = selectedEntry ? convert_vec4(io->theme->backColor1) : convert_vec4(io->theme->backColor2) - }) { - CLAY_AUTO_ID({ - .layout = { - .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} - }, - }) { - if(std::filesystem::is_directory(entry)) - gui.svg_icon("folder icon", "data/icons/folder.svg", selectedEntry); - else - gui.svg_icon("file icon", "data/icons/file.svg", selectedEntry); - } - gui.text_label(entry.filename().string()); - if(Clay_Hovered() && io->mouse.leftClick && isListHovered) { - if(selectedEntry && io->mouse.leftClick >= 2) { - if(std::filesystem::is_directory(entry)) { - filePicker.currentSearchPath = entry; - filePicker.refreshEntries = true; + constexpr float entryHeight = 25.0f; + scroll_area_many_entries(gui, "file picker entries", { + .entryHeight = entryHeight, + .entryCount = filePicker.entries.size(), + .clipHorizontal = true, + .elementContent = [&] (size_t i) { + const std::filesystem::path& entry = filePicker.entries[i]; + bool selectedEntry = filePicker.currentSelectedPath == entry; + gui.element("elem", [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(entryHeight)}, + .childGap = 1, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .backgroundColor = selectedEntry ? convert_vec4(io->theme->backColor1) : convert_vec4(io->theme->backColor2) + }) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)} + }, + }) { + if(std::filesystem::is_directory(entry)) + gui.element("folder icon", "data/icons/folder.svg", selectedEntry); + else + gui.element("file icon", "data/icons/file.svg", selectedEntry); } - else if(std::filesystem::is_regular_file(entry)) - isDoneByDoubleClick = true; + text_label(gui, entry.filename().string()); } - else { - filePicker.currentSelectedPath = entry; - if(std::filesystem::is_regular_file(entry)) - filePicker.fileName = entry.filename().string(); + }, LayoutElement::Callbacks { + .mouseButton = [&, selectedEntry, entry](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { + 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)) + filePicker.currentSearchPath = entry; + else if(std::filesystem::is_regular_file(entry)) + file_picker_gui_done(); + } + else { + filePicker.currentSelectedPath = entry; + if(std::filesystem::is_regular_file(entry)) + filePicker.fileName = entry.filename().string(); + } + }); + gui.set_to_layout(); + } + return mouseHovering; } - } + }); } }); } - gui.left_to_right_line_layout([&]() { - gui.input_text("filepicker filename", &filePicker.fileName); - size_t oldExtensionSelected = filePicker.extensionSelected; - gui.dropdown_select("filepicker select type", &filePicker.extensionSelected, filePicker.extensionFilters); - if(oldExtensionSelected != filePicker.extensionSelected) - filePicker.refreshEntries = true; + left_to_right_line_layout(gui, [&]() { + input_text(gui, "filepicker filename", &filePicker.fileName); + gui.element>("filepicker select type", &filePicker.extensionSelected, filePicker.extensionFilters, DropdownOptions{ + .onClick = [&] { file_picker_gui_refresh_entries(); } + }); }); - gui.left_to_right_line_layout([&]() { - std::filesystem::path pathToRet = std::filesystem::path(); - if(gui.text_button_wide("filepicker done", "Done") || isDoneByDoubleClick) { - if(!filePicker.fileName.empty()) { - pathToRet = filePicker.currentSearchPath / filePicker.fileName; - filePicker.postSelectionFunc(force_extension_on_path(pathToRet, filePicker.extensionFiltersComplete[filePicker.extensionSelected].extensions), filePicker.extensionFiltersComplete[filePicker.extensionSelected]); - } - filePicker.isOpen = false; - } - if(gui.text_button_wide("filepicker cancel", "Cancel")) { - filePicker.isOpen = false; - } + left_to_right_line_layout(gui, [&]() { + text_button_wide("filepicker done", "Done", [&] { file_picker_gui_done(); }); + text_button_wide("filepicker cancel", "Cancel", [&] { filePicker.isOpen = false; }); }); - } - gui.pop_id(); -} - - -void Toolbar::initialize_io() { - io->deltaTime = main.deltaTime; - io->key.leftShift = main.input.key(InputManager::KEY_TEXT_SHIFT).held; - io->key.leftCtrl = main.input.key(InputManager::KEY_TEXT_CTRL).held; - io->previousRichTextBoxToEdit = io->richTextBoxToEdit; - io->richTextBoxToEditCursor = nullptr; - io->richTextBoxToEdit = nullptr; - io->richTextBoxToEditRectangle = nullptr; - io->guiScaleMultiplier = final_gui_scale(); -} - -void Toolbar::end_io() { - if(io->clipboard.textOut) - main.input.set_clipboard_str(*io->clipboard.textOut); - if(io->richTextBoxToEdit != io->previousRichTextBoxToEdit) { - if(io->richTextBoxToEdit) - main.input.set_rich_text_box_input_front(io->richTextBoxToEdit, io->richTextBoxToEditCursor, false, io->richTextBoxToEditRectangle, io->richTextInputProperties); - if(io->previousRichTextBoxToEdit) - main.input.remove_rich_text_box_input(io->previousRichTextBoxToEdit); - } - io->key.left = false; - io->key.right = false; - io->key.up = false; - io->key.down = false; - io->key.enter = false; - io->key.escape = false; - io->mouse.leftClick = 0; - io->mouse.rightClick = 0; - io->mouse.scroll = {0, 0}; + }); } void Toolbar::input_key_callback(const InputManager::KeyCallbackArgs& key) { if(key.down) { switch(key.key) { - case InputManager::KEY_TEXT_LEFT: { - io->key.left = true; - break; - } - case InputManager::KEY_TEXT_RIGHT: { - io->key.right = true; - break; - } - case InputManager::KEY_TEXT_UP: { - io->key.up = true; - break; - } - case InputManager::KEY_TEXT_DOWN: { - io->key.down = true; - break; - } - case InputManager::KEY_TEXT_ENTER: { - io->key.enter = true; - break; - } - case InputManager::KEY_TEXT_ESCAPE: { - io->key.escape = true; - break; - } case InputManager::KEY_SAVE: { save_func(); break; @@ -2342,15 +2315,18 @@ void Toolbar::input_key_callback(const InputManager::KeyCallbackArgs& key) { } case InputManager::KEY_SHOW_METRICS: { showPerformance = !showPerformance; + gui.set_to_layout(); break; } case InputManager::KEY_OPEN_CHAT: { if(chatBoxState == CHATBOXSTATE_CLOSE) chatBoxState = CHATBOXSTATE_JUSTOPEN; + gui.set_to_layout(); break; } case InputManager::KEY_SHOW_PLAYER_LIST: { playerMenuOpen = !playerMenuOpen; + gui.set_to_layout(); break; } } @@ -2358,39 +2334,38 @@ void Toolbar::input_key_callback(const InputManager::KeyCallbackArgs& key) { } void Toolbar::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { - if(button.deviceType != InputManager::MouseDeviceType::TOUCH) { - io->mouse.globalPos = button.pos / final_gui_scale(); - if(button.down) { - switch(button.button) { - case InputManager::MouseButton::LEFT: { - io->mouse.leftClick = std::max(io->mouse.leftClick, button.clicks); - io->mouse.leftHeld = true; - break; - } - case InputManager::MouseButton::RIGHT: { - io->mouse.rightClick = std::max(io->mouse.rightClick, button.clicks); - io->mouse.rightHeld = true; - break; - } - default: - break; - } - } - else { - switch(button.button) { - case InputManager::MouseButton::LEFT: { - io->mouse.leftHeld = false; - break; - } - case InputManager::MouseButton::RIGHT: { - io->mouse.rightHeld = false; - break; - } - default: - break; - } - } - } + //if(button.deviceType != InputManager::MouseDeviceType::TOUCH) { + // if(button.down) { + // switch(button.button) { + // case InputManager::MouseButton::LEFT: { + // io->mouse.leftClick = std::max(io->mouse.leftClick, button.clicks); + // io->mouse.leftHeld = true; + // break; + // } + // case InputManager::MouseButton::RIGHT: { + // io->mouse.rightClick = std::max(io->mouse.rightClick, button.clicks); + // io->mouse.rightHeld = true; + // break; + // } + // default: + // break; + // } + // } + // else { + // switch(button.button) { + // case InputManager::MouseButton::LEFT: { + // io->mouse.leftHeld = false; + // break; + // } + // case InputManager::MouseButton::RIGHT: { + // io->mouse.rightHeld = false; + // break; + // } + // default: + // break; + // } + // } + //} } void Toolbar::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { @@ -2428,9 +2403,6 @@ void Toolbar::start_gui() { } initialize_io(); gui.io = io; - io->hoverObstructed = false; - io->hoverObstructingAABBs.clear(); - io->hoverObstructingCircles.clear(); io->isTouch = main.input.isTouchDevice; gui.begin(); } diff --git a/src/Toolbar.hpp b/src/Toolbar.hpp index 884243b..7af2cbb 100644 --- a/src/Toolbar.hpp +++ b/src/Toolbar.hpp @@ -47,6 +47,7 @@ class Toolbar { void input_finger_touch_callback(const InputManager::FingerTouchCallbackArgs& touch); void input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion); void update(); + void layout_run(); void draw(SkCanvas* canvas, bool skiaAA); void color_selector_left(Vector4f* color); void color_selector_right(Vector4f* color); @@ -99,13 +100,12 @@ class Toolbar { bool app_close_requested(); private: - void end_io(); - void initialize_io(); void calculate_final_gui_scale(); float final_gui_scale_not_fit(); static void sdl_open_file_dialog_callback(void* userData, const char * const * fileList, int filter); + void text_button_wide(const char* id, const char* str, const std::function& onClick); void reload_theme_list(); void player_list(); void chat_box(); @@ -117,18 +117,22 @@ class Toolbar { void layer_menu(bool justOpened); void drawing_program_gui(); void options_menu(); + void file_picker_gui_refresh_entries(); + void file_picker_gui_done(); void file_picker_gui(); void performance_metrics(); bool color_palette(const char* id, Vector4f* color, bool& hoveringOnDropdown); void open_world_file(bool isClient, const std::string& netSource, const std::string& serverLocalID); void load_default_palette(); void load_default_theme(); - void about_menu_gui(); + void about_menu_inner_gui(); void web_version_welcome(); void still_connecting_center_message(); void no_layers_being_edited_message(); void close_popup_gui(); void add_world_to_close_popup_data(const std::shared_ptr& w); + void general_settings_inner_gui(); + void center_obstructing_window_gui(const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function& innerContent); struct ClosePopupData { struct CloseWorldData { @@ -229,7 +233,6 @@ class Toolbar { std::vector extensionFiltersComplete; std::vector extensionFilters; std::vector entries; - bool refreshEntries = true; std::filesystem::path currentSearchPath; std::filesystem::path currentSelectedPath; std::string fileName; @@ -251,9 +254,6 @@ class Toolbar { std::unique_ptr build_paragraph_from_chat_message(const ChatMessage& message, float alpha); - void start_gui(); - void end_gui(); - void load_icons_at(const std::filesystem::path& pathToLoad); std::string serverToConnectTo;