From b5d4c380241eeef58e271b5711f87a79cb8bc132 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sat, 2 May 2026 22:00:00 +0300 Subject: [PATCH] Separated color picker element into 3 different GUI elements --- CMakeLists.txt | 3 + assets/data/icons/RemixIcon/more-fill.svg | 1 + src/GUIStuff/Elements/AlphaSlider.cpp | 73 +++++ src/GUIStuff/Elements/AlphaSlider.hpp | 36 +++ src/GUIStuff/Elements/ColorPicker.decl.hpp | 36 +-- src/GUIStuff/Elements/ColorPicker.impl.hpp | 256 ++++++------------ src/GUIStuff/Elements/DropDown.hpp | 2 +- src/GUIStuff/Elements/Element.cpp | 8 +- src/GUIStuff/Elements/HueVerticalSlider.cpp | 75 +++++ src/GUIStuff/Elements/HueVerticalSlider.hpp | 30 ++ .../Elements/SaturationValueSquare.cpp | 82 ++++++ .../Elements/SaturationValueSquare.hpp | 34 +++ src/InputManager.cpp | 18 ++ src/InputManager.hpp | 3 + src/Screens/PhoneDrawingProgramScreen.cpp | 155 +++++++++-- src/Screens/PhoneDrawingProgramScreen.hpp | 6 +- 16 files changed, 599 insertions(+), 219 deletions(-) create mode 100644 assets/data/icons/RemixIcon/more-fill.svg create mode 100644 src/GUIStuff/Elements/AlphaSlider.cpp create mode 100644 src/GUIStuff/Elements/AlphaSlider.hpp create mode 100644 src/GUIStuff/Elements/HueVerticalSlider.cpp create mode 100644 src/GUIStuff/Elements/HueVerticalSlider.hpp create mode 100644 src/GUIStuff/Elements/SaturationValueSquare.cpp create mode 100644 src/GUIStuff/Elements/SaturationValueSquare.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ccedec..f863c9e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,6 +114,9 @@ set(sources "src/main.cpp" "src/GUIStuff/ClayImplement.cpp" "src/GUIStuff/Elements/GUIStuffHelpers.cpp" "src/GUIStuff/Elements/Element.cpp" + "src/GUIStuff/Elements/AlphaSlider.cpp" + "src/GUIStuff/Elements/HueVerticalSlider.cpp" + "src/GUIStuff/Elements/SaturationValueSquare.cpp" "src/GUIStuff/Elements/ImageDisplay.cpp" "src/GUIStuff/Elements/MutableTextLabel.cpp" "src/GUIStuff/Elements/SVGIcon.cpp" diff --git a/assets/data/icons/RemixIcon/more-fill.svg b/assets/data/icons/RemixIcon/more-fill.svg new file mode 100644 index 0000000..156029a --- /dev/null +++ b/assets/data/icons/RemixIcon/more-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/GUIStuff/Elements/AlphaSlider.cpp b/src/GUIStuff/Elements/AlphaSlider.cpp new file mode 100644 index 0000000..9e714bf --- /dev/null +++ b/src/GUIStuff/Elements/AlphaSlider.cpp @@ -0,0 +1,73 @@ +#include "AlphaSlider.hpp" +#include "../GUIManager.hpp" +#include "ColorPicker.hpp" + +namespace GUIStuff { + +AlphaSlider::AlphaSlider(GUIManager& gui): Element(gui) {} + +void AlphaSlider::layout(const Clay_ElementId& id, const AlphaSliderData& opts) { + o = opts; + update(); + + CLAY(id, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}, + .custom = {.customData = this} + }) { + } +} + +void AlphaSlider::update() { + if(currentG != *o.g || currentB != *o.b || currentR != *o.r || currentAlpha != *o.alpha) { + currentR = *o.r; + currentG = *o.g; + currentB = *o.b; + currentAlpha = *o.alpha; + gui.invalidate_draw_element(this); + } +} + +void AlphaSlider::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { + if(mouseHovering && button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onHold) o.onHold(); + update_alpha(button.pos); + if(o.onChange) o.onChange(); + isHeld = true; + } + else if(isHeld && !button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onRelease) o.onRelease(); + isHeld = false; + } +} + +void AlphaSlider::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { + if(isHeld) { + update_alpha(motion.pos); + if(o.onChange) o.onChange(); + } +} + +void AlphaSlider::update_alpha(Vector2f p) { + *o.alpha = std::clamp((p.x() - boundingBox.value().min.x()) / boundingBox.value().width(), 0.0f, 1.0f); +} + +void AlphaSlider::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, bb.width(), bb.height()), skiaAA); + + SkPaint alphaBarPaint; + alphaBarPaint.setShader(ColorPickerShaders::get_alpha_bar_shader({currentR, currentG, currentB}, bb.width())); + canvas->drawPaint(alphaBarPaint); + + SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f}); + selectionLinePaint.setAntiAlias(skiaAA); + selectionLinePaint.setStrokeWidth(1.0f); + canvas->drawLine(currentAlpha * bb.width(), 0.0f, currentAlpha * bb.width(), bb.height(), selectionLinePaint); + + canvas->restore(); +} + +} diff --git a/src/GUIStuff/Elements/AlphaSlider.hpp b/src/GUIStuff/Elements/AlphaSlider.hpp new file mode 100644 index 0000000..bb8eef6 --- /dev/null +++ b/src/GUIStuff/Elements/AlphaSlider.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +struct AlphaSliderData { + float* alpha = nullptr; + float* r = nullptr; + float* g = nullptr; + float* b = nullptr; + std::function onChange; + std::function onHold; + std::function onRelease; +}; + +class AlphaSlider : public Element { + public: + AlphaSlider(GUIManager& gui); + + void layout(const Clay_ElementId& id, const AlphaSliderData& opts); + virtual void update() override; + virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; + virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + + private: + void update_alpha(Vector2f p); + AlphaSliderData o; + float currentAlpha = -1.0f; + float currentR = -1.0f; + float currentG = -1.0f; + float currentB = -1.0f; + bool isHeld = false; +}; + +} diff --git a/src/GUIStuff/Elements/ColorPicker.decl.hpp b/src/GUIStuff/Elements/ColorPicker.decl.hpp index 6f16eaa..1be99a6 100644 --- a/src/GUIStuff/Elements/ColorPicker.decl.hpp +++ b/src/GUIStuff/Elements/ColorPicker.decl.hpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -67,44 +66,17 @@ struct ColorPickerData { 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) override; - void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; - void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; + void layout(const Clay_ElementId& id, T* data, bool selectAlpha, const ColorPickerData& conf = {}); private: - void update_color_picker_pos(const Vector2f& p, bool justHeld); - float get_sv_selection_area_size(); - Vector2f get_hue_bar_pos(); - Vector2f get_hue_bar_dim(); - Vector2f get_alpha_bar_pos(); - Vector2f get_alpha_bar_dim(); - void set_hsv(const Vector3f& hsv); + void set_rgb_from_hsv(const Vector3f& hsv); 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; + static constexpr uint16_t BAR_GAP = 5; + Vector3f savedHsv = {0.0f, 0.0f, 0.0f}; T* data; std::optional oldData; ColorPickerData config; - bool selectAlpha = false; - enum class HeldBar { - NONE, - SV_HELD, - HUE_HELD, - ALPHA_HELD - }; - HeldBar held = HeldBar::NONE; }; } diff --git a/src/GUIStuff/Elements/ColorPicker.impl.hpp b/src/GUIStuff/Elements/ColorPicker.impl.hpp index 28b80c6..b8686c0 100644 --- a/src/GUIStuff/Elements/ColorPicker.impl.hpp +++ b/src/GUIStuff/Elements/ColorPicker.impl.hpp @@ -1,192 +1,116 @@ #pragma once #include "ColorPicker.decl.hpp" #include "../GUIManager.hpp" +#include "Helpers/ConvertVec.hpp" +#include "SaturationValueSquare.hpp" +#include "HueVerticalSlider.hpp" +#include "AlphaSlider.hpp" namespace GUIStuff { template ColorPicker::ColorPicker(GUIManager& gui): Element(gui) {} -template void ColorPicker::layout(const Clay_ElementId& id, T* data, bool selectAlpha, const ColorPickerData& config) { +template void ColorPicker::layout(const Clay_ElementId& id, T* data, bool selectAlpha, const ColorPickerData& conf) { this->data = data; if(!oldData.has_value() || oldData.value() != *data) { - dd.savedHsv = rgb_to_hsv(*data); + savedHsv = rgb_to_hsv(*data); oldData = *data; } - if(selectAlpha) - dd.savedAlpha = (*data)[3]; - this->selectAlpha = selectAlpha; - this->config = config; + config = conf; - CLAY(id, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } - }, - .aspectRatio = {1.0f}, - .custom = { .customData = this } - }) {} -} - -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(); - - canvas->save(); - canvas->translate(bb.min.x(), bb.min.y()); - float svSelectionAreaSize = get_sv_selection_area_size(); - canvas->scale(svSelectionAreaSize, svSelectionAreaSize); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); - - SkPaint svSelectionAreaPaint; - svSelectionAreaPaint.setShader(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 - 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 = dd.savedHsv.x() / 360.0f; - - Vector2f hueBarPos = get_hue_bar_pos(); - Vector2f hueBarDim = get_hue_bar_dim(); - canvas->save(); - canvas->translate(hueBarPos.x(), hueBarPos.y()); - canvas->scale(hueBarDim.x(), hueBarDim.y()); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); - - SkPaint hueBarPaint; - hueBarPaint.setShader(ColorPickerShaders::get_hue_shader()); - canvas->drawPaint(hueBarPaint); - canvas->drawLine(0.0f, 1.0f - normalizedHue, 1.0f, 1.0f - normalizedHue, selectionLinePaint); - - canvas->restore(); - - Vector2f alphaBarPos = get_alpha_bar_pos(); - Vector2f alphaBarDim = get_alpha_bar_dim(); - canvas->save(); - canvas->translate(alphaBarPos.x(), alphaBarPos.y()); - canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, alphaBarDim.x(), alphaBarDim.y()), skiaAA); - - SkPaint alphaBarPaint; - if(selectAlpha) - alphaBarPaint.setShader(ColorPickerShaders::get_alpha_bar_shader({(*data)[0], (*data)[1], (*data)[2]}, alphaBarDim.x())); - else - alphaBarPaint.setColor4f(SkColor4f{(*data)[0], (*data)[1], (*data)[2], 1.0f}); - canvas->drawPaint(alphaBarPaint); + auto onChange = [&] { + set_rgb_from_hsv(savedHsv); + if(config.onChange) config.onChange(); + }; if(selectAlpha) { - canvas->scale(alphaBarDim.x(), alphaBarDim.y()); - canvas->drawLine((*data)[3], 0.0f, (*data)[3], 1.0f, selectionLinePaint); - } - - canvas->restore(); -} - -template void ColorPicker::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { - HeldBar oldHeld = held; - held = HeldBar::NONE; - if(mouseHovering && button.button == InputManager::MouseButton::LEFT && button.down && boundingBox.has_value()) { - auto& bb = boundingBox.value(); - float svSelectionAreaSize = get_sv_selection_area_size(); - if(SCollision::collide(SCollision::AABB(bb.min, bb.min + Vector2f{svSelectionAreaSize, svSelectionAreaSize}), button.pos)) - held = HeldBar::SV_HELD; - else { - Vector2f hueBarPos = get_hue_bar_pos(); - Vector2f hueBarDim = get_hue_bar_dim(); - if(SCollision::collide(SCollision::AABB(hueBarPos, hueBarPos + hueBarDim), button.pos)) - held = HeldBar::HUE_HELD; - else if(selectAlpha) { - Vector2f alphaBarPos = get_alpha_bar_pos(); - Vector2f alphaBarDim = get_alpha_bar_dim(); - if(SCollision::collide(SCollision::AABB(alphaBarPos, alphaBarPos + alphaBarDim), button.pos)) - held = HeldBar::ALPHA_HELD; + CLAY(id, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, + .childGap = BAR_GAP, + .layoutDirection = CLAY_TOP_TO_BOTTOM + } + }) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .childGap = BAR_GAP, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + }) { + gui.element("sv square", SaturationValueSquareData{ + .saturation = &savedHsv.y(), + .value = &savedHsv.z(), + .hue = &savedHsv.x(), + .onChange = onChange, + .onHold = config.onHold, + .onRelease = config.onRelease + }); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(BAR_WIDTH), .height = CLAY_SIZING_GROW(0)} + }, + }) { + gui.element("hue slider", HueVerticalSliderData{ + .hue = &savedHsv.x(), + .onChange = onChange, + .onHold = config.onHold, + .onRelease = config.onRelease + }); + } + } + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(BAR_WIDTH)} + }, + }) { + gui.element("alpha slider", AlphaSliderData{ + .alpha = &(*data)[3], + .r = &(*data)[0], + .g = &(*data)[1], + .b = &(*data)[2], + .onChange = onChange, + .onHold = config.onHold, + .onRelease = config.onRelease + }); } } } - if(oldHeld != HeldBar::NONE && held == HeldBar::NONE) { - gui.set_post_callback_func([&] { - if(config.onRelease) config.onRelease(); - }); - } - update_color_picker_pos(button.pos, true); -} - -template void ColorPicker::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { - update_color_picker_pos(motion.pos, false); -} - -template void ColorPicker::update_color_picker_pos(const Vector2f& p, bool justHeld) { - if(held != HeldBar::NONE && boundingBox.has_value()) { - gui.set_post_callback_func([&, p, justHeld]() { - auto& bb = boundingBox.value(); - switch(held) { - case HeldBar::NONE: - break; - 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}); - 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; - } - case HeldBar::HUE_HELD: { - Vector2f huePos = get_hue_bar_pos(); - Vector2f hueDim = get_hue_bar_dim(); - 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; - } - case HeldBar::ALPHA_HELD: { - Vector2f alphaPos = get_alpha_bar_pos(); - Vector2f alphaDim = get_alpha_bar_dim(); - (*data)[3] = std::clamp((p.x() - alphaPos.x()) / alphaDim.x(), 0.0f, 1.0f); - dd.savedAlpha = (*data)[3]; - oldData = *data; - if(justHeld && config.onHold) config.onHold(); - if(config.onChange) config.onChange(); - break; - } + else { + CLAY(id, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}, + .childGap = BAR_GAP, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + }) { + gui.element("sv square", SaturationValueSquareData{ + .saturation = &savedHsv.y(), + .value = &savedHsv.z(), + .hue = &savedHsv.x(), + .onChange = onChange, + .onHold = config.onHold, + .onRelease = config.onRelease + }); + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(BAR_WIDTH), .height = CLAY_SIZING_GROW(0)}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + }) { + gui.element("hue slider", HueVerticalSliderData{ + .hue = &savedHsv.x(), + .onChange = onChange, + .onHold = config.onHold, + .onRelease = config.onRelease + }); } - }); + } } } -template float ColorPicker::get_sv_selection_area_size() { - return boundingBox.value().width() - (BAR_GAP + BAR_WIDTH); -} - -template Vector2f ColorPicker::get_hue_bar_pos() { - return {boundingBox.value().min.x() + get_sv_selection_area_size() + BAR_GAP, boundingBox.value().min.y()}; -} - -template Vector2f ColorPicker::get_hue_bar_dim() { - return {BAR_WIDTH, get_sv_selection_area_size()}; -} - -template Vector2f ColorPicker::get_alpha_bar_pos() { - return {boundingBox.value().min.x(), boundingBox.value().min.y() + get_sv_selection_area_size() + BAR_GAP}; -} - -template Vector2f ColorPicker::get_alpha_bar_dim() { - return {boundingBox.value().width(), BAR_WIDTH}; -} - -template void ColorPicker::set_hsv(const Vector3f& hsv) { +template void ColorPicker::set_rgb_from_hsv(const Vector3f& hsv) { Vector3f a = hsv_to_rgb(hsv); (*data)[0] = a.x(); (*data)[1] = a.y(); diff --git a/src/GUIStuff/Elements/DropDown.hpp b/src/GUIStuff/Elements/DropDown.hpp index 23e7836..92ee38c 100644 --- a/src/GUIStuff/Elements/DropDown.hpp +++ b/src/GUIStuff/Elements/DropDown.hpp @@ -16,7 +16,6 @@ struct DropdownOptions { template class DropDown : public Element { public: - static constexpr float ENTRY_HEIGHT = 27.0f; static constexpr float DROPDOWN_OFFSET = 4.0f; DropDown(GUIManager& gui): Element(gui) {} @@ -61,6 +60,7 @@ template class DropDown : public Element { if(isOpen && boundingBox.has_value()) { auto& buttonBB = boundingBox.value(); + float ENTRY_HEIGHT = buttonBB.height(); gui.set_z_index(gui.get_z_index() + 1, [&] { gui.element("DROPDOWN", [&](LayoutElement* l, const Clay_ElementId& lId) { float dropdownHeight = ENTRY_HEIGHT * selections.size() + DROPDOWN_OFFSET; diff --git a/src/GUIStuff/Elements/Element.cpp b/src/GUIStuff/Elements/Element.cpp index 8eb144a..ace042a 100644 --- a/src/GUIStuff/Elements/Element.cpp +++ b/src/GUIStuff/Elements/Element.cpp @@ -14,8 +14,12 @@ namespace GUIStuff { void Element::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { } void Element::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { } void Element::input_mouse_wheel_callback(const InputManager::MouseWheelCallbackArgs& wheel) { } - void Element::input_finger_touch_callback(const InputManager::FingerTouchCallbackArgs& touch) { } - void Element::input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion) { } + void Element::input_finger_touch_callback(const InputManager::FingerTouchCallbackArgs& touch) { + input_mouse_button_callback(InputManager::convert_finger_touch_to_mouse_button(touch)); + } + void Element::input_finger_motion_callback(const InputManager::FingerMotionCallbackArgs& motion) { + input_mouse_motion_callback(InputManager::convert_finger_motion_to_mouse_motion(motion)); + } void Element::input_key_callback(const InputManager::KeyCallbackArgs& key) {} void Element::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) {} diff --git a/src/GUIStuff/Elements/HueVerticalSlider.cpp b/src/GUIStuff/Elements/HueVerticalSlider.cpp new file mode 100644 index 0000000..ef12e02 --- /dev/null +++ b/src/GUIStuff/Elements/HueVerticalSlider.cpp @@ -0,0 +1,75 @@ +#include "HueVerticalSlider.hpp" +#include "../GUIManager.hpp" +#include "ColorPicker.hpp" + +namespace GUIStuff { + +HueVerticalSlider::HueVerticalSlider(GUIManager& gui): Element(gui) {} + +void HueVerticalSlider::layout(const Clay_ElementId& id, const HueVerticalSliderData& opts) { + o = opts; + update(); + + CLAY(id, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}, + .custom = {.customData = this} + }) { + } +} + +void HueVerticalSlider::update() { + if(currentHue != *o.hue) { + currentHue = *o.hue; + gui.invalidate_draw_element(this); + } +} + +void HueVerticalSlider::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { + if(mouseHovering && button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onHold) o.onHold(); + update_hue(button.pos); + if(o.onChange) o.onChange(); + isHeld = true; + } + else if(isHeld && !button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onRelease) o.onRelease(); + isHeld = false; + } +} + +void HueVerticalSlider::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { + if(isHeld) { + update_hue(motion.pos); + if(o.onChange) o.onChange(); + } +} + +void HueVerticalSlider::update_hue(Vector2f p) { + *o.hue = (1.0f - std::clamp((p.y() - boundingBox.value().min.y()) / boundingBox.value().height(), 0.0f, 1.0f)) * 360.0f; +} + +void HueVerticalSlider::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + float normalizedHue = currentHue / 360.0f; + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + canvas->scale(bb.width(), bb.height()); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); + SkPaint hueBarPaint; + hueBarPaint.setShader(ColorPickerShaders::get_hue_shader()); + canvas->drawPaint(hueBarPaint); + canvas->restore(); + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f}); + selectionLinePaint.setAntiAlias(skiaAA); + selectionLinePaint.setStrokeWidth(1.0f); + float hueY = bb.height() * (1.0f - normalizedHue); + canvas->drawLine(0.0f, hueY, bb.width(), hueY, selectionLinePaint); + canvas->restore(); +} + +} diff --git a/src/GUIStuff/Elements/HueVerticalSlider.hpp b/src/GUIStuff/Elements/HueVerticalSlider.hpp new file mode 100644 index 0000000..0458686 --- /dev/null +++ b/src/GUIStuff/Elements/HueVerticalSlider.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +struct HueVerticalSliderData { + float* hue = nullptr; + std::function onChange; + std::function onHold; + std::function onRelease; +}; + +class HueVerticalSlider : public Element { + public: + HueVerticalSlider(GUIManager& gui); + + void layout(const Clay_ElementId& id, const HueVerticalSliderData& opts); + virtual void update() override; + virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; + virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + + private: + void update_hue(Vector2f p); + HueVerticalSliderData o; + float currentHue = -1.0f; + bool isHeld = false; +}; + +} diff --git a/src/GUIStuff/Elements/SaturationValueSquare.cpp b/src/GUIStuff/Elements/SaturationValueSquare.cpp new file mode 100644 index 0000000..e14876b --- /dev/null +++ b/src/GUIStuff/Elements/SaturationValueSquare.cpp @@ -0,0 +1,82 @@ +#include "SaturationValueSquare.hpp" +#include "../GUIManager.hpp" +#include "ColorPicker.hpp" + +namespace GUIStuff { + +SaturationValueSquare::SaturationValueSquare(GUIManager& gui): Element(gui) {} + +void SaturationValueSquare::layout(const Clay_ElementId& id, const SaturationValueSquareData& opts) { + o = opts; + update(); + + CLAY(id, { + .layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}, + .custom = {.customData = this} + }) { + } +} + +void SaturationValueSquare::update() { + if(currentHue != *o.hue || currentSaturation != *o.saturation || currentValue != *o.value) { + currentHue = *o.hue; + currentSaturation = *o.saturation; + currentValue = *o.value; + gui.invalidate_draw_element(this); + } +} + +void SaturationValueSquare::input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) { + if(mouseHovering && button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onHold) o.onHold(); + update_sv(button.pos); + if(o.onChange) o.onChange(); + isHeld = true; + } + else if(isHeld && !button.down && button.button == InputManager::MouseButton::LEFT) { + if(o.onRelease) o.onRelease(); + isHeld = false; + } +} + +void SaturationValueSquare::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) { + if(isHeld) { + update_sv(motion.pos); + if(o.onChange) o.onChange(); + } +} + +void SaturationValueSquare::update_sv(Vector2f p) { + *o.saturation = std::clamp((p.x() - boundingBox.value().min.x()) / boundingBox.value().width(), 0.0f, 1.0f); + *o.value = 1.0f - std::clamp((p.y() - boundingBox.value().min.y()) / boundingBox.value().height(), 0.0f, 1.0f); +} + +void SaturationValueSquare::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) { + auto& bb = boundingBox.value(); + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + canvas->scale(bb.width(), bb.height()); + canvas->clipRect(SkRect::MakeXYWH(0.0f, 0.0f, 1.0f, 1.0f), skiaAA); + + SkPaint svSelectionAreaPaint; + svSelectionAreaPaint.setShader(ColorPickerShaders::get_sv_selection_shader(currentHue / 360.0f)); + canvas->drawPaint(svSelectionAreaPaint); + canvas->restore(); + + canvas->save(); + canvas->translate(bb.min.x(), bb.min.y()); + + SkPaint selectionLinePaint({1.0f, 1.0f, 1.0f, 1.0f}); + selectionLinePaint.setAntiAlias(skiaAA); + selectionLinePaint.setStrokeWidth(1.0f); + + float valueY = bb.height() * (1.0f - currentValue); + canvas->drawLine(0.0f, valueY, bb.width(), valueY, selectionLinePaint); + float saturationX = bb.width() * currentSaturation; + canvas->drawLine(saturationX, 0.0f, saturationX, bb.height(), selectionLinePaint); + + canvas->restore(); +} + +} diff --git a/src/GUIStuff/Elements/SaturationValueSquare.hpp b/src/GUIStuff/Elements/SaturationValueSquare.hpp new file mode 100644 index 0000000..d9e0538 --- /dev/null +++ b/src/GUIStuff/Elements/SaturationValueSquare.hpp @@ -0,0 +1,34 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +struct SaturationValueSquareData { + float* saturation = nullptr; + float* value = nullptr; + float* hue = nullptr; + std::function onChange; + std::function onHold; + std::function onRelease; +}; + +class SaturationValueSquare : public Element { + public: + SaturationValueSquare(GUIManager& gui); + + void layout(const Clay_ElementId& id, const SaturationValueSquareData& opts); + virtual void update() override; + virtual void input_mouse_button_callback(const InputManager::MouseButtonCallbackArgs& button) override; + virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command, bool skiaAA) override; + + private: + void update_sv(Vector2f p); + SaturationValueSquareData o; + float currentHue = -1.0f; + float currentSaturation = -1.0f; + float currentValue = -1.0f; + bool isHeld = false; +}; + +} diff --git a/src/InputManager.cpp b/src/InputManager.cpp index 2b004b8..8331b26 100644 --- a/src/InputManager.cpp +++ b/src/InputManager.cpp @@ -624,6 +624,24 @@ void InputManager::backend_pen_axis_update(const SDL_PenAxisEvent& e) { } } +InputManager::MouseButtonCallbackArgs InputManager::convert_finger_touch_to_mouse_button(const FingerTouchCallbackArgs& touch) { + return { + .deviceType = InputManager::MouseDeviceType::TOUCH, + .button = InputManager::MouseButton::LEFT, + .down = touch.down, + .clicks = static_cast(touch.fingerTapCount), + .pos = touch.pos + }; +} + +InputManager::MouseMotionCallbackArgs InputManager::convert_finger_motion_to_mouse_motion(const FingerMotionCallbackArgs& motion) { + return { + .deviceType = InputManager::MouseDeviceType::TOUCH, + .pos = motion.pos, + .move = motion.move + }; +} + std::vector InputManager::get_multiple_finger_positions() { std::vector pos; for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers) diff --git a/src/InputManager.hpp b/src/InputManager.hpp index b2c0490..315097f 100644 --- a/src/InputManager.hpp +++ b/src/InputManager.hpp @@ -395,6 +395,9 @@ struct InputManager { size_t fingerDownCount; }; + static MouseButtonCallbackArgs convert_finger_touch_to_mouse_button(const FingerTouchCallbackArgs& touch); + static MouseMotionCallbackArgs convert_finger_motion_to_mouse_motion(const FingerMotionCallbackArgs& motion); + struct WindowResizeCallbackArgs { Vector2i size; SCollision::AABB safeArea; diff --git a/src/Screens/PhoneDrawingProgramScreen.cpp b/src/Screens/PhoneDrawingProgramScreen.cpp index cbfb65a..6b013c4 100644 --- a/src/Screens/PhoneDrawingProgramScreen.cpp +++ b/src/Screens/PhoneDrawingProgramScreen.cpp @@ -3,9 +3,12 @@ #include "DrawingProgramScreen.hpp" #include "Helpers/ConvertVec.hpp" #include "../GUIStuff/Elements/GridScrollArea.hpp" +#include "../GUIStuff/Elements/DropDown.hpp" +#include "../GUIStuff/Elements/ColorPicker.hpp" #include "../GUIStuff/ElementHelpers/ButtonHelpers.hpp" #include "../GUIStuff/ElementHelpers/LayoutHelpers.hpp" #include "../GUIStuff/ElementHelpers/TextLabelHelpers.hpp" +#include "../GUIStuff/ElementHelpers/TextBoxHelpers.hpp" #include "FileSelectScreen.hpp" #include @@ -112,8 +115,10 @@ void PhoneDrawingProgramScreen::bottom_toolbar() { }) {} switch(settingsMenuPopup) { case SettingsMenuPopup::NONE: + reset_color_picker_popup_data(); break; case SettingsMenuPopup::SETTINGS: + reset_color_picker_popup_data(); tool_settings_popup(); break; case SettingsMenuPopup::FG_COLOR: @@ -234,24 +239,25 @@ void PhoneDrawingProgramScreen::tool_settings_popup() { }); } +void PhoneDrawingProgramScreen::reset_color_picker_popup_data() { + colorPickerPopupData.extraSettingsOpen = false; + colorPickerPopupData.addingPalette = false; + colorPickerPopupData.newPaletteStr.clear(); +} + void PhoneDrawingProgramScreen::color_settings_popup(Vector4f* color) { auto& gui = main.g.gui; - auto& palette = main.conf.palettes[paletteData.selectedPalette]; - gui.element("color settings popup", [&] (LayoutElement*, const Clay_ElementId& lId) { - CLAY(lId, { - .layout = { - .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, - }, - .backgroundColor = convert_vec4(gui.io.theme->backColor1), - .cornerRadius = CLAY_CORNER_RADIUS(gui.io.theme->windowCorners1) - }) { - gui.element("color selector grid", GridScrollArea::Options{ - .entryWidth = BIG_BUTTON_SIZE, - .childAlignmentX = CLAY_ALIGN_X_CENTER, - .entryHeight = BIG_BUTTON_SIZE, - .entryCount = palette.colors.size(), - .scrollbar = ScrollArea::ScrollbarType::NORMAL, - .elementContent = [&](size_t i) { + auto& palette = main.conf.palettes[colorPickerPopupData.selectedPalette]; + + auto paletteColorPickerGrid = [&] { + gui.element("color selector grid", GridScrollArea::Options{ + .entryWidth = BIG_BUTTON_SIZE, + .childAlignmentX = CLAY_ALIGN_X_CENTER, + .entryHeight = BIG_BUTTON_SIZE, + .entryCount = palette.colors.size() + 1, + .scrollbar = ScrollArea::ScrollbarType::NORMAL, + .elementContent = [&](size_t i) { + if(i < palette.colors.size()) { auto newC = std::make_shared(palette.colors[i].x(), palette.colors[i].y(), palette.colors[i].z()); color_button(gui, "c", newC.get(), { .isSelected = newC->x() == color->x() && newC->y() == color->y() && newC->z() == color->z(), @@ -264,7 +270,124 @@ void PhoneDrawingProgramScreen::color_settings_popup(Vector4f* color) { } }); } + else { + svg_icon_button(gui, "extra color settings", "data/icons/RemixIcon/more-fill.svg", { + .drawType = SelectableButton::DrawType::TRANSPARENT_ALL, + .isSelected = colorPickerPopupData.extraSettingsOpen, + .onClick = [&] { + colorPickerPopupData.extraSettingsOpen = !colorPickerPopupData.extraSettingsOpen; + } + }); + } + } + }); + }; + + auto paletteColorPickerExtras = [&] { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(200), .height = CLAY_SIZING_FIT(200)}, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + gui.element>("color picker element", color, true); + } + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + std::vector paletteNames; + for(auto& p : main.conf.palettes) + paletteNames.emplace_back(p.name); + gui.element>("paletteselector", &colorPickerPopupData.selectedPalette, paletteNames); + svg_icon_button(gui, "paletteadd", "data/icons/plus.svg", { + .onClick = [&] { + colorPickerPopupData.addingPalette = !colorPickerPopupData.addingPalette; + } }); + svg_icon_button(gui, "paletteremove", "data/icons/close.svg", { + .onClick = [&] { + main.conf.palettes.erase(main.conf.palettes.begin() + colorPickerPopupData.selectedPalette); + colorPickerPopupData.selectedPalette = 0; + } + }); + } + if(colorPickerPopupData.addingPalette) { + input_text_field(gui, "paletteinputname", "Name", &colorPickerPopupData.newPaletteStr); + text_button(gui, "addpalettebutton", "Create", { + .wide = true, + .onClick = [&] { + if(!colorPickerPopupData.newPaletteStr.empty()) { + main.conf.palettes.emplace_back(); + main.conf.palettes.back().name = colorPickerPopupData.newPaletteStr; + colorPickerPopupData.selectedPalette = main.conf.palettes.size() - 1; + colorPickerPopupData.addingPalette = false; + } + } + }); + } + if(colorPickerPopupData.selectedPalette != 0) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + } + }) { + svg_icon_button(gui, "addcolor", "data/icons/plus.svg", { + .onClick = [&, color] { + std::erase(palette.colors, Vector3f{color->x(), color->y(), color->z()}); + palette.colors.emplace_back(color->x(), color->y(), color->z()); + } + }); + svg_icon_button(gui, "deletecolor", "data/icons/close.svg", { + .onClick = [&, color] { + std::erase(palette.colors, Vector3f{color->x(), color->y(), color->z()}); + } + }); + } + } + }; + + gui.element("color settings popup", [&] (LayoutElement* l, const Clay_ElementId& lId) { + CLAY(lId, { + .layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER}, + .layoutDirection = CLAY_LEFT_TO_RIGHT + }, + .backgroundColor = convert_vec4(gui.io.theme->backColor1), + .cornerRadius = CLAY_CORNER_RADIUS(gui.io.theme->windowCorners1) + }) { + if(colorPickerPopupData.extraSettingsOpen) { + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + }) { + paletteColorPickerGrid(); + } + CLAY_AUTO_ID({ + .layout = { + .sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0)}, + .childGap = gui.io.theme->childGap1, + .layoutDirection = CLAY_TOP_TO_BOTTOM + }, + }) { + paletteColorPickerExtras(); + } + } + else + paletteColorPickerGrid(); } }); } diff --git a/src/Screens/PhoneDrawingProgramScreen.hpp b/src/Screens/PhoneDrawingProgramScreen.hpp index 8204012..0a2b42c 100644 --- a/src/Screens/PhoneDrawingProgramScreen.hpp +++ b/src/Screens/PhoneDrawingProgramScreen.hpp @@ -24,9 +24,11 @@ class PhoneDrawingProgramScreen : public DrawingProgramScreen { FG_COLOR, BG_COLOR } settingsMenuPopup = SettingsMenuPopup::NONE; - struct PaletteData { + struct ColorPickerPopupData { + bool extraSettingsOpen = false; size_t selectedPalette = 0; bool addingPalette = false; std::string newPaletteStr; - } paletteData; + } colorPickerPopupData; + void reset_color_picker_popup_data(); };