| #ifndef _RIVE_DRAWABLE_HPP_ | |
| #define _RIVE_DRAWABLE_HPP_ | |
| #include "rive/generated/drawable_base.hpp" | |
| #include "rive/hit_info.hpp" | |
| #include "rive/lazy_vector.hpp" | |
| #include "rive/renderer.hpp" | |
| #include "rive/clip_result.hpp" | |
| #include "rive/math/aabb.hpp" | |
| #include "rive/drawable_flag.hpp" | |
| #include "rive/shapes/paint/blend_mode.hpp" | |
| #include <vector> | |
| namespace rive | |
| { | |
| class ClippingShape; | |
| class CustomProperty; | |
| class LayerMask; | |
| class Artboard; | |
| class DrawRules; | |
| class LayoutComponent; | |
| class ShapePaintContainer; | |
| // How much to trust a drawable's answer to paintedWorldBounds(). | |
| // | |
| // Anything sizing a surface from content bounds needs this: a box that is too | |
| // small crops, and a crop at a raster edge is a hard line across the middle of | |
| // someone's artwork. So a drawable that cannot honestly bound itself has to say | |
| // so rather than return its best guess. | |
| enum class BoundsFidelity : uint8_t | |
| { | |
| // Cannot measure at all. The caller must fall back to something it knows | |
| // covers everything, such as the artboard's own bounds. | |
| none, | |
| // The footprint is right but the paint's full reach is not known -- glyph | |
| // ink escaping a text layout box, a stroke effect that can displace | |
| // geometry. The caller should pad before using it. | |
| approximate, | |
| // Geometry plus every stroke, join, cap and feather outset is accounted | |
| // for. | |
| exact, | |
| }; | |
| class Drawable : public DrawableBase | |
| { | |
| friend class Artboard; | |
| friend class StateMachineInstance; | |
| private: | |
| LazyVector<ClippingShape*> m_ClippingShapes; | |
| // Kept separate from m_ClippingShapes rather than unified into one | |
| // bracket list: the clip interleave in Artboard::sortDrawOrder and its | |
| // goldens are load-bearing, and a mask bracket has different nesting | |
| // rules (it must sit inside a clip, and it must hug real drawables). | |
| LazyVector<LayerMask*> m_layerMasks; | |
| // The other direction: masks that rasterize this drawable as coverage. | |
| // Disjoint from m_layerMasks -- a mask cannot mask its own source -- and | |
| // needed because a change to a source drawable has to invalidate the | |
| // coverage canvas, and the only party that can be asked is the drawable. | |
| LazyVector<LayerMask*> m_sourceOfLayerMasks; | |
| /// Used exclusively by the artboard; | |
| DrawRules* flattenedDrawRules = nullptr; | |
| Drawable* prev = nullptr; | |
| Drawable* next = nullptr; | |
| enum class RuntimeFlags : uint8_t | |
| { | |
| none = 0, | |
| needsSaveOperation = 1 << 0, | |
| // Set by a property so untagged drawables pay no child scan. | |
| hasCustomProperties = 1 << 1, | |
| }; | |
| RuntimeFlags m_runtimeFlags = RuntimeFlags::needsSaveOperation; | |
| bool runtimeFlag(RuntimeFlags flag) const | |
| { | |
| return (m_runtimeFlags & flag) == flag; | |
| } | |
| void runtimeFlag(RuntimeFlags flag, bool value) | |
| { | |
| m_runtimeFlags = value ? m_runtimeFlags | flag : m_runtimeFlags & ~flag; | |
| } | |
| protected: | |
| bool needsSaveOperation() const | |
| { | |
| return runtimeFlag(RuntimeFlags::needsSaveOperation); | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| void flagsChanged() override | |
| { | |
| // Preserve runtime-only bits when authoring the shared component flag. | |
| constexpr auto mask = static_cast<uint16_t>(DrawableFlag::Selectable); | |
| drawableFlags((drawableFlags() & ~mask) | (flags() & mask)); | |
| } | |
| #endif | |
| public: | |
| bool isSelectable() const | |
| { | |
| return (static_cast<DrawableFlag>(drawableFlags()) & | |
| DrawableFlag::Selectable) != DrawableFlag::None; | |
| } | |
| BlendMode blendMode() const { return (BlendMode)blendModeValue(); } | |
| /// additiveAmount() as the renderer wants it: 0 (plain srcOver) to 1 | |
| /// (fully additive), and always 0 for the modes that ignore it. | |
| float additiveness() const | |
| { | |
| return additivenessFor(blendMode(), additiveAmount()); | |
| } | |
| virtual void draw(Renderer* renderer) = 0; | |
| virtual Core* hitTest(HitInfo*, const Mat2D&) = 0; | |
| bool hitTestPoint(const Vec2D& position, | |
| bool skipOnUnclipped, | |
| bool isPrimaryHit) override; | |
| void addClippingShape(ClippingShape* shape); | |
| inline const std::vector<ClippingShape*>& clippingShapes() const | |
| { | |
| return m_ClippingShapes.view(); | |
| } | |
| void addLayerMask(LayerMask* mask); | |
| void addSourceOfLayerMask(LayerMask* mask); | |
| // The masks that apply to this drawable, outermost ancestor first (they are | |
| // stamped in m_Objects order, which is hierarchy order). Virtual because | |
| // DrawableProxy has to answer for the component it stands in for: a | |
| // LayoutComponent's proxy carries the save()+clipPath() whose restore() | |
| // lives in LayoutComponent::draw(), so leaving the proxy out of the bracket | |
| // would strand the save outside it and underflow the offscreen renderer's | |
| // save stack. | |
| virtual const std::vector<LayerMask*>& layerMasks() const | |
| { | |
| return m_layerMasks.view(); | |
| } | |
| // The masks this drawable supplies coverage for. Virtual for the same | |
| // reason layerMasks() is: a proxy answers for what it stands in for. | |
| virtual const std::vector<LayerMask*>& sourceOfLayerMasks() const | |
| { | |
| return m_sourceOfLayerMasks.view(); | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| // Editor-only removal companion to `addClippingShape`. Fires from | |
| // `ClippingShape::editorParentChanged` when a ClippingShape is | |
| // reparented away from this drawable's clip-affecting ancestor (or | |
| // about to be freed) so the typed list doesn't dangle. Runtime | |
| // assumes immutable hierarchy and never needs this. Body in | |
| // `editor_native/.../component_parent_editor.cpp`. | |
| void removeClippingShapeForEditor(ClippingShape* shape); | |
| // The same companions for the two LayerMask lists, which onAddedClean | |
| // registers the same one-shot way: m_layerMasks from the mask's parent | |
| // subtree, m_sourceOfLayerMasks from its source subtree. Without these a | |
| // mask deleted or reparented in the editor leaves a freed pointer that | |
| // sortDrawOrder or a colour edit will dereference. | |
| void removeLayerMaskForEditor(LayerMask* mask); | |
| void removeSourceOfLayerMaskForEditor(LayerMask* mask); | |
| #endif | |
| virtual bool isHidden() const | |
| { | |
| if ((static_cast<DrawableFlag>(drawableFlags()) & | |
| DrawableFlag::Hidden) == DrawableFlag::Hidden || | |
| hasDirt(ComponentDirt::Collapsed)) | |
| { | |
| return true; | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| // Editor-only: respect the hierarchy panel's eye toggle. | |
| // Walks the parent chain since `ComponentFlags::hidden` | |
| // doesn't physically cascade to children. Mirrors dart's | |
| // [stage_hideable.dart:76-80](packages/editor/lib/rive/stage/stage_hideable.dart#L76-L80). | |
| // Runtime flags() is always 0, so this branch is dead weight | |
| // there but free at runtime build time. | |
| if (isFlaggedRecursive(EditorFlagHidden)) | |
| { | |
| return true; | |
| } | |
| // Isolation: when any component in the artboard has the | |
| // isolated bit set, everything outside the isolated subtree | |
| // is hidden on stage. After-Effects-style. | |
| if (isOutsideEditorIsolationScope()) | |
| { | |
| return true; | |
| } | |
| #endif | |
| return false; | |
| } | |
| #ifdef WITH_RIVE_EDITOR | |
| /// True iff the active artboard has at least one component with | |
| /// `EditorFlagIsolated` set AND this drawable is NOT a descendant | |
| /// of one of those isolated subtrees. Walks the artboard's full | |
| /// hierarchy on each call — cheap because flagged components are | |
| /// rare and the walk short-circuits on the first match. Mirrors | |
| /// dart's [Artboard.hasIsolatedComponents]/[isInIsolationScope] | |
| /// pair used by | |
| /// [stage_hideable.dart:87-92](packages/editor/lib/rive/stage/stage_hideable.dart#L87-L92). | |
| /// Body in `editor_native/native/src/editor/drawable_editor.cpp` | |
| /// so this header doesn't depend on the full Artboard def. | |
| bool isOutsideEditorIsolationScope() const; | |
| #endif | |
| virtual bool isTargetOpaque() | |
| { | |
| return (static_cast<DrawableFlag>(drawableFlags()) & | |
| DrawableFlag::Opaque) == DrawableFlag::Opaque; | |
| } | |
| virtual bool isProxy() { return false; } | |
| virtual bool isClipStart() { return false; } | |
| virtual bool isClipEnd() { return false; } | |
| // Opens / closes a layer-mask bracket. Unlike a clip bracket these are not | |
| // drawn in place: the walk hands the whole enclosed range to | |
| // Artboard::drawMasked and then skips past it. | |
| virtual bool isMaskStart() { return false; } | |
| virtual bool isMaskEnd() { return false; } | |
| virtual bool willClip() { return false; } | |
| virtual bool willDraw(); | |
| // World-space bounds of everything this drawable actually paints: geometry | |
| // plus stroke, join, cap and feather outset. NOT the same as | |
| // Shape::worldBounds(), which is geometry only and would crop a feather. | |
| // | |
| // Defaults to `none` so a Drawable added later is safe by construction -- | |
| // the day someone adds a drawable that paints somewhere unpredictable, | |
| // every caller degrades instead of silently cropping it. Override only | |
| // where the answer is genuinely conservative. *out is untouched when `none` | |
| // is returned. | |
| virtual BoundsFidelity paintedWorldBounds(AABB* out) | |
| { | |
| return BoundsFidelity::none; | |
| } | |
| protected: | |
| // The shape every override below takes: lift a local-space box into world | |
| // space, then pad it by `paints` (null for a drawable that has none). | |
| // | |
| // Answers `none` for an empty local box, because | |
| // TransformComponent::localBounds() returns an empty AABB to mean "I do not | |
| // know my size" -- and a caller must not read that as "I paint nothing". | |
| BoundsFidelity paintedBoundsFromLocal(const AABB& local, | |
| const Mat2D& world, | |
| const ShapePaintContainer* paints, | |
| AABB* out); | |
| public: | |
| void needsSaveOperation(bool value) | |
| { | |
| runtimeFlag(RuntimeFlags::needsSaveOperation, value); | |
| } | |
| bool isChildOfLayout(LayoutComponent* layout); | |
| StatusCode onAddedDirty(CoreContext* context) override; | |
| virtual Drawable* hittableComponent() { return this; } | |
| // Whether custom properties tag this drawable for a draw visitor. | |
| bool hasCustomProperties(); | |
| void markHasCustomProperties() | |
| { | |
| runtimeFlag(RuntimeFlags::hasCustomProperties, true); | |
| } | |
| // The tagging property with this name id, null when there is none. | |
| CustomProperty* customProperty(uint32_t nameId); | |
| virtual int emptyClipCount() { return 0; } | |
| // Public accessors for the artboard's draw-order linked list. | |
| // Order is back-to-front (firstDrawable() is bottom-most). | |
| Drawable* nextDrawable() const { return next; } | |
| Drawable* prevDrawable() const { return prev; } | |
| }; | |
| class ProxyDrawing | |
| { | |
| public: | |
| virtual void drawProxy(Renderer* renderer) = 0; | |
| virtual bool isProxyHidden() = 0; | |
| }; | |
| class DrawableProxy : public Drawable | |
| { | |
| private: | |
| ProxyDrawing* m_proxyDrawing; | |
| public: | |
| DrawableProxy(ProxyDrawing* proxy) : m_proxyDrawing(proxy) {} | |
| void draw(Renderer* renderer) override | |
| { | |
| m_proxyDrawing->drawProxy(renderer); | |
| } | |
| bool isHidden() const override { return m_proxyDrawing->isProxyHidden(); } | |
| Drawable* hittableComponent() override; | |
| bool isTargetOpaque() override; | |
| Core* hitTest(HitInfo*, const Mat2D&) override { return nullptr; } | |
| bool isProxy() override { return true; } | |
| // Delegated for the same reason as layerMasks(): the proxy paints on behalf | |
| // of another component, so that component's reach is the answer. | |
| BoundsFidelity paintedWorldBounds(AABB* out) override; | |
| // A proxy stands in for a real component, so it has to be inside the same | |
| // mask bracket that component is in. A LayoutComponent's proxy issues | |
| // save() + clipPath() whose matching restore() is in | |
| // LayoutComponent::draw(); if the proxy were excluded from the bracket the | |
| // save would land outside the offscreen frame and the restore inside it. | |
| const std::vector<LayerMask*>& layerMasks() const override; | |
| const std::vector<LayerMask*>& sourceOfLayerMasks() const override; | |
| ProxyDrawing* proxyDrawing() const { return m_proxyDrawing; } | |
| }; | |
| } // namespace rive | |
| #endif |