Skip to main content

fission_core/ui/
node.rs

1//! The serialisable widget-tree node enum.
2//!
3//! [`Node`] is the data structure returned by [`Widget::build`](crate::Widget::build).
4//! It has one variant per built-in widget type plus a [`Custom`](Node::Custom)
5//! escape hatch for application-defined widgets that need custom lowering.
6
7use super::traits::{Lower, LowerDyn};
8use super::widgets::{
9    Align, Button, Checkbox, Clip, Column, Container, GestureDetector, FocusScope, Grid, GridItem, Icon, Image, LazyColumn, Overlay, Positioned, Radio, Row, SafeArea, Scroll, Slider, Spacer,
10    Switch, Text, TextInput, Transform, Video, ZStack,
11};
12use crate::lowering::LoweringContext;
13use fission_ir::{NodeId, Op, StructuralOp};
14use serde::{Deserialize, Serialize};
15use std::sync::Arc;
16
17/// A serialisable node in the declarative widget tree.
18///
19/// Every variant wraps one of the built-in widget structs. The tree is
20/// constructed by [`Widget::build`](crate::Widget::build) and lowered into
21/// the `fission-ir` intermediate representation for layout and rendering.
22///
23/// # Example
24///
25/// ```rust,ignore
26/// let tree = Node::Column(Column {
27///     children: vec![
28///         Node::Text(Text::new("Hello")),
29///         Node::Button(Button {
30///             child: Some(Box::new(Node::Text(Text::new("Click me")))),
31///             on_press: Some(envelope),
32///             ..Default::default()
33///         }),
34///     ],
35///     ..Default::default()
36/// });
37/// ```
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub enum Node {
40    /// Horizontal flex container. See [`Row`].
41    Row(Row),
42    /// Vertical flex container. See [`Column`].
43    Column(Column),
44    /// Center-aligns its child within the available space. See [`Align`].
45    Align(Align),
46    /// Limits focus traversal to a subtree. See [`FocusScope`].
47    FocusScope(FocusScope),
48    /// Clips child content to a rounded rectangle. See [`Clip`].
49    Clip(Clip),
50    /// Static or i18n text label. See [`Text`].
51    Text(Text),
52    /// Applies a 4x4 matrix transform. See [`Transform`].
53    Transform(Transform),
54    /// Pressable button with variant styling. See [`Button`].
55    Button(Button),
56    /// Editable text field with optional syntax-highlighting. See [`TextInput`].
57    TextInput(TextInput),
58    /// Scrollable container. See [`Scroll`].
59    Scroll(Scroll),
60    /// Raster image. See [`Image`].
61    Image(Image),
62    /// Platform-native video player. See [`Video`].
63    Video(Video),
64    /// Z-axis stacking container (children layered on top of each other). See [`ZStack`].
65    ZStack(ZStack),
66    /// Content with an overlay layer on top. See [`Overlay`].
67    Overlay(Overlay),
68    /// Universal wrapper with background, border, padding, and size. See [`Container`].
69    Container(Container),
70    /// Gesture handler (tap, drag, hover, drop). See [`GestureDetector`].
71    GestureDetector(GestureDetector),
72    /// CSS-grid-style layout. See [`Grid`].
73    Grid(Grid),
74    /// A child placed within a [`Grid`]. See [`GridItem`].
75    GridItem(GridItem),
76    /// Boolean toggle with a square indicator. See [`Checkbox`].
77    Checkbox(Checkbox),
78    /// Boolean toggle with a sliding thumb. See [`Switch`].
79    Switch(Switch),
80    /// Single-select radio button. See [`Radio`].
81    Radio(Radio),
82    /// Insets content to avoid system chrome (notch, status bar). See [`SafeArea`].
83    SafeArea(SafeArea),
84    /// Absolutely positioned child within a [`ZStack`]. See [`Positioned`].
85    Positioned(Positioned),
86    /// Flexible or fixed-size empty space. See [`Spacer`].
87    Spacer(Spacer),
88    /// Continuous value selector with a draggable thumb. See [`Slider`].
89    Slider(Slider),
90    /// Virtualized vertical list for large data sets. See [`LazyColumn`].
91    LazyColumn(LazyColumn),
92    /// Vector icon rendered from an SVG path, file, or inline content. See [`Icon`].
93    Icon(Icon),
94    /// Escape hatch for application-defined widgets with custom lowering. See [`CustomNode`].
95    Custom(CustomNode),
96}
97
98impl Node {
99    pub fn lower(&self, cx: &mut LoweringContext) -> NodeId {
100        match self {
101            Node::Row(w) => w.lower(cx),
102            Node::Column(w) => w.lower(cx),
103            Node::Align(w) => w.lower(cx),
104            Node::FocusScope(w) => w.lower(cx),
105            Node::Clip(w) => w.lower(cx),
106            Node::Text(w) => w.lower(cx),
107            Node::Transform(w) => w.lower(cx),
108            Node::Button(w) => w.lower(cx),
109            Node::TextInput(w) => w.lower(cx),
110            Node::Scroll(w) => w.lower(cx),
111            Node::Image(w) => w.lower(cx),
112            Node::Video(w) => w.lower(cx),
113            Node::ZStack(w) => w.lower(cx),
114            Node::Overlay(w) => w.lower(cx),
115            Node::Container(w) => w.lower(cx),
116            Node::GestureDetector(w) => w.lower(cx),
117            Node::Grid(w) => w.lower(cx),
118            Node::GridItem(w) => w.lower(cx),
119            Node::Checkbox(w) => w.lower(cx),
120            Node::Switch(w) => w.lower(cx),
121            Node::Radio(w) => w.lower(cx),
122            Node::SafeArea(w) => w.lower(cx),
123            Node::Positioned(w) => w.lower(cx),
124            Node::Spacer(w) => w.lower(cx),
125            Node::Slider(w) => w.lower(cx),
126            Node::LazyColumn(w) => w.lower(cx),
127            Node::Icon(w) => w.lower(cx),
128            Node::Custom(w) => {
129                let lowerer = w.lowerer.as_ref().expect("CustomNode lowerer must be set");
130                let child_id = lowerer.lower_dyn(cx);
131                let wrapper = cx.next_node_id();
132                let mut builder = crate::lowering::NodeBuilder::new(
133                    wrapper,
134                    Op::Structural(StructuralOp::Group {
135                        stable_hash: lowerer.stable_key(),
136                    }),
137                );
138                builder.add_child(child_id);
139                builder.build(cx)
140            }
141        }
142    }
143}
144
145impl From<Row> for Node {
146    fn from(w: Row) -> Self {
147        Node::Row(w)
148    }
149}
150impl From<Column> for Node {
151    fn from(w: Column) -> Self {
152        Node::Column(w)
153    }
154}
155impl From<Align> for Node {
156    fn from(w: Align) -> Self {
157        Node::Align(w)
158    }
159}
160impl From<FocusScope> for Node {
161    fn from(w: FocusScope) -> Self {
162        Node::FocusScope(w)
163    }
164}
165impl From<Clip> for Node {
166    fn from(w: Clip) -> Self {
167        Node::Clip(w)
168    }
169}
170impl From<Text> for Node {
171    fn from(w: Text) -> Self {
172        Node::Text(w)
173    }
174}
175impl From<Transform> for Node {
176    fn from(w: Transform) -> Self {
177        Node::Transform(w)
178    }
179}
180impl From<Button> for Node {
181    fn from(w: Button) -> Self {
182        Node::Button(w)
183    }
184}
185impl From<TextInput> for Node {
186    fn from(w: TextInput) -> Self {
187        Node::TextInput(w)
188    }
189}
190impl From<Scroll> for Node {
191    fn from(w: Scroll) -> Self {
192        Node::Scroll(w)
193    }
194}
195impl From<Image> for Node {
196    fn from(w: Image) -> Self {
197        Node::Image(w)
198    }
199}
200impl From<ZStack> for Node {
201    fn from(w: ZStack) -> Self {
202        Node::ZStack(w)
203    }
204}
205impl From<Overlay> for Node {
206    fn from(w: Overlay) -> Self {
207        Node::Overlay(w)
208    }
209}
210impl From<Container> for Node {
211    fn from(w: Container) -> Self {
212        Node::Container(w)
213    }
214}
215impl From<GestureDetector> for Node {
216    fn from(w: GestureDetector) -> Self {
217        Node::GestureDetector(w)
218    }
219}
220impl From<Grid> for Node {
221    fn from(w: Grid) -> Self {
222        Node::Grid(w)
223    }
224}
225impl From<GridItem> for Node {
226    fn from(w: GridItem) -> Self {
227        Node::GridItem(w)
228    }
229}
230impl From<Checkbox> for Node {
231    fn from(w: Checkbox) -> Self {
232        Node::Checkbox(w)
233    }
234}
235impl From<Switch> for Node {
236    fn from(w: Switch) -> Self {
237        Node::Switch(w)
238    }
239}
240impl From<Radio> for Node {
241    fn from(w: Radio) -> Self {
242        Node::Radio(w)
243    }
244}
245impl From<SafeArea> for Node {
246    fn from(w: SafeArea) -> Self {
247        Node::SafeArea(w)
248    }
249}
250impl From<Positioned> for Node {
251    fn from(w: Positioned) -> Self {
252        Node::Positioned(w)
253    }
254}
255impl From<Spacer> for Node {
256    fn from(w: Spacer) -> Self {
257        Node::Spacer(w)
258    }
259}
260impl From<Slider> for Node {
261    fn from(w: Slider) -> Self {
262        Node::Slider(w)
263    }
264}
265impl From<LazyColumn> for Node {
266    fn from(w: LazyColumn) -> Self {
267        Node::LazyColumn(w)
268    }
269}
270impl From<Icon> for Node {
271    fn from(w: Icon) -> Self {
272        Node::Icon(w)
273    }
274}
275
276/// An application-defined node with custom lowering logic.
277///
278/// `CustomNode` is the escape hatch for widgets that cannot be expressed with
279/// the built-in primitives. Provide a [`LowerDyn`] implementation to emit
280/// arbitrary `fission-ir` operations.
281///
282/// # Example
283///
284/// ```rust,ignore
285/// let custom = CustomNode {
286///     debug_tag: "MyCanvasWidget".to_string(),
287///     lowerer: Some(Arc::new(MyCanvasLowerer)),
288/// };
289/// Node::Custom(custom)
290/// ```
291#[derive(Clone, Debug, Serialize, Deserialize)]
292pub struct CustomNode {
293    /// Human-readable name for debugging and diagnostics.
294    pub debug_tag: String,
295    /// The lowering implementation (skipped during serialisation).
296    #[serde(skip)]
297    pub lowerer: Option<Arc<dyn LowerDyn>>,
298}