| /* | |
| * Copyright 2011 Google Inc. | |
| * Copyright 2022 Rive | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm.hpp" | |
| #include "gmutils.hpp" | |
| #include "rive/renderer.hpp" | |
| using namespace rivegm; | |
| using namespace rive; | |
| static constexpr int kPtsCount = 3; | |
| static constexpr Vec2D kPts[kPtsCount] = { | |
| {40, 40}, | |
| {80, 40}, | |
| {120, 40}, | |
| }; | |
| static Path make_path_move() | |
| { | |
| PathBuilder builder; | |
| for (Vec2D p : kPts) | |
| { | |
| builder.moveTo(p.x, p.y); | |
| } | |
| return builder.detach(); | |
| } | |
| static Path make_path_move_close() | |
| { | |
| PathBuilder builder; | |
| for (Vec2D p : kPts) | |
| { | |
| builder.moveTo(p.x, p.y).close(); | |
| } | |
| return builder.detach(); | |
| } | |
| static Path make_path_move_line() | |
| { | |
| PathBuilder builder; | |
| for (Vec2D p : kPts) | |
| { | |
| builder.moveTo(p.x, p.y).lineTo(p.x, p.y); | |
| } | |
| return builder.detach(); | |
| } | |
| static Path make_path_move_mix() | |
| { | |
| return PathBuilder() | |
| .moveTo(kPts[0].x, kPts[0].y) | |
| .moveTo(kPts[1].x, kPts[1].y) | |
| .close() | |
| .moveTo(kPts[2].x, kPts[2].y) | |
| .lineTo(kPts[2].x, kPts[2].y) | |
| .detach(); | |
| } | |
| static Path make_path_move_addpath() | |
| { | |
| Path inner = PathBuilder() | |
| .moveTo(kPts[1].x, kPts[1].y) | |
| .lineTo(kPts[1].x, kPts[1].y) | |
| .detach(); | |
| Path path; | |
| path->moveTo(kPts[0].x, kPts[0].y); | |
| path->addRenderPath(inner.get(), Mat2D()); | |
| path->moveTo(kPts[2].x, kPts[2].y); | |
| return path; | |
| } | |
| static Path make_path_addpath_line() | |
| { | |
| Path inner = PathBuilder() | |
| .moveTo(kPts[0].x, kPts[0].y) | |
| .lineTo(kPts[0].x, kPts[0].y) | |
| .detach(); | |
| Path path; | |
| path->addRenderPath(inner.get(), Mat2D()); | |
| path->moveTo(kPts[1].x, kPts[1].y); | |
| path->moveTo(kPts[2].x, kPts[2].y); | |
| path->lineTo(kPts[2].x, kPts[2].y); | |
| return path; | |
| } | |
| class EmptyStrokeGM : public GM | |
| { | |
| public: | |
| struct Options | |
| { | |
| bool stroke = false, feather = false; | |
| }; | |
| EmptyStrokeGM(Options options) : GM(180, 780), m_options(options) {} | |
| private: | |
| void onDraw(Renderer* renderer) override | |
| { | |
| // Every subpath below is degenerate, so they should render as follows: | |
| // - open: a cap | |
| // - closed: a join | |
| // - isolated moveTo: caps the same as a zero-length lineTo. | |
| // Whether that covers the red dot underneath depends on the cap/join | |
| // style. | |
| static constexpr Path (*kProcs[])() = { | |
| make_path_move, // isolated moves | |
| make_path_move_close, // closed, so joins rather than caps | |
| make_path_move_line, // zero-length lines | |
| make_path_move_mix, // a move, a close and a line, left to right | |
| make_path_move_addpath, // addPath must not drop the caps around it | |
| make_path_addpath_line, // nor the ones that follow it | |
| }; | |
| Paint paint; | |
| if (m_options.stroke) | |
| { | |
| paint->style(RenderPaintStyle::stroke); | |
| paint->thickness(21); | |
| } | |
| if (m_options.feather) | |
| { | |
| paint->feather(21); | |
| } | |
| Paint dotPaint; | |
| dotPaint->color(0xffff0000); | |
| dotPaint->style(RenderPaintStyle::fill); | |
| for (size_t j = 0; j < 3; ++j) | |
| { | |
| paint->cap(static_cast<StrokeCap>((3 - j) % 3)); | |
| paint->join(static_cast<StrokeJoin>(j)); | |
| for (auto proc : kProcs) | |
| { | |
| for (int i = 0; i < 3; ++i) | |
| { | |
| renderer->drawPath( | |
| PathBuilder::Oval( | |
| {kPts[i].x - 3.5f, | |
| kPts[i].y - 3.5f, | |
| kPts[i].x + 3.5f, | |
| kPts[i].y + 3.5f}), | |
| dotPaint); | |
| } | |
| Path path = proc(); | |
| path->fillRule(FillRule::clockwise); | |
| renderer->drawPath(path, paint); | |
| renderer->translate(0, 40); | |
| } | |
| } | |
| } | |
| Options m_options; | |
| }; | |
| GMREGISTER(emptystroke, | |
| return new EmptyStrokeGM({.stroke = true, .feather = false});) | |
| GMREGISTER(emptyfeather, | |
| return new EmptyStrokeGM({.stroke = false, .feather = true});) | |
| GMREGISTER(emptystrokefeather, | |
| return new EmptyStrokeGM({.stroke = true, .feather = true});) |