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