Skip to main content

material_ui_rs/
animation.rs

1//! Small animation helpers for Material themed state.
2
3use iced_widget::core::time::{Duration, Instant};
4use iced_widget::core::{Point, Size};
5
6use crate::{ColorScheme, tokens};
7
8const THEME_REVEAL_DURATION_MS: u64 = 1_200;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct ColorSchemeTransition {
12    from: ColorScheme,
13    to: ColorScheme,
14    started_at: Instant,
15    duration: Duration,
16    easing: tokens::motion::CubicBezier,
17}
18
19impl ColorSchemeTransition {
20    pub fn new(
21        from: ColorScheme,
22        to: ColorScheme,
23        started_at: Instant,
24        duration: Duration,
25        easing: tokens::motion::CubicBezier,
26    ) -> Self {
27        Self {
28            from,
29            to,
30            started_at,
31            duration,
32            easing,
33        }
34    }
35
36    pub fn material_theme(from: ColorScheme, to: ColorScheme, started_at: Instant) -> Self {
37        Self::new(
38            from,
39            to,
40            started_at,
41            Duration::from_millis(u64::from(tokens::motion::DURATION_MEDIUM4_MS)),
42            tokens::motion::EASING_EMPHASIZED_DECELERATE,
43        )
44    }
45
46    pub fn value_at(self, now: Instant) -> ColorScheme {
47        ColorScheme::interpolate(self.from, self.to, self.eased_progress_at(now))
48    }
49
50    pub fn is_finished_at(self, now: Instant) -> bool {
51        self.progress_at(now) >= 1.0
52    }
53
54    pub fn progress_at(self, now: Instant) -> f32 {
55        if self.duration.is_zero() {
56            return 1.0;
57        }
58
59        (now.saturating_duration_since(self.started_at).as_secs_f32() / self.duration.as_secs_f32())
60            .clamp(0.0, 1.0)
61    }
62
63    pub fn eased_progress_at(self, now: Instant) -> f32 {
64        self.easing.transform(self.progress_at(now))
65    }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq)]
69pub struct ThemeRevealTransition {
70    color_scheme: ColorSchemeTransition,
71    origin: Point,
72}
73
74impl ThemeRevealTransition {
75    pub fn new(
76        from: ColorScheme,
77        to: ColorScheme,
78        origin: Point,
79        started_at: Instant,
80        duration: Duration,
81        easing: tokens::motion::CubicBezier,
82    ) -> Self {
83        Self {
84            color_scheme: ColorSchemeTransition::new(from, to, started_at, duration, easing),
85            origin,
86        }
87    }
88
89    pub fn material_theme(
90        from: ColorScheme,
91        to: ColorScheme,
92        origin: Point,
93        started_at: Instant,
94    ) -> Self {
95        Self::new(
96            from,
97            to,
98            origin,
99            started_at,
100            Duration::from_millis(THEME_REVEAL_DURATION_MS),
101            tokens::motion::EASING_EMPHASIZED_DECELERATE,
102        )
103    }
104
105    pub fn value_at(self, now: Instant) -> ColorScheme {
106        self.color_scheme.value_at(now)
107    }
108
109    pub fn target(self) -> ColorScheme {
110        self.color_scheme.to
111    }
112
113    pub fn origin(self) -> Point {
114        self.origin
115    }
116
117    pub fn is_finished_at(self, now: Instant) -> bool {
118        self.color_scheme.is_finished_at(now)
119    }
120
121    pub fn progress_at(self, now: Instant) -> f32 {
122        self.color_scheme.progress_at(now)
123    }
124
125    pub fn eased_progress_at(self, now: Instant) -> f32 {
126        self.color_scheme.eased_progress_at(now)
127    }
128
129    pub fn reveal_radius_at(self, viewport: Size, now: Instant) -> f32 {
130        max_radius_from_origin(self.origin, viewport) * self.eased_progress_at(now)
131    }
132}
133
134pub fn max_radius_from_origin(origin: Point, viewport: Size) -> f32 {
135    [
136        Point::ORIGIN,
137        Point::new(viewport.width, 0.0),
138        Point::new(0.0, viewport.height),
139        Point::new(viewport.width, viewport.height),
140    ]
141    .into_iter()
142    .map(|corner| distance(origin, corner))
143    .fold(0.0, f32::max)
144}
145
146fn distance(a: Point, b: Point) -> f32 {
147    let x = a.x - b.x;
148    let y = a.y - b.y;
149
150    x.hypot(y)
151}
152
153#[cfg(test)]
154#[path = "../tests/animation.rs"]
155mod tests;