blob: 48554a2e2e1ca406bbd41f999a8bc1a74d780a55 [file]
#ifndef _RIVE_ARTBOARD_HPP_
#define _RIVE_ARTBOARD_HPP_
#include "rive/advance_flags.hpp"
#include "rive/resetting_component.hpp"
#include "rive/animation/linear_animation.hpp"
#include "rive/animation/state_machine.hpp"
#include "rive/core_context.hpp"
#include "rive/data_bind/data_context.hpp"
#include "rive/data_bind/data_bind_container.hpp"
#include "rive/viewmodel/viewmodel_instance_value.hpp"
#include "rive/viewmodel/viewmodel_instance_viewmodel.hpp"
#include "rive/generated/artboard_base.hpp"
#include "rive/hit_info.hpp"
#include "rive/lazy_vector.hpp"
#include "rive/sidecar.hpp"
#include "rive/math/aabb.hpp"
#include "rive/renderer.hpp"
#include "rive/text/text_value_run.hpp"
#include "rive/event.hpp"
#include "rive/audio/audio_engine.hpp"
#include "rive/math/raw_path.hpp"
#include "rive/typed_children.hpp"
#include "rive/virtualizing_component.hpp"
#include "rive/watermark.hpp"
#include "rive/input/focus_node.hpp"
#include "rive/input/focus_manager.hpp"
#include "rive/semantic/semantic_node.hpp"
#include "rive/scripting_slots.hpp"
#include <memory>
#include <queue>
#include <unordered_set>
#include <vector>
namespace rive
{
class HostDataContextConsumer;
class KeyFrameInterpolator;
class KeyFrame;
class ArtboardComponentList;
class ArtboardHost;
class File;
class Drawable;
class Factory;
namespace gpu
{
class RenderContext;
class RenderCanvas;
} // namespace gpu
namespace cmd
{
class DeferredCanvasHost;
} // namespace cmd
namespace offscreen
{
struct RasterPlan;
struct CachedRaster;
} // namespace offscreen
class LayerMask;
class LayerMaskProxyDrawable;
enum class LayerMaskOp : uint8_t;
class BitmapCache;
class Node;
class DrawTarget;
class ArtboardImporter;
class NestedArtboard;
class ArtboardInstance;
class LinearAnimationInstance;
class Scene;
class StateMachineInstance;
class Joystick;
class TextValueRun;
class Event;
class SMIBool;
class SMIInput;
class SMINumber;
class SMITrigger;
class DataBind;
class DataBindContainer;
class ScriptedObject;
class ScriptedInterpolator;
struct ArtboardScriptedInterpolators;
class FocusManager;
class SemanticManager;
class SemanticNode;
struct ArtboardSourceState;
#ifdef WITH_RIVE_TOOLS
typedef void (*ArtboardCallback)(void*);
typedef uint8_t (*TestBoundsCallback)(void*, float, float, bool);
typedef uint8_t (*IsAncestorCallback)(void*, uint16_t);
typedef float (*RootTransformCallback)(void*, float, float, bool);
/// Held by a source artboard and by every instance made from it, so the count
/// above one is how many instances are alive. Lives apart from the source so
/// an instance can let go of it after the source is gone.
class ArtboardInstanceCount : public RefCnt<ArtboardInstanceCount>
{};
#endif
// Called in place of Drawable::draw for each drawable that has custom
// properties; it decides whether and how the drawable draws.
using DrawVisitor = void (*)(void* context, Drawable*, Renderer*);
class Artboard : public ArtboardBase,
public CoreContext,
public Virtualizable,
public ResettingComponent,
public DataBindContainer
{
friend class File;
friend class ArtboardImporter;
friend class Component;
private:
std::vector<Core*> m_Objects;
#ifdef WITH_RIVE_EDITOR
// Editor-only: supplementary resolver for objects referenced by
// their coop (client, object) identity rather than a flat
// `m_Objects` index. editor_native installs one of these at
// coop-session start so `Artboard::resolve(Id)` can route
// non-runtime CoopIds through the coop object map. Left null in
// runtime-only resolution.
CoreContext* m_editorResolver = nullptr;
#endif
// Lists that are empty on the large majority of artboards hold a LazyVector
// (8 B, heap-allocated on first push) rather than an inline vector header
// (24 B). Measured over the 945 artboards in tests/unit_tests/assets:
// layerMasks 100% empty, Joysticks 98.6%, DrawTargets 97.4%,
// ScriptedObjects 93.7%, ComponentLists 92.7%, clippingShapes 90.6%,
// NestedArtboards 76.2%.
//
// The rest stay inline because they are populated on essentially every
// artboard, where LazyVector's extra allocation makes it strictly worse
// (see lazy_vector.hpp): m_Objects, m_DependencyOrder, m_Drawables,
// m_advancingComponents (0% empty, up to 142 entries) and m_ArtboardHosts
// (only 69.7% empty). The animation, state machine and rejected-object
// lists aren't here at all -- they are source-only, so they live in
// m_sourceState.
std::vector<Component*> m_DependencyOrder;
std::vector<Drawable*> m_Drawables;
LazyVector<ClippingShape*> m_clippingShapes;
LazyVector<LayerMask*> m_layerMasks;
LazyVector<DrawTarget*> m_DrawTargets;
LazyVector<NestedArtboard*> m_NestedArtboards;
LazyVector<ArtboardComponentList*> m_ComponentLists;
std::vector<ArtboardHost*> m_ArtboardHosts;
// Components instancing things bound under this artboard's data context
// (Texts' global text styles).
std::vector<HostDataContextConsumer*> m_hostDataContextConsumers;
LazyVector<Joystick*> m_Joysticks;
LazyVector<ScriptedObject*> m_ScriptedObjects;
std::vector<AdvancingComponent*> m_advancingComponents;
// Whose value binds are currently cloned into this container.
rcp<ViewModelInstance> m_instanceValueBindsSource = nullptr;
// A resync asked for mid update waits for the pass to drain.
bool m_instanceValueBindsPending = false;
void syncInstanceValueBinds();
void wakeQuietRow();
public:
#ifdef WITH_RIVE_EDITOR
void editorPrepareForDestruction() override;
#endif
void mainViewModelInstanceChanged() override;
void dataBindsProcessed() override;
private:
#ifdef WITH_RIVE_SCRIPTING
[[maybe_unused]] ScriptingVMSlot m_scriptingVM = nullptr;
#endif
bool m_JoysticksApplyBeforeUpdate = true;
// Something collapsed or uncollapsed, or the dependency order changed,
// since markCollapsedRuns: the run ends on collapsed components are
// stale. Sits in padding.
bool m_collapsedRunsDirty = true;
// Writes each collapsed component's run end; clears m_collapsedRunsDirty.
void markCollapsedRuns();
#ifdef TESTING
// Aborts if a run end updateComponents would jump by is wrong.
void verifyCollapsedRuns() const;
#endif
unsigned int m_DirtDepth = 0;
Factory* m_Factory = nullptr;
Drawable* m_FirstDrawable = nullptr;
bool m_IsInstance = false;
bool m_FrameOrigin = true;
// In the padding after the flags above; see quietHostRow.
uint32_t m_quietHostRow = kNoQuietRow;
std::unordered_set<LayoutComponent*> m_dirtyLayout;
bool m_isCleaningDirtyLayouts = false;
std::unique_ptr<KeyFrameInterpolator> m_ownedInheritedInterpolator;
// Live clones of the scripted interpolators this instance's layout styles
// use. Allocated on first use; see statefulInterpolator().
std::unique_ptr<ArtboardScriptedInterpolators> m_scriptedInterpolators;
float m_originalWidth = 0;
float m_originalHeight = 0;
bool m_updatesOwnLayout = true;
bool m_hostTransformMarkedDirty = false;
bool m_didChange = true;
// In the padding after the flags above. Bumped by every change that
// reaches this artboard -- component dirt, changed() and its own
// rotation/scale, which m_contentRevision leaves out -- so a state machine
// can tell whether anything moved since it last re-applied a held pose.
// Wraps; only compared for equality.
uint32_t m_changeSerial = 0;
// Each raster records the revision it drew, so one draw path can't consume
// another's change. Self rotation/scale don't bump it; rasters omit them.
uint64_t m_contentRevision = 1;
DrawVisitor m_drawVisitor = nullptr;
void* m_drawVisitorContext = nullptr;
// The file the visit's keys belong to, handed down because a nested
// instance of that same file does not know its file.
const File* m_drawVisitorFile = nullptr;
Artboard* parentArtboard() const;
ArtboardHost* m_host = nullptr;
// This artboard's own manager, allocated only by roots — an artboard
// opened from a File, a scripted artboard, or one a caller explicitly
// asks. Null on source artboards and on nested / component-list
// instances, which adopt their root's manager instead.
std::unique_ptr<FocusManager> m_ownedFocusManager;
FocusManager* m_activeFocusManager = nullptr;
SemanticManager* m_activeSemanticManager = nullptr;
rcp<SemanticNode> m_semanticBoundaryNode;
#ifdef WITH_RIVE_TOOLS
rcp<FocusNode> m_externalParentFocusNode;
uint32_t m_seenImageRevealGeneration = 0;
bool m_hasUpdated = false;
#endif
static uint64_t sm_frameId;
#ifdef TESTING
static uint64_t sm_layoutPassCount;
#endif
// Non-null only on top level instances vended by File for a file whose
// manifest carries a watermark. Released as soon as the watermark finishes.
std::unique_ptr<Watermark> m_watermark;
bool sharesLayoutWithHost() const;
void cloneObjectDataBinds(const Core* object, Core* clone, Artboard* artboard)
const;
// Lazily-built index of this artboard's data binds that target keyframes,
// keyed by the (shared) keyframe. Keyframe binds live in dataBinds() but
// are never applied directly (keyframes aren't Components); instead a
// playing LinearAnimationInstance clones them onto per-instance holders and
// resolves them via keyFrameValueHolder. Built once from dataBinds(); the
// source artboard's binds don't change at runtime. `mutable` so the const
// accessors below can populate it on first use.
// Source-only state: animations, state machines, rejected objects and the
// keyframe-bind index. Null on every instance. See ArtboardSourceState.
mutable Sidecar<ArtboardSourceState> m_sourceState;
// Views onto this artboard's own source state, each falling back to a
// shared empty when none was allocated -- every instance, and any artboard
// that imported no animation. To read the lists an instance plays, go
// through animationSource() first.
const std::vector<LinearAnimation*>& ownAnimations() const;
const std::vector<StateMachine*>& ownStateMachines() const;
const std::vector<Core*>& ownInvalidObjects() const;
void buildKeyFrameSourceBindsIndex() const;
void initScriptedObjects();
// Variable that tracks whenever the draw order changes. It is used by the
// state machine controllers to sort their hittable components when they are
// out of sync
uint8_t m_drawOrderChangeCounter = 0;
#ifdef WITH_RIVE_TOOLS
uint16_t m_artboardId = 0;
// Instances borrow their source's animations and state machines, so
// File::replaceArtboard can't delete a source while this says any are
// alive. Set by the File on the artboards it imports, and shared by every
// instance made from them; null on artboards no file owns.
rcp<ArtboardInstanceCount> m_instanceCount;
#endif
const Artboard* m_artboardSource = nullptr;
#ifdef EXTERNAL_RIVE_AUDIO_ENGINE
rcp<AudioEngine> m_audioEngine;
#endif
void sortDependencies();
// -- instancing fast path for the dependency order --
//
// An instance's object list is a 1:1 index-for-index clone of its
// source's, and its dependency graph is structurally identical, so the
// topological order it would compute is the same permutation the source
// already computed. Rather than re-running the graph walk per instance,
// the source records its order once as (object index, helper slot) pairs
// and instances replay it in O(n).
//
// `helperSlot` is 0 for the object itself, or 1+n for a Component the
// object owns that never appears in m_Objects (Shape's PathComposer,
// TextStyle's TextVariationHelper). Those are real nodes in the graph.
struct DependencyOrderEntry
{
uint32_t objectIndex;
uint8_t helperSlot;
};
enum class RecipeState : uint8_t
{
unbuilt,
valid,
unusable
};
/// Returns the Component at [helperSlot] of m_Objects[objectIndex], or
/// null if that slot is empty on this artboard.
Component* componentForOrderEntry(const DependencyOrderEntry& entry) const;
/// Builds (once) and returns this artboard's order recipe, or null if it
/// could not be expressed as index/slot pairs.
const std::vector<DependencyOrderEntry>* dependencyOrderRecipe() const;
/// Fills m_DependencyOrder from [source]'s recipe. False if unavailable or
/// if anything about this instance does not line up, in which case the
/// caller must fall back to sorting.
bool replaySourceDependencyOrder(const Artboard* source);
// Only a source artboard ever builds this; an instance reads its
// source's through replaySourceDependencyOrder. Sidecar'd so the
// instances that never build one carry 8 B rather than a vector header.
mutable Sidecar<std::vector<DependencyOrderEntry>> m_DependencyOrderRecipe;
mutable RecipeState m_RecipeState = RecipeState::unbuilt;
void sortDrawOrder();
// Injects each LayoutComponent's DrawableProxy into m_Drawables after its
// last child, for layouts that paint, clip or are hit targets. Expects
// m_Drawables in hierarchy order (parents before children).
void insertLayoutProxies();
void clearRedundantOperations();
// Splices layer-mask brackets into the flat draw list. Runs after the
// clipping interleave so a mask bracket nests inside a clip bracket: an
// ancestor clip has to constrain the composite rather than live inside the
// raster.
void interleaveLayerMasks();
// Finds the contiguous draw-order span covered by `members` and wraps it in
// start/end markers. False when the span is not contiguous -- a DrawTarget
// can relocate part of a subtree, and bracketing a range holding a
// non-member would mask that non-member too.
bool spliceLayerMaskBracket(
LayerMask* mask,
const std::vector<Drawable*>& members,
LayerMaskOp startOp,
LayerMaskOp endOp,
LayerMaskProxyDrawable** startOut);
void updateRenderPath() override;
void update(ComponentDirt value) override;
public:
static uint64_t frameId() { return sm_frameId; }
#ifdef TESTING
// Layout passes (calculateLayout calls) across every artboard, so a test
// can tell a settled artboard from one that re-solves every frame.
static uint64_t layoutPassCount() { return sm_layoutPassCount; }
// Dirt reported to any artboard (onComponentDirty and onDirty calls).
static uint64_t sm_dirtNotifications;
// Runs of collapsed components updateComponents skipped in one step.
static uint64_t sm_collapsedRunSkips;
static void incFrameId() { sm_frameId++; }
#elif WITH_RIVE_TOOLS
static void incFrameId() { sm_frameId++; }
#endif
void updateDataBinds(bool applyTargetToSource = true) override;
// The data bind (if any) targeting the given keyframe's value, resolved
// from this artboard's serialized keyframe binds. Used by
// LinearAnimationInstance to lazily build per-instance value holders. Call
// on the source artboard (see Artboard::artboardSource()).
DataBind* keyFrameSourceBind(const KeyFrame* keyframe) const;
// True if any of this artboard's data binds target a keyframe. Cheap gate
// so playback in files without keyframe binds does no per-keyframe work.
bool hasKeyFrameSourceBinds() const;
void host(ArtboardHost* artboardHost);
ArtboardHost* host() const;
/// While a host skips this row artboard's per-frame work (see
/// ArtboardComponentList's quiet rows), the row it is quiet at, else
/// kNoQuietRow. Whatever may change the row wakes it: its dirt, its data
/// binds and data context, its layout and host transform, and its state
/// machine (StateMachineInstance forwards its own wake-ups here).
static constexpr uint32_t kNoQuietRow = 0xFFFFFFFF;
uint32_t quietHostRow() const { return m_quietHostRow; }
void quietHostRow(uint32_t row) { m_quietHostRow = row; }
void wakeIfQuietRow()
{
if (m_quietHostRow != kNoQuietRow)
{
wakeQuietRow();
}
}
/// Whether this artboard's own per-frame work as a row (advanceInternal,
/// updateDataBinds, reset and updatePass) would currently do nothing.
/// `never` for artboards whose content can't say (hosted artboards,
/// scripts, joysticks, resettables, advancing converters, advancing
/// components that can't report).
AdvancingComponent::QuietState rowQuietState();
void addedToHost()
{
setLayoutFlag(LayoutComponentFlags::JustAddedToHost, true);
}
/// Set the active FocusManager for this artboard. Raw setter used by
/// buildFocusTree, which is the ordering authority for the walk it is
/// running; it does not tear down or rebuild anything. To point an artboard
/// at a different manager from the outside, use adoptFocusManager.
void setActiveFocusManager(FocusManager* manager)
{
m_activeFocusManager = manager;
}
/// Get the active FocusManager for this artboard.
FocusManager* focusManager() const { return m_activeFocusManager; }
/// Create this artboard's own FocusManager if it doesn't have one yet and
/// make it active. Idempotent, and a no-op once any manager is active — an
/// artboard that has adopted a parent's manager does not allocate one.
///
/// Called by whoever establishes a root artboard — File::instanceArtboard
/// and ScriptReffedArtboard
FocusManager* ensureFocusManager();
/// Point this artboard (and everything it hosts) at a different
/// FocusManager, tearing down the tree built against the previous one and
/// rebuilding it against [manager]. This is the entry point for a host that
/// owns the manager — a parent artboard sharing its own, or Dart/the editor
/// supplying one.
void adoptFocusManager(FocusManager* manager);
#ifdef WITH_RIVE_TOOLS
/// Set an external parent FocusNode for this artboard's root-level focus
/// nodes. This is used when the artboard is nested inside another artboard
/// that has a FocusData in its hierarchy. The external parent allows focus
/// nodes in this artboard to be children of a FocusData in the host
/// artboard.
void setExternalParentFocusNode(rcp<FocusNode> node);
/// Get the external parent FocusNode for this artboard.
rcp<FocusNode> externalParentFocusNode() const;
// collapseSingle is only used by the editor. Its purpose is to set the
// collapse value to the immediate artboard but not to its children
void collapseSingle(bool);
#endif
/// Build the focus tree for this artboard, registering all FocusData nodes
/// with the given FocusManager.
/// @param focusManager The FocusManager to register nodes with
/// @param parentFocusNode Optional parent node for root-level focus nodes
/// (used for nested artboards to connect to parent's focus tree)
void buildFocusTree(
FocusManager* focusManager,
rcp<FocusNode> parentFocusNode = nullptr);
/// Build the focus tree for this artboard using the parent node's manager.
/// This is a convenience overload for nested artboards - the FocusManager
/// is derived from the parent node's manager() reference.
/// @param parentFocusNode The parent node to attach this artboard's focus
/// nodes under. Must have a valid manager() or this is a no-op.
void buildFocusTree(rcp<FocusNode> parentFocusNode);
/// Clean up the focus tree for this artboard, removing all FocusData nodes
/// from the FocusManager. This should be called before the artboard is
/// destroyed or recycled to prevent use-after-free when the FocusManager
/// still holds references to FocusNodes pointing to deleted FocusData.
void cleanupFocusTree();
/// Set the active SemanticManager for this artboard. The SemanticManager is
/// typically owned by a StateMachineInstance.
void setActiveSemanticManager(SemanticManager* manager)
{
m_activeSemanticManager = manager;
}
/// Get the active SemanticManager for this artboard.
SemanticManager* semanticManager() const { return m_activeSemanticManager; }
/// Build the semantic tree for this artboard, registering all SemanticData
/// nodes with the given SemanticManager.
void buildSemanticTree(
SemanticManager* semanticManager,
rcp<SemanticNode> parentSemanticNode = nullptr);
/// Clean up the semantic tree for this artboard, removing all SemanticData
/// nodes from the SemanticManager.
void cleanupSemanticTree();
// Called when the host collapses or uncollapses. While the host is
// collapsed, every node of this artboard and the artboards nested below
// it is left out of the tree the platform sees.
void syncSemanticBoundary();
// True when the component hosting this artboard is collapsed.
bool isHostCollapsed() const;
/// Mark bounds dirty for all semantic nodes under this artboard's
/// boundary node. Called when the host transform changes.
void markSemanticBoundaryTransformDirty();
/// Get the semantic boundary node for this artboard (null for root).
rcp<SemanticNode> semanticBoundaryNode() const
{
return m_semanticBoundaryNode;
}
// Implemented for ShapePaintContainer.
const Mat2D& shapeWorldTransform() const override { return worldTransform(); }
Component* virtualizableComponent() override { return this; }
bool updatesOwnLayout() { return m_updatesOwnLayout; }
// Layouts marked dirty since the last solve. A layout dirtied while the
// components update (a row's first cascade, a text that reshaped) waits
// for the next updatePass to be solved.
bool hasDirtyLayout() const { return !m_dirtyLayout.empty(); }
StatusCode onAddedClean(CoreContext* context) override;
void addDirtyDataBind(DataBind*) override;
private:
#ifdef TESTING
public:
// Defined out of line: Artboard holds an rcp<gpu::RenderCanvas>, which is
// only forward-declared here. An inline constructor would instantiate the
// member's destructor (the exception-unwind path) in every TU that includes
// this header, where RenderCanvas is incomplete.
Artboard(Factory* factory);
#endif
void addObject(Core* object);
void addAnimation(LinearAnimation* object);
void addStateMachine(StateMachine* object);
void buildDataContext(rcp<DataContext> value);
public:
Artboard();
~Artboard() override;
bool validateObjects();
StatusCode initialize();
bool didChange() { return m_didChange; }
uint32_t changeSerial() const { return m_changeSerial; }
// The BitmapCache child of this artboard, if one exists. Its presence
// enables cache-as-bitmap rendering. Populated during initialize().
BitmapCache* bitmapCache() const { return m_BitmapCache; }
Core* resolve(Id id) const override;
// Every LayerMask in this artboard, populated by initialize().
// LayerMask::invalidate walks it to reach the masks whose cached rasters
// contain its output: a mask on an ancestor caches it as content, and a
// mask whose SOURCE encloses it caches it as coverage -- and that one can
// live anywhere in the tree, so no walk from the inner mask finds it.
//
// Deliberately NOT named layerMasks(). Artboard is a Drawable and
// Drawable::layerMasks() is virtual, meaning "the masks applied to me", so
// that name here would override it and hand every caller holding an
// Artboard as a Drawable the wrong list. (The first attempt did exactly
// that, and landed in the editor-only block below besides, where the
// runtime could not see it at all and the call silently resolved to the
// base's version.)
const std::vector<LayerMask*>& allLayerMasks() const
{
return m_layerMasks.view();
}
// Every Drawable in the artboard (plus layout proxies). Much smaller than
// objects(), so prefer it when looking for a Drawable type. The editor's
// CommandDispatcher::handleHitTest walks it back-to-front for
// component-level selection (later index = higher z).
const std::vector<Drawable*>& drawables() const { return m_Drawables; }
#ifdef WITH_RIVE_EDITOR
// Install / remove an auxiliary resolver for CoopId-backed
// references (see `m_editorResolver`). `editor_native::EditorFile`
// is the expected implementor.
void setEditorResolver(CoreContext* resolver) { m_editorResolver = resolver; }
CoreContext* editorResolver() const { return m_editorResolver; }
// Assign the Factory editor_native's coop-created Artboards use to
// mint RenderPaint / RenderPath / etc. when runtime children fire
// their onAddedDirty. Runtime `.riv` loads install the factory via
// `File::import`; editor_native's EditorFile calls this right after
// `new EditorArtboard()` in createObject.
void setFactory(Factory* factory) { m_Factory = factory; }
// Editor-mode Drawable registration. Runtime `.riv` loads populate
// `m_Drawables` during `Artboard::initialize()` by walking
// `m_Objects`. In coop-apply, `m_Objects` stays empty — children
// live in EditorFile's arena — so EditorFile's `finalizeBatch`
// drives per-Drawable registration via this setter. Mirrors Dart's
// `Artboard.addComponent` specialty-list population
// (packages/rive_core/lib/artboard.dart:1068-1098).
void addDrawable(Drawable* drawable) { m_Drawables.push_back(drawable); }
// Clear the drawables list so `finalizeBatch` can rebuild it
// idempotently when a follow-on coop batch adds more Drawables.
void clearDrawables() { m_Drawables.clear(); }
// Editor-only: clear/rebuild the animation + state-machine lists.
// Runtime `.riv` loads populate these via `ArtboardImporter::
// addAnimation` / `addStateMachine` during import; coop-hydrated
// animations / state-machines never hit that path, so
// `finalizeBatch` registers them manually after each batch. Clear
// first so repeated finalizeBatch calls don't duplicate entries.
// Rebuild the runtime playback lists from an editor arena, whose objects
// do not live in the importer's m_Objects array.
void rebuildAdvancingForEditor(Span<Core*> objects);
void clearAnimations();
void clearStateMachines();
// Editor-only: public wrappers around the otherwise-private
// `addAnimation` / `addStateMachine` so `finalizeBatch` can
// register coop-hydrated entries without friending EditorFile on
// the runtime Artboard. Runtime `.riv` loads still go through
// `ArtboardImporter` (our friend), so no changes to that path.
void addAnimationForEditor(LinearAnimation* object) { addAnimation(object); }
void addStateMachineForEditor(StateMachine* object)
{
addStateMachine(object);
}
// Public editor-only surface for the DAG topological sort.
// `sortDependencies` is private in the runtime `.riv` load path
// (driven by `initialize()`); editor_native's `finalizeBatch` needs
// to invoke it after per-component `buildDependencies` calls.
void sortDependenciesEditor() { sortDependencies(); }
// Editor drawables are rebuilt from the arena rather than m_Objects, so
// finalizeBatch injects layout proxies itself, after addDrawable.
void insertLayoutProxiesForEditor() { insertLayoutProxies(); }
// Initialize `m_layout` from the artboard's width/height so
// `layoutWidth()` / `layoutHeight()` / `bounds()` report non-zero.
// Mirrors the first lines of `Artboard::initialize()` at
// artboard.cpp:261-266. Called once by `EditorFile::finalizeBatch`.
void initLayoutForEditor();
#endif
/// The order updates run in, as produced by [sortDependencies] -- either
/// sorted, or replayed from this artboard's source. Public because both
/// the editor's `finalizeBatch` and the tests that pin replay against a
/// real sort need to read it.
const std::vector<Component*>& dependencyOrder() const
{
return m_DependencyOrder;
}
#ifdef WITH_RIVE_TOOLS
void artboardId(uint16_t id) { m_artboardId = id; }
uint16_t artboardId() const { return m_artboardId; }
/// Whether any instance made from this source artboard, directly or from
/// another of its instances, is still alive.
bool hasLiveInstances() const
{
return m_instanceCount != nullptr &&
m_instanceCount->debugging_refcnt() > 1;
}
#endif
void artboardSource(const Artboard* artboard) { m_artboardSource = artboard; }
const Artboard* artboardSource() const
{
return isInstance() ? m_artboardSource : this;
}
/// The artboard that owns the animation and state machine lists. Instances
/// share their source's instead of copying them (see instance()), so every
/// read of either list routes through here. `artboardSource()` is null on an
/// instance the editor flagged with setIsInstance without recording a
/// source; those keep their own (deliberately empty) lists, so fall back to
/// this rather than assuming a source exists.
const Artboard* animationSource() const
{
const Artboard* source = artboardSource();
return source != nullptr ? source : this;
}
bool isAncestor(const Artboard* artboard);
/// Find the id of a component in the artboard the object in the artboard.
/// The artboard itself has id 0 so we use that as a flag for not found.
uint32_t idOf(Core* object) const;
Factory* factory() const { return m_Factory; }
// EXPERIMENTAL -- for internal testing only for now.
// DO NOT RELY ON THIS as it may change/disappear in the future.
Core* hitTest(HitInfo*, const Mat2D&) override;
bool hitTestPoint(
const Vec2D& position,
bool skipOnUnclipped,
bool isPrimaryHit) override;
Vec2D rootTransform(const Vec2D&);
/// Root artboard space back into this artboard's space: the inverse of
/// rootTransform. False when that transform cannot be inverted.
bool rootToLocal(const Vec2D& rootPosition, Vec2D* outLocal);
/// Whether this artboard is the root of its tree: no component hosts it,
/// and in the editor no mounted-artboard callback maps it either.
bool isRoot() const;
void onComponentDirty(Component* component);
/// A component in this artboard collapsed or uncollapsed.
void collapseChanged() { m_collapsedRunsDirty = true; }
void rotationChanged() override;
void scaleXChanged() override;
void scaleYChanged() override;
void selfTransformChanged();
/// Update components that depend on each other in DAG order.
bool updateComponents();
// Update layouts and components. Returns true if it updated something.
bool updatePass(bool isRoot);
#ifdef WITH_RIVE_TOOLS
// For artboards a script draws itself: settles this one and everything
// it hosts when image sizes were revealed since it last drew, so layout
// measured against a 0x0 image does not draw. A no-op unless something
// reveals.
void settleIfImagesRevealed();
// Re-runs layout after image sizes are revealed, without advancing:
// every hosted instance (nested artboards, list rows, artboard paints)
// bottom up, then each hosted instance once more after the host that
// lays it out. At most two update passes per instance, however deep.
void settleAfterImageReveal();
// Update passes settleAfterImageReveal has run, for tests to bound.
static size_t settlePassesForTesting();
static void resetSettlePassesForTesting();
#endif
void onDirty(ComponentDirt dirt) override;
// Artboards don't update their world transforms in the same way
// as other TransformComponents so we override this.
// This is because LayoutComponent extends Drawable, but
// Artboard is a special type of LayoutComponent
void updateWorldTransform() override {}
void markLayoutDirty(LayoutComponent* layoutComponent);
void markHostTransformDirty();
void dataContextChanged() override;
void cleanLayout(LayoutComponent* layoutComponent);
LayoutData* takeLayoutData();
bool syncStyleChanges() override;
void syncStyleChangesWithUpdate(bool forceUpdate = false);
std::unique_ptr<KeyFrameInterpolator>& ownedInheritedInterpolator()
{
return m_ownedInheritedInterpolator;
}
/// The live clone of `source` (an interpolator of this instance's source
/// artboard) that this instance's layouts run, created on first use and
/// kept until the artboard is destroyed so layouts that inherit it --
/// including those in nested artboards and list items -- can hold the
/// pointer. Null on a non-instance, or when no clone could be vended.
ScriptedInterpolator* statefulInterpolator(
const ScriptedInterpolator* source);
void calculateLayout();
bool canHaveOverrides() override { return true; }
bool advance(
float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::AdvanceNested | AdvanceFlags::Animate |
AdvanceFlags::NewFrame);
bool advanceInternal(
float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::AdvanceNested | AdvanceFlags::Animate |
AdvanceFlags::NewFrame);
// Does nothing, like the NestedArtboard and ArtboardComponentList
// overrides: nothing in an artboard resets at the end of a frame any more.
// Kept for callers outside the runtime.
void reset() override;
uint8_t drawOrderChangeCounter() { return m_drawOrderChangeCounter; }
Drawable* firstDrawable() { return m_FirstDrawable; };
void addScriptedObject(ScriptedObject* object);
/// Poll async work (image decodes, etc.) so promises resolve before
/// script callbacks run. Called at the top of advance().
void pollAsyncWork();
/// True while async work this artboard's scripting VMs started has yet
/// to be delivered, including the VMs of files its nested artboards were
/// bound in from. Scoped to those VMs: an unrelated file's slow fetch
/// doesn't count.
bool hasPendingAsyncWork();
// Draws with the color modulation set from each drawable's property of
// this key: a number as a gray level, a color as is. Levels multiply
// down through tagged drawables that hold others.
void drawModulated(
Renderer* renderer,
uint32_t propertyKey,
const File* keysFile = nullptr);
// keysFile is the file the visitor's keys were resolved in, for a caller
// whose instance does not know its own, as one a script made does not.
void drawInternal(
Renderer* renderer,
DrawVisitor visitor = nullptr,
void* visitorContext = nullptr,
const File* keysFile = nullptr);
// The file a visit's keys and names belong to.
const File* drawVisitorFile() const;
// Draws an artboard hosted inside this one, passing on the visitor this
// one is being drawn with so it reaches nested content of the same file.
// `bypassCache` draws vectors even when `hosted` caches as a bitmap.
void drawHosted(
Artboard* hosted,
Renderer* renderer,
bool bypassCache = false);
bool hasDrawVisitor() const { return m_drawVisitor != nullptr; }
void draw(Renderer* renderer) override;
/// Attach a watermark pre-roll. While it plays this artboard neither
/// animates nor draws. Set by File on the instances it vends when the
/// file's manifest carries a watermark.
void watermark(std::unique_ptr<Watermark> watermark);
Watermark* watermark() const { return m_watermark.get(); }
/// Advances the attached watermark, if any. Returns true while the frame
/// belongs to the watermark, meaning the caller must keep this artboard
/// frozen. Releases the watermark on the handover frame, so every frame
/// after that is a null check and the pre-roll never plays twice.
bool advanceWatermark(float elapsedSeconds);
// The actual vector-drawing body of drawInternal. Split out so the
// cache-as-bitmap hook (in drawInternal) can rasterize into an offscreen
// canvas without re-entering the hook, and so the standalone-root draw()
// path can bypass caching entirely.
void drawContent(
Renderer* renderer,
DrawVisitor visitor = nullptr,
void* visitorContext = nullptr);
// The drawable-walking body of drawContent, over the half-open range
// [first, stop). Split out so a layer mask can re-run it over its own
// bracketed span against an offscreen renderer. Reads the active visitor
// off m_drawVisitor, which drawContent has already installed, so the visit
// survives the recursion through drawMasked.
void drawDrawableRange(Renderer* renderer, Drawable* first, Drawable* stop);
// Union of paintedWorldBounds() over everything the half-open range
// [first, stop) would actually draw, using the same skip rules
// drawDrawableRange applies -- so what is measured is exactly what is
// drawn.
//
// `rasterScale` is the texels-per-local-unit the caller will rasterize at,
// used to express the slop margin an `approximate` member needs in device
// pixels. `*anyDrawn` reports whether anything in the range would draw at
// all, which replaces the cheaper probe it used to be.
//
// Returns `none` when no tight box is available, either because a member
// could not bound itself or because nothing measurable was painted; the
// caller must then fall back to a box it knows covers everything.
// *out is only written when the answer is not `none`.
BoundsFidelity rangeDrawBounds(
Drawable* first,
Drawable* stop,
float rasterScale,
AABB* out,
bool* anyDrawn) const;
// Rasterizes a masked range and its mask source, composites the two, and
// draws the result through `renderer`. False means "I did nothing" and the
// caller draws the range inline.
bool drawMasked(
Renderer* renderer,
Drawable* startMarker,
Drawable* endMarker);
void addToRenderPath(RenderPath* path, const Mat2D& transform);
void addToRawPath(RawPath& path, const Mat2D* transform);
void changed();
#ifdef TESTING
const ShapePaintPath* clipPath() { return &mutableRenderPaths().world; }
const ShapePaintPath* backgroundPath() { return &mutableRenderPaths().local; }
#endif
const std::vector<Core*>& objects() const { return m_Objects; }
template <typename T> TypedChildren<T> objects()
{
return TypedChildren<T>(Span<Core*>(m_Objects.data(), m_Objects.size()));
}
const std::vector<NestedArtboard*>& nestedArtboards() const
{
return m_NestedArtboards.view();
}
/// Whether this artboard holds nested artboards or artboard lists, which
/// make state machines of their own as they advance.
bool hostsArtboards() const { return !m_ArtboardHosts.empty(); }
/// Whether this artboard runs scripts: scripted components, scripts in its
/// state machines, or scripted converters on their data binds. Each
/// instance keeps its scripts' state. Walks the artboard on every call.
bool usesScripts() const;
const std::vector<ArtboardHost*>& artboardHosts() const
{
return m_ArtboardHosts;
}
const std::vector<ArtboardComponentList*>& artboardComponentLists() const
{
return m_ComponentLists.view();
}
rcp<DataContext> dataContext() { return dataBindContext(); }
#ifdef WITH_RIVE_SCRIPTING_LUAU
void scriptingVM(rcp<ScriptingVM> value) { m_scriptingVM = std::move(value); }
#endif
NestedArtboard* nestedArtboard(const std::string& name) const;
NestedArtboard* nestedArtboardAtPath(const std::string& path) const;
float originalWidth() const { return m_originalWidth; }
float originalHeight() const { return m_originalHeight; }
float layoutWidth() const;
float layoutHeight() const;
float layoutX() const;
float layoutY() const;
float pivotOriginX() const override { return originX(); }
float pivotOriginY() const override { return originY(); }
AABB bounds() const;
AABB worldBounds() const override;
Vec2D origin() const;
void xChanged() override;
void yChanged() override;
void originXChanged() override;
void originYChanged() override;
void resetSize()
{
width(m_originalWidth);
height(m_originalHeight);
}
// Can we hide these from the public? (they use playable)
bool isTranslucent() const;
bool isTranslucent(const LinearAnimation*) const;
bool isTranslucent(const LinearAnimationInstance*) const;
void dataContext(rcp<DataContext> dataContext);
void internalDataContext(rcp<DataContext> dataContext);
void clearDataContext();
void unbind();
void rebind() override;
void relinkDataContext() override;
void bindViewModelInstance(
rcp<ViewModelInstance> viewModelInstance,
rcp<DataContext> parent);
void bindViewModelInstance(rcp<ViewModelInstance> viewModelInstance);
void bindViewModelInstances(
std::vector<rcp<ViewModelInstance>> viewModelInstances,
rcp<DataContext> parent);
// Sets the main (non-global) view model instance in the data context
// without rebinding. Call bind() to apply. A global instance is routed to
// setGlobalViewModelInstance.
void setViewModelInstance(rcp<ViewModelInstance> viewModelInstance);
// Sets/replaces the global view model instance bound under the given global
// view model name without rebinding, preserving the main instance and the
// other globals' order. Returns false if the name does not match the
// instance's global view model. Call bind() to apply.
bool setGlobalViewModelInstance(
const std::string& name,
rcp<ViewModelInstance> viewModelInstance);
// Applies the current data context: rebinds the artboard's data binds.
// No-op if nothing has been set.
void bind();
// @returns the global view model instance currently bound under the given
// name, or nullptr if none has been set. Never creates.
rcp<ViewModelInstance> globalViewModelInstance(const std::string& name);
void rebuildDataBind(DataBind*) override;
bool hasAudio() const;
template <typename T = Component> T* find(const std::string& name)
{
for (auto object : m_Objects)
{
if (object != nullptr && object->is<T>() &&
object->as<T>()->name() == name)
{
return static_cast<T*>(object);
}
}
return nullptr;
}
template <typename T = Component> size_t count()
{
size_t count = 0;
for (auto object : m_Objects)
{
if (object != nullptr && object->is<T>())
{
count++;
}
}
return count;
}
template <typename T = Component> T* objectAt(size_t index)
{
size_t count = 0;
for (auto object : m_Objects)
{
if (object != nullptr && object->is<T>())
{
if (count++ == index)
{
return static_cast<T*>(object);
}
}
}
return nullptr;
}
int objectIndex(Core* component) const
{
int count = 0;
for (auto object : m_Objects)
{
if (object == component)
{
return count;
}
count++;
}
return -1;
}
template <typename T = Component> std::vector<T*> find()
{
std::vector<T*> results;
for (auto object : m_Objects)
{
if (object != nullptr && object->is<T>())
{
results.push_back(static_cast<T*>(object));
}
}
return results;
}
size_t animationCount() const
{
return animationSource()->ownAnimations().size();
}
std::string animationNameAt(size_t index) const;
/// Returns the count of FocusData objects that don't have a parent
/// FocusData within this artboard (i.e., "root" focus nodes from this
/// artboard's perspective).
size_t rootFocusDataCount() const;
/// Returns the FocusData at the given index among root FocusData objects.
/// Returns nullptr if index is out of bounds.
class FocusData* rootFocusDataAt(size_t index) const;
size_t stateMachineCount() const
{
return animationSource()->ownStateMachines().size();
}
std::string stateMachineNameAt(size_t index) const;
LinearAnimation* firstAnimation() const { return animation(0); }
LinearAnimation* animation(const std::string& name) const;
LinearAnimation* animation(size_t index) const;
StateMachine* firstStateMachine() const { return stateMachine(0); }
StateMachine* stateMachine(const std::string& name) const;
StateMachine* stateMachine(size_t index) const;
/// When provided, the designer has specified that this artboard should
/// always autoplay this StateMachine. Returns -1 if it was not
// provided.
int defaultStateMachineIndex() const;
/// Make an instance of this artboard. A non null factory reroutes the
/// instance's render resource creation (a deferred session facade);
/// nested instances inherit it.
template <typename T = ArtboardInstance>
std::unique_ptr<T> instance(Factory* factory = nullptr) const
{
std::unique_ptr<T> artboardClone(new T);
artboardClone->copy(*this);
artboardClone->m_Factory = factory != nullptr ? factory : m_Factory;
artboardClone->m_FrameOrigin = m_FrameOrigin;
artboardClone->m_IsInstance = true;
artboardClone->m_originalWidth = m_originalWidth;
artboardClone->m_originalHeight = m_originalHeight;
#ifdef WITH_RIVE_TOOLS
artboardClone->m_artboardId = m_artboardId;
// An instance of an instance borrows the same source, and already
// holds its count.
artboardClone->m_instanceCount = m_instanceCount;
#endif
artboardClone->m_artboardSource = isInstance() ? m_artboardSource : this;
cloneObjectDataBinds(this, artboardClone.get(), artboardClone.get());
std::vector<Core*>& cloneObjects = artboardClone->m_Objects;
// The clone ends up with exactly as many objects as the source, so size it
// once instead of letting push_back grow it geometrically — that left each
// instance holding up to its own object count again in unused capacity.
cloneObjects.reserve(m_Objects.size());
cloneObjects.push_back(artboardClone.get());
if (!m_Objects.empty())
{
// Skip first object (artboard).
auto itr = m_Objects.begin();
while (++itr != m_Objects.end())
{
auto object = *itr;
cloneObjects.push_back(object == nullptr ? nullptr : object->clone());
// For each object, clone its data bind objects and target their
// clones
cloneObjectDataBinds(object, cloneObjects.back(), artboardClone.get());
}
}
// Only now that the clone's binds are all in place: setting the
// context first would make addDataBind() eagerly bind and apply each
// clone as it arrives, mid-construction. The clone still binds for
// real later, through bind()/internalDataContext().
artboardClone->dataBindContext(dataBindContext());
if (factory != nullptr && factory != m_Factory)
{
// Nested clones instanced off the file level source during the
// clone loop; redo them on the override factory before
// initialize wires animations to them.
artboardClone->reinstanceNestedArtboards(factory);
}
if (artboardClone->initialize() != StatusCode::Ok)
{
artboardClone = nullptr;
}
assert(artboardClone->isInstance());
return artboardClone;
}
void reinstanceNestedArtboards(Factory* factory);
/// Returns true if the artboard is an instance of another
bool isInstance() const { return m_IsInstance; }
#ifdef WITH_RIVE_EDITOR
/// Mark this artboard as an instance. Editor-only setter so
/// `EditorFile::cloneArtboardInstance` can produce a clone that
/// the runtime treats as a normal instance (drives the
/// `isInstance()`-gated branches in update/draw paths). The
/// runtime's own `Artboard::instance()` template sets this
/// directly because it has friend access to `m_IsInstance`.
void setIsInstance(bool value) { m_IsInstance = value; }
/// Editor-mode pass-through to the private `addObject` so
/// `EditorFile::cloneArtboardInstance` can populate `m_Objects`
/// on a freshly-constructed clone (mirroring what `instance()`
/// does internally for `.riv`-loaded sources).
void editorAddObject(Core* object) { addObject(object); }
#endif
/// Returns true when the artboard will shift the origin from the top
/// left to the relative width/height of the artboard itself. This is
/// what the editor does visually when you change the origin value to
/// give context as to where the origin lies within the framed bounds.
bool frameOrigin() const { return m_FrameOrigin; }
/// When composing multiple artboards together in a common world-space,
/// it may be desireable to have them share the same space regardless of
/// origin offset from the bounding artboard. Set frameOrigin to false
/// to move the bounds relative to the origin instead of the origin
/// relative to the bounds.
void frameOrigin(bool value);
bool deserialize(uint16_t propertyKey, BinaryReader& reader) override
{
bool result = ArtboardBase::deserialize(propertyKey, reader);
switch (propertyKey)
{
case widthPropertyKey:
m_originalWidth = width();
break;
case heightPropertyKey:
m_originalHeight = height();
break;
default:
break;
}
return result;
}
StatusCode import(ImportStack& importStack) override;
// An artboard is slot 0 of its own list (Component::import), never a slot
// in the artboard read before it.
bool claimsArtboardSlot(ImportStack& importStack) const override
{
return false;
}
float volume() const;
void volume(float value);
// Opacity imposed by a host (e.g. a NestedArtboard) that renders this
// artboard as an instance. Kept separate from the `opacity` property so the
// artboard's own opacity remains free to animate / data-bind; the two are
// multiplied together when propagating opacity to this artboard's contents.
float hostOpacity() const { return m_hostOpacity; }
void hostOpacity(float value);
// Opacity propagated to this artboard's contents: the artboard's own
// (possibly animated) opacity folded with any host-imposed opacity.
float childOpacity() override { return renderOpacity() * m_hostOpacity; }
// True when the artboard has a non-identity rotation/scale of its own.
bool hasSelfTransform() const
{
return rotation() != 0.0f || scaleX() != 1.0f || scaleY() != 1.0f;
}
// The artboard's own rotation/scale, pivoted at the content-local origin
// (0,0). Applied on top of the frame-origin translation by both draw and
// hit-test so they stay consistent. Identity when hasSelfTransform() is
// false.
Mat2D selfTransform() const
{
Mat2D m = Mat2D::fromRotation(rotation());
m.scaleByValues(scaleX(), scaleY());
return m;
}
#ifdef EXTERNAL_RIVE_AUDIO_ENGINE
rcp<AudioEngine> audioEngine() const;
void audioEngine(rcp<AudioEngine> audioEngine);
#endif
#ifdef WITH_RIVE_LAYOUT
void propagateSize() override;
#endif
#ifdef RIVE_CANVAS
// Renders this artboard's content into the BitmapCache child's offscreen
// RenderCanvas (if the cache is missing/stale) and composites it into
// `renderer`. Returns false if caching is unavailable (no GPU render
// context / deferred host, zero size, or allocation failure), so the caller
// falls back to drawContent.
bool drawCachedAsBitmap(Renderer* renderer);
void renderIntoCanvas(
cmd::DeferredCanvasHost* deferredHost,
const offscreen::RasterPlan& plan,
offscreen::CachedRaster& raster);
// Rasterizes untransformed content into `raster` when stale; null without
// an offscreen host. `fixedSize` keeps a mesh's UVs stable; otherwise the
// size follows the on-screen scale.
rcp<RenderImage> rasterImage(
Renderer* renderer,
offscreen::CachedRaster& raster,
float resolution,
bool fixedSize,
offscreen::RasterPlan* planOut,
cmd::DeferredCanvasHost** hostOut);
// True when fully transparent; consumes the change like a draw would.
bool consumeInvisibleChange();
#endif
// drawContent without the self transform, applied at composite instead.
void drawContentUntransformed(Renderer* renderer);
// The self transform as seen from outside a raster drawn without it.
Mat2D compositeSelfTransform() const;
private:
float m_volume = 1.0f;
float m_hostOpacity = 1.0f;
bool m_skipSelfTransform = false;
// The BitmapCache child, collected in initialize(). Null unless the
// artboard has one; the object itself owns the offscreen render state
// (freed when the object is deleted).
BitmapCache* m_BitmapCache = nullptr;
#ifdef WITH_RIVE_TOOLS
ArtboardCallback m_layoutChangedCallback = nullptr;
ArtboardCallback m_layoutDirtyCallback = nullptr;
ArtboardCallback m_transformDirtyCallback = nullptr;
TestBoundsCallback m_testBoundsCallback = nullptr;
IsAncestorCallback m_isAncestorCallback = nullptr;
RootTransformCallback m_rootTransformCallback = nullptr;
public:
void* callbackUserData;
void onLayoutChanged(ArtboardCallback callback)
{
m_layoutChangedCallback = callback;
}
void onLayoutDirty(ArtboardCallback callback)
{
m_layoutDirtyCallback = callback;
addDirt(ComponentDirt::Components);
}
void onTransformDirty(ArtboardCallback callback)
{
m_transformDirtyCallback = callback;
addDirt(ComponentDirt::Components);
}
void onTestBounds(TestBoundsCallback callback)
{
m_testBoundsCallback = callback;
}
void onIsAncestor(IsAncestorCallback callback)
{
m_isAncestorCallback = callback;
}
void onRootTransform(RootTransformCallback callback)
{
m_rootTransformCallback = callback;
}
#endif
protected:
// The File backing this artboard, when it is an instance. Used to lazily
// create default global view model instances. The base Artboard (e.g. an
// editor-time artboard) has no backing file.
virtual rcp<const File> artboardFile() const;
};
class ArtboardInstance : public Artboard
{
public:
ArtboardInstance();
~ArtboardInstance() override;
/// Holds a reference to the File that vended this instance so the File
/// outlives the instance.
void file(rcp<const File> file);
/// @returns the File that vended this instance, if any. Used to lazily
/// create default global view model instances on demand.
rcp<const File> file() const;
std::unique_ptr<LinearAnimationInstance> animationAt(size_t index);
std::unique_ptr<LinearAnimationInstance> animationNamed(
const std::string& name);
std::unique_ptr<StateMachineInstance> stateMachineAt(size_t index);
std::unique_ptr<StateMachineInstance> stateMachineNamed(
const std::string& name);
/// When provided, the designer has specified that this artboard should
/// always autoplay this StateMachine instance. If it was not specified,
/// this returns nullptr.
std::unique_ptr<StateMachineInstance> defaultStateMachine();
// This attemps to always return *something*, in this search order:
// 1. default statemachine instance
// 2. first statemachine instance
// 3. first animation instance
// 4. nullptr
std::unique_ptr<Scene> defaultScene();
SMIInput* input(const std::string& name, const std::string& path);
template <typename InstType>
InstType* getNamedInput(const std::string& name, const std::string& path);
SMIBool* getBool(const std::string& name, const std::string& path);
SMINumber* getNumber(const std::string& name, const std::string& path);
SMITrigger* getTrigger(const std::string& name, const std::string& path);
TextValueRun* getTextRun(const std::string& name, const std::string& path);
protected:
rcp<const File> artboardFile() const override;
private:
rcp<const File> m_file;
};
} // namespace rive
#endif