| #ifndef _RIVE_PATH_HPP_ |
| #define _RIVE_PATH_HPP_ |
| #include "rive/command_path.hpp" |
| #include "rive/generated/shapes/path_base.hpp" |
| #include "rive/math/mat2d.hpp" |
| #include "rive/math/raw_path.hpp" |
| #include "rive/shapes/shape_paint_container.hpp" |
| #include <vector> |
| |
| namespace rive |
| { |
| class Shape; |
| class PathVertex; |
| class RenderPathDeformer; |
| |
| #ifdef ENABLE_QUERY_FLAT_VERTICES |
| /// Optionally compiled in for tools that need to compute per frame world |
| /// transformed path vertices. These should not be used at runtime as it's |
| /// not optimized for performance (it does a lot of memory allocation). |
| |
| /// A flattened path is composed of only linear |
| /// and cubic vertices. No corner vertices and it's entirely in world space. |
| /// This is helpful for getting a close to identical representation of the |
| /// vertices used to issue the high level path draw commands. |
| class FlattenedPath |
| { |
| private: |
| std::vector<PathVertex*> m_Vertices; |
| |
| public: |
| ~FlattenedPath(); |
| |
| const std::vector<PathVertex*>& vertices() const { return m_Vertices; } |
| void addVertex(PathVertex* vertex, const Mat2D& transform); |
| }; |
| #endif |
| |
| class Path : public PathBase |
| { |
| protected: |
| Shape* m_Shape = nullptr; |
| std::vector<PathVertex*> m_Vertices; |
| bool m_deferredPathDirt = false; |
| PathFlags m_pathFlags = PathFlags::none; |
| RawPath m_rawPath; |
| RenderPathDeformer* deformer() const; |
| void isHoleChanged() override; |
| |
| public: |
| static float computeIdealControlPointDistance(const Vec2D& toPrev, |
| const Vec2D& toNext, |
| float radius); |
| |
| static void addRoundedRect(RawPath& rawPath, |
| const AABB& bounds, |
| float topLeft, |
| float topRight, |
| float bottomRight, |
| float bottomLeft); |
| |
| Shape* shape() const { return m_Shape; } |
| StatusCode onAddedClean(CoreContext* context) override; |
| void buildDependencies() override; |
| virtual const Mat2D& pathTransform() const; |
| bool collapse(bool value) override; |
| const RawPath& rawPath() const { return m_rawPath; } |
| // True while m_rawPath has yet to be rebuilt for pending changes: the Path |
| // dirt is still queued, or the build was deferred. Layout runs before |
| // Path::update in the update pass, so a measure can land here first and |
| // must build its own copy rather than read a stale/empty rawPath. |
| bool needsPathBuild() const |
| { |
| return hasDirt(ComponentDirt::Path) || m_deferredPathDirt; |
| } |
| // Bounds in this path's own space derived from its properties rather than |
| // its built geometry, for callers that run before update() has positioned |
| // vertices. A ParametricPath knows its box up front; anything vertex-driven |
| // returns false and must be measured from geometry. |
| virtual bool tryPropertyBounds(AABB& result) const { return false; } |
| void update(ComponentDirt value) override; |
| |
| void addFlags(PathFlags flags); |
| bool isFlagged(PathFlags flags) const; |
| |
| bool canDeferPathUpdate(); |
| void addVertex(PathVertex* vertex); |
| void popVertex(); |
| |
| virtual void markPathDirty(bool sendToLayout = true); |
| virtual bool isPathClosed() const { return true; } |
| void onDirty(ComponentDirt dirt) override; |
| inline bool isHidden() const { return (pathFlags() & 0x1) == 0x1; } |
| #ifdef ENABLE_QUERY_FLAT_VERTICES |
| FlattenedPath* makeFlat(bool transformToParent); |
| #endif |
| |
| #ifdef TESTING |
| std::vector<PathVertex*>& vertices() { return m_Vertices; } |
| #endif |
| |
| #ifdef WITH_RIVE_EDITOR |
| /// Editor-only: sort `m_Vertices` by sibling FractionalIndex |
| /// (`Component::childOrder().compareTo(...)`). The runtime `.riv` import |
| /// path adds vertices in file declaration order — which the |
| /// exporter writes in the authored order, so the resulting |
| /// path geometry is correct without any sorting. Coop |
| /// delivers vertices in server-batch arrival order, which is |
| /// arbitrary; without this resort the rendered path zigzags |
| /// across the children, producing twisted / self-intersecting |
| /// shapes (the visible "broken pose" we see in the editor's |
| /// pump-driven render). Mirrors Dart's |
| /// `ContainerComponent.sortChildren` / |
| /// `Path._sortVertices` flow that runs as part of cleanDirt |
| /// (rive_file.dart:607-613). |
| void sortVerticesForEditor(); |
| /// Idempotent add — no-op if `vertex` is already on |
| /// `m_Vertices`. Called by `PathVertex::editorParentChanged` |
| /// when its parent transitions to this Path (initial hydration |
| /// or re-parent target). The dedupe matters for cross-batch |
| /// retries and mixed-mode (`.riv`-imported + coop-edited) files. |
| void addVertexForEditor(PathVertex* vertex); |
| /// Remove `vertex` from `m_Vertices` if present. Called by |
| /// `PathVertex::editorParentChanged` when its parent transitions |
| /// AWAY from this Path (re-parent source, parent-of-parent |
| /// deletion, or child deletion). |
| void removeVertexForEditor(PathVertex* vertex); |
| #endif |
| |
| void buildPath(RawPath&) const; |
| AABB localBounds() const override; |
| }; |
| } // namespace rive |
| |
| #endif |