1use glam::Vec2;
2use std::collections::HashMap;
3use super::graph_core::{NodeId, EdgeId};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
6pub enum AnimationKind {
7 AddNode,
8 RemoveNode,
9 AddEdge,
10 RemoveEdge,
11}
12
13#[derive(Debug, Clone)]
14pub struct NodeAnimation {
15 pub kind: AnimationKind,
16 pub node_id: NodeId,
17 pub start_pos: Vec2,
18 pub target_pos: Vec2,
19 pub progress: f32,
20 pub duration: f32,
21 pub scale: f32,
22 pub alpha: f32,
23}
24
25#[derive(Debug, Clone)]
26pub struct EdgeAnimation {
27 pub kind: AnimationKind,
28 pub edge_id: EdgeId,
29 pub from: NodeId,
30 pub to: NodeId,
31 pub progress: f32,
32 pub duration: f32,
33 pub draw_progress: f32, pub alpha: f32,
35}
36
37#[derive(Debug, Clone)]
38pub struct AnimationState {
39 pub node_positions: HashMap<NodeId, Vec2>,
40 pub node_scales: HashMap<NodeId, f32>,
41 pub node_alphas: HashMap<NodeId, f32>,
42 pub edge_draw_progress: HashMap<EdgeId, f32>,
43 pub edge_alphas: HashMap<EdgeId, f32>,
44}
45
46impl AnimationState {
47 pub fn new() -> Self {
48 Self {
49 node_positions: HashMap::new(),
50 node_scales: HashMap::new(),
51 node_alphas: HashMap::new(),
52 edge_draw_progress: HashMap::new(),
53 edge_alphas: HashMap::new(),
54 }
55 }
56}
57
58pub struct GraphAnimator {
59 node_anims: Vec<NodeAnimation>,
60 edge_anims: Vec<EdgeAnimation>,
61 default_duration: f32,
62 completed_removes: Vec<(AnimationKind, u32)>, }
64
65impl GraphAnimator {
66 pub fn new() -> Self {
67 Self {
68 node_anims: Vec::new(),
69 edge_anims: Vec::new(),
70 default_duration: 0.5,
71 completed_removes: Vec::new(),
72 }
73 }
74
75 pub fn with_duration(mut self, duration: f32) -> Self {
76 self.default_duration = duration;
77 self
78 }
79
80 pub fn animate_add_node(&mut self, id: NodeId, target_pos: Vec2) {
82 self.node_anims.push(NodeAnimation {
83 kind: AnimationKind::AddNode,
84 node_id: id,
85 start_pos: Vec2::ZERO,
86 target_pos,
87 progress: 0.0,
88 duration: self.default_duration,
89 scale: 0.0,
90 alpha: 0.0,
91 });
92 }
93
94 pub fn animate_remove_node(&mut self, id: NodeId, current_pos: Vec2) {
96 self.node_anims.push(NodeAnimation {
97 kind: AnimationKind::RemoveNode,
98 node_id: id,
99 start_pos: current_pos,
100 target_pos: current_pos,
101 progress: 0.0,
102 duration: self.default_duration,
103 scale: 1.0,
104 alpha: 1.0,
105 });
106 }
107
108 pub fn animate_add_edge(&mut self, id: EdgeId, from: NodeId, to: NodeId) {
110 self.edge_anims.push(EdgeAnimation {
111 kind: AnimationKind::AddEdge,
112 edge_id: id,
113 from,
114 to,
115 progress: 0.0,
116 duration: self.default_duration,
117 draw_progress: 0.0,
118 alpha: 1.0,
119 });
120 }
121
122 pub fn animate_remove_edge(&mut self, id: EdgeId, from: NodeId, to: NodeId) {
124 self.edge_anims.push(EdgeAnimation {
125 kind: AnimationKind::RemoveEdge,
126 edge_id: id,
127 from,
128 to,
129 progress: 0.0,
130 duration: self.default_duration,
131 draw_progress: 1.0,
132 alpha: 1.0,
133 });
134 }
135
136 pub fn tick(&mut self, dt: f32) -> AnimationState {
138 let mut state = AnimationState::new();
139 self.completed_removes.clear();
140
141 for anim in &mut self.node_anims {
143 anim.progress = (anim.progress + dt / anim.duration).min(1.0);
144 let t = ease_out_cubic(anim.progress);
145
146 match anim.kind {
147 AnimationKind::AddNode => {
148 let pos = anim.start_pos.lerp(anim.target_pos, t);
149 anim.scale = t;
150 anim.alpha = t;
151 state.node_positions.insert(anim.node_id, pos);
152 state.node_scales.insert(anim.node_id, anim.scale);
153 state.node_alphas.insert(anim.node_id, anim.alpha);
154 }
155 AnimationKind::RemoveNode => {
156 anim.scale = 1.0 - t;
157 anim.alpha = 1.0 - t;
158 state.node_positions.insert(anim.node_id, anim.start_pos);
159 state.node_scales.insert(anim.node_id, anim.scale);
160 state.node_alphas.insert(anim.node_id, anim.alpha);
161 }
162 _ => {}
163 }
164 }
165
166 for anim in &mut self.edge_anims {
168 anim.progress = (anim.progress + dt / anim.duration).min(1.0);
169 let t = ease_out_cubic(anim.progress);
170
171 match anim.kind {
172 AnimationKind::AddEdge => {
173 anim.draw_progress = t;
174 anim.alpha = 1.0;
175 state.edge_draw_progress.insert(anim.edge_id, anim.draw_progress);
176 state.edge_alphas.insert(anim.edge_id, anim.alpha);
177 }
178 AnimationKind::RemoveEdge => {
179 anim.alpha = 1.0 - t;
180 state.edge_draw_progress.insert(anim.edge_id, 1.0);
181 state.edge_alphas.insert(anim.edge_id, anim.alpha);
182 }
183 _ => {}
184 }
185 }
186
187 self.node_anims.retain(|a| a.progress < 1.0);
189 self.edge_anims.retain(|a| a.progress < 1.0);
190
191 state
192 }
193
194 pub fn is_animating(&self) -> bool {
196 !self.node_anims.is_empty() || !self.edge_anims.is_empty()
197 }
198
199 pub fn active_count(&self) -> usize {
201 self.node_anims.len() + self.edge_anims.len()
202 }
203}
204
205fn ease_out_cubic(t: f32) -> f32 {
207 let t1 = t - 1.0;
208 t1 * t1 * t1 + 1.0
209}
210
211fn ease_in_out_cubic(t: f32) -> f32 {
213 if t < 0.5 {
214 4.0 * t * t * t
215 } else {
216 let t1 = -2.0 * t + 2.0;
217 1.0 - t1 * t1 * t1 / 2.0
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_add_node_animation() {
227 let mut animator = GraphAnimator::new().with_duration(1.0);
228 let nid = NodeId(0);
229 animator.animate_add_node(nid, Vec2::new(100.0, 200.0));
230
231 assert!(animator.is_animating());
232
233 let state = animator.tick(0.5);
235 let pos = state.node_positions[&nid];
236 let scale = state.node_scales[&nid];
237 assert!(pos.x > 0.0 && pos.x < 100.0);
238 assert!(scale > 0.0 && scale < 1.0);
239
240 let state = animator.tick(0.6);
242 assert!(!animator.is_animating());
243 }
244
245 #[test]
246 fn test_remove_node_animation() {
247 let mut animator = GraphAnimator::new().with_duration(1.0);
248 let nid = NodeId(0);
249 animator.animate_remove_node(nid, Vec2::new(50.0, 50.0));
250
251 let state = animator.tick(0.5);
252 let alpha = state.node_alphas[&nid];
253 assert!(alpha > 0.0 && alpha < 1.0);
254
255 let state = animator.tick(0.6);
256 assert!(!animator.is_animating());
257 }
258
259 #[test]
260 fn test_add_edge_animation() {
261 let mut animator = GraphAnimator::new().with_duration(1.0);
262 let eid = EdgeId(0);
263 animator.animate_add_edge(eid, NodeId(0), NodeId(1));
264
265 let state = animator.tick(0.5);
266 let draw = state.edge_draw_progress[&eid];
267 assert!(draw > 0.0 && draw < 1.0);
268 }
269
270 #[test]
271 fn test_remove_edge_animation() {
272 let mut animator = GraphAnimator::new().with_duration(1.0);
273 let eid = EdgeId(0);
274 animator.animate_remove_edge(eid, NodeId(0), NodeId(1));
275
276 let state = animator.tick(0.5);
277 let alpha = state.edge_alphas[&eid];
278 assert!(alpha > 0.0 && alpha < 1.0);
279 }
280
281 #[test]
282 fn test_multiple_animations() {
283 let mut animator = GraphAnimator::new().with_duration(0.5);
284 animator.animate_add_node(NodeId(0), Vec2::new(10.0, 10.0));
285 animator.animate_add_node(NodeId(1), Vec2::new(20.0, 20.0));
286 animator.animate_add_edge(EdgeId(0), NodeId(0), NodeId(1));
287
288 assert_eq!(animator.active_count(), 3);
289 let state = animator.tick(0.25);
290 assert_eq!(state.node_positions.len(), 2);
291
292 let state = animator.tick(0.3);
293 assert!(!animator.is_animating());
294 }
295
296 #[test]
297 fn test_ease_out_cubic() {
298 assert!((ease_out_cubic(0.0) - 0.0).abs() < 1e-6);
299 assert!((ease_out_cubic(1.0) - 1.0).abs() < 1e-6);
300 assert!(ease_out_cubic(0.5) > ease_out_cubic(0.25));
302 }
303
304 #[test]
305 fn test_no_animations() {
306 let mut animator = GraphAnimator::new();
307 assert!(!animator.is_animating());
308 let state = animator.tick(0.1);
309 assert!(state.node_positions.is_empty());
310 }
311}