infinipaint/src/GUIStuff/GUIManager.hpp

388 lines
19 KiB
C++
Raw Normal View History

2025-07-06 20:43:53 +03:00
#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"
2025-07-06 20:43:53 +03:00
#include "Elements/TextBox.hpp"
2025-12-20 16:38:51 +03:00
#include "Elements/TreeListing.hpp"
2025-07-06 20:43:53 +03:00
#include <any>
#include "GUIManagerID.hpp"
#include <filesystem>
#include <type_traits>
#include <clay.h>
#include <modules/skparagraph/include/Paragraph.h>
2025-07-06 20:43:53 +03:00
using namespace Eigen;
namespace GUIStuff {
class GUIManager {
public:
constexpr static float BIG_BUTTON_SIZE = 30;
2025-11-01 20:47:34 +03:00
constexpr static float SMALL_BUTTON_SIZE = 20;
2025-07-06 20:43:53 +03:00
struct ElementContainer {
std::unique_ptr<Element> elem;
std::any extra;
bool isUsedThisFrame;
2025-07-06 20:43:53 +03:00
};
GUIManager();
void begin();
void end();
void draw(SkCanvas* canvas);
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();
2026-01-07 12:03:59 +03:00
bool input_text_field(const char* id, std::string_view name, std::string* val, bool updateEveryEdit = true, const std::function<void(SelectionHelper&)>& elemUpdate = nullptr);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
bool rotate_wheel(const char* id, double* angle, float size = BIG_BUTTON_SIZE, const std::function<void()>& elemUpdate = nullptr);
2025-09-28 16:32:57 +03:00
2026-02-27 16:16:51 +03:00
void top_to_bottom_window_popup_layout(const Clay_ElementId& localID, Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& elemUpdate);
2025-07-06 20:43:53 +03:00
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);
2025-07-06 20:43:53 +03:00
bool selectable_button(const char* id, const std::function<void(SelectionHelper&, bool)>& elemUpdate, GUIStuff::SelectableButton::DrawType drawType, bool isSelected, bool onClick = false);
2025-07-06 20:43:53 +03:00
void push_id(int64_t id);
2026-01-07 12:03:59 +03:00
void push_id(const char* id);
2025-07-06 20:43:53 +03:00
void pop_id();
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
void tree_listing(const char* id, NetworkingObjects::NetObjID rootObjID, const TreeListing::DisplayData& displayData, TreeListing::SelectionData& selectionData);
2025-12-20 16:38:51 +03:00
2026-02-27 16:16:51 +03:00
void obstructing_window(const Clay_ElementId& elementID);
2025-07-06 20:43:53 +03:00
template <typename NewElement> NewElement* insert_element() {
auto [it, inserted] = elements.emplace(idStack, ElementContainer());
auto& container = it->second;
2025-07-06 20:43:53 +03:00
if(inserted)
container = {std::make_unique<NewElement>()};
container.isUsedThisFrame = true;
2025-07-06 20:43:53 +03:00
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;
2025-07-06 20:43:53 +03:00
if(inserted)
it->second.extra = def;
container.isUsedThisFrame = true;
2025-07-06 20:43:53 +03:00
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;
}
2025-07-06 20:43:53 +03:00
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 std::function<void(SelectionHelper&)>& elemUpdate = nullptr) {
2025-11-05 14:55:22 +03:00
bool isUpdating = false;
2025-07-06 20:43:53 +03:00
push_id(id);
2025-11-05 14:55:22 +03:00
isUpdating = insert_element<TextBox<T>>()->update(*io, val, fromStr, toStr, singleLine, updateEveryEdit, elemUpdate);
2025-07-06 20:43:53 +03:00
pop_id();
2025-11-05 14:55:22 +03:00
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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) {
2025-11-05 14:55:22 +03:00
return input_generic<T>(id, val,
[&](const std::string& a) {
2025-07-06 20:43:53 +03:00
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();
2025-09-06 19:06:30 +03:00
}, true, false, elemUpdate);
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
bool input_scalar(const char* id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function<void(SelectionHelper&)>& elemUpdate);
2025-07-06 20:43:53 +03:00
2026-01-07 12:03:59 +03:00
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;
2025-07-06 20:43:53 +03:00
push_id(id);
left_to_right_line_layout([&]() {
text_label(name);
2026-01-07 12:03:59 +03:00
for(size_t i = 0; i < elemCount; i++) {
push_id(i);
isUpdating |= input_scalar<T>("field", &(*val)[i], min, max, decimalPrecision, elemUpdate);
pop_id();
}
2025-07-06 20:43:53 +03:00
});
pop_id();
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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) {
2025-07-06 20:43:53 +03:00
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();
},
2025-09-06 19:06:30 +03:00
true, false, elemUpdate);
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
template <typename T> bool color_picker(const char* id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
2025-07-06 20:43:53 +03:00
push_id(id);
bool isUpdating = insert_element<ColorPicker<T>>()->update(*io, val, selectAlpha, elemUpdate);
2025-07-06 20:43:53 +03:00
pop_id();
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
template <typename T> bool slider_scalar(const char* id, T* val, T min, T max, const std::function<void()>& elemUpdate = nullptr) {
2025-07-06 20:43:53 +03:00
push_id(id);
2025-11-05 14:55:22 +03:00
bool isUpdating = insert_element<NumberSlider<T>>()->update(*io, val, min, max, elemUpdate);
2025-07-06 20:43:53 +03:00
pop_id();
2025-11-05 14:55:22 +03:00
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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) {
2025-11-05 14:55:22 +03:00
bool isUpdating = false;
2025-07-06 20:43:53 +03:00
left_to_right_line_layout([&]() {
text_label(name);
2025-11-05 14:55:22 +03:00
isUpdating = input_scalar(id, val, min, max, decimalPrecision, elemUpdate);
2025-07-06 20:43:53 +03:00
});
2025-11-05 14:55:22 +03:00
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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) {
2025-11-05 14:55:22 +03:00
bool isUpdating = false;
2025-07-06 20:43:53 +03:00
push_id(id);
left_to_right_line_layout([&]() {
text_label(name);
2025-11-05 14:55:22 +03:00
isUpdating |= input_scalar("0", val, min, max, decimalPrecision, nullptr);
2025-07-06 20:43:53 +03:00
});
2025-11-05 14:55:22 +03:00
isUpdating |= slider_scalar("1", val, min, max, elemUpdate);
2025-07-06 20:43:53 +03:00
pop_id();
2025-11-05 14:55:22 +03:00
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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;
}
2026-01-07 12:03:59 +03:00
template <typename T> bool color_button(const char* id, T* val, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr) {
2025-07-06 20:43:53 +03:00
return selectable_button(id, [&](SelectionHelper& s, bool iS) {
CLAY_AUTO_ID({.layout = {
2025-07-06 20:43:53 +03:00
.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);
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
template <typename T> bool color_button_big(const char* id, T* val, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr) {
2025-11-24 18:23:53 +03:00
bool toRet = false;
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(BIG_BUTTON_SIZE)}}}) {
2025-11-24 18:23:53 +03:00
toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) {
CLAY_AUTO_ID({.layout = {
2025-11-24 18:23:53 +03:00
.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;
}
2026-01-07 12:03:59 +03:00
template <typename T> bool color_picker_button(const char* id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
bool isUpdating = false;
2025-07-06 20:43:53 +03:00
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) } } }) {
2025-07-06 20:43:53 +03:00
clicked = color_button("0", val, isOpen, elemUpdate);
2025-12-16 23:06:46 +03:00
if(clicked)
isOpen = true;
if(isOpen) {
2026-02-27 16:16:51 +03:00
top_to_bottom_window_popup_layout(CLAY_ID_LOCAL("COLOR PICKER"), CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&]() {
2025-12-16 23:06:46 +03:00
isUpdating = color_picker_items("c", val, selectAlpha);
if(io->mouse.leftClick && !Clay_Hovered() && !clicked)
isOpen = false;
});
}
2025-07-06 20:43:53 +03:00
}
pop_id();
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
bool font_picker(const char* id, std::string* fontName);
2025-11-08 20:50:23 +03:00
2026-01-07 12:03:59 +03:00
template <typename T> bool color_picker_items(const char* id, T* val, bool selectAlpha, float fixedPickerWidth = 0.0f) {
bool isUpdating = false;
2025-07-06 20:43:53 +03:00
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);
}
}
2025-07-06 20:43:53 +03:00
left_to_right_line_layout([&]() {
text_label("R");
isUpdating |= input_color_component_255("r", &(*val)[0]);
2025-07-06 20:43:53 +03:00
text_label("G");
isUpdating |= input_color_component_255("g", &(*val)[1]);
2025-07-06 20:43:53 +03:00
text_label("B");
isUpdating |= input_color_component_255("b", &(*val)[2]);
2025-07-06 20:43:53 +03:00
if(selectAlpha) {
text_label("A");
isUpdating |= input_color_component_255("a", &(*val)[3]);
2025-07-06 20:43:53 +03:00
}
});
left_to_right_line_layout([&]() {
text_label("Hex");
isUpdating |= input_color_hex("h", val, selectAlpha);
2025-07-06 20:43:53 +03:00
});
pop_id();
return isUpdating;
2025-07-06 20:43:53 +03:00
}
2026-01-07 12:03:59 +03:00
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;
2025-07-06 20:43:53 +03:00
left_to_right_line_layout([&]() {
isUpdating = color_picker_button(id, val, selectAlpha, elemUpdate);
2025-07-06 20:43:53 +03:00
text_label(name);
});
return isUpdating;
2025-07-06 20:43:53 +03:00
}
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;
Clay_Context* clayInstance;
Clay_Arena clayArena;
Clay_RenderCommandArray renderCommands;
};
}