rlvgl_core/
animation.rs

1use crate::style::Style;
2use crate::widget::{Color, Rect};
3
4/// Simple linear fade animation for a style's background color.
5///
6/// The animation owns a mutable pointer to the [`Style`] being modified. This
7/// keeps the API lightweight for `no_std` targets at the cost of requiring
8/// unsafe access internally.
9pub struct Fade {
10    style: *mut Style,
11    start: Color,
12    end: Color,
13    duration_ms: u32,
14    elapsed: u32,
15}
16
17impl Fade {
18    /// Create a new fade animation.
19    pub fn new(style: &mut Style, start: Color, end: Color, duration_ms: u32) -> Self {
20        Self {
21            style: style as *mut Style,
22            start,
23            end,
24            duration_ms,
25            elapsed: 0,
26        }
27    }
28
29    /// Advance the animation by `delta_ms` milliseconds.
30    pub fn tick(&mut self, delta_ms: u32) {
31        self.elapsed = core::cmp::min(self.elapsed + delta_ms, self.duration_ms);
32        let progress = self.elapsed as f32 / self.duration_ms as f32;
33        let lerp = |a: u8, b: u8| a as f32 + (b as f32 - a as f32) * progress;
34        unsafe {
35            (*self.style).bg_color = Color(
36                lerp(self.start.0, self.end.0) as u8,
37                lerp(self.start.1, self.end.1) as u8,
38                lerp(self.start.2, self.end.2) as u8,
39            );
40        }
41    }
42
43    /// Returns `true` when the animation has reached its end point.
44    pub fn finished(&self) -> bool {
45        self.elapsed >= self.duration_ms
46    }
47}
48
49/// Simple linear slide animation for a [`Rect`].
50pub struct Slide {
51    rect: *mut Rect,
52    start: Rect,
53    end: Rect,
54    duration_ms: u32,
55    elapsed: u32,
56}
57
58impl Slide {
59    /// Create a new slide animation.
60    pub fn new(rect: &mut Rect, start: Rect, end: Rect, duration_ms: u32) -> Self {
61        Self {
62            rect: rect as *mut Rect,
63            start,
64            end,
65            duration_ms,
66            elapsed: 0,
67        }
68    }
69
70    /// Advance the animation by `delta_ms` milliseconds.
71    pub fn tick(&mut self, delta_ms: u32) {
72        self.elapsed = core::cmp::min(self.elapsed + delta_ms, self.duration_ms);
73        let p = self.elapsed as f32 / self.duration_ms as f32;
74        let lerp = |a: i32, b: i32| a as f32 + (b as f32 - a as f32) * p;
75        unsafe {
76            *self.rect = Rect {
77                x: lerp(self.start.x, self.end.x) as i32,
78                y: lerp(self.start.y, self.end.y) as i32,
79                width: lerp(self.start.width, self.end.width) as i32,
80                height: lerp(self.start.height, self.end.height) as i32,
81            };
82        }
83    }
84
85    /// Returns `true` once the slide has finished.
86    pub fn finished(&self) -> bool {
87        self.elapsed >= self.duration_ms
88    }
89}
90
91/// Animation timeline that updates multiple animations at once.
92pub struct Timeline {
93    fades: alloc::vec::Vec<Fade>,
94    slides: alloc::vec::Vec<Slide>,
95}
96
97impl Timeline {
98    /// Create an empty timeline.
99    pub fn new() -> Self {
100        Self {
101            fades: alloc::vec::Vec::new(),
102            slides: alloc::vec::Vec::new(),
103        }
104    }
105
106    /// Add a [`Fade`] animation to the timeline.
107    pub fn add_fade(&mut self, fade: Fade) {
108        self.fades.push(fade);
109    }
110    /// Add a [`Slide`] animation to the timeline.
111    pub fn add_slide(&mut self, slide: Slide) {
112        self.slides.push(slide);
113    }
114
115    /// Advance all animations by `delta_ms` milliseconds.
116    pub fn tick(&mut self, delta_ms: u32) {
117        for fade in &mut self.fades {
118            fade.tick(delta_ms);
119        }
120        for slide in &mut self.slides {
121            slide.tick(delta_ms);
122        }
123        self.fades.retain(|f| !f.finished());
124        self.slides.retain(|s| !s.finished());
125    }
126
127    /// Returns `true` if no animations remain in the timeline.
128    pub fn is_empty(&self) -> bool {
129        self.fades.is_empty() && self.slides.is_empty()
130    }
131}
132
133impl Default for Timeline {
134    fn default() -> Self {
135        Self::new()
136    }
137}