1use iced_widget::button::{Status, Style};
4use iced_widget::core::text as core_text;
5use iced_widget::core::time::Instant;
6use iced_widget::core::widget::{self, Tree, tree};
7use iced_widget::core::{
8 Background, Clipboard, Color, Element, Event, Layout, Length, Padding, Rectangle, Shell, Size,
9 Transformation, Vector, Widget, alignment, border, layout, mouse, overlay, renderer,
10};
11use iced_widget::graphics::geometry;
12use iced_widget::renderer::wgpu::primitive;
13use iced_widget::text;
14use iced_widget::{Column, Container, Row, Space, Stack, Text, opaque};
15
16use super::absolute_line_height;
17use super::support::{alpha_color, duration_ms, lerp};
18use crate::utils::shadow_from_level;
19use crate::{Theme, fonts, tokens};
20
21#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct Transition {
24 phase: TransitionPhase,
25 started_at: Option<Instant>,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum TransitionPhase {
31 Hidden,
32 Showing,
33 Shown,
34 Dismissing,
35}
36
37impl Default for Transition {
38 fn default() -> Self {
39 Self {
40 phase: TransitionPhase::Hidden,
41 started_at: None,
42 }
43 }
44}
45
46impl Transition {
47 pub fn show(&mut self, now: Instant) {
49 match self.phase {
50 TransitionPhase::Showing | TransitionPhase::Shown => {}
51 TransitionPhase::Hidden | TransitionPhase::Dismissing => {
52 self.phase = TransitionPhase::Showing;
53 self.started_at = Some(now);
54 }
55 }
56 }
57
58 pub fn dismiss(&mut self, now: Instant) {
60 if self.is_active() && self.phase != TransitionPhase::Dismissing {
61 self.phase = TransitionPhase::Dismissing;
62 self.started_at = Some(now);
63 }
64 }
65
66 pub fn advance(&mut self, now: Instant) -> bool {
68 match self.phase {
69 TransitionPhase::Hidden => {}
70 TransitionPhase::Showing => {
71 if self.scale_progress(now) >= 1.0 {
72 self.phase = TransitionPhase::Shown;
73 self.started_at = None;
74 }
75 }
76 TransitionPhase::Shown => {}
77 TransitionPhase::Dismissing => {
78 if self.scale_progress(now) >= 1.0 {
79 *self = Self::default();
80 }
81 }
82 }
83
84 self.is_animating()
85 }
86
87 pub fn is_active(&self) -> bool {
89 self.phase != TransitionPhase::Hidden
90 }
91
92 pub fn is_animating(&self) -> bool {
94 matches!(
95 self.phase,
96 TransitionPhase::Showing | TransitionPhase::Dismissing
97 )
98 }
99
100 pub fn phase(&self) -> TransitionPhase {
102 self.phase
103 }
104
105 pub fn scale(&self, now: Instant) -> f32 {
107 let eased = android_decelerate(
108 self.scale_progress(now),
109 tokens::component::dialog::DECELERATE_QUINT_FACTOR,
110 );
111
112 match self.phase {
113 TransitionPhase::Hidden => tokens::component::dialog::ENTER_SCALE_FROM,
114 TransitionPhase::Showing => {
115 lerp(tokens::component::dialog::ENTER_SCALE_FROM, 1.0, eased)
116 }
117 TransitionPhase::Shown => 1.0,
118 TransitionPhase::Dismissing => {
119 lerp(1.0, tokens::component::dialog::EXIT_SCALE_TO, eased)
120 }
121 }
122 }
123
124 pub fn alpha(&self, now: Instant) -> f32 {
126 let eased = android_decelerate(
127 self.alpha_progress(now),
128 tokens::component::dialog::DECELERATE_CUBIC_FACTOR,
129 );
130
131 match self.phase {
132 TransitionPhase::Hidden => 0.0,
133 TransitionPhase::Showing => eased,
134 TransitionPhase::Shown => 1.0,
135 TransitionPhase::Dismissing => 1.0 - eased,
136 }
137 }
138
139 pub fn scrim_alpha(&self, now: Instant) -> f32 {
141 let eased = android_decelerate(
142 self.scrim_progress(now),
143 tokens::component::dialog::DECELERATE_CUBIC_FACTOR,
144 );
145
146 match self.phase {
147 TransitionPhase::Hidden => 0.0,
148 TransitionPhase::Showing => eased,
149 TransitionPhase::Shown => 1.0,
150 TransitionPhase::Dismissing => 1.0 - eased,
151 }
152 }
153
154 fn scale_progress(&self, now: Instant) -> f32 {
155 self.progress(now, tokens::component::dialog::SCALE_ANIMATION_DURATION_MS)
156 }
157
158 fn alpha_progress(&self, now: Instant) -> f32 {
159 self.progress(now, tokens::component::dialog::ALPHA_ANIMATION_DURATION_MS)
160 }
161
162 fn scrim_progress(&self, now: Instant) -> f32 {
163 self.progress(now, tokens::component::dialog::SCRIM_ANIMATION_DURATION_MS)
164 }
165
166 fn progress(&self, now: Instant, duration_ms_value: u16) -> f32 {
167 let Some(started_at) = self.started_at else {
168 return match self.phase {
169 TransitionPhase::Showing | TransitionPhase::Dismissing => 1.0,
170 TransitionPhase::Hidden | TransitionPhase::Shown => 0.0,
171 };
172 };
173
174 let duration = duration_ms(duration_ms_value);
175
176 if duration.is_zero() {
177 return 1.0;
178 }
179
180 (now.saturating_duration_since(started_at).as_secs_f32() / duration.as_secs_f32())
181 .clamp(0.0, 1.0)
182 }
183}
184
185#[derive(Debug, Clone, Copy, PartialEq)]
187pub struct AlphaOptions {
188 pub alpha: f32,
189}
190
191impl Default for AlphaOptions {
192 fn default() -> Self {
193 Self { alpha: 1.0 }
194 }
195}
196
197impl AlphaOptions {
198 pub fn alpha(mut self, alpha: f32) -> Self {
200 self.alpha = alpha;
201 self
202 }
203}
204
205#[derive(Debug, Clone, PartialEq)]
207pub struct AlertOptions<'a> {
208 pub icon: Option<text::Fragment<'a>>,
209 pub alpha: f32,
210}
211
212impl Default for AlertOptions<'_> {
213 fn default() -> Self {
214 Self {
215 icon: None,
216 alpha: 1.0,
217 }
218 }
219}
220
221impl<'a> AlertOptions<'a> {
222 pub fn icon(mut self, icon: impl text::IntoFragment<'a>) -> Self {
224 self.icon = Some(icon.into_fragment());
225 self
226 }
227
228 pub fn alpha(mut self, alpha: f32) -> Self {
230 self.alpha = alpha;
231 self
232 }
233}
234
235pub fn basic<'a, Message, Renderer>(
237 content: impl Into<Element<'a, Message, Theme, Renderer>>,
238) -> Container<'a, Message, Theme, Renderer>
239where
240 Message: 'a,
241 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
242{
243 basic_with(content, AlphaOptions::default())
244}
245
246pub fn basic_with<'a, Message, Renderer>(
248 content: impl Into<Element<'a, Message, Theme, Renderer>>,
249 options: AlphaOptions,
250) -> Container<'a, Message, Theme, Renderer>
251where
252 Message: 'a,
253 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
254{
255 Container::new(content)
256 .width(Length::Fill)
257 .max_width(tokens::component::dialog::CONTAINER_MAX_WIDTH)
258 .padding(tokens::component::dialog::CONTAINER_PADDING)
259 .style(move |theme| container_style_alpha(theme, options.alpha))
260}
261
262pub fn alert<'a, Message, Renderer>(
264 title: impl text::IntoFragment<'a>,
265 supporting_text: impl text::IntoFragment<'a>,
266 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
267) -> Container<'a, Message, Theme, Renderer>
268where
269 Message: 'a,
270 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
271 iced_widget::core::Font: Into<Renderer::Font>,
272{
273 alert_with(title, supporting_text, actions, AlertOptions::default())
274}
275
276pub fn alert_with<'a, Message, Renderer>(
278 title: impl text::IntoFragment<'a>,
279 supporting_text: impl text::IntoFragment<'a>,
280 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
281 options: AlertOptions<'a>,
282) -> Container<'a, Message, Theme, Renderer>
283where
284 Message: 'a,
285 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
286 iced_widget::core::Font: Into<Renderer::Font>,
287{
288 alert_content(title, supporting_text, actions, options)
289}
290
291pub fn actions<'a, Message, Renderer>(
293 buttons: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
294) -> Container<'a, Message, Theme, Renderer>
295where
296 Message: 'a,
297 Renderer: iced_widget::core::Renderer + 'a,
298{
299 Container::new(
300 Row::with_children(buttons)
301 .spacing(tokens::component::dialog::ACTIONS_HORIZONTAL_SPACING)
302 .align_y(alignment::Vertical::Center),
303 )
304 .width(Length::Fill)
305 .align_x(alignment::Horizontal::Right)
306}
307
308pub fn action<'a, Message, Renderer>(
310 label: impl text::IntoFragment<'a>,
311) -> super::button::Button<'a, Message, Renderer>
312where
313 Message: Clone + 'a,
314 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
315{
316 action_with(label, AlphaOptions::default())
317}
318
319pub fn action_button<'a, Message, Renderer>(
321 label: impl text::IntoFragment<'a>,
322 on_press: Message,
323) -> Element<'a, Message, Theme, Renderer>
324where
325 Message: Clone + 'a,
326 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
327{
328 action_button_with(label, on_press, AlphaOptions::default())
329}
330
331pub fn action_with<'a, Message, Renderer>(
333 label: impl text::IntoFragment<'a>,
334 options: AlphaOptions,
335) -> super::button::Button<'a, Message, Renderer>
336where
337 Message: Clone + 'a,
338 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
339{
340 super::button::button(label, super::button::ButtonVariant::Text)
341 .style(move |theme, status| action_style_alpha(theme, status, options.alpha))
342}
343
344pub fn action_button_with<'a, Message, Renderer>(
346 label: impl text::IntoFragment<'a>,
347 on_press: Message,
348 options: AlphaOptions,
349) -> Element<'a, Message, Theme, Renderer>
350where
351 Message: Clone + 'a,
352 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
353{
354 action_with(label, options).on_press(on_press).into()
355}
356
357pub fn scrim<'a, Message, Renderer>(
359 content: impl Into<Element<'a, Message, Theme, Renderer>>,
360) -> Container<'a, Message, Theme, Renderer>
361where
362 Message: 'a,
363 Renderer: iced_widget::core::Renderer + 'a,
364{
365 scrim_with(content, AlphaOptions::default())
366}
367
368pub fn scrim_with<'a, Message, Renderer>(
370 content: impl Into<Element<'a, Message, Theme, Renderer>>,
371 options: AlphaOptions,
372) -> Container<'a, Message, Theme, Renderer>
373where
374 Message: 'a,
375 Renderer: iced_widget::core::Renderer + 'a,
376{
377 Container::new(content)
378 .width(Length::Fill)
379 .height(Length::Fill)
380 .style(move |theme| scrim_style_alpha(theme, options.alpha))
381}
382
383pub fn modal_overlay<'a, Message, Renderer>(
385 content: impl Into<Element<'a, Message, Theme, Renderer>>,
386) -> Container<'a, Message, Theme, Renderer>
387where
388 Message: 'a,
389 Renderer: iced_widget::core::Renderer + 'a,
390{
391 scrim(
392 Container::new(content)
393 .width(Length::Fill)
394 .height(Length::Fill)
395 .align_x(alignment::Horizontal::Center)
396 .align_y(alignment::Vertical::Center),
397 )
398}
399
400pub fn modal_layer<'a, Message, Renderer>(
406 content: impl Into<Element<'a, Message, Theme, Renderer>>,
407) -> Element<'a, Message, Theme, Renderer>
408where
409 Message: 'a,
410 Renderer: iced_widget::core::Renderer + 'a,
411{
412 let scrim = opaque(scrim(Space::new().width(Length::Fill).height(Length::Fill)));
413 let dialog = opaque(Container::new(content).center(Length::Fill));
414
415 Stack::with_children([scrim, dialog])
416 .width(Length::Fill)
417 .height(Length::Fill)
418 .into()
419}
420
421pub fn modal_layer_animated<'a, Message, Renderer>(
423 content: impl Into<Element<'a, Message, Theme, Renderer>>,
424 transition: &Transition,
425 now: Instant,
426) -> Element<'a, Message, Theme, Renderer>
427where
428 Message: 'a,
429 Renderer: iced_widget::core::Renderer + 'a,
430{
431 let scrim = opaque(scrim_with(
432 Space::new().width(Length::Fill).height(Length::Fill),
433 AlphaOptions::default().alpha(transition.scrim_alpha(now)),
434 ));
435 let dialog = opaque(
436 Container::new(scaled(
437 content,
438 transition.scale(now),
439 transition.phase() != TransitionPhase::Dismissing,
440 ))
441 .center(Length::Fill),
442 );
443
444 Stack::with_children([scrim, dialog])
445 .width(Length::Fill)
446 .height(Length::Fill)
447 .into()
448}
449
450pub fn modal<'a, Message, Renderer>(
452 content: impl Into<Element<'a, Message, Theme, Renderer>>,
453 dialog: impl Into<Element<'a, Message, Theme, Renderer>>,
454) -> Element<'a, Message, Theme, Renderer>
455where
456 Message: 'a,
457 Renderer: iced_widget::core::Renderer + 'a,
458{
459 Stack::with_children([content.into(), modal_layer(dialog)])
460 .width(Length::Fill)
461 .height(Length::Fill)
462 .into()
463}
464
465pub fn modal_animated<'a, Message, Renderer>(
467 content: impl Into<Element<'a, Message, Theme, Renderer>>,
468 transition: &Transition,
469 now: Instant,
470 dialog: impl Into<Element<'a, Message, Theme, Renderer>>,
471) -> Element<'a, Message, Theme, Renderer>
472where
473 Message: 'a,
474 Renderer: iced_widget::core::Renderer + 'a,
475{
476 let content = content.into();
477
478 if !transition.is_active() {
479 return content;
480 }
481
482 Stack::with_children([content, modal_layer_animated(dialog, transition, now)])
483 .width(Length::Fill)
484 .height(Length::Fill)
485 .into()
486}
487
488fn alert_content<'a, Message, Renderer>(
489 title: impl text::IntoFragment<'a>,
490 supporting_text: impl text::IntoFragment<'a>,
491 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
492 options: AlertOptions<'a>,
493) -> Container<'a, Message, Theme, Renderer>
494where
495 Message: 'a,
496 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
497 iced_widget::core::Font: Into<Renderer::Font>,
498{
499 let alpha = options.alpha;
500 let title_alignment = title_alignment(options.icon.is_some());
501 let mut content = Column::new().width(Length::Fill);
502
503 if let Some(icon) = options.icon {
504 content = content.push(
505 Container::new(icon_text(icon, alpha))
506 .width(Length::Fill)
507 .padding(Padding {
508 top: 0.0,
509 right: 0.0,
510 bottom: tokens::component::dialog::ICON_BOTTOM_PADDING,
511 left: 0.0,
512 })
513 .align_x(alignment::Horizontal::Center),
514 );
515 }
516
517 content = content.push(
518 Container::new(title_text(title, title_alignment, alpha))
519 .width(Length::Fill)
520 .padding(Padding {
521 top: 0.0,
522 right: 0.0,
523 bottom: tokens::component::dialog::TITLE_BOTTOM_PADDING,
524 left: 0.0,
525 }),
526 );
527
528 content = content.push(
529 Container::new(supporting_text_view(supporting_text, alpha))
530 .width(Length::Fill)
531 .padding(Padding {
532 top: 0.0,
533 right: 0.0,
534 bottom: tokens::component::dialog::SUPPORTING_TEXT_BOTTOM_PADDING,
535 left: 0.0,
536 }),
537 );
538
539 content = content.push(actions.into());
540
541 basic_with(content, AlphaOptions::default().alpha(alpha))
542}
543
544fn icon_text<'a, Renderer>(icon: text::Fragment<'a>, alpha: f32) -> Text<'a, Theme, Renderer>
545where
546 Renderer: core_text::Renderer + 'a,
547 iced_widget::core::Font: Into<Renderer::Font>,
548{
549 fonts::filled_icon(icon, tokens::component::dialog::ICON_SIZE)
550 .width(Length::Fixed(tokens::component::dialog::ICON_SIZE))
551 .height(Length::Fixed(tokens::component::dialog::ICON_SIZE))
552 .center()
553 .style(move |theme| icon_style_alpha(theme, alpha))
554}
555
556fn title_text<'a, Renderer>(
557 title: impl text::IntoFragment<'a>,
558 alignment: alignment::Horizontal,
559 alpha: f32,
560) -> Text<'a, Theme, Renderer>
561where
562 Renderer: core_text::Renderer + 'a,
563{
564 let scale = tokens::component::dialog::HEADLINE_TEXT;
565
566 Text::new(title)
567 .size(scale.size)
568 .line_height(absolute_line_height(scale.line_height))
569 .width(Length::Fill)
570 .align_x(alignment)
571 .color_maybe(None::<iced_widget::core::Color>)
572 .style(move |theme| title_style_alpha(theme, alpha))
573}
574
575fn title_alignment(has_icon: bool) -> alignment::Horizontal {
576 if has_icon {
577 alignment::Horizontal::Center
578 } else {
579 alignment::Horizontal::Left
580 }
581}
582
583fn supporting_text_view<'a, Renderer>(
584 supporting_text: impl text::IntoFragment<'a>,
585 alpha: f32,
586) -> Text<'a, Theme, Renderer>
587where
588 Renderer: core_text::Renderer + 'a,
589{
590 let scale = tokens::component::dialog::SUPPORTING_TEXT;
591
592 Text::new(supporting_text)
593 .size(scale.size)
594 .line_height(absolute_line_height(scale.line_height))
595 .width(Length::Fill)
596 .color_maybe(None::<iced_widget::core::Color>)
597 .style(move |theme| supporting_text_style_alpha(theme, alpha))
598}
599
600fn container_style(theme: &Theme) -> iced_widget::container::Style {
601 let colors = theme.colors();
602
603 iced_widget::container::Style {
604 background: Some(Background::Color(colors.surface.container.high)),
605 text_color: Some(colors.surface.text_variant),
606 border: border::rounded(tokens::component::dialog::CONTAINER_SHAPE),
607 shadow: shadow_from_level(
608 tokens::component::dialog::CONTAINER_ELEVATION_LEVEL,
609 colors.shadow,
610 ),
611 snap: cfg!(feature = "crisp"),
612 }
613}
614
615fn container_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::container::Style {
616 let mut style = container_style(theme);
617
618 style.background = style
619 .background
620 .map(|background| background.scale_alpha(alpha));
621 style.text_color = style.text_color.map(|color| alpha_color(color, alpha));
622 style.border.color = alpha_color(style.border.color, alpha);
623 style.shadow.color = alpha_color(style.shadow.color, alpha);
624
625 style
626}
627
628fn icon_style(theme: &Theme) -> iced_widget::text::Style {
629 iced_widget::text::Style {
630 color: Some(theme.colors().secondary.color),
631 }
632}
633
634fn icon_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
635 iced_widget::text::Style {
636 color: icon_style(theme)
637 .color
638 .map(|color| alpha_color(color, alpha)),
639 }
640}
641
642fn title_style(theme: &Theme) -> iced_widget::text::Style {
643 iced_widget::text::Style {
644 color: Some(theme.colors().surface.text),
645 }
646}
647
648fn title_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
649 iced_widget::text::Style {
650 color: title_style(theme)
651 .color
652 .map(|color| alpha_color(color, alpha)),
653 }
654}
655
656fn supporting_text_style(theme: &Theme) -> iced_widget::text::Style {
657 iced_widget::text::Style {
658 color: Some(theme.colors().surface.text_variant),
659 }
660}
661
662fn supporting_text_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
663 iced_widget::text::Style {
664 color: supporting_text_style(theme)
665 .color
666 .map(|color| alpha_color(color, alpha)),
667 }
668}
669
670fn action_style_alpha(theme: &Theme, status: Status, alpha: f32) -> Style {
671 let mut style = crate::style::button::text(theme, status);
672
673 style.background = style
674 .background
675 .map(|background| background.scale_alpha(alpha));
676 style.text_color = alpha_color(style.text_color, alpha);
677 style.border.color = alpha_color(style.border.color, alpha);
678 style.shadow.color = alpha_color(style.shadow.color, alpha);
679
680 style
681}
682
683fn scrim_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::container::Style {
684 iced_widget::container::Style {
685 background: Some(Background::Color(alpha_color(
686 Color {
687 a: 1.0,
688 ..theme.colors().scrim
689 },
690 tokens::component::dialog::SCRIM_OPACITY * alpha,
691 ))),
692 text_color: Some(theme.colors().surface.text),
693 ..iced_widget::container::Style::default()
694 }
695}
696
697fn scaled<'a, Message, Renderer>(
698 content: impl Into<Element<'a, Message, Theme, Renderer>>,
699 scale: f32,
700 interactive: bool,
701) -> Element<'a, Message, Theme, Renderer>
702where
703 Message: 'a,
704 Renderer: iced_widget::core::Renderer + 'a,
705{
706 Element::new(Scaled {
707 content: content.into(),
708 scale,
709 interactive,
710 })
711}
712
713struct Scaled<'a, Message, Renderer> {
714 content: Element<'a, Message, Theme, Renderer>,
715 scale: f32,
716 interactive: bool,
717}
718
719struct ScaledTag;
720
721impl<Message, Renderer> std::fmt::Debug for Scaled<'_, Message, Renderer> {
722 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
723 f.debug_struct("Scaled")
724 .field("scale", &self.scale)
725 .field("interactive", &self.interactive)
726 .finish_non_exhaustive()
727 }
728}
729
730impl<Message, Renderer> Scaled<'_, Message, Renderer> {
731 fn transformation(&self, bounds: Rectangle) -> Transformation {
732 Transformation::translate(bounds.center_x(), bounds.center_y())
733 * Transformation::scale(self.scale)
734 * Transformation::translate(-bounds.center_x(), -bounds.center_y())
735 }
736}
737
738impl<Message, Renderer> Widget<Message, Theme, Renderer> for Scaled<'_, Message, Renderer>
739where
740 Renderer: iced_widget::core::Renderer,
741{
742 fn tag(&self) -> tree::Tag {
743 tree::Tag::of::<ScaledTag>()
744 }
745
746 fn state(&self) -> tree::State {
747 tree::State::None
748 }
749
750 fn children(&self) -> Vec<Tree> {
751 vec![Tree::new(&self.content)]
752 }
753
754 fn diff(&self, tree: &mut Tree) {
755 tree.diff_children(std::slice::from_ref(&self.content));
756 }
757
758 fn size(&self) -> Size<Length> {
759 self.content.as_widget().size()
760 }
761
762 fn size_hint(&self) -> Size<Length> {
763 self.content.as_widget().size_hint()
764 }
765
766 fn layout(
767 &mut self,
768 tree: &mut Tree,
769 renderer: &Renderer,
770 limits: &layout::Limits,
771 ) -> layout::Node {
772 self.content
773 .as_widget_mut()
774 .layout(&mut tree.children[0], renderer, limits)
775 }
776
777 fn operate(
778 &mut self,
779 tree: &mut Tree,
780 layout: Layout<'_>,
781 renderer: &Renderer,
782 operation: &mut dyn widget::Operation,
783 ) {
784 self.content
785 .as_widget_mut()
786 .operate(&mut tree.children[0], layout, renderer, operation);
787 }
788
789 fn update(
790 &mut self,
791 tree: &mut Tree,
792 event: &Event,
793 layout: Layout<'_>,
794 cursor: mouse::Cursor,
795 renderer: &Renderer,
796 clipboard: &mut dyn Clipboard,
797 shell: &mut Shell<'_, Message>,
798 viewport: &Rectangle,
799 ) {
800 if !self.interactive
801 && !matches!(
802 event,
803 Event::Window(iced_widget::core::window::Event::RedrawRequested(_))
804 )
805 {
806 return;
807 }
808
809 let inverse = self.transformation(layout.bounds()).inverse();
810 let cursor = if self.interactive {
811 cursor * inverse
812 } else {
813 mouse::Cursor::Unavailable
814 };
815
816 self.content.as_widget_mut().update(
817 &mut tree.children[0],
818 event,
819 layout,
820 cursor,
821 renderer,
822 clipboard,
823 shell,
824 &(*viewport * inverse),
825 );
826 }
827
828 fn mouse_interaction(
829 &self,
830 tree: &Tree,
831 layout: Layout<'_>,
832 cursor: mouse::Cursor,
833 viewport: &Rectangle,
834 renderer: &Renderer,
835 ) -> mouse::Interaction {
836 if !self.interactive {
837 return mouse::Interaction::None;
838 }
839
840 let inverse = self.transformation(layout.bounds()).inverse();
841
842 self.content.as_widget().mouse_interaction(
843 &tree.children[0],
844 layout,
845 cursor * inverse,
846 &(*viewport * inverse),
847 renderer,
848 )
849 }
850
851 fn draw(
852 &self,
853 tree: &Tree,
854 renderer: &mut Renderer,
855 theme: &Theme,
856 style: &renderer::Style,
857 layout: Layout<'_>,
858 cursor: mouse::Cursor,
859 viewport: &Rectangle,
860 ) {
861 let Some(layer_bounds) = scaled_layer_bounds(layout.bounds(), viewport) else {
862 return;
863 };
864 let transformation = self.transformation(layout.bounds());
865 let inverse = transformation.inverse();
866 let cursor = if self.interactive {
867 cursor * inverse
868 } else {
869 mouse::Cursor::Unavailable
870 };
871
872 renderer.with_layer(layer_bounds, |renderer| {
873 renderer.with_transformation(transformation, |renderer| {
874 self.content.as_widget().draw(
875 &tree.children[0],
876 renderer,
877 theme,
878 style,
879 layout,
880 cursor,
881 &(*viewport * inverse),
882 );
883 });
884 });
885 }
886
887 fn overlay<'b>(
888 &'b mut self,
889 tree: &'b mut Tree,
890 layout: Layout<'b>,
891 renderer: &Renderer,
892 viewport: &Rectangle,
893 translation: Vector,
894 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
895 if !self.interactive {
896 return None;
897 }
898
899 let transformation = self.transformation(layout.bounds());
900 let inverse = transformation.inverse();
901
902 self.content.as_widget_mut().overlay(
903 &mut tree.children[0],
904 layout,
905 renderer,
906 &(*viewport * inverse),
907 translation + transformation.translation(),
908 )
909 }
910}
911
912fn scaled_layer_bounds(bounds: Rectangle, viewport: &Rectangle) -> Option<Rectangle> {
913 bounds.intersection(viewport).map(|_| *viewport)
914}
915
916fn android_decelerate(progress: f32, factor: f32) -> f32 {
917 let progress = progress.clamp(0.0, 1.0);
918
919 if (factor - 1.0).abs() <= f32::EPSILON {
921 1.0 - (1.0 - progress) * (1.0 - progress)
922 } else {
923 1.0 - (1.0 - progress).powf(2.0 * factor)
924 }
925}
926
927#[cfg(test)]
928#[path = "../../../tests/widget/component/dialog.rs"]
929mod tests;