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

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

View file

@ -1,28 +1,39 @@
#include "CheckBox.hpp"
#include <include/core/SkPath.h>
#include <include/core/SkPathBuilder.h>
#include "../GUIManager.hpp"
namespace GUIStuff {
void CheckBox::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
CheckBox::CheckBox(GUIManager& gui):
Element(gui) {}
void CheckBox::layout(const std::function<bool()>& isTicked, const std::function<void()>& onClick) {
this->isTicked = isTicked;
this->onClick = onClick;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(15), .height = CLAY_SIZING_FIXED(15)}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(isTicked != newIsTicked) {
hoverAnimation2 = selection.hovered ? CHECKBOX_ANIMATION_TIME : 0.0f;
isTicked = newIsTicked;
}
if(elemUpdate)
elemUpdate();
}
}) {}
}
bool CheckBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
isHovering = mouseHovering;
if(isHovering && button.button == InputManager::MouseButton::LEFT && button.down)
gui.set_post_callback_func([&](){if(onClick) onClick();});
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool CheckBox::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
isHovering = mouseHovering;
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
@ -31,7 +42,7 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma
SkPaint p;
p.setAntiAlias(skiaAA);
if(isTicked) {
if(isTicked()) {
SkRect checkBox = SkRect::MakeLTRB(-0.5f, -0.5f, 0.5f, 0.5f);
p.setColor4f(convert_vec4<SkColor4f>(io.theme->fillColor1));
p.setStyle(SkPaint::kFill_Style);
@ -39,14 +50,13 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma
}
else {
SkRect checkBox = SkRect::MakeLTRB(-0.45f, -0.45f, 0.45f, 0.45f);
p.setColor4f(convert_vec4<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->fillColor2));
p.setColor4f(convert_vec4<SkColor4f>(isHovering ? io.theme->fillColor1 : io.theme->fillColor2));
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.15f);
canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p);
}
if(isTicked) {
if(isTicked()) {
SkPaint checkP;
checkP.setAntiAlias(skiaAA);
checkP.setColor4f(io.theme->backColor1);
@ -67,7 +77,7 @@ void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderComma
static BezierEasing anim{0.445, -0.733, 0.575, 1.627};
float lerpTime2 = anim(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, CHECKBOX_ANIMATION_TIME));
float lerpTime2 = anim(smooth_two_way_time(hoverAnimation2, io.deltaTime, isHovering, CHECKBOX_ANIMATION_TIME));
std::array<Vector2f, 5> points;

View file

@ -5,13 +5,19 @@ namespace GUIStuff {
class CheckBox : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
CheckBox(GUIManager& gui);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
SelectionHelper selection;
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
void layout(const std::function<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float CHECKBOX_ANIMATION_TIME = 0.3;
bool isTicked = false;
bool isHovering = false;
float hoverAnimation2 = 0.0;
std::function<bool()> isTicked;
std::function<void()> onClick;
};
}

View file

@ -0,0 +1,102 @@
#pragma once
#include "Element.hpp"
#include <include/core/SkColor.h>
#include <include/core/SkPath.h>
#include <include/effects/SkRuntimeEffect.h>
#include <iostream>
#include <Helpers/SCollision.hpp>
#include <Helpers/HsvRgb.hpp>
namespace GUIStuff {
namespace ColorPickerShaders {
// HSV to RGB func from: https://github.com/hughsk/glsl-hsv2rgb?tab=readme-ov-file
static constexpr const char* hueBarSkSl =
R"V(
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 main(vec2 fragCoord) {
vec3 a = hsv2rgb(vec3(1.0 - fragCoord.y, 1.0, 1.0));
return vec4(a.xyz, 1.0);
})V";
static constexpr const char* svSelectionAreaSkSl =
R"V(
uniform float hue;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 main(vec2 fragCoord) {
vec3 a = hsv2rgb(vec3(hue, fragCoord.x, 1.0 - fragCoord.y));
return vec4(a.xyz, 1.0);
})V";
static constexpr const char* alphaBarSksl =
R"V(
uniform vec3 mainColor;
uniform float horizontalResolution;
vec4 main(vec2 fragcoord) {
vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0);
vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0);
vec2 flooredCoords = floor(fragcoord / 10);
bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5;
return mix(isDark ? alphaBoard2 : alphaBoard1, vec4(mainColor, 1.0), fragcoord.x / horizontalResolution);
})V";
sk_sp<SkShader> get_hue_shader();
sk_sp<SkShader> get_sv_selection_shader(float hue);
sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
}
template <typename T> class ColorPicker : public Element {
public:
ColorPicker(GUIManager& gui);
void layout(T* data, bool selectAlpha, const std::function<void()>& onChange);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA);
bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
private:
void update_color_picker_pos(const Vector2f& p);
float get_sv_selection_area_size();
Vector2f get_hue_bar_pos();
Vector2f get_hue_bar_dim();
Vector2f get_alpha_bar_pos();
Vector2f get_alpha_bar_dim();
void set_hsv(const Vector3f& hsv);
static constexpr float BAR_WIDTH = 30.0;
static constexpr float BAR_GAP = 5.0;
static sk_sp<SkShader> hueBarShader;
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
static sk_sp<SkRuntimeEffect> alphaBarEffect;
static sk_sp<SkShader> get_hue_shader();
static sk_sp<SkShader> get_sv_selection_shader(float hue);
static sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
T* data;
Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI
std::function<void()> onChange;
bool selectAlpha = false;
enum class HeldBar {
NONE,
SV_HELD,
HUE_HELD,
ALPHA_HELD
};
HeldBar held = HeldBar::NONE;
};
}

View file

@ -1,291 +1,3 @@
#pragma once
#include "Element.hpp"
#include <include/core/SkColor.h>
#include <include/core/SkPath.h>
#include <include/effects/SkRuntimeEffect.h>
#include <iostream>
#include <Helpers/SCollision.hpp>
#include <Helpers/HsvRgb.hpp>
namespace GUIStuff {
template <typename T> class ColorPicker : public Element {
public:
bool update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function<void()>& elemUpdate) {
bool isChanged = false;
T* data = newData;
selectAlpha = newSelectAlpha;
if(!data)
return false;
force_update_colorpicker(data);
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }
},
.aspectRatio = {1.0f},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(selection.clicked && data) {
float svSelectionAreaSize = get_sv_selection_area_size();
modifyingSv = SCollision::collide(SCollision::AABB<float>(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), io.mouse.pos);
Vector2f hueBarPos = get_hue_bar_pos();
Vector2f hueBarDim = get_hue_bar_dim();
modifyingHue = SCollision::collide(SCollision::AABB<float>(hueBarPos, hueBarPos + hueBarDim), io.mouse.pos);
if(selectAlpha) {
Vector2f alphaBarPos = get_alpha_bar_pos();
Vector2f alphaBarDim = get_alpha_bar_dim();
modifyingAlpha = SCollision::collide(SCollision::AABB<float>(alphaBarPos, alphaBarPos + alphaBarDim), io.mouse.pos);
}
isChanged = true;
}
if(selection.held && modifyingSv) {
float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH);
Vector2f newSv = cwise_vec_clamp<Vector2f>((io.mouse.pos - bb.min) / svSelectionAreaSize, Vector2f{0.0f, 0.0f}, Vector2f{1.0f, 1.0f});
savedHsv.y() = newSv.x();
savedHsv.z() = 1.0f - newSv.y();
set_hsv(data, savedHsv);
isChanged = true;
}
if(selection.held && modifyingHue) {
Vector2f huePos = get_hue_bar_pos();
Vector2f hueDim = get_hue_bar_dim();
savedHsv.x() = (1.0f - std::clamp((io.mouse.pos.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f;
set_hsv(data, savedHsv);
isChanged = true;
}
if(selection.held && modifyingAlpha) {
Vector2f alphaPos = get_alpha_bar_pos();
Vector2f alphaDim = get_alpha_bar_dim();
(*data)[3] = std::clamp((io.mouse.pos.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f);
oldData = *data;
isChanged = true;
}
if(!selection.held) {
modifyingSv = false;
modifyingHue = false;
modifyingAlpha = false;
}
if(elemUpdate)
elemUpdate();
}
return isChanged;
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bb = get_bb(command);
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
float svSelectionAreaSize = get_sv_selection_area_size();
canvas->scale(svSelectionAreaSize, svSelectionAreaSize);
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA);
SkPaint svSelectionAreaPaint;
svSelectionAreaPaint.setShader(get_sv_selection_shader(savedHsv.x() / 360.0f));
canvas->drawPaint(svSelectionAreaPaint);
SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f});
selectionLinePaint.setAntiAlias(skiaAA);
selectionLinePaint.setStrokeWidth(2.0f / svSelectionAreaSize);
canvas->drawLine(0.0f, 1.0f - savedHsv.z(), 1.0f, 1.0f - savedHsv.z(), selectionLinePaint);
canvas->drawLine(savedHsv.y(), 0.0f, savedHsv.y(), 1.0f, selectionLinePaint);
canvas->restore();
float normalizedHue = savedHsv.x() / 360.0f;
Vector2f hueBarPos = get_hue_bar_pos();
Vector2f hueBarDim = get_hue_bar_dim();
canvas->save();
canvas->translate(hueBarPos.x(), hueBarPos.y());
canvas->scale(hueBarDim.x(), hueBarDim.y());
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA);
SkPaint hueBarPaint;
hueBarPaint.setShader(get_hue_shader());
canvas->drawPaint(hueBarPaint);
canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint);
canvas->restore();
Vector2f alphaBarPos = get_alpha_bar_pos();
Vector2f alphaBarDim = get_alpha_bar_dim();
canvas->save();
canvas->translate(alphaBarPos.x(), alphaBarPos.y());
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()), skiaAA);
SkPaint alphaBarPaint;
if(selectAlpha)
alphaBarPaint.setShader(get_alpha_bar_shader({oldData[0], oldData[1], oldData[2]}, alphaBarDim.x()));
else
alphaBarPaint.setColor4f(SkColor4f{oldData[0], oldData[1], oldData[2], 1.0f});
canvas->drawPaint(alphaBarPaint);
if(selectAlpha) {
canvas->scale(alphaBarDim.x(), alphaBarDim.y());
canvas->drawLine(oldData[3], 0.0f, oldData[3], 1.0f, selectionLinePaint);
}
canvas->restore();
}
private:
void force_update_colorpicker(T* data) {
if(data && (*data == oldData))
return;
if(data) {
savedHsv = rgb_to_hsv<Vector3f>(*data);
oldData = *data;
}
else
oldData = T{0, 0, 0, 0};
}
float get_sv_selection_area_size() {
return bb.width() - (BAR_GAP + BAR_WIDTH);
}
Vector2f get_hue_bar_pos() {
return {bb.min.x() + get_sv_selection_area_size() + BAR_GAP, bb.min.y()};
}
Vector2f get_hue_bar_dim() {
return {BAR_WIDTH, get_sv_selection_area_size()};
}
Vector2f get_alpha_bar_pos() {
return {bb.min.x(), bb.min.y() + get_sv_selection_area_size() + BAR_GAP};
}
Vector2f get_alpha_bar_dim() {
return {bb.width(), BAR_WIDTH};
}
void set_hsv(T* data, const Vector3f& hsv) {
Vector3f a = hsv_to_rgb<Vector3f>(hsv);
(*data)[0] = a.x();
(*data)[1] = a.y();
(*data)[2] = a.z();
oldData = *data;
}
static constexpr float BAR_WIDTH = 30.0;
static constexpr float BAR_GAP = 5.0;
// HSV to RGB func from: https://github.com/hughsk/glsl-hsv2rgb?tab=readme-ov-file
static constexpr const char* hueBarSkSl =
R"V(
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 main(vec2 fragCoord) {
vec3 a = hsv2rgb(vec3(1.0 - fragCoord.y, 1.0, 1.0));
return vec4(a.xyz, 1.0);
})V";
static constexpr const char* svSelectionAreaSkSl =
R"V(uniform float hue;
vec3 hsv2rgb(vec3 c) {
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 main(vec2 fragCoord) {
vec3 a = hsv2rgb(vec3(hue, fragCoord.x, 1.0 - fragCoord.y));
return vec4(a.xyz, 1.0);
})V";
static constexpr const char* alphaBarSksl =
R"V(uniform vec3 mainColor;
uniform float horizontalResolution;
vec4 main(vec2 fragcoord) {
vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0);
vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0);
vec2 flooredCoords = floor(fragcoord / 10);
bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5;
return mix(isDark ? alphaBoard2 : alphaBoard1, vec4(mainColor, 1.0), fragcoord.x / horizontalResolution);
}
)V";
static sk_sp<SkShader> hueBarShader;
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
static sk_sp<SkRuntimeEffect> alphaBarEffect;
static sk_sp<SkShader> get_hue_shader();
static sk_sp<SkShader> get_sv_selection_shader(float hue);
static sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
SCollision::AABB<float> bb;
SelectionHelper selection;
T oldData;
Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI
bool selectAlpha;
bool modifyingSv = false;
bool modifyingHue = false;
bool modifyingAlpha = false;
};
template <typename T> sk_sp<SkShader> ColorPicker<T>::hueBarShader;
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::svSelectionAreaEffect;
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::alphaBarEffect;
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_hue_shader(){
if(!hueBarShader) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(hueBarSkSl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_hue_shader] Hue shader construction error\n";
std::cout << err.c_str() << std::endl;
}
else
hueBarShader = effect->makeShader(nullptr, {nullptr, 0});
}
return hueBarShader;
}
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_sv_selection_shader(float hue) {
if(!svSelectionAreaEffect) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(svSelectionAreaSkSl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_sv_selection_shader] SV Selection shader construction error\n";
std::cout << err.c_str() << std::endl;
return nullptr;
}
else
svSelectionAreaEffect = effect;
}
SkRuntimeShaderBuilder builder(svSelectionAreaEffect);
builder.uniform("hue") = hue;
return builder.makeShader();
}
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) {
if(!alphaBarEffect) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBarSksl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_alpha_bar_shader] Alpha bar shader construction error\n";
std::cout << err.c_str() << std::endl;
return nullptr;
}
else
alphaBarEffect = effect;
}
SkRuntimeShaderBuilder builder(alphaBarEffect);
builder.uniform("mainColor") = SkV3{mainColor.x(), mainColor.y(), mainColor.z()};
builder.uniform("horizontalResolution") = horizontalResolution;
return builder.makeShader();
}
}
#include "ColorPicker.decl.hpp"
#include "ColorPicker.impl.hpp"

View file

