From d709acb094d6cca9bb7c5cf1eac6c55c6a17c46f Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Wed, 8 Apr 2026 17:34:50 +0300 Subject: [PATCH] Added GUI draw cache --- include/Helpers/SCollision.hpp | 4 + src/GUIHolder.cpp | 13 +- src/GUIHolder.hpp | 1 + src/GUIStuff/Elements/CheckBox.cpp | 11 +- src/GUIStuff/Elements/CheckBox.hpp | 4 +- src/GUIStuff/Elements/ColorPicker.decl.hpp | 12 +- src/GUIStuff/Elements/ColorPicker.impl.hpp | 31 +- .../Elements/ColorRectangleDisplay.cpp | 16 +- .../Elements/ColorRectangleDisplay.hpp | 2 + src/GUIStuff/Elements/Element.cpp | 5 +- src/GUIStuff/Elements/Element.hpp | 2 +- src/GUIStuff/Elements/GUIStuffHelpers.cpp | 4 + src/GUIStuff/Elements/GUIStuffHelpers.hpp | 19 + src/GUIStuff/Elements/NumberSlider.hpp | 62 ++- src/GUIStuff/Elements/PaintCircleMenu.cpp | 51 ++- src/GUIStuff/Elements/PaintCircleMenu.hpp | 21 +- src/GUIStuff/Elements/RadioButton.cpp | 13 +- src/GUIStuff/Elements/RadioButton.hpp | 5 +- src/GUIStuff/Elements/RotateWheel.cpp | 26 +- src/GUIStuff/Elements/RotateWheel.hpp | 16 +- src/GUIStuff/Elements/SVGIcon.cpp | 10 +- src/GUIStuff/Elements/TextBox.decl.hpp | 7 + src/GUIStuff/Elements/TextBox.impl.hpp | 10 + src/GUIStuff/Elements/TextParagraph.cpp | 8 + src/GUIStuff/GUIManager.cpp | 410 +++++++++++------- src/GUIStuff/GUIManager.hpp | 15 +- src/MainProgram.cpp | 2 + src/TimePoint.cpp | 10 +- src/TimePoint.hpp | 2 +- src/Toolbar.cpp | 1 - 30 files changed, 547 insertions(+), 246 deletions(-) diff --git a/include/Helpers/SCollision.hpp b/include/Helpers/SCollision.hpp index 8e1e249..e2ccb02 100644 --- a/include/Helpers/SCollision.hpp +++ b/include/Helpers/SCollision.hpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace Eigen; @@ -80,6 +81,9 @@ namespace SCollision { SDL_Rect get_sdl_rect() const { return SDL_Rect{static_cast(min.x()), static_cast(min.y()), static_cast(width()), static_cast(height())}; } + SkRect get_sk_rect() const { + return SkRect::MakeLTRB(static_cast(min.x()), static_cast(min.y()), static_cast(max.x()), static_cast(max.y())); + } }; template class Circle { public: diff --git a/src/GUIHolder.cpp b/src/GUIHolder.cpp index 1400bc1..0b1c1ba 100644 --- a/src/GUIHolder.cpp +++ b/src/GUIHolder.cpp @@ -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) { diff --git a/src/GUIHolder.hpp b/src/GUIHolder.hpp index c55a20e..a40c0fe 100644 --- a/src/GUIHolder.hpp +++ b/src/GUIHolder.hpp @@ -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); diff --git a/src/GUIStuff/Elements/CheckBox.cpp b/src/GUIStuff/Elements/CheckBox.cpp index 1fa0dea..3f87057 100644 --- a/src/GUIStuff/Elements/CheckBox.cpp +++ b/src/GUIStuff/Elements/CheckBox.cpp @@ -20,6 +20,15 @@ void CheckBox::layout(const Clay_ElementId& id, const std::function& 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 points; diff --git a/src/GUIStuff/Elements/CheckBox.hpp b/src/GUIStuff/Elements/CheckBox.hpp index c9c7b3a..4771772 100644 --- a/src/GUIStuff/Elements/CheckBox.hpp +++ b/src/GUIStuff/Elements/CheckBox.hpp @@ -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& isTicked, const std::function& onClick); private: static constexpr float CHECKBOX_ANIMATION_TIME = 0.3; - float hoverAnimation2 = 0.0; + float hoverAnimation = 0.0; + bool oldIsTicked = false; std::function isTicked; std::function onClick; }; diff --git a/src/GUIStuff/Elements/ColorPicker.decl.hpp b/src/GUIStuff/Elements/ColorPicker.decl.hpp index d6eda39..112f395 100644 --- a/src/GUIStuff/Elements/ColorPicker.decl.hpp +++ b/src/GUIStuff/Elements/ColorPicker.decl.hpp @@ -68,6 +68,7 @@ template 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 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 oldData; - Vector3f savedHsv; // We save the HSV so that conversion doesnt ruin the UI ColorPickerData config; bool selectAlpha = false; enum class HeldBar { diff --git a/src/GUIStuff/Elements/ColorPicker.impl.hpp b/src/GUIStuff/Elements/ColorPicker.impl.hpp index e1b2a05..28b80c6 100644 --- a/src/GUIStuff/Elements/ColorPicker.impl.hpp +++ b/src/GUIStuff/Elements/ColorPicker.impl.hpp @@ -9,9 +9,12 @@ template ColorPicker::ColorPicker(GUIManager& gui): Element(gui) template void ColorPicker::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(*data); + dd.savedHsv = rgb_to_hsv(*data); oldData = *data; } + if(selectAlpha) + dd.savedAlpha = (*data)[3]; + this->selectAlpha = selectAlpha; this->config = config; @@ -24,6 +27,13 @@ template void ColorPicker::layout(const Clay_ElementId& id, T* d }) {} } +template void ColorPicker::update() { + if(dd != oldDD) { + gui.invalidate_draw_element(this); + oldDD = dd; + } +} + template void ColorPicker::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { auto& bb = boundingBox.value(); @@ -34,18 +44,18 @@ template void ColorPicker::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 void ColorPicker::update_color_picker_pos(const Vector2 case HeldBar::SV_HELD: { float svSelectionAreaSize = bb.width() - (BAR_GAP + BAR_WIDTH); Vector2f newSv = cwise_vec_clamp((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 void ColorPicker::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 void ColorPicker::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(); diff --git a/src/GUIStuff/Elements/ColorRectangleDisplay.cpp b/src/GUIStuff/Elements/ColorRectangleDisplay.cpp index e784336..048eff7 100644 --- a/src/GUIStuff/Elements/ColorRectangleDisplay.cpp +++ b/src/GUIStuff/Elements/ColorRectangleDisplay.cpp @@ -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(); } } diff --git a/src/GUIStuff/Elements/ColorRectangleDisplay.hpp b/src/GUIStuff/Elements/ColorRectangleDisplay.hpp index c72c3da..5e97579 100644 --- a/src/GUIStuff/Elements/ColorRectangleDisplay.hpp +++ b/src/GUIStuff/Elements/ColorRectangleDisplay.hpp @@ -8,6 +8,7 @@ class ColorRectangleDisplay : public Element { public: ColorRectangleDisplay(GUIManager& gui); void layout(const Clay_ElementId& id, const std::function& 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 colorFunc; + SkColor4f drawVal; static sk_sp alphaBackgroundEffect; static sk_sp get_alpha_background_shader(); diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index 6bd2bb6..e41e6ae 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -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 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); } } diff --git a/src/GUIStuff/Elements/Element.hpp b/src/GUIStuff/Elements/Element.hpp index cbf49ad..aaa5ca4 100644 --- a/src/GUIStuff/Elements/Element.hpp +++ b/src/GUIStuff/Elements/Element.hpp @@ -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); diff --git a/src/GUIStuff/Elements/GUIStuffHelpers.cpp b/src/GUIStuff/Elements/GUIStuffHelpers.cpp index b37f688..a8fc20c 100644 --- a/src/GUIStuff/Elements/GUIStuffHelpers.cpp +++ b/src/GUIStuff/Elements/GUIStuffHelpers.cpp @@ -3,6 +3,10 @@ #include namespace GUIStuff { + SCollision::AABB 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); diff --git a/src/GUIStuff/Elements/GUIStuffHelpers.hpp b/src/GUIStuff/Elements/GUIStuffHelpers.hpp index fd15f0e..504f862 100644 --- a/src/GUIStuff/Elements/GUIStuffHelpers.hpp +++ b/src/GUIStuff/Elements/GUIStuffHelpers.hpp @@ -16,10 +16,26 @@ #include "../../InputManager.hpp" #include "../GUIManagerID.hpp" +#ifdef USE_SKIA_BACKEND_GRAPHITE + #include +#elif USE_SKIA_BACKEND_GANESH + #include + #include +#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 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 class StringArena { public: @@ -99,6 +115,9 @@ struct UpdateInputData { std::shared_ptr theme; std::unordered_map> svgData; + + sk_sp surface; + bool redrawSurface = true; }; class SelectionHelper { diff --git a/src/GUIStuff/Elements/NumberSlider.hpp b/src/GUIStuff/Elements/NumberSlider.hpp index 1f58c0e..42c4b74 100644 --- a/src/GUIStuff/Elements/NumberSlider.hpp +++ b/src/GUIStuff/Elements/NumberSlider.hpp @@ -15,11 +15,13 @@ template 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 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(*data, maxData, minData) * bb.width(); + float holderPos = lerp_time(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 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 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(std::clamp(std::lerp(minData, maxData, fracPosOnSlider), minData, maxData)); // Clamp as double then cast so that unsigned types dont wrap on clamp + dd.val = *data = static_cast(std::clamp(std::lerp(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; }; } diff --git a/src/GUIStuff/Elements/PaintCircleMenu.cpp b/src/GUIStuff/Elements/PaintCircleMenu.cpp index 4e0816c..6e08336 100644 --- a/src/GUIStuff/Elements/PaintCircleMenu.cpp +++ b/src/GUIStuff/Elements/PaintCircleMenu.cpp @@ -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(boundingBox.value().center(), CIRCLE_END), p) && !SCollision::collide(SCollision::Circle(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(colorSelectionIndex, 0, d.palette.size() - 1); + dd.colorSelectionIndex = (selectionAngle / (std::numbers::pi * 2.0)) * d.palette.size(); + dd.colorSelectionIndex = std::clamp(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; diff --git a/src/GUIStuff/Elements/PaintCircleMenu.hpp b/src/GUIStuff/Elements/PaintCircleMenu.hpp index 8dd13e5..d1d2671 100644 --- a/src/GUIStuff/Elements/PaintCircleMenu.hpp +++ b/src/GUIStuff/Elements/PaintCircleMenu.hpp @@ -15,6 +15,7 @@ class PaintCircleMenu : public Element { std::function 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); diff --git a/src/GUIStuff/Elements/RadioButton.cpp b/src/GUIStuff/Elements/RadioButton.cpp index 09d52e4..83ef17d 100644 --- a/src/GUIStuff/Elements/RadioButton.cpp +++ b/src/GUIStuff/Elements/RadioButton.cpp @@ -18,6 +18,15 @@ void RadioButton::layout(const Clay_ElementId& id, const std::function& }) {} } +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(isHovering ? io.theme->fillColor1 : io.theme->backColor2)); + p.setColor4f(convert_vec4(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); diff --git a/src/GUIStuff/Elements/RadioButton.hpp b/src/GUIStuff/Elements/RadioButton.hpp index a4c9dab..8300827 100644 --- a/src/GUIStuff/Elements/RadioButton.hpp +++ b/src/GUIStuff/Elements/RadioButton.hpp @@ -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& isTicked, const std::function& 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 isTicked; std::function onClick; }; diff --git a/src/GUIStuff/Elements/RotateWheel.cpp b/src/GUIStuff/Elements/RotateWheel.cpp index 11430bb..0151c73 100644 --- a/src/GUIStuff/Elements/RotateWheel.cpp +++ b/src/GUIStuff/Elements/RotateWheel.cpp @@ -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); diff --git a/src/GUIStuff/Elements/RotateWheel.hpp b/src/GUIStuff/Elements/RotateWheel.hpp index a209993..f2aaf9e 100644 --- a/src/GUIStuff/Elements/RotateWheel.hpp +++ b/src/GUIStuff/Elements/RotateWheel.hpp @@ -7,6 +7,7 @@ class RotateWheel : public Element { public: RotateWheel(GUIManager& gui); void layout(const Clay_ElementId& id, double* rotateAngle, const std::function& 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 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); }; diff --git a/src/GUIStuff/Elements/SVGIcon.cpp b/src/GUIStuff/Elements/SVGIcon.cpp index afe4367..051d87c 100644 --- a/src/GUIStuff/Elements/SVGIcon.cpp +++ b/src/GUIStuff/Elements/SVGIcon.cpp @@ -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) } diff --git a/src/GUIStuff/Elements/TextBox.decl.hpp b/src/GUIStuff/Elements/TextBox.decl.hpp index 68803fc..9c4fcf2 100644 --- a/src/GUIStuff/Elements/TextBox.decl.hpp +++ b/src/GUIStuff/Elements/TextBox.decl.hpp @@ -38,6 +38,7 @@ template class TextBox : public Element { TextBox(GUIManager& gui); void layout(const Clay_ElementId& id, const TextBoxData& 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 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 edit; diff --git a/src/GUIStuff/Elements/TextBox.impl.hpp b/src/GUIStuff/Elements/TextBox.impl.hpp index ec80b5e..e1354d1 100644 --- a/src/GUIStuff/Elements/TextBox.impl.hpp +++ b/src/GUIStuff/Elements/TextBox.impl.hpp @@ -21,6 +21,16 @@ template void TextBox::layout(const Clay_ElementId& id, const Te } } +template void TextBox::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 void TextBox::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { auto& bb = boundingBox.value(); diff --git a/src/GUIStuff/Elements/TextParagraph.cpp b/src/GUIStuff/Elements/TextParagraph.cpp index 95d35ab..80acb0e 100644 --- a/src/GUIStuff/Elements/TextParagraph.cpp +++ b/src/GUIStuff/Elements/TextParagraph.cpp @@ -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(); @@ -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())} diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 1b4d839..75a643f 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -5,6 +5,7 @@ #include #include #include +#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(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(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(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(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(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 p = a.Build(); + p->layout(std::numeric_limits::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(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::max(); + } + if(!clip->vertical) { + bb.y = 0.0f; + bb.height = std::numeric_limits::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(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(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 p = a.Build(); - p->layout(std::numeric_limits::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(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::max(); - } - if(!clip->vertical) { - bb.y = 0.0f; - bb.height = std::numeric_limits::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(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 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 GUIManager::get_invalid_draw_bb(SCollision::AABB 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& bb) { + if(invalidDrawBB.has_value()) + invalidDrawBB.value().include_aabb_in_bounds(bb); + else + invalidDrawBB = bb; +} + bool GUIManager::cursor_obstructed() const { return cursorObstructed; } diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index b865ddc..8654c1b 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -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& f); void run_post_callback_func(); + void invalidate_draw_element(Element* element, const BorderData& extraPadding = {}); + void invalidate_draw_in_area(const SCollision::AABB& bb); + template 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 oldRenderCommandMap; + std::optional> invalidDrawBB; + + SCollision::AABB get_invalid_draw_bb(SCollision::AABB bb, const BorderData& extraPadding = {}); + SCollision::AABB get_invalid_draw_bb_from_command(const Clay_RenderCommand* command); + void update_invalidated_draw_area_from_layout(); + bool setToLayout; + bool setToUpdateInvalidateDrawAreaFromLayout; bool cursorObstructed; }; diff --git a/src/MainProgram.cpp b/src/MainProgram.cpp index daeb9dc..b517a98 100755 --- a/src/MainProgram.cpp +++ b/src/MainProgram.cpp @@ -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(); } } diff --git a/src/TimePoint.cpp b/src/TimePoint.cpp index 6944aca..a60dfa9 100644 --- a/src/TimePoint.cpp +++ b/src/TimePoint.cpp @@ -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) { diff --git a/src/TimePoint.hpp b/src/TimePoint.hpp index 8c458b5..e4ca49a 100644 --- a/src/TimePoint.hpp +++ b/src/TimePoint.hpp @@ -30,7 +30,7 @@ template 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: diff --git a/src/Toolbar.cpp b/src/Toolbar.cpp index c0f51e6..eef8cc9 100644 --- a/src/Toolbar.cpp +++ b/src/Toolbar.cpp @@ -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& onClick) { return svg_icon_button(gui, id, svgPath, {