material_ui_rs/
animation.rs1use 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;