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(mut w: Button) -> Self {
513 if let Some(motion) = w.motion.take() {
514 let button_id = crate::build::current_widget_id()
515 .or(w.id)
516 .unwrap_or_else(|| WidgetId::explicit("fission.core.button.motion"));
517 w.id = Some(button_id);
518 let motion_id = WidgetId::derived(button_id.as_u128(), &[0xB0770]);
519 let tracks = motion.interaction_tracks(button_id);
520 let ripple = motion.ripple();
521 let base = Self {
522 kind: Box::new(WidgetKind::Button(w)),
523 };
524 let with_motion: Widget = if tracks.is_empty() {
525 base
526 } else {
527 crate::motion::Motion {
528 id: motion_id,
529 tracks,
530 child: base,
531 ..Default::default()
532 }
533 .into()
534 };
535 return if let Some(effect) = ripple {
536 crate::motion::RippleLayer {
537 id: WidgetId::derived(button_id.as_u128(), &[0xA11E]),
538 effect,
539 child: with_motion,
540 }
541 .into()
542 } else {
543 with_motion
544 };
545 }
546 Self {
547 kind: Box::new(WidgetKind::Button(w)),
548 }
549 }
550}
551impl From<TextInput> for Widget {
552 fn from(w: TextInput) -> Self {
553 Self {
554 kind: Box::new(WidgetKind::TextInput(w)),
555 }
556 }
557}
558impl From<Scroll> for Widget {
559 fn from(w: Scroll) -> Self {
560 Self {
561 kind: Box::new(WidgetKind::Scroll(w)),
562 }
563 }
564}
565impl From<SemanticsRegion> for Widget {
566 fn from(w: SemanticsRegion) -> Self {
567 Self {
568 kind: Box::new(WidgetKind::SemanticsRegion(w)),
569 }
570 }
571}
572impl From<Image> for Widget {
573 fn from(w: Image) -> Self {
574 Self {
575 kind: Box::new(WidgetKind::Image(w)),
576 }
577 }
578}
579impl From<Video> for Widget {
580 fn from(w: Video) -> Self {
581 let node_id = crate::build::current_widget_id()
582 .or(w.id)
583 .unwrap_or_else(|| fission_ir::WidgetId::explicit(&w.source));
584 crate::build::try_register_video(crate::registry::VideoRegistration {
585 node_id,
586 source: w.source.clone(),
587 autoplay: w.autoplay,
588 loop_playback: w.loop_playback,
589 });
590 Self {
591 kind: Box::new(WidgetKind::Video(w)),
592 }
593 }
594}
595impl From<ZStack> for Widget {
596 fn from(w: ZStack) -> Self {
597 Self {
598 kind: Box::new(WidgetKind::ZStack(w)),
599 }
600 }
601}
602impl From<Overlay> for Widget {
603 fn from(w: Overlay) -> Self {
604 Self {
605 kind: Box::new(WidgetKind::Overlay(w)),
606 }
607 }
608}
609impl From<Container> for Widget {
610 fn from(w: Container) -> Self {
611 Self {
612 kind: Box::new(WidgetKind::Container(w)),
613 }
614 }
615}
616impl From<GestureDetector> for Widget {
617 fn from(w: GestureDetector) -> Self {
618 Self {
619 kind: Box::new(WidgetKind::GestureDetector(w)),
620 }
621 }
622}
623impl From<Grid> for Widget {
624 fn from(w: Grid) -> Self {
625 Self {
626 kind: Box::new(WidgetKind::Grid(w)),
627 }
628 }
629}
630impl From<GridItem> for Widget {
631 fn from(w: GridItem) -> Self {
632 Self {
633 kind: Box::new(WidgetKind::GridItem(w)),
634 }
635 }
636}
637impl From<Checkbox> for Widget {
638 fn from(w: Checkbox) -> Self {
639 Self {
640 kind: Box::new(WidgetKind::Checkbox(w)),
641 }
642 }
643}
644impl From<Switch> for Widget {
645 fn from(w: Switch) -> Self {
646 Self {
647 kind: Box::new(WidgetKind::Switch(w)),
648 }
649 }
650}
651impl From<Radio> for Widget {
652 fn from(w: Radio) -> Self {
653 Self {
654 kind: Box::new(WidgetKind::Radio(w)),
655 }
656 }
657}
658impl From<SafeArea> for Widget {
659 fn from(w: SafeArea) -> Self {
660 Self {
661 kind: Box::new(WidgetKind::SafeArea(w)),
662 }
663 }
664}
665impl From<Composite> for Widget {
666 fn from(w: Composite) -> Self {
667 Self {
668 kind: Box::new(WidgetKind::Composite(w)),
669 }
670 }
671}
672impl From<Positioned> for Widget {
673 fn from(w: Positioned) -> Self {
674 Self {
675 kind: Box::new(WidgetKind::Positioned(w)),
676 }
677 }
678}
679impl From<Spacer> for Widget {
680 fn from(w: Spacer) -> Self {
681 Self {
682 kind: Box::new(WidgetKind::Spacer(w)),
683 }
684 }
685}
686impl From<Slider> for Widget {
687 fn from(w: Slider) -> Self {
688 Self {
689 kind: Box::new(WidgetKind::Slider(w)),
690 }
691 }
692}
693impl From<LazyColumn> for Widget {
694 fn from(w: LazyColumn) -> Self {
695 Self {
696 kind: Box::new(WidgetKind::LazyColumn(w)),
697 }
698 }
699}
700impl From<Icon> for Widget {
701 fn from(w: Icon) -> Self {
702 Self {
703 kind: Box::new(WidgetKind::Icon(w)),
704 }
705 }
706}
707
708#[derive(Clone, Debug, Serialize, Deserialize)]
709pub struct InternalRenderNode {
710 pub debug_tag: String,
711 #[serde(skip)]
712 pub lowerer: Option<Arc<dyn InternalLowerer>>,
713 #[serde(skip)]
717 pub render_object: Option<Arc<dyn CustomRenderObject>>,
718}
719
720pub type CustomWidget = InternalRenderNode;
721
722impl From<CustomWidget> for Widget {
723 fn from(node: CustomWidget) -> Self {
724 Widget::custom(node)
725 }
726}