1use crate::{
2 ui::{Bounds, Color, Inset, PaintTransform, Spacing},
3 wayland::FrameAction,
4};
5
6#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
7pub enum Easing {
8 #[default]
9 Default,
10 Linear,
11 EaseIn,
12 EaseOut,
13 EaseInOut,
14}
15
16impl Easing {
17 pub fn apply(self, progress: f32) -> f32 {
18 let progress = progress.clamp(0.0, 1.0);
19 match self {
20 Self::Default => 1.0 - (1.0 - progress) * (1.0 - progress),
21 Self::Linear => progress,
22 Self::EaseIn => progress * progress,
23 Self::EaseOut => 1.0 - (1.0 - progress) * (1.0 - progress),
24 Self::EaseInOut if progress < 0.5 => 2.0 * progress * progress,
25 Self::EaseInOut => 1.0 - (-2.0 * progress + 2.0).powi(2) / 2.0,
26 }
27 }
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub struct Animation {
32 pub duration_ms: u32,
33 pub easing: Easing,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub struct AnimationFrame {
38 pub elapsed_ms: u32,
39 pub progress: f32,
40 pub complete: bool,
41}
42
43#[derive(Clone, Copy, Debug, PartialEq)]
44pub struct VisualTransition {
45 pub animation: Animation,
46 pub from_opacity: f32,
47 pub to_opacity: f32,
48 pub from_scale: f32,
49 pub to_scale: f32,
50 pub from_translate_x: i32,
51 pub to_translate_x: i32,
52 pub from_translate_y: i32,
53 pub to_translate_y: i32,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq)]
57pub struct VisualTransitionFrame {
58 pub animation: AnimationFrame,
59 pub opacity: f32,
60 pub transform: PaintTransform,
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
64pub struct Offset {
65 pub x: i32,
66 pub y: i32,
67}
68
69impl Offset {
70 pub const ZERO: Self = Self { x: 0, y: 0 };
71
72 pub const fn new(x: i32, y: i32) -> Self {
73 Self { x, y }
74 }
75}
76
77#[derive(Clone, Copy, Debug, PartialEq)]
78pub enum VisualEffect {
79 Fade {
80 from_opacity: f32,
81 to_opacity: f32,
82 },
83 Scale {
84 from_scale: f32,
85 to_scale: f32,
86 },
87 Slide {
88 from: Offset,
89 to: Offset,
90 },
91 FadeSlide {
92 from_opacity: f32,
93 to_opacity: f32,
94 from: Offset,
95 to: Offset,
96 },
97 FadeScale {
98 from_opacity: f32,
99 to_opacity: f32,
100 from_scale: f32,
101 to_scale: f32,
102 },
103}
104
105#[derive(Clone, Copy, Debug, PartialEq, Eq)]
106pub enum Edge {
107 Top,
108 Bottom,
109 Left,
110 Right,
111}
112
113#[derive(Clone, Copy, Debug, PartialEq)]
114pub struct BoundsTransition {
115 pub animation: Animation,
116 pub from: Bounds,
117 pub to: Bounds,
118}
119
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct BoundsTransitionFrame {
122 pub animation: AnimationFrame,
123 pub bounds: Bounds,
124}
125
126impl AnimationFrame {
127 pub fn should_animate(self) -> bool {
128 !self.complete
129 }
130
131 pub fn frame_action(self) -> FrameAction {
132 if self.should_animate() {
133 FrameAction::Animate
134 } else {
135 FrameAction::Wait
136 }
137 }
138}
139
140impl Animation {
141 pub const fn new(duration_ms: u32, easing: Easing) -> Self {
142 Self {
143 duration_ms,
144 easing,
145 }
146 }
147
148 pub fn progress(self, elapsed_ms: u32) -> f32 {
149 if self.duration_ms == 0 {
150 return 1.0;
151 }
152
153 self.easing
154 .apply(elapsed_ms as f32 / self.duration_ms as f32)
155 }
156
157 pub fn is_complete(self, elapsed_ms: u32) -> bool {
158 elapsed_ms >= self.duration_ms
159 }
160
161 pub fn frame(self, elapsed_ms: u32) -> AnimationFrame {
162 AnimationFrame {
163 elapsed_ms,
164 progress: self.progress(elapsed_ms),
165 complete: self.is_complete(elapsed_ms),
166 }
167 }
168}
169
170impl Default for Animation {
171 fn default() -> Self {
172 Self::new(160, Easing::Default)
173 }
174}
175
176impl VisualTransition {
177 pub const fn new(animation: Animation) -> Self {
178 Self {
179 animation,
180 from_opacity: 1.0,
181 to_opacity: 1.0,
182 from_scale: 1.0,
183 to_scale: 1.0,
184 from_translate_x: 0,
185 to_translate_x: 0,
186 from_translate_y: 0,
187 to_translate_y: 0,
188 }
189 }
190
191 pub const fn fade(animation: Animation, from_opacity: f32, to_opacity: f32) -> Self {
192 Self {
193 from_opacity,
194 to_opacity,
195 ..Self::new(animation)
196 }
197 }
198
199 pub const fn fade_scale(
200 animation: Animation,
201 from_opacity: f32,
202 to_opacity: f32,
203 from_scale: f32,
204 to_scale: f32,
205 ) -> Self {
206 Self {
207 from_opacity,
208 to_opacity,
209 from_scale,
210 to_scale,
211 ..Self::new(animation)
212 }
213 }
214
215 pub const fn fade_slide(
216 animation: Animation,
217 from_opacity: f32,
218 to_opacity: f32,
219 from: Offset,
220 to: Offset,
221 ) -> Self {
222 Self {
223 from_opacity,
224 to_opacity,
225 from_translate_x: from.x,
226 to_translate_x: to.x,
227 from_translate_y: from.y,
228 to_translate_y: to.y,
229 ..Self::new(animation)
230 }
231 }
232
233 pub const fn scale(animation: Animation, from_scale: f32, to_scale: f32) -> Self {
234 Self {
235 from_scale,
236 to_scale,
237 ..Self::new(animation)
238 }
239 }
240
241 pub const fn slide(animation: Animation, from: Offset, to: Offset) -> Self {
242 Self {
243 from_translate_x: from.x,
244 to_translate_x: to.x,
245 from_translate_y: from.y,
246 to_translate_y: to.y,
247 ..Self::new(animation)
248 }
249 }
250
251 pub const fn opacity(mut self, from: f32, to: f32) -> Self {
252 self.from_opacity = from;
253 self.to_opacity = to;
254 self
255 }
256
257 pub const fn visual_scale(mut self, from: f32, to: f32) -> Self {
258 self.from_scale = from;
259 self.to_scale = to;
260 self
261 }
262
263 pub const fn translation(mut self, from_x: i32, to_x: i32, from_y: i32, to_y: i32) -> Self {
264 self.from_translate_x = from_x;
265 self.to_translate_x = to_x;
266 self.from_translate_y = from_y;
267 self.to_translate_y = to_y;
268 self
269 }
270
271 pub fn frame(self, elapsed_ms: u32) -> VisualTransitionFrame {
272 let animation = self.animation.frame(elapsed_ms);
273 VisualTransitionFrame {
274 animation,
275 opacity: lerp_f32(self.from_opacity, self.to_opacity, animation.progress)
276 .clamp(0.0, 1.0),
277 transform: PaintTransform::new(
278 lerp_f32(self.from_scale, self.to_scale, animation.progress),
279 lerp_i32(
280 self.from_translate_x,
281 self.to_translate_x,
282 animation.progress,
283 ),
284 lerp_i32(
285 self.from_translate_y,
286 self.to_translate_y,
287 animation.progress,
288 ),
289 ),
290 }
291 }
292
293 pub fn slide_from(
294 animation: Animation,
295 edge: Edge,
296 bounds: Bounds,
297 distance: Option<u32>,
298 ) -> Self {
299 let offset = edge.offset(bounds, distance);
300 Self::fade_slide(animation, 0.0, 1.0, offset, Offset::ZERO)
301 }
302
303 pub fn slide_to(
304 animation: Animation,
305 edge: Edge,
306 bounds: Bounds,
307 distance: Option<u32>,
308 ) -> Self {
309 let offset = edge.offset(bounds, distance);
310 Self::fade_slide(animation, 1.0, 0.0, Offset::ZERO, offset)
311 }
312}
313
314impl VisualTransitionFrame {
315 pub fn frame_action(self) -> FrameAction {
316 self.animation.frame_action()
317 }
318
319 pub fn compose(self, next: Self) -> Self {
320 Self {
321 animation: AnimationFrame {
322 elapsed_ms: self.animation.elapsed_ms.max(next.animation.elapsed_ms),
323 progress: self.animation.progress.max(next.animation.progress),
324 complete: self.animation.complete && next.animation.complete,
325 },
326 opacity: (self.opacity * next.opacity).clamp(0.0, 1.0),
327 transform: self.transform.compose(next.transform),
328 }
329 }
330}
331
332impl VisualEffect {
333 pub fn transition(self, animation: Animation) -> VisualTransition {
334 match self {
335 Self::Fade {
336 from_opacity,
337 to_opacity,
338 } => VisualTransition::fade(animation, from_opacity, to_opacity),
339 Self::Scale {
340 from_scale,
341 to_scale,
342 } => VisualTransition::scale(animation, from_scale, to_scale),
343 Self::Slide { from, to } => VisualTransition::slide(animation, from, to),
344 Self::FadeSlide {
345 from_opacity,
346 to_opacity,
347 from,
348 to,
349 } => VisualTransition::fade_slide(animation, from_opacity, to_opacity, from, to),
350 Self::FadeScale {
351 from_opacity,
352 to_opacity,
353 from_scale,
354 to_scale,
355 } => VisualTransition::fade_scale(
356 animation,
357 from_opacity,
358 to_opacity,
359 from_scale,
360 to_scale,
361 ),
362 }
363 }
364}
365
366impl Edge {
367 pub fn offset(self, bounds: Bounds, distance: Option<u32>) -> Offset {
368 let distance = distance.unwrap_or_else(|| match self {
369 Self::Top | Self::Bottom => bounds.height,
370 Self::Left | Self::Right => bounds.width,
371 });
372 let distance = distance.min(i32::MAX as u32) as i32;
373 match self {
374 Self::Top => Offset::new(0, -distance),
375 Self::Bottom => Offset::new(0, distance),
376 Self::Left => Offset::new(-distance, 0),
377 Self::Right => Offset::new(distance, 0),
378 }
379 }
380}
381
382impl BoundsTransition {
383 pub const fn new(animation: Animation, from: Bounds, to: Bounds) -> Self {
384 Self {
385 animation,
386 from,
387 to,
388 }
389 }
390
391 pub fn frame(self, elapsed_ms: u32) -> BoundsTransitionFrame {
392 let animation = self.animation.frame(elapsed_ms);
393 BoundsTransitionFrame {
394 animation,
395 bounds: lerp_bounds(self.from, self.to, animation.progress),
396 }
397 }
398}
399
400impl BoundsTransitionFrame {
401 pub fn frame_action(self) -> FrameAction {
402 self.animation.frame_action()
403 }
404}
405
406pub fn lerp_f32(from: f32, to: f32, progress: f32) -> f32 {
407 from + (to - from) * progress.clamp(0.0, 1.0)
408}
409
410pub fn lerp_u32(from: u32, to: u32, progress: f32) -> u32 {
411 lerp_f32(from as f32, to as f32, progress).round() as u32
412}
413
414pub fn lerp_i32(from: i32, to: i32, progress: f32) -> i32 {
415 lerp_f32(from as f32, to as f32, progress).round() as i32
416}
417
418pub fn lerp_color(from: Color, to: Color, progress: f32) -> Color {
419 Color::rgba(
420 lerp_u8(from.red, to.red, progress),
421 lerp_u8(from.green, to.green, progress),
422 lerp_u8(from.blue, to.blue, progress),
423 lerp_u8(from.alpha, to.alpha, progress),
424 )
425}
426
427pub fn lerp_bounds(from: Bounds, to: Bounds, progress: f32) -> Bounds {
428 Bounds {
429 x: lerp_u32(from.x, to.x, progress),
430 y: lerp_u32(from.y, to.y, progress),
431 width: lerp_u32(from.width, to.width, progress),
432 height: lerp_u32(from.height, to.height, progress),
433 }
434}
435
436pub fn lerp_spacing(from: Spacing, to: Spacing, progress: f32) -> Spacing {
437 Spacing {
438 top: lerp_u32(from.top, to.top, progress),
439 right: lerp_u32(from.right, to.right, progress),
440 bottom: lerp_u32(from.bottom, to.bottom, progress),
441 left: lerp_u32(from.left, to.left, progress),
442 }
443}
444
445pub fn lerp_inset(from: Inset, to: Inset, progress: f32) -> Inset {
446 Inset {
447 top: lerp_optional_u32(from.top, to.top, progress),
448 right: lerp_optional_u32(from.right, to.right, progress),
449 bottom: lerp_optional_u32(from.bottom, to.bottom, progress),
450 left: lerp_optional_u32(from.left, to.left, progress),
451 }
452}
453
454fn lerp_u8(from: u8, to: u8, progress: f32) -> u8 {
455 lerp_f32(from as f32, to as f32, progress)
456 .round()
457 .clamp(0.0, 255.0) as u8
458}
459
460fn lerp_optional_u32(from: Option<u32>, to: Option<u32>, progress: f32) -> Option<u32> {
461 match (from, to) {
462 (Some(from), Some(to)) => Some(lerp_u32(from, to, progress)),
463 (Some(from), None) => (progress < 1.0).then_some(from),
464 (None, Some(to)) => (progress > 0.0).then_some(to),
465 (None, None) => None,
466 }
467}