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