blob: 22c85ede2c5f2fd3915043c57577380f594fa7bd [file]
#ifndef _RIVE_SEMANTIC_NODE_HPP_
#define _RIVE_SEMANTIC_NODE_HPP_
#include "rive/math/aabb.hpp"
#include "rive/refcnt.hpp"
#include <cstdint>
#include <string>
#include <vector>
namespace rive
{
class Artboard;
class Core;
class SemanticManager;
// Ref-counted tree node holding all data needed by platform accessibility.
// Created lazily by SemanticData::semanticNode() and registered with
// SemanticManager via addChild(). The manager flattens these into
// SemanticsDiffNode structs for platform consumption.
//
// Ids are scoped to the owning SemanticManager: a node is constructed
// with id == 0 (unassigned) and receives a manager-local id on first
// addChild(). This avoids collisions when multiple independent
// SemanticManagers (e.g. two unrelated artboards in the same process)
// outlive a single process-global counter.
//
// Callers that want a deterministic id (tests, serialization round-trips)
// may construct with an explicit id; SemanticManager will preserve it and
// bump its watermark to avoid future collisions.
//
// Access model:
// - Authored-value mutators (role, label, value, hint, headingLevel,
// stateFlags, traitFlags, bounds) are public: SemanticData writes
// them from its property-change handlers, Artboard sets them on
// boundary nodes, tests use them to hand-build trees.
// - Construction-time back-refs (coreOwner, semanticData, isBoundaryNode)
// are public: set once at node creation, never again.
// - Structural mutators (parent, mutable children, manager,
// id assignment) live behind `friend class SemanticManager`. The
// manager is the only code allowed to wire the tree or rebind a
// node to a different manager.
class SemanticNode : public RefCnt<SemanticNode>
{
public:
explicit SemanticNode(uint32_t id = 0) : m_id(id) {}
uint32_t id() const { return m_id; }
SemanticNode* parent() const { return m_parent; }
const std::vector<rcp<SemanticNode>>& children() const
{
return m_children;
}
void role(uint32_t value) { m_role = value; }
uint32_t role() const { return m_role; }
void stateFlags(uint32_t value) { m_stateFlags = value; }
uint32_t stateFlags() const { return m_stateFlags; }
void label(const std::string& value) { m_label = value; }
const std::string& label() const { return m_label; }
void value(const std::string& v) { m_value = v; }
const std::string& value() const { return m_value; }
void hint(const std::string& v) { m_hint = v; }
const std::string& hint() const { return m_hint; }
void headingLevel(uint32_t v) { m_headingLevel = v; }
uint32_t headingLevel() const { return m_headingLevel; }
AABB bounds() const { return m_bounds; }
void bounds(const AABB& value) { m_bounds = value; }
void traitFlags(uint32_t value) { m_traitFlags = value; }
uint32_t traitFlags() const { return m_traitFlags; }
Core* coreOwner() const { return m_coreOwner; }
void coreOwner(Core* value) { m_coreOwner = value; }
SemanticManager* manager() const { return m_manager; }
/// Boundary nodes are structural-only nodes inserted at artboard
/// boundaries. They are skipped during flattening (children reparented
/// upward) but participate in tree management for O(1) subtree
/// collapse/uncollapse and targeted bounds dirtying.
bool isBoundaryNode() const { return m_isBoundaryNode; }
void isBoundaryNode(bool value) { m_isBoundaryNode = value; }
/// For boundary nodes, the nested Artboard whose region this boundary
/// represents. The boundary's bounds are the artboard's rect mapped
/// through its rootTransform into root space.
Artboard* boundaryArtboard() const { return m_boundaryArtboard; }
void boundaryArtboard(Artboard* value) { m_boundaryArtboard = value; }
/// Back-pointer to the SemanticData that owns this node. Null for
/// boundary nodes. Used for direct collapse without walking the
/// component hierarchy.
class SemanticData* semanticData() const { return m_semanticData; }
void semanticData(class SemanticData* value) { m_semanticData = value; }
private:
// SemanticManager is the only class allowed to rewire the tree,
// assign/reassign ids, or rebind a node to a different manager.
friend class SemanticManager;
void id(uint32_t value) { m_id = value; }
void parent(SemanticNode* value) { m_parent = value; }
std::vector<rcp<SemanticNode>>& mutableChildren() { return m_children; }
void manager(SemanticManager* m) { m_manager = m; }
uint32_t m_id;
SemanticNode* m_parent = nullptr;
std::vector<rcp<SemanticNode>> m_children;
uint32_t m_role = 0;
uint32_t m_stateFlags = 0;
std::string m_label = "";
std::string m_value = "";
std::string m_hint = "";
uint32_t m_headingLevel = 0;
// Default to "no bounds" rather than (0,0,0,0) which looks like a
// valid position at the origin. forExpansion() has minX > maxX so
// isEmptyOrNaN() returns true.
AABB m_bounds = AABB::forExpansion();
uint32_t m_traitFlags = 0;
Core* m_coreOwner = nullptr;
SemanticManager* m_manager = nullptr;
bool m_isBoundaryNode = false;
class SemanticData* m_semanticData = nullptr;
Artboard* m_boundaryArtboard = nullptr;
};
} // namespace rive
#endif