Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,2 @@
#define CLAY_IMPLEMENTATION
#include <clay.h>

View file

@ -0,0 +1,90 @@
#include "CheckBox.hpp"
#include <include/core/SkPath.h>
#include <iostream>
namespace GUIStuff {
void CheckBox::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
CLAY({
.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);
if(isTicked != newIsTicked) {
hoverAnimation2 = CHECKBOX_ANIMATION_TIME;
isTicked = newIsTicked;
}
if(elemUpdate)
elemUpdate();
}
}
void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
auto bb = get_bb(command);
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
canvas->scale(bb.width(), bb.height());
canvas->translate(0.5f, 0.5f);
SkPaint p;
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);
canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p);
}
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->backColor3));
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.15f);
canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p);
}
if(isTicked) {
SkPaint checkP;
checkP.setColor4f(io.theme->backColor2);
checkP.setStyle(SkPaint::kFill_Style);
checkP.setStrokeWidth(0.12);
checkP.setStrokeCap(SkPaint::kRound_Cap);
checkP.setStrokeJoin(SkPaint::kRound_Join);
SkPath checkPath;
Vector2f checkP1{(4.5/17.0) - 0.5, (8.5/17.0) - 0.5};
Vector2f checkP2{(7.5/17.0) - 0.5, (12.0/17.0) - 0.5};
Vector2f checkP3{(12.5/17.0) - 0.5, (6.0/17.0) - 0.5};
Vector2f rectP1{-0.2f, -0.2f};
Vector2f rectP2{-0.2f, 0.2f};
Vector2f rectP3{ 0.2f, 0.2f};
Vector2f rectP4{ 0.2f, -0.2f};
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));
std::array<Vector2f, 5> points;
points[0] = lerp_vec(checkP1, rectP1, lerpTime2);
points[1] = lerp_vec(checkP2, rectP2, lerpTime2);
points[2] = lerp_vec(checkP3, rectP3, lerpTime2);
points[3] = lerp_vec(checkP2, rectP4, lerpTime2);
points[4] = points[0];
checkPath.moveTo(points[0].x(), points[0].y());
for(unsigned i = 1; i < 5; i++)
checkPath.lineTo(points[i].x(), points[i].y());
checkPath.close();
canvas->drawPath(checkPath, checkP);
checkP.setStyle(SkPaint::kStroke_Style);
canvas->drawPath(checkPath, checkP);
}
canvas->restore();
}
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class CheckBox : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
SelectionHelper selection;
private:
static constexpr float CHECKBOX_ANIMATION_TIME = 0.3;
bool isTicked = false;
float hoverAnimation2 = 0.0;
};
}

View file

@ -0,0 +1,285 @@
#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:
void update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function<void()>& elemUpdate) {
data = newData;
selectAlpha = newSelectAlpha;
if(!data)
return;
force_update_colorpicker();
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(bb.width()) }
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
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);
}
}
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(savedHsv);
}
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(savedHsv);
}
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;
}
if(!selection.held) {
modifyingSv = false;
modifyingHue = false;
modifyingAlpha = false;
}
if(elemUpdate)
elemUpdate();
}
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
if(!data)
return;
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));
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.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));
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()));
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();
}
private:
void force_update_colorpicker() {
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(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* data = nullptr;
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();
}
}

View file

@ -0,0 +1,63 @@
#include "Element.hpp"
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.1f, 0.4f, 0.8f, 1.0f};
theme->fillColor2 = {0.4f, 0.5f, 0.9f, 1.0f};
theme->fillColor3 = {1.0f, 0.0f, 0.0f, 1.0f};
theme->fillColor4 = {1.0f, 1.0f, 0.0f, 1.0f};
theme->backColor1 = {0.0f, 0.0f, 0.0f, 0.9f};
theme->backColor2 = {0.15f, 0.15f, 0.15f, 1.0f};
theme->backColor3 = {0.22f, 0.22f, 0.22f, 1.0f};
theme->frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f};
theme->frontColor2 = {0.8f, 0.8f, 0.8f, 1.0f};
return theme;
}
SCollision::AABB<float> Element::get_bb(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) {
hovered = isHovering;
clicked = false;
justUnselected = false;
bool oldSelected = selected;
if(isLeftClick) {
if(hovered) {
selected = true;
clicked = true;
held = true;
}
else
selected = false;
}
else if(!isLeftHeld)
held = false;
if(!selected && oldSelected)
justUnselected = true;
}
}

View file

@ -0,0 +1,138 @@
#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 <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
#include <nlohmann/json.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:
StringArena() {
stringArena = std::vector<char>(S);
}
void reset() {
stringArenaIndex = 0;
}
const char* insert_string_into_arena(const std::string& str) {
size_t strSize = str.size();
if(stringArenaIndex + strSize + 1 > S)
throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string");
char* startPtr = &stringArena[stringArenaIndex];
for(size_t i = 0; i < strSize; i++)
stringArena[stringArenaIndex++] = str[i];
stringArena[stringArenaIndex++] = ' ';
return startPtr;
}
Clay_String std_str_to_clay_str(const std::string& str) {
return Clay_String{.length = static_cast<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
}
private:
std::vector<char> stringArena;
size_t stringArenaIndex;
};
typedef StringArena<4000000> DefaultStringArena;
SkFont get_setup_skfont();
struct Theme {
SkColor4f fillColor1 = {0.5f, 0.6f, 1.0f, 1.0f};
SkColor4f fillColor2 = {0.4f, 0.5f, 0.9f, 1.0f};
SkColor4f fillColor3 = {1.0f, 0.0f, 0.0f, 1.0f};
SkColor4f fillColor4 = {1.0f, 1.0f, 0.0f, 1.0f};
SkColor4f backColor1 = {1.0f, 1.0f, 1.0f, 0.9f};
SkColor4f backColor2 = {0.95f, 0.95f, 0.95f, 1.0f};
SkColor4f backColor3 = {0.9f, 0.9f, 0.9f, 1.0f};
SkColor4f frontColor1 = {0.0f, 0.0f, 0.0f, 1.0f};
SkColor4f frontColor2 = {0.1f, 0.1f, 0.1f, 1.0f};
float hoverExpandTime = 0.1f;
uint16_t childGap1 = 10;
uint16_t padding1 = 10;
float windowCorners1 = 10;
uint16_t windowBorders1 = 2;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, fillColor3, fillColor4, backColor1, backColor2, backColor3, frontColor1, frontColor2, hoverExpandTime, childGap1, padding1, windowCorners1, windowBorders1)
};
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 home = false;
bool del = false;
bool backspace = false;
bool enter = false;
bool selectAll = false;
bool copy = false;
bool paste = false;
bool cut = false;
} key;
struct {
std::function<std::string()> textInFunc;
std::optional<std::string> textOut;
} clipboard;
SkFont get_font(float fSize) const;
uint16_t fontSize = 20;
sk_sp<SkTypeface> textTypeface;
std::string textInput;
bool hoverObstructed = false;
bool acceptingTextInput = false;
float deltaTime = 0.0f;
sk_sp<SkFontMgr> textFontMgr;
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);
bool held = false;
bool hovered = false;
bool selected = false;
bool clicked = false;
bool justUnselected = false;
};
class Element {
public:
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) = 0;
virtual ~Element() = default;
protected:
static SCollision::AABB<float> get_bb(Clay_RenderCommand* command);
};
}

