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