Initial Commit
This commit is contained in:
commit
6088fc29ed
978 changed files with 353647 additions and 0 deletions
90
src/GUIStuff/Elements/CheckBox.cpp
Normal file
90
src/GUIStuff/Elements/CheckBox.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
#include "CheckBox.hpp"
|
||||
#include <include/core/SkPath.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
void CheckBox::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(isTicked != newIsTicked) {
|
||||
hoverAnimation2 = CHECKBOX_ANIMATION_TIME;
|
||||
isTicked = newIsTicked;
|
||||
}
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void CheckBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
auto bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
canvas->scale(bb.width(), bb.height());
|
||||
canvas->translate(0.5f, 0.5f);
|
||||
|
||||
SkPaint p;
|
||||
if(isTicked) {
|
||||
SkRect checkBox = SkRect::MakeLTRB(-0.5f, -0.5f, 0.5f, 0.5f);
|
||||
p.setColor4f(convert_vec4<SkColor4f>(io.theme->fillColor1));
|
||||
p.setStyle(SkPaint::kFill_Style);
|
||||
canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p);
|
||||
}
|
||||
else {
|
||||
SkRect checkBox = SkRect::MakeLTRB(-0.45f, -0.45f, 0.45f, 0.45f);
|
||||
p.setColor4f(convert_vec4<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->backColor3));
|
||||
p.setStyle(SkPaint::kStroke_Style);
|
||||
p.setStrokeWidth(0.15f);
|
||||
canvas->drawRoundRect(checkBox, 0.25f, 0.25f, p);
|
||||
}
|
||||
|
||||
|
||||
if(isTicked) {
|
||||
SkPaint checkP;
|
||||
checkP.setColor4f(io.theme->backColor2);
|
||||
checkP.setStyle(SkPaint::kFill_Style);
|
||||
checkP.setStrokeWidth(0.12);
|
||||
checkP.setStrokeCap(SkPaint::kRound_Cap);
|
||||
checkP.setStrokeJoin(SkPaint::kRound_Join);
|
||||
|
||||
SkPath checkPath;
|
||||
Vector2f checkP1{(4.5/17.0) - 0.5, (8.5/17.0) - 0.5};
|
||||
Vector2f checkP2{(7.5/17.0) - 0.5, (12.0/17.0) - 0.5};
|
||||
Vector2f checkP3{(12.5/17.0) - 0.5, (6.0/17.0) - 0.5};
|
||||
|
||||
Vector2f rectP1{-0.2f, -0.2f};
|
||||
Vector2f rectP2{-0.2f, 0.2f};
|
||||
Vector2f rectP3{ 0.2f, 0.2f};
|
||||
Vector2f rectP4{ 0.2f, -0.2f};
|
||||
|
||||
static BezierEasing anim{0.445, -0.733, 0.575, 1.627};
|
||||
|
||||
float lerpTime2 = anim(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, CHECKBOX_ANIMATION_TIME));
|
||||
|
||||
std::array<Vector2f, 5> points;
|
||||
|
||||
points[0] = lerp_vec(checkP1, rectP1, lerpTime2);
|
||||
points[1] = lerp_vec(checkP2, rectP2, lerpTime2);
|
||||
points[2] = lerp_vec(checkP3, rectP3, lerpTime2);
|
||||
points[3] = lerp_vec(checkP2, rectP4, lerpTime2);
|
||||
points[4] = points[0];
|
||||
|
||||
checkPath.moveTo(points[0].x(), points[0].y());
|
||||
for(unsigned i = 1; i < 5; i++)
|
||||
checkPath.lineTo(points[i].x(), points[i].y());
|
||||
checkPath.close();
|
||||
|
||||
canvas->drawPath(checkPath, checkP);
|
||||
checkP.setStyle(SkPaint::kStroke_Style);
|
||||
canvas->drawPath(checkPath, checkP);
|
||||
}
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
}
|
||||
17
src/GUIStuff/Elements/CheckBox.hpp
Normal file
17
src/GUIStuff/Elements/CheckBox.hpp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class CheckBox : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
SelectionHelper selection;
|
||||
private:
|
||||
static constexpr float CHECKBOX_ANIMATION_TIME = 0.3;
|
||||
bool isTicked = false;
|
||||
float hoverAnimation2 = 0.0;
|
||||
};
|
||||
|
||||
}
|
||||
285
src/GUIStuff/Elements/ColorPicker.hpp
Normal file
285
src/GUIStuff/Elements/ColorPicker.hpp
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
#include <include/core/SkColor.h>
|
||||
#include <include/core/SkPath.h>
|
||||
#include <include/effects/SkRuntimeEffect.h>
|
||||
#include <iostream>
|
||||
#include <Helpers/SCollision.hpp>
|
||||
#include <Helpers/HsvRgb.hpp>
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
template <typename T> class ColorPicker : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, T* newData, bool newSelectAlpha, const std::function<void()>& elemUpdate) {
|
||||
data = newData;
|
||||
selectAlpha = newSelectAlpha;
|
||||
|
||||
if(!data)
|
||||
return;
|
||||
|
||||
force_update_colorpicker();
|
||||
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(bb.width()) }
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(selection.clicked && data) {
|
||||
float svSelectionAreaSize = get_sv_selection_area_size();
|
||||
modifyingSv = SCollision::collide(SCollision::AABB<float>(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), io.mouse.pos);
|
||||
Vector2f hueBarPos = get_hue_bar_pos();
|
||||
Vector2f hueBarDim = get_hue_bar_dim();
|
||||
modifyingHue = SCollision::collide(SCollision::AABB<float>(hueBarPos, hueBarPos + hueBarDim), io.mouse.pos);
|
||||
if(selectAlpha) {
|
||||
Vector2f alphaBarPos = get_alpha_bar_pos();
|
||||
Vector2f alphaBarDim = get_alpha_bar_dim();
|
||||
modifyingAlpha = SCollision::collide(SCollision::AABB<float>(alphaBarPos, alphaBarPos + alphaBarDim), io.mouse.pos);
|
||||
}
|
||||
}
|
||||
if(selection.held && modifyingSv) {
|
||||
float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH);
|
||||
Vector2f newSv = cwise_vec_clamp<Vector2f>((io.mouse.pos - bb.min) / svSelectionAreaSize, Vector2f{0.0f, 0.0f}, Vector2f{1.0f, 1.0f});
|
||||
savedHsv.y() = newSv.x();
|
||||
savedHsv.z() = 1.0f - newSv.y();
|
||||
set_hsv(savedHsv);
|
||||
}
|
||||
if(selection.held && modifyingHue) {
|
||||
Vector2f huePos = get_hue_bar_pos();
|
||||
Vector2f hueDim = get_hue_bar_dim();
|
||||
savedHsv.x() = (1.0f - std::clamp((io.mouse.pos.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f;
|
||||
set_hsv(savedHsv);
|
||||
}
|
||||
if(selection.held && modifyingAlpha) {
|
||||
Vector2f alphaPos = get_alpha_bar_pos();
|
||||
Vector2f alphaDim = get_alpha_bar_dim();
|
||||
(*data)[3] = std::clamp((io.mouse.pos.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f);
|
||||
oldData = *data;
|
||||
}
|
||||
if(!selection.held) {
|
||||
modifyingSv = false;
|
||||
modifyingHue = false;
|
||||
modifyingAlpha = false;
|
||||
}
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
if(!data)
|
||||
return;
|
||||
|
||||
bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
float svSelectionAreaSize = get_sv_selection_area_size();
|
||||
canvas->scale(svSelectionAreaSize, svSelectionAreaSize);
|
||||
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
|
||||
SkPaint svSelectionAreaPaint;
|
||||
svSelectionAreaPaint.setShader(get_sv_selection_shader(savedHsv.x() / 360.0f));
|
||||
canvas->drawPaint(svSelectionAreaPaint);
|
||||
|
||||
SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f});
|
||||
selectionLinePaint.setStrokeWidth(2.0f / svSelectionAreaSize);
|
||||
canvas->drawLine(0.0f, 1.0f - savedHsv.z(), 1.0f, 1.0f - savedHsv.z(), selectionLinePaint);
|
||||
canvas->drawLine(savedHsv.y(), 0.0f, savedHsv.y(), 1.0f, selectionLinePaint);
|
||||
|
||||
canvas->restore();
|
||||
|
||||
float normalizedHue = savedHsv.x() / 360.0f;
|
||||
|
||||
Vector2f hueBarPos = get_hue_bar_pos();
|
||||
Vector2f hueBarDim = get_hue_bar_dim();
|
||||
canvas->save();
|
||||
canvas->translate(hueBarPos.x(), hueBarPos.y());
|
||||
canvas->scale(hueBarDim.x(), hueBarDim.y());
|
||||
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f));
|
||||
|
||||
SkPaint hueBarPaint;
|
||||
hueBarPaint.setShader(get_hue_shader());
|
||||
canvas->drawPaint(hueBarPaint);
|
||||
canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint);
|
||||
|
||||
canvas->restore();
|
||||
|
||||
Vector2f alphaBarPos = get_alpha_bar_pos();
|
||||
Vector2f alphaBarDim = get_alpha_bar_dim();
|
||||
canvas->save();
|
||||
canvas->translate(alphaBarPos.x(), alphaBarPos.y());
|
||||
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()));
|
||||
|
||||
SkPaint alphaBarPaint;
|
||||
if(selectAlpha)
|
||||
alphaBarPaint.setShader(get_alpha_bar_shader({(*data)[0], (*data)[1], (*data)[2]}, alphaBarDim.x()));
|
||||
else
|
||||
alphaBarPaint.setColor4f(SkColor4f{(*data)[0], (*data)[1], (*data)[2], 1.0f});
|
||||
canvas->drawPaint(alphaBarPaint);
|
||||
|
||||
if(selectAlpha) {
|
||||
canvas->scale(alphaBarDim.x(), alphaBarDim.y());
|
||||
canvas->drawLine((*data)[3], 0.0f, (*data)[3], 1.0f, selectionLinePaint);
|
||||
}
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
private:
|
||||
void force_update_colorpicker() {
|
||||
if(data && (*data == oldData))
|
||||
return;
|
||||
|
||||
if(data) {
|
||||
savedHsv = rgb_to_hsv<Vector3f>(*data);
|
||||
oldData = *data;
|
||||
}
|
||||
else
|
||||
oldData = T{0, 0, 0, 0};
|
||||
}
|
||||
|
||||
float get_sv_selection_area_size() {
|
||||
return bb.width() - (BAR_GAP + BAR_WIDTH);
|
||||
}
|
||||
|
||||
Vector2f get_hue_bar_pos() {
|
||||
return {bb.min.x() + get_sv_selection_area_size() + BAR_GAP, bb.min.y()};
|
||||
}
|
||||
|
||||
Vector2f get_hue_bar_dim() {
|
||||
return {BAR_WIDTH, get_sv_selection_area_size()};
|
||||
}
|
||||
|
||||
Vector2f get_alpha_bar_pos() {
|
||||
return {bb.min.x(), bb.min.y() + get_sv_selection_area_size() + BAR_GAP};
|
||||
}
|
||||
|
||||
Vector2f get_alpha_bar_dim() {
|
||||
return {bb.width(), BAR_WIDTH};
|
||||
}
|
||||
|
||||
void set_hsv(const Vector3f& hsv) {
|
||||
Vector3f a = hsv_to_rgb<Vector3f>(hsv);
|
||||
(*data)[0] = a.x();
|
||||
(*data)[1] = a.y();
|
||||
(*data)[2] = a.z();
|
||||
oldData = *data;
|
||||
}
|
||||
|
||||
static constexpr float BAR_WIDTH = 30.0;
|
||||
static constexpr float BAR_GAP = 5.0;
|
||||
|
||||
// HSV to RGB func from: https://github.com/hughsk/glsl-hsv2rgb?tab=readme-ov-file
|
||||
static constexpr const char* hueBarSkSl =
|
||||
R"V(
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
vec4 main(vec2 fragCoord) {
|
||||
vec3 a = hsv2rgb(vec3(1.0 - fragCoord.y, 1.0, 1.0));
|
||||
return vec4(a.xyz, 1.0);
|
||||
})V";
|
||||
|
||||
static constexpr const char* svSelectionAreaSkSl =
|
||||
R"V(uniform float hue;
|
||||
vec3 hsv2rgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
vec4 main(vec2 fragCoord) {
|
||||
vec3 a = hsv2rgb(vec3(hue, fragCoord.x, 1.0 - fragCoord.y));
|
||||
return vec4(a.xyz, 1.0);
|
||||
})V";
|
||||
|
||||
static constexpr const char* alphaBarSksl =
|
||||
R"V(uniform vec3 mainColor;
|
||||
uniform float horizontalResolution;
|
||||
|
||||
vec4 main(vec2 fragcoord) {
|
||||
vec4 alphaBoard1 = vec4(0.2, 0.2, 0.2, 1.0);
|
||||
vec4 alphaBoard2 = vec4(0.8, 0.8, 0.8, 1.0);
|
||||
|
||||
vec2 flooredCoords = floor(fragcoord / 10);
|
||||
bool isDark = mod(flooredCoords.x + flooredCoords.y, 2.0) > 0.5;
|
||||
|
||||
return mix(isDark ? alphaBoard2 : alphaBoard1, vec4(mainColor, 1.0), fragcoord.x / horizontalResolution);
|
||||
}
|
||||
)V";
|
||||
|
||||
static sk_sp<SkShader> hueBarShader;
|
||||
static sk_sp<SkRuntimeEffect> svSelectionAreaEffect;
|
||||
static sk_sp<SkRuntimeEffect> alphaBarEffect;
|
||||
|
||||
static sk_sp<SkShader> get_hue_shader();
|
||||
static sk_sp<SkShader> get_sv_selection_shader(float hue);
|
||||
static sk_sp<SkShader> get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution);
|
||||
|
||||
SCollision::AABB<float> bb;
|
||||
SelectionHelper selection;
|
||||
T* data = nullptr;
|
||||
T oldData;
|
||||
Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI
|
||||
bool selectAlpha;
|
||||
bool modifyingSv = false;
|
||||
bool modifyingHue = false;
|
||||
bool modifyingAlpha = false;
|
||||
};
|
||||
|
||||
template <typename T> sk_sp<SkShader> ColorPicker<T>::hueBarShader;
|
||||
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::svSelectionAreaEffect;
|
||||
template <typename T> sk_sp<SkRuntimeEffect> ColorPicker<T>::alphaBarEffect;
|
||||
|
||||
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_hue_shader(){
|
||||
if(!hueBarShader) {
|
||||
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(hueBarSkSl));
|
||||
if(!err.isEmpty()) {
|
||||
std::cout << "[ColorPicker::get_hue_shader] Hue shader construction error\n";
|
||||
std::cout << err.c_str() << std::endl;
|
||||
}
|
||||
else
|
||||
hueBarShader = effect->makeShader(nullptr, {nullptr, 0});
|
||||
}
|
||||
return hueBarShader;
|
||||
}
|
||||
|
||||
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_sv_selection_shader(float hue) {
|
||||
if(!svSelectionAreaEffect) {
|
||||
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(svSelectionAreaSkSl));
|
||||
if(!err.isEmpty()) {
|
||||
std::cout << "[ColorPicker::get_sv_selection_shader] SV Selection shader construction error\n";
|
||||
std::cout << err.c_str() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
svSelectionAreaEffect = effect;
|
||||
}
|
||||
SkRuntimeShaderBuilder builder(svSelectionAreaEffect);
|
||||
builder.uniform("hue") = hue;
|
||||
return builder.makeShader();
|
||||
}
|
||||
|
||||
template <typename T> sk_sp<SkShader> ColorPicker<T>::get_alpha_bar_shader(const Vector3f& mainColor, float horizontalResolution) {
|
||||
if(!alphaBarEffect) {
|
||||
auto [effect, err] = SkRuntimeEffect::MakeForShader(SkString(alphaBarSksl));
|
||||
if(!err.isEmpty()) {
|
||||
std::cout << "[ColorPicker::get_alpha_bar_shader] Alpha bar shader construction error\n";
|
||||
std::cout << err.c_str() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
else
|
||||
alphaBarEffect = effect;
|
||||
}
|
||||
SkRuntimeShaderBuilder builder(alphaBarEffect);
|
||||
builder.uniform("mainColor") = SkV3{mainColor.x(), mainColor.y(), mainColor.z()};
|
||||
builder.uniform("horizontalResolution") = horizontalResolution;
|
||||
return builder.makeShader();
|
||||
}
|
||||
|
||||
}
|
||||
63
src/GUIStuff/Elements/Element.cpp
Normal file
63
src/GUIStuff/Elements/Element.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
SkFont get_setup_skfont() {
|
||||
SkFont font;
|
||||
font.setLinearMetrics(true);
|
||||
font.setHinting(SkFontHinting::kNormal);
|
||||
//font.setForceAutoHinting(true);
|
||||
font.setSubpixel(true);
|
||||
font.setBaselineSnap(true);
|
||||
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
||||
//paint.setAntiAlias(true);
|
||||
return font;
|
||||
}
|
||||
|
||||
SkFont UpdateInputData::get_font(float fSize) const {
|
||||
SkFont f = get_setup_skfont();
|
||||
f.setTypeface(textTypeface);
|
||||
f.setSize(fSize);
|
||||
return f;
|
||||
}
|
||||
|
||||
std::shared_ptr<Theme> get_default_dark_mode() {
|
||||
std::shared_ptr<Theme> theme(std::make_shared<Theme>());
|
||||
theme->fillColor1 = {0.1f, 0.4f, 0.8f, 1.0f};
|
||||
theme->fillColor2 = {0.4f, 0.5f, 0.9f, 1.0f};
|
||||
theme->fillColor3 = {1.0f, 0.0f, 0.0f, 1.0f};
|
||||
theme->fillColor4 = {1.0f, 1.0f, 0.0f, 1.0f};
|
||||
theme->backColor1 = {0.0f, 0.0f, 0.0f, 0.9f};
|
||||
theme->backColor2 = {0.15f, 0.15f, 0.15f, 1.0f};
|
||||
theme->backColor3 = {0.22f, 0.22f, 0.22f, 1.0f};
|
||||
theme->frontColor1 = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
theme->frontColor2 = {0.8f, 0.8f, 0.8f, 1.0f};
|
||||
return theme;
|
||||
}
|
||||
|
||||
SCollision::AABB<float> Element::get_bb(Clay_RenderCommand* command) {
|
||||
return {{command->boundingBox.x, command->boundingBox.y}, {command->boundingBox.x + command->boundingBox.width, command->boundingBox.y + command->boundingBox.height}};
|
||||
}
|
||||
|
||||
void SelectionHelper::update(bool isHovering, bool isLeftClick, bool isLeftHeld) {
|
||||
hovered = isHovering;
|
||||
|
||||
clicked = false;
|
||||
justUnselected = false;
|
||||
bool oldSelected = selected;
|
||||
|
||||
if(isLeftClick) {
|
||||
if(hovered) {
|
||||
selected = true;
|
||||
clicked = true;
|
||||
held = true;
|
||||
}
|
||||
else
|
||||
selected = false;
|
||||
}
|
||||
else if(!isLeftHeld)
|
||||
held = false;
|
||||
|
||||
if(!selected && oldSelected)
|
||||
justUnselected = true;
|
||||
}
|
||||
}
|
||||
138
src/GUIStuff/Elements/Element.hpp
Normal file
138
src/GUIStuff/Elements/Element.hpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
#pragma once
|
||||
#include "Helpers/ConvertVec.hpp"
|
||||
#include <include/core/SkCanvas.h>
|
||||
#include "../../TimePoint.hpp"
|
||||
#include <Eigen/Dense>
|
||||
#include <include/core/SkFont.h>
|
||||
#include <include/core/SkFontMgr.h>
|
||||
#include <modules/svg/include/SkSVGDOM.h>
|
||||
#include <Helpers/SCollision.hpp>
|
||||
#include <Helpers/Serializers.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using namespace Eigen;
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
// Taken from https://github.com/TimothyHoytBSME/ClayMan (rewritten to be a separate struct, and use templates to change size)
|
||||
template <size_t S> class StringArena {
|
||||
public:
|
||||
StringArena() {
|
||||
stringArena = std::vector<char>(S);
|
||||
}
|
||||
void reset() {
|
||||
stringArenaIndex = 0;
|
||||
}
|
||||
const char* insert_string_into_arena(const std::string& str) {
|
||||
size_t strSize = str.size();
|
||||
if(stringArenaIndex + strSize + 1 > S)
|
||||
throw std::overflow_error("[StringArena::insert_string_into_arena] Not enough space to insert string");
|
||||
char* startPtr = &stringArena[stringArenaIndex];
|
||||
for(size_t i = 0; i < strSize; i++)
|
||||
stringArena[stringArenaIndex++] = str[i];
|
||||
stringArena[stringArenaIndex++] = ' ';
|
||||
return startPtr;
|
||||
}
|
||||
Clay_String std_str_to_clay_str(const std::string& str) {
|
||||
return Clay_String{.length = static_cast<int32_t>(str.size()), .chars = insert_string_into_arena(str)};
|
||||
}
|
||||
private:
|
||||
std::vector<char> stringArena;
|
||||
size_t stringArenaIndex;
|
||||
};
|
||||
|
||||
typedef StringArena<4000000> DefaultStringArena;
|
||||
|
||||
SkFont get_setup_skfont();
|
||||
|
||||
struct Theme {
|
||||
SkColor4f fillColor1 = {0.5f, 0.6f, 1.0f, 1.0f};
|
||||
SkColor4f fillColor2 = {0.4f, 0.5f, 0.9f, 1.0f};
|
||||
SkColor4f fillColor3 = {1.0f, 0.0f, 0.0f, 1.0f};
|
||||
SkColor4f fillColor4 = {1.0f, 1.0f, 0.0f, 1.0f};
|
||||
SkColor4f backColor1 = {1.0f, 1.0f, 1.0f, 0.9f};
|
||||
SkColor4f backColor2 = {0.95f, 0.95f, 0.95f, 1.0f};
|
||||
SkColor4f backColor3 = {0.9f, 0.9f, 0.9f, 1.0f};
|
||||
SkColor4f frontColor1 = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
SkColor4f frontColor2 = {0.1f, 0.1f, 0.1f, 1.0f};
|
||||
|
||||
float hoverExpandTime = 0.1f;
|
||||
uint16_t childGap1 = 10;
|
||||
uint16_t padding1 = 10;
|
||||
float windowCorners1 = 10;
|
||||
uint16_t windowBorders1 = 2;
|
||||
|
||||
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Theme, fillColor1, fillColor2, fillColor3, fillColor4, backColor1, backColor2, backColor3, frontColor1, frontColor2, hoverExpandTime, childGap1, padding1, windowCorners1, windowBorders1)
|
||||
};
|
||||
|
||||
std::shared_ptr<Theme> get_default_dark_mode();
|
||||
|
||||
struct UpdateInputData {
|
||||
struct {
|
||||
int leftClick = 0;
|
||||
int rightClick = 0;
|
||||
bool leftDoubleClick = false;
|
||||
bool leftHeld = false;
|
||||
bool rightHeld = false;
|
||||
Vector2f globalPos{0, 0};
|
||||
Vector2f pos{0, 0}; // Position relative to window
|
||||
Vector2f scroll;
|
||||
} mouse;
|
||||
|
||||
struct {
|
||||
bool left = false;
|
||||
bool right = false;
|
||||
bool up = false;
|
||||
bool down = false;
|
||||
bool leftShift = false;
|
||||
bool leftCtrl = false;
|
||||
bool home = false;
|
||||
bool del = false;
|
||||
bool backspace = false;
|
||||
bool enter = false;
|
||||
bool selectAll = false;
|
||||
bool copy = false;
|
||||
bool paste = false;
|
||||
bool cut = false;
|
||||
} key;
|
||||
|
||||
struct {
|
||||
std::function<std::string()> textInFunc;
|
||||
std::optional<std::string> textOut;
|
||||
} clipboard;
|
||||
|
||||
SkFont get_font(float fSize) const;
|
||||
|
||||
uint16_t fontSize = 20;
|
||||
sk_sp<SkTypeface> textTypeface;
|
||||
|
||||
std::string textInput;
|
||||
bool hoverObstructed = false;
|
||||
bool acceptingTextInput = false;
|
||||
float deltaTime = 0.0f;
|
||||
sk_sp<SkFontMgr> textFontMgr;
|
||||
DefaultStringArena* strArena;
|
||||
std::shared_ptr<Theme> theme;
|
||||
std::unordered_map<std::string, sk_sp<SkSVGDOM>> svgData;
|
||||
};
|
||||
|
||||
class SelectionHelper {
|
||||
public:
|
||||
void update(bool isHovering, bool isLeftClick, bool isLeftHeld);
|
||||
bool held = false;
|
||||
bool hovered = false;
|
||||
bool selected = false;
|
||||
bool clicked = false;
|
||||
bool justUnselected = false;
|
||||
};
|
||||
|
||||
class Element {
|
||||
public:
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) = 0;
|
||||
virtual ~Element() = default;
|
||||
|
||||
protected:
|
||||
static SCollision::AABB<float> get_bb(Clay_RenderCommand* command);
|
||||
};
|
||||
|
||||
}
|
||||
79
src/GUIStuff/Elements/MovableTabList.cpp
Normal file
79
src/GUIStuff/Elements/MovableTabList.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#include "MovableTabList.hpp"
|
||||
#include "Helpers/ConvertVec.hpp"
|
||||
#include <iostream>
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
void MovableTabList::update(UpdateInputData& io, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate) {
|
||||
size_t currentSelectedTab = selectedTab;
|
||||
if(tabNames != oldTabNames) {
|
||||
buttons = std::vector<SelectableButton>(tabNames.size());
|
||||
closeButtons = std::vector<SelectableButton>(tabNames.size());
|
||||
closeIcons = std::vector<SVGIcon>(tabNames.size());
|
||||
tabIcons = std::vector<SVGIcon>(tabNames.size());
|
||||
oldTabNames = tabNames;
|
||||
}
|
||||
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(45)},
|
||||
.padding = CLAY_PADDING_ALL(2),
|
||||
.childGap = 8,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
},
|
||||
.clip = {.horizontal = true, .childOffset = Clay_GetScrollOffset()}
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
for(size_t i = 0; i < buttons.size(); i++) {
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(250), .height = CLAY_SIZING_GROW(0)},
|
||||
}
|
||||
}) {
|
||||
buttons[i].update(io, true, true, [&](SelectionHelper& s, bool iS) {
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
||||
.padding = CLAY_PADDING_ALL(4),
|
||||
.childGap = 4,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
}
|
||||
}) {
|
||||
if(!tabNames[i].first.empty()) {
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(25), .height = CLAY_SIZING_FIXED(25)},
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
}
|
||||
}) {
|
||||
tabIcons[i].update(io, tabNames[i].first, iS, nullptr);
|
||||
}
|
||||
}
|
||||
CLAY_TEXT(io.strArena->std_str_to_clay_str(tabNames[i].second), CLAY_TEXT_CONFIG({.textColor = convert_vec4<Clay_Color>(io.theme->frontColor1), .fontSize = io.fontSize }));
|
||||
if(s.clicked)
|
||||
selectedTab = i;
|
||||
CLAY({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(25), .height = CLAY_SIZING_FIXED(25)},
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
}
|
||||
}) {
|
||||
closeButtons[i].update(io, false, false, [&](SelectionHelper& s, bool iS) {
|
||||
if(s.clicked)
|
||||
closedTab = i;
|
||||
closeIcons[i].update(io, "data/icons/close.svg", s.held || s.hovered, nullptr);
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
}, currentSelectedTab == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MovableTabList::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
}
|
||||
|
||||
}
|
||||
21
src/GUIStuff/Elements/MovableTabList.hpp
Normal file
21
src/GUIStuff/Elements/MovableTabList.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
#include "SelectableButton.hpp"
|
||||
#include "SVGIcon.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class MovableTabList : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, const std::vector<std::pair<std::string, std::string>>& tabNames, size_t& selectedTab, std::optional<size_t>& closedTab, const std::function<void()>& elemUpdate);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
SelectionHelper selection;
|
||||
private:
|
||||
std::vector<std::pair<std::string, std::string>> oldTabNames;
|
||||
std::vector<SelectableButton> buttons;
|
||||
std::vector<SelectableButton> closeButtons;
|
||||
std::vector<SVGIcon> closeIcons;
|
||||
std::vector<SVGIcon> tabIcons;
|
||||
};
|
||||
|
||||
}
|
||||
91
src/GUIStuff/Elements/NumberSlider.hpp
Normal file
91
src/GUIStuff/Elements/NumberSlider.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
template <typename T> class NumberSlider : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, T* dataPtr, T minNew, T maxNew, const std::function<void()>& elemUpdate) {
|
||||
data = dataPtr;
|
||||
min = minNew;
|
||||
max = maxNew;
|
||||
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(100), .height = CLAY_SIZING_FIXED(15)}
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(selection.held && data) {
|
||||
float fracPosOnSlider = (io.mouse.pos.x() - bb.min.x()) / bb.width();
|
||||
*data = std::clamp<T>(std::lerp<double>(min, max, fracPosOnSlider), min, max);
|
||||
}
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override {
|
||||
if(!data)
|
||||
return;
|
||||
|
||||
bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
|
||||
float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, selection.hovered, io.theme->hoverExpandTime);
|
||||
|
||||
//static BezierEasing easeHeight(0.75, 0.25, 0.25, 0.75);
|
||||
static BezierEasing easeHeight(0.68, -1.55, 0.265, 2.55);
|
||||
|
||||
float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, selection.held, 0.3));
|
||||
|
||||
float holderRadius = lerp_vec(4.0, 5.0, lerpTimeHover);
|
||||
float holderHeight = lerp_vec(4.0, 10.0, lerpTimeHeld);
|
||||
|
||||
const float yChange = bb.height() * 0.5f - holderRadius * 0.5f;
|
||||
|
||||
float holderPos = lerp_time<float>(*data, max, min) * bb.width();
|
||||
|
||||
SkRect barFull = SkRect::MakeXYWH(0.0f, yChange, holderPos, holderRadius);
|
||||
SkRect barEmpty = SkRect::MakeXYWH(holderPos, yChange, bb.width() - holderPos, holderRadius);
|
||||
|
||||
SkPaint barFullP;
|
||||
barFullP.setColor(convert_vec4<SkColor4f>(io.theme->fillColor1));
|
||||
canvas->drawRoundRect(barFull, 5.0f, 5.0f, barFullP);
|
||||
|
||||
SkPaint barEmptyP;
|
||||
barEmptyP.setColor(convert_vec4<SkColor4f>(io.theme->backColor2));
|
||||
canvas->drawRoundRect(barEmpty, 5.0f, 5.0f, barEmptyP);
|
||||
|
||||
canvas->translate(holderPos, bb.height() * 0.5f);
|
||||
|
||||
SkRect holderRect = SkRect::MakeLTRB(-holderRadius, -holderHeight, holderRadius, holderHeight);
|
||||
SkPaint holderBorderP;
|
||||
holderBorderP.setStyle(SkPaint::kStroke_Style);
|
||||
holderBorderP.setStrokeWidth(3.0f);
|
||||
holderBorderP.setColor(convert_vec4<SkColor4f>(io.theme->fillColor1));
|
||||
canvas->drawRoundRect(holderRect, holderRadius, holderRadius, holderBorderP);
|
||||
|
||||
SkPaint holderP;
|
||||
holderP.setColor(convert_vec4<SkColor4f>(io.theme->backColor2));
|
||||
canvas->drawRoundRect(holderRect, holderRadius, holderRadius, holderP);
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
private:
|
||||
SCollision::AABB<float> bb;
|
||||
SelectionHelper selection;
|
||||
|
||||
T* data = nullptr;
|
||||
T min = 0.0;
|
||||
T max = 1.0;
|
||||
|
||||
float hoverAnimation = 0.0;
|
||||
float holdAnimation = 0.0;
|
||||
};
|
||||
|
||||
}
|
||||
193
src/GUIStuff/Elements/PaintCircleMenu.cpp
Normal file
193
src/GUIStuff/Elements/PaintCircleMenu.cpp
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#include "PaintCircleMenu.hpp"
|
||||
#include "Helpers/ConvertVec.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
constexpr float ROTATE_START = 125.0f;
|
||||
constexpr float PALETTE_START = 100.0f;
|
||||
constexpr float CIRCLE_END = 150.0f;
|
||||
constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.2;
|
||||
constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25;
|
||||
|
||||
void PaintCircleMenu::update(UpdateInputData& io, const Data& data, const std::function<void()>& elemUpdate) {
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
bool isHoveredNoObstruction = Clay_Hovered() && !io.hoverObstructed;
|
||||
Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized();
|
||||
float distFromCenter = vec_distance(io.mouse.pos, bb.center());
|
||||
bool rotateHovered = false;
|
||||
if(data.newRotationAngle) {
|
||||
rotateBarSelect.update(isHoveredNoObstruction && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END, io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(rotateBarSelect.held) {
|
||||
double& newRotationAngle = *data.newRotationAngle;
|
||||
newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
|
||||
if(rotateBarSelect.hovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles
|
||||
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
|
||||
if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) {
|
||||
newRotationAngle = snapPos;
|
||||
if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0
|
||||
newRotationAngle = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
*data.newRotationAngle = data.currentRotationAngle;
|
||||
rotateHovered = rotateBarSelect.hovered;
|
||||
}
|
||||
bool colorHovered = false;
|
||||
if(!data.palette.empty() && data.selectedColor) {
|
||||
colorBarSelect.update(isHoveredNoObstruction && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START, io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(colorBarSelect.hovered) {
|
||||
Vector4f& selectedColor = *data.selectedColor;
|
||||
double selectionAngle = std::atan2(-vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi;
|
||||
colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * data.palette.size();
|
||||
colorSelectionIndex = std::clamp<unsigned>(colorSelectionIndex, 0, data.palette.size() - 1);
|
||||
if(colorBarSelect.clicked) {
|
||||
selectedColor.x() = data.palette[colorSelectionIndex].x();
|
||||
selectedColor.y() = data.palette[colorSelectionIndex].y();
|
||||
selectedColor.z() = data.palette[colorSelectionIndex].z();
|
||||
}
|
||||
}
|
||||
colorHovered = colorBarSelect.hovered;
|
||||
}
|
||||
if(rotateHovered || colorHovered)
|
||||
io.hoverObstructed = true;
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
|
||||
d = data;
|
||||
}
|
||||
|
||||
void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
bb = get_bb(command);
|
||||
canvas->save();
|
||||
canvas->translate(bb.center().x(), bb.center().y());
|
||||
|
||||
draw_rotate_bar(canvas, io);
|
||||
draw_palette_bar(canvas, io);
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io) {
|
||||
if(!d.newRotationAngle)
|
||||
return;
|
||||
|
||||
double rotationAngle = d.currentRotationAngle;
|
||||
float rotateBarMiddleRadius = (CIRCLE_END + ROTATE_START) / 2.0f;
|
||||
SkPaint rotateBarFill(io.theme->backColor2);
|
||||
rotateBarFill.setStroke(true);
|
||||
rotateBarFill.setStrokeWidth(CIRCLE_END - ROTATE_START);
|
||||
canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill);
|
||||
|
||||
if(rotateBarSelect.hovered) {
|
||||
for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) {
|
||||
Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)};
|
||||
SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2);
|
||||
p.setStrokeWidth(2.0f);
|
||||
canvas->drawLine(lineDir.x() * ROTATE_START, lineDir.y() * ROTATE_START, lineDir.x() * CIRCLE_END, lineDir.y() * CIRCLE_END, p);
|
||||
}
|
||||
}
|
||||
|
||||
SkPaint rotateBarHolderFill;
|
||||
if(rotateBarSelect.held)
|
||||
rotateBarHolderFill.setColor4f(io.theme->fillColor1);
|
||||
else if(rotateBarSelect.hovered)
|
||||
rotateBarHolderFill.setColor4f(io.theme->fillColor2);
|
||||
else
|
||||
rotateBarHolderFill.setColor4f(io.theme->frontColor2);
|
||||
float rotateBarHolderRadius = (CIRCLE_END - ROTATE_START) / 2.0f;
|
||||
Vector2f rotateBarHolderPos{std::cos(rotationAngle) * rotateBarMiddleRadius, -std::sin(rotationAngle) * rotateBarMiddleRadius};
|
||||
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill);
|
||||
SkPaint rotateBarHolderOutline(io.theme->frontColor1);
|
||||
rotateBarHolderOutline.setStroke(true);
|
||||
rotateBarHolderOutline.setStrokeWidth(2.0f);
|
||||
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderOutline);
|
||||
|
||||
SkPaint rotateBarOutline(io.theme->frontColor2);
|
||||
rotateBarOutline.setStroke(true);
|
||||
rotateBarOutline.setStrokeWidth(2.0f);
|
||||
canvas->drawCircle(0.0f, 0.0f, ROTATE_START, rotateBarOutline);
|
||||
canvas->drawCircle(0.0f, 0.0f, CIRCLE_END, rotateBarOutline);
|
||||
}
|
||||
|
||||
void PaintCircleMenu::draw_palette_bar(SkCanvas* canvas, UpdateInputData& io) {
|
||||
if(d.palette.empty() || !d.selectedColor)
|
||||
return;
|
||||
|
||||
float middleRadius = (PALETTE_START + ROTATE_START) / 2.0f;
|
||||
|
||||
double colorDistribution = std::numbers::pi * 2.0 / d.palette.size();
|
||||
SkRect colorOval = SkRect::MakeLTRB(-middleRadius, -middleRadius, middleRadius, middleRadius);
|
||||
for(unsigned i = 0; i < d.palette.size(); i++) {
|
||||
double angleStart = i * colorDistribution;
|
||||
double angleEnd = (i + 1) * colorDistribution;
|
||||
|
||||
SkPaint colorP(SkColor4f{d.palette[i].x(), d.palette[i].y(), d.palette[i].z(), 1.0f});
|
||||
colorP.setStroke(true);
|
||||
colorP.setStrokeWidth(ROTATE_START - PALETTE_START);
|
||||
canvas->drawArc(colorOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false, colorP);
|
||||
|
||||
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
|
||||
SkPaint lineP(io.theme->frontColor2);
|
||||
lineP.setStrokeWidth(2.0f);
|
||||
lineP.setStroke(true);
|
||||
canvas->drawLine(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START, lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START, lineP);
|
||||
}
|
||||
|
||||
SkPaint barOutline(io.theme->frontColor2);
|
||||
barOutline.setStroke(true);
|
||||
barOutline.setStrokeWidth(2.0f);
|
||||
canvas->drawCircle(0.0f, 0.0f, PALETTE_START, barOutline);
|
||||
canvas->drawCircle(0.0f, 0.0f, ROTATE_START, barOutline);
|
||||
|
||||
SkRect startOval = SkRect::MakeLTRB(-PALETTE_START, -PALETTE_START, PALETTE_START, PALETTE_START);
|
||||
SkRect endOval = SkRect::MakeLTRB(-ROTATE_START, -ROTATE_START, ROTATE_START, ROTATE_START);
|
||||
if(colorBarSelect.hovered) {
|
||||
SkPath selectionPath;
|
||||
double angleStart = colorSelectionIndex * colorDistribution;
|
||||
double angleEnd = (colorSelectionIndex + 1) * colorDistribution;
|
||||
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
|
||||
Vector2f lineDir2{std::cos(angleEnd), std::sin(angleEnd)};
|
||||
SkPaint lineP(io.theme->fillColor2);
|
||||
lineP.setStrokeWidth(5.0f);
|
||||
lineP.setStroke(true);
|
||||
lineP.setStrokeJoin(SkPaint::kRound_Join);
|
||||
selectionPath.moveTo(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START);
|
||||
selectionPath.lineTo(lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START);
|
||||
selectionPath.arcTo(endOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false);
|
||||
selectionPath.lineTo(lineDir2.x() * PALETTE_START, lineDir2.y() * PALETTE_START);
|
||||
selectionPath.arcTo(startOval, angleEnd * 180.0 / std::numbers::pi, (angleStart - angleEnd) * 180.0 / std::numbers::pi, false);
|
||||
selectionPath.close();
|
||||
canvas->drawPath(selectionPath, lineP);
|
||||
}
|
||||
for(unsigned i = 0; i < d.palette.size(); i++) {
|
||||
if(d.palette[i].x() == d.selectedColor->x() && d.palette[i].y() == d.selectedColor->y() && d.palette[i].z() == d.selectedColor->z()) {
|
||||
SkPath selectionPath;
|
||||
double angleStart = i * colorDistribution;
|
||||
double angleEnd = (i + 1) * colorDistribution;
|
||||
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
|
||||
Vector2f lineDir2{std::cos(angleEnd), std::sin(angleEnd)};
|
||||
SkPaint lineP(io.theme->fillColor1);
|
||||
lineP.setStrokeWidth(5.0f);
|
||||
lineP.setStroke(true);
|
||||
lineP.setStrokeJoin(SkPaint::kRound_Join);
|
||||
selectionPath.moveTo(lineDir1.x() * PALETTE_START, lineDir1.y() * PALETTE_START);
|
||||
selectionPath.lineTo(lineDir1.x() * ROTATE_START, lineDir1.y() * ROTATE_START);
|
||||
selectionPath.arcTo(endOval, angleStart * 180.0 / std::numbers::pi, (angleEnd - angleStart) * 180.0 / std::numbers::pi, false);
|
||||
selectionPath.lineTo(lineDir2.x() * PALETTE_START, lineDir2.y() * PALETTE_START);
|
||||
selectionPath.arcTo(startOval, angleEnd * 180.0 / std::numbers::pi, (angleStart - angleEnd) * 180.0 / std::numbers::pi, false);
|
||||
selectionPath.close();
|
||||
canvas->drawPath(selectionPath, lineP);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
src/GUIStuff/Elements/PaintCircleMenu.hpp
Normal file
26
src/GUIStuff/Elements/PaintCircleMenu.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class PaintCircleMenu : public Element {
|
||||
public:
|
||||
struct Data {
|
||||
double currentRotationAngle = 0.0;
|
||||
double* newRotationAngle = nullptr;
|
||||
Vector4f* selectedColor = nullptr;
|
||||
std::vector<Vector3f> palette;
|
||||
};
|
||||
void update(UpdateInputData& io, const Data& data, const std::function<void()>& elemUpdate);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
private:
|
||||
void draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io);
|
||||
void draw_palette_bar(SkCanvas* canvas, UpdateInputData& io);
|
||||
Data d;
|
||||
SCollision::AABB<float> bb;
|
||||
SelectionHelper rotateBarSelect;
|
||||
SelectionHelper colorBarSelect;
|
||||
unsigned colorSelectionIndex = 0;
|
||||
};
|
||||
|
||||
}
|
||||
57
src/GUIStuff/Elements/RadioButton.cpp
Normal file
57
src/GUIStuff/Elements/RadioButton.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "RadioButton.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
void RadioButton::update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(isTicked != newIsTicked) {
|
||||
hoverAnimation2 = RADIOBUTTON_ANIMATION_TIME;
|
||||
isTicked = newIsTicked;
|
||||
}
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
auto bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
canvas->scale(bb.width(), bb.height());
|
||||
canvas->translate(0.5f, 0.5f);
|
||||
|
||||
if(isTicked) {
|
||||
SkPaint p;
|
||||
p.setColor4f(convert_vec4<SkColor4f>(io.theme->fillColor1));
|
||||
p.setStyle(SkPaint::kFill_Style);
|
||||
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);
|
||||
|
||||
SkPaint innerCircleP;
|
||||
innerCircleP.setColor4f(convert_vec4<SkColor4f>(io.theme->backColor2));
|
||||
innerCircleP.setStyle(SkPaint::kFill_Style);
|
||||
|
||||
static BezierEasing easeRadius(0.68, -2.55, 0.265, 3.55);
|
||||
float lerpTime2 = easeRadius(smooth_two_way_time(hoverAnimation2, io.deltaTime, selection.hovered, RADIOBUTTON_ANIMATION_TIME));
|
||||
float innerCircleRadius = lerp_vec(0.3f, 0.2f, lerpTime2);
|
||||
|
||||
canvas->drawCircle(0.0f, 0.0f, innerCircleRadius, innerCircleP);
|
||||
}
|
||||
else {
|
||||
SkPaint p;
|
||||
p.setColor4f(convert_vec4<SkColor4f>(selection.hovered ? io.theme->fillColor1 : io.theme->backColor3));
|
||||
p.setStyle(SkPaint::kStroke_Style);
|
||||
p.setStrokeWidth(0.15f);
|
||||
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);
|
||||
}
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
}
|
||||
19
src/GUIStuff/Elements/RadioButton.hpp
Normal file
19
src/GUIStuff/Elements/RadioButton.hpp
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class RadioButton : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, bool newIsTicked, const std::function<void()>& elemUpdate);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
SelectionHelper selection;
|
||||
private:
|
||||
static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3;
|
||||
|
||||
bool isTicked = false;
|
||||
|
||||
float hoverAnimation2 = 0.0;
|
||||
};
|
||||
|
||||
}
|
||||
67
src/GUIStuff/Elements/SVGIcon.cpp
Normal file
67
src/GUIStuff/Elements/SVGIcon.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include "SVGIcon.hpp"
|
||||
#include "Helpers/ConvertVec.hpp"
|
||||
#include <modules/svg/include/SkSVGNode.h>
|
||||
#include <include/core/SkStream.h>
|
||||
#include <include/core/SkColorFilter.h>
|
||||
#include <include/core/SkColorSpace.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
void SVGIcon::update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate) {
|
||||
auto findSVGData = io.svgData.find(newSvgPath);
|
||||
if(findSVGData == io.svgData.end()) {
|
||||
auto stream = SkStream::MakeFromFile(newSvgPath.c_str());
|
||||
if(!stream) {
|
||||
std::cout << "[SVGIcon::update] Could not open file " << newSvgPath << std::endl;
|
||||
io.svgData[newSvgPath] = nullptr;
|
||||
svgDom = nullptr;
|
||||
}
|
||||
auto newSvgDom = SkSVGDOM::Builder().make(*stream);
|
||||
if(!newSvgDom) {
|
||||
io.svgData[newSvgPath] = nullptr;
|
||||
svgDom = nullptr;
|
||||
std::cout << "[SVGIcon::update] Could not parse SVG " << newSvgPath << std::endl;
|
||||
}
|
||||
else {
|
||||
io.svgData[newSvgPath] = newSvgDom;
|
||||
svgDom = newSvgDom;
|
||||
}
|
||||
}
|
||||
else
|
||||
svgDom = findSVGData->second;
|
||||
|
||||
highlighted = newIsHighlighted;
|
||||
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void SVGIcon::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
if(!svgDom)
|
||||
return;
|
||||
|
||||
auto bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
SkRect r = SkRect::MakeXYWH(0.0f, 0.0f, bb.width(), bb.height());
|
||||
canvas->clipRect(r);
|
||||
SkPaint blendingPaint;
|
||||
blendingPaint.setColorFilter(SkColorFilters::Blend(highlighted ? io.theme->frontColor1 : io.theme->frontColor2, SkColorSpace::MakeSRGB(), SkBlendMode::kSrcIn));
|
||||
canvas->saveLayer(r, &blendingPaint);
|
||||
canvas->scale(bb.width() / svgDom->containerSize().width(), bb.height() / svgDom->containerSize().height());
|
||||
svgDom->render(canvas);
|
||||
canvas->restore();
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
}
|
||||
15
src/GUIStuff/Elements/SVGIcon.hpp
Normal file
15
src/GUIStuff/Elements/SVGIcon.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class SVGIcon : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, const std::string& newSvgPath, bool newIsHighlighted, const std::function<void()>& elemUpdate);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
private:
|
||||
bool highlighted;
|
||||
sk_sp<SkSVGDOM> svgDom;
|
||||
};
|
||||
|
||||
}
|
||||
55
src/GUIStuff/Elements/SelectableButton.cpp
Normal file
55
src/GUIStuff/Elements/SelectableButton.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "SelectableButton.hpp"
|
||||
#include "Helpers/ConvertVec.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
void SelectableButton::update(UpdateInputData& io, bool hasBorders, bool hasBorderPadding, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected) {
|
||||
if(hasBorders) {
|
||||
SkColor4f borderColor;
|
||||
if(selection.held)
|
||||
borderColor = io.theme->fillColor1;
|
||||
else if(isSelected)
|
||||
borderColor = io.theme->fillColor2;
|
||||
else if(selection.hovered)
|
||||
borderColor = io.theme->fillColor2;
|
||||
else
|
||||
borderColor = io.theme->backColor3;
|
||||
|
||||
SkColor4f backgroundColor;
|
||||
if(selection.held)
|
||||
backgroundColor = io.theme->backColor3;
|
||||
else if(isSelected)
|
||||
backgroundColor = io.theme->backColor2;
|
||||
else if(selection.hovered)
|
||||
backgroundColor = io.theme->backColor2;
|
||||
else
|
||||
backgroundColor = io.theme->backColor1;
|
||||
|
||||
CLAY({.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)},
|
||||
.padding = hasBorderPadding ? CLAY_PADDING_ALL(4) : CLAY_PADDING_ALL(0),
|
||||
.childGap = 4,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(backgroundColor),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(4),
|
||||
.border = {
|
||||
.color = convert_vec4<Clay_Color>(borderColor),
|
||||
.width = CLAY_BORDER_OUTSIDE(4)
|
||||
}
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
elemUpdate(selection, isSelected);
|
||||
}
|
||||
}
|
||||
else {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
elemUpdate(selection, isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectableButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) {
|
||||
}
|
||||
|
||||
}
|
||||
13
src/GUIStuff/Elements/SelectableButton.hpp
Normal file
13
src/GUIStuff/Elements/SelectableButton.hpp
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
class SelectableButton : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, bool hasBorders, bool hasBorderPadding, const std::function<void(SelectionHelper&, bool)>& elemUpdate, bool isSelected);
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override;
|
||||
SelectionHelper selection;
|
||||
};
|
||||
|
||||
}
|
||||
195
src/GUIStuff/Elements/TextBox.hpp
Normal file
195
src/GUIStuff/Elements/TextBox.hpp
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
#pragma once
|
||||
#include "Element.hpp"
|
||||
#include "../../CollabTextBox/CollabTextBox.hpp"
|
||||
|
||||
namespace GUIStuff {
|
||||
|
||||
template <typename T> class TextBox : public Element {
|
||||
public:
|
||||
void update(UpdateInputData& io, T* newData, const std::function<std::optional<T>(const std::string&)>& newFromStr, const std::function<std::string(const T&)> newToStr, bool newSingleLine, const std::function<void()>& elemUpdate) {
|
||||
data = newData;
|
||||
fromStr = newFromStr;
|
||||
toStr = newToStr;
|
||||
singleLine = newSingleLine;
|
||||
force_update_textbox(false);
|
||||
|
||||
textbox.setFont(SkFont(io.textTypeface, io.fontSize));
|
||||
textbox.setFontMgr(io.textFontMgr);
|
||||
|
||||
CLAY({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(static_cast<float>(io.fontSize * 2)), .height = CLAY_SIZING_GROW(0)}
|
||||
},
|
||||
.custom = { .customData = this }
|
||||
}) {
|
||||
selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld);
|
||||
if(data && selection.selected) {
|
||||
bool moved = io.key.left || io.key.right || io.key.up || io.key.down || io.key.home;
|
||||
bool movedHeld = false;
|
||||
|
||||
if(io.mouse.leftClick) {
|
||||
Vector2f textSelectPos = io.mouse.pos - bb.min;
|
||||
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
|
||||
moved = true;
|
||||
}
|
||||
else if(io.mouse.leftHeld) {
|
||||
Vector2f textSelectPos = io.mouse.pos - bb.min;
|
||||
SkIPoint p = convert_vec2<SkIPoint>(textSelectPos.cast<int32_t>());
|
||||
cur.pos = cur.selectionBeginPos = textbox.getPosition(p);
|
||||
movedHeld = true;
|
||||
}
|
||||
|
||||
if(io.key.left)
|
||||
cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordLeft : CollabTextBox::Movement::kLeft, cur.selectionBeginPos);
|
||||
if(io.key.right)
|
||||
cur.pos = cur.selectionBeginPos = textbox.move(io.key.leftCtrl ? CollabTextBox::Movement::kWordRight : CollabTextBox::Movement::kRight, cur.selectionBeginPos);
|
||||
if(io.key.up && !singleLine)
|
||||
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kUp, cur.selectionBeginPos);
|
||||
if(io.key.down && !singleLine)
|
||||
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kDown, cur.selectionBeginPos);
|
||||
if(io.key.home)
|
||||
cur.pos = cur.selectionBeginPos = textbox.move(CollabTextBox::Movement::kHome, cur.selectionBeginPos);
|
||||
|
||||
if(moved && !io.key.leftShift && !movedHeld)
|
||||
cur.selectionEndPos = cur.selectionBeginPos;
|
||||
|
||||
if(io.key.backspace) {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
else
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kLeft, cur.pos));
|
||||
}
|
||||
if(io.key.del) {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
else
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.pos, textbox.move(CollabTextBox::Movement::kRight, cur.pos));
|
||||
}
|
||||
if(io.key.paste) {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
if(singleLine) {
|
||||
std::string removedNewlines = io.clipboard.textInFunc();
|
||||
std::erase(removedNewlines, '\n');
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, removedNewlines);
|
||||
}
|
||||
else
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.clipboard.textInFunc());
|
||||
}
|
||||
if(io.key.enter) {
|
||||
if(singleLine) {
|
||||
std::optional<T> dataToAssign = fromStr(textbox.get_string());
|
||||
if(dataToAssign)
|
||||
*data = dataToAssign.value();
|
||||
force_update_textbox(true);
|
||||
}
|
||||
else {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
cur.pos = textbox.insert(cur.pos, "\n");
|
||||
cur.pos.fParagraphIndex++;
|
||||
cur.pos.fTextByteIndex = 0;
|
||||
cur.selectionBeginPos = cur.selectionEndPos = cur.pos;
|
||||
}
|
||||
}
|
||||
if(io.key.copy) {
|
||||
io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
}
|
||||
if(io.key.cut) {
|
||||
io.clipboard.textOut = textbox.copy(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
}
|
||||
if(io.key.selectAll) {
|
||||
cur.selectionEndPos.fParagraphIndex = textbox.lineCount() == 0 ? 0 : textbox.lineCount() - 1;
|
||||
cur.selectionEndPos.fTextByteIndex = textbox.line(cur.selectionEndPos.fParagraphIndex).size();
|
||||
cur.pos = cur.selectionEndPos;
|
||||
cur.selectionBeginPos.fTextByteIndex = 0;
|
||||
cur.selectionBeginPos.fParagraphIndex = 0;
|
||||
}
|
||||
if(!io.textInput.empty()) {
|
||||
if(cur.selectionBeginPos != cur.selectionEndPos)
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.remove(cur.selectionBeginPos, cur.selectionEndPos);
|
||||
cur.selectionEndPos = cur.selectionBeginPos = cur.pos = textbox.insert(cur.pos, io.textInput);
|
||||
}
|
||||
|
||||
io.acceptingTextInput = true;
|
||||
}
|
||||
if(data && selection.justUnselected) {
|
||||
std::optional<T> dataToAssign = fromStr(textbox.get_string());
|
||||
if(dataToAssign)
|
||||
*data = dataToAssign.value();
|
||||
force_update_textbox(true);
|
||||
}
|
||||
if(elemUpdate)
|
||||
elemUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override {
|
||||
if(!data)
|
||||
return;
|
||||
|
||||
bb = get_bb(command);
|
||||
|
||||
canvas->save();
|
||||
SkRect r = SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height());
|
||||
|
||||
canvas->drawRect(r, SkPaint(io.theme->backColor2));
|
||||
SkPaint outline(selection.selected ? io.theme->fillColor1 : io.theme->backColor3);
|
||||
outline.setStroke(true);
|
||||
outline.setStrokeWidth(2.0f);
|
||||
canvas->drawRoundRect(r, 2.0f, 2.0f, outline);
|
||||
|
||||
canvas->clipRect(SkRect::MakeXYWH(bb.min.x(), bb.min.y(), bb.width(), bb.height()));
|
||||
|
||||
canvas->translate(bb.min.x(), bb.min.y());
|
||||
|
||||
textbox.setFont(SkFont(io.textTypeface, io.fontSize));
|
||||
textbox.setFontMgr(io.textFontMgr);
|
||||
textbox.setWidth(singleLine ? 99999999 : bb.width());
|
||||
|
||||
CollabTextBox::Editor::PaintOpts paintOpts;
|
||||
paintOpts.fForegroundColor = io.theme->frontColor1;
|
||||
paintOpts.fBackgroundColor = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
paintOpts.cursorColor = io.theme->fillColor1;
|
||||
paintOpts.showCursor = selection.selected;
|
||||
paintOpts.cursor = cur;
|
||||
|
||||
textbox.paint(canvas, paintOpts);
|
||||
|
||||
canvas->restore();
|
||||
}
|
||||
|
||||
SelectionHelper selection;
|
||||
private:
|
||||
void force_update_textbox(bool reallyForce) {
|
||||
if(data && (*data == oldData) && !reallyForce)
|
||||
return;
|
||||
|
||||
textbox = CollabTextBox::Editor();
|
||||
cur = CollabTextBox::Cursor();
|
||||
if(data) {
|
||||
cur.pos = cur.selectionBeginPos = cur.selectionEndPos = textbox.insert({0, 0}, toStr(*data));
|
||||
oldData = *data;
|
||||
}
|
||||
else {}
|
||||
//oldData = T();
|
||||
}
|
||||
|
||||
T* data = nullptr;
|
||||
T oldData;
|
||||
|
||||
bool singleLine = true;
|
||||
|
||||
std::function<std::optional<T>(const std::string&)> fromStr;
|
||||
std::function<std::string(const T&)> toStr;
|
||||
|
||||
CollabTextBox::Editor textbox;
|
||||
CollabTextBox::Cursor cur;
|
||||
|
||||
SCollision::AABB<float> bb;
|
||||
};
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue