| /* |
| * Copyright 2024 Rive |
| */ |
| |
| #ifndef _RIVE_FOCUS_NODE_HPP_ |
| #define _RIVE_FOCUS_NODE_HPP_ |
| |
| #include "rive/input/focusable.hpp" |
| #include "rive/math/aabb.hpp" |
| #include "rive/refcnt.hpp" |
| #include <cstdint> |
| #include <string> |
| #include <vector> |
| |
| namespace rive |
| { |
| |
| class FocusManager; // Forward declaration |
| |
| enum class EdgeBehavior : uint8_t |
| { |
| parentScope = 0, // Focus exits to parent scope's next focusable |
| closedLoop = 1, // Focus wraps from last to first within this scope |
| stop = 2, // Focus stays on boundary element |
| }; |
| |
| class FocusNode : public RefCnt<FocusNode> |
| { |
| public: |
| FocusNode(Focusable* focusable = nullptr) : m_focusable(focusable) {} |
| |
| // === Focusable === |
| |
| Focusable* focusable() const { return m_focusable; } |
| void setFocusable(Focusable* focusable) { m_focusable = focusable; } |
| void clearFocusable() { m_focusable = nullptr; } |
| |
| // === Properties (bitfield-backed) === |
| |
| // Master switch: if false, node cannot receive focus at all |
| bool canFocus() const { return m_flags & Flag::kCanFocus; } |
| void canFocus(bool value) { setFlag(Flag::kCanFocus, value); } |
| |
| // Can receive focus via pointer/touch click |
| bool canTouch() const { return m_flags & Flag::kCanTouch; } |
| void canTouch(bool value) { setFlag(Flag::kCanTouch, value); } |
| |
| // Included in tab traversal |
| bool canTraverse() const { return m_flags & Flag::kCanTraverse; } |
| void canTraverse(bool value) { setFlag(Flag::kCanTraverse, value); } |
| |
| // True if this node or any descendant currently has focus |
| // (set by FocusManager during focus transitions) |
| bool hasFocus() const { return m_flags & Flag::kHasFocus; } |
| |
| // Note: A node with canFocus=true but canTouch=false and canTraverse=false |
| // can still be focused programmatically (via script) and receives text |
| // input. |
| |
| // Scope status is implicit: a node with children is a scope. |
| // EdgeBehavior only applies to scopes (nodes with children). |
| EdgeBehavior edgeBehavior() const |
| { |
| return static_cast<EdgeBehavior>((m_flags >> edgeBehaviorShift) & |
| edgeBehaviorMask); |
| } |
| void edgeBehavior(EdgeBehavior edge) |
| { |
| m_flags = (m_flags & ~(edgeBehaviorMask << edgeBehaviorShift)) | |
| (static_cast<uint8_t>(edge) << edgeBehaviorShift); |
| } |
| |
| int tabIndex() const { return m_tabIndex; } |
| void tabIndex(int value) { m_tabIndex = static_cast<int16_t>(value); } |
| |
| // Debug name (not required to be unique) |
| const std::string& name() const { return m_name; } |
| void name(const std::string& value) { m_name = value; } |
| |
| // === World Bounds (for directional navigation) === |
| |
| // World bounds in root artboard space, used for spatial navigation. |
| // Set during update cycle by owner (TextInput, Dart, etc). |
| const AABB& worldBounds() const { return m_worldBounds; } |
| void worldBounds(const AABB& bounds) { m_worldBounds = bounds; } |
| |
| // Check if bounds are available (not empty/invalid) |
| bool hasWorldBounds() const { return !m_worldBounds.isEmptyOrNaN(); } |
| |
| // Clear bounds (mark as unavailable) |
| void clearWorldBounds() { m_worldBounds = AABB(); } |
| |
| // === Hierarchy === |
| |
| FocusNode* parent() const { return m_parent; } |
| const std::vector<rcp<FocusNode>>& children() const { return m_children; } |
| FocusManager* manager() const { return m_manager; } |
| bool isScope() const { return !m_children.empty(); } |
| |
| void addChild(rcp<FocusNode> child); |
| void removeChild(rcp<FocusNode> child); |
| |
| // Remove this node from its current parent (used internally) |
| void removeFromParent(); |
| |
| // === Input Handling (delegates to Focusable) === |
| |
| bool keyInput(Key key, |
| KeyModifiers modifiers, |
| bool isPressed, |
| bool isRepeat) |
| { |
| return m_focusable |
| ? m_focusable->keyInput(key, modifiers, isPressed, isRepeat) |
| : false; |
| } |
| |
| bool textInput(const std::string& text) |
| { |
| return m_focusable ? m_focusable->textInput(text) : false; |
| } |
| |
| void focused() |
| { |
| if (m_focusable) |
| { |
| m_focusable->focused(); |
| } |
| } |
| |
| void blurred() |
| { |
| if (m_focusable) |
| { |
| m_focusable->blurred(); |
| } |
| } |
| |
| private: |
| friend class FocusManager; |
| |
| // Used by FocusManager to update focus state |
| void setHasFocus(bool value) { setFlag(Flag::kHasFocus, value); } |
| |
| enum Flag : uint8_t |
| { |
| kCanFocus = 1 << 0, // default: true |
| kCanTouch = 1 << 1, // default: true |
| kCanTraverse = 1 << 2, // default: true |
| // bits 3-4: EdgeBehavior (2 bits) |
| kHasFocus = 1 << 5, // true if this node or descendant has focus |
| }; |
| static constexpr uint8_t edgeBehaviorShift = 3; |
| static constexpr uint8_t edgeBehaviorMask = 0x3; |
| static constexpr uint8_t defaultFlags = |
| Flag::kCanFocus | Flag::kCanTouch | Flag::kCanTraverse; |
| |
| void setFlag(Flag flag, bool value) |
| { |
| if (value) |
| { |
| m_flags |= flag; |
| } |
| else |
| { |
| m_flags &= ~flag; |
| } |
| } |
| |
| Focusable* m_focusable = nullptr; |
| FocusNode* m_parent = nullptr; |
| FocusManager* m_manager = nullptr; |
| std::vector<rcp<FocusNode>> m_children; |
| std::string m_name; |
| AABB m_worldBounds; // Default empty (invalid) - hasWorldBounds() returns |
| // false |
| uint8_t m_flags = defaultFlags; |
| int16_t m_tabIndex = 0; |
| }; |
| |
| } // namespace rive |
| |
| #endif |