@ -0,0 +1,170 @@
#pragma once
#include "ColorPicker.decl.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> void ColorPicker<T>::layout(T* data, bool selectAlpha, const std::function<void()>& onChange) {
this->data = data;
savedHsv = rgb_to_hsv<T>(*data);
this->selectAlpha = selectAlpha;
this->onChange = onChange;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }
},
.aspectRatio = {1.0f},
.custom = { .customData = this }
}) {}
}
template <typename T> void ColorPicker<T>::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
float svSelectionAreaSize = get_sv_selection_area_size();
canvas->scale(svSelectionAreaSize, svSelectionAreaSize);
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA);
SkPaint svSelectionAreaPaint;
svSelectionAreaPaint.setShader(get_sv_selection_shader(savedHsv.x() / 360.0f));
canvas->drawPaint(svSelectionAreaPaint);
SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f});
selectionLinePaint.setAntiAlias(skiaAA);
selectionLinePaint.setStrokeWidth(2.0f / svSelectionAreaSize);
canvas->drawLine(0.0f, 1.0f - savedHsv.z(), 1.0f, 1.0f - savedHsv.z(), selectionLinePaint);
canvas->drawLine(savedHsv.y(), 0.0f, savedHsv.y(), 1.0f, selectionLinePaint);
canvas->restore();
float normalizedHue = savedHsv.x() / 360.0f;
Vector2f hueBarPos = get_hue_bar_pos();
Vector2f hueBarDim = get_hue_bar_dim();
canvas->save();
canvas->translate(hueBarPos.x(), hueBarPos.y());
canvas->scale(hueBarDim.x(), hueBarDim.y());
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA);
SkPaint hueBarPaint;
hueBarPaint.setShader(get_hue_shader());
canvas->drawPaint(hueBarPaint);
canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint);
canvas->restore();
Vector2f alphaBarPos = get_alpha_bar_pos();
Vector2f alphaBarDim = get_alpha_bar_dim();
canvas->save();
canvas->translate(alphaBarPos.x(), alphaBarPos.y());
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()), skiaAA);
SkPaint alphaBarPaint;
if(selectAlpha)
alphaBarPaint.setShader(get_alpha_bar_shader({(*data)[0], (*data)[1], (*data)[2]}, alphaBarDim.x()));
else
alphaBarPaint.setColor4f(SkColor4f{(*data)[0], (*data)[1], (*data)[2], 1.0f});
canvas->drawPaint(alphaBarPaint);
if(selectAlpha) {
canvas->scale(alphaBarDim.x(), alphaBarDim.y());
canvas->drawLine((*data)[3], 0.0f, (*data)[3], 1.0f, selectionLinePaint);
}
canvas->restore();
}
template <typename T> bool ColorPicker<T>::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
held = HeldBar::NONE;
if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down && boundingBox.has_value()) {
auto& bb = boundingBox.value();
float svSelectionAreaSize = get_sv_selection_area_size();
if(SCollision::collide(SCollision::AABB<float>(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), button.pos))
held = HeldBar::SV_HELD;
else {
Vector2f hueBarPos = get_hue_bar_pos();
Vector2f hueBarDim = get_hue_bar_dim();
if(SCollision::collide(SCollision::AABB<float>(hueBarPos, hueBarPos + hueBarDim), button.pos))
held = HeldBar::HUE_HELD;
else if(selectAlpha) {
Vector2f alphaBarPos = get_alpha_bar_pos();
Vector2f alphaBarDim = get_alpha_bar_dim();
if(SCollision::collide(SCollision::AABB<float>(alphaBarPos, alphaBarPos + alphaBarDim), button.pos))
held = HeldBar::ALPHA_HELD;
}
}
}
update_color_picker_pos(button.pos);
return Element::input_mouse_button_callback(button, mouseHovering);
}
template <typename T> bool ColorPicker<T>::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
update_color_picker_pos(motion.pos);
return Element::input_mouse_button_callback(motion, mouseHovering);
}
template <typename T> void ColorPicker<T>::update_color_picker_pos(const Vector2f& p) {
if(held != HeldBar::NONE && boundingBox.has_value()) {
gui.set_post_callback_func([&, p]() {
auto& bb = boundingBox.value();
switch(held) {
case HeldBar::SV_HELD: {
float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH);
Vector2f newSv = cwise_vec_clamp<Vector2f>((p - bb.min) / svSelectionAreaSize, Vector2f{0.0f, 0.0f}, Vector2f{1.0f, 1.0f});
savedHsv.y() = newSv.x();
savedHsv.z() = 1.0f - newSv.y();
set_hsv(savedHsv);
gui.set_post_callback_func([&]{if(onChange) onChange();});
break;
}
case HeldBar::HUE_HELD: {
Vector2f huePos = get_hue_bar_pos();
Vector2f hueDim = get_hue_bar_dim();
savedHsv.x() = (1.0f - std::clamp((p.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f;
set_hsv(savedHsv);
gui.set_post_callback_func([&]{if(onChange) onChange();});
break;
}
case HeldBar::ALPHA_HELD: {
Vector2f alphaPos = get_alpha_bar_pos();
Vector2f alphaDim = get_alpha_bar_dim();
(*data)[3] = std::clamp((p.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f);
gui.set_post_callback_func([&]{if(onChange) onChange();});
break;
}
}
});
}
}
template <typename T> float ColorPicker<T>::get_sv_selection_area_size() {
return boundingBox.value().width() - (BAR_GAP + BAR_WIDTH);
}
template <typename T> Vector2f ColorPicker<T>::get_hue_bar_pos() {
return {boundingBox.value().min.x() + get_sv_selection_area_size() + BAR_GAP, boundingBox.value().min.y()};
}
template <typename T> Vector2f ColorPicker<T>::get_hue_bar_dim() {
return {BAR_WIDTH, get_sv_selection_area_size()};
}
template <typename T> Vector2f ColorPicker<T>::get_alpha_bar_pos() {
return {boundingBox.value().min.x(), boundingBox.value().min.y() + get_sv_selection_area_size() + BAR_GAP};
}
template <typename T> Vector2f ColorPicker<T>::get_alpha_bar_dim() {
return {boundingBox.value().width(), BAR_WIDTH};
}
template <typename T> void ColorPicker<T>::set_hsv(const Vector3f& hsv) {
Vector3f a = hsv_to_rgb<Vector3f>(hsv);
(*data)[0] = a.x();
(*data)[1] = a.y();
(*data)[2] = a.z();
}
}

View file

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

View file

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

View file

@ -0,0 +1,35 @@
#include "ColorPickerButton.decl.hpp"
#include "../ElementHelpers/ColorPickerHelpers.hpp"
#include "../ElementHelpers/ButtonHelpers.hpp"
namespace GUIStuff {
template <typename T> ColorPickerButton<T>::ColorPickerButton(GUIManager& gui): Element(gui) {}
template <typename T> void ColorPickerButton<T>::layout(T* val, ColorPickerButtonData& d) {
this->data = d;
GUIStuff::ElementHelpers::color_button(gui, "Color Picker Button", val, {
.isSelected = isOpen,
.onClick = [&]{
isOpen = !isOpen;
gui.set_to_layout();
}
});
if(isOpen) {
top_to_bottom_window_popup_layout("Color Picker", CLAY_SIZING_FIT(300), CLAY_SIZING_FIT(0), [&, val, data = data]() {
ElementHelpers::color_picker_items(gui, "c", val, {
.hasAlpha = data.hasAlpha,
.onEdit = data.onEdit
});
}, {
.mouseButton = [&](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
if(!mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down)
isOpen = false;
return mouseHovering;
}
});
}
}
}

View file

@ -0,0 +1,55 @@
#include "ColorPicker.decl.hpp"
#include <include/effects/SkRuntimeEffect.h>
namespace GUIStuff { namespace ColorPickerShaders {
sk_sp<SkShader> get_hue_shader() {
static sk_sp<SkShader> hueBarShader;
if(!hueBarShader) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(hueBarSkSl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_hue_shader] Hue shader construction error\n";
std::cout << err.c_str() << std::endl;
}
else
hueBarShader = effect->makeShader(nullptr, {nullptr, 0});
}
return hueBarShader;
}
sk_sp<SkShader> get_sv_selection_shader(float hue) {
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
if(!svSelectionAreaEffect) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(svSelectionAreaSkSl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_sv_selection_shader] SV Selection shader construction error\n";
std::cout << err.c_str() << std::endl;
return nullptr;
}
else
svSelectionAreaEffect = effect;
}
SkRuntimeShaderBuilder builder(svSelectionAreaEffect);
builder.uniform("hue") = hue;
return builder.makeShader();
}
sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) {
static sk_sp<SkRuntimeEffect> alphaBarEffect;
if(!alphaBarEffect) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBarSksl));
if(!err.isEmpty()) {
std::cout << "[ColorPicker::get_alpha_bar_shader] Alpha bar shader construction error\n";
std::cout << err.c_str() << std::endl;
return nullptr;
}
else
alphaBarEffect = effect;
}
SkRuntimeShaderBuilder builder(alphaBarEffect);
builder.uniform("mainColor") = SkV3{mainColor.x(), mainColor.y(), mainColor.z()};
builder.uniform("horizontalResolution") = horizontalResolution;
return builder.makeShader();
}
}}

View file

@ -0,0 +1,49 @@
#include "ColorRectangleDisplay.hpp"
namespace GUIStuff {
ColorRectangleDisplay::ColorRectangleDisplay(GUIManager& gui): Element(gui) {}
void ColorRectangleDisplay::layout(const std::function<SkColor4f()>& colorFunc) {
this->colorFunc = colorFunc;
}
void ColorRectangleDisplay::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto& bb = boundingBox.value();
SkColor4f color = colorFunc();
SkRect r = SkRect::MakeLTRB(bb.min.x(), bb.min.y(), bb.max.x(), bb.max.y());
if(color.fA == 1.0f) {
SkPaint p;
p.setColor4f(color);
p.setAntiAlias(skiaAA);
canvas->drawRect(r, p);
}
else {
canvas->save();
canvas->clipRect(r, skiaAA);
SkPaint alphaPaint;
alphaPaint.setShader(get_alpha_background_shader());
canvas->drawPaint(alphaPaint);
canvas->drawPaint(SkPaint{color});
canvas->restore();
}
}
sk_sp<SkShader> ColorRectangleDisplay::get_alpha_background_shader() {
if(!alphaBackgroundEffect) {
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBackgroundSksl));
if(!err.isEmpty()) {
std::cout << "[ColorRectangleDisplay::get_alpha_background_shader] Alpha background shader construction error\n";
std::cout << err.c_str() << std::endl;
return nullptr;
}
else
alphaBackgroundEffect = effect;
}
SkRuntimeShaderBuilder builder(alphaBackgroundEffect);
return builder.makeShader();
}
}

View file

@ -0,0 +1,31 @@
#pragma once
#include "Element.hpp"
#include <include/effects/SkRuntimeEffect.h>
namespace GUIStuff {
class ColorRectangleDisplay : public Element {
public:
ColorRectangleDisplay(GUIManager& gui);
void layout(const std::function<SkColor4f()>& colorFunc);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
private:
static constexpr const char* alphaBackgroundSksl =
R"V(
vec4 main(vec2 fragcoord) {
vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0);
vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0);
vec2 flooredCoords = floor(fragcoord / 10);
bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5;
return isDark ? alphaBoard2 : alphaBoard1;
}
)V";
std::function<SkColor4f()> colorFunc;
static sk_sp<SkRuntimeEffect> alphaBackgroundEffect;
static sk_sp<SkShader> get_alpha_background_shader();
};
}

View file

@ -0,0 +1,152 @@
#pragma once
#include "Element.hpp"
#include "SelectableButton.hpp"
#include "../ElementHelpers/ScrollAreaHelpers.hpp"
#include "../ElementHelpers/LayoutHelpers.hpp"
#include "../ElementHelpers/TextLabelHelpers.hpp"
#include "../ElementHelpers/ButtonHelpers.hpp"
#include "SVGIcon.hpp"
namespace GUIStuff {
struct DropdownOptions {
float width = 200.0f;
std::function<void()> onClick;
};
template <typename T> class DropDown : public Element {
public:
DropDown(GUIManager& gui): Element(gui) {}
void layout(T* data, const std::vector<std::string>& selections, const DropdownOptions& options = {}) {
opts = options;
ElementHelpers::left_to_right_layout(gui, CLAY_SIZING_FIXED(opts.width), CLAY_SIZING_FIT(0), [&] {
gui.element<SelectableButton>("dropdown", SelectableButton::Data{
.drawType = SelectableButton::DrawType::FILLED,
.isSelected = isOpen,
.onClick = [&] {
isOpen = !isOpen;
gui.set_to_layout();
},
.innerContent = [&](const SelectableButton::InnerContentCallbackParameters&) {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) },
.padding = {.left = 4, .right = 4},
.childGap = gui.io->theme->childGap1,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
}) {
ElementHelpers::text_label(gui, selections[*data]);
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(ElementHelpers::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIT(ElementHelpers::SMALL_BUTTON_SIZE)}
}
}) {
gui.element<SVGIcon>("dropico", "data/icons/droparrow.svg", isOpen);
}
}
}
});
if(isOpen) {
Clay_ElementId localID = CLAY_ID_LOCAL("DROPDOWN");
Clay_ElementData dropdownElemData = Clay_GetElementData(localID);
float calculatedDropdownMaxHeight = 0.0f;
if(dropdownElemData.found)
calculatedDropdownMaxHeight = std::max(gui.io->windowSize.y() - dropdownElemData.boundingBox.y - 2.0f, 0.0f);
CLAY(localID, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(opts.width), .height = CLAY_SIZING_FIT(0, calculatedDropdownMaxHeight)},
.childGap = 0
},
.backgroundColor = convert_vec4<Clay_Color>(gui.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(4),
.floating = {
.offset = {
.y = 4
},
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
.attachTo = CLAY_ATTACH_TO_PARENT
},
.border = {
.color = convert_vec4<Clay_Color>(gui.io->theme->fillColor2),
.width = CLAY_BORDER_OUTSIDE(1)
}
}) {
ElementHelpers::scroll_area_many_entries(gui, "dropdown scroll area", {
.entryHeight = 18.0f,
.entryCount = selections.size(),
.clipHorizontal = true,
.elementContent = [&](size_t i) {
bool selectedEntry = static_cast<size_t>(*data) == i;
gui.element<LayoutElement>("elem", [&] {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(18)},
.padding = CLAY_PADDING_ALL(0),
.childGap = 0,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
.layoutDirection = CLAY_TOP_TO_BOTTOM
}
}) {
SkColor4f entryColor;
if(selectedEntry)
entryColor = gui.io->theme->backColor2;
else if(hoveringOver.has_value() && hoveringOver.value() == i)
entryColor = gui.io->theme->backColor2;
else
entryColor = gui.io->theme->backColor1;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = {.left = 2, .right = 2, .top = 0, .bottom = 0},
.childGap = 0,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
},
.backgroundColor = convert_vec4<Clay_Color>(entryColor)
}) {
ElementHelpers::text_label(gui, selections[i]);
}
}
}, LayoutElement::Callbacks {
.mouseButton = [&, i](const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
update_hovering_over(i, mouseHovering);
if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down) {
gui.set_post_callback_func([&] { opts.onClick(); });
gui.set_to_layout();
}
return mouseHovering;
},
.mouseMotion = [&, i](const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
update_hovering_over(i, mouseHovering);
return mouseHovering;
}
});
}
});
}
}
});
}
private:
void update_hovering_over(size_t i, bool mouseHovering) {
auto oldHoveringOver = hoveringOver;
if(mouseHovering)
hoveringOver = i;
else if(hoveringOver.has_value() && hoveringOver.value() == i)
hoveringOver = std::nullopt;
if(oldHoveringOver != hoveringOver)
gui.set_to_layout();
}
T* d;
DropdownOptions opts;
bool isOpen = false;
std::optional<size_t> hoveringOver;
};
}

View file

@ -1,68 +1,37 @@
#include "Element.hpp"
#include "Helpers/MathExtras.hpp"
#include <chrono>
namespace GUIStuff {
SkFont get_setup_skfont() {
SkFont font;
font.setLinearMetrics(true);
font.setHinting(SkFontHinting::kNormal);
//font.setForceAutoHinting(true);
font.setSubpixel(true);
font.setBaselineSnap(true);
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
//paint.setAntiAlias(true);
return font;
Element::Element(GUIManager& initGUI):
gui(initGUI) {}
void Element::tick_update() {}
bool Element::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) { return mouseHovering; }
bool Element::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) { return mouseHovering; }
bool Element::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) { return mouseHovering; }
void Element::input_key_callback(const InputManager::KeyCallbackArgs& key) {}
void Element::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {}
void Element::update_bounding_box(Clay_RenderCommand* command) {
boundingBox = get_bb_from_command(command);
}
SkFont UpdateInputData::get_font(float fSize) const {
SkFont f = get_setup_skfont();
f.setTypeface(textTypeface);
f.setSize(fSize);
return f;
void Element::clear_bounding_box() {
boundingBox = std::nullopt;
}
std::shared_ptr<Theme> get_default_dark_mode() {
std::shared_ptr<Theme> theme(std::make_shared<Theme>());
theme->fillColor1 = {0.65f, 0.64f, 1.0f, 1.0f};
theme->fillColor2 = {0.6f, 0.6f, 0.785f, 1.0f};
theme->backColor1 = {0.156f, 0.156f, 0.18f, 1.0f};
theme->backColor2 = {0.24f, 0.24f, 0.29f, 1.0f};
theme->frontColor1 = {0.87f, 0.87f, 0.87f, 1.0f};
theme->frontColor2 = {0.64f, 0.64f, 0.64f, 1.0f};
return theme;
const std::optional<SCollision::AABB<float>>& Element::get_bb() const {
return boundingBox;
}
SCollision::AABB<float> Element::get_bb(Clay_RenderCommand* command) {
bool Element::collides_with_point(const Vector2f& p) const {
if(!boundingBox.has_value())
return false;
return SCollision::collide(p, boundingBox.value());
}
SCollision::AABB<float> Element::get_bb_from_command(Clay_RenderCommand* command) {
return {{command->boundingBox.x, command->boundingBox.y}, {command->boundingBox.x + command->boundingBox.width, command->boundingBox.y + command->boundingBox.height}};
}
void SelectionHelper::update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos) {
hovered = isHovering;
clicked = false;
tapped = false;
justUnselected = false;
bool oldSelected = selected;
if(isLeftClick) {
if(hovered) {
held = true;
clicked = true;
selected = true;
timeClicked = std::chrono::steady_clock::now();
clickPos = cursorPos;
}
else
selected = false;
}
else if(!isLeftHeld) {
if(held && hovered && (std::chrono::steady_clock::now() - timeClicked) < std::chrono::milliseconds(250) && vec_distance_sqrd(clickPos, cursorPos) < (25 * 25))
tapped = true;
held = false;
}
if(!selected && oldSelected)
justUnselected = true;
}
}

View file

@ -1,149 +1,34 @@
#pragma once
#include "Helpers/ConvertVec.hpp"
#include <include/core/SkCanvas.h>
#include "../../TimePoint.hpp"
#include <Eigen/Dense>
#include <include/core/SkFont.h>
#include <include/core/SkFontMgr.h>
#include <include/core/SkTypeface.h>
#include "../../FontData.hpp"
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
#include <nlohmann/json.hpp>
#include "../../RichText/TextBox.hpp"
#include "../../InputManager.hpp"
using namespace Eigen;
#include "GUIStuffHelpers.hpp"
namespace GUIStuff {
class GUIManager;
// Taken from https://github.com/TimothyHoytBSME/ClayMan (rewritten to be a separate struct, and use templates to change size)
template <size_t S> class StringArena {
public:
void reset() {
stringArenaIndex = 0;
}
const char* insert_string_into_arena(std::string_view str) {
size_t strSize = str.size();
if(stringArenaIndex + strSize > S)
throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string");
char* startPtr = &stringArena[stringArenaIndex];
std::copy(str.begin(), str.end(), startPtr);
stringArenaIndex += strSize;
return startPtr;
}
Clay_String std_str_to_clay_str(std::string_view str) {
return Clay_String{.length = static_cast<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
}
private:
std::array<char, S> stringArena;
size_t stringArenaIndex;
};
class Element {
public:
Element(GUIManager& initGUI);
typedef StringArena<4000000> DefaultStringArena;
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA);
virtual bool collides_with_point(const Vector2f& p) const;
SkFont get_setup_skfont();
void update_bounding_box(Clay_RenderCommand* command);
void clear_bounding_box();
const std::optional<SCollision::AABB<float>>& get_bb() const;
virtual ~Element() = default;
struct Theme {
SkColor4f fillColor1 = {0.8f, 0.785f, 1.0f, 1.0f};
SkColor4f fillColor2 = {0.3f, 0.3f, 0.477f, 1.0f};
SkColor4f backColor1 = {0.0f, 0.0f, 0.0f, 1.0f};
SkColor4f backColor2 = {1.0f, 1.0f, 1.0f, 1.0f};
SkColor4f frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f};
SkColor4f frontColor2 = {0.9f, 0.9f, 0.9f, 1.0f};
int16_t zIndex;
SkColor4f errorColor = {1.0f, 0.0f, 0.0f, 1.0f};
SkColor4f warningColor = {1.0f, 1.0f, 0.0f, 1.0f};
virtual void tick_update();
float hoverExpandTime = 0.1f;
uint16_t childGap1 = 10;
uint16_t padding1 = 8;
float windowCorners1 = 8;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, backColor1, backColor2, frontColor1, frontColor2, errorColor, warningColor, hoverExpandTime, childGap1, padding1, windowCorners1)
};
std::shared_ptr<Theme> get_default_dark_mode();
struct UpdateInputData {
struct {
int leftClick = 0;
int rightClick = 0;
bool leftDoubleClick = false;
bool leftHeld = false;
bool rightHeld = false;
Vector2f globalPos{0, 0};
Vector2f pos{0, 0}; // Position relative to window
Vector2f scroll;
} mouse;
struct {
bool left = false;
bool right = false;
bool up = false;
bool down = false;
bool leftShift = false;
bool leftCtrl = false;
bool enter = false;
bool escape = false;
} key;
struct {
std::function<std::string()> textInFunc;
std::optional<std::string> textOut;
} clipboard;
std::shared_ptr<RichText::TextBox> previousRichTextBoxToEdit;
std::shared_ptr<RichText::TextBox> richTextBoxToEdit;
std::shared_ptr<RichText::TextBox::Cursor> richTextBoxToEditCursor;
InputManager::TextInputProperties richTextInputProperties;
std::shared_ptr<SCollision::AABB<float>> richTextBoxToEditRectangle;
SkFont get_font(float fSize) const;
uint16_t fontSize = 16;
std::string textInput;
bool hoverObstructed = false;
std::vector<SCollision::AABB<float>> hoverObstructingAABBs;
std::vector<SCollision::Circle<float>> hoverObstructingCircles;
bool acceptingTextInput = false;
float deltaTime = 0.0f;
bool isTouch = true;
float guiScaleMultiplier = 1.0f;
Vector2f windowPos = {0.0f, 0.0f};
std::shared_ptr<FontData> fonts;
sk_sp<SkTypeface> textTypeface;
DefaultStringArena* strArena;
std::shared_ptr<Theme> theme;
std::unordered_map<std::string, sk_sp<SkSVGDOM>> svgData;
};
class SelectionHelper {
public:
void update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos);
bool held = false;
bool hovered = false;
bool selected = false;
bool clicked = false;
bool justUnselected = false;
bool tapped = false;
Vector2f clickPos;
std::chrono::steady_clock::time_point timeClicked;
};
class Element {
public:
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) = 0;
virtual ~Element() = default;
protected:
static SCollision::AABB<float> get_bb(Clay_RenderCommand* command);
};
// Return true when mouse obstructs what's behind it
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering);
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering);
virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering);
virtual void input_key_callback(const InputManager::KeyCallbackArgs& key);
protected:
static SCollision::AABB<float> get_bb_from_command(Clay_RenderCommand* command);
std::optional<SCollision::AABB<float>> boundingBox;
GUIManager& gui;
};
}

View file

@ -6,182 +6,184 @@
#include <modules/skparagraph/src/ParagraphBuilderImpl.h>
#include <modules/skunicode/include/SkUnicode_icu.h>
#include <Helpers/Random.hpp>
#include "../GUIManager.hpp"
bool GUIStuff::FontPicker::update(UpdateInputData& io, std::string* fontName, GUIManager* gui) {
if(sortedFontList.empty()) {
auto icu = SkUnicodes::ICU::Make();
std::set<std::string> sortedFontSet;
namespace GUIStuff {
if(io.fonts->localFontMgr) {
for(int i = 0; i < io.fonts->localFontMgr->countFamilies(); i++) {
SkString familyNameSkString;
io.fonts->localFontMgr->getFamilyName(i, &familyNameSkString);
if(familyNameSkString.isEmpty())
continue;
std::string familyName(familyNameSkString.c_str(), familyNameSkString.size());
sortedFontSet.emplace(familyName);
}
}
for(int i = 0; i < io.fonts->defaultFontMgr->countFamilies(); i++) {
SkString familyNameSkString;
io.fonts->defaultFontMgr->getFamilyName(i, &familyNameSkString);
if(familyNameSkString.isEmpty())
continue;
std::string familyName(familyNameSkString.c_str(), familyNameSkString.size());
sortedFontSet.emplace(familyName);
}
sortedFontList = std::vector<std::string>(sortedFontSet.begin(), sortedFontSet.end());
sortedFontListLowercase = sortedFontList;
for(std::string& s : sortedFontListLowercase)
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
}
FontPicker::FontPicker(GUIManager& gui): Element(gui) {}
if(!fontName)
return false;
void FontPicker::layout(const Data& data) {
d = data;
//auto& io = *gui.io;
bool fontChangedFromTextbox = false;
bool fontChangedFromSelection = false;
//if(sortedFontList.empty()) {
// auto icu = SkUnicodes::ICU::Make();
// std::set<std::string> sortedFontSet;
bool clickedOut = io.mouse.leftClick;
// if(io.fonts->localFontMgr) {
// for(int i = 0; i < io.fonts->localFontMgr->countFamilies(); i++) {
// SkString familyNameSkString;
// io.fonts->localFontMgr->getFamilyName(i, &familyNameSkString);
// if(familyNameSkString.isEmpty())
// continue;
// std::string familyName(familyNameSkString.c_str(), familyNameSkString.size());
// sortedFontSet.emplace(familyName);
// }
// }
// for(int i = 0; i < io.fonts->defaultFontMgr->countFamilies(); i++) {
// SkString familyNameSkString;
// io.fonts->defaultFontMgr->getFamilyName(i, &familyNameSkString);
// if(familyNameSkString.isEmpty())
// continue;
// std::string familyName(familyNameSkString.c_str(), familyNameSkString.size());
// sortedFontSet.emplace(familyName);
// }
// sortedFontList = std::vector<std::string>(sortedFontSet.begin(), sortedFontSet.end());
// sortedFontListLowercase = sortedFontList;
// for(std::string& s : sortedFontListLowercase)
// std::transform(s.begin(), s.end(), s.begin(), ::tolower);
//}
const float ENTRY_HEIGHT = 25.0f;
//bool fontChangedFromTextbox = false;
//bool fontChangedFromSelection = false;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0)},
.childGap = 4
}
}) {
fontChangedFromTextbox = gui->input_text("Font textbox", fontName, true, [&](SelectionHelper& s) {
if(s.selected) {
dropdownOpen = true;
clickedOut = false;
}
});
//bool clickedOut = io.mouse.leftClick;
auto it = std::find(sortedFontList.begin(), sortedFontList.end(), *fontName);
size_t val = std::numeric_limits<size_t>::max();
if(it == sortedFontList.end()) {
std::string lowerFamilyName = *fontName;
std::transform(lowerFamilyName.begin(), lowerFamilyName.end(), lowerFamilyName.begin(), ::tolower);
auto itLower = std::find(sortedFontListLowercase.begin(), sortedFontListLowercase.end(), lowerFamilyName);
if(itLower != sortedFontListLowercase.end()) {
val = itLower - sortedFontListLowercase.begin();
*fontName = sortedFontList[val];
}
}
else
val = it - sortedFontList.begin();
//const float ENTRY_HEIGHT = 25.0f;
if(gui->svg_icon_button("Open font dropdown", "data/icons/droparrowbold.svg", dropdownOpen, GUIManager::SMALL_BUTTON_SIZE)) {
dropdownOpen = !dropdownOpen;
clickedOut = false;
}
if(dropdownOpen) {
Clay_ElementId localID = CLAY_ID_LOCAL("Font picker floating element");
CLAY(localID, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, 300)},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(4),
.floating = {
.offset = {
.y = 2
},
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
.attachTo = CLAY_ATTACH_TO_PARENT
},
.border = {
.color = convert_vec4<Clay_Color>(io.theme->fillColor2),
.width = CLAY_BORDER_OUTSIDE(1)
}
}) {
gui->obstructing_window(localID);
if(Clay_Hovered())
clickedOut = false;
gui->scroll_bar_many_entries_area("Font Selector", ENTRY_HEIGHT, sortedFontList.size(), true, [fontName, gui, &fontChangedFromSelection, &sortedFontList = sortedFontList, val, &io = io](size_t i, bool listHovered) {
bool selectedEntry = val == i;
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
pStyle.setTextHeightBehavior(skia::textlayout::kDisableAll);
skia::textlayout::StrutStyle strutStyle;
strutStyle.setStrutEnabled(true);
strutStyle.setForceStrutHeight(true);
pStyle.setStrutStyle(strutStyle);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(io.fontSize);
tStyle.setFontFamilies({SkString{sortedFontList[i].c_str()}, SkString{"Roboto"}});
tStyle.setForegroundColor(SkPaint{io.theme->frontColor1});
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl pBuilder(pStyle, io.fonts->collection, SkUnicodes::ICU::Make());
tStyle.setFontStyle(SkFontStyle::Normal());
pBuilder.pushStyle(tStyle);
pBuilder.addText(sortedFontList[i].c_str(), sortedFontList[i].size());
auto p = pBuilder.Build();
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(25)},
.childGap = 1,
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
}
}) {
SkColor4f entryColor;
if(selectedEntry)
entryColor = io.theme->backColor2;
else if(Clay_Hovered())
entryColor = io.theme->backColor2;
else
entryColor = io.theme->backColor1;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(25)},
.padding = {.left = 2, .right = 2},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
},
.backgroundColor = convert_vec4<Clay_Color>(entryColor)
}) {
gui->text_paragraph("font name", std::move(p), 10000.0f); // Setting width to numeric_limits::max will lead to multiplying it by any number greater than 1 to = infinity, which will cause issues
if(io.mouse.leftClick && Clay_Hovered()) {
*fontName = sortedFontList[i];
fontChangedFromSelection = true;
}
}
}
}, [ENTRY_HEIGHT, fontChangedFromTextbox, io = &io, val](float scrollContentHeight, float containerHeight, float& scrollAmount) {
if(fontChangedFromTextbox && !io->mouse.leftClick && val != std::numeric_limits<size_t>::max())
scrollAmount = -static_cast<float>(val) * ENTRY_HEIGHT;
});
#ifdef __EMSCRIPTEN__
CLAY_AUTO_ID({
.layout = {
.padding = CLAY_PADDING_ALL(io.theme->padding1)
}}) {
gui->text_label_light_centered("Local fonts not usable in web version");
}
#endif
}
}
}
//CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0)},
// .childGap = 4
// }
//}) {
// fontChangedFromTextbox = gui.input_text("Font textbox", fontName, true, [&](SelectionHelper& s) {
// if(s.selected) {
// dropdownOpen = true;
// clickedOut = false;
// }
// });
if(clickedOut)
dropdownOpen = false;
return fontChangedFromSelection | fontChangedFromTextbox;
// auto it = std::find(sortedFontList.begin(), sortedFontList.end(), *fontName);
// size_t val = std::numeric_limits<size_t>::max();
// if(it == sortedFontList.end()) {
// std::string lowerFamilyName = *fontName;
// std::transform(lowerFamilyName.begin(), lowerFamilyName.end(), lowerFamilyName.begin(), ::tolower);
// auto itLower = std::find(sortedFontListLowercase.begin(), sortedFontListLowercase.end(), lowerFamilyName);
// if(itLower != sortedFontListLowercase.end()) {
// val = itLower - sortedFontListLowercase.begin();
// *fontName = sortedFontList[val];
// }
// }
// else
// val = it - sortedFontList.begin();
// if(gui.svg_icon_button("Open font dropdown", "data/icons/droparrowbold.svg", dropdownOpen, GUIManager::SMALL_BUTTON_SIZE)) {
// dropdownOpen = !dropdownOpen;
// clickedOut = false;
// }
// if(dropdownOpen) {
// Clay_ElementId localID = CLAY_ID_LOCAL("Font picker floating element");
// CLAY(localID, {
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIT(0, 300)},
// .layoutDirection = CLAY_TOP_TO_BOTTOM
// },
// .backgroundColor = convert_vec4<Clay_Color>(io.theme->backColor1),
// .cornerRadius = CLAY_CORNER_RADIUS(4),
// .floating = {
// .offset = {
// .y = 2
// },
// .attachPoints = {
// .element = CLAY_ATTACH_POINT_LEFT_TOP,
// .parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
// },
// .attachTo = CLAY_ATTACH_TO_PARENT
// },
// .border = {
// .color = convert_vec4<Clay_Color>(io.theme->fillColor2),
// .width = CLAY_BORDER_OUTSIDE(1)
// }
// }) {
// gui->obstructing_window(localID);
// if(Clay_Hovered())
// clickedOut = false;
// gui->scroll_bar_many_entries_area("Font Selector", ENTRY_HEIGHT, sortedFontList.size(), true, [fontName, gui, &fontChangedFromSelection, &sortedFontList = sortedFontList, val, &io = io](size_t i, bool listHovered) {
// bool selectedEntry = val == i;
//
// skia::textlayout::ParagraphStyle pStyle;
// pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
// pStyle.setTextHeightBehavior(skia::textlayout::kDisableAll);
//
// skia::textlayout::StrutStyle strutStyle;
// strutStyle.setStrutEnabled(true);
// strutStyle.setForceStrutHeight(true);
// pStyle.setStrutStyle(strutStyle);
//
// skia::textlayout::TextStyle tStyle;
// tStyle.setFontSize(io.fontSize);
// tStyle.setFontFamilies({SkString{sortedFontList[i].c_str()}, SkString{"Roboto"}});
// tStyle.setForegroundColor(SkPaint{io.theme->frontColor1});
// pStyle.setTextStyle(tStyle);
//
// skia::textlayout::ParagraphBuilderImpl pBuilder(pStyle, io.fonts->collection, SkUnicodes::ICU::Make());
// tStyle.setFontStyle(SkFontStyle::Normal());
//
// pBuilder.pushStyle(tStyle);
// pBuilder.addText(sortedFontList[i].c_str(), sortedFontList[i].size());
//
// auto p = pBuilder.Build();
//
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_FIXED(25)},
// .childGap = 1,
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
// .layoutDirection = CLAY_TOP_TO_BOTTOM
// }
// }) {
// SkColor4f entryColor;
// if(selectedEntry)
// entryColor = io.theme->backColor2;
// else if(Clay_Hovered())
// entryColor = io.theme->backColor2;
// else
// entryColor = io.theme->backColor1;
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(25)},
// .padding = {.left = 2, .right = 2},
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
// },
// .backgroundColor = convert_vec4<Clay_Color>(entryColor)
// }) {
// gui->text_paragraph("font name", std::move(p), 10000.0f); // Setting width to numeric_limits::max will lead to multiplying it by any number greater than 1 to = infinity, which will cause issues
//
// if(io.mouse.leftClick && Clay_Hovered()) {
// *fontName = sortedFontList[i];
// fontChangedFromSelection = true;
// }
// }
// }
// }, [ENTRY_HEIGHT, fontChangedFromTextbox, io = &io, val](float scrollContentHeight, float containerHeight, float& scrollAmount) {
// if(fontChangedFromTextbox && !io->mouse.leftClick && val != std::numeric_limits<size_t>::max())
// scrollAmount = -static_cast<float>(val) * ENTRY_HEIGHT;
// });
// #ifdef __EMSCRIPTEN__
// CLAY_AUTO_ID({
// .layout = {
// .padding = CLAY_PADDING_ALL(io.theme->padding1)
// }}) {
// gui->text_label_light_centered("Local fonts not usable in web version");
// }
// #endif
// }
// }
//}
//if(clickedOut)
// dropdownOpen = false;
}
void GUIStuff::FontPicker::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
}