View file

@ -0,0 +1,79 @@
#include "MovableTabList.hpp"
#include "Helpers/ConvertVec.hpp"
#include <iostream>
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;
}
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(45)},
.padding = CLAY_PADDING_ALL(2),
.childGap = 8,
.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);
if(elemUpdate)
elemUpdate();
for(size_t i = 0; i < buttons.size(); i++) {
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_FIT(250), .height = CLAY_SIZING_GROW(0)},
}
}) {
buttons[i].update(io, true, true, [&](SelectionHelper& s, bool iS) {
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = CLAY_PADDING_ALL(4),
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
}
}) {
if(!tabNames[i].first.empty()) {
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(25), .height = CLAY_SIZING_FIXED(25)},
.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({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(25), .height = CLAY_SIZING_FIXED(25)},
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
}
}) {
closeButtons[i].update(io, false, false, [&](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) {
}
}

View file

@ -0,0 +1,21 @@
#pragma once
#include "Element.hpp"
#include "SelectableButton.hpp"
#include "SVGIcon.hpp"
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) 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;
};
}

View file

@ -0,0 +1,91 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
template <typename T> class NumberSlider : public Element {
public:
void update(UpdateInputData& io, T* dataPtr, T minNew, T maxNew, const std::function<void()>& elemUpdate) {
data = dataPtr;
min = minNew;
max = maxNew;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(100), .height = CLAY_SIZING_FIXED(15)}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
if(selection.held && data) {
float fracPosOnSlider = (io.mouse.pos.x() - bb.min.x()) / bb.width();
*data = std::clamp<T>(std::lerp<double>(min, max, fracPosOnSlider), min, max);
}
if(elemUpdate)
elemUpdate();
}
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override {
if(!data)
return;
bb = get_bb(command);
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, selection.hovered, 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 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>(*data, max, min) * bb.width();
SkRect barFull = SkRect::MakeXYWH(0.0f, yChange, holderPos, holderRadius);
SkRect barEmpty = SkRect::MakeXYWH(holderPos, yChange, bb.width() - holderPos, holderRadius);
SkPaint barFullP;
barFullP.setColor(convert_vec4<SkColor4f>(io.theme->fillColor1));
canvas->drawRoundRect(barFull, 5.0f, 5.0f, barFullP);
SkPaint barEmptyP;
barEmptyP.setColor(convert_vec4<SkColor4f>(io.theme->backColor2));
canvas->drawRoundRect(barEmpty, 5.0f, 5.0f, barEmptyP);
canvas->translate(holderPos, bb.height() * 0.5f);
SkRect holderRect = SkRect::MakeLTRB(-holderRadius, -holderHeight, holderRadius, holderHeight);
SkPaint holderBorderP;
holderBorderP.setStyle(SkPaint::kStroke_Style);
holderBorderP.setStrokeWidth(3.0f);
holderBorderP.setColor(convert_vec4<SkColor4f>(io.theme->fillColor1));
canvas->drawRoundRect(holderRect, holderRadius, holderRadius, holderBorderP);
SkPaint holderP;
holderP.setColor(convert_vec4<SkColor4f>(io.theme->backColor2));
canvas->drawRoundRect(holderRect, holderRadius, holderRadius, holderP);
canvas->restore();
}
private:
SCollision::AABB<float> bb;
SelectionHelper selection;
T* data = nullptr;
T min = 0.0;
T max = 1.0;
float hoverAnimation = 0.0;
float holdAnimation = 0.0;
};
}

View file

