flow_rs_core/auto_layout/
transitions.rs

1//! Layout transition management and animation
2
3use std::collections::HashMap;
4
5use crate::graph::Graph;
6use crate::types::{NodeId, Position};
7
8/// State for managing layout transitions
9#[derive(Debug)]
10pub struct TransitionState {
11    pub progress: f64,
12    pub duration: f64,
13    pub from_positions: HashMap<NodeId, Position>,
14    pub to_positions: HashMap<NodeId, Position>,
15}
16
17impl TransitionState {
18    /// Create a new transition state
19    pub fn new(
20        duration: f64,
21        from_positions: HashMap<NodeId, Position>,
22        to_positions: HashMap<NodeId, Position>,
23    ) -> Self {
24        Self {
25            progress: 0.0,
26            duration,
27            from_positions,
28            to_positions,
29        }
30    }
31}
32
33/// Helper function for smooth easing
34pub fn ease_in_out(t: f64) -> f64 {
35    if t < 0.5 {
36        2.0 * t * t
37    } else {
38        -1.0 + (4.0 - 2.0 * t) * t
39    }
40}
41
42/// Interpolate between two positions
43pub fn interpolate_position(from: Position, to: Position, t: f64) -> Position {
44    Position::new(
45        from.x + (to.x - from.x) * t,
46        from.y + (to.y - from.y) * t,
47    )
48}
49
50/// Update transition state and apply interpolated positions
51pub fn update_transition<N, E>(
52    transition_state: &mut Option<TransitionState>,
53    graph: &mut Graph<N, E>,
54    delta_time: f64,
55) -> Result<bool, crate::error::FlowError> {
56    let should_remove_state = if let Some(ref mut state) = transition_state {
57        state.progress += delta_time / state.duration;
58
59        if state.progress >= 1.0 {
60            // Transition complete - apply final positions
61            for node in graph.nodes_mut() {
62                if let Some(&target_pos) = state.to_positions.get(&node.id) {
63                    node.position = target_pos;
64                }
65            }
66            return Ok(true); // Transition completed
67        } else {
68            // Interpolate positions - collect data first to avoid borrowing issues
69            let progress = state.progress;
70            let transitions: Vec<_> = graph
71                .nodes()
72                .filter_map(|node| {
73                    if let (Some(&from_pos), Some(&to_pos)) = (
74                        state.from_positions.get(&node.id),
75                        state.to_positions.get(&node.id),
76                    ) {
77                        Some((node.id.clone(), from_pos, to_pos))
78                    } else {
79                        None
80                    }
81                })
82                .collect();
83
84            // Apply interpolated positions
85            let t = ease_in_out(progress);
86            for (node_id, from_pos, to_pos) in transitions {
87                if let Some(node) = graph.get_node_mut(&node_id) {
88                    node.position = interpolate_position(from_pos, to_pos, t);
89                }
90            }
91            return Ok(false); // Transition in progress
92        }
93    } else {
94        false
95    };
96
97    if should_remove_state {
98        *transition_state = None;
99    }
100
101    Ok(true) // No transition
102}