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