1use crate::internal::InternalLower;
2use crate::lowering::{InternalIrBuilder, InternalLoweringCx};
3use crate::motion::{
4 color, fill as motion_fill, px, scalar, shadows as motion_shadows, Motion, MotionExpr,
5 MotionPredicate, MotionPropertyId, MotionStartValue, MotionTrack, MotionTransition,
6 MotionValue, RippleFx, RippleLayer,
7};
8use crate::ui::Widget;
9use crate::ActionEnvelope;
10use fission_ir::{
11 op::{BoxShadow, BoxStyle, Color, Fill, LayoutOp, Length, Op, PaintOp, Stroke},
12 semantics::ActionTrigger,
13 ActionEntry, CompositeScalar, CompositeStyle, FocusPolicy, Hyperlink, PopoverAction,
14 PopoverTarget, Role, Semantics, WidgetId,
15};
16use serde::{Deserialize, Serialize};
17
18use super::split_box_margin;
19
20#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
22pub enum PressableRole {
23 #[default]
25 Button,
26 Link,
28 MenuItem,
30}
31
32impl PressableRole {
33 fn semantics_role(self) -> Role {
34 match self {
35 Self::Button => Role::Button,
36 Self::Link => Role::Link,
37 Self::MenuItem => Role::MenuItem,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
47pub struct PressableStyle {
48 pub background: Option<Fill>,
50 pub border: Option<Stroke>,
52 pub corner_radius: Option<f32>,
54 pub shadows: Option<Vec<BoxShadow>>,
56 pub padding: Option<[Length; 4]>,
58 pub opacity: Option<f32>,
60 pub scale: Option<f32>,
62}
63
64impl PressableStyle {
65 fn merged(&self, overlay: Option<&Self>) -> Self {
66 let Some(overlay) = overlay else {
67 return self.clone();
68 };
69 Self {
70 background: overlay
71 .background
72 .clone()
73 .or_else(|| self.background.clone()),
74 border: overlay.border.clone().or_else(|| self.border.clone()),
75 corner_radius: overlay.corner_radius.or(self.corner_radius),
76 shadows: overlay.shadows.clone().or_else(|| self.shadows.clone()),
77 padding: overlay.padding.clone().or_else(|| self.padding.clone()),
78 opacity: overlay.opacity.or(self.opacity),
79 scale: overlay.scale.or(self.scale),
80 }
81 }
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Pressable {
108 pub id: Option<WidgetId>,
110 pub child: Widget,
112 pub on_press: Option<ActionEnvelope>,
114 pub label: Option<String>,
116 pub semantics_identifier: Option<String>,
118 pub hyperlink: Option<Hyperlink>,
120 pub popover_target: Option<PopoverTarget>,
122 pub role: PressableRole,
124 pub focus_policy: FocusPolicy,
126 pub disabled: bool,
128 pub layout: BoxStyle,
130 pub flex_grow: f32,
132 pub flex_shrink: f32,
134 pub style: PressableStyle,
136 pub hover_style: Option<PressableStyle>,
138 pub pressed_style: Option<PressableStyle>,
140 pub focused_style: Option<PressableStyle>,
142 pub disabled_style: Option<PressableStyle>,
144 pub transition: Option<MotionTransition>,
146 pub ripple: Option<RippleFx>,
148}
149
150impl Pressable {
151 const MOTION_SALT: u32 = 0x5052_4553;
152
153 pub fn new(child: impl Into<Widget>) -> Self {
155 Self {
156 child: child.into(),
157 ..Default::default()
158 }
159 }
160
161 pub fn id(mut self, id: WidgetId) -> Self {
163 self.id = Some(id);
164 self
165 }
166
167 pub fn on_press(mut self, action: ActionEnvelope) -> Self {
169 self.on_press = Some(action);
170 self
171 }
172
173 pub fn disabled(mut self, disabled: bool) -> Self {
175 self.disabled = disabled;
176 self
177 }
178
179 pub fn layout(mut self, style: BoxStyle) -> Self {
181 self.layout = style;
182 self
183 }
184
185 pub fn focus_policy(mut self, policy: FocusPolicy) -> Self {
187 self.focus_policy = policy;
188 self
189 }
190
191 pub fn style(mut self, style: PressableStyle) -> Self {
193 self.style = style;
194 self
195 }
196
197 pub fn hover(mut self, style: PressableStyle) -> Self {
199 self.hover_style = Some(style);
200 self
201 }
202
203 pub fn pressed(mut self, style: PressableStyle) -> Self {
205 self.pressed_style = Some(style);
206 self
207 }
208
209 pub fn focused(mut self, style: PressableStyle) -> Self {
211 self.focused_style = Some(style);
212 self
213 }
214
215 pub fn disabled_style(mut self, style: PressableStyle) -> Self {
217 self.disabled_style = Some(style);
218 self
219 }
220
221 pub fn transition(mut self, transition: MotionTransition) -> Self {
223 self.transition = Some(transition);
224 self
225 }
226
227 pub fn ripple(mut self, ripple: RippleFx) -> Self {
229 self.ripple = Some(ripple);
230 self
231 }
232
233 pub fn semantics_identifier(mut self, identifier: impl Into<String>) -> Self {
235 self.semantics_identifier = Some(identifier.into());
236 self
237 }
238
239 pub fn label(mut self, label: impl Into<String>) -> Self {
241 self.label = Some(label.into());
242 self
243 }
244
245 pub fn role(mut self, role: PressableRole) -> Self {
247 self.role = role;
248 self
249 }
250
251 pub fn href(mut self, href: impl Into<String>) -> Self {
253 self.role = PressableRole::Link;
254 self.hyperlink = Some(Hyperlink::new(href));
255 self
256 }
257
258 pub fn hyperlink(mut self, hyperlink: Hyperlink) -> Self {
259 self.role = PressableRole::Link;
260 self.hyperlink = Some(hyperlink);
261 self
262 }
263
264 pub fn popover_target(mut self, id: impl Into<String>, action: PopoverAction) -> Self {
265 self.popover_target = Some(PopoverTarget {
266 id: id.into(),
267 action,
268 });
269 self
270 }
271
272 fn resolved_style(&self, cx: &InternalLoweringCx<'_>, id: WidgetId) -> PressableStyle {
273 if self.disabled {
274 return self.style.merged(self.disabled_style.as_ref());
275 }
276 if cx.runtime_state.interaction.is_pressed(id) {
277 self.style.merged(self.pressed_style.as_ref())
278 } else if cx.runtime_state.interaction.is_focused(id) {
279 self.style.merged(self.focused_style.as_ref())
280 } else if cx.runtime_state.interaction.is_hovered(id) {
281 self.style.merged(self.hover_style.as_ref())
282 } else {
283 self.style.clone()
284 }
285 }
286
287 fn state_scalar_expr(&self, property: fn(&PressableStyle) -> Option<f32>) -> MotionExpr {
288 let base = property(&self.style).unwrap_or(1.0);
289 if self.disabled {
290 return scalar(
291 property(&self.style.merged(self.disabled_style.as_ref())).unwrap_or(base),
292 );
293 }
294 let hover = property(&self.style.merged(self.hover_style.as_ref())).unwrap_or(base);
295 let focused = property(&self.style.merged(self.focused_style.as_ref())).unwrap_or(base);
296 let pressed = property(&self.style.merged(self.pressed_style.as_ref())).unwrap_or(base);
297 let id = self.id.expect("pressable motion requires a stable id");
298 MotionExpr::If {
299 predicate: MotionPredicate::Pressed(id),
300 then_expr: Box::new(scalar(pressed)),
301 else_expr: Box::new(MotionExpr::If {
302 predicate: MotionPredicate::Focused(id),
303 then_expr: Box::new(scalar(focused)),
304 else_expr: Box::new(MotionExpr::If {
305 predicate: MotionPredicate::Hovered(id),
306 then_expr: Box::new(scalar(hover)),
307 else_expr: Box::new(scalar(base)),
308 }),
309 }),
310 }
311 }
312
313 fn state_px_expr(
314 &self,
315 property: impl Fn(&PressableStyle) -> Option<f32> + Copy,
316 ) -> MotionExpr {
317 self.state_value_expr(property, 0.0, px)
318 }
319
320 fn state_color_expr(&self, property: fn(&PressableStyle) -> Option<Color>) -> MotionExpr {
321 let transparent = Color::TRANSPARENT;
322 let base = property(&self.style).unwrap_or(transparent);
323 if self.disabled {
324 return color(
325 property(&self.style.merged(self.disabled_style.as_ref())).unwrap_or(base),
326 );
327 }
328 let hover = property(&self.style.merged(self.hover_style.as_ref())).unwrap_or(base);
329 let focused = property(&self.style.merged(self.focused_style.as_ref())).unwrap_or(base);
330 let pressed = property(&self.style.merged(self.pressed_style.as_ref())).unwrap_or(base);
331 let id = self.id.expect("pressable motion requires a stable id");
332 MotionExpr::If {
333 predicate: MotionPredicate::Pressed(id),
334 then_expr: Box::new(color(pressed)),
335 else_expr: Box::new(MotionExpr::If {
336 predicate: MotionPredicate::Focused(id),
337 then_expr: Box::new(color(focused)),
338 else_expr: Box::new(MotionExpr::If {
339 predicate: MotionPredicate::Hovered(id),
340 then_expr: Box::new(color(hover)),
341 else_expr: Box::new(color(base)),
342 }),
343 }),
344 }
345 }
346
347 fn state_fill_expr(&self) -> MotionExpr {
348 let default = Fill::Solid(Color::TRANSPARENT);
349 let base = self.style.background.clone().unwrap_or(default);
350 if self.disabled {
351 return motion_fill(
352 self.style
353 .merged(self.disabled_style.as_ref())
354 .background
355 .unwrap_or(base),
356 );
357 }
358 let hover = self
359 .style
360 .merged(self.hover_style.as_ref())
361 .background
362 .unwrap_or_else(|| base.clone());
363 let focused = self
364 .style
365 .merged(self.focused_style.as_ref())
366 .background
367 .unwrap_or_else(|| base.clone());
368 let pressed = self
369 .style
370 .merged(self.pressed_style.as_ref())
371 .background
372 .unwrap_or_else(|| base.clone());
373 let id = self.id.expect("pressable motion requires a stable id");
374 MotionExpr::If {
375 predicate: MotionPredicate::Pressed(id),
376 then_expr: Box::new(motion_fill(pressed)),
377 else_expr: Box::new(MotionExpr::If {
378 predicate: MotionPredicate::Focused(id),
379 then_expr: Box::new(motion_fill(focused)),
380 else_expr: Box::new(MotionExpr::If {
381 predicate: MotionPredicate::Hovered(id),
382 then_expr: Box::new(motion_fill(hover)),
383 else_expr: Box::new(motion_fill(base)),
384 }),
385 }),
386 }
387 }
388
389 fn state_shadows_expr(&self) -> MotionExpr {
390 let base = self.style.shadows.clone().unwrap_or_default();
391 if self.disabled {
392 return motion_shadows(
393 self.style
394 .merged(self.disabled_style.as_ref())
395 .shadows
396 .unwrap_or(base),
397 );
398 }
399 let hover = self
400 .style
401 .merged(self.hover_style.as_ref())
402 .shadows
403 .unwrap_or_else(|| base.clone());
404 let focused = self
405 .style
406 .merged(self.focused_style.as_ref())
407 .shadows
408 .unwrap_or_else(|| base.clone());
409 let pressed = self
410 .style
411 .merged(self.pressed_style.as_ref())
412 .shadows
413 .unwrap_or_else(|| base.clone());
414 let id = self.id.expect("pressable motion requires a stable id");
415 MotionExpr::If {
416 predicate: MotionPredicate::Pressed(id),
417 then_expr: Box::new(motion_shadows(pressed)),
418 else_expr: Box::new(MotionExpr::If {
419 predicate: MotionPredicate::Focused(id),
420 then_expr: Box::new(motion_shadows(focused)),
421 else_expr: Box::new(MotionExpr::If {
422 predicate: MotionPredicate::Hovered(id),
423 then_expr: Box::new(motion_shadows(hover)),
424 else_expr: Box::new(motion_shadows(base)),
425 }),
426 }),
427 }
428 }
429
430 fn state_value_expr(
431 &self,
432 property: impl Fn(&PressableStyle) -> Option<f32> + Copy,
433 default: f32,
434 expression: fn(f32) -> MotionExpr,
435 ) -> MotionExpr {
436 let base = property(&self.style).unwrap_or(default);
437 if self.disabled {
438 return expression(
439 property(&self.style.merged(self.disabled_style.as_ref())).unwrap_or(base),
440 );
441 }
442 let hover = property(&self.style.merged(self.hover_style.as_ref())).unwrap_or(base);
443 let focused = property(&self.style.merged(self.focused_style.as_ref())).unwrap_or(base);
444 let pressed = property(&self.style.merged(self.pressed_style.as_ref())).unwrap_or(base);
445 let id = self.id.expect("pressable motion requires a stable id");
446 MotionExpr::If {
447 predicate: MotionPredicate::Pressed(id),
448 then_expr: Box::new(expression(pressed)),
449 else_expr: Box::new(MotionExpr::If {
450 predicate: MotionPredicate::Focused(id),
451 then_expr: Box::new(expression(focused)),
452 else_expr: Box::new(MotionExpr::If {
453 predicate: MotionPredicate::Hovered(id),
454 then_expr: Box::new(expression(hover)),
455 else_expr: Box::new(expression(base)),
456 }),
457 }),
458 }
459 }
460
461 fn any_state(&self, predicate: impl Fn(&PressableStyle) -> bool) -> bool {
462 predicate(&self.style)
463 || self.hover_style.as_ref().is_some_and(&predicate)
464 || self.pressed_style.as_ref().is_some_and(&predicate)
465 || self.focused_style.as_ref().is_some_and(&predicate)
466 || self.disabled_style.as_ref().is_some_and(predicate)
467 }
468
469 fn motion_id(id: WidgetId) -> WidgetId {
470 WidgetId::derived(id.as_u128(), &[Self::MOTION_SALT])
471 }
472
473 fn animated_style(
474 &self,
475 cx: &InternalLoweringCx<'_>,
476 id: WidgetId,
477 mut style: PressableStyle,
478 ) -> PressableStyle {
479 if self.transition.is_none() {
480 return style;
481 }
482 let motion_id = Self::motion_id(id);
483 let value = |property| cx.runtime_state.motion.values.get(&(motion_id, property));
484
485 if let Some(MotionValue::Color(background)) = value(MotionPropertyId::BackgroundColor) {
486 style.background = Some(Fill::Solid(*background));
487 }
488 if let Some(MotionValue::Fill(background)) = value(MotionPropertyId::BackgroundFill) {
489 style.background = Some(background.clone());
490 }
491 if let Some(MotionValue::Shadows(shadows)) = value(MotionPropertyId::BoxShadows) {
492 style.shadows = Some(shadows.clone());
493 }
494
495 let animated_border_color = match value(MotionPropertyId::BorderColor) {
496 Some(MotionValue::Color(color)) => Some(*color),
497 _ => None,
498 };
499 let animated_border_width = match value(MotionPropertyId::BorderWidth) {
500 Some(MotionValue::Px(width)) => Some(*width),
501 _ => None,
502 };
503 if animated_border_color.is_some() || animated_border_width.is_some() {
504 let mut border = style
505 .border
506 .clone()
507 .or_else(|| self.first_border())
508 .unwrap_or_else(|| Stroke {
509 fill: Fill::Solid(Color::TRANSPARENT),
510 width: 0.0,
511 dash_array: None,
512 line_cap: fission_ir::op::LineCap::Butt,
513 line_join: fission_ir::op::LineJoin::Miter,
514 });
515 if let Some(color) = animated_border_color {
516 border.fill = Fill::Solid(color);
517 }
518 if let Some(width) = animated_border_width {
519 border.width = width;
520 }
521 style.border = Some(border);
522 }
523 if let Some(MotionValue::Px(radius)) = value(MotionPropertyId::CornerRadius) {
524 style.corner_radius = Some(*radius);
525 }
526
527 let padding_properties = [
528 MotionPropertyId::PaddingLeft,
529 MotionPropertyId::PaddingRight,
530 MotionPropertyId::PaddingTop,
531 MotionPropertyId::PaddingBottom,
532 ];
533 let mut animated_padding = style.padding.clone().unwrap_or_else(|| {
534 std::array::from_fn(|index| Length::Points(self.base_padding(index)))
535 });
536 let mut has_animated_padding = false;
537 for (index, property) in padding_properties.into_iter().enumerate() {
538 if let Some(MotionValue::Px(padding)) = value(property) {
539 animated_padding[index] = Length::Points(*padding);
540 has_animated_padding = true;
541 }
542 }
543 if has_animated_padding {
544 style.padding = Some(animated_padding);
545 }
546 style
547 }
548
549 fn first_border(&self) -> Option<Stroke> {
550 [
551 Some(&self.style),
552 self.hover_style.as_ref(),
553 self.pressed_style.as_ref(),
554 self.focused_style.as_ref(),
555 self.disabled_style.as_ref(),
556 ]
557 .into_iter()
558 .flatten()
559 .find_map(|style| style.border.clone())
560 }
561
562 fn base_padding(&self, index: usize) -> f32 {
563 self.style
564 .padding
565 .as_ref()
566 .and_then(|padding| length_points(&padding[index]))
567 .unwrap_or(0.0)
568 }
569
570 fn motion_tracks(&self, transition: &MotionTransition) -> Vec<MotionTrack> {
571 let mut tracks = Vec::new();
572 if self.style.opacity.is_some()
573 || self
574 .hover_style
575 .as_ref()
576 .is_some_and(|style| style.opacity.is_some())
577 || self
578 .pressed_style
579 .as_ref()
580 .is_some_and(|style| style.opacity.is_some())
581 || self
582 .focused_style
583 .as_ref()
584 .is_some_and(|style| style.opacity.is_some())
585 || self
586 .disabled_style
587 .as_ref()
588 .is_some_and(|style| style.opacity.is_some())
589 {
590 tracks.push(
591 MotionTrack::composite(
592 MotionPropertyId::Opacity,
593 MotionStartValue::Explicit(scalar(self.style.opacity.unwrap_or(1.0))),
594 self.state_scalar_expr(|style| style.opacity),
595 )
596 .transition(transition.clone()),
597 );
598 }
599 if self.style.scale.is_some()
600 || self
601 .hover_style
602 .as_ref()
603 .is_some_and(|style| style.scale.is_some())
604 || self
605 .pressed_style
606 .as_ref()
607 .is_some_and(|style| style.scale.is_some())
608 || self
609 .focused_style
610 .as_ref()
611 .is_some_and(|style| style.scale.is_some())
612 || self
613 .disabled_style
614 .as_ref()
615 .is_some_and(|style| style.scale.is_some())
616 {
617 tracks.push(
618 MotionTrack::composite(
619 MotionPropertyId::Scale,
620 MotionStartValue::Explicit(scalar(self.style.scale.unwrap_or(1.0))),
621 self.state_scalar_expr(|style| style.scale),
622 )
623 .transition(transition.clone()),
624 );
625 }
626 let has_non_solid_background = self.any_state(|style| {
627 style
628 .background
629 .as_ref()
630 .is_some_and(|fill| !matches!(fill, Fill::Solid(_)))
631 });
632 if has_non_solid_background {
633 tracks.push(
634 MotionTrack::paint(
635 MotionPropertyId::BackgroundFill,
636 MotionStartValue::Explicit(motion_fill(
637 self.style
638 .background
639 .clone()
640 .unwrap_or(Fill::Solid(Color::TRANSPARENT)),
641 )),
642 self.state_fill_expr(),
643 )
644 .transition(MotionTransition::Instant),
645 );
646 } else if self.any_state(|style| solid_background(style).is_some()) {
647 tracks.push(
648 MotionTrack::paint(
649 MotionPropertyId::BackgroundColor,
650 MotionStartValue::Explicit(color(
651 solid_background(&self.style).unwrap_or(Color::TRANSPARENT),
652 )),
653 self.state_color_expr(solid_background),
654 )
655 .transition(transition.clone()),
656 );
657 }
658 if self.any_state(|style| style.shadows.is_some()) {
659 tracks.push(
660 MotionTrack::paint(
661 MotionPropertyId::BoxShadows,
662 MotionStartValue::Explicit(motion_shadows(
663 self.style.shadows.clone().unwrap_or_default(),
664 )),
665 self.state_shadows_expr(),
666 )
667 .transition(MotionTransition::Instant),
668 );
669 }
670 if self.any_state(|style| solid_border_color(style).is_some()) {
671 tracks.push(
672 MotionTrack::paint(
673 MotionPropertyId::BorderColor,
674 MotionStartValue::Explicit(color(
675 solid_border_color(&self.style).unwrap_or(Color::TRANSPARENT),
676 )),
677 self.state_color_expr(solid_border_color),
678 )
679 .transition(transition.clone()),
680 );
681 }
682 if self.any_state(|style| style.border.is_some()) {
683 tracks.push(
684 MotionTrack::paint(
685 MotionPropertyId::BorderWidth,
686 MotionStartValue::Explicit(px(border_width(&self.style).unwrap_or(0.0))),
687 self.state_px_expr(border_width),
688 )
689 .transition(transition.clone()),
690 );
691 }
692 if self.any_state(|style| style.corner_radius.is_some()) {
693 tracks.push(
694 MotionTrack::paint(
695 MotionPropertyId::CornerRadius,
696 MotionStartValue::Explicit(px(self.style.corner_radius.unwrap_or(0.0))),
697 self.state_px_expr(|style| style.corner_radius),
698 )
699 .transition(transition.clone()),
700 );
701 }
702 for (index, property) in [
703 MotionPropertyId::PaddingLeft,
704 MotionPropertyId::PaddingRight,
705 MotionPropertyId::PaddingTop,
706 MotionPropertyId::PaddingBottom,
707 ]
708 .into_iter()
709 .enumerate()
710 {
711 if self.any_state(|style| padding_points(style, index).is_some()) {
712 tracks.push(
713 MotionTrack::layout(
714 property,
715 MotionStartValue::Explicit(px(self.base_padding(index))),
716 self.state_value_expr(move |style| padding_points(style, index), 0.0, px),
717 )
718 .transition(transition.clone()),
719 );
720 }
721 }
722 tracks
723 }
724}
725
726fn length_points(length: &Length) -> Option<f32> {
727 match length {
728 Length::Points(value) => Some(*value),
729 _ => None,
730 }
731}
732
733fn padding_points(style: &PressableStyle, index: usize) -> Option<f32> {
734 style
735 .padding
736 .as_ref()
737 .and_then(|padding| length_points(&padding[index]))
738}
739
740fn solid_background(style: &PressableStyle) -> Option<Color> {
741 match &style.background {
742 Some(Fill::Solid(color)) => Some(*color),
743 _ => None,
744 }
745}
746
747fn solid_border_color(style: &PressableStyle) -> Option<Color> {
748 match style.border.as_ref().map(|border| &border.fill) {
749 Some(Fill::Solid(color)) => Some(*color),
750 _ => None,
751 }
752}
753
754fn border_width(style: &PressableStyle) -> Option<f32> {
755 style.border.as_ref().map(|border| border.width)
756}
757
758impl Default for Pressable {
759 fn default() -> Self {
760 Self {
761 id: None,
762 child: crate::ui::Spacer::default().into(),
763 on_press: None,
764 label: None,
765 semantics_identifier: None,
766 hyperlink: None,
767 popover_target: None,
768 role: PressableRole::Button,
769 focus_policy: FocusPolicy::FocusOnPointer,
770 disabled: false,
771 layout: BoxStyle::default(),
772 flex_grow: 0.0,
773 flex_shrink: 1.0,
774 style: PressableStyle::default(),
775 hover_style: None,
776 pressed_style: None,
777 focused_style: None,
778 disabled_style: None,
779 transition: None,
780 ripple: None,
781 }
782 }
783}
784
785impl InternalLower for Pressable {
786 fn lower(&self, cx: &mut InternalLoweringCx) -> WidgetId {
787 let id = self.id.unwrap_or_else(|| cx.next_node_id());
788 let layout_id = cx.next_node_id();
789 let style = self.animated_style(cx, id, self.resolved_style(cx, id));
790 cx.push_scope(layout_id);
791
792 let mut layout_style = self.layout.clone();
793 layout_style.padding = style.padding.clone().or(layout_style.padding);
794 let margin_style = split_box_margin(&mut layout_style);
795 let position = layout_style.position.take();
796 let grid = layout_style.grid.take();
797 let flex_grow = layout_style
798 .flex_grow
799 .map(|value| value.0)
800 .unwrap_or(self.flex_grow);
801 let flex_shrink = layout_style
802 .flex_shrink
803 .map(|value| value.0)
804 .unwrap_or(self.flex_shrink);
805 let mut layout = InternalIrBuilder::new(
806 layout_id,
807 Op::Layout(LayoutOp::StyledBox {
808 style: layout_style,
809 flex_grow,
810 flex_shrink,
811 }),
812 )
813 .composite(CompositeStyle {
814 opacity: self
815 .transition
816 .is_none()
817 .then(|| style.opacity.map(CompositeScalar::new))
818 .flatten(),
819 scale: self
820 .transition
821 .is_none()
822 .then(|| style.scale.map(CompositeScalar::new))
823 .flatten(),
824 ..Default::default()
825 });
826
827 for shadow in style.shadows.as_deref().unwrap_or_default() {
828 layout.add_child(
829 InternalIrBuilder::new(
830 cx.next_node_id(),
831 Op::Paint(PaintOp::DrawRect {
832 fill: None,
833 stroke: None,
834 corner_radius: style.corner_radius.unwrap_or(0.0),
835 shadow: Some(*shadow),
836 }),
837 )
838 .build(cx),
839 );
840 }
841 if style.background.is_some() || style.border.is_some() {
842 layout.add_child(
843 InternalIrBuilder::new(
844 cx.next_node_id(),
845 Op::Paint(PaintOp::DrawRect {
846 fill: style.background,
847 stroke: style.border,
848 corner_radius: style.corner_radius.unwrap_or(0.0),
849 shadow: None,
850 }),
851 )
852 .build(cx),
853 );
854 }
855 layout.add_child(self.child.lower(cx));
856 let layout_id = layout.build(cx);
857 cx.pop_scope();
858
859 let mut semantics = Semantics {
860 role: self.role.semantics_role(),
861 label: self.label.clone(),
862 identifier: self.semantics_identifier.clone(),
863 hyperlink: self.hyperlink.clone(),
864 popover_target: self.popover_target.clone(),
865 focusable: !self.disabled,
866 focus_policy: self.focus_policy,
867 disabled: self.disabled,
868 ..Default::default()
869 };
870 if let Some(action) = &self.on_press {
871 if !self.disabled {
872 semantics.actions.entries.push(ActionEntry {
873 trigger: ActionTrigger::Default,
874 action_id: action.id.as_u128(),
875 payload_data: Some(action.payload.clone()),
876 });
877 }
878 }
879 let mut semantics_node = InternalIrBuilder::new(id, Op::Semantics(semantics));
880 semantics_node.add_child(layout_id);
881 let mut content_id = semantics_node.build(cx);
882
883 if let Some(margin_style) = margin_style {
884 let mut outer = InternalIrBuilder::new(
885 cx.next_node_id(),
886 Op::Layout(LayoutOp::StyledBox {
887 style: margin_style,
888 flex_grow,
889 flex_shrink,
890 }),
891 );
892 outer.add_child(content_id);
893 content_id = outer.build(cx);
894 }
895 if let Some(position) = position {
896 let mut outer = InternalIrBuilder::new(
897 cx.next_node_id(),
898 Op::Layout(LayoutOp::PositionedLengths {
899 left: position.left,
900 top: position.top,
901 right: position.right,
902 bottom: position.bottom,
903 width: None,
904 height: None,
905 }),
906 );
907 outer.add_child(content_id);
908 content_id = outer.build(cx);
909 }
910 if let Some(grid) = grid {
911 let mut outer = InternalIrBuilder::new(
912 cx.next_node_id(),
913 Op::Layout(LayoutOp::GridItem {
914 row_start: grid.row_start,
915 row_end: grid.row_end,
916 col_start: grid.col_start,
917 col_end: grid.col_end,
918 }),
919 );
920 outer.add_child(content_id);
921 content_id = outer.build(cx);
922 }
923
924 content_id
925 }
926}
927
928impl From<Pressable> for Widget {
929 fn from(mut pressable: Pressable) -> Self {
930 let id = pressable
931 .id
932 .or_else(crate::build::current_widget_id)
933 .or_else(|| crate::build::next_implicit_widget_id(Pressable::MOTION_SALT))
934 .unwrap_or_else(|| WidgetId::explicit("fission.core.pressable"));
935 pressable.id = Some(id);
936 let transition = pressable.transition.clone();
937 let ripple = (!pressable.disabled)
938 .then(|| pressable.ripple.clone())
939 .flatten();
940 pressable.ripple = None;
941 let state_transition = transition.unwrap_or(MotionTransition::Instant);
942 let tracks = pressable.motion_tracks(&state_transition);
943 let base = Widget::from_pressable_raw(pressable);
944 let animated = if tracks.is_empty() {
945 base
946 } else {
947 Motion {
948 id: Pressable::motion_id(id),
949 tracks,
950 child: base,
951 ..Default::default()
952 }
953 .into()
954 };
955 if let Some(effect) = ripple {
956 RippleLayer {
957 id: WidgetId::derived(id.as_u128(), &[0x5249_5050]),
958 effect,
959 child: animated,
960 }
961 .into()
962 } else {
963 animated
964 }
965 }
966}