ferrum_flow/plugins/node/
interaction.rs1use std::time::{Duration, Instant};
2
3use gpui::{MouseButton, Pixels, Point, px};
4
5use crate::{
6 NodeId,
7 canvas::{Interaction, InteractionResult},
8 plugin::{EventResult, FlowEvent, InitPluginContext, InputEvent, Plugin, PluginContext},
9 plugins::node::command::{DragNodesCommand, SelecteNodeCommand},
10};
11
12const DRAG_THRESHOLD: Pixels = px(2.0);
13const DRAG_COMMAND_INTERVAL: Duration = Duration::from_millis(50);
14
15pub struct NodeInteractionPlugin;
16
17impl NodeInteractionPlugin {
18 pub fn new() -> Self {
19 Self
20 }
21}
22
23impl Plugin for NodeInteractionPlugin {
24 fn name(&self) -> &'static str {
25 "node_interaction"
26 }
27
28 fn setup(&mut self, _ctx: &mut InitPluginContext) {}
29
30 fn on_event(&mut self, event: &FlowEvent, ctx: &mut PluginContext) -> EventResult {
31 if let FlowEvent::Input(InputEvent::MouseDown(ev)) = event {
32 if ev.button != MouseButton::Left {
33 return EventResult::Continue;
34 }
35 let mouse_world = ctx.screen_to_world(ev.position);
36
37 if let Some(node_id) = ctx.hit_node(mouse_world) {
38 ctx.start_interaction(NodeDragInteraction::start(
39 node_id,
40 mouse_world,
41 ev.modifiers.shift,
42 ));
43
44 return EventResult::Stop;
45 } else {
46 ctx.clear_selected_node();
47 }
48 }
49
50 EventResult::Continue
51 }
52
53 fn priority(&self) -> i32 {
54 120
55 }
56}
57
58pub struct NodeDragInteraction {
59 state: NodeDragState,
60 last_drag_command_at: Option<Instant>,
61}
62
63enum NodeDragState {
64 Pending {
65 node_id: NodeId,
66 start_mouse: Point<Pixels>,
67 shift: bool,
68 },
69 Draging {
70 start_mouse: Point<Pixels>,
71 start_positions: Vec<(NodeId, Point<Pixels>)>,
72 },
73}
74
75impl NodeDragInteraction {
76 fn start(node_id: NodeId, start_mouse: Point<Pixels>, shift: bool) -> Self {
77 Self {
78 state: NodeDragState::Pending {
79 node_id,
80 start_mouse,
81 shift,
82 },
83 last_drag_command_at: None,
84 }
85 }
86}
87
88impl Interaction for NodeDragInteraction {
89 fn on_mouse_move(
90 &mut self,
91 ev: &gpui::MouseMoveEvent,
92 ctx: &mut PluginContext,
93 ) -> crate::canvas::InteractionResult {
94 match &self.state {
95 NodeDragState::Pending {
96 node_id,
97 start_mouse,
98 ..
99 } => {
100 let delta = ctx.screen_to_world(ev.position) - *start_mouse;
101 if delta.x.abs() > DRAG_THRESHOLD || delta.y.abs() > DRAG_THRESHOLD {
102 let mut nodes = vec![];
103
104 if ctx.graph.selected_node.contains(&node_id) {
105 for id in &ctx.graph.selected_node {
106 if let Some(node) = ctx.nodes().get(&id) {
107 nodes.push((id.clone(), node.point()));
108 }
109 }
110 } else {
111 if let Some(node) = ctx.nodes().get(&node_id) {
112 nodes.push((node_id.clone(), node.point()));
113 }
114 }
115 self.state = NodeDragState::Draging {
116 start_mouse: ev.position,
117 start_positions: nodes,
118 };
119
120 ctx.notify();
121 }
122 }
123 NodeDragState::Draging {
124 start_mouse,
125 start_positions,
126 } => {
127 let dx = (ev.position.x - start_mouse.x) / ctx.viewport.zoom;
128 let dy = (ev.position.y - start_mouse.y) / ctx.viewport.zoom;
129 for (id, point) in start_positions.iter() {
130 if let Some(node) = ctx.get_node_mut(id) {
131 node.x = point.x + dx;
132 node.y = point.y + dy;
133 }
134 }
135
136 if ctx.has_sync_plugin() {
137 let now = Instant::now();
138 let should_command = self
139 .last_drag_command_at
140 .map(|t| now.duration_since(t) >= DRAG_COMMAND_INTERVAL)
141 .unwrap_or(true);
142 if should_command {
143 ctx.execute_command(DragNodesCommand::new(start_positions, &ctx));
144 self.last_drag_command_at = Some(now);
145 }
146 }
147 ctx.notify();
148 }
149 }
150 InteractionResult::Continue
151 }
152 fn on_mouse_up(
153 &mut self,
154 _ev: &gpui::MouseUpEvent,
155 ctx: &mut PluginContext,
156 ) -> crate::canvas::InteractionResult {
157 match &self.state {
158 NodeDragState::Pending { node_id, shift, .. } => {
159 ctx.execute_command(SelecteNodeCommand::new(*node_id, *shift, ctx));
160 InteractionResult::End
161 }
162 NodeDragState::Draging {
163 start_positions, ..
164 } => {
165 ctx.execute_command(DragNodesCommand::new(start_positions, &ctx));
166 InteractionResult::End
167 }
168 }
169 }
170 fn render(&self, _ctx: &mut crate::plugin::RenderContext) -> Option<gpui::AnyElement> {
171 None
172 }
173}