Initial Commit
This commit is contained in:
commit
6088fc29ed
978 changed files with 353647 additions and 0 deletions
2
src/GUIStuff/ClayImplement.cpp
Normal file
2
src/GUIStuff/ClayImplement.cpp
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#define CLAY_IMPLEMENTATION
|
||||
#include <clay.h>
|
||||
90
src/GUIStuff/Elements/CheckBox.cpp
Normal file
90
src/GUIStuff/Elements/CheckBox.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
17
src/GUIStuff/Elements/CheckBox.hpp
Normal file
17
src/GUIStuff/Elements/CheckBox.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
285
src/GUIStuff/Elements/ColorPicker.hpp
Normal file
285
src/GUIStuff/Elements/ColorPicker.hpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
63
src/GUIStuff/Elements/Element.cpp
Normal file
63
src/GUIStuff/Elements/Element.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
138
src/GUIStuff/Elements/Element.hpp
Normal file
138
src/GUIStuff/Elements/Element.hpp
Normal 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);
|
||||
};
|
||||
|
||||
}
|
||||
79
src/GUIStuff/Elements/MovableTabList.cpp
Normal file
79
src/GUIStuff/Elements/MovableTabList.cpp
Normal 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) {
|
||||
}
|
||||
|
||||
}
|
||||
21
src/GUIStuff/Elements/MovableTabList.hpp
Normal file
21
src/GUIStuff/Elements/MovableTabList.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
91
src/GUIStuff/Elements/NumberSlider.hpp
Normal file
91
src/GUIStuff/Elements/NumberSlider.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
193
src/GUIStuff/Elements/PaintCircleMenu.cpp
Normal file
193
src/GUIStuff/Elements/PaintCircleMenu.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
src/GUIStuff/Elements/PaintCircleMenu.hpp
Normal file
26
src/GUIStuff/Elements/PaintCircleMenu.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
57
src/GUIStuff/Elements/RadioButton.cpp
Normal file
57
src/GUIStuff/Elements/RadioButton.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
19
src/GUIStuff/Elements/RadioButton.hpp
Normal file
19
src/GUIStuff/Elements/RadioButton.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
67
src/GUIStuff/Elements/SVGIcon.cpp
Normal file
67
src/GUIStuff/Elements/SVGIcon.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
15
src/GUIStuff/Elements/SVGIcon.hpp
Normal file
15
src/GUIStuff/Elements/SVGIcon.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
55
src/GUIStuff/Elements/SelectableButton.cpp
Normal file
55
src/GUIStuff/Elements/SelectableButton.cpp
Normal 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) {
|
||||
}
|
||||
|
||||
}
|
||||
13
src/GUIStuff/Elements/SelectableButton.hpp
Normal file
13
src/GUIStuff/Elements/SelectableButton.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
195
src/GUIStuff/Elements/TextBox.hpp
Normal file
195
src/GUIStuff/Elements/TextBox.hpp
Normal 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
702
src/GUIStuff/GUIManager.cpp
Normal 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
291
src/GUIStuff/GUIManager.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
19
src/GUIStuff/GUIManagerID.cpp
Normal file
19
src/GUIStuff/GUIManagerID.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
40
src/GUIStuff/GUIManagerID.hpp
Normal file
40
src/GUIStuff/GUIManagerID.hpp
Normal 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;
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue