Starting work on moving GUI to event system (doesnt compile)
This commit is contained in:
parent
f1a855edc3
commit
ef7dfcef05
69 changed files with 4296 additions and 3709 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
102
src/GUIStuff/Elements/ColorPicker.decl.hpp
Normal file
102
src/GUIStuff/Elements/ColorPicker.decl.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
170
src/GUIStuff/Elements/ColorPicker.impl.hpp
Normal file
170
src/GUIStuff/Elements/ColorPicker.impl.hpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
20
src/GUIStuff/Elements/ColorPickerButton.decl.hpp
Normal file
20
src/GUIStuff/Elements/ColorPickerButton.decl.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
3
src/GUIStuff/Elements/ColorPickerButton.hpp
Normal file
3
src/GUIStuff/Elements/ColorPickerButton.hpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
#pragma once
|
||||
#include "ColorPickerButton.decl.hpp"
|
||||
#include "ColorPickerButton.impl.hpp"
|
||||
35
src/GUIStuff/Elements/ColorPickerButton.impl.hpp
Normal file
35
src/GUIStuff/Elements/ColorPickerButton.impl.hpp
Normal 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
55
src/GUIStuff/Elements/ColorPickerShaders.cpp
Normal file
55
src/GUIStuff/Elements/ColorPickerShaders.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}}
|
||||
49
src/GUIStuff/Elements/ColorRectangleDisplay.cpp
Normal file
49
src/GUIStuff/Elements/ColorRectangleDisplay.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
31
src/GUIStuff/Elements/ColorRectangleDisplay.hpp
Normal file
31
src/GUIStuff/Elements/ColorRectangleDisplay.hpp
Normal 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();
|
||||
};
|
||||
|
||||
}
|
||||
152
src/GUIStuff/Elements/DropDown.hpp
Normal file
152
src/GUIStuff/Elements/DropDown.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
64
src/GUIStuff/Elements/GUIStuffHelpers.cpp
Normal file
64
src/GUIStuff/Elements/GUIStuffHelpers.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
105
src/GUIStuff/Elements/GUIStuffHelpers.hpp
Normal file
105
src/GUIStuff/Elements/GUIStuffHelpers.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
34
src/GUIStuff/Elements/LayoutElement.cpp
Normal file
34
src/GUIStuff/Elements/LayoutElement.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
24
src/GUIStuff/Elements/LayoutElement.hpp
Normal file
24
src/GUIStuff/Elements/LayoutElement.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
39
src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp
Normal file
39
src/GUIStuff/Elements/PositionAdjustingPopupMenu.cpp
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
16
src/GUIStuff/Elements/PositionAdjustingPopupMenu.hpp
Normal file
16
src/GUIStuff/Elements/PositionAdjustingPopupMenu.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
117
src/GUIStuff/Elements/ScrollArea.cpp
Normal file
117
src/GUIStuff/Elements/ScrollArea.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
42
src/GUIStuff/Elements/ScrollArea.hpp
Normal file
42
src/GUIStuff/Elements/ScrollArea.hpp
Normal 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};
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
44
src/GUIStuff/Elements/TextBox.decl.hpp
Normal file
44
src/GUIStuff/Elements/TextBox.decl.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
134
src/GUIStuff/Elements/TextBox.impl.hpp
Normal file
134
src/GUIStuff/Elements/TextBox.impl.hpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue