blob: c88562ce08df39ad23c3f87abdf477287fe3ad9d [file]
#ifndef _RIVE_NESTED_ARTBOARD_HPP_
#define _RIVE_NESTED_ARTBOARD_HPP_
#include "rive/generated/nested_artboard_base.hpp"
#include "rive/artboard_host.hpp"
#include "rive/artboard_referencer.hpp"
#include "rive/data_bind_path_referencer.hpp"
#include "rive/data_bind/data_context.hpp"
#include "rive/viewmodel/viewmodel_instance_value.hpp"
#include "rive/hit_info.hpp"
#include "rive/input/focusable.hpp"
#include "rive/animation/listener_invocation.hpp"
#include "rive/span.hpp"
#include "rive/advancing_component.hpp"
#include "rive/resetting_component.hpp"
#include "rive/viewmodel/viewmodel_instance_artboard.hpp"
#include "rive/input/focus_node.hpp"
#include "rive/nested_artboard_host_flags.hpp"
#include "rive/refcnt.hpp"
#include "rive/file.hpp"
#include <stdio.h>
namespace rive
{
class ArtboardInstance;
class FocusManager;
class NestedAnimation;
class ComponentOrigin;
class NestedInput;
class NestedStateMachine;
class StateMachineInstance;
class NestedArtboard : public NestedArtboardBase,
public AdvancingComponent,
public ResettingComponent,
public ArtboardHost,
public ArtboardReferencer,
public Focusable
{
protected:
std::unique_ptr<ArtboardInstance> m_Instance; // may be null
std::unique_ptr<NestedStateMachine>
m_boundNestedStateMachine; // may be null
std::vector<NestedAnimation*> m_NestedAnimations;
File* m_file = nullptr;
rcp<ViewModelInstance> m_viewModelInstance = nullptr;
rcp<DataContext> m_dataContext = nullptr;
// VMI actively used for binding. Either the stateful child VMI (borrowed
// from children(); freed by the parent Artboard's m_Objects teardown) or a
// dynamically-created bound VMI for a different ViewModel (owned here, see
// m_ownsActiveVmi).
ViewModelInstance* m_activeViewModelInstance = nullptr;
// Global ViewModelInstance children — appended to the data context passed
// down to the wrapped artboard. The artboard's m_Objects owns the
// construction ref; this list holds an extra ref (see ref_rcp in
// onAddedClean) so it stays valid regardless of teardown ordering.
std::vector<rcp<ViewModelInstance>> m_globalViewModelInstances;
/// Structural scope for data-bound hosts only
rcp<FocusNode> m_focusScope;
private:
void clearNestedAnimations();
float m_cumulatedSeconds = 0;
// True if m_activeViewModelInstance is a dynamically-created bound VMI
// that must be unref'd by this NestedArtboard.
bool m_ownsActiveVmi = false;
NestedArtboardHostFlags m_hostFlags = NestedArtboardHostFlags::none;
void nest(Artboard* artboard);
void detectArtboardDataBinding();
bool tryScheduleBindStateful();
void bindStateful();
// Walks children() for the first ViewModelInstance child (the stateful
// component VMI authored in the editor). Returns nullptr if none.
ViewModelInstance* findStatefulChildVmi() const;
// Releases the current active VMI if owned, then assigns the new one.
void setActiveViewModelInstance(ViewModelInstance* vmi, bool owns);
/// Create/attach this host's persistent focus scope (data-bound hosts
/// only). With `place` true (full build pass) the scope is re-appended
/// under `parentNode` so append order matches the hierarchy walk; with
/// `place` false an already-registered scope is never moved.
void registerFocusScope(FocusManager* focusManager,
rcp<FocusNode> parentNode,
bool place);
public:
/// True when this host can receive runtime artboard swaps and therefore
/// owns (or will own) a persistent focus scope.
bool isArtboardDataBound() const
{
return enums::is_flag_set(m_hostFlags,
NestedArtboardHostFlags::artboardDataBound);
}
/// Register this host's focus scope (if data-bound) and rebuild the nested
/// instance focus tree under the parent FocusManager. Called from the
/// hierarchical focus-tree build (Artboard::buildFocusTree) and from the
/// data-bound artboard swap path.
/// @param fallbackParent Parent used when the host has no closest ancestor
/// FocusNode.
/// @param placeScope True only from the full build pass, which is the
/// ordering authority: the scope is re-appended at the walk's
/// current position. All other callers leave placement untouched.
/// @param forceRebuild When true (the default), always tear down and
/// rebuild the nested instance's focus subtree. When false, skip the
/// rebuild if the subtree already shares this manager, preserving
/// focus resting inside an untouched instance. The full build pass
/// passes false unless it just re-wired the manager (which rebuilds
/// at the root and requires re-homing).
void syncNestedFocusTree(rcp<FocusNode> fallbackParent,
bool placeScope = false,
bool forceRebuild = true);
NestedArtboard();
~NestedArtboard() override;
StatusCode onAddedClean(CoreContext* context) override;
void draw(Renderer* renderer) override;
bool willDraw() override;
Core* hitTest(HitInfo*, const Mat2D&) override;
void addNestedAnimation(NestedAnimation* nestedAnimation);
void updateArtboard(
ViewModelInstanceArtboard* viewModelInstanceArtboard) override;
int referencedArtboardId() override;
void referencedArtboard(Artboard* artboard) override;
size_t artboardCount() override { return 1; }
int type() const override { return coreType(); }
ArtboardInstance* artboardInstance(int index = 0) override
{
return m_Instance.get();
}
Artboard* sourceArtboard() { return m_referencedArtboard; }
// Pushes the optional ComponentOrigin child's origin (if any) onto the
// mounted artboard instance. No-op when there is no override child, so
// nested artboards without an override carry no per-instance origin state.
void applyOriginOverride();
StatusCode import(ImportStack& importStack) override;
Core* clone() const override;
bool collapse(bool value) override;
void update(ComponentDirt value) override;
bool hasNestedStateMachines() const;
Span<NestedAnimation*> nestedAnimations();
NestedArtboard* nestedArtboard(std::string name) const;
NestedStateMachine* stateMachine(std::string name) const;
NestedInput* input(std::string name) const;
NestedInput* input(std::string name, std::string stateMachineName) const;
Vec2D measureLayout(float width,
LayoutMeasureMode widthMode,
float height,
LayoutMeasureMode heightMode) override;
void controlSize(Vec2D size,
LayoutScaleType widthScaleType,
LayoutScaleType heightScaleType,
LayoutDirection direction) override;
/// Convert a world space (relative to the artboard that this
/// NestedArtboard is a child of) to the local space of the Artboard
/// nested within. Returns true when the conversion succeeds, and false
/// when one is not possible.
bool worldToLocal(Vec2D world, Vec2D* local);
void decodeDataBindPathIds(Span<const uint8_t> value) override;
void copyDataBindPathIds(const NestedArtboardBase& object) override;
void bindViewModelInstance(rcp<ViewModelInstance> viewModelInstance,
rcp<DataContext> parent) override;
void internalDataContext(rcp<DataContext> dataContext) override;
void relinkDataContext(rcp<ViewModelInstance> viewModelInstance) override;
void clearDataContext() override;
void unbind() override;
void updateDataBinds() override;
float calculateLocalElapsedSeconds(float elapsedSeconds);
bool advanceComponent(float elapsedSeconds,
AdvanceFlags flags = AdvanceFlags::Animate |
AdvanceFlags::NewFrame) override;
void reset() override;
Artboard* parentArtboard() override { return artboard(); }
Vec2D hostTransformPoint(const Vec2D&, ArtboardInstance*) override;
Mat2D worldTransformForArtboard(ArtboardInstance*) override;
bool hitTestHost(const Vec2D& position,
bool skipOnUnclipped,
ArtboardInstance* artboard) override;
void markHostTransformDirty() override { markTransformDirty(); }
void file(File*) override;
File* file() const override;
Component* hostComponent() override { return this; }
// Focusable interface - delegates to nested state machines
bool keyInput(Key, KeyModifiers, bool, bool) override { return false; };
bool textInput(const std::string&) override { return false; };
bool gamepadDispatch(const ListenerInvocation&,
ScriptedDrawable** = nullptr) override
{
return false;
}
void focused() override {}
void blurred() override {}
Artboard* focusableArtboard() const override { return artboard(); }
};
} // namespace rive
#endif