embedded_charts/
animation.rs

1//! Simplified animation system with external timeline control.
2//!
3//! This module provides a streamlined animation system based on a 0-100 progress model
4//! where applications control the timeline externally. The design eliminates complex
5//! internal state management in favor of stateless, on-demand interpolation.
6
7use crate::data::DataSeries;
8use crate::error::ChartResult;
9use crate::time::{Milliseconds, TimeProvider};
10
11/// Animation progress value (0-100).
12///
13/// This represents the completion percentage of an animation, where:
14/// - 0 = animation start
15/// - 100 = animation complete
16/// - Values in between represent partial completion
17pub type Progress = u8;
18
19/// Animation easing functions for smooth transitions.
20#[derive(Debug, Clone, Copy, PartialEq)]
21pub enum EasingFunction {
22    /// Linear interpolation (no easing).
23    Linear,
24    /// Ease in (slow start).
25    EaseIn,
26    /// Ease out (slow end).
27    EaseOut,
28    /// Ease in-out (slow start and end).
29    EaseInOut,
30}
31
32impl EasingFunction {
33    /// Apply the easing function to a linear progress value (0.0 to 1.0).
34    pub fn apply(self, t: f32) -> f32 {
35        match self {
36            EasingFunction::Linear => t,
37            EasingFunction::EaseIn => t * t,
38            EasingFunction::EaseOut => 1.0 - (1.0 - t) * (1.0 - t),
39            EasingFunction::EaseInOut => {
40                if t < 0.5 {
41                    2.0 * t * t
42                } else {
43                    1.0 - 2.0 * (1.0 - t) * (1.0 - t)
44                }
45            }
46        }
47    }
48}
49
50/// Trait for types that can be interpolated between two values.
51pub trait Interpolatable: Clone {
52    /// Interpolate between two values with the given progress (0.0 to 1.0).
53    fn interpolate(self, other: Self, progress: f32) -> Option<Self>;
54}
55
56impl Interpolatable for f32 {
57    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
58        Some(self + (other - self) * progress)
59    }
60}
61
62impl Interpolatable for i32 {
63    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
64        Some(self + ((other - self) as f32 * progress) as i32)
65    }
66}
67
68impl Interpolatable for crate::data::point::Point2D {
69    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
70        Some(crate::data::point::Point2D::new(
71            self.x + (other.x - self.x) * progress,
72            self.y + (other.y - self.y) * progress,
73        ))
74    }
75}
76
77impl Interpolatable for crate::data::point::IntPoint {
78    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
79        Some(crate::data::point::IntPoint::new(
80            self.x + ((other.x - self.x) as f32 * progress) as i32,
81            self.y + ((other.y - self.y) as f32 * progress) as i32,
82        ))
83    }
84}
85
86impl Interpolatable for crate::data::series::StaticDataSeries<crate::data::point::Point2D, 256> {
87    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
88        let mut result = crate::data::series::StaticDataSeries::new();
89
90        // Handle different data sizes by taking the minimum
91        let min_len = self.len().min(other.len());
92
93        for i in 0..min_len {
94            if let (Some(from_point), Some(to_point)) = (self.get(i), other.get(i)) {
95                if let Some(interpolated_point) = from_point.interpolate(to_point, progress) {
96                    if result.push(interpolated_point).is_err() {
97                        return None; // Buffer full
98                    }
99                }
100            }
101        }
102
103        Some(result)
104    }
105}
106
107impl<const N: usize> Interpolatable for heapless::Vec<f32, N> {
108    fn interpolate(self, other: Self, progress: f32) -> Option<Self> {
109        let mut result = heapless::Vec::new();
110
111        // Handle different vector sizes by taking the minimum
112        let min_len = self.len().min(other.len());
113
114        for i in 0..min_len {
115            if let (Some(from_val), Some(to_val)) = (self.get(i), other.get(i)) {
116                let interpolated = from_val + (to_val - from_val) * progress;
117                if result.push(interpolated).is_err() {
118                    return None; // Buffer full
119                }
120            }
121        }
122
123        Some(result)
124    }
125}
126
127/// Simple two-state animator for basic chart transitions.
128///
129/// This animator handles transitions between two states (from and to) using
130/// external progress control. It's stateless and performs interpolation on-demand.
131#[derive(Debug, Clone)]
132pub struct ChartAnimator<T: Interpolatable> {
133    /// Starting state.
134    from_state: T,
135    /// Target state.
136    to_state: T,
137    /// Easing function to apply.
138    easing: EasingFunction,
139}
140
141impl<T: Interpolatable> ChartAnimator<T> {
142    /// Create a new chart animator.
143    ///
144    /// # Arguments
145    /// * `from` - Starting state
146    /// * `to` - Target state
147    /// * `easing` - Easing function to apply
148    pub fn new(from: T, to: T, easing: EasingFunction) -> Self {
149        Self {
150            from_state: from,
151            to_state: to,
152            easing,
153        }
154    }
155
156    /// Get the interpolated value at the given progress.
157    ///
158    /// # Arguments
159    /// * `progress` - Animation progress (0-100)
160    ///
161    /// # Returns
162    /// The interpolated value, or None if interpolation fails
163    pub fn value_at(&self, progress: Progress) -> Option<T> {
164        let linear_progress = (progress as f32) / 100.0;
165        let eased_progress = self.easing.apply(linear_progress);
166        self.from_state
167            .clone()
168            .interpolate(self.to_state.clone(), eased_progress)
169    }
170
171    /// Update the target state for a new transition.
172    ///
173    /// # Arguments
174    /// * `new_to` - New target state
175    pub fn set_target(&mut self, new_to: T) {
176        self.to_state = new_to;
177    }
178
179    /// Update both states for a new transition.
180    ///
181    /// # Arguments
182    /// * `new_from` - New starting state
183    /// * `new_to` - New target state
184    pub fn set_states(&mut self, new_from: T, new_to: T) {
185        self.from_state = new_from;
186        self.to_state = new_to;
187    }
188
189    /// Get the starting state.
190    pub fn from_state(&self) -> T {
191        self.from_state.clone()
192    }
193
194    /// Get the target state.
195    pub fn to_state(&self) -> T {
196        self.to_state.clone()
197    }
198
199    /// Get the easing function.
200    pub fn easing(&self) -> EasingFunction {
201        self.easing
202    }
203
204    /// Set the easing function.
205    pub fn set_easing(&mut self, easing: EasingFunction) {
206        self.easing = easing;
207    }
208}
209
210/// Multi-state animator for keyframe-based animations.
211///
212/// This animator supports multiple keyframes with different easing functions
213/// between each pair. It automatically determines which keyframe pair to
214/// interpolate between based on the current progress.
215#[derive(Debug, Clone)]
216pub struct MultiStateAnimator<T: Interpolatable, const N: usize> {
217    /// Keyframe states and their progress positions.
218    keyframes: heapless::Vec<(Progress, T), N>,
219    /// Easing functions between keyframes.
220    easing_functions: heapless::Vec<EasingFunction, N>,
221}
222
223impl<T: Interpolatable, const N: usize> MultiStateAnimator<T, N> {
224    /// Create a new multi-state animator.
225    pub fn new() -> Self {
226        Self {
227            keyframes: heapless::Vec::new(),
228            easing_functions: heapless::Vec::new(),
229        }
230    }
231
232    /// Add a keyframe at the specified progress.
233    ///
234    /// # Arguments
235    /// * `progress` - Progress position (0-100)
236    /// * `state` - State at this keyframe
237    /// * `easing` - Easing function to use when transitioning TO this keyframe
238    ///
239    /// # Returns
240    /// Ok(()) on success, Err if the animator is full
241    pub fn add_keyframe(
242        &mut self,
243        progress: Progress,
244        state: T,
245        easing: EasingFunction,
246    ) -> ChartResult<()> {
247        self.keyframes.push((progress, state)).map_err(|_| {
248            crate::error::ChartError::DataError(crate::error::DataError::BUFFER_FULL)
249        })?;
250
251        self.easing_functions.push(easing).map_err(|_| {
252            crate::error::ChartError::DataError(crate::error::DataError::BUFFER_FULL)
253        })?;
254
255        // Sort keyframes by progress
256        self.keyframes.sort_by_key(|(progress, _)| *progress);
257
258        Ok(())
259    }
260
261    /// Get the interpolated value at the given progress.
262    ///
263    /// # Arguments
264    /// * `progress` - Animation progress (0-100)
265    ///
266    /// # Returns
267    /// The interpolated value, or None if no keyframes are set
268    pub fn value_at(&self, progress: Progress) -> Option<T> {
269        if self.keyframes.is_empty() {
270            return None;
271        }
272
273        if self.keyframes.len() == 1 {
274            return Some(self.keyframes[0].1.clone());
275        }
276
277        // Find the keyframe pair to interpolate between
278        let mut from_idx = 0;
279        let mut to_idx = 0;
280
281        for (i, (keyframe_progress, _)) in self.keyframes.iter().enumerate() {
282            if *keyframe_progress <= progress {
283                from_idx = i;
284            } else {
285                to_idx = i;
286                break;
287            }
288        }
289
290        // If we're past the last keyframe, return the last state
291        if from_idx == self.keyframes.len() - 1 {
292            return Some(self.keyframes[from_idx].1.clone());
293        }
294
295        // If we haven't found a 'to' keyframe, use the last one
296        if to_idx == 0 && from_idx > 0 {
297            to_idx = self.keyframes.len() - 1;
298        }
299
300        let (from_progress, from_state) = &self.keyframes[from_idx];
301        let (to_progress, to_state) = &self.keyframes[to_idx];
302
303        // Calculate local progress between the two keyframes
304        let progress_range = to_progress.saturating_sub(*from_progress);
305        if progress_range == 0 {
306            return Some(from_state.clone());
307        }
308
309        let local_progress =
310            (progress.saturating_sub(*from_progress) as f32) / (progress_range as f32);
311        let easing = self
312            .easing_functions
313            .get(to_idx)
314            .copied()
315            .unwrap_or(EasingFunction::Linear);
316        let eased_progress = easing.apply(local_progress);
317
318        from_state
319            .clone()
320            .interpolate(to_state.clone(), eased_progress)
321    }
322
323    /// Get the number of keyframes.
324    pub fn keyframe_count(&self) -> usize {
325        self.keyframes.len()
326    }
327
328    /// Clear all keyframes.
329    pub fn clear(&mut self) {
330        self.keyframes.clear();
331        self.easing_functions.clear();
332    }
333}
334
335impl<T: Interpolatable, const N: usize> Default for MultiStateAnimator<T, N> {
336    fn default() -> Self {
337        Self::new()
338    }
339}
340
341/// Streaming animator for continuous data updates.
342///
343/// This animator manages a sliding window of data points and provides
344/// smooth interpolation for real-time data visualization. It's designed
345/// for scenarios where new data arrives continuously.
346#[derive(Debug)]
347pub struct StreamingAnimator<T: Copy + Clone> {
348    /// Sliding window buffer for data points.
349    buffer: crate::memory::ManagedSlidingWindow<T, 100>,
350    /// Interpolation progress for smooth transitions.
351    interpolation_progress: Progress,
352    /// Whether to enable smooth interpolation between updates.
353    smooth_interpolation: bool,
354}
355
356impl<T: Copy + Clone> StreamingAnimator<T> {
357    /// Create a new streaming animator.
358    pub fn new() -> Self {
359        Self {
360            buffer: crate::memory::ManagedSlidingWindow::new(),
361            interpolation_progress: 0,
362            smooth_interpolation: true,
363        }
364    }
365
366    /// Add a new data point to the stream.
367    ///
368    /// # Arguments
369    /// * `point` - The new data point to add
370    pub fn push_data(&mut self, point: T) {
371        self.buffer.push(point);
372        // Reset interpolation progress when new data arrives
373        self.interpolation_progress = 0;
374    }
375
376    /// Get the current data window.
377    pub fn current_data(&self) -> impl Iterator<Item = T> + '_ {
378        self.buffer.iter()
379    }
380
381    /// Set the interpolation progress for smooth transitions.
382    ///
383    /// # Arguments
384    /// * `progress` - Interpolation progress (0-100)
385    pub fn set_interpolation_progress(&mut self, progress: Progress) {
386        self.interpolation_progress = progress;
387    }
388
389    /// Get the current interpolation progress.
390    pub fn interpolation_progress(&self) -> Progress {
391        self.interpolation_progress
392    }
393
394    /// Enable or disable smooth interpolation.
395    ///
396    /// # Arguments
397    /// * `enabled` - Whether to enable smooth interpolation
398    pub fn set_smooth_interpolation(&mut self, enabled: bool) {
399        self.smooth_interpolation = enabled;
400    }
401
402    /// Check if smooth interpolation is enabled.
403    pub fn is_smooth_interpolation_enabled(&self) -> bool {
404        self.smooth_interpolation
405    }
406
407    /// Get the buffer capacity.
408    pub fn capacity(&self) -> usize {
409        100 // Fixed capacity for the sliding window
410    }
411
412    /// Get the current buffer size.
413    pub fn len(&self) -> usize {
414        self.buffer.len()
415    }
416
417    /// Check if the buffer is empty.
418    pub fn is_empty(&self) -> bool {
419        self.buffer.is_empty()
420    }
421
422    /// Clear all data from the buffer.
423    pub fn clear(&mut self) {
424        self.buffer.clear();
425        self.interpolation_progress = 0;
426    }
427
428    /// Update animation with delta time (compatibility method).
429    ///
430    /// # Arguments
431    /// * `_delta_time` - Time elapsed since last update (currently unused)
432    ///
433    /// # Returns
434    /// Always returns Ok(false) as streaming animations don't have completion state
435    pub fn update_with_delta(
436        &mut self,
437        _delta_time: Milliseconds,
438    ) -> crate::error::AnimationResult<bool> {
439        // For streaming animations, we don't need to track time-based updates
440        // The animation state is controlled externally via set_interpolation_progress
441        Ok(false)
442    }
443}
444
445impl<T: Copy + Clone> Default for StreamingAnimator<T> {
446    fn default() -> Self {
447        Self::new()
448    }
449}
450
451/// Time-based progress calculator for converting time to progress values.
452///
453/// This helper struct provides utilities for calculating progress based on
454/// elapsed time, making it easier to integrate with time-based animations
455/// while maintaining the external timeline control design.
456#[derive(Debug, Clone)]
457pub struct TimeBasedProgress {
458    /// Animation duration in milliseconds.
459    duration_ms: Milliseconds,
460    /// Start time in milliseconds.
461    start_time_ms: Option<Milliseconds>,
462    /// Whether the animation should loop.
463    looping: bool,
464}
465
466impl TimeBasedProgress {
467    /// Create a new time-based progress calculator.
468    ///
469    /// # Arguments
470    /// * `duration_ms` - Animation duration in milliseconds
471    pub fn new(duration_ms: Milliseconds) -> Self {
472        Self {
473            duration_ms,
474            start_time_ms: None,
475            looping: false,
476        }
477    }
478
479    /// Create a new looping time-based progress calculator.
480    ///
481    /// # Arguments
482    /// * `duration_ms` - Animation duration in milliseconds
483    pub fn new_looping(duration_ms: Milliseconds) -> Self {
484        Self {
485            duration_ms,
486            start_time_ms: None,
487            looping: true,
488        }
489    }
490
491    /// Calculate progress based on the current time from a time provider.
492    ///
493    /// # Arguments
494    /// * `time_provider` - Time provider to get current time
495    ///
496    /// # Returns
497    /// Current progress (0-100), or 100 if animation is complete (non-looping)
498    pub fn progress_from_time<T: TimeProvider>(&mut self, time_provider: &T) -> Progress {
499        let current_time = time_provider.current_time_ms();
500
501        // Initialize start time on first call
502        if self.start_time_ms.is_none() {
503            self.start_time_ms = Some(current_time);
504            return 0;
505        }
506
507        let start_time = self.start_time_ms.unwrap();
508        let elapsed = current_time.saturating_sub(start_time);
509
510        if self.looping {
511            // For looping animations, wrap around
512            let cycle_progress = elapsed % self.duration_ms;
513            ((cycle_progress as f32 / self.duration_ms as f32) * 100.0) as Progress
514        } else {
515            // For non-looping animations, clamp to 100
516            if elapsed >= self.duration_ms {
517                100
518            } else {
519                ((elapsed as f32 / self.duration_ms as f32) * 100.0) as Progress
520            }
521        }
522    }
523
524    /// Calculate progress based on elapsed time.
525    ///
526    /// # Arguments
527    /// * `elapsed_ms` - Elapsed time in milliseconds
528    ///
529    /// # Returns
530    /// Current progress (0-100)
531    pub fn progress_from_elapsed(&self, elapsed_ms: Milliseconds) -> Progress {
532        if self.looping {
533            let cycle_progress = elapsed_ms % self.duration_ms;
534            ((cycle_progress as f32 / self.duration_ms as f32) * 100.0) as Progress
535        } else if elapsed_ms >= self.duration_ms {
536            100
537        } else {
538            ((elapsed_ms as f32 / self.duration_ms as f32) * 100.0) as Progress
539        }
540    }
541
542    /// Reset the animation to start from the current time.
543    pub fn reset(&mut self) {
544        self.start_time_ms = None;
545    }
546
547    /// Check if the animation is complete (non-looping only).
548    ///
549    /// # Arguments
550    /// * `time_provider` - Time provider to get current time
551    pub fn is_complete<T: TimeProvider>(&self, time_provider: &T) -> bool {
552        if self.looping {
553            return false; // Looping animations never complete
554        }
555
556        if let Some(start_time) = self.start_time_ms {
557            let current_time = time_provider.current_time_ms();
558            let elapsed = current_time.saturating_sub(start_time);
559            elapsed >= self.duration_ms
560        } else {
561            false // Not started yet
562        }
563    }
564
565    /// Get the animation duration.
566    pub fn duration_ms(&self) -> Milliseconds {
567        self.duration_ms
568    }
569
570    /// Set the animation duration.
571    pub fn set_duration_ms(&mut self, duration_ms: Milliseconds) {
572        self.duration_ms = duration_ms;
573    }
574
575    /// Check if the animation is set to loop.
576    pub fn is_looping(&self) -> bool {
577        self.looping
578    }
579
580    /// Set whether the animation should loop.
581    pub fn set_looping(&mut self, looping: bool) {
582        self.looping = looping;
583    }
584}
585
586#[cfg(test)]
587mod tests {
588    use super::*;
589    use crate::time::ManualTimeProvider;
590
591    #[test]
592    fn test_easing_functions() {
593        assert_eq!(EasingFunction::Linear.apply(0.5), 0.5);
594        assert_eq!(EasingFunction::EaseIn.apply(0.5), 0.25);
595        assert_eq!(EasingFunction::EaseOut.apply(0.5), 0.75);
596        assert_eq!(EasingFunction::EaseInOut.apply(0.5), 0.5);
597    }
598
599    #[test]
600    fn test_interpolatable_f32() {
601        let result = 10.0f32.interpolate(20.0, 0.5);
602        assert_eq!(result, Some(15.0));
603    }
604
605    #[test]
606    fn test_interpolatable_i32() {
607        let result = 10i32.interpolate(20, 0.5);
608        assert_eq!(result, Some(15));
609    }
610
611    #[test]
612    fn test_chart_animator() {
613        let animator = ChartAnimator::new(0.0f32, 100.0, EasingFunction::Linear);
614
615        assert_eq!(animator.value_at(0), Some(0.0));
616        assert_eq!(animator.value_at(50), Some(50.0));
617        assert_eq!(animator.value_at(100), Some(100.0));
618    }
619
620    #[test]
621    fn test_chart_animator_easing() {
622        let animator = ChartAnimator::new(0.0f32, 100.0, EasingFunction::EaseIn);
623
624        let value_at_50 = animator.value_at(50).unwrap();
625        assert!(value_at_50 < 50.0); // EaseIn should be slower at the start
626    }
627
628    #[test]
629    fn test_multi_state_animator() {
630        let mut animator: MultiStateAnimator<f32, 4> = MultiStateAnimator::new();
631
632        animator
633            .add_keyframe(0, 0.0, EasingFunction::Linear)
634            .unwrap();
635        animator
636            .add_keyframe(50, 25.0, EasingFunction::Linear)
637            .unwrap();
638        animator
639            .add_keyframe(100, 100.0, EasingFunction::Linear)
640            .unwrap();
641
642        assert_eq!(animator.value_at(0), Some(0.0));
643        assert_eq!(animator.value_at(25), Some(12.5));
644        assert_eq!(animator.value_at(50), Some(25.0));
645        assert_eq!(animator.value_at(75), Some(62.5));
646        assert_eq!(animator.value_at(100), Some(100.0));
647    }
648
649    #[test]
650    fn test_streaming_animator() {
651        let mut animator = StreamingAnimator::new();
652
653        assert!(animator.is_empty());
654
655        animator.push_data(1.0f32);
656        animator.push_data(2.0f32);
657
658        assert_eq!(animator.len(), 2);
659        assert!(!animator.is_empty());
660
661        let data: heapless::Vec<f32, 100> = animator.current_data().collect();
662        let expected: heapless::Vec<f32, 100> = heapless::Vec::from_slice(&[1.0, 2.0]).unwrap();
663        assert_eq!(data, expected);
664    }
665
666    #[test]
667    fn test_time_based_progress() {
668        let mut progress_calc = TimeBasedProgress::new(1000); // 1 second
669        let mut time_provider = ManualTimeProvider::new();
670
671        // First call should return 0 and initialize start time
672        assert_eq!(progress_calc.progress_from_time(&time_provider), 0);
673
674        // Advance time by 500ms (50% of duration)
675        time_provider.advance_ms(500);
676        assert_eq!(progress_calc.progress_from_time(&time_provider), 50);
677
678        // Advance to completion
679        time_provider.advance_ms(500);
680        assert_eq!(progress_calc.progress_from_time(&time_provider), 100);
681
682        // Beyond completion should still return 100
683        time_provider.advance_ms(500);
684        assert_eq!(progress_calc.progress_from_time(&time_provider), 100);
685    }
686
687    #[test]
688    fn test_time_based_progress_looping() {
689        let mut progress_calc = TimeBasedProgress::new_looping(1000); // 1 second loop
690        let mut time_provider = ManualTimeProvider::new();
691
692        // First call should return 0
693        assert_eq!(progress_calc.progress_from_time(&time_provider), 0);
694
695        // Complete one cycle
696        time_provider.advance_ms(1000);
697        assert_eq!(progress_calc.progress_from_time(&time_provider), 0);
698
699        // Half way through second cycle
700        time_provider.advance_ms(500);
701        assert_eq!(progress_calc.progress_from_time(&time_provider), 50);
702    }
703
704    #[test]
705    fn test_progress_from_elapsed() {
706        let progress_calc = TimeBasedProgress::new(2000); // 2 seconds
707
708        assert_eq!(progress_calc.progress_from_elapsed(0), 0);
709        assert_eq!(progress_calc.progress_from_elapsed(1000), 50);
710        assert_eq!(progress_calc.progress_from_elapsed(2000), 100);
711        assert_eq!(progress_calc.progress_from_elapsed(3000), 100); // Clamped
712    }
713}