@ -0,0 +1,193 @@
#include "PaintCircleMenu.hpp"
#include "Helpers/ConvertVec.hpp"
namespace GUIStuff {
constexpr float ROTATE_START = 125.0f;
constexpr float PALETTE_START = 100.0f;
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(UpdateInputData& io, const Data& data, const std::function<void()>& elemUpdate) {
CLAY({.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());
bool rotateHovered = false;
if(data.newRotationAngle) {
rotateBarSelect.update(isHoveredNoObstruction && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END, io.mouse.leftClick, io.mouse.leftHeld);
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
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;
}
}
}
}
else
*data.newRotationAngle = data.currentRotationAngle;
rotateHovered = rotateBarSelect.hovered;
}
bool colorHovered = false;
if(!data.palette.empty() && data.selectedColor) {
colorBarSelect.update(isHoveredNoObstruction && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START, io.mouse.leftClick, io.mouse.leftHeld);
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();
}
}
colorHovered = colorBarSelect.hovered;
}
if(rotateHovered || colorHovered)
io.hoverObstructed = true;
if(elemUpdate)
elemUpdate();
}
d = data;
}
void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
bb = get_bb(command);
canvas->save();
canvas->translate(bb.center().x(), bb.center().y());
draw_rotate_bar(canvas, io);
draw_palette_bar(canvas, io);
canvas->restore();
}
void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io) {
if(!d.newRotationAngle)
return;
double rotationAngle = d.currentRotationAngle;
float rotateBarMiddleRadius = (CIRCLE_END + ROTATE_START) / 2.0f;
SkPaint rotateBarFill(io.theme->backColor2);
rotateBarFill.setStroke(true);
rotateBarFill.setStrokeWidth(CIRCLE_END - ROTATE_START);
canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill);
if(rotateBarSelect.hovered) {
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);
p.setStrokeWidth(2.0f);
canvas->drawLine(lineDir.x() * ROTATE_START, lineDir.y() * ROTATE_START, lineDir.x() * CIRCLE_END, lineDir.y() * CIRCLE_END, p);
}
}
SkPaint rotateBarHolderFill;
if(rotateBarSelect.held)
rotateBarHolderFill.setColor4f(io.theme->fillColor1);
else if(rotateBarSelect.hovered)
rotateBarHolderFill.setColor4f(io.theme->fillColor2);
else
rotateBarHolderFill.setColor4f(io.theme->frontColor2);
float rotateBarHolderRadius = (CIRCLE_END - ROTATE_START) / 2.0f;
Vector2f rotateBarHolderPos{std::cos(rotationAngle) * rotateBarMiddleRadius, -std::sin(rotationAngle) * rotateBarMiddleRadius};
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill);
SkPaint rotateBarHolderOutline(io.theme->frontColor1);
rotateBarHolderOutline.setStroke(true);
rotateBarHolderOutline.setStrokeWidth(2.0f);
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderOutline);
SkPaint rotateBarOutline(io.theme->frontColor2);
rotateBarOutline.setStroke(true);
rotateBarOutline.setStrokeWidth(2.0f);
canvas->drawCircle(0.0f, 0.0f, ROTATE_START, rotateBarOutline);
canvas->drawCircle(0.0f, 0.0f, CIRCLE_END, rotateBarOutline);
}
void PaintCircleMenu::draw_palette_bar(SkCanvas* canvas, UpdateInputData& io) {
if(d.palette.empty() || !d.selectedColor)
return;
float middleRadius = (PALETTE_START + ROTATE_START) / 2.0f;
double colorDistribution = std::numbers::pi * 2.0 / d.palette.size();
SkRect colorOval = SkRect::MakeLTRB(-middleRadius, -middleRadius, middleRadius, middleRadius);
for(unsigned i = 0; i < d.palette.size(); i++) {
double angleStart = i * colorDistribution;
double angleEnd = (i + 1) * colorDistribution;
SkPaint colorP(SkColor4f{d.palette[i].x(), d.palette[i].y(), d.palette[i].z(), 1.0f});
colorP.setStroke(true);
colorP.setStrokeWidth(ROTATE_START - PALETTE_START);
canvas->drawArc(colorOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false, colorP);
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
SkPaint lineP(io.theme->frontColor2);
lineP.setStrokeWidth(2.0f);
lineP.setStroke(true);
canvas->drawLine(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START, lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START, lineP);
}
SkPaint barOutline(io.theme->frontColor2);
barOutline.setStroke(true);
barOutline.setStrokeWidth(2.0f);
canvas->drawCircle(0.0f, 0.0f, PALETTE_START, barOutline);
canvas->drawCircle(0.0f, 0.0f, ROTATE_START, barOutline);
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) {
SkPath selectionPath;
double angleStart = colorSelectionIndex * colorDistribution;
double angleEnd = (colorSelectionIndex + 1) * colorDistribution;
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
Vector2f lineDir2{std::cos(angleEnd), std::sin(angleEnd)};
SkPaint lineP(io.theme->fillColor2);
lineP.setStrokeWidth(5.0f);
lineP.setStroke(true);
lineP.setStrokeJoin(SkPaint::kRound_Join);
selectionPath.moveTo(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START);
selectionPath.lineTo(lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START);
selectionPath.arcTo(endOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false);
selectionPath.lineTo(lineDir2.x() * PALETTE_START, lineDir2.y() * PALETTE_START);
selectionPath.arcTo(startOval, angleEnd * 180.0 / std::numbers::pi, (angleStart - angleEnd) * 180.0 / std::numbers::pi, false);
selectionPath.close();
canvas->drawPath(selectionPath, lineP);
}
for(unsigned i = 0; i < d.palette.size(); i++) {
if(d.palette[i].x() == d.selectedColor->x() && d.palette[i].y() == d.selectedColor->y() && d.palette[i].z() == d.selectedColor->z()) {
SkPath selectionPath;
double angleStart = i * colorDistribution;
double angleEnd = (i + 1) * colorDistribution;
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
Vector2f lineDir2{std::cos(angleEnd), std::sin(angleEnd)};
SkPaint lineP(io.theme->fillColor1);
lineP.setStrokeWidth(5.0f);
lineP.setStroke(true);
lineP.setStrokeJoin(SkPaint::kRound_Join);
selectionPath.moveTo(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START);
selectionPath.lineTo(lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START);
selectionPath.arcTo(endOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false);
selectionPath.lineTo(lineDir2.x() * PALETTE_START, lineDir2.y() * PALETTE_START);
selectionPath.arcTo(startOval, angleEnd * 180.0 / std::numbers::pi, (angleStart - angleEnd) * 180.0 / std::numbers::pi, false);
selectionPath.close();
canvas->drawPath(selectionPath, lineP);
break;
}
}
}
}

View file

@ -0,0 +1,26 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class PaintCircleMenu : public Element {
public:
struct Data {
double currentRotationAngle = 0.0;
double* newRotationAngle = nullptr;
Vector4f* selectedColor = nullptr;
std::vector<Vector3f> palette;
};
void update(UpdateInputData& io, const Data& data, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
private:
void draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io);
void draw_palette_bar(SkCanvas* canvas, UpdateInputData& io);
Data d;
SCollision::AABB<float> bb;
SelectionHelper rotateBarSelect;
SelectionHelper colorBarSelect;
unsigned colorSelectionIndex = 0;
};
}

View file

@ -0,0 +1,57 @@
#include "RadioButton.hpp"
namespace GUIStuff {
void RadioButton::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
CLAY({
.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);
if(isTicked != newIsTicked) {
hoverAnimation2 = RADIOBUTTON_ANIMATION_TIME;
isTicked = newIsTicked;
}
if(elemUpdate)
elemUpdate();
}
}
void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
auto bb = get_bb(command);
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
canvas->scale(bb.width(), bb.height());
canvas->translate(0.5f, 0.5f);
if(isTicked) {
SkPaint p;
p.setColor4f(convert_vec4<SkColor4f>(io.theme->fillColor1));
p.setStyle(SkPaint::kFill_Style);
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);
SkPaint innerCircleP;
innerCircleP.setColor4f(convert_vec4<SkColor4f>(io.theme->backColor2));
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 innerCircleRadius = lerp_vec(0.3f, 0.2f, lerpTime2);
canvas->drawCircle(0.0f, 0.0f, innerCircleRadius, innerCircleP);
}
else {
SkPaint p;
p.setColor4f(convert_vec4<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->backColor3));
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.15f);
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);
}
canvas->restore();
}
}