View file

@ -1,15 +1,19 @@
#pragma once
#include "Element.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
class FontPicker : public Element {
public:
bool update(UpdateInputData& io, std::string* fontName, GUIManager* gui);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
SelectionHelper selection;
struct Data {
std::string* fontName = nullptr;
std::function<void()> onFontChange;
};
FontPicker(GUIManager& gui);
void layout(const Data& data);
private:
Data d;
std::vector<std::string> sortedFontList;
std::vector<std::string> sortedFontListLowercase;
bool dropdownOpen = false;

View file

@ -0,0 +1,64 @@
#include "Element.hpp"
#include "Helpers/MathExtras.hpp"
#include <chrono>
namespace GUIStuff {
SkFont get_setup_skfont() {
SkFont font;
font.setLinearMetrics(true);
font.setHinting(SkFontHinting::kNormal);
//font.setForceAutoHinting(true);
font.setSubpixel(true);
font.setBaselineSnap(true);
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
//paint.setAntiAlias(true);
return font;
}
SkFont UpdateInputData::get_font(float fSize) const {
SkFont f = get_setup_skfont();
f.setTypeface(textTypeface);
f.setSize(fSize);
return f;
}
std::shared_ptr<Theme> get_default_dark_mode() {
std::shared_ptr<Theme> theme(std::make_shared<Theme>());
theme->fillColor1 = {0.65f, 0.64f, 1.0f, 1.0f};
theme->fillColor2 = {0.6f, 0.6f, 0.785f, 1.0f};
theme->backColor1 = {0.156f, 0.156f, 0.18f, 1.0f};
theme->backColor2 = {0.24f, 0.24f, 0.29f, 1.0f};
theme->frontColor1 = {0.87f, 0.87f, 0.87f, 1.0f};
theme->frontColor2 = {0.64f, 0.64f, 0.64f, 1.0f};
return theme;
}
void SelectionHelper::update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos) {
hovered = isHovering;
clicked = false;
tapped = false;
justUnselected = false;
bool oldSelected = selected;
if(isLeftClick) {
if(hovered) {
held = true;
clicked = true;
selected = true;
timeClicked = std::chrono::steady_clock::now();
clickPos = cursorPos;
}
else
selected = false;
}
else if(!isLeftHeld) {
if(held && hovered && (std::chrono::steady_clock::now() - timeClicked) < std::chrono::milliseconds(250) && vec_distance_sqrd(clickPos, cursorPos) < (25 * 25))
tapped = true;
held = false;
}
if(!selected && oldSelected)
justUnselected = true;
}
}

View file

@ -0,0 +1,105 @@
#pragma once
#include "Helpers/ConvertVec.hpp"
#include <include/core/SkCanvas.h>
#include "../../TimePoint.hpp"
#include <Eigen/Dense>
#include <include/core/SkFont.h>
#include <include/core/SkFontMgr.h>
#include <include/core/SkTypeface.h>
#include "../../FontData.hpp"
#include <modules/skparagraph/include/FontCollection.h>
#include <modules/svg/include/SkSVGDOM.h>
#include <Helpers/SCollision.hpp>
#include <Helpers/Serializers.hpp>
#include <nlohmann/json.hpp>
#include "../../RichText/TextBox.hpp"
#include "../../InputManager.hpp"
using namespace Eigen;
namespace GUIStuff {
// Taken from https://github.com/TimothyHoytBSME/ClayMan (rewritten to be a separate struct, and use templates to change size)
template <size_t S> class StringArena {
public:
void reset() {
stringArenaIndex = 0;
}
const char* insert_string_into_arena(std::string_view str) {
size_t strSize = str.size();
if(stringArenaIndex + strSize > S)
throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string");
char* startPtr = &stringArena[stringArenaIndex];
std::copy(str.begin(), str.end(), startPtr);
stringArenaIndex += strSize;
return startPtr;
}
Clay_String std_str_to_clay_str(std::string_view str) {
return Clay_String{.length = static_cast<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
}
private:
std::array<char, S> stringArena;
size_t stringArenaIndex;
};
typedef StringArena<4000000> DefaultStringArena;
SkFont get_setup_skfont();
struct Theme {
SkColor4f fillColor1 = {0.8f, 0.785f, 1.0f, 1.0f};
SkColor4f fillColor2 = {0.3f, 0.3f, 0.477f, 1.0f};
SkColor4f backColor1 = {0.0f, 0.0f, 0.0f, 1.0f};
SkColor4f backColor2 = {1.0f, 1.0f, 1.0f, 1.0f};
SkColor4f frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f};
SkColor4f frontColor2 = {0.9f, 0.9f, 0.9f, 1.0f};
SkColor4f errorColor = {1.0f, 0.0f, 0.0f, 1.0f};
SkColor4f warningColor = {1.0f, 1.0f, 0.0f, 1.0f};
float hoverExpandTime = 0.1f;
uint16_t childGap1 = 10;
uint16_t padding1 = 8;
float windowCorners1 = 8;
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, backColor1, backColor2, frontColor1, frontColor2, errorColor, warningColor, hoverExpandTime, childGap1, padding1, windowCorners1)
};
std::shared_ptr<Theme> get_default_dark_mode();
struct UpdateInputData {
InputManager* input;
std::function<void()> layoutRun;
SkFont get_font(float fSize) const;
uint16_t fontSize = 16;
float deltaTime = 0.0f;
float guiScaleMultiplier = 1.0f;
Vector2f windowPos = {0.0f, 0.0f};
Vector2f windowSize = {0.0f, 0.0f};
std::shared_ptr<FontData> fonts;
sk_sp<SkTypeface> textTypeface;
DefaultStringArena* strArena;
std::shared_ptr<Theme> theme;
std::unordered_map<std::string, sk_sp<SkSVGDOM>> svgData;
};
class SelectionHelper {
public:
void update(bool isHovering, bool isLeftClick, bool isLeftHeld, const Vector2f& cursorPos);
bool held = false;
bool hovered = false;
bool selected = false;
bool clicked = false;
bool justUnselected = false;
bool tapped = false;
Vector2f clickPos;
std::chrono::steady_clock::time_point timeClicked;
};
}

View file

@ -0,0 +1,34 @@
#include "LayoutElement.hpp"
namespace GUIStuff {
LayoutElement::LayoutElement(GUIManager& gui): Element(gui) {}
void LayoutElement::layout(const std::function<void()>& layout, const Callbacks& c) {
this->c = c;
layout();
}
bool LayoutElement::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
if(c.mouseButton)
return c.mouseButton(button, mouseHovering);
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool LayoutElement::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
if(c.mouseMotion)
return c.mouseMotion(motion, mouseHovering);
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
bool LayoutElement::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) {
if(c.mouseWheel)
return c.mouseWheel(wheel, mouseHovering);
return Element::input_mouse_wheel_callback(wheel, mouseHovering);
}
void LayoutElement::input_key_callback(const InputManager::KeyCallbackArgs& key) {
if(c.key) c.key(key);
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "Element.hpp"
namespace GUIStuff {
class LayoutElement : public Element {
public:
LayoutElement(GUIManager& gui);
struct Callbacks {
std::function<bool(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering)> mouseButton;
std::function<bool(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering)> mouseMotion;
std::function<bool(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering)> mouseWheel;
std::function<void(const InputManager::KeyCallbackArgs& key)> key;
};
void layout(const std::function<void()>& layout, const Callbacks& c = {});
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) override;
virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override;
private:
Callbacks c;
};
}

View file

@ -5,76 +5,63 @@
namespace GUIStuff {
void MovableTabList::update(UpdateInputData& io, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate) {
size_t currentSelectedTab = selectedTab;
if(tabNames != oldTabNames) {
buttons = std::vector<SelectableButton>(tabNames.size());
closeButtons = std::vector<SelectableButton>(tabNames.size());
closeIcons = std::vector<SVGIcon>(tabNames.size());
tabIcons = std::vector<SVGIcon>(tabNames.size());
oldTabNames = tabNames;
}
MovableTabList::MovableTabList(GUIManager& gui): Element(gui) {}
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)},
.padding = CLAY_PADDING_ALL(0),
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
},
.clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()}
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(elemUpdate)
elemUpdate();
for(size_t i = 0; i < buttons.size(); i++) {
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)},
}
}) {
buttons[i].update(io, SelectableButton::DrawType::FILLED, [&](SelectionHelper& s, bool iS) {
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = CLAY_PADDING_ALL(0),
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
}
}) {
if(!tabNames[i].first.empty()) {
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)},
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
}
}) {
tabIcons[i].update(io, tabNames[i].first, iS, nullptr);
}
}
CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4<Clay_Color>(io.theme->frontColor1), .fontSize = io.fontSize }));
if(s.clicked)
selectedTab = i;
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)},
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT
}
}) {
closeButtons[i].update(io, SelectableButton::DrawType::TRANSPARENT_ALL, [&](SelectionHelper& s, bool iS) {
if(s.clicked)
closedTab = i;
closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr);
}, false);
}
}
}, currentSelectedTab == i);
}
}
}
}
void MovableTabList::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
void MovableTabList::layout(const Data& d) {
//CLAY_AUTO_ID({.layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::BIG_BUTTON_SIZE)},
// .padding = CLAY_PADDING_ALL(0),
// .childGap = 4,
// .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER },
// .layoutDirection = CLAY_LEFT_TO_RIGHT
// },
// .clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()}
//}) {
// for(size_t i = 0; i < d.tabNames.size(); i++) {
// CLAY_AUTO_ID({.layout = {
// .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_GROW(0)},
// }
// }) {
// buttons[i].update(io, SelectableButton::DrawType::FILLED, [&](SelectionHelper& s, bool iS) {
// CLAY_AUTO_ID({.layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
// .padding = CLAY_PADDING_ALL(0),
// .childGap = 4,
// .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
// .layoutDirection = CLAY_LEFT_TO_RIGHT
// }
// }) {
// if(!tabNames[i].first.empty()) {
// CLAY_AUTO_ID({.layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)},
// .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
// .layoutDirection = CLAY_LEFT_TO_RIGHT
// }
// }) {
// tabIcons[i].update(io, tabNames[i].first, iS, nullptr);
// }
// }
// CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4<Clay_Color>(io.theme->frontColor1), .fontSize = io.fontSize }));
// if(s.clicked)
// selectedTab = i;
// CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
// CLAY_AUTO_ID({.layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE), .height = CLAY_SIZING_FIXED(GUIStuff::GUIManager::SMALL_BUTTON_SIZE)},
// .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
// .layoutDirection = CLAY_LEFT_TO_RIGHT
// }
// }) {
// closeButtons[i].update(io, SelectableButton::DrawType::TRANSPARENT_ALL, [&](SelectionHelper& s, bool iS) {
// if(s.clicked)
// closedTab = i;
// closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr);
// }, false);
// }
// }
// }, currentSelectedTab == i);
// }
// }
//}
}
}

