ferrum_flow/plugins/
viewport.rs1use 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}