flow_rs_core/
edge_creator.rs

1//! Interactive edge creation system
2//!
3//! Provides functionality for creating edges interactively through drag operations
4//! from source handles to target handles or nodes.
5
6use crate::{Edge, FlowError, Graph, NodeId, Position, Result};
7
8/// Helper function to create edge with default data
9fn create_edge_with_defaults<E: Default>(
10    id: impl Into<crate::EdgeId>,
11    source: impl Into<NodeId>,
12    target: impl Into<NodeId>,
13) -> Edge<E> {
14    Edge::new(id, source, target, E::default())
15}
16
17/// Preview edge shown during interactive edge creation
18#[derive(Debug, Clone)]
19pub struct PreviewEdge {
20    pub source_node: NodeId,
21    pub source_handle: Option<String>,
22    pub start_position: Position,
23    pub end_position: Position,
24}
25
26/// Feedback for connection validation during edge creation
27#[derive(Debug, Clone)]
28pub struct ConnectionFeedback {
29    pub is_valid: bool,
30    pub can_connect: bool,
31    pub message: Option<String>,
32}
33
34/// Interactive edge creator for handling edge creation workflow
35#[derive(Debug)]
36pub struct EdgeCreator {
37    /// Current preview edge being created
38    preview_edge: Option<PreviewEdge>,
39    /// Whether we're currently in edge creation mode
40    is_creating: bool,
41}
42
43impl EdgeCreator {
44    /// Create a new edge creator
45    pub fn new() -> Self {
46        Self {
47            preview_edge: None,
48            is_creating: false,
49        }
50    }
51
52    /// Check if currently creating an edge
53    pub fn is_creating_edge(&self) -> bool {
54        self.is_creating
55    }
56
57    /// Validate basic connection constraints
58    fn validate_basic_connection(&self, source_node: &NodeId, target_node: &NodeId) -> Result<()> {
59        // Check for self-connection
60        if source_node == target_node {
61            return Err(FlowError::SelfConnection);
62        }
63
64        Ok(())
65    }
66
67    /// Validate handle compatibility and connection limits
68    fn validate_handle_compatibility<N, E>(
69        &self,
70        graph: &Graph<N, E>,
71        source_node_ref: &crate::Node<N>,
72        target_node_ref: &crate::Node<N>,
73        source_handle: &str,
74        target_handle: &str,
75        source_node_id: &NodeId,
76    ) -> Result<()> {
77        // Get handles from nodes
78        let source_handle_ref = source_node_ref
79            .get_handle(&source_handle.into())
80            .ok_or_else(|| FlowError::handle_not_found(source_handle))?;
81
82        let target_handle_ref = target_node_ref
83            .get_handle(&target_handle.into())
84            .ok_or_else(|| FlowError::handle_not_found(target_handle))?;
85
86        // Check handle compatibility
87        if !source_handle_ref.can_connect_to(target_handle_ref) {
88            return Err(FlowError::invalid_connection(format!(
89                "Incompatible handles: {} cannot connect to {}",
90                source_handle, target_handle
91            )));
92        }
93
94        // Check connection limits for source handle
95        if let Some(limit) = source_handle_ref.connection_limit {
96            let current_connections = graph
97                .edges()
98                .filter(|edge| {
99                    &edge.source == source_node_id
100                        && edge.source_handle.as_deref() == Some(source_handle)
101                })
102                .count();
103
104            if current_connections >= limit {
105                return Err(FlowError::connection_limit_exceeded(
106                    source_handle,
107                    current_connections,
108                    limit,
109                ));
110            }
111        }
112
113        // Check connection limits for target handle
114        if let Some(limit) = target_handle_ref.connection_limit {
115            let target_node_id: NodeId = target_node_ref.id.clone();
116            let current_connections = graph
117                .edges()
118                .filter(|edge| {
119                    edge.target == target_node_id
120                        && edge.target_handle.as_deref() == Some(target_handle)
121                })
122                .count();
123
124            if current_connections >= limit {
125                return Err(FlowError::connection_limit_exceeded(
126                    target_handle,
127                    current_connections,
128                    limit,
129                ));
130            }
131        }
132
133        Ok(())
134    }
135
136    /// Get the current preview edge
137    pub fn get_preview_edge(&self) -> Option<&PreviewEdge> {
138        self.preview_edge.as_ref()
139    }
140
141    /// Start edge creation from a source handle
142    pub fn start_edge_creation<N, E>(
143        &mut self,
144        graph: &Graph<N, E>,
145        source_node: &str,
146        source_handle: &str,
147        start_position: Position,
148    ) -> Result<()> {
149        let node_id: NodeId = source_node.into();
150
151        // Validate source node exists
152        if graph.get_node(&node_id).is_none() {
153            return Err(FlowError::node_not_found(source_node));
154        }
155
156        // Create preview edge
157        self.preview_edge = Some(PreviewEdge {
158            source_node: node_id,
159            source_handle: Some(source_handle.to_string()),
160            start_position,
161            end_position: start_position,
162        });
163        self.is_creating = true;
164
165        Ok(())
166    }
167
168    /// Update the preview edge end position
169    pub fn update_edge_preview(&mut self, end_position: Position) {
170        if let Some(preview) = &mut self.preview_edge {
171            preview.end_position = end_position;
172        }
173    }
174
175    /// Complete edge creation to a target node/handle
176    pub fn complete_edge_creation<N, E>(
177        &mut self,
178        graph: &mut Graph<N, E>,
179        target_node: &str,
180        target_handle: Option<&str>,
181        _end_position: Position,
182    ) -> Result<()>
183    where
184        N: Clone,
185        E: Clone + Default,
186    {
187        let preview = self
188            .preview_edge
189            .take()
190            .ok_or_else(|| FlowError::InvalidOperation {
191                message: "No edge creation in progress".to_string(),
192            })?;
193
194        let target_node_id: NodeId = target_node.into();
195
196        // Validate target node exists
197        if graph.get_node(&target_node_id).is_none() {
198            return Err(FlowError::node_not_found(target_node));
199        }
200
201        // Validate basic constraints
202        self.validate_basic_connection(&preview.source_node, &target_node_id)?;
203
204        // Get source and target nodes for validation
205        let source_node_ref = graph.get_node(&preview.source_node)
206            .ok_or_else(|| FlowError::node_not_found(preview.source_node.as_str()))?;
207        let target_node_ref = graph.get_node(&target_node_id)
208            .ok_or_else(|| FlowError::node_not_found(target_node_id.as_str()))?;
209
210        // Validate handle compatibility if both handles specified
211        if let (Some(source_handle), Some(target_handle)) = (&preview.source_handle, target_handle)
212        {
213            self.validate_handle_compatibility(
214                graph,
215                source_node_ref,
216                target_node_ref,
217                source_handle,
218                target_handle,
219                &preview.source_node,
220            )?;
221        }
222
223        // Create the actual edge
224        let edge_id = format!("edge_{}_to_{}", preview.source_node.as_str(), target_node);
225        let mut edge = create_edge_with_defaults::<E>(
226            edge_id,
227            preview.source_node.as_str(),
228            target_node_id.as_str(),
229        );
230
231        // Set handle references if provided
232        if let Some(source_handle) = preview.source_handle {
233            edge = edge.with_source_handle(source_handle);
234        }
235        if let Some(target_handle) = target_handle {
236            edge = edge.with_target_handle(target_handle.to_string());
237        }
238
239        graph.add_edge(edge)?;
240        self.is_creating = false;
241
242        Ok(())
243    }
244
245    /// Cancel edge creation
246    pub fn cancel_edge_creation(&mut self) {
247        self.preview_edge = None;
248        self.is_creating = false;
249    }
250
251    /// Get potential drop target at given position
252    pub fn get_drop_target<N, E>(
253        &self,
254        graph: &Graph<N, E>,
255        position: Position,
256        tolerance: f64,
257    ) -> Option<(NodeId, Option<String>)> {
258        // First check for handle hits
259        for node in graph.nodes() {
260            for handle in node.handles() {
261                let handle_pos = node.position + handle.position.to_position();
262                let distance = ((position.x - handle_pos.x).powi(2)
263                    + (position.y - handle_pos.y).powi(2))
264                .sqrt();
265
266                if distance <= tolerance {
267                    return Some((node.id.clone(), Some(handle.id.as_str().to_string())));
268                }
269            }
270        }
271
272        // Then check for node hits
273        for node in graph.nodes() {
274            let node_bounds = crate::types::Rect::new(
275                node.position.x,
276                node.position.y,
277                node.size.width,
278                node.size.height,
279            );
280
281            if node_bounds.contains_point(position) {
282                return Some((node.id.clone(), None));
283            }
284        }
285
286        None
287    }
288
289    /// Get connection feedback for validation
290    pub fn get_connection_feedback<N, E>(
291        &self,
292        _graph: &Graph<N, E>,
293        target_node: &str,
294        _target_handle: Option<&str>,
295    ) -> ConnectionFeedback {
296        let preview = match &self.preview_edge {
297            Some(preview) => preview,
298            None => {
299                return ConnectionFeedback {
300                    is_valid: false,
301                    can_connect: false,
302                    message: Some("No edge creation in progress".to_string()),
303                };
304            }
305        };
306
307        let target_node_id: NodeId = target_node.into();
308
309        // Check for self-connection
310        if preview.source_node == target_node_id {
311            return ConnectionFeedback {
312                is_valid: false,
313                can_connect: false,
314                message: Some("Cannot connect to self".to_string()),
315            };
316        }
317
318        // For now, assume all other connections are valid
319        ConnectionFeedback {
320            is_valid: true,
321            can_connect: true,
322            message: None,
323        }
324    }
325}
326
327impl Default for EdgeCreator {
328    fn default() -> Self {
329        Self::new()
330    }
331}