1use crate::{
2 ui::{Color, Inset, PaintTransform, Spacing},
3 wayland::FrameAction,
4};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum Easing {
8 #[default]
9 Linear,
10 EaseIn,
11 EaseOut,
12 EaseInOut,
13}
14
15impl Easing {
16 pub fn apply(self, progress: f32) -> f32 {
17 let progress = progress.clamp(0.0, 1.0);
18 match self {
19 Self::Linear => progress,
20 Self::EaseIn => progress * progress,
21 Self::EaseOut => 1.0 - (1.0 - progress) * (1.0 - progress),
22 Self::EaseInOut if progress < 0.5 => 2.0 * progress * progress,
23 Self::EaseInOut => 1.0 - (-2.0 * progress + 2.0).powi(2) / 2.0,
24 }
25 }
26}
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
29pub struct Animation {
30 pub duration_ms: u32,
31 pub easing: Easing,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq)]
35pub struct AnimationFrame {
36 pub elapsed_ms: u32,
37 pub progress: f32,
38 pub complete: bool,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq)]
42pub struct VisualTransition {
43 pub animation: Animation,
44 pub from_opacity: f32,
45 pub to_opacity: f32,
46 pub from_scale: f32,
47 pub to_scale: f32,
48 pub from_translate_x: i32,
49 pub to_translate_x: i32,
50 pub from_translate_y: i32,
51 pub to_translate_y: i32,
52}
53
54#[derive(Clone, Copy, Debug, PartialEq)]
55pub struct VisualTransitionFrame {
56 pub animation: AnimationFrame,
57 pub opacity: f32,
58 pub transform: PaintTransform,
59}
60
61impl AnimationFrame {
62 pub fn should_animate(self) -> bool {
63 !self.complete
64 }
65
66 pub fn frame_action(self) -> FrameAction {
67 if self.should_animate() {
68 FrameAction::Animate
69 } else {
70 FrameAction::Wait
71 }
72 }
73}
74
75impl Animation {
76 pub const fn new(duration_ms: u32, easing: Easing) -> Self {
77 Self {
78 duration_ms,
79 easing,
80 }
81 }
82
83 pub fn progress(self, elapsed_ms: u32) -> f32 {
84 if self.duration_ms == 0 {
85 return 1.0;
86 }
87
88 self.easing
89 .apply(elapsed_ms as f32 / self.duration_ms as f32)
90 }
91
92 pub fn is_complete(self, elapsed_ms: u32) -> bool {
93 elapsed_ms >= self.duration_ms
94 }
95
96 pub fn frame(self, elapsed_ms: u32) -> AnimationFrame {
97 AnimationFrame {
98 elapsed_ms,
99 progress: self.progress(elapsed_ms),
100 complete: self.is_complete(elapsed_ms),
101 }
102 }
103}
104
105impl VisualTransition {
106 pub const fn new(animation: Animation) -> Self {
107 Self {
108 animation,
109 from_opacity: 1.0,
110 to_opacity: 1.0,
111 from_scale: 1.0,
112 to_scale: 1.0,
113 from_translate_x: 0,
114 to_translate_x: 0,
115 from_translate_y: 0,
116 to_translate_y: 0,
117 }
118 }
119
120 pub const fn fade(animation: Animation, from_opacity: f32, to_opacity: f32) -> Self {
121 Self {
122 from_opacity,
123 to_opacity,
124 ..Self::new(animation)
125 }
126 }
127
128 pub const fn scale(animation: Animation, from_scale: f32, to_scale: f32) -> Self {
129 Self {
130 from_scale,
131 to_scale,
132 ..Self::new(animation)
133 }
134 }
135
136 pub const fn translate(
137 animation: Animation,
138 from_translate_x: i32,
139 to_translate_x: i32,
140 from_translate_y: i32,
141 to_translate_y: i32,
142 ) -> Self {
143 Self {
144 from_translate_x,
145 to_translate_x,
146 from_translate_y,
147 to_translate_y,
148 ..Self::new(animation)
149 }
150 }
151
152 pub const fn opacity(mut self, from: f32, to: f32) -> Self {
153 self.from_opacity = from;
154 self.to_opacity = to;
155 self
156 }
157
158 pub const fn visual_scale(mut self, from: f32, to: f32) -> Self {
159 self.from_scale = from;
160 self.to_scale = to;
161 self
162 }
163
164 pub const fn translation(mut self, from_x: i32, to_x: i32, from_y: i32, to_y: i32) -> Self {
165 self.from_translate_x = from_x;
166 self.to_translate_x = to_x;
167 self.from_translate_y = from_y;
168 self.to_translate_y = to_y;
169 self
170 }
171
172 pub fn frame(self, elapsed_ms: u32) -> VisualTransitionFrame {
173 let animation = self.animation.frame(elapsed_ms);
174 VisualTransitionFrame {
175 animation,
176 opacity: lerp_f32(self.from_opacity, self.to_opacity, animation.progress)
177 .clamp(0.0, 1.0),
178 transform: PaintTransform::new(
179 lerp_f32(self.from_scale, self.to_scale, animation.progress),
180 lerp_i32(
181 self.from_translate_x,
182 self.to_translate_x,
183 animation.progress,
184 ),
185 lerp_i32(
186 self.from_translate_y,
187 self.to_translate_y,
188 animation.progress,
189 ),
190 ),
191 }
192 }
193}
194
195impl VisualTransitionFrame {
196 pub fn frame_action(self) -> FrameAction {
197 self.animation.frame_action()
198 }
199}
200
201pub fn lerp_f32(from: f32, to: f32, progress: f32) -> f32 {
202 from + (to - from) * progress.clamp(0.0, 1.0)
203}
204
205pub fn lerp_u32(from: u32, to: u32, progress: f32) -> u32 {
206 lerp_f32(from as f32, to as f32, progress).round() as u32
207}
208
209pub fn lerp_i32(from: i32, to: i32, progress: f32) -> i32 {
210 lerp_f32(from as f32, to as f32, progress).round() as i32
211}
212
213pub fn lerp_color(from: Color, to: Color, progress: f32) -> Color {
214 Color::rgba(
215 lerp_u8(from.red, to.red, progress),
216 lerp_u8(from.green, to.green, progress),
217 lerp_u8(from.blue, to.blue, progress),
218 lerp_u8(from.alpha, to.alpha, progress),
219 )
220}
221
222pub fn lerp_spacing(from: Spacing, to: Spacing, progress: f32) -> Spacing {
223 Spacing {
224 top: lerp_u32(from.top, to.top, progress),
225 right: lerp_u32(from.right, to.right, progress),
226 bottom: lerp_u32(from.bottom, to.bottom, progress),
227 left: lerp_u32(from.left, to.left, progress),
228 }
229}
230
231pub fn lerp_inset(from: Inset, to: Inset, progress: f32) -> Inset {
232 Inset {
233 top: lerp_optional_u32(from.top, to.top, progress),
234 right: lerp_optional_u32(from.right, to.right, progress),
235 bottom: lerp_optional_u32(from.bottom, to.bottom, progress),
236 left: lerp_optional_u32(from.left, to.left, progress),
237 }
238}
239
240fn lerp_u8(from: u8, to: u8, progress: f32) -> u8 {
241 lerp_f32(from as f32, to as f32, progress)
242 .round()
243 .clamp(0.0, 255.0) as u8
244}
245
246fn lerp_optional_u32(from: Option<u32>, to: Option<u32>, progress: f32) -> Option<u32> {
247 match (from, to) {
248 (Some(from), Some(to)) => Some(lerp_u32(from, to, progress)),
249 (Some(from), None) => (progress < 1.0).then_some(from),
250 (None, Some(to)) => (progress > 0.0).then_some(to),
251 (None, None) => None,
252 }
253}