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