Added GUI draw cache
This commit is contained in:
parent
caaad67c42
commit
d709acb094
30 changed files with 547 additions and 246 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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())}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue