jellyflow_runtime/runtime/drag/
planner.rs1use crate::io::NodeGraphNudgeStepMode;
2use crate::io::{NodeGraphInteractionState, NodeGraphViewState};
3use jellyflow_core::core::{CanvasPoint, Graph, NodeId};
4use jellyflow_core::ops::{GraphOp, GraphTransaction};
5
6use super::candidates::drag_candidates;
7use super::constraints::{drag_items, snapped_delta};
8use super::parent_expansion::parent_expansion_ops;
9use super::types::{
10 NODE_DRAG_TRANSACTION_LABEL, NODE_NUDGE_TRANSACTION_LABEL, NodeDragItem, NodeDragPlan,
11 NodeDragRequest, NodeNudgePlan, NodeNudgeRequest,
12};
13
14pub fn plan_node_drag(
16 graph: &Graph,
17 view_state: &NodeGraphViewState,
18 interaction: &NodeGraphInteractionState,
19 request: NodeDragRequest,
20) -> Option<NodeDragPlan> {
21 if !request.to.is_finite() {
22 return None;
23 }
24
25 let primary = graph.nodes.get(&request.node)?;
26 let delta = CanvasPoint {
27 x: request.to.x - primary.pos.x,
28 y: request.to.y - primary.pos.y,
29 };
30 let (_delta, items, transaction) = plan_node_move_delta(
31 graph,
32 view_state,
33 interaction,
34 request.node,
35 delta,
36 NODE_DRAG_TRANSACTION_LABEL,
37 )?;
38 let primary_to = items
39 .iter()
40 .find(|item| item.node == request.node)
41 .map(|item| item.to)?;
42
43 Some(NodeDragPlan::new(
44 request.node,
45 primary.pos,
46 primary_to,
47 items,
48 transaction,
49 ))
50}
51
52pub fn plan_node_nudge(
54 graph: &Graph,
55 view_state: &NodeGraphViewState,
56 interaction: &NodeGraphInteractionState,
57 request: NodeNudgeRequest,
58) -> Option<NodeNudgePlan> {
59 if interaction.keyboard_interaction().disable_keyboard_a11y {
60 return None;
61 }
62
63 let primary = nudge_primary(graph, view_state, interaction)?;
64 let delta = nudge_delta(view_state, interaction, request)?;
65 let (delta, items, transaction) = plan_node_move_delta(
66 graph,
67 view_state,
68 interaction,
69 primary,
70 delta,
71 NODE_NUDGE_TRANSACTION_LABEL,
72 )?;
73
74 Some(NodeNudgePlan::new(
75 request.direction,
76 delta,
77 items,
78 transaction,
79 ))
80}
81
82fn plan_node_move_delta(
83 graph: &Graph,
84 view_state: &NodeGraphViewState,
85 interaction: &NodeGraphInteractionState,
86 primary: NodeId,
87 delta: CanvasPoint,
88 label: &'static str,
89) -> Option<(CanvasPoint, Vec<NodeDragItem>, GraphTransaction)> {
90 if !delta.is_finite() || delta == CanvasPoint::default() {
91 return None;
92 }
93
94 let candidates = drag_candidates(graph, view_state, interaction, primary);
95 if !candidates.iter().any(|item| item.node == primary) {
96 return None;
97 }
98 let delta = snapped_delta(interaction, &candidates, delta);
99 if !delta.is_finite() || delta == CanvasPoint::default() {
100 return None;
101 }
102 let items = drag_items(interaction, &candidates, delta);
103 if items.is_empty() || items.iter().all(|item| item.from == item.to) {
104 return None;
105 }
106 let mut ops = items
107 .iter()
108 .map(|item| GraphOp::SetNodePos {
109 id: item.node,
110 from: item.from,
111 to: item.to,
112 })
113 .collect::<Vec<_>>();
114 ops.extend(parent_expansion_ops(graph, &candidates, &items));
115 let transaction = GraphTransaction::from_ops(ops).with_label(label);
116
117 Some((delta, items, transaction))
118}
119
120fn nudge_primary(
121 graph: &Graph,
122 view_state: &NodeGraphViewState,
123 interaction: &NodeGraphInteractionState,
124) -> Option<NodeId> {
125 let mut nodes = view_state.selected_nodes.clone();
126 nodes.sort();
127 nodes.dedup();
128 nodes.into_iter().find(|node| {
129 drag_candidates(graph, view_state, interaction, *node)
130 .iter()
131 .any(|candidate| candidate.node == *node)
132 })
133}
134
135fn nudge_delta(
136 view_state: &NodeGraphViewState,
137 interaction: &NodeGraphInteractionState,
138 request: NodeNudgeRequest,
139) -> Option<CanvasPoint> {
140 let direction = request.direction.unit_delta();
141 let keyboard = interaction.keyboard_interaction();
142 let node_drag = interaction.node_drag_interaction();
143 let (step_x, step_y) = match keyboard.nudge_step_mode {
144 NodeGraphNudgeStepMode::ScreenPx => {
145 let step_px = if request.fast {
146 keyboard.nudge_fast_step_px
147 } else {
148 keyboard.nudge_step_px
149 };
150 let zoom = if view_state.zoom.is_finite() && view_state.zoom > 0.0 {
151 view_state.zoom
152 } else {
153 1.0
154 };
155 let step = step_px / zoom;
156 (step, step)
157 }
158 NodeGraphNudgeStepMode::Grid => {
159 let grid = node_drag.snap_grid;
160 if !grid.is_positive_finite() {
161 return None;
162 }
163 let factor = if request.fast { 4.0 } else { 1.0 };
164 (grid.width * factor, grid.height * factor)
165 }
166 };
167
168 finite_positive_step(step_x, step_y)?;
169 Some(CanvasPoint {
170 x: direction.x * step_x,
171 y: direction.y * step_y,
172 })
173}
174
175fn finite_positive_step(step_x: f32, step_y: f32) -> Option<()> {
176 (step_x.is_finite() && step_x > 0.0 && step_y.is_finite() && step_y > 0.0).then_some(())
177}