| #ifndef _RIVE_STATE_MACHINE_INSTANCE_HPP_ | |
| #define _RIVE_STATE_MACHINE_INSTANCE_HPP_ | |
| #include <string> | |
| #include <stddef.h> | |
| #include <vector> | |
| #include <unordered_map> | |
| #include "rive/advancing_component.hpp" | |
| #include "rive/animation/gamepad_listener_group.hpp" | |
| #include "rive/animation/keyboard_listener_group.hpp" | |
| #include "rive/animation/semantic_listener_group.hpp" | |
| #include "rive/animation/linear_animation_instance.hpp" | |
| #include "rive/animation/state_instance.hpp" | |
| #include "rive/animation/state_transition.hpp" | |
| #include "rive/core/field_types/core_callback_type.hpp" | |
| #include "rive/hit_result.hpp" | |
| #include "rive/listener_type.hpp" | |
| #include "rive/nested_animation.hpp" | |
| #include "rive/scene.hpp" | |
| #include "rive/data_bind/bindable_property_number.hpp" | |
| #include "rive/data_bind/data_bind_container.hpp" | |
| #include "rive/input/focusable.hpp" | |
| #include "rive/input/focus_manager.hpp" | |
| #include "rive/lazy_vector.hpp" | |
| #include "rive/semantic/semantic_manager.hpp" | |
| #include "rive/sidecar.hpp" | |
| namespace rive | |
| { | |
| class FocusData; | |
| class FocusListenerGroup; | |
| class StateMachine; | |
| class LayerState; | |
| class SMIInput; | |
| class ArtboardInstance; | |
| class SMIBool; | |
| class SMINumber; | |
| class SMITrigger; | |
| class Shape; | |
| class StateMachineLayerInstance; | |
| class HitComponent; | |
| class Text; | |
| class TextStyleReference; | |
| class HitShape; | |
| class ListenerGroup; | |
| class NestedArtboard; | |
| class NestedEventListener; | |
| class NestedEventNotifier; | |
| class Event; | |
| class KeyedProperty; | |
| class EventReport; | |
| class DataBind; | |
| class BindableProperty; | |
| class ViewModelInstanceValue; | |
| class StateInstance; | |
| class HitDrawable; | |
| class HitNestedArtboard; | |
| class HitComponentList; | |
| class HostDispatchScope; | |
| class DraggableProxy; | |
| class ListenerViewModel; | |
| class ScriptedListenerAction; | |
| class ScriptedDrawable; | |
| // Cold state clusters, defined in state_machine_instance_clusters.hpp. Each is | |
| // reached through a Sidecar below and stays unallocated until the feature it | |
| // covers is actually used by the file. Left as forward declarations here so the | |
| // cluster header's includes stay out of this one. | |
| struct SMIReporting; | |
| struct SMIBindables; | |
| struct SMIInputExtras; | |
| struct SMIScripting; | |
| typedef void (*DataBindChanged)(); | |
| #ifdef WITH_RIVE_TOOLS | |
| class StateMachineInstance; | |
| typedef void (*InputChanged)(StateMachineInstance*, uint64_t); | |
| #endif | |
| class StateMachineInstance : public Scene, | |
| public NestedEventNotifier, | |
| public NestedEventListener, | |
| public DataBindContainer | |
| { | |
| friend class SMIInput; | |
| friend class KeyedProperty; | |
| friend class HitComponent; | |
| friend class HitNestedArtboard; | |
| friend class HitComponentList; | |
| friend class HostDispatchScope; | |
| friend class StateMachineLayerInstance; | |
| private: | |
| /// Provide a hitListener if you want to process a down or an up for the | |
| /// pointer position too. | |
| HitResult updateListeners( | |
| Vec2D position, | |
| ListenerType hitListener, | |
| int pointerId = 0, | |
| float timeStamp = 0, | |
| PointerButton button = PointerButton::primary); | |
| template <typename SMType, typename InstType> | |
| InstType* getNamedInput(const std::string& name) const; | |
| void notifyEventListeners( | |
| const std::vector<EventReport>& events, | |
| NestedArtboard* source); | |
| void sortHitComponents(); | |
| /// Maps a host-space position into artboard content space. False when a | |
| /// degenerate self transform has collapsed the contents. | |
| bool mapToContentSpace(Vec2D& position, Vec2D* delta = nullptr) const; | |
| double randomValue(); | |
| StateTransition* findRandomTransition( | |
| StateInstance* stateFromInstance, | |
| StateMachineLayerInstance* layerInstance); | |
| StateTransition* findAllowedTransition( | |
| StateInstance* stateFromInstance, | |
| StateMachineLayerInstance* layerInstance); | |
| // Ensures the current data context holds an instance for the artboard's | |
| // main view model and for every global view model in the file, creating | |
| // (completing) any that are missing before the context is applied. | |
| void completeViewModelInstances(); | |
| /// With captureHits, the hit drawables the target resolves to are | |
| /// collected there instead of taking listenerGroup as a normal listener. | |
| void addToHitLookup( | |
| Component* target, | |
| bool isLayoutComponent, | |
| std::unordered_map<Component*, HitDrawable*>& hitLookup, | |
| ListenerGroup* listenerGroup, | |
| bool isOpaque, | |
| std::vector<HitDrawable*>* captureHits = nullptr); | |
| /// This machine's own capture listeners, see capturePointer. | |
| HitResult captureLocalPointer( | |
| Vec2D position, | |
| ListenerType hitType, | |
| int pointerId, | |
| float timeStamp, | |
| PointerButton button, | |
| bool collapsed); | |
| /// A capture listener on scope took a press: turns off, for pointerId, | |
| /// everything under scope until that pointer's release. | |
| void claimPointer(Component* scope, int pointerId, PointerButton button); | |
| /// Lets go of pointerId's claims here (only those taken with button, | |
| /// unless anyButton), turning back on what they turned off. True if | |
| /// there were any. | |
| bool releasePointerClaims( | |
| int pointerId, | |
| PointerButton button, | |
| bool anyButton); | |
| /// Turns off again what pointerId's claims still hold, after something | |
| /// turned on more than a released claim had (enablePointerEvents, or a | |
| /// claim on the same scope for another button). Everything, while the | |
| /// pointer is off from outside (isPointerDisabled). | |
| void applyPointerClaims(int pointerId); | |
| public: | |
| StateMachineInstance(const StateMachine* machine, ArtboardInstance* instance); | |
| StateMachineInstance(StateMachineInstance const&) = delete; | |
| ~StateMachineInstance() override; | |
| void markNeedsAdvance(); | |
| // Advance the state machine by the specified time. Returns true if the | |
| // state machine will continue to animate after this advance. | |
| bool advance(float seconds, bool newFrame); | |
| bool advance(float seconds) { return advance(seconds, true); } | |
| // Returns true when the StateMachineInstance has more data to process. | |
| bool needsAdvance() const; | |
| void resetState(); | |
| // Returns a pointer to the instance's stateMachine | |
| const StateMachine* stateMachine() const { return m_machine; } | |
| size_t inputCount() const override { return m_inputInstances.size(); } | |
| SMIInput* input(size_t index) const override; | |
| SMIBool* getBool(const std::string& name) const override; | |
| SMINumber* getNumber(const std::string& name) const override; | |
| SMITrigger* getTrigger(const std::string& name) const override; | |
| void bindViewModelInstance(rcp<ViewModelInstance> viewModelInstance) override; | |
| // 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 artboard + state machine data | |
| // binds in a single pass. 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 bindDataContext(rcp<DataContext> dataContext); | |
| void inheritDataContext(rcp<DataContext> dataContext); | |
| void dataContext(rcp<DataContext> dataContext); | |
| rcp<DataContext> dataContext() const { return dataBindContext(); } | |
| void rebind() override; | |
| size_t currentAnimationCount() const; | |
| const LinearAnimationInstance* currentAnimationByIndex(size_t index) const; | |
| // The number of state changes that occurred across all layers on the | |
| // previous advance. | |
| std::size_t stateChangedCount() const; | |
| // Returns the state name for states that changed in layers on the | |
| // previously called advance. If the index of out of range, it returns | |
| // the empty string. | |
| const LayerState* stateChangedByIndex(size_t index) const; | |
| bool advanceAndApply(float secs) override; | |
| // Advances without playing the file's watermark, for a script driving a | |
| // nested scripted artboard. advanceViewModels no longer changes anything: | |
| // nothing consumes view model values at the end of a frame any more. | |
| bool advanceAndApply(float secs, bool advanceViewModels); | |
| // Does nothing: nothing resets at the end of a frame any more. Kept for | |
| // callers outside the runtime. | |
| void reset(); | |
| std::string name() const override; | |
| HitResult pointerMove(Vec2D position, float timeStamp = 0, int pointerId = 0) | |
| override; | |
| HitResult pointerDown( | |
| Vec2D position, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary) override; | |
| HitResult pointerUp( | |
| Vec2D position, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary) override; | |
| HitResult pointerExit(Vec2D position, int pointerId = 0) override; | |
| /// A down, up or move at `position` that can't land on anything here, as | |
| /// when a host draws this artboard through a mesh and the pointer is past | |
| /// its edge. Hover unwinds and presses miss, but a drag in progress keeps | |
| /// following `position`. A dragStart or dragEnd, from a host's drag, still | |
| /// turns this machine's pointers off or back on, and misses the same way. | |
| HitResult pointerMiss( | |
| Vec2D position, | |
| ListenerType hitType, | |
| float timeStamp = 0, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary); | |
| /// Whether a pointerMiss is being processed, including the drag passes it | |
| /// sets off. Hosted machines then miss too rather than see the pointer | |
| /// exit, which would end their drags. | |
| bool inPointerMiss() const { return m_inPointerMiss; } | |
| HitResult dragStart( | |
| Vec2D position, | |
| float timeStamp = 0, | |
| bool disablePointer = true, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary); | |
| HitResult dragEnd( | |
| Vec2D position, | |
| float timeStamp = 0, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary); | |
| HitResult pointerScroll( | |
| Vec2D position, | |
| const ScrollEvent& event, | |
| float timeStamp = 0, | |
| int pointerId = 0) override; | |
| /// Whether anything under position would move for this scroll event. Lets | |
| /// a caller chain outward without committing the event first. | |
| bool wantsScroll(Vec2D position, const ScrollEvent& event); | |
| /// Whether a scroll gesture is latched here or in a nested artboard. | |
| bool hasScrollLatch(); | |
| /// Whether an interactive scroll view sits under position at all, even | |
| /// one that cannot move. Keeps a gesture from escaping to the host. | |
| bool hasScrollTargetAt(Vec2D position); | |
| /// Whether an opaque hit that cannot scroll sits under position. It | |
| /// blocks scroll to whatever lies behind it, as hitOpaque does for | |
| /// pointer events. | |
| bool scrollOccludedAt(Vec2D position); | |
| /// Ends any scroll gesture in flight here, latch included. | |
| void cancelScroll(); | |
| /// Whether a listener under position responds to presses of button, | |
| /// looking through nested artboards. Lets a host keep a mouse button for | |
| /// its own use wherever nothing in the scene wants it. | |
| bool listensToButtonAt(Vec2D position, PointerButton button); | |
| /// The capture phase of a pointer event: this machine's capture listeners | |
| /// and then those of every machine nested in it, outermost first, all | |
| /// ahead of the normal phase. A capture listener that includes a press | |
| /// type and is hovered on a down claims that pointer's gesture: until its | |
| /// release, normal handling under the listener's target -- its hit | |
| /// components and whole nested machines -- is off for that pointer. | |
| /// A capture target under an opaque hit drawn above it, from outside its | |
| /// own target, doesn't fire. | |
| /// | |
| /// The pointer methods run this themselves, once, at the root of the | |
| /// dispatch; a host only calls it when it runs the top level's normal | |
| /// phase itself (see captureRunByHost). Returns hit when a capture | |
| /// listener was under the pointer. | |
| HitResult capturePointer( | |
| Vec2D position, | |
| ListenerType hitType, | |
| int pointerId = 0, | |
| float timeStamp = 0, | |
| PointerButton button = PointerButton::primary); | |
| /// Whether this machine, or one nested in it, has a capture-phase | |
| /// pointer listener. | |
| bool hasCaptureListeners(); | |
| /// Whether an opaque hit under position hides what lies below it from | |
| /// capture listeners, looking through nested artboards. | |
| bool pointerOccludedAt(Vec2D position); | |
| /// For a host that runs this machine's capture phase from its own top | |
| /// level, as the editor does from Dart: the pointer methods then never | |
| /// start one here. | |
| void captureRunByHost(bool value) { m_captureRunByHost = value; } | |
| /// Live instances, process-wide, with a capture-phase pointer listener of | |
| /// their own. While it is 0 no machine runs a capture phase, so a host | |
| /// running its own can skip reaching into nested machines. | |
| static uint32_t liveCaptureMachines(); | |
| /// Tells every machine that the trees holding capture listeners may have | |
| /// changed, so the next dispatch from a root asks its tree again. Building | |
| /// or destroying a machine with one already does this; a host that puts | |
| /// an existing machine back into a tree (a list reusing a pooled row) has | |
| /// to. | |
| static void captureTopologyChanged(); | |
| bool tryChangeState(); | |
| /// Makes this (a text style's state machine, instanced for one Text) | |
| /// serve the runs of [text] drawn with [style]: its pointer listeners | |
| /// with no target listen to all of them as one target, and pointers | |
| /// arrive in the Text's artboard space, unmapped (the style artboard's | |
| /// own origin and transform mean nothing for a run). | |
| void serveTextStyle(Text* text, const TextStyleReference* style); | |
| bool hitTest(Vec2D position) const; | |
| /// hitTest, except that a scripted layout counts only inside its own | |
| /// box. Scripts are handed every pointer event and bounds-check | |
| /// themselves, so hitTest has to say yes everywhere once one listens; | |
| /// a host deciding whether a press is the scene's at all (or the | |
| /// window's, say) wants the box. A scripted drawable with no box still | |
| /// counts everywhere. | |
| bool hitTestBounded(Vec2D position) const; | |
| private: | |
| bool hitTestInternal(Vec2D position, bool bounded) const; | |
| /// hasCaptureListeners for the root of a dispatch, cached until | |
| /// captureTopologyChanged: the uncached walk visits every machine in the | |
| /// tree, on every pointer event. | |
| bool treeHasCaptureListeners(); | |
| public: | |
| float durationSeconds() const override { return -1; } | |
| Loop loop() const override { return Loop::oneShot; } | |
| bool isTranslucent() const override { return true; } | |
| /// Allow anything referencing a concrete StateMachineInstace access to | |
| /// the backing artboard (explicitly not allowed on Scenes). | |
| Artboard* artboard() const { return m_artboardInstance; } | |
| void setParentStateMachineInstance(StateMachineInstance* instance) | |
| { | |
| m_parentStateMachineInstance = instance; | |
| } | |
| StateMachineInstance* parentStateMachineInstance() | |
| { | |
| return m_parentStateMachineInstance; | |
| } | |
| void setParentNestedArtboard(NestedArtboard* artboard) | |
| { | |
| m_parentNestedArtboard = artboard; | |
| } | |
| NestedArtboard* parentNestedArtboard() { return m_parentNestedArtboard; } | |
| void notify(const std::vector<EventReport>& events, NestedArtboard* context) | |
| override; | |
| void notifyListenerViewModels(const std::vector<ListenerViewModel*>& events); | |
| /// Tracks an event that reported, will be cleared at the end of the next | |
| /// advance. | |
| void reportEvent(Event* event, float secondsDelay = 0.0f) override; | |
| void applyEvents(); | |
| void reportListenerViewModel(ListenerViewModel*); | |
| /// Gets the number of events that reported since the last advance. | |
| std::size_t reportedEventCount() const; | |
| /// Gets a reported event at an index < reportedEventCount(). | |
| const EventReport reportedEventAt(std::size_t index) const; | |
| bool playsAudio() override { return true; } | |
| BindableProperty* bindablePropertyInstance( | |
| BindableProperty* bindableProperty) const; | |
| DataBind* bindableDataBindToSource(BindableProperty* bindableProperty) const; | |
| DataBind* bindableDataBindToTarget(BindableProperty* bindableProperty) const; | |
| /// Whether a change to `value` (a trigger fire, or any change a "changed" | |
| /// condition reads) is still pending for `layer`. A change is pending for | |
| /// one frame: until the end of the first frame in which `layer` searched | |
| /// its transitions after it happened, so it is gone even if no transition | |
| /// could take it then. A layer searched before a later one made the change | |
| /// gets it in its next search. And within that frame only until a | |
| /// transition `layer` takes has read the value (useViewModelValue). | |
| /// Changes made before this instance was created or reset, or while a host | |
| /// above it was collapsed or paused, are never pending for it. Except when | |
| /// it is created or reset while another machine advances (a list making | |
| /// its rows' machines): what is pending for that machine then is pending | |
| /// for it too. | |
| bool viewModelValueChanged( | |
| const ViewModelInstanceValue* value, | |
| const StateMachineLayerInstance* layer) const; | |
| /// Whether a fire of `trigger` happened after this instance last handled | |
| /// its view model listeners' reports (or was created or reset). A listener | |
| /// that binds then reports it, so a fire made before it was listening, by a | |
| /// script's init() or the host, still reaches it, while one a listener | |
| /// would already have handled isn't replayed. | |
| bool viewModelTriggerPendingForListeners( | |
| const ViewModelInstanceValue* trigger) const; | |
| /// Whether a change at `changeSequence` (ViewModelInstanceValue's change | |
| /// sequence) is still pending for `layer`'s transitions: the frame window | |
| /// of viewModelValueChanged, without its per-layer use. How a component's | |
| /// trigger compared as a number in a condition reads as fired. Without a | |
| /// layer, the window of the instance's earliest layer. | |
| bool changePending( | |
| uint64_t changeSequence, | |
| const StateMachineLayerInstance* layer = nullptr) const; | |
| /// Records that a transition `layer` took read `value`, so its pending | |
| /// change can't satisfy another of the layer's transitions this frame. | |
| void useViewModelValue( | |
| const ViewModelInstanceValue* value, | |
| const StateMachineLayerInstance* layer) const; | |
| /// Find the per-instance BindablePropertyNumber for the given shared | |
| /// StateTransition and property key (e.g. durationPropertyKey). | |
| /// Returns nullptr if no binding exists. | |
| BindablePropertyNumber* findTransitionPropertyInstance( | |
| const StateTransition* transition, | |
| uint32_t propertyKey) const; | |
| bool hasListeners() { return m_hitComponents.size() > 0; } | |
| bool hasFocusNodes(); | |
| bool focusNext(); | |
| bool focusPrevious(); | |
| /// Move focus to the nearest focus stop in the given direction from the | |
| /// primary focus. Returns false when nothing is focused or no candidate | |
| /// lies that way. | |
| bool focusLeft(); | |
| bool focusRight(); | |
| bool focusUp(); | |
| bool focusDown(); | |
| void clearFocus(); | |
| /// Route a key event to the focused element via the active focus manager. | |
| /// Returns true if the event was handled. | |
| bool keyInput(Key key, KeyModifiers modifiers, bool isPressed, bool isRepeat); | |
| /// Route committed text (a typed character, an Input Method Editor (IME) | |
| /// commit, a paste) to the focused element via the active focus manager. | |
| /// Returns true if the text was handled. | |
| bool textInput(const std::string& text); | |
| /// The selected text of the focused element, via the active focus manager. | |
| /// Empty when nothing with a selection is focused. | |
| std::string selectedText() const; | |
| void clearDataContext(); | |
| void relinkDataContext() override; | |
| void rebuildDataBind(DataBind*) override; | |
| // The artboard bound through this machine is not a dependent of the | |
| // context itself, so context hooks reach it from here. | |
| void mainViewModelInstanceChanged() override; | |
| void updateDataBinds(bool applyTargetToSource = true) override; | |
| void addDirtyDataBind(DataBind* dataBind) override; | |
| void dataContextChanged() override; | |
| /// Whether this machine's per-frame work as a list row (advance, | |
| /// tryChangeState and updateDataBinds) would currently do nothing. See | |
| /// Artboard::rowQuietState. | |
| AdvancingComponent::QuietState rowQuietState(); | |
| // Something may have changed this machine: wake its artboard if a host | |
| // had it quiet. | |
| void wakeRow(); | |
| void dropInstanceValueBindsTargeting(Core* target) override; | |
| void internalDataContext(rcp<DataContext> dataContext); | |
| ScriptedObject* scriptedObject(const ScriptedObject*) const; | |
| /// Queue a focus event for deferred execution during advance(). | |
| void queueFocusEvent(FocusListenerGroup* group, bool isFocus); | |
| /// Queue a semantic event for deferred execution during advance(). | |
| void queueSemanticEvent( | |
| SemanticListenerGroup* group, | |
| SemanticActionType actionType); | |
| /// Fire a semantic action on the SemanticData with the given node ID. | |
| void fireSemanticAction( | |
| uint32_t semanticNodeId, | |
| SemanticActionType actionType); | |
| FocusManager* focusManager(); | |
| /// Const overload of [focusManager], used by read-only consumers such as | |
| /// condition evaluation. | |
| const FocusManager* focusManager() const; | |
| /// Parses embedder gamepad batch bytes (same format as JS | |
| /// `GAMEPAD_BATCH_VERSION` in `registerGamepadInteractions`). Invokes | |
| /// `focusManager()->gamepadDispatch` for each record. Returns false if the | |
| /// buffer is invalid or truncated. | |
| bool submitGamepadsFromBuffer(const uint8_t* data, size_t length); | |
| /// Forward `invocation` to every `ScriptedDrawable` in this state machine | |
| /// whose script declares a gamepad handler (`wantsGamePad()`), skipping | |
| /// `alreadyDispatched` (typically the focus-tree recipient) so a script | |
| /// is never invoked twice for the same event. Used by gamepad dispatch | |
| /// after the focus bubble so non-focused scripts can still react. | |
| HitResult broadcastGamepadToScriptedDrawables( | |
| const ListenerInvocation& invocation, | |
| ScriptedDrawable* alreadyDispatched); | |
| /// Set an external focus manager to use instead of the internal one. | |
| /// This is used when a nested artboard should share focus with its parent. | |
| /// If the focus tree was already built with a different manager, it will | |
| /// be rebuilt with the new manager. | |
| void setExternalFocusManager(FocusManager* manager); | |
| /// Set focus to a specific FocusData's node. | |
| void setFocus(FocusData* focusData); | |
| /// Queue a focus change requested by a FocusAction, to be applied once the | |
| /// artboard's components are up to date for the frame. See | |
| /// FocusManager::PendingFocusRequest for why this is deferred rather than | |
| /// applied where the action runs. | |
| void queueFocusTarget(FocusData* focusData); | |
| void queueClearFocus(); | |
| /// [traversalKind]: 0=next, 1=previous, 2=up, 3=down, 4=left, 5=right | |
| /// (sync with FocusActionTraversal's traversalKind property). | |
| void queueFocusTraversal(uint32_t traversalKind); | |
| /// Snapshot of the current focus state. Designed for host polling (e.g. | |
| /// deciding whether to show a soft keyboard / IME). Cheap to query; | |
| /// future fields (e.g. keyboard type) will be added additively. | |
| struct FocusState | |
| { | |
| /// True if any element currently holds focus in this state machine's | |
| /// active focus manager (internal or external). | |
| bool hasFocus = false; | |
| /// True if the focused element accepts keyboard input — i.e. a | |
| /// TextInput, or a FocusData target with registered key/text-input | |
| /// listeners. False when nothing is focused. | |
| bool expectsKeyboardInput = false; | |
| }; | |
| FocusState focusState() const; | |
| /// Get the semantic manager for this state machine instance. | |
| /// Returns the external manager if set, otherwise the internal one. | |
| /// Returns nullptr if semantics has not been enabled. Out of line because | |
| /// both managers live in the SMIInputExtras cluster. | |
| SemanticManager* semanticManager() const; | |
| /// Enable semantics for this state machine instance. Creates the | |
| /// internal SemanticManager (if no external one was set) and builds | |
| /// the semantic tree. This is a no-op if semantics is already enabled. | |
| void enableSemantics(); | |
| /// Set an external semantic manager to use instead of the internal one. | |
| /// Used when a nested artboard should share the parent's semantic tree. | |
| /// Rebuilds the semantic tree with the new manager if already built. | |
| /// @param parentNode If provided, the rebuilt tree is attached under it; | |
| /// nullptr attaches at the manager root. | |
| void setExternalSemanticManager( | |
| SemanticManager* manager, | |
| rcp<SemanticNode> parentNode = nullptr); | |
| #ifdef TESTING | |
| size_t hitComponentsCount() { return m_hitComponents.size(); }; | |
| HitComponent* hitComponent(size_t index) | |
| { | |
| if (index < m_hitComponents.size()) | |
| { | |
| return m_hitComponents[index].get(); | |
| } | |
| return nullptr; | |
| } | |
| const LayerState* layerState(size_t index); | |
| // Whether the layer would skip its next advance (see | |
| // StateMachineLayerInstance::isFrozen), given nothing changes before it. | |
| bool isLayerFrozen(size_t index) const; | |
| // Whether this instance tracks view model changes at all, and whether it | |
| // keeps a change window per layer (see SMIBindables::layerChanges). | |
| bool tracksViewModelChanges() const { return bindables() != nullptr; } | |
| bool hasLayerChangeWindows() const; | |
| // Transition searches skipped because the layer had settled, across every | |
| // instance (see StateMachineLayerInstance::updateState). | |
| static uint64_t sm_settledLayerSkips; | |
| // Layer advances skipped because the layer was frozen: settled in a state | |
| // that keys nothing and waits on no exit time. | |
| static uint64_t sm_frozenLayerAdvances; | |
| // The subset of those resting on a pose whose values were already in place | |
| // (each one verified against the artboard). | |
| static uint64_t sm_frozenPoseAdvances; | |
| // Machines visited by capturePointer, across every instance. Stays put | |
| // while nothing in the process has a capture listener. | |
| static uint64_t sm_capturePassVisits; | |
| #endif | |
| void enablePointerEvents(int pointerId = 0); | |
| void disablePointerEvents(int pointerId = 0); | |
| /// Whether disablePointerEvents turned pointerId off from outside this | |
| /// machine (a host's drag or capture claim, or the embedder), and | |
| /// nothing has turned it back on. | |
| bool isPointerDisabled(int pointerId) const; | |
| /// The exit a host sends when something it draws above this machine's | |
| /// contents covers the pointer, which hasn't left. Unlike pointerExit, it | |
| /// leaves the pointer off if it was: the drag or claim that turned it off | |
| /// still holds. | |
| HitResult hoverExit(Vec2D position, int pointerId = 0); | |
| void dispose(); | |
| private: | |
| const StateMachine* m_machine; | |
| std::vector<SMIInput*> m_inputInstances; // we own each pointer | |
| StateMachineLayerInstance* m_layers = nullptr; | |
| // Set once from machine->layerCount() and never mutated; a machine has far | |
| // fewer than 65536 layers. Kept adjacent to the other scalars so they | |
| // share one 8 B slot with m_advanceChangeSerial instead of opening more. | |
| uint16_t m_layerCount = 0; | |
| uint8_t m_drawOrderChangeCounter = 0; | |
| // One bit each, sharing the byte after m_drawOrderChangeCounter; bit-fields | |
| // can't have default member initializers before C++20, so the constructor | |
| // sets them. | |
| bool m_needsAdvance : 1; | |
| // Settled layers, see StateMachineLayerInstance::updateState: a bind was | |
| // dirtied and not yet applied, so bindables may still hold old values. | |
| bool m_bindsPending : 1; | |
| // Some layer is settled, so unsettleLayers has work to do. | |
| bool m_anySettled : 1; | |
| // Set as the destructor starts. The artboard may already be gone (a host | |
| // can destroy it first), so nothing may wake its row from then on. | |
| bool m_destroying : 1; | |
| // Pointer positions arrive in the space of the Text this state machine | |
| // serves (see serveTextStyle), not mapped into the artboard's content. | |
| bool m_hostSpacePointers : 1; | |
| // Set while a host machine forwards a pointer event here: the host already | |
| // ran the capture phase over this machine, so updateListeners doesn't. | |
| bool m_hostDispatching : 1; | |
| // See captureRunByHost. | |
| bool m_captureRunByHost : 1; | |
| // See inPointerMiss. | |
| bool m_inPointerMiss : 1; | |
| // The artboard's changeSerial() when the last advance started. A layer | |
| // resting on a pose re-applies it only if the serial moved since then | |
| // (see StateMachineLayerInstance::isFrozen). | |
| uint32_t m_advanceChangeSerial = 0; | |
| std::vector<std::unique_ptr<HitComponent>> m_hitComponents; | |
| std::vector<std::unique_ptr<ListenerGroup>> m_listenerGroups; | |
| // Owns the in-flight scroll gesture. Latched for the whole gesture: | |
| // re-picking per event would hand a flick's tail to the outer view. | |
| HitComponent* m_scrollLatch = nullptr; | |
| // Pointers disablePointerEvents turned off. A hover exit keeps them off; | |
| // a real exit forgets them, as it does every other per-pointer state. | |
| LazyVector<int> m_disabledPointerIds; | |
| StateMachineInstance* m_parentStateMachineInstance = nullptr; | |
| NestedArtboard* m_parentNestedArtboard = nullptr; | |
| void unbind(); | |
| void removeEventListeners(); | |
| void initScriptedObjects(); | |
| // Something a settled layer's conditions read may have changed: search | |
| // every layer again on its next update. | |
| void unsettleLayers(); | |
| // `layer` is about to search its transitions: view model changes made from | |
| // here on are new to it. See viewModelValueChanged. | |
| void markViewModelChangesSearched(const StateMachineLayerInstance* layer); | |
| // A frozen `layer` skipped its advance without searching. It still had | |
| // its frame, so changes made so far aren't pending for it when it next | |
| // searches: a fire on an instance this machine binds later must not | |
| // replay. Without a host loop that searches it (tryChangeState), nothing | |
| // else would move its window. | |
| void markFrozenLayerSearched(const StateMachineLayerInstance* layer); | |
| // Where `layer` sits in m_layers, or m_layerCount if it isn't one of them. | |
| size_t layerIndex(const StateMachineLayerInstance* layer) const; | |
| // The baseline of `layer`'s change window: where changes pending for it | |
| // this frame start. The earliest layer's without a layer. | |
| uint64_t changeBaselineFor(const StateMachineLayerInstance* layer) const; | |
| // No view model change made so far is pending for this instance any more. | |
| void forgetViewModelChanges(); | |
| // Cold clusters. See state_machine_instance_clusters.hpp. | |
| Sidecar<SMIReporting> m_reporting; | |
| Sidecar<SMIBindables> m_bindables; | |
| Sidecar<SMIInputExtras> m_inputExtras; | |
| Sidecar<SMIScripting> m_scripting; | |
| // Read accessors — null when the cluster's feature is unused, which every | |
| // caller must handle. The ensure...() variants allocate and are defined in | |
| // the .cpp, where the cluster types are complete. | |
| const SMIReporting* reporting() const { return m_reporting.get(); } | |
| SMIReporting* reporting() { return m_reporting.get(); } | |
| SMIReporting& ensureReporting(); | |
| const SMIBindables* bindables() const { return m_bindables.get(); } | |
| SMIBindables& ensureBindables(); | |
| const SMIInputExtras* inputExtras() const { return m_inputExtras.get(); } | |
| SMIInputExtras* inputExtras() { return m_inputExtras.get(); } | |
| SMIInputExtras& ensureInputExtras(); | |
| const SMIScripting* scripting() const { return m_scripting.get(); } | |
| SMIScripting& ensureScripting(); | |
| // True while any reported event or viewModel listener is still waiting to | |
| // be delivered — the "keep advancing" condition for advance() and | |
| // advanceAndApply(). One null check on the cold cluster replaces two | |
| // .empty() probes on containers that were inline for every instance. | |
| bool hasPendingReports() const; | |
| void processFocusEvents(); | |
| // Root artboard of this instance's tree, used to tag deferred focus | |
| // requests so a manager shared across independent roots drains each root's | |
| // requests only after that root has updated. See | |
| // FocusManager::PendingFocusRequest. | |
| const Artboard* rootArtboard() const; | |
| void processSemanticEvents(); | |
| #ifdef WITH_RIVE_TOOLS | |
| public: | |
| void onInputChanged(InputChanged callback) | |
| { | |
| m_inputChangedCallback = callback; | |
| } | |
| void onDataBindChanged(DataBindChanged callback); | |
| InputChanged m_inputChangedCallback = nullptr; | |
| #endif | |
| }; | |
| class HitComponent | |
| { | |
| public: | |
| Component* component() const { return m_component; } | |
| HitComponent(Component* component, StateMachineInstance* stateMachineInstance) | |
| : m_component(component) | |
| , m_stateMachineInstance(stateMachineInstance) | |
| {} | |
| virtual ~HitComponent() {} | |
| virtual HitResult processEvent( | |
| Vec2D position, | |
| ListenerType hitType, | |
| bool canHit, | |
| float timeStamp = 0, | |
| int pointerId = 0, | |
| PointerButton button = PointerButton::primary) = 0; | |
| virtual HitResult processGamepadInvocation( | |
| const ListenerInvocation& invocation, | |
| ScriptedDrawable* alreadyDispatched) = 0; | |
| virtual void prepareEvent( | |
| Vec2D position, | |
| ListenerType hitType, | |
| int pointerId) = 0; | |
| virtual bool hitTest(Vec2D position) const = 0; | |
| /// See StateMachineInstance::hitTestBounded; the same as hitTest for | |
| /// everything but scripted drawables. | |
| virtual bool hitTestBounded(Vec2D position) const | |
| { | |
| return hitTest(position); | |
| } | |
| virtual void enablePointerEvents(int pointerId = 0) {} | |
| virtual void disablePointerEvents(int pointerId = 0) {} | |
| /// Scroll dispatch. wantsScroll is the interest test; scrollGestureActive | |
| /// keeps the latch alive between events. | |
| virtual bool wantsScroll(Vec2D position, const ScrollEvent& event) | |
| { | |
| return false; | |
| } | |
| virtual bool scrollGestureActive() { return false; } | |
| /// Ends any scroll gesture this component owns. | |
| virtual void cancelScroll() {} | |
| /// Whether an interactive scroll view sits under position at all, even | |
| /// one that cannot move right now. | |
| virtual bool hasScrollTarget(Vec2D position) { return false; } | |
| /// Whether an opaque hit that cannot scroll sits under position. Ends | |
| /// the walk: nothing behind it may take the event, as with hitOpaque. | |
| virtual bool occludesScroll(Vec2D position) { return false; } | |
| /// Whether a listener under position responds to presses of button. | |
| virtual bool listensToButtonAt(Vec2D position, PointerButton button) | |
| { | |
| return false; | |
| } | |
| /// Whether an opaque hit sits under position, blocking presses from | |
| /// reaching whatever lies behind it, as hitOpaque does in processEvent. | |
| virtual bool occludesPointer(Vec2D position) { return false; } | |
| virtual HitResult processScroll( | |
| Vec2D position, | |
| const ScrollEvent& event, | |
| float timeStamp, | |
| int pointerId) | |
| { | |
| return HitResult::none; | |
| } | |
| /// Whether a machine this hosts, or one nested in it, has a capture | |
| /// listener: the capture phase only descends where one does. | |
| virtual bool hasCaptureListeners() { return false; } | |
| /// The capture phase, for hit components that host other machines. | |
| virtual HitResult capturePointer( | |
| Vec2D position, | |
| ListenerType hitType, | |
| int pointerId, | |
| float timeStamp, | |
| PointerButton button) | |
| { | |
| return HitResult::none; | |
| } | |
| /// Whether an opaque hit under position hides capture listeners drawn | |
| /// below it. Unlike occludesPointer it looks into nested artboards, whose | |
| /// opaque contents (a text input, say) cover what lies under them too. | |
| virtual bool blocksCapture(Vec2D position) | |
| { | |
| return occludesPointer(position); | |
| } | |
| #ifdef TESTING | |
| int earlyOutCount = 0; | |
| #endif | |
| protected: | |
| Component* m_component; | |
| StateMachineInstance* m_stateMachineInstance; | |
| }; | |
| } // namespace rive | |
| #endif |