jellyflow_runtime/runtime/selection/
node_drag_start.rs1use serde::{Deserialize, Serialize};
2
3use crate::io::{NodeGraphInteractionState, NodeGraphViewState};
4use crate::runtime::drag::{
5 PointerGestureClaim, PointerGestureClaimInput, resolve_pointer_gesture_claim,
6};
7use crate::runtime::policy::resolve_node_interaction_policy;
8use crate::runtime::store::NodeGraphStore;
9use jellyflow_core::core::CanvasPoint;
10use jellyflow_core::core::{EdgeId, Graph, GroupId, NodeId};
11
12use super::types::SelectionModifier;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub struct NodeDragStartSelectionInput {
17 pub node: NodeId,
18 pub modifier: SelectionModifier,
19}
20
21impl NodeDragStartSelectionInput {
22 pub fn new(node: NodeId, multi_selection_active: bool) -> Self {
23 Self {
24 node,
25 modifier: if multi_selection_active {
26 SelectionModifier::Additive
27 } else {
28 SelectionModifier::Replace
29 },
30 }
31 }
32
33 pub fn with_modifier(node: NodeId, modifier: SelectionModifier) -> Self {
34 Self { node, modifier }
35 }
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum NodeDragStartSelectionAction {
41 Unchanged,
43 Clear,
45 SelectOnly(NodeId),
47 Add(NodeId),
49 Remove(NodeId),
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub struct NodePointerDownDecision {
56 pub selection: NodeDragStartSelectionAction,
57 pub drag_claim: PointerGestureClaim,
58}
59
60impl NodePointerDownDecision {
61 pub fn new(selection: NodeDragStartSelectionAction, drag_claim: PointerGestureClaim) -> Self {
62 Self {
63 selection,
64 drag_claim,
65 }
66 }
67
68 pub fn apply_to_view_state(self, view_state: &mut NodeGraphViewState) {
69 self.selection.apply_to_view_state(view_state);
70 }
71
72 fn selection_after(
73 self,
74 view_state: &NodeGraphViewState,
75 ) -> Option<(Vec<NodeId>, Vec<EdgeId>, Vec<GroupId>)> {
76 self.selection.selection_after(view_state)
77 }
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
82pub struct NodePointerDownInput {
83 pub node: NodeId,
84 #[serde(default)]
85 pub multi_selection_active: bool,
86 pub screen_delta: CanvasPoint,
87}
88
89impl NodePointerDownInput {
90 pub fn new(node: NodeId, multi_selection_active: bool, screen_delta: CanvasPoint) -> Self {
91 Self {
92 node,
93 multi_selection_active,
94 screen_delta,
95 }
96 }
97}
98
99impl NodeDragStartSelectionAction {
100 pub fn is_unchanged(self) -> bool {
101 self == Self::Unchanged
102 }
103
104 pub fn apply_to_view_state(self, view_state: &mut NodeGraphViewState) {
105 match self {
106 Self::Unchanged => {}
107 Self::Clear => view_state.set_selection(Vec::new(), Vec::new(), Vec::new()),
108 Self::SelectOnly(node) => view_state.set_selection(vec![node], Vec::new(), Vec::new()),
109 Self::Add(node) => {
110 if !view_state.selected_nodes.contains(&node) {
111 view_state.selected_nodes.push(node);
112 view_state.selected_nodes.sort();
113 view_state.selected_nodes.dedup();
114 }
115 }
116 Self::Remove(node) => {
117 view_state
118 .selected_nodes
119 .retain(|selected| *selected != node);
120 }
121 }
122 }
123
124 fn selection_after(
125 self,
126 view_state: &NodeGraphViewState,
127 ) -> Option<(Vec<NodeId>, Vec<EdgeId>, Vec<GroupId>)> {
128 if self.is_unchanged() {
129 return None;
130 }
131
132 let mut next = view_state.clone();
133 self.apply_to_view_state(&mut next);
134 Some((
135 next.selected_nodes,
136 next.selected_edges,
137 next.selected_groups,
138 ))
139 }
140}
141
142pub fn resolve_node_drag_start_selection(
148 graph: &Graph,
149 view_state: &NodeGraphViewState,
150 interaction: &NodeGraphInteractionState,
151 input: NodeDragStartSelectionInput,
152) -> NodeDragStartSelectionAction {
153 let Some(node) = graph.nodes.get(&input.node) else {
154 return NodeDragStartSelectionAction::Unchanged;
155 };
156 if node.hidden {
157 return NodeDragStartSelectionAction::Unchanged;
158 }
159
160 let selected = view_state.selected_nodes.contains(&input.node);
161 let selectable = resolve_node_interaction_policy(node, interaction).selectable;
162 let selection = interaction.selection_interaction();
163
164 if (!selection.select_nodes_on_drag || !selectable) && !input.modifier.additive() {
165 return if selected {
166 NodeDragStartSelectionAction::Unchanged
167 } else {
168 NodeDragStartSelectionAction::Clear
169 };
170 }
171
172 if !selectable || !selection.select_nodes_on_drag {
173 return NodeDragStartSelectionAction::Unchanged;
174 }
175
176 if !selected {
177 if input.modifier.additive() {
178 NodeDragStartSelectionAction::Add(input.node)
179 } else {
180 NodeDragStartSelectionAction::SelectOnly(input.node)
181 }
182 } else if input.modifier.additive() {
183 NodeDragStartSelectionAction::Remove(input.node)
184 } else {
185 NodeDragStartSelectionAction::Unchanged
186 }
187}
188
189pub fn resolve_node_pointer_down(
194 graph: &Graph,
195 view_state: &NodeGraphViewState,
196 interaction: &NodeGraphInteractionState,
197 input: NodePointerDownInput,
198) -> NodePointerDownDecision {
199 let selection = resolve_node_drag_start_selection(
200 graph,
201 view_state,
202 interaction,
203 NodeDragStartSelectionInput::new(input.node, input.multi_selection_active),
204 );
205 let drag_claim = resolve_pointer_gesture_claim(PointerGestureClaimInput::new(
206 input.screen_delta,
207 input.multi_selection_active,
208 false,
209 false,
210 interaction.node_drag_interaction().node_drag_threshold,
211 interaction.node_drag_interaction().node_drag_threshold,
212 ));
213
214 NodePointerDownDecision::new(selection, drag_claim)
215}
216
217impl NodeGraphStore {
218 pub fn apply_node_pointer_down(
220 &mut self,
221 input: NodePointerDownInput,
222 ) -> NodePointerDownDecision {
223 let interaction = self.resolved_interaction_state();
224 let decision =
225 resolve_node_pointer_down(self.graph(), self.view_state(), &interaction, input);
226 if let Some((nodes, edges, groups)) = decision.selection_after(self.view_state()) {
227 self.set_selection(nodes, edges, groups);
228 }
229 decision
230 }
231
232 pub fn apply_node_drag_start_selection(
234 &mut self,
235 input: NodeDragStartSelectionInput,
236 ) -> NodeDragStartSelectionAction {
237 self.apply_node_pointer_down(NodePointerDownInput::new(
238 input.node,
239 input.modifier.additive(),
240 CanvasPoint::default(),
241 ))
242 .selection
243 }
244
245 pub fn resolve_node_pointer_down(
247 &self,
248 input: NodePointerDownInput,
249 ) -> NodePointerDownDecision {
250 let interaction = self.resolved_interaction_state();
251 resolve_node_pointer_down(self.graph(), self.view_state(), &interaction, input)
252 }
253}