| /* |
| * Copyright 2026 Rive |
| */ |
| |
| #include "gm.hpp" |
| #include "gmutils.hpp" |
| #include "rive/math/math_types.hpp" |
| #include "rive/renderer.hpp" |
| #include "common/rand.hpp" |
| |
| using namespace rivegm; |
| using namespace rive; |
| |
| namespace |
| { |
| |
| static void drawStripedBackground(rive::Renderer* renderer, int32_t dim) |
| { |
| { |
| Paint p; |
| p->color(0xff000000); |
| renderer->drawPath( |
| PathBuilder::Rect({0.0f, 0.0f, float(dim), float(dim)}), |
| p); |
| } |
| { |
| Paint p; |
| p->color(0xff2c1642); |
| p->feather(10.0f); |
| auto yStep = 40.0f; |
| for (auto y = 20.0f; y < float(dim); y++) |
| { |
| auto path = PathBuilder::Rect({ |
| -100.0f, |
| y, |
| float(dim) + 100.0f, |
| y + yStep * 0.3f, |
| }); |
| path->fillRule(FillRule::clockwise); |
| renderer->drawPath(path, p); |
| y += yStep; |
| yStep *= 1.12f; |
| } |
| } |
| } |
| |
| class ClipStrokeGM : public GM |
| { |
| public: |
| static constexpr int32_t SectionSize = 400; |
| static constexpr int32_t Spacing = 50; |
| static constexpr int32_t RoundRadius = 50; |
| static constexpr int32_t SectionCount = 3; |
| static constexpr int32_t ImageDim = SectionCount * SectionSize; |
| |
| ClipStrokeGM() : GM(ImageDim, ImageDim) {} |
| |
| void onDraw(rive::Renderer* renderer) override |
| { |
| drawStripedBackground(renderer, ImageDim); |
| |
| constexpr uint32_t Colors[SectionCount][SectionCount] = { |
| { |
| 0xff881177, |
| 0xffeedd00, |
| 0xff00bbcc, |
| }, |
| { |
| 0xffaa3355, |
| 0xff99dd55, |
| 0xffee9944, |
| }, |
| { |
| 0xffcc6666, |
| 0xff3366bb, |
| 0xff22ccbb, |
| }, |
| }; |
| for (auto yIndex = 0; yIndex < 3; yIndex++) |
| { |
| for (auto xIndex = 0; xIndex < 3; xIndex++) |
| { |
| renderer->save(); |
| { |
| const auto box = AABB{ |
| float(SectionSize * xIndex + Spacing / 2), |
| float(SectionSize * yIndex + Spacing / 2), |
| float(SectionSize * (xIndex + 1) - Spacing / 2), |
| float(SectionSize * (yIndex + 1) - Spacing / 2), |
| }; |
| auto clipper = PathBuilder::RRect(box, |
| float(RoundRadius), |
| float(RoundRadius)); |
| renderer->clipStroke( |
| clipper, |
| {.thickness = float(yIndex * 3 + xIndex + 1) * 3.0f}); |
| |
| Paint p; |
| p->color(Colors[xIndex][yIndex]); |
| |
| const auto dim = float(SectionCount * SectionSize) * 0.5f; |
| auto path = PathBuilder::Circle(dim, dim, dim * 0.8f); |
| path->fillRule(FillRule::clockwise); |
| renderer->drawPath(path, p); |
| } |
| renderer->restore(); |
| } |
| } |
| } |
| }; |
| |
| enum class NestType |
| { |
| pathInStroke, |
| strokeInPath, |
| strokeInStroke, |
| }; |
| |
| template <NestType N> class ClipStrokeNestedGM : public GM |
| { |
| public: |
| static constexpr int32_t SectionSize = 400; |
| static constexpr int32_t Spacing = 50; |
| static constexpr int32_t RoundRadius = 50; |
| static constexpr int32_t SectionCount = 3; |
| static constexpr int32_t ImageDim = SectionCount * SectionSize; |
| |
| ClipStrokeNestedGM() : GM(ImageDim, ImageDim) {} |
| |
| void onDraw(rive::Renderer* renderer) override |
| { |
| { |
| Paint p; |
| p->color(0xff000000); |
| renderer->drawPath( |
| PathBuilder::Rect( |
| {0.0f, 0.0f, float(ImageDim), float(ImageDim)}), |
| p); |
| } |
| { |
| Paint p; |
| p->color(0xff2c1642); |
| p->feather(10.0f); |
| auto yStep = 40.0f; |
| for (auto y = 20.0f; y < float(SectionCount * SectionSize); y++) |
| { |
| auto path = PathBuilder::Rect({ |
| -100.0f, |
| y, |
| float(ImageDim) + 100.0f, |
| y + yStep * 0.3f, |
| }); |
| path->fillRule(FillRule::clockwise); |
| renderer->drawPath(path, p); |
| y += yStep; |
| yStep *= 1.12f; |
| } |
| } |
| constexpr uint32_t Colors[] = { |
| 0xffeedd00, |
| 0xffee9944, |
| 0xffcc6666, |
| 0xffaa3355, |
| 0xff881177, |
| 0xff3366bb, |
| 0xff00bbcc, |
| 0xff22ccbb, |
| 0xff99dd55, |
| }; |
| |
| const auto dim = float(SectionCount * SectionSize) * 0.5f; |
| auto outerPath = PathBuilder::Circle(dim, dim, dim * 0.8f); |
| |
| constexpr auto CircleDiameter = 100; |
| constexpr auto CircleSpacing = 20; |
| constexpr auto CircleRadius = float(CircleDiameter) * 0.5f; |
| auto innerCircle = |
| PathBuilder::Circle(CircleRadius, CircleRadius, CircleRadius); |
| |
| renderer->save(); |
| { |
| if constexpr (N == NestType::strokeInPath) |
| { |
| renderer->clipPath(outerPath); |
| } |
| else |
| { |
| renderer->clipStroke(outerPath, {.thickness = 200.0f}); |
| } |
| |
| for (auto yIndex = 0; yIndex < 3; yIndex++) |
| { |
| for (auto xIndex = 0; xIndex < 3; xIndex++) |
| { |
| renderer->save(); |
| { |
| const auto box = AABB{ |
| float(SectionSize * xIndex + Spacing / 2), |
| float(SectionSize * yIndex + Spacing / 2), |
| float(SectionSize * (xIndex + 1) - Spacing / 2), |
| float(SectionSize * (yIndex + 1) - Spacing / 2), |
| }; |
| auto clipper = PathBuilder::RRect(box, |
| float(RoundRadius), |
| float(RoundRadius)); |
| |
| if constexpr (N == NestType::pathInStroke) |
| { |
| renderer->clipPath(clipper); |
| } |
| else |
| { |
| renderer->clipStroke(clipper, {.thickness = 20.0f}); |
| } |
| |
| auto colorIndex = 0u; |
| for (auto y = 5; y < ImageDim; |
| y += CircleDiameter + CircleSpacing) |
| { |
| for (auto x = 5; x < ImageDim; |
| x += CircleDiameter + CircleSpacing) |
| { |
| Paint p; |
| p->color( |
| Colors[colorIndex % std::size(Colors)]); |
| colorIndex++; |
| renderer->save(); |
| { |
| renderer->translate(x, y); |
| renderer->drawPath(innerCircle, p); |
| } |
| renderer->restore(); |
| } |
| } |
| } |
| |
| renderer->restore(); |
| } |
| } |
| } |
| renderer->restore(); |
| } |
| }; |
| |
| class ClipStrokeOpenGM : public GM |
| { |
| public: |
| static constexpr int32_t SectionSize = 400; |
| static constexpr int32_t SectionCount = 3; |
| static constexpr int32_t ImageDim = SectionCount * SectionSize; |
| static constexpr int32_t StrokeWidth = 50.0f; |
| |
| ClipStrokeOpenGM() : GM(ImageDim, ImageDim) {} |
| |
| void onDraw(rive::Renderer* renderer) override |
| { |
| Path clipper = |
| PathBuilder() |
| .addPolygon( |
| std::vector{Vec2D{StrokeWidth, StrokeWidth}, |
| Vec2D{SectionSize - StrokeWidth, StrokeWidth}, |
| Vec2D{SectionSize - StrokeWidth, |
| SectionSize - StrokeWidth}, |
| Vec2D{StrokeWidth, SectionSize - StrokeWidth}}, |
| false) |
| .detach(); |
| |
| drawStripedBackground(renderer, ImageDim); |
| |
| constexpr uint32_t Colors[SectionCount][SectionCount] = { |
| { |
| 0xff881177, |
| 0xffeedd00, |
| 0xff00bbcc, |
| }, |
| { |
| 0xffaa3355, |
| 0xff99dd55, |
| 0xffee9944, |
| }, |
| { |
| 0xffcc6666, |
| 0xff3366bb, |
| 0xff22ccbb, |
| }, |
| }; |
| for (auto yIndex = 0; yIndex < SectionCount; yIndex++) |
| { |
| for (auto xIndex = 0; xIndex < SectionCount; xIndex++) |
| { |
| renderer->save(); |
| { |
| const auto tX = float(SectionSize * xIndex); |
| const auto tY = float(SectionSize * yIndex); |
| renderer->translate(tX, tY); |
| renderer->clipStroke(clipper, |
| { |
| .thickness = StrokeWidth, |
| .join = StrokeJoin(xIndex), |
| .cap = StrokeCap(yIndex), |
| }); |
| |
| Paint p; |
| p->color(Colors[xIndex][yIndex]); |
| |
| renderer->translate(-tX, -tY); |
| |
| const auto dim = float(SectionCount * SectionSize) * 0.5f; |
| auto path = PathBuilder::Circle(dim, dim, dim * 5.0f); |
| path->fillRule(FillRule::clockwise); |
| renderer->drawPath(path, p); |
| } |
| renderer->restore(); |
| } |
| } |
| } |
| }; |
| } // namespace |
| |
| GMREGISTER(clip_stroke_basic, return (new ClipStrokeGM())) |
| GMREGISTER(clip_stroke_nested_a, |
| return (new ClipStrokeNestedGM<NestType::pathInStroke>())) |
| GMREGISTER(clip_stroke_nested_b, |
| return (new ClipStrokeNestedGM<NestType::strokeInPath>())) |
| GMREGISTER(clip_stroke_nested_c, |
| return (new ClipStrokeNestedGM<NestType::strokeInStroke>())) |
| GMREGISTER(clip_stroke_open, return (new ClipStrokeOpenGM())) |
| |
| DEF_SIMPLE_GM(clip_stroke_position, 768, 768, renderer) |
| { |
| drawStripedBackground(renderer, 768); |
| |
| Path path = PathBuilder::Circle(128.0f, 128.0f, 100.0f); |
| path->fillRule(FillRule::clockwise); |
| |
| Path otherClip = PathBuilder::Polygon({Vec2D{-30.0f, 226.0f}, |
| Vec2D{226.0f, -30.0f}, |
| Vec2D{286.0f, 30.0f}, |
| Vec2D{30.0f, 286.0f}}, |
| true, |
| FillRule::clockwise); |
| |
| Path fillA = |
| PathBuilder::RRect({30.0f, 30.0f, 226.0f, 226.0f}, 50.0f, 50.0f); |
| Path fillB = |
| PathBuilder::RRect({40.0f, 40.0f, 216.0f, 216.0f}, 50.0f, 50.0f); |
| |
| for (auto nestIndex = 0; nestIndex < 3; nestIndex++) |
| { |
| renderer->save(); |
| |
| renderer->translate(0.0f, nestIndex * 256.0f); |
| |
| for (auto i = 0; i < 3; i++) |
| { |
| renderer->save(); |
| { |
| if (nestIndex == 1) |
| { |
| // nest 1 is "positioned clip stroke inside of fill" |
| renderer->clipPath(otherClip); |
| } |
| |
| renderer->clipStroke( |
| path, |
| {.thickness = 20.0f, .position = StrokePosition(i)}); |
| |
| if (nestIndex == 2) |
| { |
| // nest 1 is "fill inside of positioned clip stroke" |
| renderer->clipPath(otherClip); |
| } |
| |
| Paint p; |
| p->color(0xffaa3355); |
| p->style(RenderPaintStyle::fill); |
| renderer->drawPath(fillA, p); |
| p->color(0xff00bbcc); |
| renderer->drawPath(fillB, p); |
| } |
| renderer->restore(); |
| |
| renderer->translate(256.0f, 0.0f); |
| } |
| |
| renderer->restore(); |
| } |
| } |