jellyflow_runtime/runtime/connection/
handles.rs1use serde::{Deserialize, Serialize};
2
3use jellyflow_core::core::{CanvasPoint, CanvasRect, NodeId, PortDirection, PortId};
4
5use crate::runtime::geometry::{HandleBounds, HandlePosition, handle_center_position};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct ConnectionHandleRef {
10 pub node: NodeId,
11 pub port: PortId,
12 pub direction: PortDirection,
13}
14
15impl ConnectionHandleRef {
16 pub fn new(node: NodeId, port: PortId, direction: PortDirection) -> Self {
17 Self {
18 node,
19 port,
20 direction,
21 }
22 }
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
27pub struct ConnectionHandleCandidate {
28 pub handle: ConnectionHandleRef,
29 pub node_rect: CanvasRect,
30 pub bounds: HandleBounds,
31}
32
33impl ConnectionHandleCandidate {
34 pub fn new(handle: ConnectionHandleRef, node_rect: CanvasRect, bounds: HandleBounds) -> Self {
35 Self {
36 handle,
37 node_rect,
38 bounds,
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct ClosestConnectionHandleInput<'a> {
46 pub pointer: CanvasPoint,
47 pub radius: f32,
48 pub from: ConnectionHandleRef,
49 pub candidates: &'a [ConnectionHandleCandidate],
50}
51
52impl<'a> ClosestConnectionHandleInput<'a> {
53 pub fn new(
54 pointer: CanvasPoint,
55 radius: f32,
56 from: ConnectionHandleRef,
57 candidates: &'a [ConnectionHandleCandidate],
58 ) -> Self {
59 Self {
60 pointer,
61 radius,
62 from,
63 candidates,
64 }
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
70pub struct ClosestConnectionHandle {
71 pub handle: ConnectionHandleRef,
72 pub center: CanvasPoint,
73 pub position: HandlePosition,
74 pub distance: f32,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
79#[serde(rename_all = "snake_case")]
80pub enum ConnectionHandleValidity {
81 Valid,
83 Invalid,
85 NoHandle,
87}
88
89pub fn closest_connection_handle(
94 input: ClosestConnectionHandleInput<'_>,
95) -> Option<ClosestConnectionHandle> {
96 if !input.pointer.is_finite() || !input.radius.is_finite() || input.radius < 0.0 {
97 return None;
98 }
99
100 let mut closest: Vec<ClosestConnectionHandle> = Vec::new();
101 let mut min_distance = f32::INFINITY;
102
103 for candidate in input.candidates {
104 if candidate.handle == input.from {
105 continue;
106 }
107
108 let Some(center) = handle_center_position(
109 candidate.node_rect,
110 Some(candidate.bounds),
111 candidate.bounds.position,
112 ) else {
113 continue;
114 };
115 let distance = (center.x - input.pointer.x).hypot(center.y - input.pointer.y);
116 if !distance.is_finite() || distance > input.radius {
117 continue;
118 }
119
120 let resolved = ClosestConnectionHandle {
121 handle: candidate.handle,
122 center,
123 position: candidate.bounds.position,
124 distance,
125 };
126 if distance < min_distance {
127 closest.clear();
128 closest.push(resolved);
129 min_distance = distance;
130 } else if distance == min_distance {
131 closest.push(resolved);
132 }
133 }
134
135 let preferred_direction = opposite_direction(input.from.direction);
136 closest
137 .iter()
138 .find(|candidate| candidate.handle.direction == preferred_direction)
139 .copied()
140 .or_else(|| closest.first().copied())
141}
142
143fn opposite_direction(direction: PortDirection) -> PortDirection {
144 match direction {
145 PortDirection::In => PortDirection::Out,
146 PortDirection::Out => PortDirection::In,
147 }
148}
149
150pub fn connection_handle_validity(
155 is_inside_connection_radius: bool,
156 is_handle_valid: bool,
157) -> ConnectionHandleValidity {
158 if is_handle_valid {
159 ConnectionHandleValidity::Valid
160 } else if is_inside_connection_radius {
161 ConnectionHandleValidity::Invalid
162 } else {
163 ConnectionHandleValidity::NoHandle
164 }
165}