Added GUI draw cache

This commit is contained in:
Yousef Khadadeh 2026-04-08 17:34:50 +03:00
parent caaad67c42
commit d709acb094
30 changed files with 547 additions and 246 deletions

View file

@ -7,6 +7,7 @@
#include <ostream>
#include <Eigen/Geometry>
#include <SDL3/SDL_rect.h>
#include <include/core/SkRect.h>
using namespace Eigen;
@ -80,6 +81,9 @@ namespace SCollision {
SDL_Rect get_sdl_rect() const {
return SDL_Rect{static_cast<int>(min.x()), static_cast<int>(min.y()), static_cast<int>(width()), static_cast<int>(height())};
}
SkRect get_sk_rect() const {
return SkRect::MakeLTRB(static_cast<float>(min.x()), static_cast<float>(min.y()), static_cast<float>(max.x()), static_cast<float>(max.y()));
}
};
template <typename T> class Circle {
public:

View file

@ -93,6 +93,7 @@ bool GUIHolder::load_theme(const std::filesystem::path& configPath, const std::s
void GUIHolder::update() {
gui.io.deltaTime = main.deltaTime;
gui.update();
}
void GUIHolder::window_update() {
@ -114,11 +115,17 @@ float GUIHolder::final_gui_scale_not_fit() {
return main.conf.guiScale * main.get_scale_and_density_factor_gui();
}
void GUIHolder::delete_cache_surface() {
gui.io.surface = nullptr;
}
void GUIHolder::draw(SkCanvas* canvas, bool skiaAA) {
canvas->save();
canvas->scale(final_gui_scale(), final_gui_scale());
if(!gui.io.surface) {
gui.io.surface = main.create_native_surface(main.window.size, true);
gui.io.redrawSurface = true;
}
gui.draw(canvas, skiaAA);
canvas->restore();
}
void GUIHolder::input_paste_callback(const CustomEvents::PasteEventData& paste) {

View file

@ -18,6 +18,7 @@ class GUIHolder {
void update();
void window_update();
void delete_cache_surface();
void draw(SkCanvas* canvas, bool skiaAA);
void input_paste_callback(const CustomEvents::PasteEventData& paste);

View file

@ -20,6 +20,15 @@ void CheckBox::layout(const Clay_ElementId& id, const std::function<bool()>& isT
}) {}
}
void CheckBox::update() {
if(smooth_two_way_time(hoverAnimation, gui.io.deltaTime, mouseHovering, CHECKBOX_ANIMATION_TIME))
gui.invalidate_draw_element(this);
if(oldIsTicked != isTicked()) {
gui.invalidate_draw_element(this);
oldIsTicked = isTicked();
}
}
void CheckBox::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) {
if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down)
gui.set_post_callback_func([&](){if(onClick) onClick();});
@ -70,7 +79,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, mouseHovering, CHECKBOX_ANIMATION_TIME));
float lerpTime2 = anim(hoverAnimation / CHECKBOX_ANIMATION_TIME);
std::array<Vector2f, 5> points;

View file

