Skip to main content

fission_core/ui/
node.rs

1use super::custom_render::CustomRenderObject;
2use super::traits::{Lower, LowerDyn};
3use super::widgets::{
4    ActionScope, Align, Button, Checkbox, Clip, Column, Composite, Container, FocusScope,
5    GestureDetector, Grid, GridItem, Icon, Image, LazyColumn, Overlay, Positioned, Radio, RichText,
6    Row, SafeArea, Scroll, Slider, Spacer, Switch, Text, TextInput, Transform, Video, ZStack,
7};
8use crate::lowering::LoweringContext;
9use fission_ir::{NodeId, Op, StructuralOp};
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14pub enum Node {
15    ActionScope(ActionScope),
16    Row(Row),
17    Column(Column),
18    Align(Align),
19    FocusScope(FocusScope),
20    Clip(Clip),
21    Text(Text),
22    RichText(RichText),
23    Transform(Transform),
24    Button(Button),
25    TextInput(TextInput),
26    Scroll(Scroll),
27    Image(Image),
28    Video(Video),
29    ZStack(ZStack),
30    Overlay(Overlay),
31    Container(Container),
32    GestureDetector(GestureDetector),
33    Grid(Grid),
34    GridItem(GridItem),
35    Checkbox(Checkbox),
36    Switch(Switch),
37    Radio(Radio),
38    SafeArea(SafeArea),
39    Positioned(Positioned),
40    Spacer(Spacer),
41    Slider(Slider),
42    LazyColumn(LazyColumn),
43    Icon(Icon),
44    Composite(Composite),
45    Custom(CustomNode),
46}
47
48impl Node {
49    pub fn lower(&self, cx: &mut LoweringContext) -> NodeId {
50        match self {
51            Node::ActionScope(w) => w.lower(cx),
52            Node::Row(w) => w.lower(cx),
53            Node::Column(w) => w.lower(cx),
54            Node::Align(w) => w.lower(cx),
55            Node::FocusScope(w) => w.lower(cx),
56            Node::Clip(w) => w.lower(cx),
57            Node::Text(w) => w.lower(cx),
58            Node::RichText(w) => w.lower(cx),
59            Node::Transform(w) => w.lower(cx),
60            Node::Button(w) => w.lower(cx),
61            Node::TextInput(w) => w.lower(cx),
62            Node::Scroll(w) => w.lower(cx),
63            Node::Image(w) => w.lower(cx),
64            Node::Video(w) => w.lower(cx),
65            Node::ZStack(w) => w.lower(cx),
66            Node::Overlay(w) => w.lower(cx),
67            Node::Container(w) => w.lower(cx),
68            Node::GestureDetector(w) => w.lower(cx),
69            Node::Grid(w) => w.lower(cx),
70            Node::GridItem(w) => w.lower(cx),
71            Node::Checkbox(w) => w.lower(cx),
72            Node::Switch(w) => w.lower(cx),
73            Node::Radio(w) => w.lower(cx),
74            Node::SafeArea(w) => w.lower(cx),
75            Node::Positioned(w) => w.lower(cx),
76            Node::Spacer(w) => w.lower(cx),
77            Node::Slider(w) => w.lower(cx),
78            Node::LazyColumn(w) => w.lower(cx),
79            Node::Icon(w) => w.lower(cx),
80            Node::Composite(w) => w.lower(cx),
81            Node::Custom(w) => {
82                let lowerer = w.lowerer.as_ref().expect("CustomNode lowerer must be set");
83                let child_id = lowerer.lower_dyn(cx);
84                let wrapper = cx.next_node_id();
85                let mut builder = crate::lowering::NodeBuilder::new(
86                    wrapper,
87                    Op::Structural(StructuralOp::Group {
88                        stable_hash: lowerer.stable_key(),
89                    }),
90                );
91                builder.add_child(child_id);
92                let node_id = builder.build(cx);
93
94                // If the custom node carries a render object, store it in the
95                // IR so that hit-testing and event handling can find it later.
96                // We wrap the `Arc<dyn CustomRenderObject>` in a `RenderObjectHolder`
97                // so it can be stored as `Arc<dyn Any + Send + Sync>` in the
98                // dependency-free IR crate and downcast back later.
99                if let Some(render_obj) = &w.render_object {
100                    let holder = crate::ui::custom_render::RenderObjectHolder(render_obj.clone());
101                    let erased: fission_ir::AnyRenderObject = Arc::new(holder);
102                    // Register the render object at the wrapper AND every node in
103                    // the lowered subtree so the parent-walk from any hit descendant
104                    // finds it regardless of tree depth.
105                    cx.ir.custom_render_objects.insert(node_id, erased.clone());
106                    fn register_subtree(
107                        ir: &mut fission_ir::CoreIR,
108                        node_id: fission_ir::NodeId,
109                        erased: &fission_ir::AnyRenderObject,
110                    ) {
111                        ir.custom_render_objects.insert(node_id, erased.clone());
112                        if let Some(children) = ir.nodes.get(&node_id).map(|n| n.children.clone()) {
113                            for child_id in children {
114                                register_subtree(ir, child_id, erased);
115                            }
116                        }
117                    }
118                    register_subtree(&mut cx.ir, child_id, &erased);
119                }
120
121                node_id
122            }
123        }
124    }
125}
126
127impl From<Row> for Node {
128    fn from(w: Row) -> Self {
129        Node::Row(w)
130    }
131}
132impl From<ActionScope> for Node {
133    fn from(w: ActionScope) -> Self {
134        Node::ActionScope(w)
135    }
136}
137impl From<Column> for Node {
138    fn from(w: Column) -> Self {
139        Node::Column(w)
140    }
141}
142impl From<Align> for Node {
143    fn from(w: Align) -> Self {
144        Node::Align(w)
145    }
146}
147impl From<FocusScope> for Node {
148    fn from(w: FocusScope) -> Self {
149        Node::FocusScope(w)
150    }
151}
152impl From<Clip> for Node {
153    fn from(w: Clip) -> Self {
154        Node::Clip(w)
155    }
156}
157impl From<Text> for Node {
158    fn from(w: Text) -> Self {
159        Node::Text(w)
160    }
161}
162impl From<RichText> for Node {
163    fn from(w: RichText) -> Self {
164        Node::RichText(w)
165    }
166}
167impl From<Transform> for Node {
168    fn from(w: Transform) -> Self {
169        Node::Transform(w)
170    }
171}
172impl From<Button> for Node {
173    fn from(w: Button) -> Self {
174        Node::Button(w)
175    }
176}
177impl From<TextInput> for Node {
178    fn from(w: TextInput) -> Self {
179        Node::TextInput(w)
180    }
181}
182impl From<Scroll> for Node {
183    fn from(w: Scroll) -> Self {
184        Node::Scroll(w)
185    }
186}
187impl From<Image> for Node {
188    fn from(w: Image) -> Self {
189        Node::Image(w)
190    }
191}
192impl From<ZStack> for Node {
193    fn from(w: ZStack) -> Self {
194        Node::ZStack(w)
195    }
196}
197impl From<Overlay> for Node {
198    fn from(w: Overlay) -> Self {
199        Node::Overlay(w)
200    }
201}
202impl From<Container> for Node {
203    fn from(w: Container) -> Self {
204        Node::Container(w)
205    }
206}
207impl From<GestureDetector> for Node {
208    fn from(w: GestureDetector) -> Self {
209        Node::GestureDetector(w)
210    }
211}
212impl From<Grid> for Node {
213    fn from(w: Grid) -> Self {
214        Node::Grid(w)
215    }
216}
217impl From<GridItem> for Node {
218    fn from(w: GridItem) -> Self {
219        Node::GridItem(w)
220    }
221}
222impl From<Checkbox> for Node {
223    fn from(w: Checkbox) -> Self {
224        Node::Checkbox(w)
225    }
226}
227impl From<Switch> for Node {
228    fn from(w: Switch) -> Self {
229        Node::Switch(w)
230    }
231}
232impl From<Radio> for Node {
233    fn from(w: Radio) -> Self {
234        Node::Radio(w)
235    }
236}
237impl From<SafeArea> for Node {
238    fn from(w: SafeArea) -> Self {
239        Node::SafeArea(w)
240    }
241}
242impl From<Composite> for Node {
243    fn from(w: Composite) -> Self {
244        Node::Composite(w)
245    }
246}
247impl From<Positioned> for Node {
248    fn from(w: Positioned) -> Self {
249        Node::Positioned(w)
250    }
251}
252impl From<Spacer> for Node {
253    fn from(w: Spacer) -> Self {
254        Node::Spacer(w)
255    }
256}
257impl From<Slider> for Node {
258    fn from(w: Slider) -> Self {
259        Node::Slider(w)
260    }
261}
262impl From<LazyColumn> for Node {
263    fn from(w: LazyColumn) -> Self {
264        Node::LazyColumn(w)
265    }
266}
267impl From<Icon> for Node {
268    fn from(w: Icon) -> Self {
269        Node::Icon(w)
270    }
271}
272
273#[derive(Clone, Debug, Serialize, Deserialize)]
274pub struct CustomNode {
275    pub debug_tag: String,
276    #[serde(skip)]
277    pub lowerer: Option<Arc<dyn LowerDyn>>,
278    /// Optional render object that participates in hit-testing, event handling,
279    /// and painting.  When `None`, the node behaves exactly as before (lowering
280    /// only via `LowerDyn`).
281    #[serde(skip)]
282    pub render_object: Option<Arc<dyn CustomRenderObject>>,
283}