Skip to main content

jellyflow_runtime/runtime/binding/
resolve.rs

1use 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
18/// Resolves binding endpoints using graph facts, lookup measurements, and runtime node origin.
19pub 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}