Skip to main content

ferrum_flow/plugins/node/
interaction.rs

1use 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}