View file

@ -0,0 +1,19 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class RadioButton : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
SelectionHelper selection;
private:
static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3;
bool isTicked = false;
float hoverAnimation2 = 0.0;
};
}

View file

@ -0,0 +1,67 @@
#include "SVGIcon.hpp"
#include "Helpers/ConvertVec.hpp"
#include <modules/svg/include/SkSVGNode.h>
#include <include/core/SkStream.h>
#include <include/core/SkColorFilter.h>
#include <include/core/SkColorSpace.h>
#include <iostream>
namespace GUIStuff {
void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate) {
auto findSVGData = io.svgData.find(newSvgPath);
if(findSVGData == io.svgData.end()) {
auto stream = SkStream::MakeFromFile(newSvgPath.c_str());
if(!stream) {
std::cout << "[SVGIcon::update] Could not open file " << newSvgPath << std::endl;
io.svgData[newSvgPath] = nullptr;
svgDom = nullptr;
}
auto newSvgDom = SkSVGDOM::Builder().make(*stream);
if(!newSvgDom) {
io.svgData[newSvgPath] = nullptr;
svgDom = nullptr;
std::cout << "[SVGIcon::update] Could not parse SVG " << newSvgPath << std::endl;
}
else {
io.svgData[newSvgPath] = newSvgDom;
svgDom = newSvgDom;
}
}
else
svgDom = findSVGData->second;
highlighted = newIsHighlighted;
CLAY({
.layout = {
.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) {
if(!svgDom)
return;
auto bb = get_bb(command);
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
SkRect r = SkRect::MakeXYWH(0.0f, 0.0f, bb.width(), bb.height());
canvas->clipRect(r);
SkPaint blendingPaint;
blendingPaint.setColorFilter(SkColorFilters::Blend(highlighted ? io.theme->frontColor1 : io.theme->frontColor2, SkColorSpace::MakeSRGB(), SkBlendMode::kSrcIn));
canvas->saveLayer(r, &blendingPaint);
canvas->scale(bb.width() / svgDom->containerSize().width(), bb.height() / svgDom->containerSize().height());
svgDom->render(canvas);
canvas->restore();
canvas->restore();
}
}

View file

@ -0,0 +1,15 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class SVGIcon : public Element {
public:
void update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
private:
bool highlighted;
sk_sp<SkSVGDOM> svgDom;
};
}

View file

@ -0,0 +1,55 @@
#include "SelectableButton.hpp"
#include "Helpers/ConvertVec.hpp"
namespace GUIStuff {
void SelectableButton::update(UpdateInputData& io, bool hasBorders, bool hasBorderPadding, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected) {
if(hasBorders) {
SkColor4f borderColor;
if(selection.held)
borderColor = io.theme->fillColor1;
else if(isSelected)
borderColor = io.theme->fillColor2;
else if(selection.hovered)
borderColor = io.theme->fillColor2;
else
borderColor = io.theme->backColor3;
SkColor4f backgroundColor;
if(selection.held)
backgroundColor = io.theme->backColor3;
else if(isSelected)
backgroundColor = io.theme->backColor2;
else if(selection.hovered)
backgroundColor = io.theme->backColor2;
else
backgroundColor = io.theme->backColor1;
CLAY({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = hasBorderPadding ? CLAY_PADDING_ALL(4) : CLAY_PADDING_ALL(0),
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(backgroundColor),
.cornerRadius = CLAY_CORNER_RADIUS(4),
.border = {
.color = convert_vec4<Clay_Color>(borderColor),
.width = CLAY_BORDER_OUTSIDE(4)
}
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
elemUpdate(selection, isSelected);
}
}
else {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
elemUpdate(selection, isSelected);
}
}
void SelectableButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
}
}

View file

@ -0,0 +1,13 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class SelectableButton : public Element {
public:
void update(UpdateInputData& io, bool hasBorders, bool hasBorderPadding, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
SelectionHelper selection;
};
}

View file

@ -0,0 +1,195 @@
#pragma once
#include "Element.hpp"
#include "../../CollabTextBox/CollabTextBox.hpp"
namespace GUIStuff {
template <typename T> class TextBox : public Element {
public:
void 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, const std::function<void()>& elemUpdate) {
data = newData;
fromStr = newFromStr;
toStr = newToStr;
singleLine = newSingleLine;
force_update_textbox(false);
textbox.setFont(SkFont(io.textTypeface, io.fontSize));
textbox.setFontMgr(io.textFontMgr);
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_GROW(0)}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
if(data && selection.selected) {
bool moved = io.key.left || io.key.right || io.key.up || io.key.down || io.key.home;
bool movedHeld = false;
if(io.mouse.leftClick) {
Vector2f textSelectPos = io.mouse.pos - bb.min;
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
moved = true;
}
else if(io.mouse.leftHeld) {
Vector2f textSelectPos = io.mouse.pos - bb.min;
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
movedHeld = true;
}
if(io.key.left)
cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordLeft : CollabTextBox::Movement::kLeft, cur.selectionBeginPos);
if(io.key.right)
cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordRight : CollabTextBox::Movement::kRight, cur.selectionBeginPos);
if(io.key.up && !singleLine)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kUp, cur.selectionBeginPos);
if(io.key.down && !singleLine)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kDown, cur.selectionBeginPos);
if(io.key.home)
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kHome, cur.selectionBeginPos);
if(moved && !io.key.leftShift && !movedHeld)
cur.selectionEndPos = cur.selectionBeginPos;
if(io.key.backspace) {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
else
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kLeft, cur.pos));
}
if(io.key.del) {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
else
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kRight, cur.pos));
}
if(io.key.paste) {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
if(singleLine) {
std::string removedNewlines = io.clipboard.textInFunc();
std::erase(removedNewlines, '\n');
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, removedNewlines);
}
else
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.clipboard.textInFunc());
}
if(io.key.enter) {
if(singleLine) {
std::optional<T> dataToAssign = fromStr(textbox.get_string());
if(dataToAssign)
*data = dataToAssign.value();
force_update_textbox(true);
}
else {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
cur.pos = textbox.insert(cur.pos, "\n");
cur.pos.fParagraphIndex++;
cur.pos.fTextByteIndex = 0;
cur.selectionBeginPos = cur.selectionEndPos = cur.pos;
}
}
if(io.key.copy) {
io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
}
if(io.key.cut) {
io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
}
if(io.key.selectAll) {
cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1;
cur.selectionEndPos.fTextByteIndex = textbox.line(cur.selectionEndPos.fParagraphIndex).size();
cur.pos = cur.selectionEndPos;
cur.selectionBeginPos.fTextByteIndex = 0;
cur.selectionBeginPos.fParagraphIndex = 0;
}
if(!io.textInput.empty()) {
if(cur.selectionBeginPos != cur.selectionEndPos)
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.textInput);
}
io.acceptingTextInput = true;
}
if(data && selection.justUnselected) {
std::optional<T> dataToAssign = fromStr(textbox.get_string());
if(dataToAssign)
*data = dataToAssign.value();
force_update_textbox(true);
}
if(elemUpdate)
elemUpdate();
}
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override {
if(!data)
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->backColor3);
outline.setStroke(true);
outline.setStrokeWidth(2.0f);
canvas->drawRoundRect(r, 2.0f, 2.0f, outline);
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
canvas->translate(bb.min.x(), bb.min.y());
textbox.setFont(SkFont(io.textTypeface, io.fontSize));
textbox.setFontMgr(io.textFontMgr);
textbox.setWidth(singleLine ? 99999999 : bb.width());
CollabTextBox::Editor::PaintOpts paintOpts;
paintOpts.fForegroundColor = io.theme->frontColor1;
paintOpts.fBackgroundColor = {0.0f, 0.0f, 0.0f, 0.0f};
paintOpts.cursorColor = io.theme->fillColor1;
paintOpts.showCursor = selection.selected;
paintOpts.cursor = cur;
textbox.paint(canvas, paintOpts);
canvas->restore();
}
SelectionHelper selection;
private:
void force_update_textbox(bool reallyForce) {
if(data && (*data == oldData) && !reallyForce)
return;
textbox = CollabTextBox::Editor();
cur = CollabTextBox::Cursor();
if(data) {
cur.pos = cur.selectionBeginPos = cur.selectionEndPos = textbox.insert({0, 0}, toStr(*data));
oldData = *data;
}
else {}
//oldData = T();
}
T* data = nullptr;
T oldData;
bool singleLine = true;
std::function<std::optional<T>(const std::string&)> fromStr;
std::function<std::string(const T&)> toStr;
CollabTextBox::Editor textbox;
CollabTextBox::Cursor cur;
SCollision::AABB<float> bb;
};
}

