diff --git a/CMakeLists.txt b/CMakeLists.txt index 8712478..aa42fef 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ set(sources "src/main.cpp" "src/GUIStuff/Elements/SelectableButton.cpp" "src/GUIStuff/Elements/MovableTabList.cpp" "src/GUIStuff/Elements/PaintCircleMenu.cpp" + "src/GUIStuff/Elements/RotateWheel.cpp" "include/sago/platform_folders.cpp" "include/Helpers/Networking/NetLibrary.cpp" "include/Helpers/Networking/NetServer.cpp" diff --git a/src/DrawingProgram/DrawingProgram.cpp b/src/DrawingProgram/DrawingProgram.cpp index a894391..4244adc 100644 --- a/src/DrawingProgram/DrawingProgram.cpp +++ b/src/DrawingProgram/DrawingProgram.cpp @@ -201,6 +201,12 @@ void DrawingProgram::toolbar_gui() { if(t.gui.svg_icon_button("Eyedropper Toolbar Button", "data/icons/eyedropper.svg", drawTool->get_type() == DrawingProgramToolType::EYEDROPPER)) { switch_to_tool(DrawingProgramToolType::EYEDROPPER); } if(t.gui.svg_icon_button("Zoom Canvas Toolbar Button", "data/icons/zoom.svg", drawTool->get_type() == DrawingProgramToolType::ZOOM)) { switch_to_tool(DrawingProgramToolType::ZOOM); } if(t.gui.svg_icon_button("Pan Canvas Toolbar Button", "data/icons/hand.svg", drawTool->get_type() == DrawingProgramToolType::PAN)) { switch_to_tool(DrawingProgramToolType::PAN); } + + double newRotationAngle = world.drawData.cam.c.rotation, oldRotationAngle = world.drawData.cam.c.rotation; + t.gui.rotate_wheel("Canvas Rotate Wheel", &newRotationAngle); + if(newRotationAngle != oldRotationAngle) + world.drawData.cam.c.rotate_about(world.drawData.cam.c.from_space(world.main.window.size.cast() * 0.5f), newRotationAngle - oldRotationAngle); + CLAY({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(40), .height = CLAY_SIZING_FIXED(40)}}}) { if(t.gui.color_button("Foreground Color", &controls.foregroundColor, &controls.foregroundColor == t.colorLeft)) { t.color_selector_left(&controls.foregroundColor == t.colorLeft ? nullptr : &controls.foregroundColor); diff --git a/src/GUIStuff/Elements/RotateWheel.cpp b/src/GUIStuff/Elements/RotateWheel.cpp new file mode 100644 index 0000000..f16ae6d --- /dev/null +++ b/src/GUIStuff/Elements/RotateWheel.cpp @@ -0,0 +1,105 @@ +#include "RotateWheel.hpp" +#include "Helpers/ConvertVec.hpp" + +namespace GUIStuff { + +constexpr float WHEEL_WIDTH = 15.0f; + +constexpr double ROTATE_BAR_SNAP_DISTANCE = 0.3; +constexpr double ROTATE_BAR_SNAP_DISTRIBUTION = std::numbers::pi * 0.25; + +void RotateWheel::update(UpdateInputData& io, double* newRotationAnglePtr, const std::function& elemUpdate) { + rotateAngle = 0.0f; + if(!newRotationAnglePtr) + return; + + CLAY({.layout = { + .sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)} + }, + .custom = { .customData = this } + }) { + selection.update(Clay_Hovered(), io.mouse.leftClick, io.mouse.leftHeld); + Vector2f vecFromCenter = (io.mouse.pos - bb.center()).normalized(); + float distFromCenter = vec_distance(io.mouse.pos, bb.center()); + wheelHovered = selection.hovered && distFromCenter > wheel_start() && distFromCenter < wheel_end(); + if(selection.held) { + double& newRotationAngle = *newRotationAnglePtr; + newRotationAngle = std::atan2(vecFromCenter.y(), -vecFromCenter.x()) + std::numbers::pi; + if(wheelHovered) { // If we're hovering over the rotation bar, we should try snapping to specific angles + for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0 + 0.001; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { + if(std::fabs(newRotationAngle - snapPos) < ROTATE_BAR_SNAP_DISTANCE) { + newRotationAngle = snapPos; + if(newRotationAngle >= std::numbers::pi * 2.0 - 0.001) // Set 2pi back to 0 + newRotationAngle = 0; + break; + } + } + } + } + if(elemUpdate) + elemUpdate(); + } + + rotateAngle = *newRotationAnglePtr; +} + +float RotateWheel::wheel_start() { + return wheel_end() - WHEEL_WIDTH; +} + +float RotateWheel::wheel_end() { + return bb.width() * 0.5f; +} + +void RotateWheel::clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) { + bb = get_bb(command); + canvas->save(); + canvas->translate(bb.center().x(), bb.center().y()); + + draw_rotate_wheel(canvas, io); + + canvas->restore(); +} + +void RotateWheel::draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io) { + float wheelStart = wheel_start(); + float wheelEnd = wheel_end(); + + float rotateBarMiddleRadius = (wheelEnd + wheelStart) / 2.0f; + SkPaint rotateBarFill(io.theme->backColor2); + rotateBarFill.setStroke(true); + rotateBarFill.setStrokeWidth(wheelEnd - wheelStart); + canvas->drawCircle(0.0f, 0.0f, rotateBarMiddleRadius, rotateBarFill); + + if(wheelHovered) { + for(double snapPos = 0.0; snapPos < std::numbers::pi * 2.0; snapPos += ROTATE_BAR_SNAP_DISTRIBUTION) { + Vector2f lineDir{std::cos(snapPos), -std::sin(snapPos)}; + SkPaint p(snapPos == 0.0 ? io.theme->fillColor1 : io.theme->frontColor2); + p.setStrokeWidth(2.0f); + canvas->drawLine(lineDir.x() * wheelStart, lineDir.y() * wheelStart, lineDir.x() * wheelEnd, lineDir.y() * wheelEnd, p); + } + } + + SkPaint rotateBarHolderFill; + if(selection.held) + rotateBarHolderFill.setColor4f(io.theme->fillColor1); + else if(selection.hovered) + 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}; + canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderFill); + SkPaint rotateBarHolderOutline(io.theme->frontColor1); + rotateBarHolderOutline.setStroke(true); + rotateBarHolderOutline.setStrokeWidth(2.0f); + canvas->drawCircle(rotateBarHolderPos.x(), rotateBarHolderPos.y(), rotateBarHolderRadius, rotateBarHolderOutline); + + SkPaint rotateBarOutline(io.theme->frontColor2); + rotateBarOutline.setStroke(true); + rotateBarOutline.setStrokeWidth(2.0f); + canvas->drawCircle(0.0f, 0.0f, wheelStart, rotateBarOutline); + canvas->drawCircle(0.0f, 0.0f, wheelEnd, rotateBarOutline); +} + +} diff --git a/src/GUIStuff/Elements/RotateWheel.hpp b/src/GUIStuff/Elements/RotateWheel.hpp new file mode 100644 index 0000000..c6501ca --- /dev/null +++ b/src/GUIStuff/Elements/RotateWheel.hpp @@ -0,0 +1,21 @@ +#pragma once +#include "Element.hpp" + +namespace GUIStuff { + +class RotateWheel : public Element { + public: + void update(UpdateInputData& io, double* newRotationAnglePtr, const std::function& elemUpdate); + virtual void clay_draw(SkCanvas* canvas, UpdateInputData& io, Clay_RenderCommand* command) override; + SelectionHelper selection; + private: + float wheel_start(); + float wheel_end(); + void draw_rotate_wheel(SkCanvas* canvas, UpdateInputData& io); + + double rotateAngle = 0.0f; + SCollision::AABB bb; + bool wheelHovered; +}; + +} diff --git a/src/GUIStuff/GUIManager.cpp b/src/GUIStuff/GUIManager.cpp index 832ac57..15e7a0a 100644 --- a/src/GUIStuff/GUIManager.cpp +++ b/src/GUIStuff/GUIManager.cpp @@ -6,6 +6,7 @@ #include #include "Elements/SVGIcon.hpp" #include "Elements/SelectableButton.hpp" +#include "Elements/RotateWheel.hpp" #include "Elements/MovableTabList.hpp" #include "Elements/Element.hpp" #include @@ -660,6 +661,18 @@ void GUIManager::scroll_bar_many_entries_area(const std::string& uniqueId, float pop_id(); } +bool GUIManager::rotate_wheel(const std::string& id, double* angle, float size, const std::function& elemUpdate) { + bool toRet = false; + push_id(id); + CLAY ({.layout = {.sizing = {.width = CLAY_SIZING_FIXED(size), .height = CLAY_SIZING_FIXED(size) } } }) { + RotateWheel* e = insert_element(); + e->update(*io, angle, elemUpdate); + toRet = e->selection.clicked; + } + pop_id(); + return toRet; +} + void GUIManager::paint_circle_popup_menu(const std::string& id, const Vector2f& centerPos, const PaintCircleMenu::Data& val, const std::function& elemUpdate) { float size = 300.0f; push_id(id); diff --git a/src/GUIStuff/GUIManager.hpp b/src/GUIStuff/GUIManager.hpp index 8830673..8e03ca4 100644 --- a/src/GUIStuff/GUIManager.hpp +++ b/src/GUIStuff/GUIManager.hpp @@ -53,6 +53,8 @@ class GUIManager { void svg_icon(const std::string& id, const std::string& svgPath, bool isHighlighted = false, const std::function& elemUpdate = nullptr); bool svg_icon_button(const std::string& id, const std::string& svgPath, bool isSelected = false, float size = 40.0f, bool hasBorder = true, const std::function& elemUpdate = nullptr); + bool rotate_wheel(const std::string& id, double* angle, float size = 40.0f, const std::function& elemUpdate = nullptr); + void top_to_bottom_window_popup_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); void left_to_right_layout(Clay_SizingAxis x, Clay_SizingAxis y, const std::function& elemUpdate); void left_to_right_line_layout(const std::function& elemUpdate);