Skip to main content

ferrum_flow/plugins/
viewport.rs

1use gpui::{MouseButton, Pixels, Point, px};
2
3use crate::{
4    canvas::{Command, Interaction, InteractionResult},
5    plugin::{EventResult, FlowEvent, InputEvent, Plugin},
6};
7
8pub struct ViewportPlugin;
9
10impl ViewportPlugin {
11    pub fn new() -> Self {
12        Self {}
13    }
14}
15
16impl Plugin for ViewportPlugin {
17    fn name(&self) -> &'static str {
18        "viewport"
19    }
20    fn setup(&mut self, _ctx: &mut crate::plugin::InitPluginContext) {}
21    fn on_event(
22        &mut self,
23        event: &crate::plugin::FlowEvent,
24        ctx: &mut crate::plugin::PluginContext,
25    ) -> EventResult {
26        if let FlowEvent::Input(InputEvent::MouseDown(ev)) = event
27            && ev.button == MouseButton::Left
28            && ev.modifiers.shift
29        {
30            ctx.start_interaction(Panning {
31                start_mouse: ev.position,
32                start_offset: ctx.viewport.offset,
33            });
34            return EventResult::Stop;
35        } else if let FlowEvent::Input(InputEvent::Wheel(ev)) = event {
36            let cursor = ev.position;
37
38            let before = ctx.screen_to_world(cursor);
39
40            let delta = f32::from(ev.delta.pixel_delta(px(1.0)).y);
41            if delta == 0.0 {
42                return EventResult::Continue;
43            }
44
45            let zoom_delta = if delta > 0.0 { 0.9 } else { 1.1 };
46
47            ctx.viewport.zoom *= zoom_delta;
48
49            ctx.viewport.zoom = ctx.viewport.zoom.clamp(0.7, 3.0);
50
51            let after = ctx.world_to_screen(before);
52
53            ctx.viewport.offset.x += cursor.x - after.x;
54            ctx.viewport.offset.y += cursor.y - after.y;
55            ctx.notify();
56        }
57        EventResult::Continue
58    }
59    fn priority(&self) -> i32 {
60        10
61    }
62    fn render(&mut self, _context: &mut crate::plugin::RenderContext) -> Option<gpui::AnyElement> {
63        None
64    }
65}
66
67struct Panning {
68    start_mouse: Point<Pixels>,
69    start_offset: Point<Pixels>,
70}
71
72impl Interaction for Panning {
73    fn on_mouse_move(
74        &mut self,
75        ev: &gpui::MouseMoveEvent,
76        ctx: &mut crate::plugin::PluginContext,
77    ) -> InteractionResult {
78        let dx = ev.position.x - self.start_mouse.x;
79        let dy = ev.position.y - self.start_mouse.y;
80
81        ctx.viewport.offset.x = self.start_offset.x + dx;
82        ctx.viewport.offset.y = self.start_offset.y + dy;
83        ctx.notify();
84
85        InteractionResult::Continue
86    }
87    fn on_mouse_up(
88        &mut self,
89        _event: &gpui::MouseUpEvent,
90        ctx: &mut crate::plugin::PluginContext,
91    ) -> crate::canvas::InteractionResult {
92        ctx.execute_command(PanningCommand {
93            from: self.start_offset,
94            to: ctx.viewport.offset,
95        });
96        ctx.cancel_interaction();
97        InteractionResult::End
98    }
99    fn render(&self, _ctx: &mut crate::plugin::RenderContext) -> Option<gpui::AnyElement> {
100        None
101    }
102}
103
104struct PanningCommand {
105    from: Point<Pixels>,
106    to: Point<Pixels>,
107}
108
109impl Command for PanningCommand {
110    fn name(&self) -> &'static str {
111        "panning"
112    }
113    fn execute(&mut self, ctx: &mut crate::canvas::CommandContext) {
114        ctx.viewport.offset.x = self.to.x;
115        ctx.viewport.offset.y = self.to.y;
116    }
117    fn undo(&mut self, ctx: &mut crate::canvas::CommandContext) {
118        ctx.viewport.offset.x = self.from.x;
119        ctx.viewport.offset.y = self.from.y;
120    }
121
122    fn to_ops(&self, _ctx: &mut crate::CommandContext) -> Vec<crate::GraphOp> {
123        vec![]
124    }
125}