1use std::cell::{Cell, RefCell};
2use std::rc::Rc;
3
4use ::util::ResultExt;
5use anyhow::Result;
6use gpui::*;
7use windows::Win32::{
8 Foundation::*,
9 Graphics::{DirectManipulation::*, Gdi::*},
10 System::Com::*,
11 UI::{Input::Pointer::*, WindowsAndMessaging::*},
12};
13
14use crate::*;
15
16/// Default viewport size in pixels. The actual content size doesn't matter
17/// because we're using the viewport only for gesture recognition, not for
18/// visual output.
19const DEFAULT_VIEWPORT_SIZE: i32 = 1000;
20
21pub(crate) struct DirectManipulationHandler {
22 manager: IDirectManipulationManager,
23 update_manager: IDirectManipulationUpdateManager,
24 viewport: IDirectManipulationViewport,
25 _handler_cookie: u32,
26 window: HWND,
27 scale_factor: Rc<Cell<f32>>,
28 pending_events: Rc<RefCell<Vec<PlatformInput>>>,
29}
30
31impl DirectManipulationHandler {
32 pub fn new(window: HWND, scale_factor: f32) -> Result<Self> {
33 unsafe {
34 let manager: IDirectManipulationManager =
35 CoCreateInstance(&DirectManipulationManager, None, CLSCTX_INPROC_SERVER)?;
36
37 let update_manager: IDirectManipulationUpdateManager = manager.GetUpdateManager()?;
38
39 let viewport: IDirectManipulationViewport = manager.CreateViewport(None, window)?;
40
41 let configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION
42 | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X
43 | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y
44 | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA
45 | DIRECTMANIPULATION_CONFIGURATION_RAILS_X
46 | DIRECTMANIPULATION_CONFIGURATION_RAILS_Y
47 | DIRECTMANIPULATION_CONFIGURATION_SCALING;
48 viewport.ActivateConfiguration(configuration)?;
49
50 viewport.SetViewportOptions(
51 DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE
52 | DIRECTMANIPULATION_VIEWPORT_OPTIONS_DISABLEPIXELSNAPPING,
53 )?;
54
55 let mut rect = RECT {
56 left: 0,
57 top: 0,
58 right: DEFAULT_VIEWPORT_SIZE,
59 bottom: DEFAULT_VIEWPORT_SIZE,
60 };
61 viewport.SetViewportRect(&mut rect)?;
62
63 manager.Activate(window)?;
64 viewport.Enable()?;
65
66 let scale_factor = Rc::new(Cell::new(scale_factor));
67 let pending_events = Rc::new(RefCell::new(Vec::new()));
68
69 let event_handler: IDirectManipulationViewportEventHandler =
70 DirectManipulationEventHandler::new(
71 window,
72 Rc::clone(&scale_factor),
73 Rc::clone(&pending_events),
74 )
75 .into();
76
77 let handler_cookie = viewport.AddEventHandler(Some(window), &event_handler)?;
78
79 update_manager.Update(None)?;
80
81 Ok(Self {
82 manager,
83 update_manager,
84 viewport,
85 _handler_cookie: handler_cookie,
86 window,
87 scale_factor,
88 pending_events,
89 })
90 }
91 }
92
93 pub fn set_scale_factor(&self, scale_factor: f32) {
94 self.scale_factor.set(scale_factor);
95 }
96
97 pub fn on_pointer_hit_test(&self, wparam: WPARAM) {
98 unsafe {
99 let pointer_id = wparam.loword() as u32;
100 let mut pointer_type = POINTER_INPUT_TYPE::default();
101 if GetPointerType(pointer_id, &mut pointer_type).is_ok() && pointer_type == PT_TOUCHPAD
102 {
103 self.viewport.SetContact(pointer_id).log_err();
104 }
105 }
106 }
107
108 pub fn update(&self) {
109 unsafe {
110 self.update_manager.Update(None).log_err();
111 }
112 }
113
114 pub fn drain_events(&self) -> Vec<PlatformInput> {
115 std::mem::take(&mut *self.pending_events.borrow_mut())
116 }
117}
118
119impl Drop for DirectManipulationHandler {
120 fn drop(&mut self) {
121 unsafe {
122 self.viewport.Stop().log_err();
123 self.viewport.Abandon().log_err();
124 self.manager.Deactivate(self.window).log_err();
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
130enum GestureKind {
131 None,
132 Scroll,
133 Pinch,
134}
135
136#[windows_core::implement(IDirectManipulationViewportEventHandler)]
137struct DirectManipulationEventHandler {
138 window: HWND,
139 scale_factor: Rc<Cell<f32>>,
140 gesture_kind: Cell<GestureKind>,
141 last_scale: Cell<f32>,
142 last_x_offset: Cell<f32>,
143 last_y_offset: Cell<f32>,
144 scroll_phase: Cell<TouchPhase>,
145 pending_events: Rc<RefCell<Vec<PlatformInput>>>,
146}
147
148impl DirectManipulationEventHandler {
149 fn new(
150 window: HWND,
151 scale_factor: Rc<Cell<f32>>,
152 pending_events: Rc<RefCell<Vec<PlatformInput>>>,
153 ) -> Self {
154 Self {
155 window,
156 scale_factor,
157 gesture_kind: Cell::new(GestureKind::None),
158 last_scale: Cell::new(1.0),
159 last_x_offset: Cell::new(0.0),
160 last_y_offset: Cell::new(0.0),
161 scroll_phase: Cell::new(TouchPhase::Started),
162 pending_events,
163 }
164 }
165
166 fn end_gesture(&self) {
167 let position = self.mouse_position();
168 let modifiers = current_modifiers();
169 match self.gesture_kind.get() {
170 GestureKind::Scroll => {
171 self.pending_events
172 .borrow_mut()
173 .push(PlatformInput::ScrollWheel(ScrollWheelEvent {
174 position,
175 delta: ScrollDelta::Pixels(point(px(0.0), px(0.0))),
176 modifiers,
177 touch_phase: TouchPhase::Ended,
178 }));
179 }
180 GestureKind::Pinch => {
181 self.pending_events
182 .borrow_mut()
183 .push(PlatformInput::Pinch(PinchEvent {
184 position,
185 delta: 0.0,
186 modifiers,
187 phase: TouchPhase::Ended,
188 }));
189 }
190 GestureKind::None => {}
191 }
192 self.gesture_kind.set(GestureKind::None);
193 }
194
195 fn mouse_position(&self) -> Point<Pixels> {
196 let scale_factor = self.scale_factor.get();
197 unsafe {
198 let mut point: POINT = std::mem::zeroed();
199 let _ = GetCursorPos(&mut point);
200 let _ = ScreenToClient(self.window, &mut point);
201 logical_point(point.x as f32, point.y as f32, scale_factor)
202 }
203 }
204}
205
206impl IDirectManipulationViewportEventHandler_Impl for DirectManipulationEventHandler_Impl {
207 fn OnViewportStatusChanged(
208 &self,
209 viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
210 current: DIRECTMANIPULATION_STATUS,
211 previous: DIRECTMANIPULATION_STATUS,
212 ) -> windows_core::Result<()> {
213 if current == previous {
214 return Ok(());
215 }
216
217 // A new gesture interrupted inertia, so end the old sequence.
218 if current == DIRECTMANIPULATION_RUNNING && previous == DIRECTMANIPULATION_INERTIA {
219 self.end_gesture();
220 }
221
222 if current == DIRECTMANIPULATION_READY {
223 self.end_gesture();
224
225 // Reset the content transform so the viewport is ready for the next gesture.
226 // ZoomToRect triggers a second RUNNING -> READY cycle, so prevent an infinite loop here.
227 if self.last_scale.get() != 1.0
228 || self.last_x_offset.get() != 0.0
229 || self.last_y_offset.get() != 0.0
230 {
231 if let Some(viewport) = viewport.as_ref() {
232 unsafe {
233 viewport
234 .ZoomToRect(
235 0.0,
236 0.0,
237 DEFAULT_VIEWPORT_SIZE as f32,
238 DEFAULT_VIEWPORT_SIZE as f32,
239 false,
240 )
241 .log_err();
242 }
243 }
244 }
245
246 self.last_scale.set(1.0);
247 self.last_x_offset.set(0.0);
248 self.last_y_offset.set(0.0);
249 }
250
251 Ok(())
252 }
253
254 fn OnViewportUpdated(
255 &self,
256 _viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
257 ) -> windows_core::Result<()> {
258 Ok(())
259 }
260
261 fn OnContentUpdated(
262 &self,
263 _viewport: windows_core::Ref<'_, IDirectManipulationViewport>,
264 content: windows_core::Ref<'_, IDirectManipulationContent>,
265 ) -> windows_core::Result<()> {
266 let content = content.as_ref().ok_or(E_POINTER)?;
267
268 // Get the 6-element content transform: [scale, 0, 0, scale, tx, ty]
269 let mut xform = [0.0f32; 6];
270 unsafe {
271 content.GetContentTransform(&mut xform)?;
272 }
273
274 let scale = xform[0];
275 let scale_factor = self.scale_factor.get();
276 let x_offset = xform[4] / scale_factor;
277 let y_offset = xform[5] / scale_factor;
278
279 if scale == 0.0 {
280 return Ok(());
281 }
282
283 let last_scale = self.last_scale.get();
284 let last_x = self.last_x_offset.get();
285 let last_y = self.last_y_offset.get();
286
287 if float_equals(scale, last_scale)
288 && float_equals(x_offset, last_x)
289 && float_equals(y_offset, last_y)
290 {
291 return Ok(());
292 }
293
294 let position = self.mouse_position();
295 let modifiers = current_modifiers();
296
297 // Direct Manipulation reports both translation and scale in every content update.
298 // Translation values can shift during a pinch due to the zoom center shifting.
299 // We classify each gesture as either scroll or pinch and only emit one type of event.
300 // We allow Scroll -> Pinch (a pinch can start with a small pan) but not the reverse.
301 if !float_equals(scale, 1.0) {
302 if self.gesture_kind.get() != GestureKind::Pinch {
303 self.end_gesture();
304 self.gesture_kind.set(GestureKind::Pinch);
305 self.pending_events
306 .borrow_mut()
307 .push(PlatformInput::Pinch(PinchEvent {
308 position,
309 delta: 0.0,
310 modifiers,
311 phase: TouchPhase::Started,
312 }));
313 }
314 } else if self.gesture_kind.get() == GestureKind::None {
315 self.gesture_kind.set(GestureKind::Scroll);
316 self.scroll_phase.set(TouchPhase::Started);
317 }
318
319 match self.gesture_kind.get() {
320 GestureKind::Scroll => {
321 let dx = x_offset - last_x;
322 let dy = y_offset - last_y;
323 let touch_phase = self.scroll_phase.get();
324 self.scroll_phase.set(TouchPhase::Moved);
325 self.pending_events
326 .borrow_mut()
327 .push(PlatformInput::ScrollWheel(ScrollWheelEvent {
328 position,
329 delta: ScrollDelta::Pixels(point(px(dx), px(dy))),
330 modifiers,
331 touch_phase,
332 }));
333 }
334 GestureKind::Pinch => {
335 let scale_delta = scale / last_scale;
336 self.pending_events
337 .borrow_mut()
338 .push(PlatformInput::Pinch(PinchEvent {
339 position,
340 delta: scale_delta - 1.0,
341 modifiers,
342 phase: TouchPhase::Moved,
343 }));
344 }
345 GestureKind::None => {}
346 }
347
348 self.last_scale.set(scale);
349 self.last_x_offset.set(x_offset);
350 self.last_y_offset.set(y_offset);
351
352 Ok(())
353 }
354}
355
356fn float_equals(f1: f32, f2: f32) -> bool {
357 const EPSILON_SCALE: f32 = 0.00001;
358 (f1 - f2).abs() < EPSILON_SCALE * f1.abs().max(f2.abs()).max(EPSILON_SCALE)
359}