| #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/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/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_map> | |
| #include <unordered_set> | |
| #include <vector> | |
| namespace rive | |
| { | |
| class KeyFrameInterpolator; | |
| class KeyFrame; | |
| class ArtboardComponentList; | |
| class ArtboardHost; | |
| class File; | |
| class Drawable; | |
| class Factory; | |
| 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 FocusManager; | |
| class SemanticManager; | |
| class SemanticNode; | |
| #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); | |
| #endif | |
| 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 | |
| std::vector<Core*> m_invalidObjects; | |
| std::vector<LinearAnimation*> m_Animations; | |
| std::vector<StateMachine*> m_StateMachines; | |
| std::vector<Component*> m_DependencyOrder; | |
| std::vector<Drawable*> m_Drawables; | |
| std::vector<ClippingShape*> m_clippingShapes; | |
| std::vector<DrawTarget*> m_DrawTargets; | |
| std::vector<NestedArtboard*> m_NestedArtboards; | |
| std::vector<ArtboardComponentList*> m_ComponentLists; | |
| std::vector<ArtboardHost*> m_ArtboardHosts; | |
| std::vector<Joystick*> m_Joysticks; | |
| std::vector<ResettingComponent*> m_Resettables; | |
| std::vector<ScriptedObject*> m_ScriptedObjects; | |
| std::vector<AdvancingComponent*> m_advancingComponents; | |
| rcp<DataContext> m_DataContext = nullptr; | |
| #ifdef WITH_RIVE_SCRIPTING | |
| [[maybe_unused]] ScriptingVMSlot m_scriptingVM = nullptr; | |
| #endif | |
| bool m_JoysticksApplyBeforeUpdate = true; | |
| unsigned int m_DirtDepth = 0; | |
| Factory* m_Factory = nullptr; | |
| Drawable* m_FirstDrawable = nullptr; | |
| bool m_IsInstance = false; | |
| bool m_FrameOrigin = true; | |
| std::unordered_set<LayoutComponent*> m_dirtyLayout; | |
| bool m_isCleaningDirtyLayouts = false; | |
| std::unique_ptr<KeyFrameInterpolator> m_ownedInheritedInterpolator; | |
| float m_originalWidth = 0; | |
| float m_originalHeight = 0; | |
| bool m_updatesOwnLayout = true; | |
| bool m_hostTransformMarkedDirty = false; | |
| bool m_didChange = true; | |
| 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; | |
| #endif | |
| static uint64_t sm_frameId; | |
| 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. | |
| mutable std::unordered_map<const KeyFrame*, DataBind*> | |
| m_keyFrameSourceBinds; | |
| mutable bool m_keyFrameSourceBindsBuilt = false; | |
| 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; | |
| #endif | |
| const Artboard* m_artboardSource = nullptr; | |
| #ifdef EXTERNAL_RIVE_AUDIO_ENGINE | |
| rcp<AudioEngine> m_audioEngine; | |
| #endif | |
| void sortDependencies(); | |
| void sortDrawOrder(); | |
| void clearRedundantOperations(); | |
| void updateRenderPath() override; | |
| void update(ComponentDirt value) override; | |
| public: | |
| static uint64_t frameId() { return sm_frameId; } | |
| #ifdef TESTING | |
| 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; | |
| 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(); | |
| // Semantic-only collapse for this artboard's semantic nodes. | |
| // When collapsing, walks the boundary's semantic subtree (O(K) semantic | |
| // nodes). When uncollapsing, falls back to m_Objects because | |
| // SemanticData::collapse(false) re-parents nodes outside the boundary. | |
| void collapseSemanticBoundary(bool value); | |
| /// 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; } | |
| StatusCode onAddedClean(CoreContext* context) override; | |
| void addDirtyDataBind(DataBind*) override; | |
| private: | |
| #ifdef TESTING | |
| public: | |
| Artboard(Factory* factory) : m_Factory(factory) { m_Clip = true; } | |
| #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; } | |
| Core* resolve(Id id) const override; | |
| #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-side iteration over registered drawables. Used by | |
| // CommandDispatcher::handleHitTest to walk components inside an | |
| // artboard back-to-front for component-level selection. Vector | |
| // order matches draw order (later index = higher z). | |
| const std::vector<Drawable*>& drawables() const { return m_Drawables; } | |
| // 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. | |
| void clearAnimations() { m_Animations.clear(); } | |
| void clearStateMachines() { m_StateMachines.clear(); } | |
| // 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(); } | |
| const std::vector<Component*>& dependencyOrder() const | |
| { | |
| return m_DependencyOrder; | |
| } | |
| // 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 | |
| #ifdef WITH_RIVE_TOOLS | |
| void artboardId(uint16_t id) { m_artboardId = id; } | |
| uint16_t artboardId() const { return m_artboardId; } | |
| #endif | |
| void artboardSource(const Artboard* artboard) | |
| { | |
| m_artboardSource = artboard; | |
| } | |
| const Artboard* artboardSource() const | |
| { | |
| return isInstance() ? m_artboardSource : 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&); | |
| void onComponentDirty(Component* component); | |
| /// 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); | |
| 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 cleanLayout(LayoutComponent* layoutComponent); | |
| LayoutData* takeLayoutData(); | |
| bool syncStyleChanges() override; | |
| void syncStyleChangesWithUpdate(bool forceUpdate = false); | |
| std::unique_ptr<KeyFrameInterpolator>& ownedInheritedInterpolator() | |
| { | |
| return m_ownedInheritedInterpolator; | |
| } | |
| 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); | |
| 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(); | |
| void drawInternal(Renderer* renderer); | |
| void draw(Renderer* renderer) override; | |
| 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; | |
| } | |
| const std::vector<ArtboardComponentList*> artboardComponentLists() const | |
| { | |
| return m_ComponentLists; | |
| } | |
| rcp<DataContext> dataContext() { return m_DataContext; } | |
| #ifdef WITH_RIVE_SCRIPTING_LUAU | |
| void scriptingVM(rcp<ScriptingVM> value) | |
| { | |
| m_scriptingVM = std::move(value); | |
| } | |
| #endif | |
| // Advances detached scripted view model instances (those with no parents), | |
| // which are not reachable from the bound view model tree. No-op when | |
| // scripting is disabled. Called at the end of each frame. | |
| void advanceScriptedViewModels(); | |
| 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 m_Animations.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 m_StateMachines.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_DataContext = m_DataContext; | |
| artboardClone->m_IsInstance = true; | |
| artboardClone->m_originalWidth = m_originalWidth; | |
| artboardClone->m_originalHeight = m_originalHeight; | |
| #ifdef WITH_RIVE_TOOLS | |
| artboardClone->m_artboardId = m_artboardId; | |
| #endif | |
| artboardClone->m_artboardSource = | |
| isInstance() ? m_artboardSource : this; | |
| cloneObjectDataBinds(this, artboardClone.get(), artboardClone.get()); | |
| std::vector<Core*>& cloneObjects = artboardClone->m_Objects; | |
| 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()); | |
| } | |
| } | |
| for (auto animation : m_Animations) | |
| { | |
| artboardClone->m_Animations.push_back(animation); | |
| } | |
| for (auto stateMachine : m_StateMachines) | |
| { | |
| artboardClone->m_StateMachines.push_back(stateMachine); | |
| } | |
| 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; | |
| 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 | |
| private: | |
| float m_volume = 1.0f; | |
| float m_hostOpacity = 1.0f; | |
| #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 |