jellyflow_runtime/runtime/binding/
resolve.rs1use jellyflow_core::core::{
2 BindingEndpoint, CanvasPoint, CanvasRect, Graph, GraphLocalBindingTarget, NodeId,
3 PortDirection, PortId,
4};
5
6use crate::runtime::connection::ConnectionHandleRef;
7use crate::runtime::geometry::{
8 EdgeEndpointInput, HandleBounds, HandlePosition, edge_position, handle_anchor_position,
9};
10use crate::runtime::lookups::NodeGraphLookups;
11use crate::runtime::utils::get_node_rect;
12
13use super::query::{
14 BindingEndpointResolution, BindingQueryOptions, BindingQueryResult, ResolvedBinding,
15 ResolvedBindingEndpoint,
16};
17
18pub fn resolve_binding_query(
20 graph: &Graph,
21 lookups: &NodeGraphLookups,
22 revision: u64,
23 node_origin: (f32, f32),
24 options: BindingQueryOptions,
25) -> BindingQueryResult {
26 let bindings = graph
27 .bindings
28 .iter()
29 .map(|(id, binding)| {
30 ResolvedBinding::new(
31 *id,
32 resolve_endpoint(graph, lookups, node_origin, options, &binding.subject),
33 resolve_endpoint(graph, lookups, node_origin, options, &binding.target),
34 binding.kind.clone(),
35 )
36 })
37 .collect();
38
39 BindingQueryResult::new(revision, bindings)
40}
41
42fn resolve_endpoint(
43 graph: &Graph,
44 lookups: &NodeGraphLookups,
45 node_origin: (f32, f32),
46 options: BindingQueryOptions,
47 endpoint: &BindingEndpoint,
48) -> ResolvedBindingEndpoint {
49 match endpoint {
50 BindingEndpoint::Source { .. } => ResolvedBindingEndpoint::source(endpoint.clone()),
51 BindingEndpoint::GraphLocal { target } => resolve_graph_local_target(
52 graph,
53 lookups,
54 node_origin,
55 options,
56 endpoint.clone(),
57 *target,
58 ),
59 }
60}
61
62fn resolve_graph_local_target(
63 graph: &Graph,
64 lookups: &NodeGraphLookups,
65 node_origin: (f32, f32),
66 options: BindingQueryOptions,
67 endpoint: BindingEndpoint,
68 target: GraphLocalBindingTarget,
69) -> ResolvedBindingEndpoint {
70 let resolution = match target {
71 GraphLocalBindingTarget::Graph => BindingEndpointResolution::Graph,
72 GraphLocalBindingTarget::Node { id } => {
73 resolve_node_target(graph, lookups, id, node_origin, options)
74 }
75 GraphLocalBindingTarget::Port { id } => {
76 resolve_port_target(graph, lookups, id, node_origin, options)
77 }
78 GraphLocalBindingTarget::Edge { id } => {
79 resolve_edge_target(graph, lookups, id, node_origin, options)
80 }
81 GraphLocalBindingTarget::Group { id } => graph
82 .groups
83 .get(&id)
84 .map(|group| BindingEndpointResolution::GroupRect {
85 group: id,
86 rect: group.rect,
87 center: rect_center(group.rect),
88 })
89 .unwrap_or(BindingEndpointResolution::Unresolved),
90 GraphLocalBindingTarget::StickyNote { id } => graph
91 .sticky_notes
92 .get(&id)
93 .map(|note| BindingEndpointResolution::StickyNoteRect {
94 note: id,
95 rect: note.rect,
96 center: rect_center(note.rect),
97 })
98 .unwrap_or(BindingEndpointResolution::Unresolved),
99 };
100
101 ResolvedBindingEndpoint::new(endpoint, resolution)
102}
103
104fn resolve_node_target(
105 graph: &Graph,
106 lookups: &NodeGraphLookups,
107 node: NodeId,
108 node_origin: (f32, f32),
109 options: BindingQueryOptions,
110) -> BindingEndpointResolution {
111 let Some(model) = graph.nodes.get(&node) else {
112 return BindingEndpointResolution::Unresolved;
113 };
114 if model.hidden && !options.include_hidden {
115 return BindingEndpointResolution::Hidden;
116 }
117 let Some(rect) = get_node_rect(lookups, node, node_origin, options.fallback_node_size) else {
118 return BindingEndpointResolution::Unresolved;
119 };
120 BindingEndpointResolution::NodeRect {
121 node,
122 rect,
123 center: rect_center(rect),
124 }
125}
126
127fn resolve_port_target(
128 graph: &Graph,
129 lookups: &NodeGraphLookups,
130 port: PortId,
131 node_origin: (f32, f32),
132 options: BindingQueryOptions,
133) -> BindingEndpointResolution {
134 let Some(model) = graph.ports.get(&port) else {
135 return BindingEndpointResolution::Unresolved;
136 };
137 let Some(node) = graph.nodes.get(&model.node) else {
138 return BindingEndpointResolution::Unresolved;
139 };
140 if node.hidden && !options.include_hidden {
141 return BindingEndpointResolution::Hidden;
142 }
143 let Some(node_rect) =
144 get_node_rect(lookups, model.node, node_origin, options.fallback_node_size)
145 else {
146 return BindingEndpointResolution::Unresolved;
147 };
148 let Some(point) = handle_anchor_position(
149 node_rect,
150 measured_handle_bounds(
151 lookups,
152 ConnectionHandleRef::new(model.node, port, model.dir),
153 ),
154 fallback_handle_position(model.dir),
155 )
156 .map(|endpoint| endpoint.point) else {
157 return BindingEndpointResolution::Unresolved;
158 };
159
160 BindingEndpointResolution::PortAnchor {
161 node: model.node,
162 point,
163 }
164}
165
166fn resolve_edge_target(
167 graph: &Graph,
168 lookups: &NodeGraphLookups,
169 edge: jellyflow_core::core::EdgeId,
170 node_origin: (f32, f32),
171 options: BindingQueryOptions,
172) -> BindingEndpointResolution {
173 let Some(edge_model) = graph.edges.get(&edge) else {
174 return BindingEndpointResolution::Unresolved;
175 };
176 if edge_model.hidden && !options.include_hidden {
177 return BindingEndpointResolution::Hidden;
178 }
179 let Some(from_port) = graph.ports.get(&edge_model.from) else {
180 return BindingEndpointResolution::Unresolved;
181 };
182 let Some(to_port) = graph.ports.get(&edge_model.to) else {
183 return BindingEndpointResolution::Unresolved;
184 };
185 let Some(source_node) = graph.nodes.get(&from_port.node) else {
186 return BindingEndpointResolution::Unresolved;
187 };
188 let Some(target_node) = graph.nodes.get(&to_port.node) else {
189 return BindingEndpointResolution::Unresolved;
190 };
191 if (source_node.hidden || target_node.hidden) && !options.include_hidden {
192 return BindingEndpointResolution::Hidden;
193 }
194
195 let Some(source_rect) = get_node_rect(
196 lookups,
197 from_port.node,
198 node_origin,
199 options.fallback_node_size,
200 ) else {
201 return BindingEndpointResolution::Unresolved;
202 };
203 let Some(target_rect) = get_node_rect(
204 lookups,
205 to_port.node,
206 node_origin,
207 options.fallback_node_size,
208 ) else {
209 return BindingEndpointResolution::Unresolved;
210 };
211 let Some(position) = edge_position(
212 EdgeEndpointInput {
213 node_rect: source_rect,
214 handle: measured_handle_bounds(
215 lookups,
216 ConnectionHandleRef::new(from_port.node, edge_model.from, from_port.dir),
217 ),
218 fallback_position: fallback_handle_position(from_port.dir),
219 },
220 EdgeEndpointInput {
221 node_rect: target_rect,
222 handle: measured_handle_bounds(
223 lookups,
224 ConnectionHandleRef::new(to_port.node, edge_model.to, to_port.dir),
225 ),
226 fallback_position: fallback_handle_position(to_port.dir),
227 },
228 ) else {
229 return BindingEndpointResolution::Unresolved;
230 };
231
232 BindingEndpointResolution::EdgePosition { edge, position }
233}
234
235fn measured_handle_bounds(
236 lookups: &NodeGraphLookups,
237 handle: ConnectionHandleRef,
238) -> Option<HandleBounds> {
239 lookups
240 .node_lookup
241 .get(&handle.node)?
242 .measured_handles
243 .iter()
244 .find(|measured| measured.handle == handle)
245 .map(|measured| measured.bounds)
246}
247
248fn fallback_handle_position(direction: PortDirection) -> HandlePosition {
249 match direction {
250 PortDirection::In => HandlePosition::Left,
251 PortDirection::Out => HandlePosition::Right,
252 }
253}
254
255fn rect_center(rect: CanvasRect) -> CanvasPoint {
256 CanvasPoint {
257 x: rect.origin.x + rect.size.width * 0.5,
258 y: rect.origin.y + rect.size.height * 0.5,
259 }
260}