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 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 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 #[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}