@ -9,11 +9,13 @@ class CheckBox : public Element {
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override;
virtual void update() override;
void layout(const Clay_ElementId& id, const std::function<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float CHECKBOX_ANIMATION_TIME = 0.3;
float hoverAnimation2 = 0.0;
float hoverAnimation = 0.0;
bool oldIsTicked = false;
std::function<bool()> isTicked;
std::function<void()> onClick;
};

View file

@ -68,6 +68,7 @@ template <typename T> class ColorPicker : public Element {
public:
ColorPicker(GUIManager& gui);
void layout(const Clay_ElementId& id, T* data, bool selectAlpha, const ColorPickerData& config);
virtual void update() override;
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA);
void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override;
void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override;
@ -83,9 +84,18 @@ template <typename T> class ColorPicker : public Element {
static constexpr float BAR_WIDTH = 30.0;
static constexpr float BAR_GAP = 5.0;
struct DisplayData {
Vector3f savedHsv = {0.0f, 0.0f, 0.0f};
float savedAlpha = 0.0f;
bool operator!=(const DisplayData&) const = default;
bool operator==(const DisplayData&) const = default;
};
DisplayData dd;
DisplayData oldDD;
T* data;
std::optional<T> oldData;
Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI
ColorPickerData config;
bool selectAlpha = false;
enum class HeldBar {

View file

@ -9,9 +9,12 @@ template <typename T> ColorPicker<T>::ColorPicker(GUIManager& gui): Element(gui)
template <typename T> void ColorPicker<T>::layout(const Clay_ElementId& id, T* data, bool selectAlpha, const ColorPickerData& config) {
this->data = data;
if(!oldData.has_value() || oldData.value() != *data) {
savedHsv = rgb_to_hsv<Vector3f, T>(*data);
dd.savedHsv = rgb_to_hsv<Vector3f, T>(*data);
oldData = *data;
}
if(selectAlpha)
dd.savedAlpha = (*data)[3];
this->selectAlpha = selectAlpha;
this->config = config;
@ -24,6 +27,13 @@ template <typename T> void ColorPicker<T>::layout(const Clay_ElementId& id, T* d
}) {}
}
template <typename T> void ColorPicker<T>::update() {
if(dd != oldDD) {
gui.invalidate_draw_element(this);
oldDD = dd;
}
}
template <typename T> void ColorPicker<T>::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto& bb = boundingBox.value();
@ -34,18 +44,18 @@ template <typename T> void ColorPicker<T>::clay_draw(SkCanvas* canvas, UpdateInp
canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA);
SkPaint svSelectionAreaPaint;
svSelectionAreaPaint.setShader(ColorPickerShaders::get_sv_selection_shader(savedHsv.x() / 360.0f));
svSelectionAreaPaint.setShader(ColorPickerShaders::get_sv_selection_shader(dd.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->drawLine(0.0f, 1.0f - dd.savedHsv.z(), 1.0f, 1.0f - dd.savedHsv.z(), selectionLinePaint);
canvas->drawLine(dd.savedHsv.y(), 0.0f, dd.savedHsv.y(), 1.0f, selectionLinePaint);
canvas->restore();
float normalizedHue = savedHsv.x() / 360.0f;
float normalizedHue = dd.savedHsv.x() / 360.0f;
Vector2f hueBarPos = get_hue_bar_pos();
Vector2f hueBarDim = get_hue_bar_dim();
@ -125,9 +135,9 @@ template <typename T> void ColorPicker<T>::update_color_picker_pos(const Vector2
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);
dd.savedHsv.y() = newSv.x();
dd.savedHsv.z() = 1.0f - newSv.y();
set_hsv(dd.savedHsv);
if(justHeld && config.onHold) config.onHold();
if(config.onChange) config.onChange();
break;
@ -135,8 +145,8 @@ template <typename T> void ColorPicker<T>::update_color_picker_pos(const Vector2
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);
dd.savedHsv.x() = (1.0f - std::clamp((p.y() - huePos.y()) / hueDim.y(), 0.0f, 1.0f)) * 360.0f;
set_hsv(dd.savedHsv);
if(justHeld && config.onHold) config.onHold();
if(config.onChange) config.onChange();
break;
@ -145,6 +155,7 @@ template <typename T> void ColorPicker<T>::update_color_picker_pos(const Vector2
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);
dd.savedAlpha = (*data)[3];
oldData = *data;
if(justHeld && config.onHold) config.onHold();
if(config.onChange) config.onChange();

View file

@ -1,4 +1,5 @@
#include "ColorRectangleDisplay.hpp"
#include "../GUIManager.hpp"
namespace GUIStuff {
@ -14,15 +15,22 @@ void ColorRectangleDisplay::layout(const Clay_ElementId& id, const std::function
}) {}
}
void ColorRectangleDisplay::update() {
SkColor4f newDrawVal = colorFunc();
if(newDrawVal != drawVal) {
drawVal = newDrawVal;
gui.invalidate_draw_element(this);
}
}
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) {
if(drawVal.fA == 1.0f) {
SkPaint p;
p.setColor4f(color);
p.setColor4f(drawVal);
p.setAntiAlias(skiaAA);
canvas->drawRect(r, p);
}
@ -32,7 +40,7 @@ void ColorRectangleDisplay::clay_draw(SkCanvas* canvas, UpdateInputData& io, Cla
SkPaint alphaPaint;
alphaPaint.setShader(get_alpha_background_shader());
canvas->drawPaint(alphaPaint);
canvas->drawPaint(SkPaint{SkColor4f(color.fR, color.fG, color.fB, std::sqrt(color.fA))}); // Square root for alpha to keep color visible
canvas->drawPaint(SkPaint{SkColor4f(drawVal.fR, drawVal.fG, drawVal.fB, std::sqrt(drawVal.fA))}); // Square root for alpha to keep color visible
canvas->restore();
}
}

View file

@ -8,6 +8,7 @@ class ColorRectangleDisplay : public Element {
public:
ColorRectangleDisplay(GUIManager& gui);
void layout(const Clay_ElementId& id, const std::function<SkColor4f()>& colorFunc);
virtual void update() override;
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
private:
static constexpr const char* alphaBackgroundSksl =
@ -23,6 +24,7 @@ vec4 main(vec2 fragcoord) {
}
)V";
std::function<SkColor4f()> colorFunc;
SkColor4f drawVal;
static sk_sp<SkRuntimeEffect> alphaBackgroundEffect;
static sk_sp<SkShader> get_alpha_background_shader();

View file

@ -1,10 +1,11 @@
#include "Element.hpp"
#include "GUIStuffHelpers.hpp"
namespace GUIStuff {
Element::Element(GUIManager& initGUI):
gui(initGUI) {}
void Element::tick_update() {}
void Element::update() {}
void Element::input_paste_callback(const CustomEvents::PasteEventData& paste) { }
void Element::input_text_key_callback(const InputManager::KeyCallbackArgs& key) { }
@ -39,6 +40,6 @@ namespace GUIStuff {
}
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}};
return clay_bounding_box_to_aabb(command->boundingBox);
}
}

View file

@ -22,7 +22,7 @@ namespace GUIStuff {
Element* parent = nullptr;
virtual void tick_update();
virtual void update();
virtual void input_paste_callback(const CustomEvents::PasteEventData& paste);
virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key);

View file

