direct_manipulation.rs

  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}