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