@ -3,6 +3,10 @@
#include <chrono>
namespace GUIStuff {
SCollision::AABB<float> clay_bounding_box_to_aabb(const Clay_BoundingBox& bb) {
return {{bb.x, bb.y}, {bb.x + bb.width, bb.y + bb.height}};
}
SkFont get_setup_skfont() {
SkFont font;
font.setLinearMetrics(true);

View file

@ -16,10 +16,26 @@
#include "../../InputManager.hpp"
#include "../GUIManagerID.hpp"
#ifdef USE_SKIA_BACKEND_GRAPHITE
#include <include/gpu/graphite/Surface.h>
#elif USE_SKIA_BACKEND_GANESH
#include <include/gpu/ganesh/GrDirectContext.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#endif
using namespace Eigen;
namespace GUIStuff {
struct BorderData {
float top = 0.0f;
float bottom = 0.0f;
float left = 0.0f;
float right = 0.0f;
};
SCollision::AABB<float> clay_bounding_box_to_aabb(const Clay_BoundingBox& bb);
// 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:
@ -99,6 +115,9 @@ struct UpdateInputData {
std::shared_ptr<Theme> theme;
std::unordered_map<std::string, sk_sp<SkSVGDOM>> svgData;
sk_sp<SkSurface> surface;
bool redrawSurface = true;
};
class SelectionHelper {

View file

@ -15,11 +15,13 @@ template <typename T> class NumberSlider : public Element {
NumberSlider(GUIManager& gui):
Element(gui) {}
static constexpr float HOLD_ANIMATION_TIME = 0.3f;
void layout(const Clay_ElementId& id, T* data, T minData, T maxData, const NumberSliderData& config) {
this->data = data;
this->minData = minData;
this->maxData = maxData;
dd.val = *data;
dd.minData = minData;
dd.maxData = maxData;
this->config = config;
CLAY(id, {
@ -31,24 +33,38 @@ template <typename T> class NumberSlider : public Element {
}
}
virtual void update() {
smooth_two_way_time(dd.holdAnimation, gui.io.deltaTime, dd.isHeld, HOLD_ANIMATION_TIME);
smooth_two_way_time(dd.hoverAnimation, gui.io.deltaTime, mouseHovering, gui.io.theme->hoverExpandTime);
if(oldDD != dd) {
gui.invalidate_draw_element(this, {
.top = 10.0f,
.bottom = 10.0f,
.left = 10.0f,
.right = 10.0f
});
oldDD = dd;
}
}
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override {
auto& bb = boundingBox.value();
canvas->save();
canvas->translate(bb.min.x(), bb.min.y());
float lerpTimeHover = smooth_two_way_time(hoverAnimation, io.deltaTime, mouseHovering, io.theme->hoverExpandTime);
float lerpTimeHover = smooth_two_way_time(dd.hoverAnimation, io.deltaTime, mouseHovering, io.theme->hoverExpandTime);
static BezierEasing easeHeight(0.68, -1.55, 0.265, 2.55);
float lerpTimeHeld = easeHeight(smooth_two_way_time(holdAnimation, io.deltaTime, isHeld, 0.3));
float lerpTimeHeld = easeHeight(dd.holdAnimation / HOLD_ANIMATION_TIME);
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, maxData, minData) * bb.width();
float holderPos = lerp_time<float>(dd.val, dd.maxData, dd.minData) * bb.width();
SkRect barFull = SkRect::MakeXYWH(0.0f, yChange, holderPos, holderRadius);
SkRect barEmpty = SkRect::MakeXYWH(holderPos, yChange, bb.width() - holderPos, holderRadius);
@ -81,19 +97,19 @@ template <typename T> class NumberSlider : public Element {
canvas->restore();
}
virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override {
bool oldIsHeld = isHeld;
isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
if(oldIsHeld && !isHeld) {
bool oldIsHeld = dd.isHeld;
dd.isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
if(oldIsHeld && !dd.isHeld) {
gui.set_post_callback_func([&] {
if(config.onRelease) config.onRelease();
});
}
else if(isHeld && boundingBox.has_value())
else if(dd.isHeld && boundingBox.has_value())
update_slider_pos(button.pos, true);
}
virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override {
if(isHeld && boundingBox.has_value())
if(dd.isHeld && boundingBox.has_value())
update_slider_pos(motion.pos, false);
}
@ -101,20 +117,32 @@ template <typename T> class NumberSlider : public Element {
void update_slider_pos(const Vector2f& p, bool justHeld) {
gui.set_post_callback_func([&, p, justHeld] {
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
dd.val = *data = static_cast<T>(std::clamp<double>(std::lerp<double>(dd.minData, dd.maxData, fracPosOnSlider), dd.minData, dd.maxData)); // Clamp as double then cast so that unsigned types dont wrap on clamp
if(justHeld && config.onHold) config.onHold();
if(config.onChange) config.onChange();
});
}
bool isHeld = false;
T* data = nullptr;
T minData = 0.0;
T maxData = 1.0;
NumberSliderData config;
float hoverAnimation = 0.0;
float holdAnimation = 0.0;
struct DisplayData {
bool isHeld = false;
T val = 0.0;
T minData = 0.0;
T maxData = 1.0;
float hoverAnimation = 0.0;
float holdAnimation = 0.0;
bool operator!=(const DisplayData&) const = default;
bool operator==(const DisplayData&) const = default;
};
DisplayData dd;
DisplayData oldDD;
NumberSliderData config;
};
}

View file

@ -26,15 +26,24 @@ void PaintCircleMenu::layout(const Clay_ElementId& id, const Data& data) {
}
}
void PaintCircleMenu::update() {
dd.selectedColorDisplayed = *d.selectedColor;
dd.rotationAngleDisplayed = *d.rotationAngle;
if(dd != oldDD) {
oldDD = dd;
gui.invalidate_draw_element(this);
}
}
bool PaintCircleMenu::collides_with_point(const Vector2f& p) const {
return boundingBox.has_value() && SCollision::collide(SCollision::Circle<float>(boundingBox.value().center(), CIRCLE_END), p) && !SCollision::collide(SCollision::Circle<float>(boundingBox.value().center(), PALETTE_START), p);
}
void PaintCircleMenu::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) {
if(d.mouseButton) d.mouseButton(button, mouseHovering);
isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
dd.isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
update_paint_circle_menu_mouse_hover(button.pos);
isRotateBarHeld = isRotateBarHovered && isHeld;
dd.isRotateBarHeld = dd.isRotateBarHovered && dd.isHeld;
update_paint_circle_menu_mouse(button.pos, button.button == InputManager::MouseButton::LEFT && button.down);
}
@ -45,25 +54,25 @@ void PaintCircleMenu::input_mouse_motion_callback(const InputManager::MouseMotio
void PaintCircleMenu::update_paint_circle_menu_mouse_hover(const Vector2f& p) {
float distFromCenter = vec_distance(p, boundingBox.value().center());
isRotateBarHovered = mouseHovering && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END;
isColorBarHovered = mouseHovering && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START;
dd.isRotateBarHovered = mouseHovering && distFromCenter > ROTATE_START && distFromCenter < CIRCLE_END;
dd.isColorBarHovered = mouseHovering && distFromCenter > PALETTE_START && distFromCenter < ROTATE_START;
}
void PaintCircleMenu::update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked) {
if(boundingBox.has_value()) {
Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized();
if(isColorBarHovered) {
if(dd.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);
dd.colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * d.palette.size();
dd.colorSelectionIndex = std::clamp<unsigned>(dd.colorSelectionIndex, 0, d.palette.size() - 1);
}
if(isRotateBarHeld) {
if(dd.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
if(dd.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;
@ -76,12 +85,12 @@ void PaintCircleMenu::update_paint_circle_menu_mouse(const Vector2f& p, bool lef
d.onRotate();
});
}
else if(isColorBarHovered && leftClicked) {
else if(dd.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();
selectedColor.x() = d.palette[dd.colorSelectionIndex].x();
selectedColor.y() = d.palette[dd.colorSelectionIndex].y();
selectedColor.z() = d.palette[dd.colorSelectionIndex].z();
d.onPaletteClick();
});
}
@ -101,7 +110,7 @@ void PaintCircleMenu::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_Rend
}
void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA) {
double rotationAngle = *d.rotationAngle;
double rotationAngle = dd.rotationAngleDisplayed;
float rotateBarMiddleRadius = (CIRCLE_END + ROTATE_START) / 2.0f;
SkPaint rotateBarFill(io.theme->backColor2);
rotateBarFill.setAntiAlias(skiaAA);
@ -109,7 +118,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(isRotateBarHovered) {
if(dd.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);
@ -121,9 +130,9 @@ void PaintCircleMenu::draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, boo
SkPaint rotateBarHolderFill;
rotateBarHolderFill.setAntiAlias(skiaAA);
if(isRotateBarHeld)
if(dd.isRotateBarHeld)
rotateBarHolderFill.setColor4f(io.theme->fillColor1);
else if(isRotateBarHovered)
else if(dd.isRotateBarHovered)
rotateBarHolderFill.setColor4f(io.theme->fillColor2);
else
rotateBarHolderFill.setColor4f(io.theme->frontColor1);
@ -179,10 +188,10 @@ 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(isColorBarHovered) {
if(dd.isColorBarHovered) {
SkPathBuilder selectionPathBuilder;
double angleStart = colorSelectionIndex * colorDistribution;
double angleEnd = (colorSelectionIndex + 1) * colorDistribution;
double angleStart = dd.colorSelectionIndex * colorDistribution;
double angleEnd = (dd.colorSelectionIndex + 1) * colorDistribution;
Vector2f lineDir1{std::cos(angleStart), std::sin(angleStart)};
Vector2f lineDir2{std::cos(angleEnd), std::sin(angleEnd)};
SkPaint lineP(io.theme->fillColor1);
@ -199,7 +208,7 @@ void PaintCircleMenu::draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bo
canvas->drawPath(selectionPathBuilder.detach(), 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()) {
if(d.palette[i].x() == dd.selectedColorDisplayed.x() && d.palette[i].y() == dd.selectedColorDisplayed.y() && d.palette[i].z() == dd.selectedColorDisplayed.z()) {
SkPathBuilder selectionPathBuilder;
double angleStart = i * colorDistribution;
double angleEnd = (i + 1) * colorDistribution;

View file

@ -15,6 +15,7 @@ class PaintCircleMenu : public Element {
std::function<void(const InputManager::MouseButtonCallbackArgs& button, bool mouseHovering)> mouseButton;
};
void layout(const Clay_ElementId& id, const Data& data);
virtual void update() override;
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 void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override;
@ -23,11 +24,21 @@ class PaintCircleMenu : public Element {
void draw_rotate_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
void draw_palette_bar(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
Data d;
unsigned colorSelectionIndex = 0;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
bool isColorBarHovered = false;
bool isHeld = false;
struct DisplayData {
Vector4f selectedColorDisplayed = {0.0f, 0.0f, 0.0f, 0.0f};
double rotationAngleDisplayed = 0.0;
unsigned colorSelectionIndex = 0;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
bool isColorBarHovered = false;
bool isHeld = false;
bool operator!=(const DisplayData&) const = default;
bool operator==(const DisplayData&) const = default;
};
DisplayData dd;
DisplayData oldDD;
void update_paint_circle_menu_mouse_hover(const Vector2f& p);
void update_paint_circle_menu_mouse(const Vector2f& p, bool leftClicked);

View file

@ -18,6 +18,15 @@ void RadioButton::layout(const Clay_ElementId& id, const std::function<bool()>&
}) {}
}
void RadioButton::update() {
if(smooth_two_way_time(hoverAnimation, gui.io.deltaTime, mouseHovering, RADIOBUTTON_ANIMATION_TIME))
gui.invalidate_draw_element(this);
if(oldIsTicked != isTicked()) {
gui.invalidate_draw_element(this);
oldIsTicked = isTicked();
}
}
void RadioButton::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) {
if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down)
gui.set_post_callback_func([&] { if(onClick) onClick(); });
@ -44,7 +53,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, isHovering, RADIOBUTTON_ANIMATION_TIME));
float lerpTime2 = easeRadius(hoverAnimation / RADIOBUTTON_ANIMATION_TIME);
float innerCircleRadius = lerp_vec(0.3f, 0.2f, lerpTime2);
canvas->drawCircle(0.0f, 0.0f, innerCircleRadius, innerCircleP);
@ -52,7 +61,7 @@ void RadioButton::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCo
else {
SkPaint p;
p.setAntiAlias(skiaAA);
p.setColor4f(convert_vec4<SkColor4f>(isHovering ? io.theme->fillColor1 : io.theme->backColor2));
p.setColor4f(convert_vec4<SkColor4f>(mouseHovering ? io.theme->fillColor1 : io.theme->backColor2));
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.15f);
canvas->drawCircle(0.0f, 0.0f, 0.5f, p);

View file

@ -8,12 +8,13 @@ class RadioButton : public Element {
RadioButton(GUIManager& gui);
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override;
virtual void update() override;
void layout(const Clay_ElementId& id, const std::function<bool()>& isTicked, const std::function<void()>& onClick);
private:
static constexpr float RADIOBUTTON_ANIMATION_TIME = 0.3;
bool isHovering = false;
float hoverAnimation2 = 0.0;
bool oldIsTicked = false;
float hoverAnimation = 0.0;
std::function<bool()> isTicked;
std::function<void()> onClick;
};

View file

@ -26,6 +26,14 @@ void RotateWheel::layout(const Clay_ElementId& id, double* rotateAngle, const st
}
}
void RotateWheel::update() {
dd.rotateAngleDisplayed = *rotateAngle;
if(oldDD != dd) {
oldDD = dd;
gui.invalidate_draw_element(this);
}
}
float RotateWheel::wheel_start() {
return wheel_end() - WHEEL_WIDTH;
}
@ -35,9 +43,9 @@ float RotateWheel::wheel_end() {
}
void RotateWheel::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) {
isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
dd.isHeld = mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down;
update_rotate_wheel_mouse_hover(button.pos);
isRotateBarHeld = isRotateBarHovered && isHeld;
dd.isRotateBarHeld = dd.isRotateBarHovered && dd.isHeld;
update_rotate_wheel_mouse(button.pos);
Element::input_mouse_button_callback(button);
}
@ -50,17 +58,17 @@ void RotateWheel::input_mouse_motion_callback(const InputManager::MouseMotionCal
void RotateWheel::update_rotate_wheel_mouse_hover(const Vector2f& p) {
float distFromCenter = vec_distance(p, boundingBox.value().center());
isRotateBarHovered = mouseHovering && distFromCenter > wheel_start() && distFromCenter < wheel_end();
dd.isRotateBarHovered = mouseHovering && distFromCenter > wheel_start() && distFromCenter < wheel_end();
}
void RotateWheel::update_rotate_wheel_mouse(const Vector2f& p) {
if(boundingBox.has_value()) {
Vector2f vecFromCenter = (p - boundingBox.value().center()).normalized();
if(isRotateBarHeld) {
if(dd.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
if(dd.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;
@ -97,7 +105,7 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool
rotateBarFill.setStrokeWidth(wheelEnd - wheelStart);
canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill);
if(isRotateBarHovered) {
if(dd.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);
@ -109,14 +117,14 @@ void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool
SkPaint rotateBarHolderFill;
rotateBarHolderFill.setAntiAlias(skiaAA);
if(isRotateBarHeld)
if(dd.isRotateBarHeld)
rotateBarHolderFill.setColor4f(io.theme->fillColor1);
else if(isRotateBarHovered)
else if(dd.isRotateBarHovered)
rotateBarHolderFill.setColor4f(io.theme->fillColor2);
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(dd.rotateAngleDisplayed) * rotateBarMiddleRadius, -std::sin(dd.rotateAngleDisplayed) * rotateBarMiddleRadius};
canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill);
SkPaint rotateBarHolderOutline(io.theme->frontColor1);
rotateBarHolderOutline.setAntiAlias(skiaAA);

View file

@ -7,6 +7,7 @@ class RotateWheel : public Element {
public:
RotateWheel(GUIManager& gui);
void layout(const Clay_ElementId& id, double* rotateAngle, const std::function<void()>& onChange);
virtual void update() override;
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override;
virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override;
@ -17,11 +18,20 @@ class RotateWheel : public Element {
void draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io, bool skiaAA);
double* rotateAngle = nullptr;
bool isHeld = false;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
std::function<void()> onChange;
struct DisplayData {
double rotateAngleDisplayed = 0.0;
bool isHeld = false;
bool isRotateBarHeld = false;
bool isRotateBarHovered = false;
bool operator!=(const DisplayData&) const = default;
bool operator==(const DisplayData&) const = default;
};
DisplayData dd;
DisplayData oldDD;
void update_rotate_wheel_mouse_hover(const Vector2f& p);
void update_rotate_wheel_mouse(const Vector2f& p);
};

View file

@ -12,15 +12,23 @@ SVGIcon::SVGIcon(GUIManager& gui):
Element(gui) {}
void SVGIcon::layout(const Clay_ElementId& id, const std::string& newSvgPath, bool newIsHighlighted) {
bool redraw = false;
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);
else
else {
redraw |= svgDom != findSVGData->second;
svgDom = findSVGData->second;
}
redraw |= highlighted != newIsHighlighted;
highlighted = newIsHighlighted;
if(redraw)
gui.invalidate_draw_element(this);
CLAY(id, {
.layout = {
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }

View file

@ -38,6 +38,7 @@ template <typename T> class TextBox : public Element {
TextBox(GUIManager& gui);
void layout(const Clay_ElementId& id, const TextBoxData<T>& userInfo);
virtual void update();
virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override;
virtual void input_paste_callback(const CustomEvents::PasteEventData& paste) override;
virtual void input_text_key_callback(const InputManager::KeyCallbackArgs& key) override;
@ -52,6 +53,12 @@ template <typename T> class TextBox : public Element {
bool update_data();
bool is_selected() const;
struct OldDisplayData {
bool isSelected = false;
std::string textboxStr;
RichText::TextBox::Cursor cur;
} oldDD;
bool isHeld = false;
std::optional<TextEditData> edit;

View file

@ -21,6 +21,16 @@ template <typename T> void TextBox<T>::layout(const Clay_ElementId& id, const Te
}
}
template <typename T> void TextBox<T>::update() {
std::string newTextboxStr = textbox->get_string();
if(oldDD.isSelected != is_selected() || oldDD.textboxStr != newTextboxStr || oldDD.cur != *cur) {
oldDD.isSelected = is_selected();
oldDD.textboxStr = newTextboxStr;
oldDD.cur = *cur;
gui.invalidate_draw_element(this);
}
}
template <typename T> void TextBox<T>::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {
auto& bb = boundingBox.value();

View file

@ -9,6 +9,8 @@ TextParagraph::TextParagraph(GUIManager& gui):
Element(gui) {}
void TextParagraph::layout(const Clay_ElementId& id, const Data& newData) {
bool redraw = false;
if(!textbox) {
d = newData;
textbox = std::make_unique<RichText::TextBox>();
@ -16,18 +18,24 @@ void TextParagraph::layout(const Clay_ElementId& id, const Data& newData) {
textbox->set_rich_text_data(d.text);
textbox->set_font_data(gui.io.fonts);
textbox->set_allow_newlines(true);
redraw = true;
}
if(d.width != newData.width) {
d.width = newData.width;
textbox->set_width(d.width);
redraw = true;
}
if(d.text != newData.text) {
d.text = newData.text;
textbox->set_rich_text_data(d.text);
redraw = true;
}
if(redraw)
gui.invalidate_draw_element(this);
CLAY(id, {
.layout = {
.sizing = {.width = CLAY_SIZING_FIXED(d.width), .height = CLAY_SIZING_FIXED(textbox->get_height())}

View file

@ -5,6 +5,7 @@
#include <include/core/SkFontMetrics.h>
#include <include/core/SkPath.h>
#include <include/core/SkPathBuilder.h>
#include "Elements/GUIStuffHelpers.hpp"
#include "Elements/SVGIcon.hpp"
#include "Elements/FontPicker.hpp"
#include "Elements/SelectableButton.hpp"
@ -57,175 +58,257 @@ Clay_Dimensions GUIManager::clay_skia_measure_text(Clay_StringSlice str, Clay_Te
return Clay_Dimensions(p->getMaxIntrinsicWidth(), p->getHeight());
}
void GUIManager::draw(SkCanvas* canvas, bool skiaAA) {
canvas->save();
canvas->translate(io.windowPos.x(), io.windowPos.y());
void GUIManager::draw(SkCanvas* c, bool skiaAA) {
if(io.redrawSurface) {
oldRenderCommandMap.clear();
setToUpdateInvalidateDrawAreaFromLayout = false;
}
update_invalidated_draw_area_from_layout();
if(io.redrawSurface || invalidDrawBB.has_value()) {
SkCanvas* canvas = io.surface->getCanvas();
canvas->save();
if(!io.redrawSurface)
canvas->clipRect(invalidDrawBB.value().get_sk_rect());
canvas->clear({0.0f, 0.0f, 0.0f, 0.0f});
canvas->scale(io.guiScaleMultiplier, io.guiScaleMultiplier);
canvas->translate(io.windowPos.x(), io.windowPos.y());
for(size_t i = 0; i < static_cast<size_t>(renderCommands.length); i++) {
Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i);
Clay_BoundingBox bb = command->boundingBox;
switch(command->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData* config = &command->renderData.rectangle;
SkVector radii[4] = {
{config->cornerRadius.topLeft, config->cornerRadius.topLeft},
{config->cornerRadius.topRight, config->cornerRadius.topRight},
{config->cornerRadius.bottomRight, config->cornerRadius.bottomRight},
{config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft}
};
for(size_t i = 0; i < static_cast<size_t>(renderCommands.length); i++) {
Clay_RenderCommand* command = Clay_RenderCommandArray_Get(&renderCommands, i);
Clay_BoundingBox bb = command->boundingBox;
SkRRect rrect;
rrect.setRectRadii(
SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height),
radii
);
if(!io.redrawSurface && command->commandType != CLAY_RENDER_COMMAND_TYPE_SCISSOR_START && command->commandType != CLAY_RENDER_COMMAND_TYPE_SCISSOR_END && !SCollision::collide(get_invalid_draw_bb_from_command(command), invalidDrawBB.value()))
continue;
SkPaint paint;
paint.setAntiAlias(skiaAA);
paint.setStyle(SkPaint::kFill_Style);
paint.setColor4f(convert_vec4<SkColor4f>(config->backgroundColor));
canvas->drawRRect(rrect, paint);
switch(command->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData* config = &command->renderData.rectangle;
SkVector radii[4] = {
{config->cornerRadius.topLeft, config->cornerRadius.topLeft},
{config->cornerRadius.topRight, config->cornerRadius.topRight},
{config->cornerRadius.bottomRight, config->cornerRadius.bottomRight},
{config->cornerRadius.bottomLeft, config->cornerRadius.bottomLeft}
};
break;
SkRRect rrect;
rrect.setRectRadii(
SkRect::MakeXYWH(bb.x, bb.y, bb.width, bb.height),
radii
);
SkPaint paint;
paint.setAntiAlias(skiaAA);
paint.setStyle(SkPaint::kFill_Style);
paint.setColor4f(convert_vec4<SkColor4f>(config->backgroundColor));
canvas->drawRRect(rrect, paint);
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData* config = &command->renderData.text;
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(config->fontSize);
tStyle.setFontFamilies(io.fonts->get_default_font_families());
tStyle.setColor(convert_vec4<SkColor4f>(config->textColor).toSkColor());
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, io.fonts->collection, SkUnicodes::ICU::Make());
a.addText(config->stringContents.chars, config->stringContents.length);
std::unique_ptr<skia::textlayout::Paragraph> p = a.Build();
p->layout(std::numeric_limits<float>::max());
p->paint(canvas, bb.x, bb.y);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData* config = &command->renderData.border;
SkPaint p;
p.setColor4f(convert_vec4<SkColor4f>(config->color));
p.setStyle(SkPaint::kStroke_Style);
p.setAntiAlias(skiaAA);
float halfLineWidth = 0.0f;
// Top Left corner
if (config->cornerRadius.topLeft > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo(bb.x + halfLineWidth, bb.y + config->cornerRadius.topLeft + halfLineWidth);
path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)}, config->cornerRadius.topLeft);
canvas->drawPath(path.detach(), p);
}
// Top border
if (config->width.top > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Top Right Corner
if (config->cornerRadius.topRight > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)}, config->cornerRadius.topRight);
canvas->drawPath(path.detach(), p);
}
// Right border
if (config->width.right > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.right;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
path.lineTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Bottom Right Corner
if (config->cornerRadius.bottomRight > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, config->cornerRadius.bottomRight);
canvas->drawPath(path.detach(), p);
}
// Bottom Border
if (config->width.bottom > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Bottom Left Corner
if (config->cornerRadius.bottomLeft > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)}, config->cornerRadius.bottomLeft);
canvas->drawPath(path.detach(), p);
}
// Left Border
if (config->width.left > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.left;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth));
path.lineTo((bb.x + halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
canvas->drawPath(path.detach(), p);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
Clay_ClipRenderData* clip = &command->renderData.clip;
if(!clip->horizontal) {
bb.x = 0.0f;
bb.width = std::numeric_limits<float>::max();
}
if(!clip->vertical) {
bb.y = 0.0f;
bb.height = std::numeric_limits<float>::max();
}
canvas->save();
SkRect clipRect = SkRect::MakeXYWH(bb.x - 1.0f, bb.y - 1.0f, bb.width + 1.0f, bb.height + 1.0f);
canvas->clipRect(clipRect);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
canvas->restore();
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Element* customElement = static_cast<Element*>(command->renderData.custom.customData);
customElement->clay_draw(canvas, io, command, skiaAA);
break;
}
default:
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData* config = &command->renderData.text;
skia::textlayout::ParagraphStyle pStyle;
pStyle.setTextAlign(skia::textlayout::TextAlign::kLeft);
skia::textlayout::TextStyle tStyle;
tStyle.setFontSize(config->fontSize);
tStyle.setFontFamilies(io.fonts->get_default_font_families());
tStyle.setColor(convert_vec4<SkColor4f>(config->textColor).toSkColor());
pStyle.setTextStyle(tStyle);
skia::textlayout::ParagraphBuilderImpl a(pStyle, io.fonts->collection, SkUnicodes::ICU::Make());
a.addText(config->stringContents.chars, config->stringContents.length);
std::unique_ptr<skia::textlayout::Paragraph> p = a.Build();
p->layout(std::numeric_limits<float>::max());
p->paint(canvas, bb.x, bb.y);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData* config = &command->renderData.border;
SkPaint p;
p.setColor4f(convert_vec4<SkColor4f>(config->color));
p.setStyle(SkPaint::kStroke_Style);
p.setAntiAlias(skiaAA);
float halfLineWidth = 0.0f;
// Top Left corner
if (config->cornerRadius.topLeft > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo(bb.x + halfLineWidth, bb.y + config->cornerRadius.topLeft + halfLineWidth);
path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth)}, config->cornerRadius.topLeft);
canvas->drawPath(path.detach(), p);
}
// Top border
if (config->width.top > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.topLeft + halfLineWidth), (bb.y + halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Top Right Corner
if (config->cornerRadius.topRight > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.top;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - config->cornerRadius.topRight - halfLineWidth), (bb.y + halfLineWidth));
path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + halfLineWidth)}, SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth)}, config->cornerRadius.topRight);
canvas->drawPath(path.detach(), p);
}
// Right border
if (config->width.right > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.right;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
path.lineTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Bottom Right Corner
if (config->cornerRadius.bottomRight > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + bb.width - halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomRight - halfLineWidth));
path.arcTo(SkPoint{(bb.x + bb.width - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth)}, config->cornerRadius.bottomRight);
canvas->drawPath(path.detach(), p);
}
// Bottom Border
if (config->width.bottom > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.lineTo((bb.x + bb.width - config->cornerRadius.bottomRight - halfLineWidth), (bb.y + bb.height - halfLineWidth));
canvas->drawPath(path.detach(), p);
}
// Bottom Left Corner
if (config->cornerRadius.bottomLeft > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.bottom;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + config->cornerRadius.bottomLeft + halfLineWidth), (bb.y + bb.height - halfLineWidth));
path.arcTo(SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - halfLineWidth)}, SkPoint{(bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth)}, config->cornerRadius.bottomLeft);
canvas->drawPath(path.detach(), p);
}
// Left Border
if (config->width.left > 0.0f) {
SkPathBuilder path;
float lineWidth = config->width.left;
p.setStrokeWidth(lineWidth);
path.moveTo((bb.x + halfLineWidth), (bb.y + bb.height - config->cornerRadius.bottomLeft - halfLineWidth));
path.lineTo((bb.x + halfLineWidth), (bb.y + config->cornerRadius.topRight + halfLineWidth));
canvas->drawPath(path.detach(), p);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
Clay_ClipRenderData* clip = &command->renderData.clip;
if(!clip->horizontal) {
bb.x = 0.0f;
bb.width = std::numeric_limits<float>::max();
}
if(!clip->vertical) {
bb.y = 0.0f;
bb.height = std::numeric_limits<float>::max();
}
canvas->save();
SkRect clipRect = SkRect::MakeXYWH(bb.x - 1.0f, bb.y - 1.0f, bb.width + 1.0f, bb.height + 1.0f);
canvas->clipRect(clipRect);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
canvas->restore();
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Element* customElement = static_cast<Element*>(command->renderData.custom.customData);
customElement->clay_draw(canvas, io, command, skiaAA);
break;
}
default:
break;
}
canvas->restore();
}
canvas->restore();
invalidDrawBB = std::nullopt;
io.redrawSurface = false;
c->drawImage(io.surface->makeTemporaryImage(), 0, 0, {SkFilterMode::kNearest, SkMipmapMode::kNone}, nullptr);
}
SCollision::AABB<float> GUIManager::get_invalid_draw_bb_from_command(const Clay_RenderCommand* command) {
BorderData extraPadding;
if(command->commandType == CLAY_RENDER_COMMAND_TYPE_BORDER) {
const Clay_BorderRenderData& border = command->renderData.border;
extraPadding.top = border.width.top;
extraPadding.bottom = border.width.bottom;
extraPadding.left = border.width.left;
extraPadding.right = border.width.right;
}
return get_invalid_draw_bb(clay_bounding_box_to_aabb(command->boundingBox), extraPadding);
}
SCollision::AABB<float> GUIManager::get_invalid_draw_bb(SCollision::AABB<float> bb, const BorderData& extraPadding) {
constexpr float EXTRA_AREA = 3.0f;
bb.min.y() -= EXTRA_AREA + extraPadding.top;
bb.max.y() += EXTRA_AREA + extraPadding.bottom;
bb.min.x() -= EXTRA_AREA + extraPadding.left;
bb.max.x() += EXTRA_AREA + extraPadding.right;
bb.min *= io.guiScaleMultiplier;
bb.max *= io.guiScaleMultiplier;
return bb;
}
void GUIManager::update_invalidated_draw_area_from_layout() {
auto renderCommandMapCopy = oldRenderCommandMap;
if(setToUpdateInvalidateDrawAreaFromLayout) {
for(int32_t i = 0; i < renderCommands.length; i++) {
const Clay_RenderCommand& renderCommand = renderCommands.internalArray[i];
auto oldCommandIt = oldRenderCommandMap.find(renderCommand.id);
if(oldCommandIt != oldRenderCommandMap.end()) {
Clay_RenderCommand& oldRenderCommand = oldCommandIt->second;
if(std::memcmp(&renderCommand, &oldRenderCommand, sizeof(Clay_RenderCommand)) != 0) {
invalidate_draw_in_area(get_invalid_draw_bb_from_command(&oldRenderCommand));
invalidate_draw_in_area(get_invalid_draw_bb_from_command(&renderCommand));
oldRenderCommand = renderCommand;
}
}
else {
invalidate_draw_in_area(get_invalid_draw_bb_from_command(&renderCommand));
oldRenderCommandMap.emplace(renderCommand.id, renderCommand);
}
renderCommandMapCopy.erase(renderCommand.id);
}
for(auto& [id, command] : renderCommandMapCopy) {
invalidate_draw_in_area(get_invalid_draw_bb_from_command(&command));
oldRenderCommandMap.erase(id);
}
setToUpdateInvalidateDrawAreaFromLayout = false;
}
}
void GUIManager::update() {
for(ElementContainer* e : orderedElements)
e->elem->update();
}
void GUIManager::update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier) {
if(io.windowPos != windowPos || io.windowSize != windowSize || io.guiScaleMultiplier != guiScaleMultiplier)
if(io.windowPos != windowPos || io.windowSize != windowSize || io.guiScaleMultiplier != guiScaleMultiplier) {
io.redrawSurface = true;
set_to_layout();
}
io.windowPos = windowPos;
io.windowSize = windowSize / guiScaleMultiplier;
io.guiScaleMultiplier = guiScaleMultiplier;
@ -239,6 +322,7 @@ void GUIManager::layout_if_necessary() {
if(setToLayout) {
layout();
setToLayout = false;
setToUpdateInvalidateDrawAreaFromLayout = true;
}
}
@ -344,6 +428,18 @@ void GUIManager::run_post_callback_func() {
postCallbackFuncIsHighPriority = false;
}
void GUIManager::invalidate_draw_element(Element* element, const BorderData& extraPadding) {
if(element->get_bb().has_value())
invalidate_draw_in_area(get_invalid_draw_bb(element->get_bb().value(), extraPadding));
}
void GUIManager::invalidate_draw_in_area(const SCollision::AABB<float>& bb) {
if(invalidDrawBB.has_value())
invalidDrawBB.value().include_aabb_in_bounds(bb);
else
invalidDrawBB = bb;
}
bool GUIManager::cursor_obstructed() const {
return cursorObstructed;
}

View file

@ -17,10 +17,11 @@ class GUIManager {
};
GUIManager();
void draw(SkCanvas* canvas, bool skiaAA);
void draw(SkCanvas* c, bool skiaAA);
void set_to_layout();
void layout_if_necessary();
void update();
void update_window(const Vector2f& windowPos, const Vector2f& windowSize, float guiScaleMultiplier);
UpdateInputData io;
@ -36,6 +37,9 @@ class GUIManager {
void set_post_callback_func_high_priority(const std::function<void()>& f);
void run_post_callback_func();
void invalidate_draw_element(Element* element, const BorderData& extraPadding = {});
void invalidate_draw_in_area(const SCollision::AABB<float>& bb);
template <typename ElementType, typename... Args> ElementType* element(const char* id, const Args&... a) {
push_id(id);
@ -133,8 +137,17 @@ class GUIManager {
bool postCallbackFuncIsHighPriority;
Clay_Context* clayInstance;
Clay_Arena clayArena;
Clay_RenderCommandArray renderCommands;
std::unordered_map<uint32_t, Clay_RenderCommand> oldRenderCommandMap;
std::optional<SCollision::AABB<float>> invalidDrawBB;
SCollision::AABB<float> get_invalid_draw_bb(SCollision::AABB<float> bb, const BorderData& extraPadding = {});
SCollision::AABB<float> get_invalid_draw_bb_from_command(const Clay_RenderCommand* command);
void update_invalidated_draw_area_from_layout();
bool setToLayout;
bool setToUpdateInvalidateDrawAreaFromLayout;
bool cursorObstructed;
};

View file

@ -236,6 +236,8 @@ void MainProgram::refresh_draw_surfaces() {
if(!window.intermediateCanvas)
throw std::runtime_error("[refresh_draw_surfaces] Could not create intermediate canvas");
g.delete_cache_surface();
DrawingProgramCache::delete_all_draw_cache();
}
}

View file

@ -21,9 +21,13 @@ TimePoint::operator float() const {
return get_time_since();
}
float smooth_two_way_time(float& timeToUpdate, float deltaTime, bool isWay1, float animDuration) {
timeToUpdate = std::clamp(isWay1 ? (timeToUpdate + deltaTime) : (timeToUpdate - deltaTime), 0.0f, animDuration);
return timeToUpdate / animDuration;
bool smooth_two_way_time(float& timeToUpdate, float deltaTime, bool isWay1, float animDuration) {
float newTimeToUpdate = std::clamp(isWay1 ? (timeToUpdate + deltaTime) : (timeToUpdate - deltaTime), 0.0f, animDuration);
if(newTimeToUpdate != timeToUpdate) {
timeToUpdate = newTimeToUpdate;
return true;
}
return false;
}
WorldVec lerp_world_vec_double(const WorldVec& v1, const WorldVec& v2, double t) {

View file

@ -30,7 +30,7 @@ template<typename TimeType> SkColor4f lerp_vec(const SkColor4f& t1, const SkColo
std::lerp(t1[3], t2[3], t)};
}
float smooth_two_way_time(float& timeToUpdate, float deltaTime, bool isWay1, float animDuration);
bool smooth_two_way_time(float& timeToUpdate, float deltaTime, bool isWay1, float animDuration);
class TimePoint {
public:

View file

@ -493,7 +493,6 @@ void Toolbar::top_toolbar() {
.cornerRadius = CLAY_CORNER_RADIUS(io.theme->windowCorners1)
}) {
global_log();
bool layerMenuPopUpJustOpen = false;
auto icon_button_top_toolbar = [&](const char* id, const std::string& svgPath, bool isSelected, const std::function<void()>& onClick) {
return svg_icon_button(gui, id, svgPath, {