Skip to main content

fission_ir/
lib.rs

1//! Intermediate representation for the Fission UI framework.
2//!
3//! `fission-ir` defines the node graph that sits between the high-level widget tree
4//! and the low-level layout and paint pipelines. Every widget compiles down to one or
5//! more [`CoreNode`]s stored inside a [`CoreIR`] container. Each node carries a single
6//! [`Op`] that describes what it does: lay out children, draw something on screen,
7//! group subtrees, or declare accessibility semantics.
8//!
9//! # Architecture
10//!
11//! ```text
12//! Widget Tree  -->  fission-ir (CoreIR)  -->  Layout Engine  -->  Display List
13//! ```
14//!
15//! The IR is platform-agnostic, serializable (via serde), and content-addressed
16//! (every [`NodeId`] is a BLAKE3 hash). This makes it cheap to diff across frames
17//! and safe to send across process boundaries.
18//!
19//! # Example
20//!
21//! ```rust
22//! use fission_ir::{CoreIR, NodeId, Op, LayoutOp, FlexDirection, FlexWrap, AlignItems, JustifyContent};
23//!
24//! let mut ir = CoreIR::new();
25//!
26//! let child = NodeId::explicit("label");
27//! ir.add_node(child, Op::Layout(LayoutOp::Box {
28//!     width: Some(200.0), height: Some(40.0),
29//!     min_width: None, max_width: None,
30//!     min_height: None, max_height: None,
31//!     padding: [4.0; 4],
32//!     flex_grow: 0.0, flex_shrink: 1.0,
33//!     aspect_ratio: None,
34//! }), vec![]);
35//!
36//! let root = NodeId::explicit("root");
37//! ir.add_node(root, Op::Layout(LayoutOp::Flex {
38//!     direction: FlexDirection::Column,
39//!     wrap: FlexWrap::NoWrap,
40//!     flex_grow: 1.0, flex_shrink: 1.0,
41//!     padding: [8.0; 4], gap: Some(4.0),
42//!     align_items: AlignItems::Start,
43//!     justify_content: JustifyContent::Start,
44//! }), vec![child]);
45//!
46//! ir.set_root(root);
47//! assert_eq!(ir.nodes.len(), 2);
48//! ```
49
50pub mod node_id;
51pub mod op;
52pub mod semantics;
53pub mod widget_id;
54
55use serde::{Deserialize, Serialize};
56use std::collections::HashMap;
57
58pub use node_id::NodeId;
59pub use op::{
60    AlignItems, EmbedKind, FlexDirection, FlexWrap, GridPlacement, GridTrack, JustifyContent,
61    LayoutOp, Op, PaintOp, StructuralOp,
62};
63pub use semantics::{ActionEntry, ActionSet, Role, Semantics};
64pub use widget_id::WidgetNodeId;
65
66/// The current version of the IR format.
67///
68/// Increment this when making breaking changes to the serialized representation
69/// so that consumers can detect version mismatches.
70pub const IR_VERSION: u32 = 1;
71
72/// A single node in the intermediate representation graph.
73///
74/// Every element in a Fission UI compiles down to one or more `CoreNode`s. A node
75/// carries an [`Op`] that says what it *does* (layout, paint, group, or declare
76/// semantics), a list of children, and a content hash for efficient diffing.
77///
78/// # Fields
79///
80/// Nodes form a tree through `children` (downward links) and `parent` (upward link).
81/// The `hash` field is a content hash of the node's operation and children, used to
82/// skip unchanged subtrees during reconciliation.
83///
84/// # Example
85///
86/// You rarely construct `CoreNode` directly -- use [`CoreIR::add_node`] instead:
87///
88/// ```rust
89/// use fission_ir::{CoreIR, NodeId, Op, LayoutOp};
90///
91/// let mut ir = CoreIR::new();
92/// let id = NodeId::explicit("box");
93/// ir.add_node(id, Op::Layout(LayoutOp::Box {
94///     width: Some(100.0), height: Some(50.0),
95///     min_width: None, max_width: None,
96///     min_height: None, max_height: None,
97///     padding: [0.0; 4], flex_grow: 0.0, flex_shrink: 1.0,
98///     aspect_ratio: None,
99/// }), vec![]);
100/// ```
101#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
102pub struct CoreNode {
103    /// The unique, content-addressed identity of this node.
104    pub id: NodeId,
105    /// The operation this node performs (layout, paint, structural, or semantics).
106    pub op: Op,
107    /// Ordered list of child node IDs. Order matters for layout and paint order.
108    pub children: Vec<NodeId>,
109    /// The parent of this node, or `None` if this is the root.
110    /// Set automatically by [`CoreIR::add_node`].
111    pub parent: Option<NodeId>,
112    /// A content hash of this node's operation and subtree, used for efficient
113    /// diffing between frames. A value of `0` means the hash has not been computed.
114    pub hash: u64,
115}
116
117/// The root container for an intermediate representation graph.
118///
119/// `CoreIR` owns all nodes in the tree and knows which one is the root. It is the
120/// primary data structure you build when compiling a widget tree, and the primary
121/// input to the layout engine.
122///
123/// # Example
124///
125/// ```rust
126/// use fission_ir::{CoreIR, NodeId, Op, LayoutOp};
127///
128/// let mut ir = CoreIR::new();
129/// let root = NodeId::explicit("root");
130/// ir.add_node(root, Op::Layout(LayoutOp::Box {
131///     width: None, height: None,
132///     min_width: None, max_width: None,
133///     min_height: None, max_height: None,
134///     padding: [0.0; 4], flex_grow: 1.0, flex_shrink: 1.0,
135///     aspect_ratio: None,
136/// }), vec![]);
137/// ir.set_root(root);
138/// assert!(ir.root.is_some());
139/// ```
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
141pub struct CoreIR {
142    /// All nodes in the graph, keyed by their [`NodeId`].
143    pub nodes: HashMap<NodeId, CoreNode>,
144    /// The root node of the tree, or `None` if the tree is empty.
145    pub root: Option<NodeId>,
146}
147
148impl CoreIR {
149    /// Creates an empty IR with no nodes and no root.
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    /// Adds a node to the graph and wires up parent-child relationships.
155    ///
156    /// Each child in `children` that already exists in the graph will have its
157    /// `parent` field set to `id`. Add children before their parents to ensure
158    /// the parent link is established.
159    ///
160    /// # Arguments
161    ///
162    /// * `id` -- The identity of the new node.
163    /// * `op` -- What this node does (layout, paint, structural, or semantics).
164    /// * `children` -- Ordered list of child [`NodeId`]s.
165    pub fn add_node(&mut self, id: NodeId, op: Op, children: Vec<NodeId>) {
166        let core_node = CoreNode {
167            id,
168            op,
169            children: children.clone(),
170            parent: None,
171            hash: 0,
172        };
173        self.nodes.insert(id, core_node);
174
175        for child_id in children {
176            if let Some(child_node) = self.nodes.get_mut(&child_id) {
177                child_node.parent = Some(id);
178            }
179        }
180    }
181
182    /// Designates a node as the root of the tree.
183    ///
184    /// The layout engine starts traversal from this node. There must be exactly
185    /// one root; calling this again replaces the previous root.
186    pub fn set_root(&mut self, id: NodeId) {
187        self.root = Some(id);
188    }
189}