View file

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

View file

@ -1,15 +1,19 @@
#pragma once
#include "Element.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> class NumberSlider : public Element {
public:
bool update(UpdateInputData& io, T* data, T minNew, T maxNew, const std::function<void()>& elemUpdate) {
bool isUpdating = false;
NumberSlider(GUIManager& gui):
Element(gui) {}
min = minNew;
max = maxNew;
void layout(T* data, T minData, T maxData, const std::function<void()>& onChange) {
this->data = data;
this->minData = minData;
this->maxData = maxData;
this->onChange = onChange;
CLAY_AUTO_ID({
.layout = {
@ -17,43 +21,27 @@ template <typename T> class NumberSlider : public Element {
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(selection.held && data) {
float fracPosOnSlider = (io.mouse.pos.x() - bb.min.x()) / bb.width();
*data = static_cast<T>(std::clamp<double>(std::lerp<double>(min, max, fracPosOnSlider), min, max)); // Clamp as double then cast so that unsigned types dont wrap on clamp
isUpdating = true;
}
if(elemUpdate)
elemUpdate();
}
if(data)
cachedData = *data;
else
cachedData = 0.0;
return isUpdating;
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override {
bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, selection.hovered, io.theme->hoverExpandTime);
float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, isHovering, io.theme->hoverExpandTime);
//static BezierEasing easeHeight(0.75, 0.25, 0.25, 0.75);
static BezierEasing easeHeight(0.68, -1.55, 0.265, 2.55);
float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, selection.held, 0.3));
float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, isHeld, 0.3));
float holderRadius = lerp_vec(4.0, 5.0, lerpTimeHover);
float holderHeight = lerp_vec(4.0, 10.0, lerpTimeHeld);
const float yChange = bb.height() * 0.5f - holderRadius * 0.5f;
float holderPos = lerp_time<float>(cachedData, max, min) * bb.width();
float holderPos = lerp_time<float>(*data, maxData, minData) * bb.width();
SkRect barFull = SkRect::MakeXYWH(0.0f, yChange, holderPos, holderRadius);
SkRect barEmpty = SkRect::MakeXYWH(holderPos, yChange, bb.width() - holderPos, holderRadius);
@ -85,14 +73,35 @@ template <typename T> class NumberSlider : public Element {
canvas->restore();
}
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override {
isHovering = mouseHovering;
isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down;
update_slider_pos(button.pos);
return Element::input_mouse_button_callback(button, mouseHovering);
}
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override {
update_slider_pos(motion.pos);
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
private:
SCollision::AABB<float> bb;
SelectionHelper selection;
void update_slider_pos(const Vector2f& p) {
gui.set_post_callback_func([&, p] {
if(isHeld && boundingBox.has_value()) {
float fracPosOnSlider = (p.x() - boundingBox.value().min.x()) / boundingBox.value().width();
*data = static_cast<T>(std::clamp<double>(std::lerp<double>(minData, maxData, fracPosOnSlider), minData, maxData)); // Clamp as double then cast so that unsigned types dont wrap on clamp
onChange();
}
});
}
T min = 0.0;
T max = 1.0;
T cachedData = 0.0;
bool isHeld = false;
bool isHovering = false;
T* data = nullptr;
T minData = 0.0;
T maxData = 1.0;
std::function<void()> onChange;
float hoverAnimation = 0.0;
float holdAnimation = 0.0;

View file

@ -11,61 +11,84 @@ constexpr float CIRCLE_END = 150.0f;
constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.2;
constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25;
void PaintCircleMenu::update(GUIManager& gui, const Data& data, const std::function<void()>& elemUpdate) {
auto& io = *gui.io;
CLAY_AUTO_ID({.layout = {
PaintCircleMenu::PaintCircleMenu(GUIManager& gui):
Element(gui) {}
void PaintCircleMenu::layout(const Data& data, const std::function<void()>& onChange) {
d = data;
this->onChange = onChange;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
},
.custom = { .customData = this }
}) {
bool isHoveredNoObstruction = Clay_Hovered() && !io.hoverObstructed;
Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized();
float distFromCenter = vec_distance(io.mouse.pos, bb.center());
if(data.newRotationAngle) {
rotateBarSelect.update(isHoveredNoObstruction && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END, io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(rotateBarSelect.held) {
double& newRotationAngle = *data.newRotationAngle;
newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
if(rotateBarSelect.hovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles
}
}
bool PaintCircleMenu::collides_with_point(const Vector2f& p) const {
return boundingBox.has_value() && SCollision::collide(SCollision::Circle<float>(boundingBox.value().center(), CIRCLE_END), p);
}
bool PaintCircleMenu::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
isHovering = mouseHovering;
isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down;
update_paint_circle_menu_mouse(button.pos, button.button == InputManager::MouseButton::LEFT && button.down);
return mouseHovering && collides_with_point(button.pos);
}
bool PaintCircleMenu::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
update_paint_circle_menu_mouse(motion.pos, false);
return mouseHovering && collides_with_point(motion.pos);
}
void PaintCircleMenu::update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked) {
if(boundingBox.has_value()) {
Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized();
float distFromCenter = vec_distance(p, boundingBox.value().center());
isRotateBarHovered = isHovering && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END;
isRotateBarHeld = isRotateBarHovered && isHeld;
isColorBarHovered = isHovering && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START;
if(isColorBarHovered) {
double selectionAngle = std::atan2(-vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * d.palette.size();
colorSelectionIndex = std::clamp<unsigned>(colorSelectionIndex, 0, d.palette.size() - 1);
}
if(isRotateBarHeld) {
gui.set_post_callback_func([&, vecFromCenter] {
auto& rotationAngle = *d.rotationAngle;
rotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
if(isRotateBarHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) {
newRotationAngle = snapPos;
if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0
newRotationAngle = 0;
if(std::fabs(rotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) {
rotationAngle = snapPos;
if(rotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0
rotationAngle = 0;
break;
}
}
d.onRotate();
}
}
else
*data.newRotationAngle = data.currentRotationAngle;
});
}
if(!data.palette.empty() && data.selectedColor) {
colorBarSelect.update(isHoveredNoObstruction && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START, io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(colorBarSelect.hovered) {
Vector4f& selectedColor = *data.selectedColor;
double selectionAngle = std::atan2(-vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * data.palette.size();
colorSelectionIndex = std::clamp<unsigned>(colorSelectionIndex, 0, data.palette.size() - 1);
if(colorBarSelect.clicked) {
selectedColor.x() = data.palette[colorSelectionIndex].x();
selectedColor.y() = data.palette[colorSelectionIndex].y();
selectedColor.z() = data.palette[colorSelectionIndex].z();
}
}
else if(isColorBarHovered && leftClicked) {
gui.set_post_callback_func([&] {
Vector4f& selectedColor = *d.selectedColor;
selectedColor.x() = d.palette[colorSelectionIndex].x();
selectedColor.y() = d.palette[colorSelectionIndex].y();
selectedColor.z() = d.palette[colorSelectionIndex].z();
d.onPaletteClick();
});
}
io.hoverObstructingCircles.emplace_back(bb.center() + gui.windowPos, CIRCLE_END);
if(SCollision::collide(io.hoverObstructingCircles.back(), io.mouse.pos))
io.hoverObstructed = true;
if(elemUpdate)
elemUpdate();
}
d = data;
}
void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.center().x(), bb.center().y());
@ -80,10 +103,7 @@ void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_Rend
}
void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA) {
if(!d.newRotationAngle)
return;
double rotationAngle = d.currentRotationAngle;
double rotationAngle = *d.rotationAngle;
float rotateBarMiddleRadius = (CIRCLE_END + ROTATE_START) / 2.0f;
SkPaint rotateBarFill(io.theme->backColor2);
rotateBarFill.setAntiAlias(skiaAA);
@ -91,7 +111,7 @@ void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, boo
rotateBarFill.setStrokeWidth(CIRCLE_END - ROTATE_START);
canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill);
if(rotateBarSelect.hovered) {
if(isRotateBarHovered) {
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)};
SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2);
@ -103,9 +123,9 @@ void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, boo
SkPaint rotateBarHolderFill;
rotateBarHolderFill.setAntiAlias(skiaAA);
if(rotateBarSelect.held)
if(isRotateBarHeld)
rotateBarHolderFill.setColor4f(io.theme->fillColor1);
else if(rotateBarSelect.hovered)
else if(isRotateBarHovered)
rotateBarHolderFill.setColor4f(io.theme->fillColor2);
else
rotateBarHolderFill.setColor4f(io.theme->frontColor1);
@ -161,7 +181,7 @@ void PaintCircleMenu::draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bo
SkRect startOval = SkRect::MakeLTRB(-PALETTE_START, -PALETTE_START, PALETTE_START, PALETTE_START);
SkRect endOval = SkRect::MakeLTRB(-ROTATE_START, -ROTATE_START, ROTATE_START, ROTATE_START);
if(colorBarSelect.hovered) {
if(isColorBarHovered) {
SkPathBuilder selectionPathBuilder;
double angleStart = colorSelectionIndex * colorDistribution;
double angleEnd = (colorSelectionIndex + 1) * colorDistribution;

View file

@ -3,26 +3,34 @@
namespace GUIStuff {
class GUIManager;
class PaintCircleMenu : public Element {
public:
PaintCircleMenu(GUIManager& gui);
struct Data {
double currentRotationAngle = 0.0;
double* newRotationAngle = nullptr;
double* rotationAngle = nullptr;
Vector4f* selectedColor = nullptr;
std::vector<Vector3f> palette;
std::function<void()> onRotate;
std::function<void()> onPaletteClick;
};
void update(GUIManager& gui, const Data& data, const std::function<void()>& elemUpdate);
void layout(const Data& data, const std::function<void()>& onChange);
virtual bool collides_with_point(const Vector2f& p) const override;
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
private:
void draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
void draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
Data d;
SCollision::AABB<float> bb;
SelectionHelper rotateBarSelect;
SelectionHelper colorBarSelect;
std::function<void()> onChange;
unsigned colorSelectionIndex = 0;
bool isHovering = false;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
bool isColorBarHovered = false;
bool isHeld = false;
void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked);
};
}

View file

@ -0,0 +1,39 @@
#include "PositionAdjustingPopupMenu.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
PositionAdjustingPopupMenu::PositionAdjustingPopupMenu(GUIManager& gui): Element(gui) {}
void PositionAdjustingPopupMenu::layout(Vector2f popupPos, const std::function<void()>& innerContent) {
if(layoutElement && layoutElement->get_bb().has_value()) {
auto& bb = layoutElement->get_bb().value();
if((popupPos.y() + bb.height()) > gui.io->windowSize.y())
popupPos.y() -= bb.height();
if((popupPos.x() + bb.width()) > gui.io->windowSize.x())
popupPos.x() -= bb.width();
}
layoutElement = gui.element<LayoutElement>("popup", [&]{
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIT(100), .height = CLAY_SIZING_FIT(0)},
.padding = CLAY_PADDING_ALL(gui.io->theme->padding1),
.childGap = 1,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = CLAY_TOP_TO_BOTTOM
},
.backgroundColor = convert_vec4<Clay_Color>(gui.io->theme->backColor1),
.cornerRadius = CLAY_CORNER_RADIUS(gui.io->theme->windowCorners1),
.floating = {
.offset = {popupPos.x(), popupPos.y()},
.zIndex = gui.zIndex,
.attachTo = CLAY_ATTACH_TO_ROOT
}
}) {
innerContent();
}
});
}
}

View file

@ -0,0 +1,16 @@
#pragma once
#include "Element.hpp"
#include "LayoutElement.hpp"
namespace GUIStuff {
class PositionAdjustingPopupMenu : public Element {
public:
PositionAdjustingPopupMenu(GUIManager& gui);
void layout(Vector2f popupPos, const std::function<void()>& innerContent);
private:
LayoutElement* layoutElement = nullptr;
};
}

View file

@ -1,33 +1,44 @@
#include "RadioButton.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
void RadioButton::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
RadioButton::RadioButton(GUIManager& gui):
Element(gui) {}
void RadioButton::layout(const std::function<bool()>& isTicked, const std::function<void()>& onClick) {
this->onClick = onClick;
this->isTicked = isTicked;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(isTicked != newIsTicked) {
hoverAnimation2 = RADIOBUTTON_ANIMATION_TIME;
isTicked = newIsTicked;
}
if(elemUpdate)
elemUpdate();
}
}) {}
}
bool RadioButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
isHovering = mouseHovering;
if(isHovering && button.button == InputManager::MouseButton::LEFT && button.down)
gui.set_post_callback_func([&] { if(onClick) onClick(); });
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool RadioButton::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
isHovering = mouseHovering;
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
canvas->scale(bb.width(), bb.height());
canvas->translate(0.5f, 0.5f);
if(isTicked) {
if(isTicked()) {
SkPaint p;
p.setAntiAlias(skiaAA);
p.setColor4f(convert_vec4<SkColor4f>(io.theme->fillColor1));
@ -40,7 +51,7 @@ void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCo
innerCircleP.setStyle(SkPaint::kFill_Style);
static BezierEasing easeRadius(0.68, -2.55, 0.265, 3.55);
float lerpTime2 = easeRadius(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, RADIOBUTTON_ANIMATION_TIME));
float lerpTime2 = easeRadius(smooth_two_way_time(hoverAnimation2, io.deltaTime, isHovering, RADIOBUTTON_ANIMATION_TIME));
float innerCircleRadius = lerp_vec(0.3f, 0.2f, lerpTime2);
canvas->drawCircle(0.0f, 0.0f, innerCircleRadius, innerCircleP);
@ -48,7 +59,7 @@ void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCo
else {
SkPaint p;
p.setAntiAlias(skiaAA);
p.setColor4f(convert_vec4<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->backColor2));
p.setColor4f(convert_vec4<SkColor4f>(isHovering ? io.theme->fillColor1 : io.theme->backColor2));
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.15f);
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);

View file

