From ce341b106876c313c488ea17b0f3aedd1b9e08e9 Mon Sep 17 00:00:00 2001 From: kunitoki Date: Sat, 4 May 2024 00:22:46 +0200 Subject: [PATCH] More graphics support --- examples/graphics/source/main.cpp | 159 +++++---- examples/render/source/main.cpp | 8 +- modules/yup_graphics/graphics/yup_Color.h | 144 ++++++++ .../yup_graphics/graphics/yup_ColorGradient.h | 146 ++++++++ .../yup_graphics/graphics/yup_Graphics.cpp | 335 +++++++++++++++++- modules/yup_graphics/graphics/yup_Graphics.h | 103 ++++++ modules/yup_graphics/graphics/yup_StrokeCap.h | 33 ++ .../yup_graphics/graphics/yup_StrokeJoin.h | 33 ++ modules/yup_graphics/primitives/yup_Path.h | 27 ++ .../yup_graphics/primitives/yup_Rectangle.h | 168 ++++++++- modules/yup_graphics/primitives/yup_Size.h | 39 ++ modules/yup_graphics/yup_graphics.h | 5 + modules/yup_gui/component/yup_Component.cpp | 60 +++- modules/yup_gui/component/yup_Component.h | 7 +- .../yup_gui/component/yup_ComponentNative.cpp | 2 +- modules/yup_gui/native/yup_Windowing_glfw.cpp | 2 +- 16 files changed, 1181 insertions(+), 90 deletions(-) create mode 100644 modules/yup_graphics/graphics/yup_Color.h create mode 100644 modules/yup_graphics/graphics/yup_ColorGradient.h create mode 100644 modules/yup_graphics/graphics/yup_StrokeCap.h create mode 100644 modules/yup_graphics/graphics/yup_StrokeJoin.h create mode 100644 modules/yup_graphics/primitives/yup_Path.h diff --git a/examples/graphics/source/main.cpp b/examples/graphics/source/main.cpp index ab207945..87fcadec 100644 --- a/examples/graphics/source/main.cpp +++ b/examples/graphics/source/main.cpp @@ -30,11 +30,94 @@ //============================================================================== +class CustomComponent : public juce::Component +{ +public: + CustomComponent() + { + random.setSeedRandomly(); + } + + void paint (juce::Graphics& g, float frameRate) override + { + } + +private: + juce::Random& random = juce::Random::getSystemRandom(); +}; + +//============================================================================== + class CustomWindow : public juce::DocumentWindow { + CustomComponent c; + public: CustomWindow() { + addAndMakeVisible (c); + } + + void resized() override + { + c.setBounds (getLocalBounds()); + } + + void paint (juce::Graphics& g, float frameRate) override + { + double time = juce::Time::getMillisecondCounterHiRes() / 1000.0; + + auto renderer = g.getRenderer(); + auto factory = g.getFactory(); + + rive::float2 p[9]; + for (int i = 0; i < 9; ++i) + p[i] = pts[i] + translate; + + rive::RawPath rawPath; + rawPath.moveTo (p[0].x, p[0].y); + rawPath.cubicTo (p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y); + rive::float2 c0 = rive::simd::mix (p[3], p[4], rive::float2 (2 / 3.f)); + rive::float2 c1 = rive::simd::mix (p[5], p[4], rive::float2 (2 / 3.f)); + rawPath.cubicTo (c0.x, c0.y, c1.x, c1.y, p[5].x, p[5].y); + rawPath.cubicTo (p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y); + if (doClose) + rawPath.close(); + + auto path = factory->makeRenderPath (rawPath, rive::FillRule::nonZero); + + auto fillPaint = factory->makeRenderPaint(); + fillPaint->style (rive::RenderPaintStyle::fill); + fillPaint->color (-1); + + auto strokePaint = factory->makeRenderPaint(); + strokePaint->style (rive::RenderPaintStyle::stroke); + strokePaint->color (0x8000ffff); + strokePaint->thickness (strokeWidth); + strokePaint->join (join); + strokePaint->cap (cap); + + renderer->drawPath (path.get(), fillPaint.get()); + renderer->drawPath (path.get(), strokePaint.get()); + + // Draw the interactive points. + auto pointPaint = factory->makeRenderPaint(); + pointPaint->style (rive::RenderPaintStyle::stroke); + pointPaint->color (0xff0000ff); + pointPaint->thickness (14); + pointPaint->cap (rive::StrokeCap::round); + + auto pointPath = factory->makeEmptyRenderPath(); + for (int i : { 1, 2, 4, 6, 7 }) + { + rive::float2 pt = pts[i] + translate; + pointPath->moveTo (pt.x, pt.y); + } + + renderer->drawPath (pointPath.get(), pointPaint.get()); + + updateFrameTime (time); + updateWindowTitle(); } void mouseDown (const juce::MouseEvent& event) override @@ -47,7 +130,7 @@ class CustomWindow : public juce::DocumentWindow dragIdx = -1; for (int i = 0; i < kNumInteractivePts; ++i) { - if (rive::simd::all (rive::simd::abs (dragLastPos - (pts[i] + translate)) < 100)) + if (rive::simd::all (rive::simd::abs (dragLastPos - (pts[i] + translate)) < 20)) { dragIdx = i; break; @@ -92,7 +175,6 @@ class CustomWindow : public juce::DocumentWindow forceAtomicMode = !forceAtomicMode; fpsLastTime = 0; fpsFrames = 0; - resized(); break; case juce::KeyPress::textDKey: @@ -144,93 +226,28 @@ class CustomWindow : public juce::DocumentWindow } } - void resized() override - { - auto [width, height] = getContentSize(); - - updateWindowTitle (width, height); - } - - void paint (juce::Graphics& g, float frameRate) override - { - double time = juce::Time::getMillisecondCounterHiRes() / 1000.0; - auto [width, height] = getContentSize(); - auto renderer = g.getRenderer(); - - rive::float2 p[9]; - for (int i = 0; i < 9; ++i) - p[i] = pts[i] + translate; - - rive::RawPath rawPath; - rawPath.moveTo (p[0].x, p[0].y); - rawPath.cubicTo (p[1].x, p[1].y, p[2].x, p[2].y, p[3].x, p[3].y); - rive::float2 c0 = rive::simd::mix (p[3], p[4], rive::float2 (2 / 3.f)); - rive::float2 c1 = rive::simd::mix (p[5], p[4], rive::float2 (2 / 3.f)); - rawPath.cubicTo (c0.x, c0.y, c1.x, c1.y, p[5].x, p[5].y); - rawPath.cubicTo (p[6].x, p[6].y, p[7].x, p[7].y, p[8].x, p[8].y); - if (doClose) - rawPath.close(); - - rive::Factory* factory = g.getFactory(); - auto path = factory->makeRenderPath (rawPath, rive::FillRule::nonZero); - - auto fillPaint = factory->makeRenderPaint(); - fillPaint->style (rive::RenderPaintStyle::fill); - fillPaint->color (-1); - - auto strokePaint = factory->makeRenderPaint(); - strokePaint->style (rive::RenderPaintStyle::stroke); - strokePaint->color (0x8000ffff); - strokePaint->thickness (strokeWidth); - strokePaint->join (join); - strokePaint->cap (cap); - - renderer->drawPath (path.get(), fillPaint.get()); - renderer->drawPath (path.get(), strokePaint.get()); - - // Draw the interactive points. - auto pointPaint = factory->makeRenderPaint(); - pointPaint->style (rive::RenderPaintStyle::stroke); - pointPaint->color (0xff0000ff); - pointPaint->thickness (14); - pointPaint->cap (rive::StrokeCap::round); - - auto pointPath = factory->makeEmptyRenderPath(); - for (int i : { 1, 2, 4, 6, 7 }) - { - rive::float2 pt = pts[i] + translate; - pointPath->moveTo (pt.x, pt.y); - } - - renderer->drawPath (pointPath.get(), pointPaint.get()); - - updateFrameTime (time, width, height); - } - void userTriedToCloseWindow() override { juce::JUCEApplication::getInstance()->systemRequestedQuit(); } private: - void updateWindowTitle(int width, int height) + void updateWindowTitle() { juce::String title; if (currentFps != 0) - title << "[" << currentFps << " FPS]"; + title << "[" << juce::String (currentFps, 1) << " FPS]"; title << " | " << "YUP On Rive Renderer"; if (forceAtomicMode) title << " (atomic)"; - title << " | " << width << " x " << height; - setTitle (title); } - void updateFrameTime (double time, int width, int height) + void updateFrameTime (double time) { ++fpsFrames; @@ -239,7 +256,7 @@ class CustomWindow : public juce::DocumentWindow { currentFps = fpsLastTime == 0 ? 0 : fpsFrames / fpsElapsed; - updateWindowTitle (width, height); + updateWindowTitle (); fpsFrames = 0; fpsLastTime = time; diff --git a/examples/render/source/main.cpp b/examples/render/source/main.cpp index 7ca4ea6c..efc68189 100644 --- a/examples/render/source/main.cpp +++ b/examples/render/source/main.cpp @@ -168,6 +168,10 @@ class CustomWindow : public juce::DocumentWindow --downRepeat; break; + case juce::KeyPress::textZKey: + setFullScreen (!isFullScreen()); + break; + case juce::KeyPress::upKey: { float oldScale = scale; @@ -312,7 +316,7 @@ class CustomWindow : public juce::DocumentWindow juce::String title; if (fps != 0) - title << "[" << fps << " FPS]"; + title << "[" << juce::String (fps, 1) << " FPS]"; if (instances > 1) title << " (x" << instances << " instances)"; @@ -393,7 +397,7 @@ struct Application : juce::JUCEApplication juce::Logger::outputDebugString ("Starting app " + commandLineParameters); window = std::make_unique(); - window->setSize ({ 1280, 866 }); + window->centreWithSize ({ 1280, 866 }); window->setVisible (true); } diff --git a/modules/yup_graphics/graphics/yup_Color.h b/modules/yup_graphics/graphics/yup_Color.h new file mode 100644 index 00000000..30dc6de4 --- /dev/null +++ b/modules/yup_graphics/graphics/yup_Color.h @@ -0,0 +1,144 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class JUCE_API Color +{ +public: + //============================================================================== + constexpr Color() noexcept = default; + + constexpr Color (uint32 color) noexcept + : data (color) + { + } + + constexpr Color (uint8 r, uint8 g, uint8 b) noexcept + : b (b) + , g (g) + , r (r) + , a (255) + { + } + + constexpr Color (uint8 a, uint8 r, uint8 g, uint8 b) noexcept + : b (b) + , g (g) + , r (r) + , a (a) + { + } + + //============================================================================== + constexpr uint32 getARGB() const noexcept + { + return data; + } + + constexpr operator uint32() const noexcept + { + return data; + } + + //============================================================================== + constexpr uint8 getAlpha() const noexcept + { + return a; + } + + constexpr Color& setAlpha (uint8 alpha) noexcept + { + a = alpha; + return *this; + } + + constexpr Color withAlpha (uint8 alpha) const noexcept + { + return Color (alpha, r, g, b); + } + + //============================================================================== + constexpr uint8 getRed() const noexcept + { + return r; + } + + constexpr Color& setRed (uint8 red) noexcept + { + r = red; + return *this; + } + + constexpr Color withRed (uint8 red) const noexcept + { + return Color (a, red, g, b); + } + + //============================================================================== + constexpr uint8 getGreen() const noexcept + { + return g; + } + + constexpr Color& setGreen (uint8 green) noexcept + { + g = green; + return *this; + } + + constexpr Color withGreen (uint8 green) const noexcept + { + return Color (a, r, green, b); + } + + //============================================================================== + constexpr uint8 getBlue() const noexcept + { + return b; + } + + constexpr Color& setBlue (uint8 blue) noexcept + { + b = blue; + return *this; + } + + constexpr Color withBlue (uint8 blue) const noexcept + { + return Color (a, r, g, blue); + } + +private: + union + { + struct + { + uint8 b, g, r, a; + }; + + uint32 data = 0xff000000; + }; +}; + +} // namespace juce diff --git a/modules/yup_graphics/graphics/yup_ColorGradient.h b/modules/yup_graphics/graphics/yup_ColorGradient.h new file mode 100644 index 00000000..7f14fc90 --- /dev/null +++ b/modules/yup_graphics/graphics/yup_ColorGradient.h @@ -0,0 +1,146 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +class JUCE_API ColorGradient +{ +public: + //============================================================================== + enum Type : unsigned int + { + Linear, + Radial + }; + + //============================================================================== + ColorGradient() noexcept = default; + + ColorGradient (Color color1, float x1, float y1, Color color2, float x2, float y2, Type type) noexcept + : type (type) + , start (color1, x1, y1, 0.0f) + , finish (color2, x2, y2, 1.0f) + { + if (type == Radial) + radius = std::sqrt (juce::square (x2 - x1) + juce::square (y2 - y1)); + } + + ColorGradient (const ColorGradient& other) = default; + ColorGradient (ColorGradient&& other) = default; + ColorGradient& operator= (const ColorGradient& other) = default; + ColorGradient& operator= (ColorGradient&& other) = default; + + //============================================================================== + Type getType() const noexcept + { + return type; + } + + //============================================================================== + Color getStartColor() const + { + return start.color; + } + + float getStartX() const + { + return start.x; + } + + float getStartY() const + { + return start.y; + } + + float getStartDelta() const + { + return start.delta; + } + + //============================================================================== + Color getFinishColor() const + { + return finish.color; + } + + float getFinishX() const + { + return finish.x; + } + + float getFinishY() const + { + return finish.y; + } + + float getFinishDelta() const + { + return finish.delta; + } + + //============================================================================== + float getRadius() const + { + return radius; + } + + //============================================================================== + void setAlpha (uint8 alpha) + { + start.color = start.color.withAlpha (alpha); + finish.color = finish.color.withAlpha (alpha); + } + + ColorGradient withAlpha (uint8 alpha) + { + ColorGradient result (*this); + result.setAlpha (alpha); + return result; + } + +private: + struct ColorStop + { + ColorStop() = default; + + ColorStop (Color color, float x, float y, float delta) + : color (color) + , x (x) + , y (y) + , delta (delta) + { + } + + Color color; + float x = 0.0f; + float y = 0.0f; + float delta = 0.0f; + }; + + Type type = Type::Linear; + ColorStop start; + ColorStop finish; + float radius = 0.0f; +}; + +} // namespace juce diff --git a/modules/yup_graphics/graphics/yup_Graphics.cpp b/modules/yup_graphics/graphics/yup_Graphics.cpp index c6151c25..00014724 100644 --- a/modules/yup_graphics/graphics/yup_Graphics.cpp +++ b/modules/yup_graphics/graphics/yup_Graphics.cpp @@ -22,20 +22,351 @@ namespace juce { +//============================================================================== +namespace { + +rive::StrokeJoin toStrokeJoin (StrokeJoin join) noexcept +{ + return static_cast (join); +} + +rive::StrokeCap toStrokeCap (StrokeCap cap) noexcept +{ + return static_cast (cap); +} + +rive::RawPath createRoundedRectPath (float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight) +{ + radiusTopLeft = jmin (radiusTopLeft, jmin (width / 2.0f, height / 2.0f)); + radiusTopRight = jmin (radiusTopRight, jmin (width / 2.0f, height / 2.0f)); + radiusBottomLeft = jmin (radiusBottomLeft, jmin (width / 2.0f, height / 2.0f)); + radiusBottomRight = jmin (radiusBottomRight, jmin (width / 2.0f, height / 2.0f)); + + rive::RawPath rawPath; + rawPath.moveTo (x + radiusTopLeft, y); + rawPath.lineTo (x + width - radiusTopRight, y); + rawPath.cubicTo (x + width - radiusTopRight * 0.55f, y, x + width, y + radiusTopRight * 0.45f, x + width, y + radiusTopRight); + rawPath.lineTo (x + width, y + height - radiusBottomRight); + rawPath.cubicTo (x + width, y + height - radiusBottomRight * 0.55f, x + width - radiusBottomRight * 0.55f, y + height, x + width - radiusBottomRight, y + height); + rawPath.lineTo (x + radiusBottomLeft, y + height); + rawPath.cubicTo (x + radiusBottomLeft * 0.55f, y + height, x, y + height - radiusBottomLeft * 0.55f, x, y + height - radiusBottomLeft); + rawPath.lineTo (x, y + radiusTopLeft); + rawPath.cubicTo (x, y + radiusTopLeft * 0.55f, x + radiusTopLeft * 0.55f, y, x + radiusTopLeft, y); + rawPath.lineTo (x + radiusTopLeft, y); + + return rawPath; +} + +rive::rcp toColorGradient (rive::Factory& factory, const ColorGradient& gradient) +{ + const uint32 colors[] = { gradient.getStartColor(), gradient.getFinishColor() }; + const float stops[] = { gradient.getStartDelta(), gradient.getFinishDelta() }; + + if (gradient.getType() == ColorGradient::Linear) + { + return factory.makeLinearGradient (gradient.getStartX(), + gradient.getStartY(), + gradient.getFinishX(), + gradient.getFinishY(), + colors, + stops, + 2); + } + else + { + return factory.makeRadialGradient (gradient.getStartX(), + gradient.getStartY(), + gradient.getRadius(), + colors, + stops, + 2); + } +} + +} // namespace + +//============================================================================== +Graphics::SavedState::SavedState (Graphics& g) + : g (std::addressof (g)) +{ +} + +Graphics::SavedState::SavedState (SavedState&& other) + : g (std::exchange (other.g, nullptr)) +{ +} + +Graphics::SavedState& Graphics::SavedState::operator= (SavedState&& other) +{ + g = std::exchange (other.g, nullptr); + return *this; +} + +Graphics::SavedState::~SavedState() +{ + if (g != nullptr) + g->restoreState(); +} + +//============================================================================== Graphics::Graphics (GraphicsContext& context, rive::Renderer& renderer) noexcept : context (context) + , factory (*context.factory()) , renderer (renderer) { + renderOptions.emplace_back(); } +//============================================================================== rive::Factory* Graphics::getFactory() { - return context.factory(); + return std::addressof (factory); } rive::Renderer* Graphics::getRenderer() { - return &renderer; + return std::addressof (renderer); +} + +//============================================================================== +Graphics::RenderOptions& Graphics::currentRenderOptions() +{ + jassert (! renderOptions.empty()); + + return renderOptions.back(); +} + +const Graphics::RenderOptions& Graphics::currentRenderOptions() const +{ + jassert (! renderOptions.empty()); + + return renderOptions.back(); +} + +//============================================================================== +Graphics::SavedState Graphics::saveState() +{ + jassert (! renderOptions.empty()); + + renderOptions.emplace_back (renderOptions.back()); + + renderer.save(); + + return { *this }; +} + +void Graphics::restoreState() +{ + renderer.restore(); + + renderOptions.pop_back(); +} + +//============================================================================== +void Graphics::setColor (Color color) +{ + currentRenderOptions().color = color; + currentRenderOptions().isCurrentBrushColor = true; +} + +Color Graphics::getColor() const +{ + return currentRenderOptions().color; +} + +void Graphics::setColorGradient (ColorGradient gradient) +{ + currentRenderOptions().gradient = std::move (gradient); + currentRenderOptions().isCurrentBrushColor = false; +} + +ColorGradient Graphics::getColorGradient() const +{ + return currentRenderOptions().gradient; +} + +void Graphics::setOpacity (uint8 opacity) +{ + currentRenderOptions().color.setAlpha (opacity); + currentRenderOptions().gradient.setAlpha (opacity); +} + +uint8 Graphics::getOpacity() const +{ + return currentRenderOptions().color.getAlpha(); +} + +void Graphics::setStrokeJoin (StrokeJoin join) +{ + currentRenderOptions().join = join; +} + +StrokeJoin Graphics::getStrokeJoin() const +{ + return currentRenderOptions().join; +} + +void Graphics::setStrokeCap (StrokeCap cap) +{ + currentRenderOptions().cap = cap; +} + +StrokeCap Graphics::getStrokeCap() const +{ + return currentRenderOptions().cap; +} + +//============================================================================== +void Graphics::drawLine (float x1, float y1, float x2, float y2, float thickness) +{ + rive::RawPath rawPath; + rawPath.moveTo (x1, y1); + rawPath.lineTo (x2, y2); + + auto& options = currentRenderOptions(); + + auto paint = factory.makeRenderPaint(); + paint->style (rive::RenderPaintStyle::stroke); + paint->thickness (thickness); + paint->join (toStrokeJoin (options.join)); + paint->cap (toStrokeCap (options.cap)); + + if (options.isColor()) + paint->color (options.getColor()); + else + paint->shader (toColorGradient (factory, options.getColorGradient())); + + auto path = factory.makeRenderPath (rawPath, rive::FillRule::nonZero); + renderer.drawPath (path.get(), paint.get()); +} + +void Graphics::drawLine (const Point& p1, const Point& p2, float thickness) +{ + drawLine (p1.getX(), p1.getY(), p2.getX(), p2.getY(), thickness); +} + +//============================================================================== +void Graphics::fillRect (float x, float y, float width, float height) +{ + rive::RawPath rawPath; + rawPath.addRect (rive::AABB (x, y, x + width, y + height)); + + auto& options = currentRenderOptions(); + + auto paint = factory.makeRenderPaint(); + paint->style (rive::RenderPaintStyle::fill); + + if (options.isColor()) + paint->color (options.getColor()); + else + paint->shader (toColorGradient (factory, options.getColorGradient())); + + auto path = factory.makeRenderPath (rawPath, rive::FillRule::nonZero); + renderer.drawPath (path.get(), paint.get()); +} + +void Graphics::fillRect (const Rectangle& r) +{ + fillRect (r.getX(), r.getY(), r.getWidth(), r.getHeight()); +} + +//============================================================================== +void Graphics::drawRect (float x, float y, float width, float height, float thickness) +{ + rive::RawPath rawPath; + rawPath.addRect (rive::AABB (x, y, x + width, y + height)); + + auto& options = currentRenderOptions(); + + auto paint = factory.makeRenderPaint(); + paint->style (rive::RenderPaintStyle::stroke); + paint->thickness (thickness); + paint->join (toStrokeJoin (options.join)); + paint->cap (toStrokeCap (options.cap)); + + if (options.isColor()) + paint->color (options.getColor()); + else + paint->shader (toColorGradient (factory, options.getColorGradient())); + + auto path = factory.makeRenderPath (rawPath, rive::FillRule::nonZero); + renderer.drawPath (path.get(), paint.get()); +} + +void Graphics::drawRect (const Rectangle& r, float thickness) +{ + drawRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), thickness); +} + +//============================================================================== +void Graphics::fillRoundedRect (float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight) +{ + rive::RawPath rawPath = createRoundedRectPath (x, y, width, height, radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight); + + auto& options = currentRenderOptions(); + + auto paint = factory.makeRenderPaint(); + paint->style (rive::RenderPaintStyle::fill); + + if (options.isColor()) + paint->color (options.getColor()); + else + paint->shader (toColorGradient (factory, options.getColorGradient())); + + auto path = factory.makeRenderPath (rawPath, rive::FillRule::nonZero); + renderer.drawPath (path.get(), paint.get()); +} + +void Graphics::fillRoundedRect (float x, float y, float width, float height, float radius) +{ + fillRoundedRect (x, y, width, height, radius, radius, radius, radius); +} + +void Graphics::fillRoundedRect (const Rectangle& r, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight) +{ + fillRoundedRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight); +} + +void Graphics::fillRoundedRect (const Rectangle& r, float radius) +{ + fillRoundedRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), radius, radius, radius, radius); +} + +//============================================================================== +void Graphics::drawRoundedRect (float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, float thickness) +{ + rive::RawPath rawPath = createRoundedRectPath (x, y, width, height, radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight); + + auto& options = currentRenderOptions(); + + auto paint = factory.makeRenderPaint(); + paint->style (rive::RenderPaintStyle::stroke); + paint->thickness (thickness); + paint->join (toStrokeJoin (options.join)); + paint->cap (toStrokeCap (options.cap)); + + if (options.isColor()) + paint->color (options.getColor()); + else + paint->shader (toColorGradient (factory, options.getColorGradient())); + + auto path = factory.makeRenderPath (rawPath, rive::FillRule::nonZero); + renderer.drawPath (path.get(), paint.get()); +} + +void Graphics::drawRoundedRect (float x, float y, float width, float height, float radius, float thickness) +{ + drawRoundedRect (x, y, width, height, radius, radius, radius, radius, thickness); +} + +void Graphics::drawRoundedRect (const Rectangle& r, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, float thickness) +{ + drawRoundedRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), radiusTopLeft, radiusTopRight, radiusBottomLeft, radiusBottomRight, thickness); +} + +void Graphics::drawRoundedRect (const Rectangle& r, float radius, float thickness) +{ + drawRoundedRect (r.getX(), r.getY(), r.getWidth(), r.getHeight(), radius, radius, radius, radius, thickness); } } // namespace juce diff --git a/modules/yup_graphics/graphics/yup_Graphics.h b/modules/yup_graphics/graphics/yup_Graphics.h index 4e6f92d0..68d70ea2 100644 --- a/modules/yup_graphics/graphics/yup_Graphics.h +++ b/modules/yup_graphics/graphics/yup_Graphics.h @@ -22,19 +22,122 @@ namespace juce { +//============================================================================== class GraphicsContext; +//============================================================================== class JUCE_API Graphics { public: + + //============================================================================== + class JUCE_API SavedState + { + public: + SavedState (Graphics& g); + + SavedState (SavedState&&); + SavedState& operator= (SavedState&&); + + SavedState (const SavedState&) = delete; + SavedState& operator= (const SavedState&) = delete; + + ~SavedState(); + + private: + Graphics* g = nullptr; + }; + + //============================================================================== Graphics (GraphicsContext& context, rive::Renderer& renderer) noexcept; + //============================================================================== + SavedState saveState(); + + //============================================================================== + void setColor (Color color); + Color getColor() const; + + void setColorGradient (ColorGradient gradient); + ColorGradient getColorGradient() const; + + void setOpacity (uint8 opacity); + uint8 getOpacity() const; + + void setStrokeJoin (StrokeJoin join); + StrokeJoin getStrokeJoin() const; + + void setStrokeCap (StrokeCap cap); + StrokeCap getStrokeCap() const; + + //============================================================================== + void drawLine (float x1, float y1, float x2, float y2, float thickness); + void drawLine (const Point& p1, const Point& p2, float thickness); + + //============================================================================== + void fillRect (float x, float y, float width, float height); + void fillRect (const Rectangle& r); + + void drawRect (float x, float y, float width, float height, float thickness); + void drawRect (const Rectangle& r, float thickness); + + //============================================================================== + void fillRoundedRect (float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight); + void fillRoundedRect (const Rectangle& r, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight); + void fillRoundedRect (float x, float y, float width, float height, float radius); + void fillRoundedRect (const Rectangle& r, float radius); + + void drawRoundedRect (float x, float y, float width, float height, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, float thickness); + void drawRoundedRect (const Rectangle& r, float radiusTopLeft, float radiusTopRight, float radiusBottomLeft, float radiusBottomRight, float thickness); + void drawRoundedRect (float x, float y, float width, float height, float radius, float thickness); + void drawRoundedRect (const Rectangle& r, float radius, float thickness); + + //============================================================================== rive::Factory* getFactory(); rive::Renderer* getRenderer(); private: + struct RenderOptions + { + RenderOptions() noexcept = default; + + bool isColor() const noexcept + { + return isCurrentBrushColor; + } + + Color getColor() const noexcept + { + return color; + } + + bool isColorGradient() const noexcept + { + return ! isCurrentBrushColor; + } + + ColorGradient getColorGradient() const noexcept + { + return gradient; + } + + StrokeJoin join = StrokeJoin::Miter; + StrokeCap cap = StrokeCap::Square; + Color color = 0xff000000; + ColorGradient gradient; + bool isCurrentBrushColor = true; + }; + + RenderOptions& currentRenderOptions(); + const RenderOptions& currentRenderOptions() const; + void restoreState(); + GraphicsContext& context; + + rive::Factory& factory; rive::Renderer& renderer; + + std::vector renderOptions; }; } // namespace juce diff --git a/modules/yup_graphics/graphics/yup_StrokeCap.h b/modules/yup_graphics/graphics/yup_StrokeCap.h new file mode 100644 index 00000000..84fa0f9f --- /dev/null +++ b/modules/yup_graphics/graphics/yup_StrokeCap.h @@ -0,0 +1,33 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +enum class StrokeCap : unsigned int +{ + Butt = 0, + Round = 1, + Square = 2 +}; + +} // namespace juce diff --git a/modules/yup_graphics/graphics/yup_StrokeJoin.h b/modules/yup_graphics/graphics/yup_StrokeJoin.h new file mode 100644 index 00000000..654630df --- /dev/null +++ b/modules/yup_graphics/graphics/yup_StrokeJoin.h @@ -0,0 +1,33 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +enum class StrokeJoin : unsigned int +{ + Miter = 0, + Round = 1, + Bevel = 2 +}; + +} // namespace juce diff --git a/modules/yup_graphics/primitives/yup_Path.h b/modules/yup_graphics/primitives/yup_Path.h new file mode 100644 index 00000000..30a94ddc --- /dev/null +++ b/modules/yup_graphics/primitives/yup_Path.h @@ -0,0 +1,27 @@ +/* + ============================================================================== + + This file is part of the YUP library. + Copyright (c) 2024 - kunitoki@gmail.com + + YUP is an open source library subject to open-source licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + to use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + YUP IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== + +} // namespace juce diff --git a/modules/yup_graphics/primitives/yup_Rectangle.h b/modules/yup_graphics/primitives/yup_Rectangle.h index 24c6b042..6e5e0431 100644 --- a/modules/yup_graphics/primitives/yup_Rectangle.h +++ b/modules/yup_graphics/primitives/yup_Rectangle.h @@ -26,6 +26,7 @@ template class JUCE_API Rectangle { public: + //============================================================================== constexpr Rectangle() noexcept = default; constexpr Rectangle (ValueType x, ValueType y, ValueType width, ValueType height) noexcept @@ -54,17 +55,19 @@ class JUCE_API Rectangle template >> constexpr Rectangle (const Rectangle& other) noexcept - : xy (other.getTopLeft().template to()) + : xy (other.getPosition().template to()) , size (other.getSize().template to()) { static_assert (std::numeric_limits::max() >= std::numeric_limits::max(), "Invalid narrow cast"); } + //============================================================================== constexpr Rectangle (const Rectangle& other) noexcept = default; constexpr Rectangle (Rectangle&& other) noexcept = default; constexpr Rectangle& operator=(const Rectangle& other) noexcept = default; constexpr Rectangle& operator=(Rectangle&& other) noexcept = default; + //============================================================================== constexpr ValueType getX() const noexcept { return xy.getX(); @@ -75,6 +78,7 @@ class JUCE_API Rectangle return xy.getY(); } + //============================================================================== constexpr ValueType getWidth() const noexcept { return size.getWidth(); @@ -85,18 +89,21 @@ class JUCE_API Rectangle return size.getHeight(); } - constexpr Point getTopLeft() const noexcept + //============================================================================== + constexpr Point getPosition() const noexcept { return xy; } - constexpr Size getSize() const noexcept + constexpr Rectangle& setPosition (const Point& newPosition) noexcept { - return size; + xy = newPosition; + + return *this; } template - constexpr Rectangle withPosition (const Point& newPosition) noexcept + constexpr Rectangle withPosition (const Point& newPosition) const noexcept { static_assert (std::numeric_limits::max() >= std::numeric_limits::max(), "Invalid narrow cast"); @@ -104,13 +111,84 @@ class JUCE_API Rectangle } template - constexpr Rectangle withSize (const Size& newSize) noexcept + constexpr Rectangle withPosition (T x, T y) const noexcept + { + static_assert (std::numeric_limits::max() >= std::numeric_limits::max(), "Invalid narrow cast"); + + return { static_cast (x), static_cast (y), size }; + } + + constexpr Rectangle withZeroPosition() const noexcept + { + return { 0, 0, size }; + } + + //============================================================================== + constexpr Size getSize() const noexcept + { + return size; + } + + constexpr Rectangle& setSize (const Size& newSize) noexcept + { + size = newSize; + + return *this; + } + + template + constexpr Rectangle withSize (const Size& newSize) const noexcept { static_assert (std::numeric_limits::max() >= std::numeric_limits::max(), "Invalid narrow cast"); return { xy, newSize.template to() }; } + template + constexpr Rectangle withSize (T width, T height) const noexcept + { + static_assert (std::numeric_limits::max() >= std::numeric_limits::max(), "Invalid narrow cast"); + + return { xy, static_cast (width), static_cast (height) }; + } + + template + constexpr auto withScaledSize (T scaleFactor) const noexcept + -> std::enable_if_t, Rectangle> + { + return withSize (size * scaleFactor); + } + + constexpr Rectangle withZeroSize() const noexcept + { + return { xy, 0, 0 }; + } + + //============================================================================== + constexpr Rectangle& setBounds (int x, int y, int w, int h) noexcept + { + xy = { x, y }; + size = { w, h }; + + return *this; + } + + //============================================================================== + constexpr Rectangle& setCentre (ValueType centreX, ValueType centreY) noexcept + { + xy = { centreX - size.getWidth() / static_cast (2), centreY - size.getHeight() / static_cast (2) }; + + return *this; + } + + constexpr Rectangle& setCentre (const Point centre) noexcept + { + setCentre (centre.getX(), centre.getY()); + + return *this; + } + + //============================================================================== constexpr Rectangle& translate (ValueType deltaX, ValueType deltaY) noexcept { xy.translate (deltaX, deltaY); @@ -128,12 +206,90 @@ class JUCE_API Rectangle return { xy.translated (delta), size }; } + //============================================================================== + constexpr Rectangle removeFromTop (ValueType delta) noexcept + { + const Rectangle result { xy, size.withHeight (jmax (0, delta)) }; + + xy = xy.withY (xy.getY() + delta); + size = size.withHeight (jmax (0, size.getHeight() - delta)); + + return result; + } + + constexpr Rectangle removeFromLeft (ValueType delta) noexcept + { + const Rectangle result { xy, size.withWidth (jmax (0, delta)) }; + + xy = xy.withX (xy.getX() + delta); + size = size.withWidth (jmax (0, size.getWidth() - delta)); + + return result; + } + + constexpr Rectangle removeFromBottom (ValueType delta) noexcept + { + const Rectangle result { xy.withY (jmax (0, xy.getY() + xy.getHeight() - delta)), size.withHeight (jmax (0, delta)) }; + + size = size.withHeight (jmax (0, size.getHeight() - delta)); + + return result; + } + + constexpr Rectangle removeFromRight (ValueType delta) noexcept + { + const Rectangle result { xy.withX (jmax (0, xy.getX() + xy.getWidth() - delta)), size.withWidth (jmax (0, delta)) }; + + size = size.withWidth (jmax (0, size.getWidth() - delta)); + + return result; + } + + //============================================================================== template constexpr Rectangle to() const noexcept { return { xy.template to(), size.template to() }; } + //============================================================================== + template + constexpr auto operator* (T scaleFactor) const noexcept + -> std::enable_if_t, Rectangle> + { + Rectangle r (*this); + r *= scaleFactor; + return r; + } + + template + constexpr auto operator*= (T scaleFactor) noexcept + -> std::enable_if_t, Rectangle&> + { + xy = { static_cast (xy.getX() * scaleFactor), static_cast (xy.getY() * scaleFactor) }; + size = { static_cast (size.getWidth() * scaleFactor), static_cast (size.getHeight() * scaleFactor) }; + return *this; + } + + template + constexpr auto operator/ (T scaleFactor) const noexcept + -> std::enable_if_t, Rectangle> + { + Rectangle r (*this); + r /= scaleFactor; + return r; + } + + template + constexpr auto operator/= (T scaleFactor) noexcept + -> std::enable_if_t, Rectangle&> + { + xy = { static_cast (xy.getX() / scaleFactor), static_cast (xy.getY() / scaleFactor) }; + size = { static_cast (size.getWidth() / scaleFactor), static_cast (size.getHeight() / scaleFactor) }; + return *this; + } + + //============================================================================== constexpr bool operator== (const Rectangle& other) const noexcept { return xy == other.xy && size == other.size; diff --git a/modules/yup_graphics/primitives/yup_Size.h b/modules/yup_graphics/primitives/yup_Size.h index eb25a121..28d90742 100644 --- a/modules/yup_graphics/primitives/yup_Size.h +++ b/modules/yup_graphics/primitives/yup_Size.h @@ -26,6 +26,7 @@ template class JUCE_API Size { public: + //============================================================================== constexpr Size() noexcept = default; constexpr Size (ValueType newWidth, ValueType newHeight) noexcept @@ -34,11 +35,13 @@ class JUCE_API Size { } + //============================================================================== constexpr Size (const Size& other) noexcept = default; constexpr Size (Size&& other) noexcept = default; constexpr Size& operator=(const Size& other) noexcept = default; constexpr Size& operator=(Size&& other) noexcept = default; + //============================================================================== constexpr ValueType getWidth() const noexcept { return width; @@ -49,6 +52,7 @@ class JUCE_API Size return height; } + //============================================================================== constexpr Size withWidth (ValueType newWidth) const noexcept { return { newWidth, height }; @@ -59,12 +63,47 @@ class JUCE_API Size return { width, newHeight }; } + //============================================================================== template constexpr Size to() const noexcept { return { static_cast (width), static_cast (height) }; } + //============================================================================== + template + constexpr auto operator* (T scaleFactor) const noexcept -> std::enable_if_t, Size> + { + Size r (*this); + r *= scaleFactor; + return r; + } + + template + constexpr auto operator*= (T scaleFactor) noexcept -> std::enable_if_t, Size&> + { + width = static_cast (width * scaleFactor); + height = static_cast (height * scaleFactor); + return *this; + } + + template + constexpr auto operator/ (T scaleFactor) const noexcept -> std::enable_if_t, Size> + { + Size r (*this); + r /= scaleFactor; + return r; + } + + template + constexpr auto operator/= (T scaleFactor) noexcept -> std::enable_if_t, Size&> + { + width = static_cast (width / scaleFactor); + height = static_cast (height / scaleFactor); + return *this; + } + + //============================================================================== constexpr bool operator== (const Size& other) const noexcept { return width == other.width && height == other.height; diff --git a/modules/yup_graphics/yup_graphics.h b/modules/yup_graphics/yup_graphics.h index 22465e16..e557b7d3 100644 --- a/modules/yup_graphics/yup_graphics.h +++ b/modules/yup_graphics/yup_graphics.h @@ -61,5 +61,10 @@ #include "primitives/yup_Size.h" #include "primitives/yup_Point.h" #include "primitives/yup_Rectangle.h" +#include "primitives/yup_Path.h" +#include "graphics/yup_Color.h" +#include "graphics/yup_ColorGradient.h" +#include "graphics/yup_StrokeJoin.h" +#include "graphics/yup_StrokeCap.h" #include "graphics/yup_Graphics.h" #include "context/yup_GraphicsContext.h" diff --git a/modules/yup_gui/component/yup_Component.cpp b/modules/yup_gui/component/yup_Component.cpp index 8bf8ccfa..ef1d6fff 100644 --- a/modules/yup_gui/component/yup_Component.cpp +++ b/modules/yup_gui/component/yup_Component.cpp @@ -88,10 +88,16 @@ String Component::getTitle() const void Component::setSize (const Size& newSize) { - boundsInParent = boundsInParent.withSize (newSize); - if (options.onDesktop) + { + boundsInParent = boundsInParent.withSize (newSize * getScaleDpi()); + native->setSize (newSize); + } + else + { + boundsInParent = boundsInParent.withSize (newSize); + } resized(); } @@ -124,7 +130,7 @@ int Component::getHeight() const Point Component::getPosition() const { - return boundsInParent.getTopLeft().to(); + return boundsInParent.getPosition().to(); } int Component::getX() const @@ -139,10 +145,16 @@ int Component::getY() const void Component::setBounds (const Rectangle& newBounds) { - boundsInParent = newBounds.to(); - if (options.onDesktop) + { + boundsInParent = newBounds.withScaledSize (getScaleDpi()).to(); + native->setBounds (newBounds); + } + else + { + boundsInParent = newBounds.to(); + } resized(); } @@ -152,6 +164,11 @@ Rectangle Component::getBounds() const return boundsInParent.to(); } +Rectangle Component::getLocalBounds() const +{ + return boundsInParent.withZeroPosition().to(); +} + void Component::resized() { } @@ -276,6 +293,20 @@ void Component::addChildComponent (Component* component) children.addIfNotAlreadyThere (component); } +void Component::addAndMakeVisible (Component& component) +{ + addChildComponent (component); + + component.setVisible (true); +} + +void Component::addAndMakeVisible (Component* component) +{ + addChildComponent (component); + + component->setVisible (true); +} + void Component::removeChildComponent (Component& component) { component.parentComponent = nullptr; @@ -293,6 +324,7 @@ void Component::removeChildComponent (Component* component) //============================================================================== void Component::paint (Graphics& g, float frameRate) {} +void Component::paintOverChildren (Graphics& g, float frameRate) {} //============================================================================== @@ -311,7 +343,18 @@ void Component::userTriedToCloseWindow() {} void Component::internalPaint (Graphics& g, float frameRate) { + if (! isVisible()) + return; + paint (g, frameRate); + + for (auto child : children) + { + if (child->isVisible()) + child->internalPaint (g, frameRate); + } + + paintOverChildren (g, frameRate); } void Component::internalMouseDown (const MouseEvent& event) @@ -344,8 +387,13 @@ void Component::internalKeyUp (const KeyPress& keys, double x, double y) keyUp (keys, x, y); } -void Component::internalResized() +void Component::internalResized (int width, int height) { + if (options.onDesktop) + boundsInParent = boundsInParent.withSize (Size (width, height) * getScaleDpi()); + else + boundsInParent = boundsInParent.withSize (width, height); + resized(); } diff --git a/modules/yup_gui/component/yup_Component.h b/modules/yup_gui/component/yup_Component.h index f9890887..d46f0c05 100644 --- a/modules/yup_gui/component/yup_Component.h +++ b/modules/yup_gui/component/yup_Component.h @@ -56,6 +56,7 @@ class JUCE_API Component virtual void setBounds (const Rectangle& newBounds); Rectangle getBounds() const; + Rectangle getLocalBounds() const; virtual void resized(); @@ -87,11 +88,15 @@ class JUCE_API Component void addChildComponent (Component& component); void addChildComponent (Component* component); + void addAndMakeVisible (Component& component); + void addAndMakeVisible (Component* component); + void removeChildComponent (Component& component); void removeChildComponent (Component* component); //============================================================================== virtual void paint (Graphics& g, float frameRate); + virtual void paintOverChildren (Graphics& g, float frameRate); //============================================================================== virtual void mouseDown (const MouseEvent& event); @@ -111,7 +116,7 @@ class JUCE_API Component void internalMouseUp (const MouseEvent& event); void internalKeyDown (const KeyPress& keys, double x, double y); void internalKeyUp (const KeyPress& keys, double x, double y); - void internalResized(); + void internalResized (int width, int height); void internalUserTriedToCloseWindow(); friend class ComponentNative; diff --git a/modules/yup_gui/component/yup_ComponentNative.cpp b/modules/yup_gui/component/yup_ComponentNative.cpp index 8df25f20..680db642 100644 --- a/modules/yup_gui/component/yup_ComponentNative.cpp +++ b/modules/yup_gui/component/yup_ComponentNative.cpp @@ -78,7 +78,7 @@ void ComponentNative::handleKeyUp (const KeyPress& keys, double x, double y) void ComponentNative::handleResized (int width, int height) { - component.internalResized(); + component.internalResized (width, height); } void ComponentNative::handleUserTriedToCloseWindow() diff --git a/modules/yup_gui/native/yup_Windowing_glfw.cpp b/modules/yup_gui/native/yup_Windowing_glfw.cpp index 5f3d75b9..4a4cd308 100644 --- a/modules/yup_gui/native/yup_Windowing_glfw.cpp +++ b/modules/yup_gui/native/yup_Windowing_glfw.cpp @@ -503,7 +503,7 @@ void juce_glfwWindowSize (GLFWwindow* window, int width, int height) { auto* component = static_cast (glfwGetWindowUserPointer (window)); - component->handleResized(width, height); + component->handleResized (width, height); } //==============================================================================