Skip to main content

fission_core/ui/
node.rs

1use super::custom_render::CustomRenderObject;
2use super::traits::{InternalLower, InternalLowerer};
3use super::widgets::{
4    ActionScope, Align, Button, Checkbox, Clip, Column, Composite, Container, ContextMenuRegion,
5    FocusScope, GestureDetector, Grid, GridItem, Icon, Image, LazyColumn, Overlay, Positioned,
6    Pressable, Radio, Responsive, RichText, Row, SafeArea, Scroll, SemanticsRegion, Slider, Spacer,
7    Switch, Text, TextInput, Transform, Video, ZStack,
8};
9use crate::lowering::InternalLoweringCx;
10use fission_ir::{Op, StructuralOp, WidgetId};
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct Widget {
16    kind: Box<WidgetKind>,
17}
18
19#[derive(Clone, Debug, Serialize, Deserialize)]
20enum WidgetKind {
21    Identified { id: WidgetId, child: Widget },
22    ActionScope(ActionScope),
23    Row(Row),
24    Column(Column),
25    Align(Align),
26    FocusScope(FocusScope),
27    Clip(Clip),
28    Text(Text),
29    RichText(RichText),
30    Transform(Transform),
31    Button(Button),
32    Pressable(Pressable),
33    TextInput(TextInput),
34    Scroll(Scroll),
35    SemanticsRegion(SemanticsRegion),
36    Image(Image),
37    Video(Video),
38    ZStack(ZStack),
39    Overlay(Overlay),
40    Container(Container),
41    ContextMenuRegion(ContextMenuRegion),
42    GestureDetector(GestureDetector),
43    Grid(Grid),
44    GridItem(GridItem),
45    Responsive(Responsive),
46    Checkbox(Checkbox),
47    Switch(Switch),
48    Radio(Radio),
49    SafeArea(SafeArea),
50    Positioned(Positioned),
51    Spacer(Spacer),
52    Slider(Slider),
53    LazyColumn(LazyColumn),
54    Icon(Icon),
55    Composite(Composite),
56    Custom(InternalRenderNode),
57}
58
59impl Widget {
60    pub(crate) fn with_id(self, id: WidgetId) -> Self {
61        let kind = match *self.kind {
62            WidgetKind::Identified { child, .. } => WidgetKind::Identified { id, child },
63            WidgetKind::ActionScope(w) => WidgetKind::Identified {
64                id,
65                child: Widget {
66                    kind: Box::new(WidgetKind::ActionScope(w)),
67                },
68            },
69            WidgetKind::Custom(w) => WidgetKind::Identified {
70                id,
71                child: Widget {
72                    kind: Box::new(WidgetKind::Custom(w)),
73                },
74            },
75            WidgetKind::Row(mut w) => {
76                w.id = Some(id);
77                WidgetKind::Row(w)
78            }
79            WidgetKind::Column(mut w) => {
80                w.id = Some(id);
81                WidgetKind::Column(w)
82            }
83            WidgetKind::Align(mut w) => {
84                w.id = Some(id);
85                WidgetKind::Align(w)
86            }
87            WidgetKind::FocusScope(mut w) => {
88                w.id = Some(id);
89                WidgetKind::FocusScope(w)
90            }
91            WidgetKind::Clip(mut w) => {
92                w.id = Some(id);
93                WidgetKind::Clip(w)
94            }
95            WidgetKind::Text(mut w) => {
96                w.id = Some(id);
97                WidgetKind::Text(w)
98            }
99            WidgetKind::RichText(mut w) => {
100                w.id = Some(id);
101                WidgetKind::RichText(w)
102            }
103            WidgetKind::Transform(mut w) => {
104                w.id = Some(id);
105                WidgetKind::Transform(w)
106            }
107            WidgetKind::Button(mut w) => {
108                w.id = Some(id);
109                WidgetKind::Button(w)
110            }
111            WidgetKind::Pressable(mut w) => {
112                w.id = Some(id);
113                WidgetKind::Pressable(w)
114            }
115            WidgetKind::TextInput(mut w) => {
116                w.id = Some(id);
117                WidgetKind::TextInput(w)
118            }
119            WidgetKind::Scroll(mut w) => {
120                w.id = Some(id);
121                WidgetKind::Scroll(w)
122            }
123            WidgetKind::SemanticsRegion(mut w) => {
124                w.id = Some(id);
125                WidgetKind::SemanticsRegion(w)
126            }
127            WidgetKind::Image(mut w) => {
128                w.id = Some(id);
129                WidgetKind::Image(w)
130            }
131            WidgetKind::Video(mut w) => {
132                w.id = Some(id);
133                WidgetKind::Video(w)
134            }
135            WidgetKind::ZStack(mut w) => {
136                w.id = Some(id);
137                WidgetKind::ZStack(w)
138            }
139            WidgetKind::Overlay(mut w) => {
140                w.id = Some(id);
141                WidgetKind::Overlay(w)
142            }
143            WidgetKind::Container(mut w) => {
144                w.id = Some(id);
145                WidgetKind::Container(w)
146            }
147            WidgetKind::ContextMenuRegion(mut w) => {
148                w.id = Some(id);
149                WidgetKind::ContextMenuRegion(w)
150            }
151            WidgetKind::GestureDetector(mut w) => {
152                w.id = Some(id);
153                WidgetKind::GestureDetector(w)
154            }
155            WidgetKind::Grid(mut w) => {
156                w.id = Some(id);
157                WidgetKind::Grid(w)
158            }
159            WidgetKind::GridItem(mut w) => {
160                w.id = Some(id);
161                WidgetKind::GridItem(w)
162            }
163            WidgetKind::Responsive(mut w) => {
164                w.id = Some(id);
165                WidgetKind::Responsive(w)
166            }
167            WidgetKind::Checkbox(mut w) => {
168                w.id = Some(id);
169                WidgetKind::Checkbox(w)
170            }
171            WidgetKind::Switch(mut w) => {
172                w.id = Some(id);
173                WidgetKind::Switch(w)
174            }
175            WidgetKind::Radio(mut w) => {
176                w.id = Some(id);
177                WidgetKind::Radio(w)
178            }
179            WidgetKind::SafeArea(mut w) => {
180                w.id = Some(id);
181                WidgetKind::SafeArea(w)
182            }
183            WidgetKind::Positioned(mut w) => {
184                w.id = Some(id);
185                WidgetKind::Positioned(w)
186            }
187            WidgetKind::Spacer(mut w) => {
188                w.id = Some(id);
189                WidgetKind::Spacer(w)
190            }
191            WidgetKind::Slider(mut w) => {
192                w.id = Some(id);
193                WidgetKind::Slider(w)
194            }
195            WidgetKind::LazyColumn(mut w) => {
196                w.id = Some(id);
197                WidgetKind::LazyColumn(w)
198            }
199            WidgetKind::Icon(mut w) => {
200                w.id = Some(id);
201                WidgetKind::Icon(w)
202            }
203            WidgetKind::Composite(mut w) => {
204                w.id = Some(id);
205                WidgetKind::Composite(w)
206            }
207        };
208        Self {
209            kind: Box::new(kind),
210        }
211    }
212
213    pub fn id<I>(self, id: I) -> Self
214    where
215        I: Into<WidgetId>,
216    {
217        self.with_id(id.into())
218    }
219
220    pub(crate) fn custom(node: InternalRenderNode) -> Self {
221        Self {
222            kind: Box::new(WidgetKind::Custom(node)),
223        }
224    }
225
226    pub(crate) fn from_pressable_raw(pressable: Pressable) -> Self {
227        Self {
228            kind: Box::new(WidgetKind::Pressable(pressable)),
229        }
230    }
231
232    pub(crate) fn into_text(self) -> Result<Text, Self> {
233        match *self.kind {
234            WidgetKind::Text(text) => Ok(text),
235            kind => Err(Self {
236                kind: Box::new(kind),
237            }),
238        }
239    }
240
241    pub(crate) fn kind_name(&self) -> &'static str {
242        match &*self.kind {
243            WidgetKind::Identified { .. } => "Identified",
244            WidgetKind::ActionScope(_) => "ActionScope",
245            WidgetKind::Row(_) => "Row",
246            WidgetKind::Column(_) => "Column",
247            WidgetKind::Align(_) => "Align",
248            WidgetKind::FocusScope(_) => "FocusScope",
249            WidgetKind::Clip(_) => "Clip",
250            WidgetKind::Text(_) => "Text",
251            WidgetKind::RichText(_) => "RichText",
252            WidgetKind::Transform(_) => "Transform",
253            WidgetKind::Button(_) => "Button",
254            WidgetKind::Pressable(_) => "Pressable",
255            WidgetKind::TextInput(_) => "TextInput",
256            WidgetKind::Scroll(_) => "Scroll",
257            WidgetKind::SemanticsRegion(_) => "SemanticsRegion",
258            WidgetKind::Image(_) => "Image",
259            WidgetKind::Video(_) => "Video",
260            WidgetKind::ZStack(_) => "ZStack",
261            WidgetKind::Overlay(_) => "Overlay",
262            WidgetKind::Container(_) => "Container",
263            WidgetKind::ContextMenuRegion(_) => "ContextMenuRegion",
264            WidgetKind::GestureDetector(_) => "GestureDetector",
265            WidgetKind::Grid(_) => "Grid",
266            WidgetKind::GridItem(_) => "GridItem",
267            WidgetKind::Responsive(_) => "Responsive",
268            WidgetKind::Checkbox(_) => "Checkbox",
269            WidgetKind::Switch(_) => "Switch",
270            WidgetKind::Radio(_) => "Radio",
271            WidgetKind::SafeArea(_) => "SafeArea",
272            WidgetKind::Positioned(_) => "Positioned",
273            WidgetKind::Spacer(_) => "Spacer",
274            WidgetKind::Slider(_) => "Slider",
275            WidgetKind::LazyColumn(_) => "LazyColumn",
276            WidgetKind::Icon(_) => "Icon",
277            WidgetKind::Composite(_) => "Composite",
278            WidgetKind::Custom(_) => "Custom",
279        }
280    }
281
282    pub(crate) fn as_row(&self) -> Option<&Row> {
283        match &*self.kind {
284            WidgetKind::Identified { child, .. } => child.as_row(),
285            WidgetKind::Row(widget) => Some(widget),
286            _ => None,
287        }
288    }
289
290    pub(crate) fn as_column(&self) -> Option<&Column> {
291        match &*self.kind {
292            WidgetKind::Identified { child, .. } => child.as_column(),
293            WidgetKind::Column(widget) => Some(widget),
294            _ => None,
295        }
296    }
297
298    pub(crate) fn as_container(&self) -> Option<&Container> {
299        match &*self.kind {
300            WidgetKind::Identified { child, .. } => child.as_container(),
301            WidgetKind::Container(widget) => Some(widget),
302            _ => None,
303        }
304    }
305
306    pub(crate) fn as_scroll(&self) -> Option<&Scroll> {
307        match &*self.kind {
308            WidgetKind::Identified { child, .. } => child.as_scroll(),
309            WidgetKind::Scroll(widget) => Some(widget),
310            _ => None,
311        }
312    }
313
314    pub(crate) fn as_rich_text(&self) -> Option<&RichText> {
315        match &*self.kind {
316            WidgetKind::Identified { child, .. } => child.as_rich_text(),
317            WidgetKind::RichText(widget) => Some(widget),
318            _ => None,
319        }
320    }
321
322    pub(crate) fn as_text(&self) -> Option<&Text> {
323        match &*self.kind {
324            WidgetKind::Identified { child, .. } => child.as_text(),
325            WidgetKind::Text(widget) => Some(widget),
326            _ => None,
327        }
328    }
329
330    pub(crate) fn as_text_input(&self) -> Option<&TextInput> {
331        match &*self.kind {
332            WidgetKind::Identified { child, .. } => child.as_text_input(),
333            WidgetKind::TextInput(widget) => Some(widget),
334            _ => None,
335        }
336    }
337
338    pub(crate) fn as_button(&self) -> Option<&Button> {
339        match &*self.kind {
340            WidgetKind::Identified { child, .. } => child.as_button(),
341            WidgetKind::Button(widget) => Some(widget),
342            _ => None,
343        }
344    }
345
346    pub(crate) fn as_gesture_detector(&self) -> Option<&GestureDetector> {
347        match &*self.kind {
348            WidgetKind::Identified { child, .. } => child.as_gesture_detector(),
349            WidgetKind::GestureDetector(widget) => Some(widget),
350            _ => None,
351        }
352    }
353
354    pub(crate) fn as_zstack(&self) -> Option<&ZStack> {
355        match &*self.kind {
356            WidgetKind::Identified { child, .. } => child.as_zstack(),
357            WidgetKind::ZStack(widget) => Some(widget),
358            _ => None,
359        }
360    }
361}
362
363pub trait WidgetIdExt: Into<Widget> + Sized {
364    fn id<I>(self, id: I) -> Widget
365    where
366        I: Into<WidgetId>,
367    {
368        let id = id.into();
369        crate::build::with_widget_id(id, || {
370            let widget: Widget = self.into();
371            widget.with_id(id)
372        })
373    }
374}
375
376impl<T> WidgetIdExt for T where T: Into<Widget> {}
377
378impl Widget {
379    pub(crate) fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
380        match &*self.kind {
381            WidgetKind::Identified { id, child } => {
382                let child_id = child.lower(cx);
383                let mut builder = crate::lowering::InternalIrBuilder::new(
384                    (*id).into(),
385                    Op::Structural(StructuralOp::Group {
386                        stable_hash: id.as_u128() as u64,
387                    }),
388                );
389                builder.add_child(child_id);
390                builder.build(cx)
391            }
392            WidgetKind::ActionScope(w) => w.lower(cx),
393            WidgetKind::Row(w) => w.lower(cx),
394            WidgetKind::Column(w) => w.lower(cx),
395            WidgetKind::Align(w) => w.lower(cx),
396            WidgetKind::FocusScope(w) => w.lower(cx),
397            WidgetKind::Clip(w) => w.lower(cx),
398            WidgetKind::Text(w) => w.lower(cx),
399            WidgetKind::RichText(w) => w.lower(cx),
400            WidgetKind::Transform(w) => w.lower(cx),
401            WidgetKind::Button(w) => w.lower(cx),
402            WidgetKind::Pressable(w) => w.lower(cx),
403            WidgetKind::TextInput(w) => w.lower(cx),
404            WidgetKind::Scroll(w) => w.lower(cx),
405            WidgetKind::SemanticsRegion(w) => w.lower(cx),
406            WidgetKind::Image(w) => w.lower(cx),
407            WidgetKind::Video(w) => w.lower(cx),
408            WidgetKind::ZStack(w) => w.lower(cx),
409            WidgetKind::Overlay(w) => w.lower(cx),
410            WidgetKind::Container(w) => w.lower(cx),
411            WidgetKind::ContextMenuRegion(w) => w.lower(cx),
412            WidgetKind::GestureDetector(w) => w.lower(cx),
413            WidgetKind::Grid(w) => w.lower(cx),
414            WidgetKind::GridItem(w) => w.lower(cx),
415            WidgetKind::Responsive(w) => w.lower(cx),
416            WidgetKind::Checkbox(w) => w.lower(cx),
417            WidgetKind::Switch(w) => w.lower(cx),
418            WidgetKind::Radio(w) => w.lower(cx),
419            WidgetKind::SafeArea(w) => w.lower(cx),
420            WidgetKind::Positioned(w) => w.lower(cx),
421            WidgetKind::Spacer(w) => w.lower(cx),
422            WidgetKind::Slider(w) => w.lower(cx),
423            WidgetKind::LazyColumn(w) => w.lower(cx),
424            WidgetKind::Icon(w) => w.lower(cx),
425            WidgetKind::Composite(w) => w.lower(cx),
426            WidgetKind::Custom(w) => {
427                let lowerer = w
428                    .lowerer
429                    .as_ref()
430                    .expect("CustomWidget lowerer must be set");
431                let child_id = lowerer.lower_dyn(cx);
432                let wrapper = cx.next_node_id();
433                let mut builder = crate::lowering::InternalIrBuilder::new(
434                    wrapper,
435                    Op::Structural(StructuralOp::Group {
436                        stable_hash: lowerer.stable_key(),
437                    }),
438                );
439                builder.add_child(child_id);
440                let node_id = builder.build(cx);
441
442                // If the custom node carries a render object, store it in the
443                // IR so that hit-testing and event handling can find it later.
444                // We wrap the `Arc<dyn CustomRenderObject>` in a `RenderObjectHolder`
445                // so it can be stored as `Arc<dyn Any + Send + Sync>` in the
446                // dependency-free IR crate and downcast back later.
447                if let Some(render_obj) = &w.render_object {
448                    let holder = crate::ui::custom_render::RenderObjectHolder(render_obj.clone());
449                    let erased: fission_ir::AnyRenderObject = Arc::new(holder);
450                    // Register the render object at the wrapper AND every node in
451                    // the lowered subtree so the parent-walk from any hit descendant
452                    // finds it regardless of tree depth.
453                    cx.ir.custom_render_objects.insert(node_id, erased.clone());
454                    fn register_subtree(
455                        ir: &mut fission_ir::CoreIR,
456                        node_id: fission_ir::WidgetId,
457                        erased: &fission_ir::AnyRenderObject,
458                    ) {
459                        ir.custom_render_objects.insert(node_id, erased.clone());
460                        if let Some(children) = ir.nodes.get(&node_id).map(|n| n.children.clone()) {
461                            for child_id in children {
462                                register_subtree(ir, child_id, erased);
463                            }
464                        }
465                    }
466                    register_subtree(&mut cx.ir, child_id, &erased);
467                }
468
469                node_id
470            }
471        }
472    }
473}
474
475impl From<Row> for Widget {
476    fn from(w: Row) -> Self {
477        Self {
478            kind: Box::new(WidgetKind::Row(w)),
479        }
480    }
481}
482impl From<ActionScope> for Widget {
483    fn from(w: ActionScope) -> Self {
484        Self {
485            kind: Box::new(WidgetKind::ActionScope(w)),
486        }
487    }
488}
489impl From<Column> for Widget {
490    fn from(w: Column) -> Self {
491        Self {
492            kind: Box::new(WidgetKind::Column(w)),
493        }
494    }
495}
496impl From<Align> for Widget {
497    fn from(w: Align) -> Self {
498        Self {
499            kind: Box::new(WidgetKind::Align(w)),
500        }
501    }
502}
503impl From<FocusScope> for Widget {
504    fn from(w: FocusScope) -> Self {
505        Self {
506            kind: Box::new(WidgetKind::FocusScope(w)),
507        }
508    }
509}
510impl From<Clip> for Widget {
511    fn from(w: Clip) -> Self {
512        Self {
513            kind: Box::new(WidgetKind::Clip(w)),
514        }
515    }
516}
517impl From<Text> for Widget {
518    fn from(w: Text) -> Self {
519        Self {
520            kind: Box::new(WidgetKind::Text(w)),
521        }
522    }
523}
524impl From<RichText> for Widget {
525    fn from(w: RichText) -> Self {
526        Self {
527            kind: Box::new(WidgetKind::RichText(w)),
528        }
529    }
530}
531impl From<Transform> for Widget {
532    fn from(w: Transform) -> Self {
533        Self {
534            kind: Box::new(WidgetKind::Transform(w)),
535        }
536    }
537}
538impl From<Button> for Widget {
539    fn from(mut w: Button) -> Self {
540        if let Some(motion) = w.motion.take() {
541            let button_id = crate::build::current_widget_id()
542                .or(w.id)
543                .unwrap_or_else(|| WidgetId::explicit("fission.core.button.motion"));
544            w.id = Some(button_id);
545            let motion_id = WidgetId::derived(button_id.as_u128(), &[0xB0770]);
546            let tracks = motion.interaction_tracks(button_id);
547            let ripple = motion.ripple();
548            let base = Self {
549                kind: Box::new(WidgetKind::Button(w)),
550            };
551            let with_motion: Widget = if tracks.is_empty() {
552                base
553            } else {
554                crate::motion::Motion {
555                    id: motion_id,
556                    tracks,
557                    child: base,
558                    ..Default::default()
559                }
560                .into()
561            };
562            return if let Some(effect) = ripple {
563                crate::motion::RippleLayer {
564                    id: WidgetId::derived(button_id.as_u128(), &[0xA11E]),
565                    effect,
566                    child: with_motion,
567                }
568                .into()
569            } else {
570                with_motion
571            };
572        }
573        Self {
574            kind: Box::new(WidgetKind::Button(w)),
575        }
576    }
577}
578impl From<TextInput> for Widget {
579    fn from(w: TextInput) -> Self {
580        Self {
581            kind: Box::new(WidgetKind::TextInput(w)),
582        }
583    }
584}
585impl From<Scroll> for Widget {
586    fn from(w: Scroll) -> Self {
587        Self {
588            kind: Box::new(WidgetKind::Scroll(w)),
589        }
590    }
591}
592impl From<SemanticsRegion> for Widget {
593    fn from(w: SemanticsRegion) -> Self {
594        Self {
595            kind: Box::new(WidgetKind::SemanticsRegion(w)),
596        }
597    }
598}
599impl From<Image> for Widget {
600    fn from(w: Image) -> Self {
601        Self {
602            kind: Box::new(WidgetKind::Image(w)),
603        }
604    }
605}
606impl From<Video> for Widget {
607    fn from(w: Video) -> Self {
608        let node_id = crate::build::current_widget_id()
609            .or(w.id)
610            .unwrap_or_else(|| fission_ir::WidgetId::explicit(&w.source.key()));
611        crate::build::try_register_video(crate::registry::VideoRegistration {
612            node_id,
613            source: w.source.as_str().to_string(),
614            autoplay: w.autoplay,
615            loop_playback: w.loop_playback,
616            audio: w.audio.clone(),
617        });
618        Self {
619            kind: Box::new(WidgetKind::Video(w)),
620        }
621    }
622}
623impl From<ZStack> for Widget {
624    fn from(w: ZStack) -> Self {
625        Self {
626            kind: Box::new(WidgetKind::ZStack(w)),
627        }
628    }
629}
630impl From<Overlay> for Widget {
631    fn from(w: Overlay) -> Self {
632        Self {
633            kind: Box::new(WidgetKind::Overlay(w)),
634        }
635    }
636}
637impl From<ContextMenuRegion> for Widget {
638    fn from(w: ContextMenuRegion) -> Self {
639        Self {
640            kind: Box::new(WidgetKind::ContextMenuRegion(w)),
641        }
642    }
643}
644
645impl From<Container> for Widget {
646    fn from(w: Container) -> Self {
647        Self {
648            kind: Box::new(WidgetKind::Container(w)),
649        }
650    }
651}
652impl From<GestureDetector> for Widget {
653    fn from(w: GestureDetector) -> Self {
654        Self {
655            kind: Box::new(WidgetKind::GestureDetector(w)),
656        }
657    }
658}
659impl From<Grid> for Widget {
660    fn from(w: Grid) -> Self {
661        Self {
662            kind: Box::new(WidgetKind::Grid(w)),
663        }
664    }
665}
666impl From<GridItem> for Widget {
667    fn from(w: GridItem) -> Self {
668        Self {
669            kind: Box::new(WidgetKind::GridItem(w)),
670        }
671    }
672}
673impl From<Responsive> for Widget {
674    fn from(w: Responsive) -> Self {
675        Self {
676            kind: Box::new(WidgetKind::Responsive(w)),
677        }
678    }
679}
680impl From<Checkbox> for Widget {
681    fn from(w: Checkbox) -> Self {
682        Self {
683            kind: Box::new(WidgetKind::Checkbox(w)),
684        }
685    }
686}
687impl From<Switch> for Widget {
688    fn from(w: Switch) -> Self {
689        Self {
690            kind: Box::new(WidgetKind::Switch(w)),
691        }
692    }
693}
694impl From<Radio> for Widget {
695    fn from(w: Radio) -> Self {
696        Self {
697            kind: Box::new(WidgetKind::Radio(w)),
698        }
699    }
700}
701impl From<SafeArea> for Widget {
702    fn from(w: SafeArea) -> Self {
703        Self {
704            kind: Box::new(WidgetKind::SafeArea(w)),
705        }
706    }
707}
708impl From<Composite> for Widget {
709    fn from(w: Composite) -> Self {
710        Self {
711            kind: Box::new(WidgetKind::Composite(w)),
712        }
713    }
714}
715impl From<Positioned> for Widget {
716    fn from(w: Positioned) -> Self {
717        Self {
718            kind: Box::new(WidgetKind::Positioned(w)),
719        }
720    }
721}
722impl From<Spacer> for Widget {
723    fn from(w: Spacer) -> Self {
724        Self {
725            kind: Box::new(WidgetKind::Spacer(w)),
726        }
727    }
728}
729impl From<Slider> for Widget {
730    fn from(w: Slider) -> Self {
731        Self {
732            kind: Box::new(WidgetKind::Slider(w)),
733        }
734    }
735}
736impl From<LazyColumn> for Widget {
737    fn from(w: LazyColumn) -> Self {
738        Self {
739            kind: Box::new(WidgetKind::LazyColumn(w)),
740        }
741    }
742}
743impl From<Icon> for Widget {
744    fn from(w: Icon) -> Self {
745        Self {
746            kind: Box::new(WidgetKind::Icon(w)),
747        }
748    }
749}
750
751#[derive(Clone, Debug, Serialize, Deserialize)]
752pub struct InternalRenderNode {
753    pub debug_tag: String,
754    #[serde(skip)]
755    pub lowerer: Option<Arc<dyn InternalLowerer>>,
756    /// Optional render object that participates in hit-testing, event handling,
757    /// and painting.  When `None`, the node behaves exactly as before (lowering
758    /// only via `InternalLowerer`).
759    #[serde(skip)]
760    pub render_object: Option<Arc<dyn CustomRenderObject>>,
761}
762
763pub type CustomWidget = InternalRenderNode;
764
765impl From<CustomWidget> for Widget {
766    fn from(node: CustomWidget) -> Self {
767        Widget::custom(node)
768    }
769}