@ -5,15 +5,18 @@ namespace GUIStuff {
class RadioButton : public Element {
public:
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
RadioButton(GUIManager& gui);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
SelectionHelper selection;
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
void layout(const std::function<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3;
bool isTicked = false;
bool isHovering = false;
float hoverAnimation2 = 0.0;
std::function<bool()> isTicked;
std::function<void()> onClick;
};
}

View file

@ -1,5 +1,6 @@
#include "RotateWheel.hpp"
#include "Helpers/ConvertVec.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
@ -8,39 +9,20 @@ constexpr float WHEEL_WIDTH = 13.0f;
constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.3;
constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25;
void RotateWheel::update(UpdateInputData& io, double* newRotationAnglePtr, const std::function<void()>& elemUpdate) {
rotateAngle = 0.0f;
if(!newRotationAnglePtr)
return;
RotateWheel::RotateWheel(GUIManager& gui):
Element(gui) {}
CLAY_AUTO_ID({.layout = {
void RotateWheel::layout(double* rotateAngle, const std::function<void()>& onChange) {
this->rotateAngle = rotateAngle;
this->onChange = onChange;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized();
float distFromCenter = vec_distance(io.mouse.pos, bb.center());
wheelHovered = selection.hovered && distFromCenter > wheel_start() && distFromCenter < wheel_end();
if(selection.held) {
double& newRotationAngle = *newRotationAnglePtr;
newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
if(wheelHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) {
newRotationAngle = snapPos;
if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0
newRotationAngle = 0;
break;
}
}
}
}
if(elemUpdate)
elemUpdate();
}
rotateAngle = *newRotationAnglePtr;
}
float RotateWheel::wheel_start() {
@ -48,11 +30,50 @@ float RotateWheel::wheel_start() {
}
float RotateWheel::wheel_end() {
return bb.width() * 0.5f;
return boundingBox.value().width() * 0.5f;
}
bool RotateWheel::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
isHovering = mouseHovering;
isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down;
update_paint_circle_menu_mouse(button.pos, button.button == InputManager::MouseButton::LEFT && button.down);
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool RotateWheel::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
update_paint_circle_menu_mouse(motion.pos, false);
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
void RotateWheel::update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked) {
if(boundingBox.has_value()) {
Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized();
float distFromCenter = vec_distance(p, boundingBox.value().center());
isRotateBarHovered = isHovering && distFromCenter > wheel_start() && distFromCenter < wheel_end();
isRotateBarHeld = isRotateBarHovered && isHeld;
if(isRotateBarHeld) {
gui.set_post_callback_func([&, vecFromCenter] {
auto& rotationAngle = *rotateAngle;
rotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
if(isRotateBarHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
if(std::fabs(rotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) {
rotationAngle = snapPos;
if(rotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0
rotationAngle = 0;
break;
}
}
}
});
}
}
}
void RotateWheel::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.center().x(), bb.center().y());
@ -71,7 +92,7 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool
rotateBarFill.setStrokeWidth(wheelEnd - wheelStart);
canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill);
if(wheelHovered) {
if(isRotateBarHovered) {
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)};
SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2);
@ -90,7 +111,7 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool
else
rotateBarHolderFill.setColor4f(io.theme->frontColor2);
float rotateBarHolderRadius = (wheelEnd - wheelStart) / 2.0f;
Vector2f rotateBarHolderPos{std::cos(rotateAngle) * rotateBarMiddleRadius, -std::sin(rotateAngle) * rotateBarMiddleRadius};
Vector2f rotateBarHolderPos{std::cos(*rotateAngle) * rotateBarMiddleRadius, -std::sin(*rotateAngle) * rotateBarMiddleRadius};
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill);
SkPaint rotateBarHolderOutline(io.theme->frontColor1);
rotateBarHolderOutline.setAntiAlias(skiaAA);

View file

@ -5,17 +5,25 @@ namespace GUIStuff {
class RotateWheel : public Element {
public:
void update(UpdateInputData& io, double* newRotationAnglePtr, const std::function<void()>& elemUpdate);
RotateWheel(GUIManager& gui);
void layout(double* rotateAngle, const std::function<void()>& onChange);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
SelectionHelper selection;
private:
float wheel_start();
float wheel_end();
void draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
double rotateAngle = 0.0f;
SCollision::AABB<float> bb;
bool wheelHovered;
double* rotateAngle = nullptr;
bool isHeld = false;
bool isHovering = false;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
std::function<void()> onChange;
void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked);
};
}

View file

@ -3,10 +3,15 @@
#include <include/core/SkColorFilter.h>
#include <include/core/SkColorSpace.h>
#include <iostream>
#include "../GUIManager.hpp"
namespace GUIStuff {
void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate) {
SVGIcon::SVGIcon(GUIManager& gui):
Element(gui) {}
void SVGIcon::layout(const std::string& newSvgPath, bool newIsHighlighted) {
auto& io = *gui.io;
auto findSVGData = io.svgData.find(newSvgPath);
if(findSVGData == io.svgData.end())
throw std::runtime_error("[SVGIcon::update] Could not load icon " + newSvgPath);
@ -20,17 +25,14 @@ void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool ne
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }
},
.custom = { .customData = this }
}) {
if(elemUpdate)
elemUpdate();
}
}) {}
}
void SVGIcon::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
if(!svgDom)
return;
auto bb = get_bb(command);
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());

View file

@ -5,7 +5,8 @@ namespace GUIStuff {
class SVGIcon : public Element {
public:
void update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate);
SVGIcon(GUIManager& gui);
void layout(const std::string& newSvgPath, bool newIsHighlighted);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
private:
bool highlighted;

View file

@ -0,0 +1,117 @@
#include "ScrollArea.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
ScrollArea::ScrollArea(GUIManager& gui): Element(gui) {}
void ScrollArea::layout(const Options& options) {
opts = options;
CLAY_AUTO_ID({
.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}
}) {
Clay_ElementId localID = CLAY_ID_LOCAL("SCROLL_AREA");
Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(localID);
if(scrollData.found) {
contentDimensions = {scrollData.contentDimensions.width, scrollData.contentDimensions.height};
containerDimensions = {scrollData.scrollContainerDimensions.width, scrollData.scrollContainerDimensions.height};
}
CLAY(localID, {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
.layoutDirection = opts.layoutDirection
},
.clip = {.horizontal = opts.clipHorizontal, .vertical = opts.clipVertical, .childOffset = {.x = scrollOffset.x(), .y = scrollOffset.y()}}
}) {
opts.innerContent({.contentDimensions = contentDimensions, .containerDimensions = containerDimensions, .scrollOffset = scrollOffset});
}
//if(scrollData.contentDimensions.height > scrollData.scrollContainerDimensions.height) {
// float sAreaDim = scrollData.scrollContainerDimensions.height;
// float contDim = scrollData.contentDimensions.height;
// float scrollerSize = (sAreaDim / contDim) * sAreaDim;
// float scrollPosMax = contDim - sAreaDim;
// float scrollerPos = std::fabs(scrollData.scrollPosition->y / scrollPosMax);
// float areaAboveScrollerSize = scrollerPos * (sAreaDim - scrollerSize);
// currentScrollPos = scrollData.scrollPosition->y;
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(12), .height = CLAY_SIZING_GROW(0)},
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_TOP},
// .layoutDirection = CLAY_TOP_TO_BOTTOM
// },
// .backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor2)
// }) {
// SkColor4f scrollerColor;
// if(sD.isMoving)
// scrollerColor = io->theme->fillColor1;
// else if(Clay_Hovered())
// scrollerColor = io->theme->fillColor1;
// else
// scrollerColor = io->theme->fillColor2;
// bool isHoveringOverScroller = false;
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(areaAboveScrollerSize)}
// }
// }) {}
// CLAY_AUTO_ID({
// .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(scrollerSize)}},
// .backgroundColor = convert_vec4<Clay_Color>(scrollerColor),
// .cornerRadius = CLAY_CORNER_RADIUS(3),
// }) {
// if(Clay_Hovered())
// isHoveringOverScroller = true;
// }
// CLAY_AUTO_ID({ .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
// if(Clay_Hovered() && io->mouse.leftClick) {
// isMoving = true;
// if(isHoveringOverScroller)
// scrollerStartPos = scrollAreaBB.y + areaAboveScrollerSize + scrollerSize * 0.5f;
// else
// scrollerStartPos = std::clamp(io->mouse.pos.y(), scrollAreaBB.y + scrollerSize * 0.5f, scrollAreaBB.y + scrollAreaBB.height - scrollerSize * 0.5f);
// mouseStartPos = io->mouse.pos.y();
// }
// if(!io->mouse.leftHeld)
// isMoving = false;
// if(isMoving) {
// float newScrollPosFrac;
// newScrollPosFrac = std::clamp((scrollerStartPos - (mouseStartPos - io->mouse.pos.y()) - scrollAreaBB.y - scrollerSize * 0.5f) / (scrollAreaBB.height - scrollerSize), 0.0f, 1.0f);
// currentScrollPos = newScrollPosFrac * (-scrollPosMax);
// }
// currentScrollPos = std::clamp(currentScrollPos, -scrollPosMax, 0.0f);
// scrollData.scrollPosition->y = currentScrollPos;
// }
//}
//else
// sD.currentScrollPos = 0.0f;
}
}
bool ScrollArea::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool ScrollArea::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
bool ScrollArea::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) {
if(mouseHovering) {
if(opts.scrollVertical)
scrollOffset.y() += wheel.amount.y();
if(opts.scrollHorizontal)
scrollOffset.x() += wheel.amount.x();
gui.set_to_layout();
}
return Element::input_mouse_wheel_callback(wheel, mouseHovering);
}
}

View file

@ -0,0 +1,42 @@
#pragma once
#include "Element.hpp"
#include "Helpers/ConvertVec.hpp"
namespace GUIStuff {
class ScrollArea : public Element {
public:
ScrollArea(GUIManager& gui);
struct InnerContentParameters {
Vector2f contentDimensions;
Vector2f containerDimensions;
Vector2f scrollOffset;
};
struct Options {
bool scrollVertical = false;
bool scrollHorizontal = false;
bool clipHorizontal = false;
bool clipVertical = false;
bool showScrollbarX = false;
bool showScrollbarY = false;
Clay_LayoutDirection layoutDirection = CLAY_TOP_TO_BOTTOM;
std::function<void(const InnerContentParameters&)> innerContent;
};
void layout(const Options& options);
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
virtual bool input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel, bool mouseHovering) override;
private:
Options opts;
bool isScrollbarXHeld = false;
bool isScrollbarYHeld = false;
Vector2f contentDimensions = {0.0f, 0.0f};
Vector2f containerDimensions = {0.0f, 0.0f};
Vector2f scrollOffset = {0.0f, 0.0f};
};
}

View file

@ -1,30 +1,38 @@
#include "SelectableButton.hpp"
#include "Helpers/ConvertVec.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected) {
SelectableButton::SelectableButton(GUIManager& gui):
Element(gui) {}
void SelectableButton::layout(const Data& d) {
auto& io = *gui.io;
SkColor4f borderColor;
SkColor4f backgroundColorHighlight;
SkColor4f backgroundColor;
if(selection.held || ((isSelected || selection.hovered) && drawType == DrawType::TRANSPARENT_BORDER))
onClick = d.onClick;
if(isHeld || ((d.isSelected || isHovering) && d.drawType == DrawType::TRANSPARENT_BORDER))
borderColor = io.theme->fillColor1;
else if(drawType == DrawType::TRANSPARENT_BORDER)
else if(d.drawType == DrawType::TRANSPARENT_BORDER)
borderColor = io.theme->backColor2;
else
borderColor = SkColor4f{0.0f, 0.0f, 0.0f, 0.0f};
if(isSelected)
if(d.isSelected)
backgroundColorHighlight = color_mul_alpha(io.theme->fillColor1, 0.4f);
else if(selection.hovered || selection.held)
else if(isHovering || isHeld)
backgroundColorHighlight = color_mul_alpha(io.theme->fillColor1, 0.2f);
else
backgroundColorHighlight = {0.0f, 0.0f, 0.0f, 0.0f};
if(drawType == DrawType::TRANSPARENT_ALL || drawType == DrawType::TRANSPARENT_BORDER)
if(d.drawType == DrawType::TRANSPARENT_ALL || d.drawType == DrawType::TRANSPARENT_BORDER)
backgroundColor = {0.0f, 0.0f, 0.0f, 0.0f};
else if(drawType == DrawType::FILLED)
else if(d.drawType == DrawType::FILLED)
backgroundColor = io.theme->backColor2;
else
backgroundColor = io.theme->fillColor2;
@ -43,19 +51,38 @@ void SelectableButton::update(UpdateInputData& io, DrawType drawType, const std:
}) {
CLAY_AUTO_ID({.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.padding = (drawType == DrawType::TRANSPARENT_BORDER) ? CLAY_PADDING_ALL(0) : CLAY_PADDING_ALL(4),
.padding = (d.drawType == DrawType::TRANSPARENT_BORDER) ? CLAY_PADDING_ALL(0) : CLAY_PADDING_ALL(4),
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = convert_vec4<Clay_Color>(backgroundColorHighlight),
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
elemUpdate(selection, isSelected);
if(d.innerContent)
d.innerContent({.isSelected = d.isSelected, .isHovering = isHovering, .isHeld = isHeld});
}
}
}
void SelectableButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bool SelectableButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
bool oldIsHovering = isHovering;
bool oldIsHeld = isHeld;
isHovering = mouseHovering;
isHeld = isHovering && button.button == InputManager::MouseButton::LEFT && button.down;
if(isHeld) {
gui.set_post_callback_func([&]{onClick();});
gui.set_to_layout();
}
else if(isHovering != oldIsHovering || isHeld != oldIsHeld)
gui.set_to_layout();
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool SelectableButton::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
bool oldIsHovering = isHovering;
isHovering = mouseHovering;
if(isHovering != oldIsHovering)
gui.set_to_layout();
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
}

View file

@ -12,9 +12,28 @@ class SelectableButton : public Element {
TRANSPARENT_BORDER
};
void update(UpdateInputData& io, DrawType drawType, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
SelectionHelper selection;
struct InnerContentCallbackParameters {
bool isSelected;
bool isHovering;
bool isHeld;
};
struct Data {
DrawType drawType = DrawType::TRANSPARENT_ALL;
bool isSelected = false;
std::function<void()> onClick;
std::function<void(const InnerContentCallbackParameters&)> innerContent;
};
SelectableButton(GUIManager& gui);
void layout(const Data& d);
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
private:
bool isHovering = false;
bool isHeld = false;
std::function<void()> onClick;
};
}

View file

@ -0,0 +1,44 @@
#pragma once
#include "Element.hpp"
#include "../../RichText/TextBox.hpp"
#include <limits>
#include <modules/skparagraph/include/TextStyle.h>
#include "../../FontData.hpp"
#include "../../InputManager.hpp"
namespace GUIStuff {
template <typename T> struct TextBoxData {
T* data = nullptr;
std::function<std::optional<T>(const std::string&)> fromStr;
std::function<std::string(const T&)> toStr;
bool singleLine = true;
bool updateEveryEdit = false;
bool immutable = false;
InputManager::TextInputProperties textInputProps;
std::function<void()> onEdit;
std::function<void()> onEnter;
};
template <typename T> class TextBox : public Element {
public:
TextBox(GUIManager& gui);
void layout(const TextBoxData<T>& userInfo);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override;
~TextBox();
private:
void init_textbox(UpdateInputData& io);
bool update_data();
bool isSelected = false;
TextBoxData<T> userInfo;
std::shared_ptr<RichText::TextBox> textbox;
std::shared_ptr<RichText::TextBox::Cursor> cur;
std::shared_ptr<SCollision::AABB<float>> rect;
};
}

View file