702
src/GUIStuff/GUIManager.cpp Normal file
View file

@ -0,0 +1,702 @@
#include "GUIManager.hpp"
#include <include/core/SkPaint.h>
#include <include/core/SkRRect.h>
#include <include/core/SkFont.h>
#include <include/core/SkFontMetrics.h>
#include <include/core/SkPath.h>
#include "Elements/SVGIcon.hpp"
#include "Elements/SelectableButton.hpp"
#include "Elements/MovableTabList.hpp"
#include "Elements/Element.hpp"
#include <Helpers/ConvertVec.hpp>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <cmath>
namespace GUIStuff {
GUIManager::GUIManager()
{
Clay_SetMaxMeasureTextCacheWordCount(100000);
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) {
GUIManager* window = static_cast<GUIManager*>(userData);
SkFont font = window->io->get_font(config->fontSize);
SkFontMetrics metrics;
font.getMetrics(&metrics);
float nextText = font.measureText(str.chars, str.length, SkTextEncoding::kUTF8, nullptr);
return Clay_Dimensions(nextText, - metrics.fAscent + metrics.fDescent);
}
void GUIManager::begin() {
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);
Clay_UpdateScrollContainers(false, Clay_Vector2(io->mouse.scroll.y(), io->mouse.scroll.y()), io->deltaTime * 2.0f);
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)");
}
void GUIManager::draw(SkCanvas* canvas) {
canvas->save();
canvas->translate(windowPos.x(), windowPos.y());
for(size_t i = 0; i < static_cast<size_t>(renderCommands.length); i++) {
Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i);
Clay_BoundingBox bb = command->boundingBox;
switch(command->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData* config = &command->renderData.rectangle;
SkVector radii[4] = {
{config->cornerRadius.topLeft, config->cornerRadius.topLeft},
{config->cornerRadius.topRight, config->cornerRadius.topRight},
{config->cornerRadius.bottomRight, config->cornerRadius.bottomRight},
{config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft}
};
SkRRect rrect;
rrect.setRectRadii(
SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height),
radii
);
SkPaint paint;
paint.setStyle(SkPaint::kFill_Style);
paint.setColor4f(convert_vec4<SkColor4f>(config->backgroundColor));
canvas->drawRRect(rrect, paint);
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData* config = &command->renderData.text;
SkPaint paint;
paint.setColor4f(convert_vec4<SkColor4f>(config->textColor));
SkFont f = io->get_font(config->fontSize);
SkFontMetrics metrics;
f.getMetrics(&metrics);
canvas->drawSimpleText(config->stringContents.chars, config->stringContents.length, SkTextEncoding::kUTF8, bb.x, (bb.y + bb.height - metrics.fDescent), io->get_font(config->fontSize), paint);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData* config = &command->renderData.border;
SkPaint p;
p.setColor4f(convert_vec4<SkColor4f>(config->color));
p.setStyle(SkPaint::kStroke_Style);
float halfLineWidth = 0.0f;
// Top Left corner
if (config->cornerRadius.topLeft > 0.0f) {
SkPath path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo(bb.x + halfLineWidth, bb.y + config->cornerRadius.topLeft + halfLineWidth);
path.arcTo((bb.x + halfLineWidth), (bb.y + halfLineWidth), (bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth), config->cornerRadius.topLeft);
canvas->drawPath(path, p);
}
// Top border
if (config->width.top > 0.0f) {
SkPath path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
canvas->drawPath(path, p);
}
// Top Right Corner
if (config->cornerRadius.topRight > 0.0f) {
SkPath path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
path.arcTo((bb.x + bb.width - halfLineWidth), (bb.y + halfLineWidth), (bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth), config->cornerRadius.topRight);
canvas->drawPath(path, p);
}
// Right border
if (config->width.right > 0.0f) {
SkPath path;
float lineWidth = config->width.right;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
path.lineTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
canvas->drawPath(path, p);
}
// Bottom Right Corner
if (config->cornerRadius.bottomRight > 0.0f) {
SkPath path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
path.arcTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - halfLineWidth), (bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth), config->cornerRadius.bottomRight);
canvas->drawPath(path, p);
}
// Bottom Border
if (config->width.bottom > 0.0f) {
SkPath path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth));
canvas->drawPath(path, p);
}
// Bottom Left Corner
if (config->cornerRadius.bottomLeft > 0.0f) {
SkPath path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.arcTo((bb.x + halfLineWidth), (bb.y + bb.height - halfLineWidth), (bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth), config->cornerRadius.bottomLeft);
canvas->drawPath(path, p);
}
// Left Border
if (config->width.left > 0.0f) {
SkPath path;
float lineWidth = config->width.left;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth));
path.lineTo((bb.x + halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
canvas->drawPath(path, p);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
canvas->save();
SkRect clipRect = SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height);
canvas->clipRect(clipRect);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
canvas->restore();
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Element* customElement = (Element*)command->renderData.custom.customData;
customElement->clay_draw(canvas, *io, command);
break;
}
default:
break;
}
}
canvas->restore();
}
void GUIManager::top_to_bottom_window_popup_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& elemUpdate) {
CLAY({.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->backColor2), .width = CLAY_BORDER_OUTSIDE(2)}
}) {
if(Clay_Hovered())
io->hoverObstructed = true;
elemUpdate();
}
}
void GUIManager::left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function<void()>& elemUpdate) {
CLAY({.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::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::text_label_size(const std::string& 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(const std::string& 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(const std::string& 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(const std::string& val) {
CLAY({
.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 }));
}
}
bool GUIManager::radio_button(const std::string& 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 std::string& 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();
}
void GUIManager::input_text_field(const std::string& id, const std::string& name, std::string* val, const std::function<void()>& elemUpdate) {
left_to_right_line_layout([&]() {
text_label(name);
input_text(id, val, elemUpdate);
});
}
void GUIManager::input_path_field(const std::string& id, const std::string& name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void()>& elemUpdate) {
left_to_right_line_layout([&]() {
text_label(name);
input_path(id, val, fileTypeRestriction, elemUpdate);
});
}
void GUIManager::checkbox_field(const std::string& id, const std::string& 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 std::string& id, const std::string& 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;
}
void GUIManager::input_color_component_255(const std::string& id, float* val, const std::function<void()>& elemUpdate) {
push_id(id);
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, elemUpdate);
pop_id();
}
void GUIManager::input_text(const std::string& id, std::string* val, const std::function<void()>& elemUpdate) {
push_id(id);
insert_element<TextBox<std::string>>()->update(*io, val,
[&](const std::string& str) {
return str;
},
[&](const std::string& str) {
return str;
}, true, elemUpdate);
pop_id();
}
bool GUIManager::selectable_button(const std::string& id, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool hasBorder, bool hasBorderPadding, bool isSelected) {
push_id(id);
SelectableButton* e = insert_element<SelectableButton>();
e->update(*io, hasBorder, hasBorderPadding, elemUpdate, isSelected);
bool toRet = e->selection.clicked;
pop_id();
return toRet;
}
void GUIManager::svg_icon(const std::string& 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 std::string& id, const std::string& svgPath, bool isSelected, float size, const std::function<void()>& elemUpdate) {
bool toRet = false;
push_id(id);
CLAY ({.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();
}, true, true, isSelected);
}
pop_id();
return toRet;
}
bool GUIManager::text_button_sized(const std::string& id, const std::string& text, Clay_SizingAxis x, Clay_SizingAxis y, bool isSelected, const std::function<void()>& elemUpdate) {
bool toRet = false;
CLAY ({.layout = {.sizing = {.width = x, .height = y } } }) {
toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) {
text_label(text);
if(elemUpdate)
elemUpdate();
}, true, true, isSelected);
}
return toRet;
}
bool GUIManager::text_button(const std::string& id, const std::string& text, bool isSelected, const std::function<void()>& elemUpdate) {
bool toRet = false;
CLAY ({.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();
}, true, true, isSelected);
}
return toRet;
}
bool GUIManager::text_button_wide(const std::string& id, const std::string& text, bool isSelected, const std::function<void()>& elemUpdate) {
bool toRet = false;
CLAY ({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) } } }) {
toRet = selectable_button(id, [&](SelectionHelper& s, bool iS) {
text_label(text);
if(elemUpdate)
elemUpdate();
}, true, true, isSelected);
}
return toRet;
}
void GUIManager::input_scalar(const std::string& id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function<void()>& elemUpdate) {
push_id(id);
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, elemUpdate);
pop_id();
}
void GUIManager::input_path(const std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void()>& elemUpdate) {
push_id(id);
insert_element<TextBox<std::filesystem::path>>()->update(*io, val,
[&](const std::string& a) {
std::filesystem::path toRet = std::filesystem::path(a);
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, elemUpdate);
pop_id();
}
void GUIManager::tab_list(const std::string& 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();
}
void GUIManager::dropdown_select(const std::string& id, size_t* val, const std::vector<std::string>& selections, float width, const std::function<void()>& hoverboxElemUpdate) {
push_id(id);
bool& dropDownOpen = insert_any_with_id(0, false);
left_to_right_layout(CLAY_SIZING_FIXED(width), CLAY_SIZING_FIT(0), [&]() {
bool click = selectable_button(id, [&](SelectionHelper& s, bool iS) {
CLAY({
.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({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(20), .height = CLAY_SIZING_FIT(20)}
}
}) {
svg_icon("dropico", "data/icons/droparrow.svg", dropDownOpen);
}
}
}, true, false, dropDownOpen);
if(dropDownOpen) {
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIT(0)},
.padding = {.left = 2, .right = 2},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.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->backColor3),
.width = CLAY_BORDER_OUTSIDE(4)
}
}) {
obstructing_window();
for(size_t i = 0; i < selections.size(); i++) {
bool selectedEntry = *val == i;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
},
}) {
SkColor4f entryColor;
if(selectedEntry)
entryColor = io->theme->backColor2;
else if(Clay_Hovered())
entryColor = io->theme->backColor3;
else
entryColor = io->theme->backColor1;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)},
},
.backgroundColor = convert_vec4<Clay_Color>(entryColor)
}) {
text_label(selections[i]);
if(io->mouse.leftClick && Clay_Hovered()) {
*val = i;
dropDownOpen = false;
}
}
}
}
if(hoverboxElemUpdate)
hoverboxElemUpdate();
}
}
if(click)
dropDownOpen = !dropDownOpen;
});
pop_id();
}
void GUIManager::scroll_bar_area(const std::string& uniqueId, const std::function<void(float, float, float)>& elemUpdate) {
CLAY({
.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 contentDimensions = 100.0f;
float containerDimensions = 100.0f;
};
push_id(uniqueId);
ScrollAreaData& sD = insert_any(ScrollAreaData{});
pop_id();
Clay_ElementId clayID = Clay_GetElementId(strArena.std_str_to_clay_str(uniqueId));
CLAY({
.id = clayID,
.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 = {.vertical = true, .childOffset = Clay_GetScrollOffset()}
}) {
elemUpdate(sD.contentDimensions, sD.containerDimensions, sD.currentScrollPos);
}
// Make sure you are accessing this data after the element has been created
Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(clayID);
Clay_BoundingBox scrollAreaBB = Clay_GetElementData(clayID).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) + 3.0f; // NOTE: Adding + 3.0f to prevent bug with scroller that pushes everything under it when its at the bottom of the scroll area
float scrollerPos = std::fabs(scrollData.scrollPosition->y / scrollPosMax);
float areaAboveScrollerSize = scrollerPos * (sAreaDim - scrollerSize);
if(Clay_Hovered())
sD.currentScrollPos += io->mouse.scroll.y() * io->deltaTime * 3000.0f;
CLAY({
.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->backColor1)
}) {
SkColor4f scrollerColor;
if(sD.isMoving)
scrollerColor = io->theme->fillColor1;
else if(Clay_Hovered())
scrollerColor = io->theme->fillColor2;
else
scrollerColor = io->theme->backColor3;
CLAY({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(areaAboveScrollerSize)}
}
}) {}
CLAY({
.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(scrollerSize)}},
.backgroundColor = convert_vec4<Clay_Color>(scrollerColor),
.cornerRadius = CLAY_CORNER_RADIUS(3),
}) {
}
CLAY({ .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
if(Clay_Hovered() && io->mouse.leftClick)
sD.isMoving = true;
if(!io->mouse.leftHeld)
sD.isMoving = false;
if(sD.isMoving)
sD.currentScrollPos = ((io->mouse.pos.y() - scrollAreaBB.y) / scrollAreaBB.height) * (-scrollPosMax);
sD.currentScrollPos = std::clamp(sD.currentScrollPos, -scrollPosMax, 0.0f);
scrollData.scrollPosition->y = sD.currentScrollPos;
}
}
else
sD.currentScrollPos = 0.0f;
}
}
void GUIManager::scroll_bar_many_entries_area(const std::string& uniqueId, float entryHeight, size_t entryCount, const std::function<void(size_t, bool)>& entryUpdate, const std::function<void(float, float, float)>& elemUpdate) {
push_id(uniqueId);
scroll_bar_area(uniqueId, [&](float scrollContentHeight, float containerHeight, float scrollAmount) {
if(elemUpdate)
elemUpdate(scrollContentHeight, containerHeight, scrollAmount);
bool listHovered = Clay_Hovered();
scrollAmount = std::fabs(scrollAmount);
float entryHeight = 25.0f;
size_t startPoint = scrollAmount / entryHeight;
size_t elementsContainable = (containerHeight / entryHeight) + 1;
size_t endPoint = std::min(entryCount, startPoint + elementsContainable);
if(startPoint != 0) {
CLAY({
.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({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED((entryCount - endPoint) * entryHeight)},
}
}) { }
}
});
pop_id();
}
void GUIManager::paint_circle_popup_menu(const std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function<void()>& elemUpdate) {
float size = 300.0f;
push_id(id);
CLAY({.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(*io, val, elemUpdate);
}
pop_id();
}
void GUIManager::obstructing_window() {
if(Clay_Hovered())
io->hoverObstructed = true;
}
void GUIManager::clay_error_handler(Clay_ErrorData errorData) {
std::cout << "[Clay Error] " << errorData.errorText.chars << std::endl;
}
void GUIManager::push_id(int64_t id) {
idStack.emplace_back(id);
}
void GUIManager::push_id(const std::string& id) {
idStack.emplace_back(id);
}
void GUIManager::pop_id() {
idStack.pop_back();
}
}

