Skip to main content

simple_render/
animation.rs

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}