Starting work on moving GUI to event system (doesnt compile)

This commit is contained in:
Yousef Khadadeh 2026-03-27 09:51:53 +03:00
parent f1a855edc3
commit ef7dfcef05
69 changed files with 4296 additions and 3709 deletions

View file

@ -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"

View file

@ -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<SelectableButton>(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<SelectableButton>(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<SVGIcon>("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<SelectableButton>(id, options);
}
}
}}

View file

@ -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 <typename OptT> 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<void()> onClick;
};
struct SVGButtonOptions {
SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED;
bool isSelected = false;
float size = BIG_BUTTON_SIZE;
std::function<void()> onClick;
};
struct FixedSizeColorButtonOptions {
SelectableButton::DrawType drawType = SelectableButton::DrawType::FILLED;
bool isSelected = false;
bool hasAlpha = true;
float size = BIG_BUTTON_SIZE;
std::function<void()> 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 <typename T> 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<ColorRectangleDisplay>([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<SelectableButton>(id, options);
}
}
}}

View file

@ -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<void()>& onClick) {
gui.element<CheckBox>(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<bool()>& isTicked, const std::function<void()>& onClick) {
left_to_right_line_layout(gui, [&]() {
gui.element<CheckBox>(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<void()>& onClick) {
left_to_right_line_layout(gui, [&]() {
checkbox_boolean(gui, id, d, onClick);
text_label(gui, name);
});
}
}}

View file

@ -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<void()>& onClick = nullptr);
void checkbox_field(GUIManager& gui, const char* id, std::string_view name, const std::function<bool()>& isTicked, const std::function<void()>& onClick);
void checkbox_boolean_field(GUIManager& gui, const char* id, std::string_view name, bool* d, const std::function<void()>& onClick = nullptr);
}}

View file

@ -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<void()> onEdit;
};
template <typename T> 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<ColorPicker>("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 <typename T> 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<ColorPickerButton<T>>(id, val, options);
text_label(gui, name);
});
}
} }

View file

@ -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<void()>& innerContent, const LayoutElement::Callbacks& callbacks) {
gui.element<LayoutElement>(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<Clay_Color>(gui.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(10),
.floating = { .attachTo = CLAY_ATTACH_TO_PARENT },
.border = {.color = convert_vec4<Clay_Color>(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<void()>& 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<void()>& 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<void()>& 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();
}
}
}}

View file

@ -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<void()>& innerContent, const LayoutElement::Callbacks& callbacks);
void left_to_right_layout(GUIManager& gui, Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& innerContent);
void left_to_right_line_layout(GUIManager& gui, const std::function<void()>& innerContent);
void left_to_right_line_centered_layout(GUIManager& gui, const std::function<void()>& innerContent);
}}

View file

@ -0,0 +1,20 @@
#pragma once
#include "../GUIManager.hpp"
#include "TextBoxHelpers.hpp"
#include "../Elements/NumberSlider.hpp"
namespace GUIStuff { namespace ElementHelpers {
template <typename T> 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<NumberSlider<T>>("slider", val, min, max, options.onEdit);
gui.pop_id();
}
}}

View file

@ -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<LayoutElement>(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<PaintCircleMenu>("paint circle menu", val);
}
});
}
}}

View file

@ -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);
}}

View file

@ -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<bool()>& isTicked, const std::function<void()>& onClick) {
left_to_right_line_layout(gui, [&]() {
gui.element<RadioButton>(id, isTicked, onClick);
text_label(gui, name);
});
}
}}

View file

@ -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<bool()>& isTicked, const std::function<void()>& onClick);
template <typename T> void radio_button_selector(GUIManager& gui, const char* id, T* data, const std::vector<std::pair<std::string_view, T>>& options, const std::function<void()>& 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();
}
}}

View file

@ -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<ScrollArea>(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)},
}
}) { }
}
}
});
}
}}

View file

@ -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<void(size_t elementIndex)> elementContent;
std::function<void(const ScrollArea::InnerContentParameters&)> innerContentExtraCallback;
};
void scroll_area_many_entries(GUIManager& gui, const char* id, const ScrollBarManyEntriesOptions& options);
}}

View file

@ -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<float> d = textbox_options_to_data<float>(options);
d.textInputProps = textInputProps;
d.fromStr = [](const std::string& str) {
int roundTo255;
std::stringstream ss;
ss << str;
ss >> roundTo255;
return std::clamp<float>(static_cast<float>(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<TextBox<std::string>>(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<std::string> d = textbox_options_to_data<std::string>(options);
d.textInputProps = textInputProps;
d.fromStr = [](const std::string& a) {
return a;
};
d.toStr = [](const std::string& a) {
return a;
};
gui.element<TextBox<std::string>>(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<uint8_t> d = textbox_options_to_data<uint8_t>(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<uint32_t>(a));
};
gui.element<TextBox<uint8_t>>(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<std::filesystem::path> d = textbox_options_to_data<std::filesystem::path>(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<const char8_t*>(a.c_str()), a.length()));
if(fileTypeRestriction != std::filesystem::file_type::none && std::filesystem::status(toRet).type() != fileTypeRestriction)
return std::optional<std::filesystem::path>(std::nullopt);
return std::optional<std::filesystem::path>(toRet);
};
d.toStr = [](const std::filesystem::path& a) {
return a.string();
};
gui.element<TextBox<std::filesystem::path>>(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);
});
}
} }

View file