291
src/GUIStuff/GUIManager.hpp Normal file
View file

@ -0,0 +1,291 @@
#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/TextBox.hpp"
#include <any>
#include "GUIManagerID.hpp"
#include <filesystem>
#include <type_traits>
#include <clay.h>
using namespace Eigen;
namespace GUIStuff {
class GUIManager {
public:
struct ElementContainer {
std::unique_ptr<Element> elem;
std::any extra;
};
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();
void input_text_field(const std::string& id, const std::string& name, std::string* val, const std::function<void()>& elemUpdate = nullptr);
void text_label_color(const std::string& val, const SkColor4f& color);
void text_label_size(const std::string& val, float modifier);
void text_label(const std::string& val);
void text_label_centered(const std::string& val);
bool text_button(const std::string& id, const std::string& text, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr);
bool text_button_wide(const std::string& id, const std::string& text, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr);
bool text_button_sized(const std::string& id, const std::string& text, Clay_SizingAxis x, Clay_SizingAxis y, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr);
void svg_icon(const std::string& id, const std::string& svgPath, bool isHighlighted = false, const std::function<void()>& elemUpdate = nullptr);
bool svg_icon_button(const std::string& id, const std::string& svgPath, bool isSelected = false, float size = 40.0f, const std::function<void()>& elemUpdate = nullptr);
void top_to_bottom_window_popup_layout(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);
bool selectable_button(const std::string& id, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool hasBorder, bool hasBorderPadding, bool isSelected);
void push_id(int64_t id);
void push_id(const std::string& id);
void pop_id();
void scroll_bar_area(const std::string& uniqueId, const std::function<void(float scrollContentHeight, float containerHeight, float scrollAmount)>& elemUpdate);
void scroll_bar_many_entries_area(const std::string& uniqueId, float entryHeight, size_t entryCount, const std::function<void(size_t elementIndex, bool listHovered)>& entryUpdate, const std::function<void(float scrollContentHeight, float containerHeight, float scrollAmount)>& elemUpdate = nullptr);
void input_color_component_255(const std::string& id, float* val, const std::function<void()>& elemUpdate = nullptr);
void input_text(const std::string& id, std::string* val, const std::function<void()>& elemUpdate = nullptr);
bool radio_button(const std::string& id, bool val, const std::function<void()>& elemUpdate = nullptr);
bool radio_button_field(const std::string& id, const std::string& name, bool val, const std::function<void()>& elemUpdate = nullptr);
void checkbox_field(const std::string& id, const std::string& name, bool* val, const std::function<void()>& elemUpdate = nullptr);
void checkbox(const std::string& id, bool* val, const std::function<void()>& elemUpdate = nullptr);
void tab_list(const std::string& 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 std::string& id, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void()>& elemUpdate = nullptr);
void input_path_field(const std::string& id, const std::string& name, std::filesystem::path* val, std::filesystem::file_type fileTypeRestriction, const std::function<void()>& elemUpdate = nullptr);
void dropdown_select(const std::string& 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 std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function<void()>& elemUpdate = nullptr);
void obstructing_window();
template <typename NewElement> NewElement* insert_element() {
auto [it, inserted] = elements.emplace(idStack, ElementContainer());
if(inserted)
it->second = {std::make_unique<NewElement>()};
return static_cast<NewElement*>(it->second.elem.get());
}
template <typename T> T& insert_any(const T& def) {
auto [it, inserted] = elements.emplace(idStack, ElementContainer());
if(inserted)
it->second.extra = def;
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> void input_generic(const std::string& id, T* val, const std::function<std::optional<T>(const std::string&)>& fromStr, const std::function<std::string(const T&)>& toStr, bool singleLine, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
insert_element<TextBox<T>>()->update(*io, val, fromStr, toStr, singleLine, elemUpdate);
pop_id();
}
template <typename T> void input_scalar(const std::string& id, T* val, T min, T max, int decimalPrecision = 0, const std::function<void()>& elemUpdate = nullptr) {
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, elemUpdate);
}
void input_scalar(const std::string& id, uint8_t* val, uint8_t min, uint8_t max, int decimalPrecision, const std::function<void()>& elemUpdate);
template <typename TContainer, typename T> void input_scalar_fields(const std::string& id, const std::string& name, TContainer* val, size_t elemCount, T min, T max, int decimalPrecision = 0, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
left_to_right_line_layout([&]() {
text_label(name);
for(size_t i = 0; i < elemCount; i++)
input_scalar<T>(std::to_string(i), &(*val)[i], min, max, decimalPrecision, elemUpdate);
});
pop_id();
}
template <typename T> void input_color_hex(const std::string& id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
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, elemUpdate);
}
template <typename T> void color_picker(const std::string& id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
insert_element<ColorPicker<T>>()->update(*io, val, selectAlpha, elemUpdate);
pop_id();
}
template <typename T> void slider_scalar(const std::string& id, T* val, T min, T max, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
insert_element<NumberSlider<T>>()->update(*io, val, min, max, elemUpdate);
pop_id();
}
template <typename T> void input_scalar_field(const std::string& id, const std::string& name, T* val, T min, T max, int decimalPrecision = 0, const std::function<void()>& elemUpdate = nullptr) {
left_to_right_line_layout([&]() {
text_label(name);
input_scalar(id, val, min, max, decimalPrecision, elemUpdate);
});
}
template <typename T> void slider_scalar_field(const std::string& id, const std::string& name, T* val, T min, T max, int decimalPrecision = 0, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
left_to_right_line_layout([&]() {
text_label(name);
input_scalar("0", val, min, max, decimalPrecision, elemUpdate);
});
slider_scalar("1", val, min, max, elemUpdate);
pop_id();
}
template <typename T> bool color_button(const std::string& id, T* val, bool isSelected = false, const std::function<void()>& elemUpdate = nullptr) {
return selectable_button(id, [&](SelectionHelper& s, bool iS) {
CLAY({.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();
}, true, false, isSelected);
}
template <typename T> void color_picker_button(const std::string& id, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
push_id(id);
bool& isOpen = insert_any_with_id<bool>(0, false);
bool clicked;
CLAY ({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40) } } }) {
clicked = color_button("0", val, isOpen, elemUpdate);
}
if(clicked)
isOpen = true;
if(isOpen) {
top_to_bottom_window_popup_layout(CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&]() {
color_picker_items("c", val, selectAlpha);
if(io->mouse.leftClick && !Clay_Hovered() && !clicked)
isOpen = false;
});
}
pop_id();
}
template <typename T> void color_picker_items(const std::string& id, T* val, bool selectAlpha) {
push_id(id);
color_picker("c", val, selectAlpha);
left_to_right_line_layout([&]() {
text_label("R");
input_color_component_255("r", &(*val)[0]);
text_label("G");
input_color_component_255("g", &(*val)[1]);
text_label("B");
input_color_component_255("b", &(*val)[2]);
if(selectAlpha) {
text_label("A");
input_color_component_255("a", &(*val)[3]);
}
});
left_to_right_line_layout([&]() {
text_label("Hex");
input_color_hex("h", val, selectAlpha);
});
pop_id();
}
template <typename T> void color_picker_button_field(const std::string& id, const std::string& name, T* val, bool selectAlpha, const std::function<void()>& elemUpdate = nullptr) {
left_to_right_line_layout([&]() {
color_picker_button(id, val, selectAlpha, elemUpdate);
text_label(name);
});
}
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;
};
}

