1use super::custom_render::CustomRenderObject;
2use super::traits::{InternalLower, InternalLowerer};
3#[cfg(feature = "interactive-canvas")]
4use super::widgets::InteractiveViewer;
5use super::widgets::{
6 ActionScope, Align, Button, Checkbox, Clip, Column, Composite, Container, ContextMenuEntry,
7 ContextMenuRegion, FocusScope, GestureDetector, Grid, GridItem, Icon, Image, LazyColumn,
8 Overlay, Positioned, Pressable, Radio, Responsive, RichText, Row, SafeArea, Scroll,
9 SelectionRegion, SemanticsRegion, Slider, Spacer, Switch, Text, TextInput, Transform, Video,
10 ZStack,
11};
12use crate::lowering::InternalLoweringCx;
13use fission_ir::{Op, StructuralOp, WidgetId};
14use serde::{Deserialize, Serialize};
15use std::ops::ControlFlow;
16use std::sync::Arc;
17
18#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct Widget {
20 kind: Box<WidgetKind>,
21}
22
23#[derive(Clone, Debug, Serialize, Deserialize)]
24pub enum WidgetKind {
25 Identified {
26 id: WidgetId,
27 child: Widget,
28 },
29 ActionScope(ActionScope),
30 Row(Row),
31 Column(Column),
32 Align(Align),
33 FocusScope(FocusScope),
34 SelectionRegion(SelectionRegion),
35 Clip(Clip),
36 Text(Text),
37 RichText(RichText),
38 Transform(Transform),
39 #[cfg(feature = "interactive-canvas")]
40 InteractiveViewer(InteractiveViewer),
41 Button(Button),
42 Pressable(Pressable),
43 TextInput(TextInput),
44 Scroll(Scroll),
45 SemanticsRegion(SemanticsRegion),
46 Image(Image),
47 Video(Video),
48 ZStack(ZStack),
49 Overlay(Overlay),
50 Container(Container),
51 ContextMenuRegion(ContextMenuRegion),
52 GestureDetector(GestureDetector),
53 Grid(Grid),
54 GridItem(GridItem),
55 Responsive(Responsive),
56 Checkbox(Checkbox),
57 Switch(Switch),
58 Radio(Radio),
59 SafeArea(SafeArea),
60 Positioned(Positioned),
61 Spacer(Spacer),
62 Slider(Slider),
63 LazyColumn(LazyColumn),
64 Icon(Icon),
65 Composite(Composite),
66 Custom(InternalRenderNode),
67}
68
69impl Widget {
70 const CHILD_ROLE: u32 = 0xF155_2000;
71
72 pub fn kind(&self) -> &WidgetKind {
74 self.kind.as_ref()
75 }
76
77 pub fn visit(&self, visitor: &mut impl FnMut(&Widget) -> ControlFlow<()>) -> ControlFlow<()> {
83 visitor(self)?;
84 match self.kind.as_ref() {
85 WidgetKind::Identified { child, .. }
86 | WidgetKind::ActionScope(ActionScope { child, .. })
87 | WidgetKind::SelectionRegion(SelectionRegion { child, .. })
88 | WidgetKind::Align(Align { child, .. })
89 | WidgetKind::Clip(Clip { child, .. })
90 | WidgetKind::Transform(Transform { child, .. })
91 | WidgetKind::Pressable(Pressable { child, .. })
92 | WidgetKind::GestureDetector(GestureDetector { child, .. })
93 | WidgetKind::GridItem(GridItem { child, .. })
94 | WidgetKind::SafeArea(SafeArea { child, .. })
95 | WidgetKind::Composite(Composite { child, .. }) => child.visit(visitor),
96 WidgetKind::Row(Row { children, .. })
97 | WidgetKind::Column(Column { children, .. })
98 | WidgetKind::FocusScope(FocusScope { children, .. })
99 | WidgetKind::ZStack(ZStack { children, .. })
100 | WidgetKind::Grid(Grid { children, .. })
101 | WidgetKind::LazyColumn(LazyColumn { children, .. }) => {
102 for child in children {
103 child.visit(visitor)?;
104 }
105 ControlFlow::Continue(())
106 }
107 WidgetKind::Button(Button { child, .. })
108 | WidgetKind::Scroll(Scroll { child, .. })
109 | WidgetKind::SemanticsRegion(SemanticsRegion { child, .. })
110 | WidgetKind::Container(Container { child, .. })
111 | WidgetKind::Positioned(Positioned { child, .. }) => {
112 if let Some(child) = child {
113 child.visit(visitor)?;
114 }
115 ControlFlow::Continue(())
116 }
117 WidgetKind::Overlay(Overlay {
118 content, overlay, ..
119 }) => {
120 content.visit(visitor)?;
121 overlay.visit(visitor)
122 }
123 WidgetKind::ContextMenuRegion(ContextMenuRegion { child, menu, .. }) => {
124 child.visit(visitor)?;
125 for entry in &menu.items {
126 if let ContextMenuEntry::Item(item) = entry {
127 item.child.visit(visitor)?;
128 }
129 }
130 ControlFlow::Continue(())
131 }
132 WidgetKind::Responsive(Responsive {
133 cases, fallback, ..
134 }) => {
135 for case in cases {
136 case.child.visit(visitor)?;
137 }
138 fallback.visit(visitor)
139 }
140 WidgetKind::RichText(RichText { inline_widgets, .. }) => {
141 for inline in inline_widgets {
142 inline.widget.visit(visitor)?;
143 }
144 ControlFlow::Continue(())
145 }
146 WidgetKind::TextInput(TextInput { prefix, suffix, .. }) => {
147 if let Some(prefix) = prefix {
148 prefix.visit(visitor)?;
149 }
150 if let Some(suffix) = suffix {
151 suffix.visit(visitor)?;
152 }
153 ControlFlow::Continue(())
154 }
155 #[cfg(feature = "interactive-canvas")]
156 WidgetKind::InteractiveViewer(InteractiveViewer { child, .. }) => child.visit(visitor),
157 WidgetKind::Custom(_)
158 | WidgetKind::Text(_)
159 | WidgetKind::Image(_)
160 | WidgetKind::Video(_)
161 | WidgetKind::Checkbox(_)
162 | WidgetKind::Switch(_)
163 | WidgetKind::Radio(_)
164 | WidgetKind::Spacer(_)
165 | WidgetKind::Slider(_)
166 | WidgetKind::Icon(_) => ControlFlow::Continue(()),
167 }
168 }
169
170 pub(crate) fn with_id(self, id: WidgetId) -> Self {
171 let kind = match *self.kind {
172 WidgetKind::Identified { child, .. } => WidgetKind::Identified { id, child },
173 WidgetKind::ActionScope(w) => WidgetKind::Identified {
174 id,
175 child: Widget {
176 kind: Box::new(WidgetKind::ActionScope(w)),
177 },
178 },
179 WidgetKind::Custom(w) => WidgetKind::Identified {
180 id,
181 child: Widget {
182 kind: Box::new(WidgetKind::Custom(w)),
183 },
184 },
185 WidgetKind::Row(mut w) => {
186 w.id = Some(id);
187 WidgetKind::Row(w)
188 }
189 WidgetKind::Column(mut w) => {
190 w.id = Some(id);
191 WidgetKind::Column(w)
192 }
193 WidgetKind::Align(mut w) => {
194 w.id = Some(id);
195 WidgetKind::Align(w)
196 }
197 WidgetKind::FocusScope(mut w) => {
198 w.id = Some(id);
199 WidgetKind::FocusScope(w)
200 }
201 WidgetKind::SelectionRegion(mut w) => {
202 w.id = Some(id);
203 WidgetKind::SelectionRegion(w)
204 }
205 WidgetKind::Clip(mut w) => {
206 w.id = Some(id);
207 WidgetKind::Clip(w)
208 }
209 WidgetKind::Text(mut w) => {
210 w.id = Some(id);
211 WidgetKind::Text(w)
212 }
213 WidgetKind::RichText(mut w) => {
214 w.id = Some(id);
215 WidgetKind::RichText(w)
216 }
217 WidgetKind::Transform(mut w) => {
218 w.id = Some(id);
219 WidgetKind::Transform(w)
220 }
221 #[cfg(feature = "interactive-canvas")]
222 WidgetKind::InteractiveViewer(mut w) => {
223 w.id = Some(id);
224 WidgetKind::InteractiveViewer(w)
225 }
226 WidgetKind::Button(mut w) => {
227 w.id = Some(id);
228 WidgetKind::Button(w)
229 }
230 WidgetKind::Pressable(mut w) => {
231 w.id = Some(id);
232 WidgetKind::Pressable(w)
233 }
234 WidgetKind::TextInput(mut w) => {
235 w.id = Some(id);
236 WidgetKind::TextInput(w)
237 }
238 WidgetKind::Scroll(mut w) => {
239 w.id = Some(id);
240 WidgetKind::Scroll(w)
241 }
242 WidgetKind::SemanticsRegion(mut w) => {
243 w.id = Some(id);
244 WidgetKind::SemanticsRegion(w)
245 }
246 WidgetKind::Image(mut w) => {
247 w.id = Some(id);
248 WidgetKind::Image(w)
249 }
250 WidgetKind::Video(mut w) => {
251 w.id = Some(id);
252 WidgetKind::Video(w)
253 }
254 WidgetKind::ZStack(mut w) => {
255 w.id = Some(id);
256 WidgetKind::ZStack(w)
257 }
258 WidgetKind::Overlay(mut w) => {
259 w.id = Some(id);
260 WidgetKind::Overlay(w)
261 }
262 WidgetKind::Container(mut w) => {
263 w.id = Some(id);
264 WidgetKind::Container(w)
265 }
266 WidgetKind::ContextMenuRegion(mut w) => {
267 w.id = Some(id);
268 WidgetKind::ContextMenuRegion(w)
269 }
270 WidgetKind::GestureDetector(mut w) => {
271 w.id = Some(id);
272 WidgetKind::GestureDetector(w)
273 }
274 WidgetKind::Grid(mut w) => {
275 w.id = Some(id);
276 WidgetKind::Grid(w)
277 }
278 WidgetKind::GridItem(mut w) => {
279 w.id = Some(id);
280 WidgetKind::GridItem(w)
281 }
282 WidgetKind::Responsive(mut w) => {
283 w.id = Some(id);
284 WidgetKind::Responsive(w)
285 }
286 WidgetKind::Checkbox(mut w) => {
287 w.id = Some(id);
288 WidgetKind::Checkbox(w)
289 }
290 WidgetKind::Switch(mut w) => {
291 w.id = Some(id);
292 WidgetKind::Switch(w)
293 }
294 WidgetKind::Radio(mut w) => {
295 w.id = Some(id);
296 WidgetKind::Radio(w)
297 }
298 WidgetKind::SafeArea(mut w) => {
299 w.id = Some(id);
300 WidgetKind::SafeArea(w)
301 }
302 WidgetKind::Positioned(mut w) => {
303 w.id = Some(id);
304 WidgetKind::Positioned(w)
305 }
306 WidgetKind::Spacer(mut w) => {
307 w.id = Some(id);
308 WidgetKind::Spacer(w)
309 }
310 WidgetKind::Slider(mut w) => {
311 w.id = Some(id);
312 WidgetKind::Slider(w)
313 }
314 WidgetKind::LazyColumn(mut w) => {
315 w.id = Some(id);
316 WidgetKind::LazyColumn(w)
317 }
318 WidgetKind::Icon(mut w) => {
319 w.id = Some(id);
320 WidgetKind::Icon(w)
321 }
322 WidgetKind::Composite(mut w) => {
323 w.id = Some(id);
324 WidgetKind::Composite(w)
325 }
326 };
327 Self {
328 kind: Box::new(kind),
329 }
330 }
331
332 pub fn id<I>(self, id: I) -> Self
333 where
334 I: Into<WidgetId>,
335 {
336 self.with_id(id.into())
337 }
338
339 pub(crate) fn custom(node: InternalRenderNode) -> Self {
340 Self {
341 kind: Box::new(WidgetKind::Custom(node)),
342 }
343 }
344
345 pub(crate) fn from_pressable_raw(pressable: Pressable) -> Self {
346 Self {
347 kind: Box::new(WidgetKind::Pressable(pressable)),
348 }
349 }
350
351 pub(crate) fn into_text(self) -> Result<Text, Self> {
352 match *self.kind {
353 WidgetKind::Text(text) => Ok(text),
354 kind => Err(Self {
355 kind: Box::new(kind),
356 }),
357 }
358 }
359
360 pub(crate) fn kind_name(&self) -> &'static str {
361 match &*self.kind {
362 WidgetKind::Identified { .. } => "Identified",
363 WidgetKind::ActionScope(_) => "ActionScope",
364 WidgetKind::Row(_) => "Row",
365 WidgetKind::Column(_) => "Column",
366 WidgetKind::Align(_) => "Align",
367 WidgetKind::FocusScope(_) => "FocusScope",
368 WidgetKind::SelectionRegion(_) => "SelectionRegion",
369 WidgetKind::Clip(_) => "Clip",
370 WidgetKind::Text(_) => "Text",
371 WidgetKind::RichText(_) => "RichText",
372 WidgetKind::Transform(_) => "Transform",
373 #[cfg(feature = "interactive-canvas")]
374 WidgetKind::InteractiveViewer(_) => "InteractiveViewer",
375 WidgetKind::Button(_) => "Button",
376 WidgetKind::Pressable(_) => "Pressable",
377 WidgetKind::TextInput(_) => "TextInput",
378 WidgetKind::Scroll(_) => "Scroll",
379 WidgetKind::SemanticsRegion(_) => "SemanticsRegion",
380 WidgetKind::Image(_) => "Image",
381 WidgetKind::Video(_) => "Video",
382 WidgetKind::ZStack(_) => "ZStack",
383 WidgetKind::Overlay(_) => "Overlay",
384 WidgetKind::Container(_) => "Container",
385 WidgetKind::ContextMenuRegion(_) => "ContextMenuRegion",
386 WidgetKind::GestureDetector(_) => "GestureDetector",
387 WidgetKind::Grid(_) => "Grid",
388 WidgetKind::GridItem(_) => "GridItem",
389 WidgetKind::Responsive(_) => "Responsive",
390 WidgetKind::Checkbox(_) => "Checkbox",
391 WidgetKind::Switch(_) => "Switch",
392 WidgetKind::Radio(_) => "Radio",
393 WidgetKind::SafeArea(_) => "SafeArea",
394 WidgetKind::Positioned(_) => "Positioned",
395 WidgetKind::Spacer(_) => "Spacer",
396 WidgetKind::Slider(_) => "Slider",
397 WidgetKind::LazyColumn(_) => "LazyColumn",
398 WidgetKind::Icon(_) => "Icon",
399 WidgetKind::Composite(_) => "Composite",
400 WidgetKind::Custom(_) => "Custom",
401 }
402 }
403
404 fn kind_discriminator(&self) -> u32 {
405 match &*self.kind {
408 WidgetKind::Identified { .. } => 1,
409 WidgetKind::ActionScope(_) => 2,
410 WidgetKind::Row(_) => 3,
411 WidgetKind::Column(_) => 4,
412 WidgetKind::Align(_) => 5,
413 WidgetKind::FocusScope(_) => 6,
414 WidgetKind::Clip(_) => 7,
415 WidgetKind::Text(_) => 8,
416 WidgetKind::RichText(_) => 9,
417 WidgetKind::Transform(_) => 10,
418 #[cfg(feature = "interactive-canvas")]
419 WidgetKind::InteractiveViewer(_) => 11,
420 WidgetKind::Button(_) => 12,
421 WidgetKind::Pressable(_) => 13,
422 WidgetKind::TextInput(_) => 14,
423 WidgetKind::Scroll(_) => 15,
424 WidgetKind::SemanticsRegion(_) => 16,
425 WidgetKind::Image(_) => 17,
426 WidgetKind::Video(_) => 18,
427 WidgetKind::ZStack(_) => 19,
428 WidgetKind::Overlay(_) => 20,
429 WidgetKind::Container(_) => 21,
430 WidgetKind::ContextMenuRegion(_) => 22,
431 WidgetKind::GestureDetector(_) => 23,
432 WidgetKind::Grid(_) => 24,
433 WidgetKind::GridItem(_) => 25,
434 WidgetKind::Responsive(_) => 26,
435 WidgetKind::Checkbox(_) => 27,
436 WidgetKind::Switch(_) => 28,
437 WidgetKind::Radio(_) => 29,
438 WidgetKind::SafeArea(_) => 30,
439 WidgetKind::Positioned(_) => 31,
440 WidgetKind::Spacer(_) => 32,
441 WidgetKind::Slider(_) => 33,
442 WidgetKind::LazyColumn(_) => 34,
443 WidgetKind::Icon(_) => 35,
444 WidgetKind::Composite(_) => 36,
445 WidgetKind::Custom(_) => 37,
446 WidgetKind::SelectionRegion(_) => 38,
447 }
448 }
449
450 pub(crate) fn declared_id(&self) -> Option<WidgetId> {
451 match &*self.kind {
452 WidgetKind::Identified { id, .. } => Some(*id),
453 WidgetKind::ActionScope(_) => None,
454 WidgetKind::Custom(widget) => widget
455 .lowerer
456 .as_ref()
457 .and_then(|lowerer| lowerer.widget_id()),
458 WidgetKind::Row(widget) => widget.id,
459 WidgetKind::Column(widget) => widget.id,
460 WidgetKind::Align(widget) => widget.id,
461 WidgetKind::FocusScope(widget) => widget.id,
462 WidgetKind::SelectionRegion(widget) => widget.id,
463 WidgetKind::Clip(widget) => widget.id,
464 WidgetKind::Text(widget) => widget.id,
465 WidgetKind::RichText(widget) => widget.id,
466 WidgetKind::Transform(widget) => widget.id,
467 #[cfg(feature = "interactive-canvas")]
468 WidgetKind::InteractiveViewer(widget) => widget.id,
469 WidgetKind::Button(widget) => widget.id,
470 WidgetKind::Pressable(widget) => widget.id,
471 WidgetKind::TextInput(widget) => widget.id,
472 WidgetKind::Scroll(widget) => widget.id,
473 WidgetKind::SemanticsRegion(widget) => widget.id,
474 WidgetKind::Image(widget) => widget.id,
475 WidgetKind::Video(widget) => widget.id,
476 WidgetKind::ZStack(widget) => widget.id,
477 WidgetKind::Overlay(widget) => widget.id,
478 WidgetKind::Container(widget) => widget.id,
479 WidgetKind::ContextMenuRegion(widget) => widget.id,
480 WidgetKind::GestureDetector(widget) => widget.id,
481 WidgetKind::Grid(widget) => widget.id,
482 WidgetKind::GridItem(widget) => widget.id,
483 WidgetKind::Responsive(widget) => widget.id,
484 WidgetKind::Checkbox(widget) => widget.id,
485 WidgetKind::Switch(widget) => widget.id,
486 WidgetKind::Radio(widget) => widget.id,
487 WidgetKind::SafeArea(widget) => widget.id,
488 WidgetKind::Positioned(widget) => widget.id,
489 WidgetKind::Spacer(widget) => widget.id,
490 WidgetKind::Slider(widget) => widget.id,
491 WidgetKind::LazyColumn(widget) => widget.id,
492 WidgetKind::Icon(widget) => widget.id,
493 WidgetKind::Composite(widget) => widget.id,
494 }
495 }
496
497 pub(crate) fn resolve_identities(self, root: WidgetId) -> Self {
498 self.resolve_identity(root)
499 }
500
501 fn resolve_identity(self, automatic_id: WidgetId) -> Self {
502 let resolved = if self.declared_id().is_some() {
503 self
504 } else {
505 self.with_id(automatic_id)
506 };
507 let parent = resolved.declared_id().unwrap_or(automatic_id);
508 resolved.resolve_descendants(parent)
509 }
510
511 fn resolve_descendants(self, parent: WidgetId) -> Self {
512 let child = |widget: Widget, slot: u32| {
513 let id = WidgetId::derived(
514 parent.as_u128(),
515 &[Self::CHILD_ROLE, slot, widget.kind_discriminator()],
516 );
517 widget.resolve_identity(id)
518 };
519 let children = |widgets: Vec<Widget>, first_slot: u32| {
520 widgets
521 .into_iter()
522 .enumerate()
523 .map(|(index, widget)| child(widget, first_slot + index as u32))
524 .collect()
525 };
526
527 let kind = match *self.kind {
528 WidgetKind::Identified {
529 id,
530 child: identified_child,
531 } => WidgetKind::Identified {
532 id,
533 child: identified_child.resolve_descendants(id),
538 },
539 WidgetKind::ActionScope(mut widget) => {
540 widget.child = child(widget.child, 0);
541 WidgetKind::ActionScope(widget)
542 }
543 WidgetKind::Row(mut widget) => {
544 widget.children = children(widget.children, 0);
545 WidgetKind::Row(widget)
546 }
547 WidgetKind::Column(mut widget) => {
548 widget.children = children(widget.children, 0);
549 WidgetKind::Column(widget)
550 }
551 WidgetKind::Align(mut widget) => {
552 widget.child = child(widget.child, 0);
553 WidgetKind::Align(widget)
554 }
555 WidgetKind::FocusScope(mut widget) => {
556 widget.children = children(widget.children, 0);
557 WidgetKind::FocusScope(widget)
558 }
559 WidgetKind::SelectionRegion(mut widget) => {
560 widget.child = child(widget.child, 0);
561 WidgetKind::SelectionRegion(widget)
562 }
563 WidgetKind::Clip(mut widget) => {
564 widget.child = child(widget.child, 0);
565 WidgetKind::Clip(widget)
566 }
567 WidgetKind::Text(widget) => WidgetKind::Text(widget),
568 WidgetKind::RichText(mut widget) => {
569 for (index, inline) in widget.inline_widgets.iter_mut().enumerate() {
570 inline.widget = child(inline.widget.clone(), index as u32);
571 }
572 WidgetKind::RichText(widget)
573 }
574 WidgetKind::Transform(mut widget) => {
575 widget.child = child(widget.child, 0);
576 WidgetKind::Transform(widget)
577 }
578 #[cfg(feature = "interactive-canvas")]
579 WidgetKind::InteractiveViewer(mut widget) => {
580 widget.child = child(widget.child, 0);
581 WidgetKind::InteractiveViewer(widget)
582 }
583 WidgetKind::Button(mut widget) => {
584 widget.child = widget.child.map(|value| child(value, 0));
585 WidgetKind::Button(widget)
586 }
587 WidgetKind::Pressable(mut widget) => {
588 widget.child = child(widget.child, 0);
589 WidgetKind::Pressable(widget)
590 }
591 WidgetKind::TextInput(mut widget) => {
592 widget.prefix = widget.prefix.map(|value| child(value, 0));
593 widget.suffix = widget.suffix.map(|value| child(value, 1));
594 WidgetKind::TextInput(widget)
595 }
596 WidgetKind::Scroll(mut widget) => {
597 widget.child = widget.child.map(|value| child(value, 0));
598 WidgetKind::Scroll(widget)
599 }
600 WidgetKind::SemanticsRegion(mut widget) => {
601 widget.child = widget.child.map(|value| child(value, 0));
602 WidgetKind::SemanticsRegion(widget)
603 }
604 WidgetKind::Image(widget) => WidgetKind::Image(widget),
605 WidgetKind::Video(widget) => WidgetKind::Video(widget),
606 WidgetKind::ZStack(mut widget) => {
607 widget.children = children(widget.children, 0);
608 WidgetKind::ZStack(widget)
609 }
610 WidgetKind::Overlay(mut widget) => {
611 widget.content = child(widget.content, 0);
612 widget.overlay = child(widget.overlay, 1);
613 WidgetKind::Overlay(widget)
614 }
615 WidgetKind::Container(mut widget) => {
616 widget.child = widget.child.map(|value| child(value, 0));
617 WidgetKind::Container(widget)
618 }
619 WidgetKind::ContextMenuRegion(mut widget) => {
620 widget.child = child(widget.child, 0);
621 for (index, entry) in widget.menu.items.iter_mut().enumerate() {
622 if let ContextMenuEntry::Item(item) = entry {
623 item.child = child(item.child.clone(), 1 + index as u32);
624 }
625 }
626 WidgetKind::ContextMenuRegion(widget)
627 }
628 WidgetKind::GestureDetector(mut widget) => {
629 widget.child = child(widget.child, 0);
630 WidgetKind::GestureDetector(widget)
631 }
632 WidgetKind::Grid(mut widget) => {
633 widget.children = children(widget.children, 0);
634 WidgetKind::Grid(widget)
635 }
636 WidgetKind::GridItem(mut widget) => {
637 widget.child = child(widget.child, 0);
638 WidgetKind::GridItem(widget)
639 }
640 WidgetKind::Responsive(mut widget) => {
641 for (index, case) in widget.cases.iter_mut().enumerate() {
642 case.child = child(case.child.clone(), index as u32);
643 }
644 widget.fallback = child(widget.fallback, u32::MAX);
645 WidgetKind::Responsive(widget)
646 }
647 WidgetKind::Checkbox(widget) => WidgetKind::Checkbox(widget),
648 WidgetKind::Switch(widget) => WidgetKind::Switch(widget),
649 WidgetKind::Radio(widget) => WidgetKind::Radio(widget),
650 WidgetKind::SafeArea(mut widget) => {
651 widget.child = child(widget.child, 0);
652 WidgetKind::SafeArea(widget)
653 }
654 WidgetKind::Positioned(mut widget) => {
655 widget.child = widget.child.map(|value| child(value, 0));
656 WidgetKind::Positioned(widget)
657 }
658 WidgetKind::Spacer(widget) => WidgetKind::Spacer(widget),
659 WidgetKind::Slider(widget) => WidgetKind::Slider(widget),
660 WidgetKind::LazyColumn(mut widget) => {
661 widget.children = children(widget.children, 0);
662 WidgetKind::LazyColumn(widget)
663 }
664 WidgetKind::Icon(widget) => WidgetKind::Icon(widget),
665 WidgetKind::Composite(mut widget) => {
666 widget.child = child(widget.child, 0);
667 WidgetKind::Composite(widget)
668 }
669 WidgetKind::Custom(widget) => WidgetKind::Custom(widget),
670 };
671
672 Self {
673 kind: Box::new(kind),
674 }
675 }
676
677 pub(crate) fn as_row(&self) -> Option<&Row> {
678 match &*self.kind {
679 WidgetKind::Identified { child, .. } => child.as_row(),
680 WidgetKind::Row(widget) => Some(widget),
681 _ => None,
682 }
683 }
684
685 pub(crate) fn as_column(&self) -> Option<&Column> {
686 match &*self.kind {
687 WidgetKind::Identified { child, .. } => child.as_column(),
688 WidgetKind::Column(widget) => Some(widget),
689 _ => None,
690 }
691 }
692
693 pub(crate) fn as_container(&self) -> Option<&Container> {
694 match &*self.kind {
695 WidgetKind::Identified { child, .. } => child.as_container(),
696 WidgetKind::Container(widget) => Some(widget),
697 _ => None,
698 }
699 }
700
701 pub(crate) fn as_scroll(&self) -> Option<&Scroll> {
702 match &*self.kind {
703 WidgetKind::Identified { child, .. } => child.as_scroll(),
704 WidgetKind::Scroll(widget) => Some(widget),
705 _ => None,
706 }
707 }
708
709 pub(crate) fn as_rich_text(&self) -> Option<&RichText> {
710 match &*self.kind {
711 WidgetKind::Identified { child, .. } => child.as_rich_text(),
712 WidgetKind::RichText(widget) => Some(widget),
713 _ => None,
714 }
715 }
716
717 pub(crate) fn as_text(&self) -> Option<&Text> {
718 match &*self.kind {
719 WidgetKind::Identified { child, .. } => child.as_text(),
720 WidgetKind::Text(widget) => Some(widget),
721 _ => None,
722 }
723 }
724
725 pub(crate) fn as_text_input(&self) -> Option<&TextInput> {
726 match &*self.kind {
727 WidgetKind::Identified { child, .. } => child.as_text_input(),
728 WidgetKind::TextInput(widget) => Some(widget),
729 _ => None,
730 }
731 }
732
733 pub(crate) fn as_button(&self) -> Option<&Button> {
734 match &*self.kind {
735 WidgetKind::Identified { child, .. } => child.as_button(),
736 WidgetKind::Button(widget) => Some(widget),
737 _ => None,
738 }
739 }
740
741 pub(crate) fn as_gesture_detector(&self) -> Option<&GestureDetector> {
742 match &*self.kind {
743 WidgetKind::Identified { child, .. } => child.as_gesture_detector(),
744 WidgetKind::GestureDetector(widget) => Some(widget),
745 _ => None,
746 }
747 }
748
749 pub(crate) fn as_zstack(&self) -> Option<&ZStack> {
750 match &*self.kind {
751 WidgetKind::Identified { child, .. } => child.as_zstack(),
752 WidgetKind::ZStack(widget) => Some(widget),
753 _ => None,
754 }
755 }
756
757 #[cfg(feature = "interactive-canvas")]
758 pub(crate) fn as_interactive_viewer(&self) -> Option<&InteractiveViewer> {
759 match &*self.kind {
760 WidgetKind::Identified { child, .. } => child.as_interactive_viewer(),
761 WidgetKind::InteractiveViewer(widget) => Some(widget),
762 _ => None,
763 }
764 }
765}
766
767pub trait WidgetIdExt: Into<Widget> + Sized {
774 fn id<I>(self, id: I) -> Widget
775 where
776 I: Into<WidgetId>,
777 {
778 let id = id.into();
779 crate::build::with_widget_id(id, || {
780 let widget: Widget = self.into();
781 widget.with_id(id)
782 })
783 }
784}
785
786impl<T> WidgetIdExt for T where T: Into<Widget> {}
787
788impl Widget {
789 pub(crate) fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
790 match &*self.kind {
791 WidgetKind::Identified { id, child } => {
792 cx.push_scope(*id);
793 let child_id = child.lower(cx);
794 cx.pop_scope();
795 let mut builder = crate::lowering::InternalIrBuilder::new(
796 (*id).into(),
797 Op::Structural(StructuralOp::Group {
798 stable_hash: id.as_u128() as u64,
799 }),
800 );
801 builder.add_child(child_id);
802 builder.build(cx)
803 }
804 WidgetKind::ActionScope(w) => w.lower(cx),
805 WidgetKind::Row(w) => w.lower(cx),
806 WidgetKind::Column(w) => w.lower(cx),
807 WidgetKind::Align(w) => w.lower(cx),
808 WidgetKind::FocusScope(w) => w.lower(cx),
809 WidgetKind::SelectionRegion(w) => w.lower(cx),
810 WidgetKind::Clip(w) => w.lower(cx),
811 WidgetKind::Text(w) => w.lower(cx),
812 WidgetKind::RichText(w) => w.lower(cx),
813 WidgetKind::Transform(w) => w.lower(cx),
814 #[cfg(feature = "interactive-canvas")]
815 WidgetKind::InteractiveViewer(w) => w.lower(cx),
816 WidgetKind::Button(w) => w.lower(cx),
817 WidgetKind::Pressable(w) => w.lower(cx),
818 WidgetKind::TextInput(w) => w.lower(cx),
819 WidgetKind::Scroll(w) => w.lower(cx),
820 WidgetKind::SemanticsRegion(w) => w.lower(cx),
821 WidgetKind::Image(w) => w.lower(cx),
822 WidgetKind::Video(w) => w.lower(cx),
823 WidgetKind::ZStack(w) => w.lower(cx),
824 WidgetKind::Overlay(w) => w.lower(cx),
825 WidgetKind::Container(w) => w.lower(cx),
826 WidgetKind::ContextMenuRegion(w) => w.lower(cx),
827 WidgetKind::GestureDetector(w) => w.lower(cx),
828 WidgetKind::Grid(w) => w.lower(cx),
829 WidgetKind::GridItem(w) => w.lower(cx),
830 WidgetKind::Responsive(w) => w.lower(cx),
831 WidgetKind::Checkbox(w) => w.lower(cx),
832 WidgetKind::Switch(w) => w.lower(cx),
833 WidgetKind::Radio(w) => w.lower(cx),
834 WidgetKind::SafeArea(w) => w.lower(cx),
835 WidgetKind::Positioned(w) => w.lower(cx),
836 WidgetKind::Spacer(w) => w.lower(cx),
837 WidgetKind::Slider(w) => w.lower(cx),
838 WidgetKind::LazyColumn(w) => w.lower(cx),
839 WidgetKind::Icon(w) => w.lower(cx),
840 WidgetKind::Composite(w) => w.lower(cx),
841 WidgetKind::Custom(w) => {
842 let lowerer = w
843 .lowerer
844 .as_ref()
845 .expect("CustomWidget lowerer must be set");
846 let wrapper = lowerer.widget_id().unwrap_or_else(|| cx.next_node_id());
847 cx.push_scope(wrapper);
848 let child_id = lowerer.lower_dyn(cx);
849 cx.pop_scope();
850 let mut builder = crate::lowering::InternalIrBuilder::new(
851 wrapper,
852 Op::Structural(StructuralOp::Group {
853 stable_hash: lowerer.stable_key(),
854 }),
855 );
856 builder.add_child(child_id);
857 let node_id = builder.build(cx);
858
859 if let Some(render_obj) = &w.render_object {
865 let holder = crate::ui::custom_render::RenderObjectHolder(render_obj.clone());
866 let erased: fission_ir::AnyRenderObject = Arc::new(holder);
867 cx.ir.custom_render_objects.insert(node_id, erased);
868 }
869
870 node_id
871 }
872 }
873 }
874}
875
876impl From<Row> for Widget {
877 fn from(w: Row) -> Self {
878 Self {
879 kind: Box::new(WidgetKind::Row(w)),
880 }
881 }
882}
883impl From<ActionScope> for Widget {
884 fn from(w: ActionScope) -> Self {
885 Self {
886 kind: Box::new(WidgetKind::ActionScope(w)),
887 }
888 }
889}
890impl From<Column> for Widget {
891 fn from(w: Column) -> Self {
892 Self {
893 kind: Box::new(WidgetKind::Column(w)),
894 }
895 }
896}
897impl From<Align> for Widget {
898 fn from(w: Align) -> Self {
899 Self {
900 kind: Box::new(WidgetKind::Align(w)),
901 }
902 }
903}
904impl From<FocusScope> for Widget {
905 fn from(w: FocusScope) -> Self {
906 Self {
907 kind: Box::new(WidgetKind::FocusScope(w)),
908 }
909 }
910}
911impl From<SelectionRegion> for Widget {
912 fn from(w: SelectionRegion) -> Self {
913 Self {
914 kind: Box::new(WidgetKind::SelectionRegion(w)),
915 }
916 }
917}
918impl From<Clip> for Widget {
919 fn from(w: Clip) -> Self {
920 Self {
921 kind: Box::new(WidgetKind::Clip(w)),
922 }
923 }
924}
925impl From<Text> for Widget {
926 fn from(w: Text) -> Self {
927 Self {
928 kind: Box::new(WidgetKind::Text(w)),
929 }
930 }
931}
932impl From<RichText> for Widget {
933 fn from(w: RichText) -> Self {
934 Self {
935 kind: Box::new(WidgetKind::RichText(w)),
936 }
937 }
938}
939impl From<Transform> for Widget {
940 fn from(w: Transform) -> Self {
941 Self {
942 kind: Box::new(WidgetKind::Transform(w)),
943 }
944 }
945}
946#[cfg(feature = "interactive-canvas")]
947impl From<InteractiveViewer> for Widget {
948 fn from(w: InteractiveViewer) -> Self {
949 Self {
950 kind: Box::new(WidgetKind::InteractiveViewer(w)),
951 }
952 }
953}
954impl From<Button> for Widget {
955 fn from(mut w: Button) -> Self {
956 if let Some(motion) = w.motion.take() {
957 let button_id = crate::build::current_widget_id()
958 .or(w.id)
959 .unwrap_or_else(|| WidgetId::explicit("fission.core.button.motion"));
960 w.id = Some(button_id);
961 let motion_id = WidgetId::derived(button_id.as_u128(), &[0xB0770]);
962 let tracks = motion.interaction_tracks(button_id);
963 let ripple = motion.ripple();
964 let base = Self {
965 kind: Box::new(WidgetKind::Button(w)),
966 };
967 let with_motion: Widget = if tracks.is_empty() {
968 base
969 } else {
970 crate::motion::Motion {
971 id: motion_id,
972 tracks,
973 child: base,
974 ..Default::default()
975 }
976 .into()
977 };
978 return if let Some(effect) = ripple {
979 crate::motion::RippleLayer {
980 id: WidgetId::derived(button_id.as_u128(), &[0xA11E]),
981 effect,
982 child: with_motion,
983 }
984 .into()
985 } else {
986 with_motion
987 };
988 }
989 Self {
990 kind: Box::new(WidgetKind::Button(w)),
991 }
992 }
993}
994impl From<TextInput> for Widget {
995 fn from(w: TextInput) -> Self {
996 Self {
997 kind: Box::new(WidgetKind::TextInput(w)),
998 }
999 }
1000}
1001impl From<Scroll> for Widget {
1002 fn from(w: Scroll) -> Self {
1003 Self {
1004 kind: Box::new(WidgetKind::Scroll(w)),
1005 }
1006 }
1007}
1008impl From<SemanticsRegion> for Widget {
1009 fn from(w: SemanticsRegion) -> Self {
1010 Self {
1011 kind: Box::new(WidgetKind::SemanticsRegion(w)),
1012 }
1013 }
1014}
1015impl From<Image> for Widget {
1016 fn from(w: Image) -> Self {
1017 Self {
1018 kind: Box::new(WidgetKind::Image(w)),
1019 }
1020 }
1021}
1022impl From<Video> for Widget {
1023 fn from(w: Video) -> Self {
1024 let node_id = crate::build::current_widget_id()
1025 .or(w.id)
1026 .unwrap_or_else(|| fission_ir::WidgetId::explicit(&w.source.key()));
1027 crate::build::try_register_video(crate::registry::VideoRegistration {
1028 node_id,
1029 source: w.source.as_str().to_string(),
1030 autoplay: w.autoplay,
1031 loop_playback: w.loop_playback,
1032 audio: w.audio.clone(),
1033 });
1034 Self {
1035 kind: Box::new(WidgetKind::Video(w)),
1036 }
1037 }
1038}
1039impl From<ZStack> for Widget {
1040 fn from(w: ZStack) -> Self {
1041 Self {
1042 kind: Box::new(WidgetKind::ZStack(w)),
1043 }
1044 }
1045}
1046impl From<Overlay> for Widget {
1047 fn from(w: Overlay) -> Self {
1048 Self {
1049 kind: Box::new(WidgetKind::Overlay(w)),
1050 }
1051 }
1052}
1053impl From<ContextMenuRegion> for Widget {
1054 fn from(w: ContextMenuRegion) -> Self {
1055 Self {
1056 kind: Box::new(WidgetKind::ContextMenuRegion(w)),
1057 }
1058 }
1059}
1060
1061impl From<Container> for Widget {
1062 fn from(w: Container) -> Self {
1063 Self {
1064 kind: Box::new(WidgetKind::Container(w)),
1065 }
1066 }
1067}
1068impl From<GestureDetector> for Widget {
1069 fn from(w: GestureDetector) -> Self {
1070 Self {
1071 kind: Box::new(WidgetKind::GestureDetector(w)),
1072 }
1073 }
1074}
1075impl From<Grid> for Widget {
1076 fn from(w: Grid) -> Self {
1077 Self {
1078 kind: Box::new(WidgetKind::Grid(w)),
1079 }
1080 }
1081}
1082impl From<GridItem> for Widget {
1083 fn from(w: GridItem) -> Self {
1084 Self {
1085 kind: Box::new(WidgetKind::GridItem(w)),
1086 }
1087 }
1088}
1089impl From<Responsive> for Widget {
1090 fn from(w: Responsive) -> Self {
1091 Self {
1092 kind: Box::new(WidgetKind::Responsive(w)),
1093 }
1094 }
1095}
1096impl From<Checkbox> for Widget {
1097 fn from(w: Checkbox) -> Self {
1098 Self {
1099 kind: Box::new(WidgetKind::Checkbox(w)),
1100 }
1101 }
1102}
1103impl From<Switch> for Widget {
1104 fn from(w: Switch) -> Self {
1105 Self {
1106 kind: Box::new(WidgetKind::Switch(w)),
1107 }
1108 }
1109}
1110impl From<Radio> for Widget {
1111 fn from(w: Radio) -> Self {
1112 Self {
1113 kind: Box::new(WidgetKind::Radio(w)),
1114 }
1115 }
1116}
1117impl From<SafeArea> for Widget {
1118 fn from(w: SafeArea) -> Self {
1119 Self {
1120 kind: Box::new(WidgetKind::SafeArea(w)),
1121 }
1122 }
1123}
1124impl From<Composite> for Widget {
1125 fn from(w: Composite) -> Self {
1126 Self {
1127 kind: Box::new(WidgetKind::Composite(w)),
1128 }
1129 }
1130}
1131impl From<Positioned> for Widget {
1132 fn from(w: Positioned) -> Self {
1133 Self {
1134 kind: Box::new(WidgetKind::Positioned(w)),
1135 }
1136 }
1137}
1138impl From<Spacer> for Widget {
1139 fn from(w: Spacer) -> Self {
1140 Self {
1141 kind: Box::new(WidgetKind::Spacer(w)),
1142 }
1143 }
1144}
1145impl From<Slider> for Widget {
1146 fn from(w: Slider) -> Self {
1147 Self {
1148 kind: Box::new(WidgetKind::Slider(w)),
1149 }
1150 }
1151}
1152impl From<LazyColumn> for Widget {
1153 fn from(w: LazyColumn) -> Self {
1154 Self {
1155 kind: Box::new(WidgetKind::LazyColumn(w)),
1156 }
1157 }
1158}
1159impl From<Icon> for Widget {
1160 fn from(w: Icon) -> Self {
1161 Self {
1162 kind: Box::new(WidgetKind::Icon(w)),
1163 }
1164 }
1165}
1166
1167#[derive(Clone, Debug, Serialize, Deserialize)]
1168pub struct InternalRenderNode {
1169 pub debug_tag: String,
1170 #[serde(skip)]
1171 pub lowerer: Option<Arc<dyn InternalLowerer>>,
1172 #[serde(skip)]
1176 pub render_object: Option<Arc<dyn CustomRenderObject>>,
1177}
1178
1179pub type CustomWidget = InternalRenderNode;
1180
1181impl From<CustomWidget> for Widget {
1182 fn from(node: CustomWidget) -> Self {
1183 Widget::custom(node)
1184 }
1185}
1186
1187#[cfg(test)]
1188mod visitor_tests {
1189 use super::*;
1190
1191 #[test]
1192 fn visitor_walks_nested_widgets_and_can_stop() {
1193 let root: Widget = Column {
1194 children: vec![Text::new("first").into(), Text::new("second").into()],
1195 ..Default::default()
1196 }
1197 .into();
1198 let mut visited = 0;
1199 let result = root.visit(&mut |widget| {
1200 visited += 1;
1201 if matches!(widget.kind(), WidgetKind::Text(_)) {
1202 ControlFlow::Break(())
1203 } else {
1204 ControlFlow::Continue(())
1205 }
1206 });
1207 assert!(matches!(result, ControlFlow::Break(())));
1208 assert_eq!(visited, 2);
1209 }
1210}