1#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use crate::error::{FlowError, Result};
10use crate::types::{NodeId, Position, Size};
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct HandleId(pub String);
16
17impl HandleId {
18 pub fn new(id: impl Into<String>) -> Self {
20 Self(id.into())
21 }
22
23 pub fn as_str(&self) -> &str {
25 &self.0
26 }
27}
28
29impl From<&str> for HandleId {
30 fn from(id: &str) -> Self {
31 Self(id.to_string())
32 }
33}
34
35impl From<String> for HandleId {
36 fn from(id: String) -> Self {
37 Self(id)
38 }
39}
40
41#[derive(Debug, Clone, PartialEq)]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44pub enum HandleType {
45 Source,
46 Target,
47}
48
49impl HandleType {
50 pub fn is_source(&self) -> bool {
52 matches!(self, HandleType::Source)
53 }
54
55 pub fn is_target(&self) -> bool {
57 matches!(self, HandleType::Target)
58 }
59}
60
61#[derive(Debug, Clone, PartialEq)]
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64pub enum HandlePosition {
65 Top,
66 Right,
67 Bottom,
68 Left,
69 Custom(Position),
70}
71
72impl HandlePosition {
73 pub fn to_position(&self) -> Position {
75 match self {
76 HandlePosition::Top => Position::new(0.0, -10.0),
77 HandlePosition::Right => Position::new(80.0, 30.0),
78 HandlePosition::Bottom => Position::new(40.0, 60.0),
79 HandlePosition::Left => Position::new(0.0, 30.0),
80 HandlePosition::Custom(pos) => *pos,
81 }
82 }
83}
84
85#[derive(Debug, Clone, PartialEq)]
87#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
88pub struct Handle {
89 pub id: HandleId,
90 pub handle_type: HandleType,
91 pub position: HandlePosition,
92 pub connection_limit: Option<usize>,
93 pub valid_connection_types: Option<Vec<String>>,
94 pub style: Option<String>,
95}
96
97impl Handle {
98 pub fn new(id: impl Into<HandleId>, handle_type: HandleType, position: HandlePosition) -> Self {
100 Self {
101 id: id.into(),
102 handle_type,
103 position,
104 connection_limit: None,
105 valid_connection_types: None,
106 style: None,
107 }
108 }
109
110 pub fn source(id: impl Into<HandleId>, position: HandlePosition) -> Self {
112 Self::new(id, HandleType::Source, position)
113 }
114
115 pub fn target(id: impl Into<HandleId>, position: HandlePosition) -> Self {
117 Self::new(id, HandleType::Target, position)
118 }
119
120 pub fn with_connection_limit(mut self, limit: usize) -> Self {
122 self.connection_limit = Some(limit);
123 self
124 }
125
126 pub fn with_connection_types(mut self, types: Vec<String>) -> Self {
128 self.valid_connection_types = Some(types);
129 self
130 }
131
132 pub fn with_style(mut self, style: impl Into<String>) -> Self {
134 self.style = Some(style.into());
135 self
136 }
137
138 pub fn absolute_position(&self, node_pos: Position, node_size: Size) -> Position {
140 match &self.position {
141 HandlePosition::Top => Position::new(node_pos.x + node_size.width / 2.0, node_pos.y),
142 HandlePosition::Right => Position::new(
143 node_pos.x + node_size.width,
144 node_pos.y + node_size.height / 2.0,
145 ),
146 HandlePosition::Bottom => Position::new(
147 node_pos.x + node_size.width / 2.0,
148 node_pos.y + node_size.height,
149 ),
150 HandlePosition::Left => Position::new(node_pos.x, node_pos.y + node_size.height / 2.0),
151 HandlePosition::Custom(pos) => Position::new(node_pos.x + pos.x, node_pos.y + pos.y),
152 }
153 }
154
155 pub fn contains_point(
157 &self,
158 point: Position,
159 node_pos: Position,
160 node_size: Size,
161 handle_size: f64,
162 ) -> bool {
163 let handle_pos = self.absolute_position(node_pos, node_size);
164 let half_size = handle_size / 2.0;
165
166 point.x >= handle_pos.x - half_size
167 && point.x <= handle_pos.x + half_size
168 && point.y >= handle_pos.y - half_size
169 && point.y <= handle_pos.y + half_size
170 }
171
172 pub fn can_connect_to(&self, other: &Handle) -> bool {
174 if self.handle_type == other.handle_type {
176 return false;
177 }
178
179 if let (Some(self_types), Some(other_types)) =
181 (&self.valid_connection_types, &other.valid_connection_types)
182 {
183 return self_types.iter().any(|t| other_types.contains(t));
185 }
186
187 true }
189}
190
191#[derive(Debug, Clone, PartialEq)]
193#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
194pub struct HandleManager {
195 handles: Vec<Handle>,
196 node_id: NodeId,
197}
198
199impl Default for HandleManager {
200 fn default() -> Self {
201 Self {
202 handles: Vec::new(),
203 node_id: "default".into(),
204 }
205 }
206}
207
208impl HandleManager {
209 pub fn new(node_id: NodeId) -> Self {
211 Self {
212 handles: Vec::new(),
213 node_id,
214 }
215 }
216
217 pub fn add_handle(&mut self, handle: Handle) -> Result<()> {
219 if self.handles.iter().any(|h| h.id == handle.id) {
221 return Err(FlowError::invalid_operation(format!(
222 "Handle '{}' already exists",
223 handle.id.as_str()
224 )));
225 }
226
227 self.handles.push(handle);
228 Ok(())
229 }
230
231 pub fn remove_handle(&mut self, handle_id: &HandleId) -> Result<Handle> {
233 let index = self
234 .handles
235 .iter()
236 .position(|h| &h.id == handle_id)
237 .ok_or_else(|| {
238 FlowError::invalid_operation(format!("Handle '{}' not found", handle_id.as_str()))
239 })?;
240
241 Ok(self.handles.remove(index))
242 }
243
244 pub fn get_handle(&self, handle_id: &HandleId) -> Option<&Handle> {
246 self.handles.iter().find(|h| &h.id == handle_id)
247 }
248
249 pub fn handles(&self) -> &[Handle] {
251 &self.handles
252 }
253
254 pub fn handle_at_position(
256 &self,
257 point: Position,
258 node_pos: Position,
259 node_size: Size,
260 handle_size: f64,
261 ) -> Option<&Handle> {
262 self.handles
263 .iter()
264 .find(|handle| handle.contains_point(point, node_pos, node_size, handle_size))
265 }
266
267 pub fn source_handles(&self) -> impl Iterator<Item = &Handle> {
269 self.handles
270 .iter()
271 .filter(|h| h.handle_type == HandleType::Source)
272 }
273
274 pub fn target_handles(&self) -> impl Iterator<Item = &Handle> {
276 self.handles
277 .iter()
278 .filter(|h| h.handle_type == HandleType::Target)
279 }
280
281 pub fn connection_count(&self, _handle_id: &HandleId) -> usize {
286 0
290 }
291
292 pub fn can_accept_connection(&self, handle_id: &HandleId) -> bool {
297 if let Some(handle) = self.get_handle(handle_id) {
298 if let Some(_limit) = handle.connection_limit {
299 return true; }
304 }
305 true
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::types::{Position, Size};
313
314 #[test]
315 fn test_handle_creation() {
316 let handle = Handle::new("output", HandleType::Source, HandlePosition::Right);
317
318 assert_eq!(handle.id.as_str(), "output");
319 assert_eq!(handle.handle_type, HandleType::Source);
320 assert_eq!(handle.position, HandlePosition::Right);
321 assert!(handle.connection_limit.is_none());
322 }
323
324 #[test]
325 fn test_handle_builder_methods() {
326 let handle = Handle::source("out", HandlePosition::Right)
327 .with_connection_limit(1)
328 .with_connection_types(vec!["data".to_string()])
329 .with_style("custom");
330
331 assert_eq!(handle.connection_limit, Some(1));
332 assert_eq!(
333 handle.valid_connection_types,
334 Some(vec!["data".to_string()])
335 );
336 assert_eq!(handle.style, Some("custom".to_string()));
337 }
338
339 #[test]
340 fn test_absolute_position_calculation() {
341 let node_pos = Position::new(100.0, 200.0);
342 let node_size = Size::new(80.0, 60.0);
343
344 let top_handle = Handle::new("top", HandleType::Source, HandlePosition::Top);
346 assert_eq!(
347 top_handle.absolute_position(node_pos, node_size),
348 Position::new(140.0, 200.0) );
350
351 let right_handle = Handle::new("right", HandleType::Source, HandlePosition::Right);
352 assert_eq!(
353 right_handle.absolute_position(node_pos, node_size),
354 Position::new(180.0, 230.0) );
356
357 let custom_handle = Handle::new(
358 "custom",
359 HandleType::Source,
360 HandlePosition::Custom(Position::new(10.0, 20.0)),
361 );
362 assert_eq!(
363 custom_handle.absolute_position(node_pos, node_size),
364 Position::new(110.0, 220.0) );
366 }
367
368 #[test]
369 fn test_point_inside_handle() {
370 let handle = Handle::new("test", HandleType::Source, HandlePosition::Right);
371 let node_pos = Position::new(0.0, 0.0);
372 let node_size = Size::new(100.0, 50.0);
373 let handle_size = 10.0;
374
375 assert!(handle.contains_point(
377 Position::new(100.0, 25.0),
378 node_pos,
379 node_size,
380 handle_size
381 ));
382 assert!(handle.contains_point(Position::new(95.0, 25.0), node_pos, node_size, handle_size));
383 assert!(handle.contains_point(
384 Position::new(105.0, 25.0),
385 node_pos,
386 node_size,
387 handle_size
388 ));
389 assert!(!handle.contains_point(
390 Position::new(90.0, 25.0),
391 node_pos,
392 node_size,
393 handle_size
394 ));
395 assert!(!handle.contains_point(
396 Position::new(100.0, 35.0),
397 node_pos,
398 node_size,
399 handle_size
400 ));
401 }
402
403 #[test]
404 fn test_handle_connection_compatibility() {
405 let source_handle = Handle::source("out", HandlePosition::Right);
406 let target_handle = Handle::target("in", HandlePosition::Left);
407 let another_source = Handle::source("out2", HandlePosition::Bottom);
408
409 assert!(source_handle.can_connect_to(&target_handle));
411 assert!(target_handle.can_connect_to(&source_handle));
412
413 assert!(!source_handle.can_connect_to(&another_source));
415 }
416
417 #[test]
418 fn test_handle_connection_type_validation() {
419 let data_source = Handle::source("data_out", HandlePosition::Right)
420 .with_connection_types(vec!["data".to_string()]);
421 let data_target = Handle::target("data_in", HandlePosition::Left)
422 .with_connection_types(vec!["data".to_string()]);
423 let control_target = Handle::target("control_in", HandlePosition::Left)
424 .with_connection_types(vec!["control".to_string()]);
425
426 assert!(data_source.can_connect_to(&data_target));
428
429 assert!(!data_source.can_connect_to(&control_target));
431 }
432
433 #[test]
434 fn test_handle_manager_operations() {
435 let mut manager = HandleManager::new("node1".into());
436
437 let handle1 = Handle::source("out", HandlePosition::Right);
439 let handle2 = Handle::target("in", HandlePosition::Left);
440
441 manager.add_handle(handle1.clone()).unwrap();
442 manager.add_handle(handle2.clone()).unwrap();
443
444 assert_eq!(manager.handles().len(), 2);
445
446 assert!(manager.add_handle(handle1.clone()).is_err());
448
449 assert!(manager.get_handle(&"out".into()).is_some());
451 assert!(manager.get_handle(&"nonexistent".into()).is_none());
452
453 let removed = manager.remove_handle(&"out".into()).unwrap();
455 assert_eq!(removed.id.as_str(), "out");
456 assert_eq!(manager.handles().len(), 1);
457 }
458
459 #[test]
460 fn test_handle_manager_find_at_position() {
461 let mut manager = HandleManager::new("node1".into());
462 let handle = Handle::new("right", HandleType::Source, HandlePosition::Right);
463 manager.add_handle(handle).unwrap();
464
465 let node_pos = Position::new(0.0, 0.0);
466 let node_size = Size::new(100.0, 50.0);
467 let handle_size = 10.0;
468
469 let found = manager.handle_at_position(
471 Position::new(100.0, 25.0),
472 node_pos,
473 node_size,
474 handle_size,
475 );
476 assert!(found.is_some());
477 assert_eq!(found.unwrap().id.as_str(), "right");
478
479 let not_found =
481 manager.handle_at_position(Position::new(50.0, 25.0), node_pos, node_size, handle_size);
482 assert!(not_found.is_none());
483 }
484
485 #[test]
486 fn test_handle_type_filtering() {
487 let mut manager = HandleManager::new("node1".into());
488
489 manager
490 .add_handle(Handle::source("out1", HandlePosition::Right))
491 .unwrap();
492 manager
493 .add_handle(Handle::source("out2", HandlePosition::Top))
494 .unwrap();
495 manager
496 .add_handle(Handle::target("in1", HandlePosition::Left))
497 .unwrap();
498
499 let sources: Vec<_> = manager.source_handles().collect();
500 let targets: Vec<_> = manager.target_handles().collect();
501
502 assert_eq!(sources.len(), 2);
503 assert_eq!(targets.len(), 1);
504 }
505}