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
185pub fn basic<'a, Message, Renderer>(
187 content: impl Into<Element<'a, Message, Theme, Renderer>>,
188) -> Container<'a, Message, Theme, Renderer>
189where
190 Message: 'a,
191 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
192{
193 basic_alpha(content, 1.0)
194}
195
196pub fn basic_alpha<'a, Message, Renderer>(
198 content: impl Into<Element<'a, Message, Theme, Renderer>>,
199 alpha: f32,
200) -> Container<'a, Message, Theme, Renderer>
201where
202 Message: 'a,
203 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
204{
205 Container::new(content)
206 .width(Length::Fill)
207 .max_width(tokens::component::dialog::CONTAINER_MAX_WIDTH)
208 .padding(tokens::component::dialog::CONTAINER_PADDING)
209 .style(move |theme| container_style_alpha(theme, alpha))
210}
211
212pub fn alert<'a, Message, Renderer>(
214 title: impl text::IntoFragment<'a>,
215 supporting_text: impl text::IntoFragment<'a>,
216 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
217) -> Container<'a, Message, Theme, Renderer>
218where
219 Message: 'a,
220 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
221 iced_widget::core::Font: Into<Renderer::Font>,
222{
223 alert_alpha(title, supporting_text, actions, 1.0)
224}
225
226pub fn alert_alpha<'a, Message, Renderer>(
228 title: impl text::IntoFragment<'a>,
229 supporting_text: impl text::IntoFragment<'a>,
230 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
231 alpha: f32,
232) -> Container<'a, Message, Theme, Renderer>
233where
234 Message: 'a,
235 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
236 iced_widget::core::Font: Into<Renderer::Font>,
237{
238 alert_content(None, title, supporting_text, actions, alpha)
239}
240
241pub fn alert_with_icon<'a, Message, Renderer>(
243 icon: impl text::IntoFragment<'a>,
244 title: impl text::IntoFragment<'a>,
245 supporting_text: impl text::IntoFragment<'a>,
246 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
247) -> Container<'a, Message, Theme, Renderer>
248where
249 Message: 'a,
250 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
251 iced_widget::core::Font: Into<Renderer::Font>,
252{
253 alert_with_icon_alpha(icon, title, supporting_text, actions, 1.0)
254}
255
256pub fn alert_with_icon_alpha<'a, Message, Renderer>(
258 icon: impl text::IntoFragment<'a>,
259 title: impl text::IntoFragment<'a>,
260 supporting_text: impl text::IntoFragment<'a>,
261 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
262 alpha: f32,
263) -> Container<'a, Message, Theme, Renderer>
264where
265 Message: 'a,
266 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
267 iced_widget::core::Font: Into<Renderer::Font>,
268{
269 alert_content(
270 Some(icon.into_fragment()),
271 title,
272 supporting_text,
273 actions,
274 alpha,
275 )
276}
277
278pub fn actions<'a, Message, Renderer>(
280 buttons: impl IntoIterator<Item = Element<'a, Message, Theme, Renderer>>,
281) -> Container<'a, Message, Theme, Renderer>
282where
283 Message: 'a,
284 Renderer: iced_widget::core::Renderer + 'a,
285{
286 Container::new(
287 Row::with_children(buttons.into_iter())
288 .spacing(tokens::component::dialog::ACTIONS_HORIZONTAL_SPACING)
289 .align_y(alignment::Vertical::Center),
290 )
291 .width(Length::Fill)
292 .align_x(alignment::Horizontal::Right)
293}
294
295pub fn action<'a, Message, Renderer>(
297 label: impl text::IntoFragment<'a>,
298) -> super::button::Button<'a, Message, Renderer>
299where
300 Message: Clone + 'a,
301 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
302{
303 super::button::text(label)
304}
305
306pub fn action_button<'a, Message, Renderer>(
308 label: impl text::IntoFragment<'a>,
309 on_press: Message,
310) -> Element<'a, Message, Theme, Renderer>
311where
312 Message: Clone + 'a,
313 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
314{
315 action(label).on_press(on_press).into()
316}
317
318pub fn action_alpha<'a, Message, Renderer>(
320 label: impl text::IntoFragment<'a>,
321 alpha: f32,
322) -> super::button::Button<'a, Message, Renderer>
323where
324 Message: Clone + 'a,
325 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
326{
327 action(label).style(move |theme, status| action_style_alpha(theme, status, alpha))
328}
329
330pub fn action_button_alpha<'a, Message, Renderer>(
332 label: impl text::IntoFragment<'a>,
333 on_press: Message,
334 alpha: f32,
335) -> Element<'a, Message, Theme, Renderer>
336where
337 Message: Clone + 'a,
338 Renderer: geometry::Renderer + primitive::Renderer + core_text::Renderer + 'a,
339{
340 action_alpha(label, alpha).on_press(on_press).into()
341}
342
343pub fn scrim<'a, Message, Renderer>(
345 content: impl Into<Element<'a, Message, Theme, Renderer>>,
346) -> Container<'a, Message, Theme, Renderer>
347where
348 Message: 'a,
349 Renderer: iced_widget::core::Renderer + 'a,
350{
351 scrim_alpha(content, 1.0)
352}
353
354pub fn scrim_alpha<'a, Message, Renderer>(
356 content: impl Into<Element<'a, Message, Theme, Renderer>>,
357 alpha: f32,
358) -> Container<'a, Message, Theme, Renderer>
359where
360 Message: 'a,
361 Renderer: iced_widget::core::Renderer + 'a,
362{
363 Container::new(content)
364 .width(Length::Fill)
365 .height(Length::Fill)
366 .style(move |theme| scrim_style_alpha(theme, alpha))
367}
368
369pub fn modal_overlay<'a, Message, Renderer>(
371 content: impl Into<Element<'a, Message, Theme, Renderer>>,
372) -> Container<'a, Message, Theme, Renderer>
373where
374 Message: 'a,
375 Renderer: iced_widget::core::Renderer + 'a,
376{
377 scrim(
378 Container::new(content)
379 .width(Length::Fill)
380 .height(Length::Fill)
381 .align_x(alignment::Horizontal::Center)
382 .align_y(alignment::Vertical::Center),
383 )
384}
385
386pub fn modal_layer<'a, Message, Renderer>(
392 content: impl Into<Element<'a, Message, Theme, Renderer>>,
393) -> Element<'a, Message, Theme, Renderer>
394where
395 Message: 'a,
396 Renderer: iced_widget::core::Renderer + 'a,
397{
398 let scrim = opaque(scrim(Space::new().width(Length::Fill).height(Length::Fill)));
399 let dialog = opaque(Container::new(content).center(Length::Fill));
400
401 Stack::with_children([scrim, dialog])
402 .width(Length::Fill)
403 .height(Length::Fill)
404 .into()
405}
406
407pub fn modal_layer_animated<'a, Message, Renderer>(
409 content: impl Into<Element<'a, Message, Theme, Renderer>>,
410 transition: &Transition,
411 now: Instant,
412) -> Element<'a, Message, Theme, Renderer>
413where
414 Message: 'a,
415 Renderer: iced_widget::core::Renderer + 'a,
416{
417 let scrim = opaque(scrim_alpha(
418 Space::new().width(Length::Fill).height(Length::Fill),
419 transition.scrim_alpha(now),
420 ));
421 let dialog = opaque(
422 Container::new(scaled(
423 content,
424 transition.scale(now),
425 transition.phase() != TransitionPhase::Dismissing,
426 ))
427 .center(Length::Fill),
428 );
429
430 Stack::with_children([scrim, dialog])
431 .width(Length::Fill)
432 .height(Length::Fill)
433 .into()
434}
435
436pub fn modal<'a, Message, Renderer>(
438 content: impl Into<Element<'a, Message, Theme, Renderer>>,
439 dialog: impl Into<Element<'a, Message, Theme, Renderer>>,
440) -> Element<'a, Message, Theme, Renderer>
441where
442 Message: 'a,
443 Renderer: iced_widget::core::Renderer + 'a,
444{
445 Stack::with_children([content.into(), modal_layer(dialog)])
446 .width(Length::Fill)
447 .height(Length::Fill)
448 .into()
449}
450
451pub fn modal_animated<'a, Message, Renderer>(
453 content: impl Into<Element<'a, Message, Theme, Renderer>>,
454 transition: &Transition,
455 now: Instant,
456 dialog: impl Into<Element<'a, Message, Theme, Renderer>>,
457) -> Element<'a, Message, Theme, Renderer>
458where
459 Message: 'a,
460 Renderer: iced_widget::core::Renderer + 'a,
461{
462 let content = content.into();
463
464 if !transition.is_active() {
465 return content;
466 }
467
468 Stack::with_children([content, modal_layer_animated(dialog, transition, now)])
469 .width(Length::Fill)
470 .height(Length::Fill)
471 .into()
472}
473
474fn alert_content<'a, Message, Renderer>(
475 icon: Option<text::Fragment<'a>>,
476 title: impl text::IntoFragment<'a>,
477 supporting_text: impl text::IntoFragment<'a>,
478 actions: impl Into<Element<'a, Message, Theme, Renderer>>,
479 alpha: f32,
480) -> Container<'a, Message, Theme, Renderer>
481where
482 Message: 'a,
483 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
484 iced_widget::core::Font: Into<Renderer::Font>,
485{
486 let title_alignment = title_alignment(icon.is_some());
487 let mut content = Column::new().width(Length::Fill);
488
489 if let Some(icon) = icon {
490 content = content.push(
491 Container::new(icon_text(icon, alpha))
492 .width(Length::Fill)
493 .padding(Padding {
494 top: 0.0,
495 right: 0.0,
496 bottom: tokens::component::dialog::ICON_BOTTOM_PADDING,
497 left: 0.0,
498 })
499 .align_x(alignment::Horizontal::Center),
500 );
501 }
502
503 content = content.push(
504 Container::new(title_text(title, title_alignment, alpha))
505 .width(Length::Fill)
506 .padding(Padding {
507 top: 0.0,
508 right: 0.0,
509 bottom: tokens::component::dialog::TITLE_BOTTOM_PADDING,
510 left: 0.0,
511 }),
512 );
513
514 content = content.push(
515 Container::new(supporting_text_view(supporting_text, alpha))
516 .width(Length::Fill)
517 .padding(Padding {
518 top: 0.0,
519 right: 0.0,
520 bottom: tokens::component::dialog::SUPPORTING_TEXT_BOTTOM_PADDING,
521 left: 0.0,
522 }),
523 );
524
525 content = content.push(actions.into());
526
527 basic_alpha(content, alpha)
528}
529
530fn icon_text<'a, Renderer>(icon: text::Fragment<'a>, alpha: f32) -> Text<'a, Theme, Renderer>
531where
532 Renderer: core_text::Renderer + 'a,
533 iced_widget::core::Font: Into<Renderer::Font>,
534{
535 fonts::filled_icon(icon, tokens::component::dialog::ICON_SIZE)
536 .width(Length::Fixed(tokens::component::dialog::ICON_SIZE))
537 .height(Length::Fixed(tokens::component::dialog::ICON_SIZE))
538 .center()
539 .style(move |theme| icon_style_alpha(theme, alpha))
540}
541
542fn title_text<'a, Renderer>(
543 title: impl text::IntoFragment<'a>,
544 alignment: alignment::Horizontal,
545 alpha: f32,
546) -> Text<'a, Theme, Renderer>
547where
548 Renderer: core_text::Renderer + 'a,
549{
550 let scale = tokens::component::dialog::HEADLINE_TEXT;
551
552 Text::new(title)
553 .size(scale.size)
554 .line_height(absolute_line_height(scale.line_height))
555 .width(Length::Fill)
556 .align_x(alignment)
557 .color_maybe(None::<iced_widget::core::Color>)
558 .style(move |theme| title_style_alpha(theme, alpha))
559}
560
561fn title_alignment(has_icon: bool) -> alignment::Horizontal {
562 if has_icon {
563 alignment::Horizontal::Center
564 } else {
565 alignment::Horizontal::Left
566 }
567}
568
569fn supporting_text_view<'a, Renderer>(
570 supporting_text: impl text::IntoFragment<'a>,
571 alpha: f32,
572) -> Text<'a, Theme, Renderer>
573where
574 Renderer: core_text::Renderer + 'a,
575{
576 let scale = tokens::component::dialog::SUPPORTING_TEXT;
577
578 Text::new(supporting_text)
579 .size(scale.size)
580 .line_height(absolute_line_height(scale.line_height))
581 .width(Length::Fill)
582 .color_maybe(None::<iced_widget::core::Color>)
583 .style(move |theme| supporting_text_style_alpha(theme, alpha))
584}
585
586fn container_style(theme: &Theme) -> iced_widget::container::Style {
587 let colors = theme.colors();
588
589 iced_widget::container::Style {
590 background: Some(Background::Color(colors.surface.container.high)),
591 text_color: Some(colors.surface.text_variant),
592 border: border::rounded(tokens::component::dialog::CONTAINER_SHAPE),
593 shadow: shadow_from_level(
594 tokens::component::dialog::CONTAINER_ELEVATION_LEVEL,
595 colors.shadow,
596 ),
597 snap: cfg!(feature = "crisp"),
598 }
599}
600
601fn container_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::container::Style {
602 let mut style = container_style(theme);
603
604 style.background = style
605 .background
606 .map(|background| background.scale_alpha(alpha));
607 style.text_color = style.text_color.map(|color| alpha_color(color, alpha));
608 style.border.color = alpha_color(style.border.color, alpha);
609 style.shadow.color = alpha_color(style.shadow.color, alpha);
610
611 style
612}
613
614fn icon_style(theme: &Theme) -> iced_widget::text::Style {
615 iced_widget::text::Style {
616 color: Some(theme.colors().secondary.color),
617 }
618}
619
620fn icon_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
621 iced_widget::text::Style {
622 color: icon_style(theme)
623 .color
624 .map(|color| alpha_color(color, alpha)),
625 }
626}
627
628fn title_style(theme: &Theme) -> iced_widget::text::Style {
629 iced_widget::text::Style {
630 color: Some(theme.colors().surface.text),
631 }
632}
633
634fn title_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
635 iced_widget::text::Style {
636 color: title_style(theme)
637 .color
638 .map(|color| alpha_color(color, alpha)),
639 }
640}
641
642fn supporting_text_style(theme: &Theme) -> iced_widget::text::Style {
643 iced_widget::text::Style {
644 color: Some(theme.colors().surface.text_variant),
645 }
646}
647
648fn supporting_text_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::text::Style {
649 iced_widget::text::Style {
650 color: supporting_text_style(theme)
651 .color
652 .map(|color| alpha_color(color, alpha)),
653 }
654}
655
656fn action_style_alpha(theme: &Theme, status: Status, alpha: f32) -> Style {
657 let mut style = crate::style::button::text(theme, status);
658
659 style.background = style
660 .background
661 .map(|background| background.scale_alpha(alpha));
662 style.text_color = alpha_color(style.text_color, alpha);
663 style.border.color = alpha_color(style.border.color, alpha);
664 style.shadow.color = alpha_color(style.shadow.color, alpha);
665
666 style
667}
668
669fn scrim_style_alpha(theme: &Theme, alpha: f32) -> iced_widget::container::Style {
670 iced_widget::container::Style {
671 background: Some(Background::Color(alpha_color(
672 Color {
673 a: 1.0,
674 ..theme.colors().scrim
675 },
676 tokens::component::dialog::SCRIM_OPACITY * alpha,
677 ))),
678 text_color: Some(theme.colors().surface.text),
679 ..iced_widget::container::Style::default()
680 }
681}
682
683fn scaled<'a, Message, Renderer>(
684 content: impl Into<Element<'a, Message, Theme, Renderer>>,
685 scale: f32,
686 interactive: bool,
687) -> Element<'a, Message, Theme, Renderer>
688where
689 Message: 'a,
690 Renderer: iced_widget::core::Renderer + 'a,
691{
692 Element::new(Scaled {
693 content: content.into(),
694 scale,
695 interactive,
696 })
697}
698
699struct Scaled<'a, Message, Renderer> {
700 content: Element<'a, Message, Theme, Renderer>,
701 scale: f32,
702 interactive: bool,
703}
704
705struct ScaledTag;
706
707impl<Message, Renderer> std::fmt::Debug for Scaled<'_, Message, Renderer> {
708 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
709 f.debug_struct("Scaled")
710 .field("scale", &self.scale)
711 .field("interactive", &self.interactive)
712 .finish_non_exhaustive()
713 }
714}
715
716impl<Message, Renderer> Scaled<'_, Message, Renderer> {
717 fn transformation(&self, bounds: Rectangle) -> Transformation {
718 Transformation::translate(bounds.center_x(), bounds.center_y())
719 * Transformation::scale(self.scale)
720 * Transformation::translate(-bounds.center_x(), -bounds.center_y())
721 }
722}
723
724impl<Message, Renderer> Widget<Message, Theme, Renderer> for Scaled<'_, Message, Renderer>
725where
726 Renderer: iced_widget::core::Renderer,
727{
728 fn tag(&self) -> tree::Tag {
729 tree::Tag::of::<ScaledTag>()
730 }
731
732 fn state(&self) -> tree::State {
733 tree::State::None
734 }
735
736 fn children(&self) -> Vec<Tree> {
737 vec![Tree::new(&self.content)]
738 }
739
740 fn diff(&self, tree: &mut Tree) {
741 tree.diff_children(std::slice::from_ref(&self.content));
742 }
743
744 fn size(&self) -> Size<Length> {
745 self.content.as_widget().size()
746 }
747
748 fn size_hint(&self) -> Size<Length> {
749 self.content.as_widget().size_hint()
750 }
751
752 fn layout(
753 &mut self,
754 tree: &mut Tree,
755 renderer: &Renderer,
756 limits: &layout::Limits,
757 ) -> layout::Node {
758 self.content
759 .as_widget_mut()
760 .layout(&mut tree.children[0], renderer, limits)
761 }
762
763 fn operate(
764 &mut self,
765 tree: &mut Tree,
766 layout: Layout<'_>,
767 renderer: &Renderer,
768 operation: &mut dyn widget::Operation,
769 ) {
770 self.content
771 .as_widget_mut()
772 .operate(&mut tree.children[0], layout, renderer, operation);
773 }
774
775 fn update(
776 &mut self,
777 tree: &mut Tree,
778 event: &Event,
779 layout: Layout<'_>,
780 cursor: mouse::Cursor,
781 renderer: &Renderer,
782 clipboard: &mut dyn Clipboard,
783 shell: &mut Shell<'_, Message>,
784 viewport: &Rectangle,
785 ) {
786 if !self.interactive {
787 if !matches!(
788 event,
789 Event::Window(iced_widget::core::window::Event::RedrawRequested(_))
790 ) {
791 return;
792 }
793 }
794
795 let inverse = self.transformation(layout.bounds()).inverse();
796 let cursor = if self.interactive {
797 cursor * inverse
798 } else {
799 mouse::Cursor::Unavailable
800 };
801
802 self.content.as_widget_mut().update(
803 &mut tree.children[0],
804 event,
805 layout,
806 cursor,
807 renderer,
808 clipboard,
809 shell,
810 &(*viewport * inverse),
811 );
812 }
813
814 fn mouse_interaction(
815 &self,
816 tree: &Tree,
817 layout: Layout<'_>,
818 cursor: mouse::Cursor,
819 viewport: &Rectangle,
820 renderer: &Renderer,
821 ) -> mouse::Interaction {
822 if !self.interactive {
823 return mouse::Interaction::None;
824 }
825
826 let inverse = self.transformation(layout.bounds()).inverse();
827
828 self.content.as_widget().mouse_interaction(
829 &tree.children[0],
830 layout,
831 cursor * inverse,
832 &(*viewport * inverse),
833 renderer,
834 )
835 }
836
837 fn draw(
838 &self,
839 tree: &Tree,
840 renderer: &mut Renderer,
841 theme: &Theme,
842 style: &renderer::Style,
843 layout: Layout<'_>,
844 cursor: mouse::Cursor,
845 viewport: &Rectangle,
846 ) {
847 let Some(layer_bounds) = scaled_layer_bounds(layout.bounds(), viewport) else {
848 return;
849 };
850 let transformation = self.transformation(layout.bounds());
851 let inverse = transformation.inverse();
852 let cursor = if self.interactive {
853 cursor * inverse
854 } else {
855 mouse::Cursor::Unavailable
856 };
857
858 renderer.with_layer(layer_bounds, |renderer| {
859 renderer.with_transformation(transformation, |renderer| {
860 self.content.as_widget().draw(
861 &tree.children[0],
862 renderer,
863 theme,
864 style,
865 layout,
866 cursor,
867 &(*viewport * inverse),
868 );
869 });
870 });
871 }
872
873 fn overlay<'b>(
874 &'b mut self,
875 tree: &'b mut Tree,
876 layout: Layout<'b>,
877 renderer: &Renderer,
878 viewport: &Rectangle,
879 translation: Vector,
880 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
881 if !self.interactive {
882 return None;
883 }
884
885 let transformation = self.transformation(layout.bounds());
886 let inverse = transformation.inverse();
887
888 self.content.as_widget_mut().overlay(
889 &mut tree.children[0],
890 layout,
891 renderer,
892 &(*viewport * inverse),
893 translation + transformation.translation(),
894 )
895 }
896}
897
898fn scaled_layer_bounds(bounds: Rectangle, viewport: &Rectangle) -> Option<Rectangle> {
899 bounds.intersection(viewport).map(|_| *viewport)
900}
901
902fn android_decelerate(progress: f32, factor: f32) -> f32 {
903 let progress = progress.clamp(0.0, 1.0);
904
905 if (factor - 1.0).abs() <= f32::EPSILON {
907 1.0 - (1.0 - progress) * (1.0 - progress)
908 } else {
909 1.0 - (1.0 - progress).powf(2.0 * factor)
910 }
911}
912
913#[cfg(test)]
914#[path = "../../../tests/widget/component/dialog.rs"]
915mod tests;