Skip to main content

egui_map/map/
animation.rs

1//! Built-in animation effects, used when no custom
2//! [`NodeTemplate`](crate::map::objects::NodeTemplate) is installed.
3
4use crate::map::objects::RawPoint;
5use egui::{Color32, Painter, Shape, epaint::CircleShape};
6use std::time::Instant;
7
8/// Factory for the default node notification animation.
9pub(crate) struct Animation {}
10
11impl Animation {
12    /// Draws one frame of an expanding, fading circle centered on `center`.
13    ///
14    /// The pulse starts at `initial_time` and plays for about 3.5 seconds,
15    /// growing in radius while its transparency decreases. Returns `true`
16    /// while the animation is still playing (the caller should request a
17    /// repaint) and `false` once it has finished, so the caller can drop the
18    /// notification.
19    pub(crate) fn pulse(
20        painter: &Painter,
21        center: RawPoint,
22        zoom: f32,
23        initial_time: Instant,
24        color: Color32,
25    ) -> bool {
26        let current_instant = Instant::now();
27        let time_diff = current_instant.duration_since(initial_time);
28        let secs_played = time_diff.as_secs_f32();
29        let radius = (4.00 + (40.00 * secs_played)) * zoom;
30        let mut transparency = 1.00 - (secs_played / 3.50).abs();
31        if transparency < 0.00 {
32            transparency = 0.00;
33        }
34        let corrected_color = Color32::from_rgba_unmultiplied(
35            color.r(),
36            color.g(),
37            color.b(),
38            (255.00 * transparency).round() as u8,
39        );
40        let circle = Shape::Circle(CircleShape::filled(center.into(), radius, corrected_color));
41        painter.extend(vec![circle]);
42        secs_played < 3.50
43    }
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49    use egui::{Context, LayerId, Pos2, Rect, Vec2};
50    use std::time::Duration;
51
52    fn headless_painter() -> Painter {
53        Painter::new(
54            Context::default(),
55            LayerId::background(),
56            Rect::from_min_size(Pos2::ZERO, Vec2::new(100.0, 100.0)),
57        )
58    }
59
60    #[test]
61    fn pulse_returns_true_while_running() {
62        let painter = headless_painter();
63        let result = Animation::pulse(
64            &painter,
65            RawPoint::default(),
66            1.0,
67            Instant::now(),
68            Color32::RED,
69        );
70        assert!(result);
71    }
72
73    #[test]
74    fn pulse_returns_false_when_finished() {
75        let painter = headless_painter();
76        // la animación dura 3.5 segundos; 4 segundos después ya terminó
77        let initial_time = Instant::now() - Duration::from_secs(4);
78        let result = Animation::pulse(
79            &painter,
80            RawPoint::default(),
81            1.0,
82            initial_time,
83            Color32::RED,
84        );
85        assert!(!result);
86    }
87}