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 = lowerer.widget_id().unwrap_or_else(|| 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);
451 }
452
453 node_id
454 }
455 }
456 }
457}
458
459impl From<Row> for Widget {
460 fn from(w: Row) -> Self {
461 Self {
462 kind: Box::new(WidgetKind::Row(w)),
463 }
464 }
465}
466impl From<ActionScope> for Widget {
467 fn from(w: ActionScope) -> Self {
468 Self {
469 kind: Box::new(WidgetKind::ActionScope(w)),
470 }
471 }
472}
473impl From<Column> for Widget {
474 fn from(w: Column) -> Self {
475 Self {
476 kind: Box::new(WidgetKind::Column(w)),
477 }
478 }
479}
480impl From<Align> for Widget {
481 fn from(w: Align) -> Self {
482 Self {
483 kind: Box::new(WidgetKind::Align(w)),
484 }
485 }
486}
487impl From<FocusScope> for Widget {
488 fn from(w: FocusScope) -> Self {
489 Self {
490 kind: Box::new(WidgetKind::FocusScope(w)),
491 }
492 }
493}
494impl From<Clip> for Widget {
495 fn from(w: Clip) -> Self {
496 Self {
497 kind: Box::new(WidgetKind::Clip(w)),
498 }
499 }
500}
501impl From<Text> for Widget {
502 fn from(w: Text) -> Self {
503 Self {
504 kind: Box::new(WidgetKind::Text(w)),
505 }
506 }
507}
508impl From<RichText> for Widget {
509 fn from(w: RichText) -> Self {
510 Self {
511 kind: Box::new(WidgetKind::RichText(w)),
512 }
513 }
514}
515impl From<Transform> for Widget {
516 fn from(w: Transform) -> Self {
517 Self {
518 kind: Box::new(WidgetKind::Transform(w)),
519 }
520 }
521}
522impl From<Button> for Widget {
523 fn from(mut w: Button) -> Self {
524 if let Some(motion) = w.motion.take() {
525 let button_id = crate::build::current_widget_id()
526 .or(w.id)
527 .unwrap_or_else(|| WidgetId::explicit("fission.core.button.motion"));
528 w.id = Some(button_id);
529 let motion_id = WidgetId::derived(button_id.as_u128(), &[0xB0770]);
530 let tracks = motion.interaction_tracks(button_id);
531 let ripple = motion.ripple();
532 let base = Self {
533 kind: Box::new(WidgetKind::Button(w)),
534 };
535 let with_motion: Widget = if tracks.is_empty() {
536 base
537 } else {
538 crate::motion::Motion {
539 id: motion_id,
540 tracks,
541 child: base,
542 ..Default::default()
543 }
544 .into()
545 };
546 return if let Some(effect) = ripple {
547 crate::motion::RippleLayer {
548 id: WidgetId::derived(button_id.as_u128(), &[0xA11E]),
549 effect,
550 child: with_motion,
551 }
552 .into()
553 } else {
554 with_motion
555 };
556 }
557 Self {
558 kind: Box::new(WidgetKind::Button(w)),
559 }
560 }
561}
562impl From<TextInput> for Widget {
563 fn from(w: TextInput) -> Self {
564 Self {
565 kind: Box::new(WidgetKind::TextInput(w)),
566 }
567 }
568}
569impl From<Scroll> for Widget {
570 fn from(w: Scroll) -> Self {
571 Self {
572 kind: Box::new(WidgetKind::Scroll(w)),
573 }
574 }
575}
576impl From<SemanticsRegion> for Widget {
577 fn from(w: SemanticsRegion) -> Self {
578 Self {
579 kind: Box::new(WidgetKind::SemanticsRegion(w)),
580 }
581 }
582}
583impl From<Image> for Widget {
584 fn from(w: Image) -> Self {
585 Self {
586 kind: Box::new(WidgetKind::Image(w)),
587 }
588 }
589}
590impl From<Video> for Widget {
591 fn from(w: Video) -> Self {
592 let node_id = crate::build::current_widget_id()
593 .or(w.id)
594 .unwrap_or_else(|| fission_ir::WidgetId::explicit(&w.source.key()));
595 crate::build::try_register_video(crate::registry::VideoRegistration {
596 node_id,
597 source: w.source.as_str().to_string(),
598 autoplay: w.autoplay,
599 loop_playback: w.loop_playback,
600 audio: w.audio.clone(),
601 });
602 Self {
603 kind: Box::new(WidgetKind::Video(w)),
604 }
605 }
606}
607impl From<ZStack> for Widget {
608 fn from(w: ZStack) -> Self {
609 Self {
610 kind: Box::new(WidgetKind::ZStack(w)),
611 }
612 }
613}
614impl From<Overlay> for Widget {
615 fn from(w: Overlay) -> Self {
616 Self {
617 kind: Box::new(WidgetKind::Overlay(w)),
618 }
619 }
620}
621impl From<ContextMenuRegion> for Widget {
622 fn from(w: ContextMenuRegion) -> Self {
623 Self {
624 kind: Box::new(WidgetKind::ContextMenuRegion(w)),
625 }
626 }
627}
628
629impl From<Container> for Widget {
630 fn from(w: Container) -> Self {
631 Self {
632 kind: Box::new(WidgetKind::Container(w)),
633 }
634 }
635}
636impl From<GestureDetector> for Widget {
637 fn from(w: GestureDetector) -> Self {
638 Self {
639 kind: Box::new(WidgetKind::GestureDetector(w)),
640 }
641 }
642}
643impl From<Grid> for Widget {
644 fn from(w: Grid) -> Self {
645 Self {
646 kind: Box::new(WidgetKind::Grid(w)),
647 }
648 }
649}
650impl From<GridItem> for Widget {
651 fn from(w: GridItem) -> Self {
652 Self {
653 kind: Box::new(WidgetKind::GridItem(w)),
654 }
655 }
656}
657impl From<Responsive> for Widget {
658 fn from(w: Responsive) -> Self {
659 Self {
660 kind: Box::new(WidgetKind::Responsive(w)),
661 }
662 }
663}
664impl From<Checkbox> for Widget {
665 fn from(w: Checkbox) -> Self {
666 Self {
667 kind: Box::new(WidgetKind::Checkbox(w)),
668 }
669 }
670}
671impl From<Switch> for Widget {
672 fn from(w: Switch) -> Self {
673 Self {
674 kind: Box::new(WidgetKind::Switch(w)),
675 }
676 }
677}
678impl From<Radio> for Widget {
679 fn from(w: Radio) -> Self {
680 Self {
681 kind: Box::new(WidgetKind::Radio(w)),
682 }
683 }
684}
685impl From<SafeArea> for Widget {
686 fn from(w: SafeArea) -> Self {
687 Self {
688 kind: Box::new(WidgetKind::SafeArea(w)),
689 }
690 }
691}
692impl From<Composite> for Widget {
693 fn from(w: Composite) -> Self {
694 Self {
695 kind: Box::new(WidgetKind::Composite(w)),
696 }
697 }
698}
699impl From<Positioned> for Widget {
700 fn from(w: Positioned) -> Self {
701 Self {
702 kind: Box::new(WidgetKind::Positioned(w)),
703 }
704 }
705}
706impl From<Spacer> for Widget {
707 fn from(w: Spacer) -> Self {
708 Self {
709 kind: Box::new(WidgetKind::Spacer(w)),
710 }
711 }
712}
713impl From<Slider> for Widget {
714 fn from(w: Slider) -> Self {
715 Self {
716 kind: Box::new(WidgetKind::Slider(w)),
717 }
718 }
719}
720impl From<LazyColumn> for Widget {
721 fn from(w: LazyColumn) -> Self {
722 Self {
723 kind: Box::new(WidgetKind::LazyColumn(w)),
724 }
725 }
726}
727impl From<Icon> for Widget {
728 fn from(w: Icon) -> Self {
729 Self {
730 kind: Box::new(WidgetKind::Icon(w)),
731 }
732 }
733}
734
735#[derive(Clone, Debug, Serialize, Deserialize)]
736pub struct InternalRenderNode {
737 pub debug_tag: String,
738 #[serde(skip)]
739 pub lowerer: Option<Arc<dyn InternalLowerer>>,
740 #[serde(skip)]
744 pub render_object: Option<Arc<dyn CustomRenderObject>>,
745}
746
747pub type CustomWidget = InternalRenderNode;
748
749impl From<CustomWidget> for Widget {
750 fn from(node: CustomWidget) -> Self {
751 Widget::custom(node)
752 }
753}