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