@ -1,147 +1,3 @@
#pragma once
#include "Element.hpp"
#include "../../RichText/TextBox.hpp"
#include <limits>
#include <modules/skparagraph/include/TextStyle.h>
#include "../../FontData.hpp"
#include "../../InputManager.hpp"
namespace GUIStuff {
template <typename T> class TextBox : public Element {
public:
bool update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, bool updateEveryEdit, const InputManager::TextInputProperties& textInputProps, const std::function<void(SelectionHelper&)>& elemUpdate) {
bool isUpdating = false;
if(!textbox || !cur)
force_update_textbox(io, true);
data = newData;
fromStr = newFromStr;
toStr = newToStr;
force_update_textbox(io, false);
textbox->set_width(std::numeric_limits<float>::max());
//textbox->set_text_direction_between(0, 0, textbox->get_suggested_direction(0));
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast<float>(io.fontSize * 1.25f))}
},
.custom = { .customData = this }
}) {
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld, io.mouse.pos);
if(data && selection.selected) {
io.richTextBoxToEdit = textbox;
io.richTextBoxToEditCursor = cur;
io.richTextBoxToEditRectangle = rect;
io.richTextInputProperties = textInputProps;
textbox->process_mouse_left_button(*cur, io.mouse.pos - bb.min, io.mouse.leftClick, io.mouse.leftHeld, io.key.leftShift);
if((updateEveryEdit && textbox->inputChangedTextBox) || io.key.enter) {
update_on_edit();
isUpdating = true;
}
}
if(data && selection.justUnselected) {
std::optional<T> dataToAssign = fromStr(textbox->get_string());
if(dataToAssign.has_value())
*data = dataToAssign.value();
force_update_textbox(io, true);
isUpdating = true;
}
if(elemUpdate)
elemUpdate(selection);
}
if(textbox)
textbox->inputChangedTextBox = false;
*rect = {(bb.min + io.windowPos) * io.guiScaleMultiplier, (bb.max + io.windowPos) * io.guiScaleMultiplier};
return isUpdating;
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override {
if(!data || !textbox || !cur)
return;
bb = get_bb(command);
canvas->save();
SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height());
canvas->drawRect(r, SkPaint(io.theme->backColor2));
SkPaint outline(selection.selected ? io.theme->fillColor1 : io.theme->backColor2);
outline.setStroke(true);
outline.setStrokeWidth(2.0f);
outline.setAntiAlias(skiaAA);
canvas->drawRoundRect(r, 2.0f, 2.0f, outline);
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
float yOffset = bb.height() * 0.5f;
yOffset -= textbox->get_height() * 0.5f;
RichText::TextBox::PaintOpts paintOpts;
if(selection.selected) {
paintOpts.cursor = *cur;
SkRect cursorRect = textbox->get_cursor_rect(cur->pos);
canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset);
}
else
canvas->translate(bb.min.x() + 2.0f, bb.min.y() + yOffset);
paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB};
paintOpts.skiaAA = skiaAA;
skia::textlayout::TextStyle tStyle;
tStyle.setFontFamilies(io.fonts->get_default_font_families());
tStyle.setFontSize(io.fontSize);
tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1});
textbox->set_initial_text_style(tStyle);
textbox->paint(canvas, paintOpts);
canvas->restore();
}
SelectionHelper selection;
private:
void force_update_textbox(UpdateInputData& io, bool reallyForce) {
if(data && oldData.has_value() && (*data == oldData.value()) && !reallyForce && textbox && cur)
return;
textbox = std::make_shared<RichText::TextBox>();
textbox->set_font_data(io.fonts);
textbox->set_allow_newlines(false);
cur = std::make_shared<RichText::TextBox::Cursor>();
rect = std::make_shared<SCollision::AABB<float>>();
if(data) {
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*data));
oldData = *data;
}
}
void update_on_edit() {
std::optional<T> dataToAssign = fromStr(textbox->get_string());
if(dataToAssign.has_value()) {
*data = dataToAssign.value();
oldData = *data;
}
}
T* data = nullptr;
std::optional<T> oldData;
std::function<std::optional<T>(const std::string&)> fromStr;
std::function<std::string(const T&)> toStr;
std::shared_ptr<RichText::TextBox> textbox;
std::shared_ptr<RichText::TextBox::Cursor> cur;
std::shared_ptr<SCollision::AABB<float>> rect;
SCollision::AABB<float> bb = {{1.0f, 1.0f}, {2.0f, 2.0f}};
};
}
#include "TextBox.decl.hpp"
#include "TextBox.impl.hpp"

View file

@ -0,0 +1,134 @@
#include "TextBox.decl.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
template <typename T> void TextBox<T>::layout(const TextBoxData<T>& userInfo) {
auto& io = *gui.io;
this->userInfo = userInfo;
textbox->onUserTextEdit = [&] {
if(userInfo.immutable)
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*userInfo.data));
else {
if(update_data() && userInfo.onEdit)
gui.set_post_callback_func(userInfo.onEdit);
}
};
init_textbox(io);
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_FIXED(static_cast<float>(io.fontSize * 1.25f))}
},
.custom = { .customData = this }
}) {
}
}
template <typename T> void TextBox<T>::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto& bb = boundingBox.value();
canvas->save();
SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height());
canvas->drawRect(r, SkPaint(io.theme->backColor2));
SkPaint outline(isSelected ? io.theme->fillColor1 : io.theme->backColor2);
outline.setStroke(true);
outline.setStrokeWidth(2.0f);
outline.setAntiAlias(skiaAA);
canvas->drawRoundRect(r, 2.0f, 2.0f, outline);
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
float yOffset = bb.height() * 0.5f;
yOffset -= textbox->get_height() * 0.5f;
RichText::TextBox::PaintOpts paintOpts;
if(isSelected) {
paintOpts.cursor = *cur;
SkRect cursorRect = textbox->get_cursor_rect(cur->pos);
canvas->translate(bb.min.x() - std::max(cursorRect.fRight - bb.width(), -2.0f), bb.min.y() + yOffset);
}
else
canvas->translate(bb.min.x() + 2.0f, bb.min.y() + yOffset);
paintOpts.cursorColor = {io.theme->fillColor1.fR, io.theme->fillColor1.fG, io.theme->fillColor1.fB};
paintOpts.skiaAA = skiaAA;
skia::textlayout::TextStyle tStyle;
tStyle.setFontFamilies(io.fonts->get_default_font_families());
tStyle.setFontSize(io.fontSize);
tStyle.setForegroundPaint(SkPaint{io.theme->frontColor1});
textbox->set_initial_text_style(tStyle);
textbox->paint(canvas, paintOpts);
canvas->restore();
}
template <typename T> bool TextBox<T>::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
auto& io = *gui.io;
if(button.button == InputManager::MouseButton::LEFT && boundingBox.has_value()) {
if(button.down) {
if(mouseHovering) {
*rect = {(boundingBox.value().min + io.windowPos) * io.guiScaleMultiplier, (boundingBox.value().max + io.windowPos) * io.guiScaleMultiplier};
gui.io->input->set_rich_text_box_input_front(textbox, cur, false, rect, userInfo.textInputProps, nullptr);
textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, button.clicks, true, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held);
isSelected = true;
}
else {
gui.io->input->remove_rich_text_box_input(textbox);
if(update_data())
gui.set_post_callback_func(userInfo.onEdit);
update_textbox(io);
isSelected = false;
}
}
else {
textbox->process_mouse_left_button(*cur, button.pos - boundingBox.value().min, 0, false, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held);
}
}
return Element::input_mouse_button_callback(button, mouseHovering);
}
template <typename T> bool TextBox<T>::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
if(boundingBox.has_value())
textbox->process_mouse_left_button(*cur, motion.pos - boundingBox.value().min, 0, gui.io->input->mouse.leftDown, gui.io->input->key(InputManager::KEY_TEXT_SHIFT).held);
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
template <typename T> void TextBox<T>::input_key_callback(const InputManager::KeyCallbackArgs& key) {
if(key.key == InputManager::KEY_TEXT_ENTER && key.down && userInfo.onEnter) {
bool success = update_data();
gui.set_post_callback_func([&, success] {
if(success && userInfo.onEdit) userInfo.onEdit();
userInfo.onEnter();
});
}
}
template <typename T> void TextBox<T>::init_textbox(UpdateInputData& io) {
textbox = std::make_shared<RichText::TextBox>();
textbox->set_width(std::numeric_limits<float>::max());
textbox->set_font_data(io.fonts);
textbox->set_allow_newlines(false);
cur = std::make_shared<RichText::TextBox::Cursor>();
rect = std::make_shared<SCollision::AABB<float>>();
cur->pos = cur->selectionBeginPos = cur->selectionEndPos = textbox->insert({0, 0}, toStr(*userInfo.data));
}
template <typename T> bool TextBox<T>::update_data() {
std::optional<T> dataToAssign = userInfo.fromStr(textbox->get_string());
if(dataToAssign.has_value()) {
*userInfo.data = dataToAssign.value();
return true;
}
return false;
}
template <typename T> TextBox<T>::~TextBox() {
gui.io->input->remove_rich_text_box_input(textbox);
}
}

View file

@ -4,7 +4,10 @@
namespace GUIStuff {
void TextParagraph::update(UpdateInputData& io, std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width, const std::function<void()>& elemUpdate) {
TextParagraph::TextParagraph(GUIManager& gui):
Element(gui) {}
void TextParagraph::layout(std::unique_ptr<skia::textlayout::Paragraph> paragraph, float width) {
data = std::move(paragraph);
data->layout(width);
CLAY_AUTO_ID({
@ -12,17 +15,14 @@ void TextParagraph::update(UpdateInputData& io, std::unique_ptr<skia::textlayout
.sizing = {.width = CLAY_SIZING_FIXED(width), .height = CLAY_SIZING_FIXED(data->getHeight())}
},
.custom = { .customData = this }
}) {
if(elemUpdate)
elemUpdate();
}
}) {}
}
void TextParagraph::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bb = get_bb(command);
if(data)
if(data) {
auto& bb = boundingBox.value();
data->paint(canvas, bb.min.x(), bb.min.y());
}
}
}

View file

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

View file

