| #ifndef _RIVE_CLIPPING_SHAPE_HPP_ | |
| #define _RIVE_CLIPPING_SHAPE_HPP_ | |
| #include "rive/renderer.hpp" | |
| #include "rive/generated/shapes/clipping_shape_base.hpp" | |
| #include "rive/shapes/shape_paint_path.hpp" | |
| #include "rive/drawable.hpp" | |
| #ifdef WITH_RIVE_EDITOR | |
| #include "rive/editor/object_arena.hpp" | |
| #endif | |
| #include <vector> | |
| namespace rive | |
| { | |
| class Shape; | |
| class Node; | |
| class RenderPath; | |
| class ClippingShapeStart; | |
| class ClippingShapeEnd; | |
| class ClippingShapeOperation | |
| { | |
| public: | |
| virtual ~ClippingShapeOperation() = default; | |
| virtual void draw(Renderer* renderer, bool needsSaveOperation) = 0; | |
| virtual int emptyClipCount() = 0; | |
| void clippingShape(ClippingShape* shape) { m_clippingShape = shape; } | |
| virtual bool isStart() { return false; } | |
| virtual bool isVisible() { return true; } | |
| protected: | |
| ClippingShape* m_clippingShape = nullptr; | |
| }; | |
| class ClippingShapeStart : public ClippingShapeOperation | |
| { | |
| public: | |
| void draw(Renderer* renderer, bool needsSaveOperation) override; | |
| int emptyClipCount() override; | |
| bool isStart() override { return true; } | |
| bool isVisible() override; | |
| }; | |
| class ClippingShapeEnd : public ClippingShapeOperation | |
| { | |
| void draw(Renderer* renderer, bool needsSaveOperation) override; | |
| int emptyClipCount() override; | |
| }; | |
| class ClippingShapeProxyDrawable : public Drawable | |
| { | |
| public: | |
| ClippingShapeProxyDrawable(ClippingShapeOperation* operation) | |
| : m_clippingShapeOperation(operation) | |
| {} | |
| void draw(Renderer* renderer) override | |
| { | |
| m_clippingShapeOperation->draw(renderer, needsSaveOperation()); | |
| } | |
| int emptyClipCount() override | |
| { | |
| return m_clippingShapeOperation->emptyClipCount(); | |
| } | |
| bool isHidden() const override { return false; } | |
| Drawable* hittableComponent() override { return nullptr; }; | |
| bool isTargetOpaque() override { return false; }; | |
| Core* hitTest(HitInfo*, const Mat2D&) override { return nullptr; } | |
| void operation(ClippingShapeOperation* value) | |
| { | |
| m_clippingShapeOperation = value; | |
| } | |
| bool isProxy() override { return true; } | |
| bool isClipStart() override { return m_clippingShapeOperation->isStart(); } | |
| bool isClipEnd() override { return !m_clippingShapeOperation->isStart(); } | |
| bool willClip() override { return m_clippingShapeOperation->isVisible(); } | |
| private: | |
| ClippingShapeOperation* m_clippingShapeOperation = nullptr; | |
| }; | |
| class ClippingShape : public ClippingShapeBase | |
| { | |
| private: | |
| std::vector<Shape*> m_Shapes; | |
| std::vector<ClippingShapeProxyDrawable*> m_proxyDrawables; | |
| std::vector<ClippingShapeProxyDrawable*> m_pooledProxyDrawables; | |
| Node* m_Source = nullptr; | |
| #ifdef WITH_RIVE_EDITOR | |
| // Slice 6 Phase E dual-storage. See targeted_constraint.hpp. | |
| CoreHandle m_SourceHandle; | |
| #endif | |
| public: | |
| ~ClippingShape(); | |
| #ifdef WITH_RIVE_EDITOR | |
| // Body in editor_native/native/src/editor/shapes/ | |
| // clipping_shape_editor.cpp. | |
| Node* source() const; | |
| void setSourceForEditor(Node* n); | |
| #else | |
| inline Node* source() const { return m_Source; } | |
| #endif | |
| const std::vector<Shape*>& shapes() const { return m_Shapes; } | |
| StatusCode onAddedClean(CoreContext* context) override; | |
| StatusCode onAddedDirty(CoreContext* context) override; | |
| void buildDependencies() override; | |
| void update(ComponentDirt value) override; | |
| void isVisibleChanged() override; | |
| #ifdef WITH_RIVE_EDITOR | |
| // Editor-only property-change hooks. Bodies in | |
| // `editor_native/native/src/editor/shapes/clipping_shape_editor.cpp`. | |
| // - `sourceIdChanged`: resolves new sourceId to Node, swaps source | |
| // storage, clears cached clip path, dirties the clipping pipeline. | |
| // The dependent-shape rebuild rides on the next finalizeBatch's | |
| // Pass B (which re-runs `buildDependencies` for every Component). | |
| // - `fillRuleChanged`: dart parity — propagates Clipping dirt down | |
| // the parent's subtree (re-clips affected drawables) and Path | |
| // dirt on this so the next update recomposes the clip path. | |
| void sourceIdChanged() override; | |
| void fillRuleChanged() override; | |
| // Stage A v2 — typed-list maintenance on add / remove / reparent. | |
| // `from`'s subtree drawables: removeClippingShapeForEditor(this). | |
| // `to`'s subtree drawables: addClippingShape(this). Mirrors | |
| // `onAddedClean`'s walk (which only fires once at hydration). | |
| // Body in `component_parent_editor.cpp` — same file as the rest | |
| // of the typed-list editorParentChanged overrides. | |
| void editorParentChanged(ContainerComponent* from, ContainerComponent* to) | |
| override; | |
| #endif | |
| ShapePaintPath* path() { return m_clipPath; } | |
| void resetDrawables() | |
| { | |
| m_pooledProxyDrawables.insert( | |
| m_pooledProxyDrawables.end(), | |
| m_proxyDrawables.begin(), | |
| m_proxyDrawables.end()); | |
| m_proxyDrawables.clear(); | |
| } | |
| ClippingShapeProxyDrawable* createProxyDrawable( | |
| ClippingShapeOperation* operation) | |
| { | |
| ClippingShapeProxyDrawable* drawable; | |
| if (m_pooledProxyDrawables.size() > 0) | |
| { | |
| drawable = m_pooledProxyDrawables.back(); | |
| drawable->operation(operation); | |
| drawable->needsSaveOperation(true); | |
| m_pooledProxyDrawables.pop_back(); | |
| } | |
| else | |
| { | |
| drawable = new ClippingShapeProxyDrawable(operation); | |
| } | |
| m_proxyDrawables.push_back(drawable); | |
| return drawable; | |
| } | |
| ClippingShapeStart clipStart; | |
| ClippingShapeEnd clipEnd; | |
| private: | |
| // Folds a shape's fills into m_path, preferring post-effect paths. False | |
| // when it contributed nothing. | |
| bool addFillPaths(Shape* shape); | |
| ShapePaintPath m_path; | |
| ShapePaintPath* m_clipPath = nullptr; | |
| }; | |
| } // namespace rive | |
| #endif |