Skip to main content

ferrum_flow/plugins/
viewport.rs

1use gpui::{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.modifiers.shift
28        {
29            ctx.start_interaction(Panning {
30                start_mouse: ev.position,
31                start_offset: ctx.viewport.offset,
32            });
33            return EventResult::Stop;
34        } else if let FlowEvent::Input(InputEvent::Wheel(ev)) = event {
35            let cursor = ev.position;
36
37            let before = ctx.viewport.screen_to_world(cursor);
38
39            let delta = f32::from(ev.delta.pixel_delta(px(1.0)).y);
40            if delta == 0.0 {
41                return EventResult::Continue;
42            }
43
44            let zoom_delta = if delta > 0.0 { 0.9 } else { 1.1 };
45
46            ctx.viewport.zoom *= zoom_delta;
47
48            ctx.viewport.zoom = ctx.viewport.zoom.clamp(0.7, 3.0);
49
50            let after = ctx.viewport.world_to_screen(before);
51
52            ctx.viewport.offset.x += cursor.x - after.x;
53            ctx.viewport.offset.y += cursor.y - after.y;
54            ctx.notify();
55        }
56        EventResult::Continue
57    }
58    fn priority(&self) -> i32 {
59        10
60    }
61    fn render(&mut self, _context: &mut crate::plugin::RenderContext) -> Option<gpui::AnyElement> {
62        None
63    }
64}
65
66struct Panning {
67    start_mouse: Point<Pixels>,
68    start_offset: Point<Pixels>,
69}
70
71impl Interaction for Panning {
72    fn on_mouse_move(
73        &mut self,
74        ev: &gpui::MouseMoveEvent,
75        ctx: &mut crate::plugin::PluginContext,
76    ) -> InteractionResult {
77        let dx = ev.position.x - self.start_mouse.x;
78        let dy = ev.position.y - self.start_mouse.y;
79
80        ctx.viewport.offset.x = self.start_offset.x + dx;
81        ctx.viewport.offset.y = self.start_offset.y + dy;
82        ctx.notify();
83
84        InteractionResult::Continue
85    }
86    fn on_mouse_up(
87        &mut self,
88        _event: &gpui::MouseUpEvent,
89        ctx: &mut crate::plugin::PluginContext,
90    ) -> crate::canvas::InteractionResult {
91        ctx.execute_command(PanningCommand {
92            from: self.start_offset,
93            to: ctx.viewport.offset,
94        });
95        ctx.cancel_interaction();
96        InteractionResult::End
97    }
98    fn render(&self, _ctx: &mut crate::plugin::RenderContext) -> Option<gpui::AnyElement> {
99        None
100    }
101}
102
103struct PanningCommand {
104    from: Point<Pixels>,
105    to: Point<Pixels>,
106}
107
108impl Command for PanningCommand {
109    fn name(&self) -> &'static str {
110        "panning"
111    }
112    fn execute(&mut self, ctx: &mut crate::canvas::CanvasState) {
113        ctx.viewport.offset.x = self.to.x;
114        ctx.viewport.offset.y = self.to.y;
115    }
116    fn undo(&mut self, ctx: &mut crate::canvas::CanvasState) {
117        ctx.viewport.offset.x = self.from.x;
118        ctx.viewport.offset.y = self.from.y;
119    }
120}