@ -3,279 +3,293 @@
#include "Helpers/ConvertVec.hpp"
#include <chrono>
#include "../../TimePoint.hpp"
#include "../GUIManager.hpp"
#define TIME_TO_HOVER_TO_OPEN_DIRECTORY std::chrono::milliseconds(700)
namespace GUIStuff {
void TreeListing::update(UpdateInputData& io, GUIManager& gui, NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData) {
if(!io.mouse.leftHeld)
dragHoldSelected = false;
TreeListing::TreeListing(GUIManager& gui):
Element(gui) {}
selectionData.orderedObjsSelected.clear();
std::optional<ParentObjectIDStack> hoveredObject;
void TreeListing::layout(NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData) {
//auto& io = *gui.io;
CLAY_AUTO_ID({
.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}
}) {
Clay_ElementId localID = CLAY_ID_LOCAL("scroll area");
float setScrollAmount;
float setScrollContentHeight;
CLAY(localID, {
.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}
}) {
gui.scroll_bar_area("scroll area", true, [&](float scrollContentHeight, float containerHeight, float& scrollAmount) {
setScrollAmount = scrollAmount;
setScrollContentHeight = scrollContentHeight;
size_t itemCount = 0;
size_t itemCountToStartAt = -scrollAmount / ENTRY_HEIGHT;
size_t itemCountToEndAt = (containerHeight - scrollAmount) / ENTRY_HEIGHT;
itemCountToEndAt++;
std::vector<NetworkingObjects::NetObjID> parentIDStack;
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * itemCountToStartAt)}
}
}) {}
recursive_gui(io, gui, displayData, selectionData, rootObjID, itemCount, itemCountToStartAt, itemCountToEndAt, 0, hoveredObject, parentIDStack, false);
if(itemCount > itemCountToEndAt) {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * (itemCount - itemCountToEndAt))}
}
}) {}
}
}, false);
}
Clay_ElementData scrollAreaBB = Clay_GetElementData(localID);
if(scrollAreaBB.found) {
// Clamp by fabs(scrollcontentheight - 3), small offset is added to account for error (when youre at the very edge it might assume youre at the top of an element)
float mouseRelativeYPos = std::clamp(std::fabs(io.mouse.pos.y() - scrollAreaBB.boundingBox.y - setScrollAmount), 0.0f, std::fabs(setScrollContentHeight - 3.0f));
topHalfOfHovered = std::fmod(mouseRelativeYPos, ENTRY_HEIGHT) < (ENTRY_HEIGHT / 2.0f);
}
}
//if(!io.mouse.leftHeld)
// dragHoldSelected = false;
// Any objects that were part of a collapsed directory will be unselected
selectionData.objsSelected.clear();
for(auto& [parent, object] : selectionData.orderedObjsSelected)
selectionData.objsSelected.emplace(object);
//selectionData.orderedObjsSelected.clear();
//std::optional<ParentObjectIDStack> hoveredObject;
if(objDragged.has_value()) {
if(hoveredObject.has_value() && !selectionData.objsSelected.contains(hoveredObject.value().object) && (std::chrono::steady_clock::now() - timeStartedHoveringOverObject) >= TIME_TO_HOVER_TO_OPEN_DIRECTORY) {
ParentObjectIDStack& hoveredObjVal = hoveredObject.value();
size_t hoveredObjIndex = displayData.getIndexOfObjInList({hoveredObjVal.parents.back(), hoveredObjVal.object});
std::optional<DisplayData::ObjInList> objHoveringOver = displayData.getObjInListAtIndex(hoveredObjVal.parents.back(), hoveredObjIndex);
if(objHoveringOver.value().isDirectory && !objHoveringOver.value().isDirectoryOpen)
displayData.setDirectoryOpen(hoveredObjVal.object, true);
}
//CLAY_AUTO_ID({
// .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}
//}) {
// Clay_ElementId localID = CLAY_ID_LOCAL("scroll area");
// float setScrollAmount;
// float setScrollContentHeight;
// CLAY(localID, {
// .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}
// }) {
// gui.scroll_bar_area("scroll area", true, [&](float scrollContentHeight, float containerHeight, float& scrollAmount) {
// setScrollAmount = scrollAmount;
// setScrollContentHeight = scrollContentHeight;
// size_t itemCount = 0;
// size_t itemCountToStartAt = -scrollAmount / ENTRY_HEIGHT;
// size_t itemCountToEndAt = (containerHeight - scrollAmount) / ENTRY_HEIGHT;
// itemCountToEndAt++;
// std::vector<NetworkingObjects::NetObjID> parentIDStack;
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * itemCountToStartAt)}
// }
// }) {}
// recursive_gui(io, gui, displayData, selectionData, rootObjID, itemCount, itemCountToStartAt, itemCountToEndAt, 0, hoveredObject, parentIDStack, false);
// if(itemCount > itemCountToEndAt) {
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT * (itemCount - itemCountToEndAt))}
// }
// }) {}
// }
// }, false);
// }
// Clay_ElementData scrollAreaBB = Clay_GetElementData(localID);
// if(scrollAreaBB.found) {
// // Clamp by fabs(scrollcontentheight - 3), small offset is added to account for error (when youre at the very edge it might assume youre at the top of an element)
// float mouseRelativeYPos = std::clamp(std::fabs(io.mouse.pos.y() - scrollAreaBB.boundingBox.y - setScrollAmount), 0.0f, std::fabs(setScrollContentHeight - 3.0f));
// topHalfOfHovered = std::fmod(mouseRelativeYPos, ENTRY_HEIGHT) < (ENTRY_HEIGHT / 2.0f);
// }
//}
ParentObjectIDPair& objDraggedVal = objDragged.value();
if(!selectionData.objsSelected.contains(objDraggedVal.object))
objDragged = std::nullopt;
else if(!io.mouse.leftHeld) {
if(!hoveredObject.has_value())
set_single_selected_object(selectionData);
else {
ParentObjectIDStack hoveredObjValToUse = hoveredObject.value(); // By value since it'll be edited
size_t indexToMoveFrom = displayData.getIndexOfObjInList(objDraggedVal);
size_t indexToMoveTo = displayData.getIndexOfObjInList({hoveredObjValToUse.parents.back(), hoveredObjValToUse.object});
std::optional<DisplayData::ObjInList> objHoveringOver = displayData.getObjInListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo);
if(objHoveringOver.value().isDirectoryOpen && !topHalfOfHovered) { // Move to front of the directory object
hoveredObjValToUse.parents.emplace_back(hoveredObjValToUse.object);
indexToMoveTo = 0;
}
else if(!topHalfOfHovered)
indexToMoveTo++;
if((indexToMoveFrom == indexToMoveTo || (indexToMoveFrom + 1) == indexToMoveTo) && objDraggedVal.parent == hoveredObjValToUse.parents.back())
set_single_selected_object(selectionData);
else {
bool wrongMovement = false;
// Check if object is about to move into itself
for(const NetworkingObjects::NetObjID& parent : hoveredObjValToUse.parents) {
if(selectionData.objsSelected.contains(parent)) {
wrongMovement = true;
break;
}
}
if(!wrongMovement) {
displayData.moveObjectsToListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo, selectionData.orderedObjsSelected);
selectionData.objsSelected.clear();
selectionData.orderedObjsSelected.clear();
}
else
set_single_selected_object(selectionData);
}
}
objDragged = std::nullopt;
}
}
//// Any objects that were part of a collapsed directory will be unselected
//selectionData.objsSelected.clear();
//for(auto& [parent, object] : selectionData.orderedObjsSelected)
// selectionData.objsSelected.emplace(object);
oldHoveredObject = hoveredObject;
//if(objDragged.has_value()) {
// if(hoveredObject.has_value() && !selectionData.objsSelected.contains(hoveredObject.value().object) && (std::chrono::steady_clock::now() - timeStartedHoveringOverObject) >= TIME_TO_HOVER_TO_OPEN_DIRECTORY) {
// ParentObjectIDStack& hoveredObjVal = hoveredObject.value();
// size_t hoveredObjIndex = displayData.getIndexOfObjInList({hoveredObjVal.parents.back(), hoveredObjVal.object});
// std::optional<DisplayData::ObjInList> objHoveringOver = displayData.getObjInListAtIndex(hoveredObjVal.parents.back(), hoveredObjIndex);
// if(objHoveringOver.value().isDirectory && !objHoveringOver.value().isDirectoryOpen)
// displayData.setDirectoryOpen(hoveredObjVal.object, true);
// }
// ParentObjectIDPair& objDraggedVal = objDragged.value();
// if(!selectionData.objsSelected.contains(objDraggedVal.object))
// objDragged = std::nullopt;
// else if(!io.mouse.leftHeld) {
// if(!hoveredObject.has_value())
// set_single_selected_object(selectionData);
// else {
// ParentObjectIDStack hoveredObjValToUse = hoveredObject.value(); // By value since it'll be edited
// size_t indexToMoveFrom = displayData.getIndexOfObjInList(objDraggedVal);
// size_t indexToMoveTo = displayData.getIndexOfObjInList({hoveredObjValToUse.parents.back(), hoveredObjValToUse.object});
// std::optional<DisplayData::ObjInList> objHoveringOver = displayData.getObjInListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo);
// if(objHoveringOver.value().isDirectoryOpen && !topHalfOfHovered) { // Move to front of the directory object
// hoveredObjValToUse.parents.emplace_back(hoveredObjValToUse.object);
// indexToMoveTo = 0;
// }
// else if(!topHalfOfHovered)
// indexToMoveTo++;
// if((indexToMoveFrom == indexToMoveTo || (indexToMoveFrom + 1) == indexToMoveTo) && objDraggedVal.parent == hoveredObjValToUse.parents.back())
// set_single_selected_object(selectionData);
// else {
// bool wrongMovement = false;
// // Check if object is about to move into itself
// for(const NetworkingObjects::NetObjID& parent : hoveredObjValToUse.parents) {
// if(selectionData.objsSelected.contains(parent)) {
// wrongMovement = true;
// break;
// }
// }
// if(!wrongMovement) {
// displayData.moveObjectsToListAtIndex(hoveredObjValToUse.parents.back(), indexToMoveTo, selectionData.orderedObjsSelected);
// selectionData.objsSelected.clear();
// selectionData.orderedObjsSelected.clear();
// }
// else
// set_single_selected_object(selectionData);
// }
// }
// objDragged = std::nullopt;
// }
//}
//oldHoveredObject = hoveredObject;
}
void TreeListing::set_single_selected_object(SelectionData& selectionData) {
selectionData.objsSelected.clear();
selectionData.orderedObjsSelected.clear();
selectionData.objsSelected.emplace(objDragged.value().object);
selectionData.orderedObjsSelected.emplace_back(objDragged.value());
//selectionData.objsSelected.clear();
//selectionData.orderedObjsSelected.clear();
//selectionData.objsSelected.emplace(objDragged.value().object);
//selectionData.orderedObjsSelected.emplace_back(objDragged.value());
}
void TreeListing::recursive_gui(UpdateInputData& io, GUIManager& gui, const DisplayData& displayData, SelectionData& selectionData, NetworkingObjects::NetObjID objID, size_t& itemCount, size_t itemCountToStartAt, size_t itemCountToEndAt, size_t listDepth, std::optional<ParentObjectIDStack>& hoveredObject, std::vector<NetworkingObjects::NetObjID>& parentIDStack, bool parentJustSelected) {
parentIDStack.emplace_back(objID);
for(size_t i = 0;; i++) {
std::optional<DisplayData::ObjInList> objInListOptional = displayData.getObjInListAtIndex(objID, i);
if(!objInListOptional.has_value())
break;
else {
auto& objInList = objInListOptional.value();
bool selfJustSelected = false;
if(itemCount >= itemCountToStartAt && itemCount < itemCountToEndAt) {
gui.push_id(itemCount);
bool buttonClicked = false;
uint16_t topOutlineWidth = 0;
uint16_t bottomOutlineWidth = 0;
//parentIDStack.emplace_back(objID);
//for(size_t i = 0;; i++) {
// std::optional<DisplayData::ObjInList> objInListOptional = displayData.getObjInListAtIndex(objID, i);
// if(!objInListOptional.has_value())
// break;
// else {
// auto& objInList = objInListOptional.value();
// bool selfJustSelected = false;
// if(itemCount >= itemCountToStartAt && itemCount < itemCountToEndAt) {
// gui.push_id(itemCount);
// bool buttonClicked = false;
// uint16_t topOutlineWidth = 0;
// uint16_t bottomOutlineWidth = 0;
bool isOldHoveredObject = oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id;
SkColor4f backgroundColor;
if(selectionData.objsSelected.contains(objInList.id))
backgroundColor = io.theme->backColor1;
else if(isOldHoveredObject && objInList.isDirectory && !objInList.isDirectoryOpen && objDragged.has_value())
backgroundColor = lerp_vec(io.theme->backColor2, io.theme->fillColor1, std::clamp(std::chrono::duration<float, std::chrono::milliseconds::period>(std::chrono::steady_clock::now() - timeStartedHoveringOverObject) / TIME_TO_HOVER_TO_OPEN_DIRECTORY, 0.0f, 1.0f));
else
backgroundColor = io.theme->backColor2;
// bool isOldHoveredObject = oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id;
// SkColor4f backgroundColor;
// if(selectionData.objsSelected.contains(objInList.id))
// backgroundColor = io.theme->backColor1;
// else if(isOldHoveredObject && objInList.isDirectory && !objInList.isDirectoryOpen && objDragged.has_value())
// backgroundColor = lerp_vec(io.theme->backColor2, io.theme->fillColor1, std::clamp(std::chrono::duration<float, std::chrono::milliseconds::period>(std::chrono::steady_clock::now() - timeStartedHoveringOverObject) / TIME_TO_HOVER_TO_OPEN_DIRECTORY, 0.0f, 1.0f));
// else
// backgroundColor = io.theme->backColor2;
if(oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id) {
if(objDragged.has_value()) {
ParentObjectIDPair& objDraggedVal = objDragged.value();
if(objDraggedVal.object != objInList.id) {
if(topHalfOfHovered)
topOutlineWidth = 1;
else
bottomOutlineWidth = 1;
}
}
}
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
},
.backgroundColor = convert_vec4<Clay_Color>(backgroundColor)
}) {
if(listDepth != 0) {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT * listDepth), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}
},
}) {}
}
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
},
.border = {
.color = convert_vec4<Clay_Color>(io.theme->frontColor1),
.width = {
.left = 0,
.right = 0,
.top = topOutlineWidth,
.bottom = bottomOutlineWidth,
.betweenChildren = 0
}
}
}) {
if(objInList.isDirectory) {
if(gui.svg_icon_button_transparent("ico", objInList.isDirectoryOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", false, ENTRY_HEIGHT)) {
displayData.setDirectoryOpen(objInList.id, !objInList.isDirectoryOpen);
buttonClicked = true;
}
}
else {
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
},
}) {
buttonClicked |= displayData.drawNonDirectoryObjIconGUI(objInList.id);
}
}
CLAY_AUTO_ID({
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
.childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
}
}) {
buttonClicked |= displayData.drawObjGUI({objID, objInList.id});
}
}
if(Clay_Hovered()) {
hoveredObject = {parentIDStack, objInList.id};
if(!isOldHoveredObject)
timeStartedHoveringOverObject = std::chrono::steady_clock::now();
if(io.mouse.leftClick && !buttonClicked && !parentJustSelected) {
if(io.key.leftShift)
dragHoldSelected = true;
else if(io.key.leftCtrl) {
bool alreadySelected = selectionData.objsSelected.contains(objInList.id);
if(alreadySelected)
selectionData.objsSelected.erase(objInList.id);
else {
select_and_unselect_parents(parentIDStack, objInList.id, selectionData);
selfJustSelected = true;
}
}
else {
if(!selectionData.objsSelected.contains(objInList.id)) {
selectionData.objsSelected.clear();
selectionData.orderedObjsSelected.clear();
selectionData.objsSelected.emplace(objInList.id);
selfJustSelected = true;
}
objDragged = {objID, objInList.id};
}
}
if(io.key.leftShift && dragHoldSelected && !parentJustSelected) {
select_and_unselect_parents(parentIDStack, objInList.id, selectionData);
selfJustSelected = true;
}
}
}
gui.pop_id();
}
if(!parentJustSelected) {
if(selectionData.objsSelected.contains(objInList.id))
selectionData.orderedObjsSelected.emplace_back(objID, objInList.id);
}
else
unselect_object(objInList.id, selectionData);
itemCount++;
if(objInList.isDirectoryOpen)
recursive_gui(io, gui, displayData, selectionData, objInList.id, itemCount, itemCountToStartAt, itemCountToEndAt, listDepth + 1, hoveredObject, parentIDStack, selfJustSelected | parentJustSelected);
}
}
parentIDStack.pop_back();
// if(oldHoveredObject.has_value() && oldHoveredObject.value().object == objInList.id) {
// if(objDragged.has_value()) {
// ParentObjectIDPair& objDraggedVal = objDragged.value();
// if(objDraggedVal.object != objInList.id) {
// if(topHalfOfHovered)
// topOutlineWidth = 1;
// else
// bottomOutlineWidth = 1;
// }
// }
// }
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)},
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
// },
// .backgroundColor = convert_vec4<Clay_Color>(backgroundColor)
// }) {
// if(listDepth != 0) {
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT * listDepth), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)}
// },
// }) {}
// }
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
// },
// .border = {
// .color = convert_vec4<Clay_Color>(io.theme->frontColor1),
// .width = {
// .left = 0,
// .right = 0,
// .top = topOutlineWidth,
// .bottom = bottomOutlineWidth,
// .betweenChildren = 0
// }
// }
// }) {
// if(objInList.isDirectory) {
// if(gui.svg_icon_button_transparent("ico", objInList.isDirectoryOpen ? "data/icons/droparrow.svg" : "data/icons/droparrowclose.svg", false, ENTRY_HEIGHT)) {
// displayData.setDirectoryOpen(objInList.id, !objInList.isDirectoryOpen);
// buttonClicked = true;
// }
// }
// else {
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_FIXED(ENTRY_HEIGHT), .height = CLAY_SIZING_FIXED(ENTRY_HEIGHT)},
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
// },
// }) {
// buttonClicked |= displayData.drawNonDirectoryObjIconGUI(objInList.id);
// }
// }
// CLAY_AUTO_ID({
// .layout = {
// .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
// .childAlignment = {.x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}
// }
// }) {
// buttonClicked |= displayData.drawObjGUI({objID, objInList.id});
// }
// }
//
// if(Clay_Hovered()) {
// hoveredObject = {parentIDStack, objInList.id};
// if(!isOldHoveredObject)
// timeStartedHoveringOverObject = std::chrono::steady_clock::now();
// if(io.mouse.leftClick && !buttonClicked && !parentJustSelected) {
// if(io.key.leftShift)
// dragHoldSelected = true;
// else if(io.key.leftCtrl) {
// bool alreadySelected = selectionData.objsSelected.contains(objInList.id);
// if(alreadySelected)
// selectionData.objsSelected.erase(objInList.id);
// else {
// select_and_unselect_parents(parentIDStack, objInList.id, selectionData);
// selfJustSelected = true;
// }
// }
// else {
// if(!selectionData.objsSelected.contains(objInList.id)) {
// selectionData.objsSelected.clear();
// selectionData.orderedObjsSelected.clear();
// selectionData.objsSelected.emplace(objInList.id);
// selfJustSelected = true;
// }
// objDragged = {objID, objInList.id};
// }
// }
// if(io.key.leftShift && dragHoldSelected && !parentJustSelected) {
// select_and_unselect_parents(parentIDStack, objInList.id, selectionData);
// selfJustSelected = true;
// }
// }
// }
// gui.pop_id();
// }
// if(!parentJustSelected) {
// if(selectionData.objsSelected.contains(objInList.id))
// selectionData.orderedObjsSelected.emplace_back(objID, objInList.id);
// }
// else
// unselect_object(objInList.id, selectionData);
// itemCount++;
// if(objInList.isDirectoryOpen)
// recursive_gui(io, gui, displayData, selectionData, objInList.id, itemCount, itemCountToStartAt, itemCountToEndAt, listDepth + 1, hoveredObject, parentIDStack, selfJustSelected | parentJustSelected);
// }
//}
//parentIDStack.pop_back();
}
void TreeListing::unselect_object(NetworkingObjects::NetObjID id, SelectionData& selectionData) {
selectionData.objsSelected.erase(id);
std::erase_if(selectionData.orderedObjsSelected, [&id](const auto& p) {
return p.object == id;
});
//selectionData.objsSelected.erase(id);
//std::erase_if(selectionData.orderedObjsSelected, [&id](const auto& p) {
// return p.object == id;
//});
}
void TreeListing::select_and_unselect_parents(const std::vector<NetworkingObjects::NetObjID>& parentIDStack, NetworkingObjects::NetObjID newSelectedObj, SelectionData& selectionData) {
for(const NetworkingObjects::NetObjID& parent : parentIDStack) {
if(selectionData.objsSelected.contains(parent)) {
unselect_object(parent, selectionData);
break;
}
}
selectionData.objsSelected.emplace(newSelectedObj);
//for(const NetworkingObjects::NetObjID& parent : parentIDStack) {
// if(selectionData.objsSelected.contains(parent)) {
// unselect_object(parent, selectionData);
// break;
// }
//}
//selectionData.objsSelected.emplace(newSelectedObj);
}
void TreeListing::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
bool TreeListing::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) {
return Element::input_mouse_button_callback(button, mouseHovering);
}
bool TreeListing::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) {
return Element::input_mouse_motion_callback(motion, mouseHovering);
}
void TreeListing::input_key_callback(const InputManager::KeyCallbackArgs& key) {
}
}

View file

@ -40,8 +40,11 @@ class TreeListing : public Element {
std::unordered_set<NetworkingObjects::NetObjID> objsSelected;
};
void update(UpdateInputData& io, GUIManager& gui, NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
TreeListing(GUIManager& gui);
void layout(NetworkingObjects::NetObjID rootObjID, const DisplayData& displayData, SelectionData& selectionData);
virtual bool input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering) override;
virtual bool input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion, bool mouseHovering) override;
virtual void input_key_callback(const InputManager::KeyCallbackArgs& key) override;
private:
bool dragHoldSelected = false;