View file

@ -0,0 +1,19 @@
#include "GUIManagerID.hpp"
namespace GUIStuff {
GUIManagerID::GUIManagerID(int64_t numID):
idIsStr(false),
idNum(numID)
{}
GUIManagerID::GUIManagerID(const std::string& strID):
idIsStr(true),
idStr(strID)
{}
bool GUIManagerID::operator==(const GUIManagerID& other) const {
return idIsStr == other.idIsStr && idStr == other.idStr && idNum == other.idNum;
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <stack>
#include <Helpers/Hashes.hpp>
namespace GUIStuff {
struct GUIManagerID {
GUIManagerID() = default;
GUIManagerID(int64_t numID);
GUIManagerID(const std::string& strID);
bool operator==(const GUIManagerID& other) const;
bool idIsStr;
std::string idStr;
int64_t idNum = 0;
};
typedef std::vector<GUIManagerID> GUIManagerIDStack;
}
template <> struct std::hash<GUIStuff::GUIManagerID> {
size_t operator()(const GUIStuff::GUIManagerID& v) const
{
if(v.idIsStr)
return std::hash<std::string>{}(v.idStr);
else
return std::hash<uint64_t>{}(v.idNum);
}
};
template <> struct std::hash<GUIStuff::GUIManagerIDStack> {
size_t operator()(const GUIStuff::GUIManagerIDStack& v) const
{
uint64_t h = 0;
for(const auto& i : v)
hash_combine(h, i);
return h;
}
};