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