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,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;
};
}