Skip to main content

simple_render/
animation.rs

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}