blob: 4f192b52215b744d107a24e91e5c058e7b0a72a8 [file]
#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