@ -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 <typename T, typename OptT> TextBoxData<T> textbox_options_to_data(const OptT& options) {
TextBoxData<T> 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<void()> onEnter;
std::function<void()> onEdit;
};
struct TextBoxScalarOptions {
int decimalPrecision = 0;
bool updateEveryEdit = false;
bool immutable = false;
std::function<void()> onEnter;
std::function<void()> onEdit;
};
struct TextBoxPathOptions {
std::filesystem::file_type fileTypeRestriction;
bool updateEveryEdit = false;
bool immutable = false;
std::function<void()> onEnter;
std::function<void()> onEdit;
};
struct TextBoxHexColorOptions {
bool hasAlpha = true;
bool updateEveryEdit = false;
bool immutable = false;
std::function<void()> onEnter;
std::function<void()> 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 <typename T> 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<T>())
textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_SIGNED;
if(std::is_floating_point<T>())
textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_DECIMAL;
TextBoxData<T> d = textbox_options_to_data<T>(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<T>()) {
ss.precision(decimalPrecision);
ss << std::fixed << *a;
}
else
ss << *a;
return ss.str();
};
gui.element<TextBox<T>>(id, d);
}
template <typename TContainer, typename T> 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<T>(gui, "field", &(*val)[i], minVal, maxVal, options);
gui.pop_id();
}
});
gui.pop_id();
}
template <typename T> 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<T>(gui, id, val, minVal, maxVal, options);
});
}
template <typename T> 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<T> d = textbox_options_to_data<T>(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<int>(std::clamp<float>(a[i], 0, 1) * 255);
ss << std::setfill('0') << std::setw(2) << std::hex << convertTo255;
}
return ss.str();
};
gui.element<TextBox<T>>(id, d);
}
} }

View file

@ -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<Clay_Color>(gui.io->theme->frontColor1), .fontSize = static_cast<uint16_t>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(gui.io->theme->frontColor2), .fontSize = gui.io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER}));
}
}
}}

View file

@ -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);
}}

View file

