use std::cell::{Cell, RefCell}; use std::rc::Rc; use ::util::ResultExt; use anyhow::Result; use gpui::*; use windows::Win32::{ Foundation::*, Graphics::{DirectManipulation::*, Gdi::*}, System::Com::*, UI::{Input::Pointer::*, WindowsAndMessaging::*}, }; use crate::*; /// Default viewport size in pixels. The actual content size doesn't matter /// because we're using the viewport only for gesture recognition, not for /// visual output. const DEFAULT_VIEWPORT_SIZE: i32 = 1000; pub(crate) struct DirectManipulationHandler { manager: IDirectManipulationManager, update_manager: IDirectManipulationUpdateManager, viewport: IDirectManipulationViewport, _handler_cookie: u32, window: HWND, scale_factor: Rc>, pending_events: Rc>>, } impl DirectManipulationHandler { pub fn new(window: HWND, scale_factor: f32) -> Result { unsafe { let manager: IDirectManipulationManager = CoCreateInstance(&DirectManipulationManager, None, CLSCTX_INPROC_SERVER)?; let update_manager: IDirectManipulationUpdateManager = manager.GetUpdateManager()?; let viewport: IDirectManipulationViewport = manager.CreateViewport(None, window)?; let configuration = DIRECTMANIPULATION_CONFIGURATION_INTERACTION | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_X | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_Y | DIRECTMANIPULATION_CONFIGURATION_TRANSLATION_INERTIA | DIRECTMANIPULATION_CONFIGURATION_RAILS_X | DIRECTMANIPULATION_CONFIGURATION_RAILS_Y | DIRECTMANIPULATION_CONFIGURATION_SCALING; viewport.ActivateConfiguration(configuration)?; viewport.SetViewportOptions( DIRECTMANIPULATION_VIEWPORT_OPTIONS_MANUALUPDATE | DIRECTMANIPULATION_VIEWPORT_OPTIONS_DISABLEPIXELSNAPPING, )?; let mut rect = RECT { left: 0, top: 0, right: DEFAULT_VIEWPORT_SIZE, bottom: DEFAULT_VIEWPORT_SIZE, }; viewport.SetViewportRect(&mut rect)?; manager.Activate(window)?; viewport.Enable()?; let scale_factor = Rc::new(Cell::new(scale_factor)); let pending_events = Rc::new(RefCell::new(Vec::new())); let event_handler: IDirectManipulationViewportEventHandler = DirectManipulationEventHandler::new( window, Rc::clone(&scale_factor), Rc::clone(&pending_events), ) .into(); let handler_cookie = viewport.AddEventHandler(Some(window), &event_handler)?; update_manager.Update(None)?; Ok(Self { manager, update_manager, viewport, _handler_cookie: handler_cookie, window, scale_factor, pending_events, }) } } pub fn set_scale_factor(&self, scale_factor: f32) { self.scale_factor.set(scale_factor); } pub fn on_pointer_hit_test(&self, wparam: WPARAM) { unsafe { let pointer_id = wparam.loword() as u32; let mut pointer_type = POINTER_INPUT_TYPE::default(); if GetPointerType(pointer_id, &mut pointer_type).is_ok() && pointer_type == PT_TOUCHPAD { self.viewport.SetContact(pointer_id).log_err(); } } } pub fn update(&self) { unsafe { self.update_manager.Update(None).log_err(); } } pub fn drain_events(&self) -> Vec { std::mem::take(&mut *self.pending_events.borrow_mut()) } } impl Drop for DirectManipulationHandler { fn drop(&mut self) { unsafe { self.viewport.Stop().log_err(); self.viewport.Abandon().log_err(); self.manager.Deactivate(self.window).log_err(); } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum GestureKind { None, Scroll, Pinch, } #[windows_core::implement(IDirectManipulationViewportEventHandler)] struct DirectManipulationEventHandler { window: HWND, scale_factor: Rc>, gesture_kind: Cell, last_scale: Cell, last_x_offset: Cell, last_y_offset: Cell, scroll_phase: Cell, pending_events: Rc>>, } impl DirectManipulationEventHandler { fn new( window: HWND, scale_factor: Rc>, pending_events: Rc>>, ) -> Self { Self { window, scale_factor, gesture_kind: Cell::new(GestureKind::None), last_scale: Cell::new(1.0), last_x_offset: Cell::new(0.0), last_y_offset: Cell::new(0.0), scroll_phase: Cell::new(TouchPhase::Started), pending_events, } } fn end_gesture(&self) { let position = self.mouse_position(); let modifiers = current_modifiers(); match self.gesture_kind.get() { GestureKind::Scroll => { self.pending_events .borrow_mut() .push(PlatformInput::ScrollWheel(ScrollWheelEvent { position, delta: ScrollDelta::Pixels(point(px(0.0), px(0.0))), modifiers, touch_phase: TouchPhase::Ended, })); } GestureKind::Pinch => { self.pending_events .borrow_mut() .push(PlatformInput::Pinch(PinchEvent { position, delta: 0.0, modifiers, phase: TouchPhase::Ended, })); } GestureKind::None => {} } self.gesture_kind.set(GestureKind::None); } fn mouse_position(&self) -> Point { let scale_factor = self.scale_factor.get(); unsafe { let mut point: POINT = std::mem::zeroed(); let _ = GetCursorPos(&mut point); let _ = ScreenToClient(self.window, &mut point); logical_point(point.x as f32, point.y as f32, scale_factor) } } } impl IDirectManipulationViewportEventHandler_Impl for DirectManipulationEventHandler_Impl { fn OnViewportStatusChanged( &self, viewport: windows_core::Ref<'_, IDirectManipulationViewport>, current: DIRECTMANIPULATION_STATUS, previous: DIRECTMANIPULATION_STATUS, ) -> windows_core::Result<()> { if current == previous { return Ok(()); } // A new gesture interrupted inertia, so end the old sequence. if current == DIRECTMANIPULATION_RUNNING && previous == DIRECTMANIPULATION_INERTIA { self.end_gesture(); } if current == DIRECTMANIPULATION_READY { self.end_gesture(); // Reset the content transform so the viewport is ready for the next gesture. // ZoomToRect triggers a second RUNNING -> READY cycle, so prevent an infinite loop here. if self.last_scale.get() != 1.0 || self.last_x_offset.get() != 0.0 || self.last_y_offset.get() != 0.0 { if let Some(viewport) = viewport.as_ref() { unsafe { viewport .ZoomToRect( 0.0, 0.0, DEFAULT_VIEWPORT_SIZE as f32, DEFAULT_VIEWPORT_SIZE as f32, false, ) .log_err(); } } } self.last_scale.set(1.0); self.last_x_offset.set(0.0); self.last_y_offset.set(0.0); } Ok(()) } fn OnViewportUpdated( &self, _viewport: windows_core::Ref<'_, IDirectManipulationViewport>, ) -> windows_core::Result<()> { Ok(()) } fn OnContentUpdated( &self, _viewport: windows_core::Ref<'_, IDirectManipulationViewport>, content: windows_core::Ref<'_, IDirectManipulationContent>, ) -> windows_core::Result<()> { let content = content.as_ref().ok_or(E_POINTER)?; // Get the 6-element content transform: [scale, 0, 0, scale, tx, ty] let mut xform = [0.0f32; 6]; unsafe { content.GetContentTransform(&mut xform)?; } let scale = xform[0]; let scale_factor = self.scale_factor.get(); let x_offset = xform[4] / scale_factor; let y_offset = xform[5] / scale_factor; if scale == 0.0 { return Ok(()); } let last_scale = self.last_scale.get(); let last_x = self.last_x_offset.get(); let last_y = self.last_y_offset.get(); if float_equals(scale, last_scale) && float_equals(x_offset, last_x) && float_equals(y_offset, last_y) { return Ok(()); } let position = self.mouse_position(); let modifiers = current_modifiers(); // Direct Manipulation reports both translation and scale in every content update. // Translation values can shift during a pinch due to the zoom center shifting. // We classify each gesture as either scroll or pinch and only emit one type of event. // We allow Scroll -> Pinch (a pinch can start with a small pan) but not the reverse. if !float_equals(scale, 1.0) { if self.gesture_kind.get() != GestureKind::Pinch { self.end_gesture(); self.gesture_kind.set(GestureKind::Pinch); self.pending_events .borrow_mut() .push(PlatformInput::Pinch(PinchEvent { position, delta: 0.0, modifiers, phase: TouchPhase::Started, })); } } else if self.gesture_kind.get() == GestureKind::None { self.gesture_kind.set(GestureKind::Scroll); self.scroll_phase.set(TouchPhase::Started); } match self.gesture_kind.get() { GestureKind::Scroll => { let dx = x_offset - last_x; let dy = y_offset - last_y; let touch_phase = self.scroll_phase.get(); self.scroll_phase.set(TouchPhase::Moved); self.pending_events .borrow_mut() .push(PlatformInput::ScrollWheel(ScrollWheelEvent { position, delta: ScrollDelta::Pixels(point(px(dx), px(dy))), modifiers, touch_phase, })); } GestureKind::Pinch => { let scale_delta = scale / last_scale; self.pending_events .borrow_mut() .push(PlatformInput::Pinch(PinchEvent { position, delta: scale_delta - 1.0, modifiers, phase: TouchPhase::Moved, })); } GestureKind::None => {} } self.last_scale.set(scale); self.last_x_offset.set(x_offset); self.last_y_offset.set(y_offset); Ok(()) } } fn float_equals(f1: f32, f2: f32) -> bool { const EPSILON_SCALE: f32 = 0.00001; (f1 - f2).abs() < EPSILON_SCALE * f1.abs().max(f2.abs()).max(EPSILON_SCALE) }