Skip to main content

ferrum_flow/plugins/node/
interaction.rs

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