@ -1,28 +1,39 @@
#include "CheckBox.hpp"
#include <include/core/SkPath.h>
#include <include/core/SkPathBuilder.h>
#include "../GUIManager.hpp"
namespace GUIStuff {
void CheckBox::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
CheckBox::CheckBox(GUIManager& gui):
Element(gui) {}
void CheckBox::layout(const std::function<bool()>& isTicked, const std::function<void()>& 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<SkColor4f>(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<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->fillColor2));
p.setColor4f(convert_vec4<SkColor4f>(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<Vector2f, 5> points;

View file

@ -5,13 +5,19 @@ namespace GUIStuff {
class CheckBox : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& 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<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float CHECKBOX_ANIMATION_TIME = 0.3;
bool isTicked = false;
bool isHovering = false;
float hoverAnimation2 = 0.0;
std::function<bool()> isTicked;
std::function<void()> onClick;
};
}

View file

@ -0,0 +1,102 @@
#pragma once
#include "Element.hpp"
#include <include/core/SkColor.h>
#include <include/core/SkPath.h>
#include <include/effects/SkRuntimeEffect.h>
#include <iostream>
#include <Helpers/SCollision.hpp>
#include <Helpers/HsvRgb.hpp>
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<SkShader> get_hue_shader();
sk_sp<SkShader> get_sv_selection_shader(float hue);
sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
}
template <typename T> class ColorPicker : public Element {
public:
ColorPicker(GUIManager& gui);
void layout(T* data, bool selectAlpha, const std::function<void()>& 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<SkShader> hueBarShader;
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
static sk_sp<SkRuntimeEffect> alphaBarEffect;
static sk_sp<SkShader> get_hue_shader();
static sk_sp<SkShader> get_sv_selection_shader(float hue);
static sk_sp<SkShader> 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<void()> onChange;
bool selectAlpha = false;
enum class HeldBar {
NONE,
SV_HELD,
HUE_HELD,
ALPHA_HELD
};
HeldBar held = HeldBar::NONE;
};
}

View file

@ -1,291 +1,3 @@
#pragma once
#include "Element.hpp"
#include <include/core/SkColor.h>
#include <include/core/SkPath.h>
#include <include/effects/SkRuntimeEffect.h>
#include <iostream>
#include <Helpers/SCollision.hpp>
#include <Helpers/HsvRgb.hpp>
namespace GUIStuff {
template <typename T> class ColorPicker : public Element {
public:
bool update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function<void()>& 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<float>(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<float>(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<float>(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<Vector2f>((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<Vector3f>(*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<Vector3f>(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<SkShader> hueBarShader;
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
static sk_sp<SkRuntimeEffect> alphaBarEffect;
static sk_sp<SkShader> get_hue_shader();
static sk_sp<SkShader> get_sv_selection_shader(float hue);
static sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
SCollision::AABB<float> 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 <typename T> sk_sp<SkShader> ColorPicker<T>::hueBarShader;
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::svSelectionAreaEffect;
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::alphaBarEffect;
template <typename T> sk_sp<SkShader> ColorPicker<T>::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 <typename T> sk_sp<SkShader> ColorPicker<T>::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 <typename T> sk_sp<SkShader> ColorPicker<T>::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"

View file

@ -0,0 +1,170 @@
#pragma once
#include "ColorPicker.decl.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> void ColorPicker<T>::layout(T* data, bool selectAlpha, const std::function<void()>& onChange) {
this->data = data;
savedHsv = rgb_to_hsv<T>(*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 <typename T> void ColorPicker<T>::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 <typename T> bool ColorPicker<T>::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<float>(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<float>(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<float>(alphaBarPos, alphaBarPos + alphaBarDim), button.pos))
held = HeldBar::ALPHA_HELD;
}
}
}
update_color_picker_pos(button.pos);
return Element::input_mouse_button_callback(button, mouseHovering);
}
template <typename T> bool ColorPicker<T>::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 <typename T> void ColorPicker<T>::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<Vector2f>((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 <typename T> float ColorPicker<T>::get_sv_selection_area_size() {
return boundingBox.value().width() - (BAR_GAP + BAR_WIDTH);
}
template <typename T> Vector2f ColorPicker<T>::get_hue_bar_pos() {
return {boundingBox.value().min.x() + get_sv_selection_area_size() + BAR_GAP, boundingBox.value().min.y()};
}
template <typename T> Vector2f ColorPicker<T>::get_hue_bar_dim() {
return {BAR_WIDTH, get_sv_selection_area_size()};
}
template <typename T> Vector2f ColorPicker<T>::get_alpha_bar_pos() {
return {boundingBox.value().min.x(), boundingBox.value().min.y() + get_sv_selection_area_size() + BAR_GAP};
}
template <typename T> Vector2f ColorPicker<T>::get_alpha_bar_dim() {
return {boundingBox.value().width(), BAR_WIDTH};
}
template <typename T> void ColorPicker<T>::set_hsv(const Vector3f& hsv) {
Vector3f a = hsv_to_rgb<Vector3f>(hsv);
(*data)[0] = a.x();
(*data)[1] = a.y();
(*data)[2] = a.z();
}
}

View file

@ -0,0 +1,20 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
struct ColorPickerButtonData {
bool hasAlpha = false;
std::function<void()> onEdit;
};
template <typename T> class ColorPickerButton : public Element {
public:
ColorPickerButton(GUIManager& gui);
void layout(T* val, ColorPickerButtonData& d);
private:
ColorPickerButtonData data;
bool isOpen = false;
};
}

View file

@ -0,0 +1,3 @@
#pragma once
#include "ColorPickerButton.decl.hpp"
#include "ColorPickerButton.impl.hpp"

View file

@ -0,0 +1,35 @@
#include "ColorPickerButton.decl.hpp"
#include "../ElementHelpers/ColorPickerHelpers.hpp"
#include "../ElementHelpers/ButtonHelpers.hpp"
namespace GUIStuff {
template <typename T> ColorPickerButton<T>::ColorPickerButton(GUIManager& gui): Element(gui) {}
template <typename T> void ColorPickerButton<T>::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;
}
});
}
}
}

View file

@ -0,0 +1,55 @@
#include "ColorPicker.decl.hpp"
#include <include/effects/SkRuntimeEffect.h>
namespace GUIStuff { namespace ColorPickerShaders {
sk_sp<SkShader> get_hue_shader() {
static sk_sp<SkShader> 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<SkShader> get_sv_selection_shader(float hue) {
static sk_sp<SkRuntimeEffect> 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<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) {
static sk_sp<SkRuntimeEffect> 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();
}
}}

View file

@ -0,0 +1,49 @@
#include "ColorRectangleDisplay.hpp"
namespace GUIStuff {
ColorRectangleDisplay::ColorRectangleDisplay(GUIManager& gui): Element(gui) {}
void ColorRectangleDisplay::layout(const std::function<SkColor4f()>& 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<SkShader> 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();
}
}

View file

@ -0,0 +1,31 @@
#pragma once
#include "Element.hpp"
#include <include/effects/SkRuntimeEffect.h>
namespace GUIStuff {
class ColorRectangleDisplay : public Element {
public:
ColorRectangleDisplay(GUIManager& gui);
void layout(const std::function<SkColor4f()>& 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<SkColor4f()> colorFunc;
static sk_sp<SkRuntimeEffect> alphaBackgroundEffect;
static sk_sp<SkShader> get_alpha_background_shader();
};
}

View file

@ -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<void()> onClick;
};
template <typename T> class DropDown : public Element {
public:
DropDown(GUIManager& gui): Element(gui) {}
void layout(T* data, const std::vector<std::string>& selections, const DropdownOptions& options = {}) {
opts = options;
ElementHelpers::left_to_right_layout(gui, CLAY_SIZING_FIXED(opts.width), CLAY_SIZING_FIT(0), [&] {
gui.element<SelectableButton>("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<SVGIcon>("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<Clay_Color>(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<Clay_Color>(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<size_t>(*data) == i;
gui.element<LayoutElement>("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<Clay_Color>(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<size_t> hoveringOver;
};
}

View file

@ -1,68 +1,37 @@
#include "Element.hpp"
#include "Helpers/MathExtras.hpp"
#include <chrono>
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<Theme> get_default_dark_mode() {
std::shared_ptr<Theme> theme(std::make_shared<Theme>());
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<SCollision::AABB<float>>& Element::get_bb() const {
return boundingBox;
}
SCollision::AABB<float> 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<float> 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;
}
}

View file

@ -1,149 +1,34 @@
#pragma once
#include "Helpers/ConvertVec.hpp"
#include <include/core/SkCanvas.h>
#include "../../TimePoint.hpp"
#include <Eigen/Dense>
#include <include/core/SkFont.h>
#include <include/core/SkFontMgr.h>
#include <include/core/SkTypeface.h>
#include "../../FontData.hpp"
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
#include <nlohmann/json.hpp>
#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 <size_t S> 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<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
}
private:
std::array<char, S> 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<SCollision::AABB<float>>& 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<Theme> 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<std::string()> textInFunc;
std::optional<std::string> textOut;
} clipboard;
std::shared_ptr<RichText::TextBox> previousRichTextBoxToEdit;
std::shared_ptr<RichText::TextBox> richTextBoxToEdit;
std::shared_ptr<RichText::TextBox::Cursor> richTextBoxToEditCursor;
InputManager::TextInputProperties richTextInputProperties;
std::shared_ptr<SCollision::AABB<float>> richTextBoxToEditRectangle;
SkFont get_font(float fSize) const;
uint16_t fontSize = 16;
std::string textInput;
bool hoverObstructed = false;
std::vector<SCollision::AABB<float>> hoverObstructingAABBs;
std::vector<SCollision::Circle<float>> hoverObstructingCircles;
bool acceptingTextInput = false;
float deltaTime = 0.0f;
bool isTouch = true;
float guiScaleMultiplier = 1.0f;
Vector2f windowPos = {0.0f, 0.0f};
std::shared_ptr<FontData> fonts;
sk_sp<SkTypeface> textTypeface;
DefaultStringArena* strArena;
std::shared_ptr<Theme> theme;
std::unordered_map<std::string, sk_sp<SkSVGDOM>> 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<float> 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<float> get_bb_from_command(Clay_RenderCommand* command);
std::optional<SCollision::AABB<float>> boundingBox;
GUIManager& gui;
};
}

View file

@ -6,182 +6,184 @@
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
#include <modules/skunicode/include/SkUnicode_icu.h>
#include <Helpers/Random.hpp>
#include "../GUIManager.hpp"
bool GUIStuff::FontPicker::update(UpdateInputData& io, std::string* fontName, GUIManager* gui) {
if(sortedFontList.empty()) {
auto icu = SkUnicodes::ICU::Make();
std::set<std::string> 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<std::string>(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<std::string> 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<std::string>(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<size_t>::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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<size_t>::max())
scrollAmount = -static_cast<float>(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<size_t>::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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<size_t>::max())
// scrollAmount = -static_cast<float>(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) {
}

View file

@ -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<void()> onFontChange;
};
FontPicker(GUIManager& gui);
void layout(const Data& data);
private:
Data d;
std::vector<std::string> sortedFontList;
std::vector<std::string> sortedFontListLowercase;
bool dropdownOpen = false;

View file

@ -0,0 +1,64 @@
#include "Element.hpp"
#include "Helpers/MathExtras.hpp"
#include <chrono>
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<Theme> get_default_dark_mode() {
std::shared_ptr<Theme> theme(std::make_shared<Theme>());
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;
}
}

View file

@ -0,0 +1,105 @@
#pragma once
#include "Helpers/ConvertVec.hpp"
#include <include/core/SkCanvas.h>
#include "../../TimePoint.hpp"
#include <Eigen/Dense>
#include <include/core/SkFont.h>
#include <include/core/SkFontMgr.h>
#include <include/core/SkTypeface.h>
#include "../../FontData.hpp"
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
#include <nlohmann/json.hpp>
#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 <size_t S> 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<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
}
private:
std::array<char, S> 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<Theme> get_default_dark_mode();
struct UpdateInputData {
InputManager* input;
std::function<void()> 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<FontData> fonts;
sk_sp<SkTypeface> textTypeface;
DefaultStringArena* strArena;
std::shared_ptr<Theme> theme;
std::unordered_map<std::string, sk_sp<SkSVGDOM>> 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;
};
}

View file

@ -0,0 +1,34 @@
#include "LayoutElement.hpp"
namespace GUIStuff {
LayoutElement::LayoutElement(GUIManager& gui): Element(gui) {}
void LayoutElement::layout(const std::function<void()>& 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);
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class LayoutElement : public Element {
public:
LayoutElement(GUIManager& gui);
struct Callbacks {
std::function<bool(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering)> mouseButton;
std::function<bool(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering)> mouseMotion;
std::function<bool(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering)> mouseWheel;
std::function<void(const InputManager::KeyCallbackArgs& key)> key;
};
void layout(const std::function<void()>& 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;
};
}

View file

@ -5,76 +5,63 @@
namespace GUIStuff {
void MovableTabList::update(UpdateInputData& io, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate) {
size_t currentSelectedTab = selectedTab;
if(tabNames != oldTabNames) {
buttons = std::vector<SelectableButton>(tabNames.size());
closeButtons = std::vector<SelectableButton>(tabNames.size());
closeIcons = std::vector<SVGIcon>(tabNames.size());
tabIcons = std::vector<SVGIcon>(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<Clay_Color>(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<Clay_Color>(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);
// }
// }
//}
}
}

View file

@ -7,15 +7,15 @@ namespace GUIStuff {
class MovableTabList : public Element {
public:
void update(UpdateInputData& io, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
SelectionHelper selection;
private:
std::vector<std::pair<std::string, std::string>> oldTabNames;
std::vector<SelectableButton> buttons;
std::vector<SelectableButton> closeButtons;
std::vector<SVGIcon> closeIcons;
std::vector<SVGIcon> tabIcons;
struct Data {
std::vector<std::pair<std::string, std::string>> tabNames;
size_t selectedTab = 0;
std::function<void(size_t)> changeSelectedTab;
std::function<void(size_t)> closeTab;
};
MovableTabList(GUIManager& gui);
void layout(const Data& d);
};
}

View file

@ -1,15 +1,19 @@
#pragma once
#include "Element.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> class NumberSlider : public Element {
public:
bool update(UpdateInputData& io, T* data, T minNew, T maxNew, const std::function<void()>& elemUpdate) {
bool isUpdating = false;
NumberSlider(GUIManager& gui):
Element(gui) {}
min = minNew;
max = maxNew;
void layout(T* data, T minData, T maxData, const std::function<void()>& onChange) {
this->data = data;
this->minData = minData;
this->maxData = maxData;
this->onChange = onChange;
CLAY_AUTO_ID({
.layout = {
@ -17,43 +21,27 @@ template <typename T> 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<T>(std::clamp<double>(std::lerp<double>(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<float>(cachedData, max, min) * bb.width();
float holderPos = lerp_time<float>(*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 <typename T> 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<float> 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<T>(std::clamp<double>(std::lerp<double>(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<void()> onChange;
float hoverAnimation = 0.0;
float holdAnimation = 0.0;

View file

@ -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<void()>& elemUpdate) {
auto& io = *gui.io;
CLAY_AUTO_ID({.layout = {
PaintCircleMenu::PaintCircleMenu(GUIManager& gui):
Element(gui) {}
void PaintCircleMenu::layout(const Data& data, const std::function<void()>& 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<float>(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<unsigned>(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<unsigned>(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;

View file

@ -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<Vector3f> palette;
std::function<void()> onRotate;
std::function<void()> onPaletteClick;
};
void update(GUIManager& gui, const Data& data, const std::function<void()>& elemUpdate);
void layout(const Data& data, const std::function<void()>& 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<float> bb;
SelectionHelper rotateBarSelect;
SelectionHelper colorBarSelect;
std::function<void()> 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);
};
}

View file

@ -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<void()>& 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<LayoutElement>("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<Clay_Color>(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();
}
});
}
}

View file

@ -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<void()>& innerContent);
private:
LayoutElement* layoutElement = nullptr;
};
}

View file

@ -1,33 +1,44 @@
#include "RadioButton.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
void RadioButton::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
RadioButton::RadioButton(GUIManager& gui):
Element(gui) {}
void RadioButton::layout(const std::function<bool()>& isTicked, const std::function<void()>& 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<SkColor4f>(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<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->backColor2));
p.setColor4f(convert_vec4<SkColor4f>(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);

View file

@ -5,15 +5,18 @@ namespace GUIStuff {
class RadioButton : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& 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<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3;
bool isTicked = false;
bool isHovering = false;
float hoverAnimation2 = 0.0;
std::function<bool()> isTicked;
std::function<void()> onClick;
};
}

View file

@ -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<void()>& 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<void()>& 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);

View file

@ -5,17 +5,25 @@ namespace GUIStuff {
class RotateWheel : public Element {
public:
void update(UpdateInputData& io, double* newRotationAnglePtr, const std::function<void()>& elemUpdate);
RotateWheel(GUIManager& gui);
void layout(double* rotateAngle, const std::function<void()>& 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<float> bb;
bool wheelHovered;
double* rotateAngle = nullptr;
bool isHeld = false;
bool isHovering = false;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
std::function<void()> onChange;
void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked);
};
}

View file

@ -3,10 +3,15 @@
#include <include/core/SkColorFilter.h>
#include <include/core/SkColorSpace.h>
#include <iostream>
#include "../GUIManager.hpp"
namespace GUIStuff {
void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& 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());

View file

@ -5,7 +5,8 @@ namespace GUIStuff {
class SVGIcon : public Element {
public:
void update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& 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;

View file

@ -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<Clay_Color>(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<Clay_Color>(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);
}
}

View file

@ -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<void(const InnerContentParameters&)> 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};
};
}

View file

@ -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<void(SelectionHelper&, bool)>& 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<Clay_Color>(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);
}
}

View file

@ -12,9 +12,28 @@ class SelectableButton : public Element {
TRANSPARENT_BORDER
};
void update(UpdateInputData& io, DrawType drawType, const std::function<void(SelectionHelper&, bool)>& 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<void()> onClick;
std::function<void(const InnerContentCallbackParameters&)> 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<void()> onClick;
};
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "Element.hpp"
#include "../../RichText/TextBox.hpp"
#include <limits>
#include <modules/skparagraph/include/TextStyle.h>
#include "../../FontData.hpp"
#include "../../InputManager.hpp"
namespace GUIStuff {
template <typename T> struct TextBoxData {
T* data = nullptr;
std::function<std::optional<T>(const std::string&)> fromStr;
std::function<std::string(const T&)> toStr;
bool singleLine = true;
bool updateEveryEdit = false;
bool immutable = false;
InputManager::TextInputProperties textInputProps;
std::function<void()> onEdit;
std::function<void()> onEnter;
};
template <typename T> class TextBox : public Element {
public:
TextBox(GUIManager& gui);
void layout(const TextBoxData<T>& 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<T> userInfo;
std::shared_ptr<RichText::TextBox> textbox;
std::shared_ptr<RichText::TextBox::Cursor> cur;
std::shared_ptr<SCollision::AABB<float>> rect;
};
}

View file

@ -1,147 +1,3 @@
#pragma once
#include "Element.hpp"
#include "../../RichText/TextBox.hpp"
#include <limits>
#include <modules/skparagraph/include/TextStyle.h>
#include "../../FontData.hpp"
#include "../../InputManager.hpp"
namespace GUIStuff {
template <typename T> class TextBox : public Element {
public:
bool update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, bool updateEveryEdit, const InputManager::TextInputProperties& textInputProps, const std::function<void(SelectionHelper&)>& 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<float>::max());
//textbox->set_text_direction_between(0, 0, textbox->get_suggested_direction(0));
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast<float>(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<T> 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<RichText::TextBox>();
textbox->set_font_data(io.fonts);
textbox->set_allow_newlines(false);
cur = std::make_shared<RichText::TextBox::Cursor>();
rect = std::make_shared<SCollision::AABB<float>>();
if(data) {
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*data));
oldData = *data;
}
}
void update_on_edit() {
std::optional<T> dataToAssign = fromStr(textbox->get_string());
if(dataToAssign.has_value()) {
*data = dataToAssign.value();
oldData = *data;
}
}
T* data = nullptr;
std::optional<T> oldData;
std::function<std::optional<T>(const std::string&)> fromStr;
std::function<std::string(const T&)> toStr;
std::shared_ptr<RichText::TextBox> textbox;
std::shared_ptr<RichText::TextBox::Cursor> cur;
std::shared_ptr<SCollision::AABB<float>> rect;
SCollision::AABB<float> bb = {{1.0f, 1.0f}, {2.0f, 2.0f}};
};
}
#include "TextBox.decl.hpp"
#include "TextBox.impl.hpp"

View file

@ -0,0 +1,134 @@
#include "TextBox.decl.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> void TextBox<T>::layout(const TextBoxData<T>& 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<float>(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast<float>(io.fontSize * 1.25f))}
},
.custom = { .customData = this }
}) {
}
}
template <typename T> void TextBox<T>::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 <typename T> bool TextBox<T>::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 <typename T> bool TextBox<T>::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 <typename T> void TextBox<T>::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 <typename T> void TextBox<T>::init_textbox(UpdateInputData& io) {
textbox = std::make_shared<RichText::TextBox>();
textbox->set_width(std::numeric_limits<float>::max());
textbox->set_font_data(io.fonts);
textbox->set_allow_newlines(false);
cur = std::make_shared<RichText::TextBox::Cursor>();
rect = std::make_shared<SCollision::AABB<float>>();
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*userInfo.data));
}
template <typename T> bool TextBox<T>::update_data() {
std::optional<T> dataToAssign = userInfo.fromStr(textbox->get_string());
if(dataToAssign.has_value()) {
*userInfo.data = dataToAssign.value();
return true;
}
return false;
}
template <typename T> TextBox<T>::~TextBox() {
gui.io->input->remove_rich_text_box_input(textbox);
}
}

View file

@ -4,7 +4,10 @@
namespace GUIStuff {
void TextParagraph::update(UpdateInputData& io, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate) {
TextParagraph::TextParagraph(GUIManager& gui):
Element(gui) {}
void TextParagraph::layout(std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width) {
data = std::move(paragraph);
data->layout(width);
CLAY_AUTO_ID({
@ -12,17 +15,14 @@ void TextParagraph::update(UpdateInputData& io, std::unique_ptr<skia::textlayout
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIXED(data->getHeight())}
},
.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());
}
}
}

View file

@ -6,10 +6,10 @@ namespace GUIStuff {
class TextParagraph : public Element {
public:
void update(UpdateInputData& io, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate);
TextParagraph(GUIManager& gui);
void layout(std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
private:
SCollision::AABB<float> bb = SCollision::AABB<float>({0.0f, 0.0f}, {0.0f, 0.0f});
std::unique_ptr<skia::textlayout::Paragraph> data;
};

View file

@ -3,279 +3,293 @@
#include "Helpers/ConvertVec.hpp"
#include <chrono>
#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<ParentObjectIDStack> 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<NetworkingObjects::NetObjID> 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<ParentObjectIDStack> 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<DisplayData::ObjInList> 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<NetworkingObjects::NetObjID> 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<DisplayData::ObjInList> 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<DisplayData::ObjInList> 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<DisplayData::ObjInList> 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<ParentObjectIDStack>& hoveredObject, std::vector<NetworkingObjects::NetObjID>& parentIDStack, bool parentJustSelected) {
parentIDStack.emplace_back(objID);
for(size_t i = 0;; i++) {
std::optional<DisplayData::ObjInList> 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<DisplayData::ObjInList> 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<float, std::chrono::milliseconds::period>(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<float, std::chrono::milliseconds::period>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<NetworkingObjects::NetObjID>& 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) {
}
}

View file

@ -40,8 +40,11 @@ class TreeListing : public Element {
std::unordered_set<NetworkingObjects::NetObjID> 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;

View file

@ -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<size_t>(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<Element*>(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<void()>& 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<Clay_Color>(io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(10),
.floating = { .attachTo = CLAY_ATTACH_TO_PARENT },
.border = {.color = convert_vec4<Clay_Color>(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<void()>& 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<void()>& 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<void()>& 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<Clay_Color>(io->theme->frontColor1), .fontSize = static_cast<uint16_t>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(io->theme->frontColor2), .fontSize = io->fontSize, .textAlignment = CLAY_TEXT_ALIGN_CENTER}));
}
}
bool GUIManager::radio_button(const char* id, bool val, const std::function<void()>& elemUpdate) {
push_id(id);
RadioButton* e = insert_element<RadioButton>();
e->update(*io, val, elemUpdate);
pop_id();
return e->selection.clicked;
}
void GUIManager::checkbox(const char* id, bool* val, const std::function<void()>& elemUpdate) {
push_id(id);
CheckBox* e = insert_element<CheckBox>();
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<void(SelectionHelper&)>& 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<void(SelectionHelper&)>& 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<void()>& 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<void()>& 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<void(SelectionHelper&)>& elemUpdate) {
push_id(id);
bool isUpdating = insert_element<TextBox<float>>()->update(*io, val,
[&](const std::string& str) {
int roundTo255;
std::stringstream ss;
ss << str;
ss >> roundTo255;
return std::clamp<float>(static_cast<float>(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<void(SelectionHelper&)>& elemUpdate) {
push_id(id);
bool toRet = insert_element<TextBox<std::string>>()->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<void(SelectionHelper&, bool)>& elemUpdate, GUIStuff::SelectableButton::DrawType drawType, bool isSelected, bool onClick) {
push_id(id);
SelectableButton* e = insert_element<SelectableButton>();
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<void()>& elemUpdate) {
push_id(id);
insert_element<SVGIcon>()->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<void()>& 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<void()>& 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<void()>& 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<void()>& 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<void()>& 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<void()>& 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<void(SelectionHelper&)>& elemUpdate) {
bool isUpdating = false;
push_id(id);
isUpdating = insert_element<TextBox<uint8_t>>()->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<uint8_t>(toRet, min, max);
},
[&](const uint8_t& a) {
return std::to_string(static_cast<uint32_t>(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<void(SelectionHelper&)>& elemUpdate) {
push_id(id);
insert_element<TextBox<std::filesystem::path>>()->update(*io, val,
[&](const std::string& a) {
std::filesystem::path toRet = std::filesystem::path(std::u8string_view(reinterpret_cast<const char8_t*>(a.c_str()), a.length()));
if(fileTypeRestriction != std::filesystem::file_type::none && std::filesystem::status(toRet).type() != fileTypeRestriction)
return std::optional<std::filesystem::path>(std::nullopt);
return std::optional<std::filesystem::path>(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<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate) {
push_id(id);
insert_element<MovableTabList>()->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<FontPicker>()->update(*io, fontName, this);
pop_id();
return toRet;
}
void GUIManager::dropdown_select(const char* id, size_t* val, const std::vector<std::string>& selections, float width, const std::function<void()>& hoverboxElemUpdate) {
push_id(id);
struct DropdownData {
bool isOpen = false;
};
DropdownData& dropdownData = insert_any_with_id<DropdownData>(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<Clay_Color>(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<Clay_Color>(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<Clay_Color>(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<size_t>(renderCommands.length); i++) {
Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i);
if(command->commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM) {
Element* customElement = static_cast<Element*>(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<void(float, float, float&)>& 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<ScrollAreaData>(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<Clay_Color>(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<Clay_Color>(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<void(size_t, bool)>& entryUpdate, const std::function<void(float, float, float&)>& 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<void()>& 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<RotateWheel>();
e->update(*io, angle, elemUpdate);
toRet = e->selection.clicked;
}
pop_id();
return toRet;
}
void GUIManager::text_paragraph(const char* id, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate) {
push_id(id);
TextParagraph* e = insert_element<TextParagraph>();
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<void()>& 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<PaintCircleMenu>();
e->update(*this, val, elemUpdate);
}
pop_id();
}
void GUIManager::list_popup_menu(const char* id, Vector2f popupPos, const std::function<void()>& 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<Clay_Color>(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<TreeListing>()->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<float>{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<void()>& f) {
postCallbackFunc = f;
}
}

View file

@ -1,22 +1,9 @@
#pragma once
#include <include/core/SkCanvas.h>
#include <Eigen/Dense>
#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 <any>
#include "GUIManagerID.hpp"
#include <filesystem>
#include <type_traits>
#include <clay.h>
#include <modules/skparagraph/include/Paragraph.h>
#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<Element> 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<UpdateInputData> io;
GUIManagerIDStack idStack;
std::unordered_map<GUIManagerIDStack, ElementContainer> 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<void(SelectionHelper&)>& elemUpdate = nullptr);
void text_paragraph(const char* id, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& 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<void()>& elemUpdate = nullptr);
bool text_button(const char* id, std::string_view text, bool isSelected = false, bool onClick = false, const std::function<void()>& elemUpdate = nullptr);
bool text_button_wide(const char* id, std::string_view text, bool isSelected = false, const std::function<void()>& 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<void()>& elemUpdate = nullptr);
void svg_icon(const char* id, const std::string& svgPath, bool isHighlighted = false, const std::function<void()>& 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<void()>& 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<void()>& elemUpdate = nullptr);
bool rotate_wheel(const char* id, double* angle, float size = BIG_BUTTON_SIZE, const std::function<void()>& elemUpdate = nullptr);
void top_to_bottom_window_popup_layout(const Clay_ElementId& localID, Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& elemUpdate);
void left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& elemUpdate);
void left_to_right_line_layout(const std::function<void()>& elemUpdate);
void left_to_right_line_centered_layout(const std::function<void()>& elemUpdate);
bool selectable_button(const char* id, const std::function<void(SelectionHelper&, bool)>& 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<void(float scrollContentHeight, float containerHeight, float& scrollAmount)>& elemUpdate, bool maxScrollBugWorkaround = true);
void scroll_bar_many_entries_area(const char* id, float entryHeight, size_t entryCount, bool clipHorizontal, const std::function<void(size_t elementIndex, bool listHovered)>& entryUpdate, const std::function<void(float scrollContentHeight, float containerHeight, float& scrollAmount)>& elemUpdate = nullptr);
void set_post_callback_func(const std::function<void()>& f);
bool input_color_component_255(const char* id, float* val, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
bool input_text(const char* id, std::string* val, bool updateEveryEdit = true, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
int16_t zIndex = 0;
template <typename ElementType, typename... Args> ElementType* element(const char* id, const Args&... a) {
push_id(id);
ElementType* elem = insert_element<ElementType>();
elem->layout(a...);
pop_id();
return elem;
}
bool radio_button(const char* id, bool val, const std::function<void()>& elemUpdate = nullptr);
bool radio_button_field(const char* id, std::string_view name, bool val, const std::function<void()>& elemUpdate = nullptr);
DefaultStringArena strArena;
void checkbox_field(const char* id, std::string_view name, bool* val, const std::function<void()>& elemUpdate = nullptr);
void checkbox(const char* id, bool* val, const std::function<void()>& elemUpdate = nullptr);
void tab_list(const char* id, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate = nullptr);
void input_path(const char* id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void(SelectionHelper&)>& 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<void(SelectionHelper&)>& elemUpdate = nullptr);
void dropdown_select(const char* id, size_t* val, const std::vector<std::string>& selections, float width = 200.0f, const std::function<void()>& hoverboxElemUpdate = nullptr);
void paint_circle_popup_menu(const char* id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function<void()>& elemUpdate = nullptr);
void list_popup_menu(const char* id, Vector2f popupPos, const std::function<void()>& 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 <typename NewElement> NewElement* insert_element() {
auto [it, inserted] = elements.emplace(idStack, ElementContainer());
auto& container = it->second;
if(inserted)
container = {std::make_unique<NewElement>()};
container = {std::make_unique<NewElement>(*this)};
container.elem->zIndex = zIndex;
container.isUsedThisFrame = true;
return static_cast<NewElement*>(it->second.elem.get());
}
template <typename T> 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<T&>(it->second.extra);
}
template <typename T> T& insert_any_with_id(int64_t id, const T& def) {
push_id(id);
T& toRet = insert_any(def);
pop_id();
return toRet;
}
template <typename T> T& insert_any_with_function(std::function<T()> 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<T&>(it->second.extra);
}
template <typename T> T& insert_any_with_id_with_function(int64_t id, std::function<T()> funcToRun) {
push_id(id);
T& toRet = insert_any_with_function(funcToRun);
pop_id();
return toRet;
}
template <typename T> bool input_generic(const char* id, T* val, const std::function<std::optional<T>(const std::string&)>& fromStr, const std::function<std::string(const T&)>& toStr, bool singleLine, bool updateEveryEdit, const InputManager::TextInputProperties& inputProps, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr) {
bool isUpdating = false;
push_id(id);
isUpdating = insert_element<TextBox<T>>()->update(*io, val, fromStr, toStr, singleLine, updateEveryEdit, inputProps, elemUpdate);
pop_id();
return isUpdating;
}
template <typename T> bool input_scalar(const char* id, T* val, T min, T max, int decimalPrecision = 0, const std::function<void(SelectionHelper&)>& 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<T>())
textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_SIGNED;
if(std::is_floating_point<T>())
textInputProps.androidInputType |= InputManager::AndroidInputType::ANDROIDTEXT_TYPE_NUMBER_FLAG_DECIMAL;
return input_generic<T>(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<T>()) {
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<void(SelectionHelper&)>& elemUpdate);
template <typename TContainer, typename T> 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<void(SelectionHelper&)>& 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<T>("field", &(*val)[i], min, max, decimalPrecision, elemUpdate);
pop_id();
}
});
pop_id();
return isUpdating;
}
template <typename T> bool input_color_hex(const char* id, T* val, bool selectAlpha, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr) {
return input_generic<T>(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<int>(std::clamp<float>(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 <typename T> bool color_picker(const char* id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
bool isUpdating = insert_element<ColorPicker<T>>()->update(*io, val, selectAlpha, elemUpdate);
pop_id();
return isUpdating;
}
template <typename T> bool slider_scalar(const char* id, T* val, T min, T max, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
bool isUpdating = insert_element<NumberSlider<T>>()->update(*io, val, min, max, elemUpdate);
pop_id();
return isUpdating;
}
template <typename T> bool input_scalar_field(const char* id, std::string_view name, T* val, T min, T max, int decimalPrecision = 0, const std::function<void(SelectionHelper&)>& 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 <typename T> bool slider_scalar_field(const char* id, std::string_view name, T* val, T min, T max, int decimalPrecision = 0, const std::function<void()>& 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 <typename T> bool big_color_button(const char* id, T* val, bool isSelected = false, const std::function<void()>& 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<T>(id, val, isSelected, elemUpdate);
}
return toRet;
}
template <typename T> bool color_button(const char* id, T* val, bool isSelected = false, const std::function<void()>& 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<Clay_Color>(*val),
.cornerRadius = CLAY_CORNER_RADIUS(5),
}) {}
if(elemUpdate)
elemUpdate();
}, GUIStuff::SelectableButton::DrawType::TRANSPARENT_BORDER, isSelected);
}
template <typename T> bool color_button_big(const char* id, T* val, bool isSelected = false, const std::function<void()>& 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<Clay_Color>(*val),
.cornerRadius = CLAY_CORNER_RADIUS(5),
}) {}
if(elemUpdate)
elemUpdate();
}, GUIStuff::SelectableButton::DrawType::TRANSPARENT_BORDER, isSelected);
}
return toRet;
}
template <typename T> bool color_picker_button(const char* id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
bool isUpdating = false;
push_id(id);
bool& isOpen = insert_any_with_id<bool>(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 <typename T> 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 <typename T> bool color_picker_button_field(const char* id, std::string_view name, T* val, bool selectAlpha, const std::function<void()>& 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<void()> postCallbackFunc;
Clay_Context* clayInstance;
Clay_Arena clayArena;
Clay_RenderCommandArray renderCommands;
bool setToLayout;
};
}

View file

@ -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<std::string, TextData> 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<FontData>& 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;

View file

@ -67,6 +67,8 @@ class TextBox {
TextBox();
bool inputChangedTextBox = false;
std::function<void()> onChange;
std::function<void()> onUserTextEdit;
enum class Movement {
NOWHERE,

File diff suppressed because it is too large Load diff

View file

@ -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<void()>& 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<World>& w);
void general_settings_inner_gui();
void center_obstructing_window_gui(const char* id, Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& innerContent);
struct ClosePopupData {
struct CloseWorldData {
@ -229,7 +233,6 @@ class Toolbar {
std::vector<ExtensionFilter> extensionFiltersComplete;
std::vector<std::string> extensionFilters;
std::vector<std::filesystem::path> entries;
bool refreshEntries = true;
std::filesystem::path currentSearchPath;
std::filesystem::path currentSelectedPath;
std::string fileName;
@ -251,9 +254,6 @@ class Toolbar {
std::unique_ptr<skia::textlayout::Paragraph> 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;