mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
gpui: Implement pinch event support for X11 and Windows (#51354)
Closes #51312 - Remove platform-specific #[cfg] gates from PinchEvent, event listeners, and dispatch logic in GPUI - Windows: Intercept Ctrl+ScrollWheel (emitted by precision trackpads for pinch gestures) and convert them to GPUI PinchEvents - Image Viewer: remove redundant platform-specific blocks - X11: Bump XInput version to 2.4 and implement handlers for XinputGesturePinch events - [x] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - Pinching gestures now available on all devices. --------- Co-authored-by: John Tur <john-tur@outlook.com>
This commit is contained in:
parent
d72a03827d
commit
6694a3bd14
11 changed files with 463 additions and 66 deletions
|
|
@ -812,6 +812,7 @@ features = [
|
|||
"Win32_Graphics_Direct3D_Fxc",
|
||||
"Win32_Graphics_DirectComposition",
|
||||
"Win32_Graphics_DirectWrite",
|
||||
"Win32_Graphics_DirectManipulation",
|
||||
"Win32_Graphics_Dwm",
|
||||
"Win32_Graphics_Dxgi",
|
||||
"Win32_Graphics_Dxgi_Common",
|
||||
|
|
@ -843,6 +844,7 @@ features = [
|
|||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_Input_Ime",
|
||||
"Win32_UI_Input_KeyboardAndMouse",
|
||||
"Win32_UI_Input_Pointer",
|
||||
"Win32_UI_Shell",
|
||||
"Win32_UI_Shell_Common",
|
||||
"Win32_UI_Shell_PropertiesSystem",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@
|
|||
//! and Tailwind-like styling that you can use to build your own custom elements. Div is
|
||||
//! constructed by combining these two systems into an all-in-one element.
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use crate::PinchEvent;
|
||||
use crate::{
|
||||
AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
|
||||
|
|
@ -357,11 +356,7 @@ impl Interactivity {
|
|||
|
||||
/// Bind the given callback to pinch gesture events during the bubble phase.
|
||||
///
|
||||
/// Note: This event is only available on macOS and Wayland (Linux).
|
||||
/// On Windows, pinch gestures are simulated as scroll wheel events with Ctrl held.
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn on_pinch(&mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) {
|
||||
self.pinch_listeners
|
||||
.push(Box::new(move |event, phase, hitbox, window, cx| {
|
||||
|
|
@ -373,11 +368,7 @@ impl Interactivity {
|
|||
|
||||
/// Bind the given callback to pinch gesture events during the capture phase.
|
||||
///
|
||||
/// Note: This event is only available on macOS and Wayland (Linux).
|
||||
/// On Windows, pinch gestures are simulated as scroll wheel events with Ctrl held.
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub fn capture_pinch(
|
||||
&mut self,
|
||||
listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
|
||||
|
|
@ -675,15 +666,9 @@ impl Interactivity {
|
|||
self.hitbox_behavior = HitboxBehavior::BlockMouseExceptScroll;
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn has_pinch_listeners(&self) -> bool {
|
||||
!self.pinch_listeners.is_empty()
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
fn has_pinch_listeners(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for elements that want to use the standard GPUI event handlers that don't
|
||||
|
|
@ -957,11 +942,7 @@ pub trait InteractiveElement: Sized {
|
|||
/// Bind the given callback to pinch gesture events during the bubble phase.
|
||||
/// The fluent API equivalent to [`Interactivity::on_pinch`].
|
||||
///
|
||||
/// Note: This event is only available on macOS and Wayland (Linux).
|
||||
/// On Windows, pinch gestures are simulated as scroll wheel events with Ctrl held.
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn on_pinch(mut self, listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static) -> Self {
|
||||
self.interactivity().on_pinch(listener);
|
||||
self
|
||||
|
|
@ -970,11 +951,7 @@ pub trait InteractiveElement: Sized {
|
|||
/// Bind the given callback to pinch gesture events during the capture phase.
|
||||
/// The fluent API equivalent to [`Interactivity::capture_pinch`].
|
||||
///
|
||||
/// Note: This event is only available on macOS and Wayland (Linux).
|
||||
/// On Windows, pinch gestures are simulated as scroll wheel events with Ctrl held.
|
||||
///
|
||||
/// See [`Context::listener`](crate::Context::listener) to get access to a view's state from this callback.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn capture_pinch(
|
||||
mut self,
|
||||
listener: impl Fn(&PinchEvent, &mut Window, &mut App) + 'static,
|
||||
|
|
@ -1367,7 +1344,6 @@ pub(crate) type MouseMoveListener =
|
|||
pub(crate) type ScrollWheelListener =
|
||||
Box<dyn Fn(&ScrollWheelEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) type PinchListener =
|
||||
Box<dyn Fn(&PinchEvent, DispatchPhase, &Hitbox, &mut Window, &mut App) + 'static>;
|
||||
|
||||
|
|
@ -1725,7 +1701,6 @@ pub struct Interactivity {
|
|||
pub(crate) mouse_pressure_listeners: Vec<MousePressureListener>,
|
||||
pub(crate) mouse_move_listeners: Vec<MouseMoveListener>,
|
||||
pub(crate) scroll_wheel_listeners: Vec<ScrollWheelListener>,
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub(crate) pinch_listeners: Vec<PinchListener>,
|
||||
pub(crate) key_down_listeners: Vec<KeyDownListener>,
|
||||
pub(crate) key_up_listeners: Vec<KeyUpListener>,
|
||||
|
|
@ -2297,7 +2272,6 @@ impl Interactivity {
|
|||
})
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
for listener in self.pinch_listeners.drain(..) {
|
||||
let hitbox = hitbox.clone();
|
||||
window.on_mouse_event(move |event: &PinchEvent, phase, window, cx| {
|
||||
|
|
|
|||
|
|
@ -473,10 +473,7 @@ impl Default for ScrollDelta {
|
|||
/// A pinch gesture event from the platform, generated when the user performs
|
||||
/// a pinch-to-zoom gesture (typically on a trackpad).
|
||||
///
|
||||
/// Note: This event is only available on macOS and Wayland (Linux).
|
||||
/// On Windows, pinch gestures are simulated as scroll wheel events with Ctrl held.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
pub struct PinchEvent {
|
||||
/// The position of the pinch center on the window.
|
||||
pub position: Point<Pixels>,
|
||||
|
|
@ -493,20 +490,15 @@ pub struct PinchEvent {
|
|||
pub phase: TouchPhase,
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl Sealed for PinchEvent {}
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl InputEvent for PinchEvent {
|
||||
fn to_platform_input(self) -> PlatformInput {
|
||||
PlatformInput::Pinch(self)
|
||||
}
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl GestureEvent for PinchEvent {}
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl MouseEvent for PinchEvent {}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
impl Deref for PinchEvent {
|
||||
type Target = Modifiers;
|
||||
|
||||
|
|
@ -675,7 +667,6 @@ pub enum PlatformInput {
|
|||
/// The scroll wheel was used.
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
/// A pinch gesture was performed.
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
Pinch(PinchEvent),
|
||||
/// Files were dragged and dropped onto the window.
|
||||
FileDrop(FileDropEvent),
|
||||
|
|
@ -693,7 +684,6 @@ impl PlatformInput {
|
|||
PlatformInput::MousePressure(event) => Some(event),
|
||||
PlatformInput::MouseExited(event) => Some(event),
|
||||
PlatformInput::ScrollWheel(event) => Some(event),
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
PlatformInput::Pinch(event) => Some(event),
|
||||
PlatformInput::FileDrop(event) => Some(event),
|
||||
}
|
||||
|
|
@ -710,7 +700,6 @@ impl PlatformInput {
|
|||
PlatformInput::MousePressure(_) => None,
|
||||
PlatformInput::MouseExited(_) => None,
|
||||
PlatformInput::ScrollWheel(_) => None,
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
PlatformInput::Pinch(_) => None,
|
||||
PlatformInput::FileDrop(_) => None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4146,7 +4146,6 @@ impl Window {
|
|||
self.modifiers = scroll_wheel.modifiers;
|
||||
PlatformInput::ScrollWheel(scroll_wheel)
|
||||
}
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
PlatformInput::Pinch(pinch) => {
|
||||
self.mouse_position = pinch.position;
|
||||
self.modifiers = pinch.modifiers;
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ pub struct X11ClientState {
|
|||
pub(crate) last_mouse_button: Option<MouseButton>,
|
||||
pub(crate) last_location: Point<Pixels>,
|
||||
pub(crate) current_count: usize,
|
||||
pub(crate) pinch_scale: f32,
|
||||
|
||||
pub(crate) gpu_context: GpuContext,
|
||||
pub(crate) compositor_gpu: Option<CompositorGpuHint>,
|
||||
|
|
@ -342,11 +343,12 @@ impl X11Client {
|
|||
xcb_connection.prefetch_extension_information(render::X11_EXTENSION_NAME)?;
|
||||
xcb_connection.prefetch_extension_information(xinput::X11_EXTENSION_NAME)?;
|
||||
|
||||
// Announce to X server that XInput up to 2.1 is supported. To increase this to 2.2 and
|
||||
// beyond, support for touch events would need to be added.
|
||||
// Announce to X server that XInput up to 2.4 is supported.
|
||||
// Version 2.4 is needed for gesture events (GesturePinchBegin/Update/End).
|
||||
// If the server only supports an older version, gesture events simply won't be delivered.
|
||||
let xinput_version = get_reply(
|
||||
|| "XInput XiQueryVersion failed",
|
||||
xcb_connection.xinput_xi_query_version(2, 1),
|
||||
xcb_connection.xinput_xi_query_version(2, 4),
|
||||
)?;
|
||||
assert!(
|
||||
xinput_version.major_version >= 2,
|
||||
|
|
@ -502,6 +504,7 @@ impl X11Client {
|
|||
last_mouse_button: None,
|
||||
last_location: Point::new(px(0.0), px(0.0)),
|
||||
current_count: 0,
|
||||
pinch_scale: 1.0,
|
||||
gpu_context: Rc::new(RefCell::new(None)),
|
||||
compositor_gpu,
|
||||
scale_factor,
|
||||
|
|
@ -1324,6 +1327,64 @@ impl X11Client {
|
|||
reset_pointer_device_scroll_positions(pointer);
|
||||
}
|
||||
}
|
||||
Event::XinputGesturePinchBegin(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.pinch_scale = 1.0;
|
||||
let modifiers = modifiers_from_xinput_info(event.mods);
|
||||
state.modifiers = modifiers;
|
||||
let position = point(
|
||||
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
);
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::Pinch(gpui::PinchEvent {
|
||||
position,
|
||||
delta: 0.0,
|
||||
modifiers,
|
||||
phase: gpui::TouchPhase::Started,
|
||||
}));
|
||||
}
|
||||
Event::XinputGesturePinchUpdate(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
let modifiers = modifiers_from_xinput_info(event.mods);
|
||||
state.modifiers = modifiers;
|
||||
let position = point(
|
||||
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
);
|
||||
// scale is in FP16.16 format: divide by 65536 to get the float value
|
||||
let new_absolute_scale = event.scale as f32 / 65536.0;
|
||||
let previous_scale = state.pinch_scale;
|
||||
let zoom_delta = new_absolute_scale - previous_scale;
|
||||
state.pinch_scale = new_absolute_scale;
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::Pinch(gpui::PinchEvent {
|
||||
position,
|
||||
delta: zoom_delta,
|
||||
modifiers,
|
||||
phase: gpui::TouchPhase::Moved,
|
||||
}));
|
||||
}
|
||||
Event::XinputGesturePinchEnd(event) => {
|
||||
let window = self.get_window(event.event)?;
|
||||
let mut state = self.0.borrow_mut();
|
||||
state.pinch_scale = 1.0;
|
||||
let modifiers = modifiers_from_xinput_info(event.mods);
|
||||
state.modifiers = modifiers;
|
||||
let position = point(
|
||||
px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
px(event.event_y as f32 / u16::MAX as f32 / state.scale_factor),
|
||||
);
|
||||
drop(state);
|
||||
window.handle_input(PlatformInput::Pinch(gpui::PinchEvent {
|
||||
position,
|
||||
delta: 0.0,
|
||||
modifiers,
|
||||
phase: gpui::TouchPhase::Ended,
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -671,7 +671,13 @@ impl X11WindowState {
|
|||
| xinput::XIEventMask::BUTTON_PRESS
|
||||
| xinput::XIEventMask::BUTTON_RELEASE
|
||||
| xinput::XIEventMask::ENTER
|
||||
| xinput::XIEventMask::LEAVE,
|
||||
| xinput::XIEventMask::LEAVE
|
||||
// x11rb 0.13 doesn't define XIEventMask constants for gesture
|
||||
// events, so we construct them from the event opcodes (each
|
||||
// XInput event type N maps to mask bit N).
|
||||
| xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_BEGIN_EVENT)
|
||||
| xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_UPDATE_EVENT)
|
||||
| xinput::XIEventMask::from(1u32 << xinput::GESTURE_PINCH_END_EVENT),
|
||||
],
|
||||
}],
|
||||
),
|
||||
|
|
|
|||
359
crates/gpui_windows/src/direct_manipulation.rs
Normal file
359
crates/gpui_windows/src/direct_manipulation.rs
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
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<Cell<f32>>,
|
||||
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
|
||||
}
|
||||
|
||||
impl DirectManipulationHandler {
|
||||
pub fn new(window: HWND, scale_factor: f32) -> Result<Self> {
|
||||
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<PlatformInput> {
|
||||
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<Cell<f32>>,
|
||||
gesture_kind: Cell<GestureKind>,
|
||||
last_scale: Cell<f32>,
|
||||
last_x_offset: Cell<f32>,
|
||||
last_y_offset: Cell<f32>,
|
||||
scroll_phase: Cell<TouchPhase>,
|
||||
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
|
||||
}
|
||||
|
||||
impl DirectManipulationEventHandler {
|
||||
fn new(
|
||||
window: HWND,
|
||||
scale_factor: Rc<Cell<f32>>,
|
||||
pending_events: Rc<RefCell<Vec<PlatformInput>>>,
|
||||
) -> 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<Pixels> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -111,6 +111,7 @@ impl WindowsWindowInner {
|
|||
WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam),
|
||||
WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true),
|
||||
WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
|
||||
DM_POINTERHITTEST => self.handle_dm_pointer_hit_test(wparam),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(n) = handled {
|
||||
|
|
@ -758,6 +759,10 @@ impl WindowsWindowInner {
|
|||
self.state.scale_factor.set(new_scale_factor);
|
||||
self.state.border_offset.update(handle).log_err();
|
||||
|
||||
self.state
|
||||
.direct_manipulation
|
||||
.set_scale_factor(new_scale_factor);
|
||||
|
||||
if is_maximized {
|
||||
// Get the monitor and its work area at the new DPI
|
||||
let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST) };
|
||||
|
|
@ -1139,10 +1144,27 @@ impl WindowsWindowInner {
|
|||
Some(0)
|
||||
}
|
||||
|
||||
fn handle_dm_pointer_hit_test(&self, wparam: WPARAM) -> Option<isize> {
|
||||
self.state.direct_manipulation.on_pointer_hit_test(wparam);
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn draw_window(&self, handle: HWND, force_render: bool) -> Option<isize> {
|
||||
let mut request_frame = self.state.callbacks.request_frame.take()?;
|
||||
|
||||
self.state.direct_manipulation.update();
|
||||
|
||||
let events = self.state.direct_manipulation.drain_events();
|
||||
if !events.is_empty() {
|
||||
if let Some(mut func) = self.state.callbacks.input.take() {
|
||||
for event in events {
|
||||
func(event);
|
||||
}
|
||||
self.state.callbacks.input.set(Some(func));
|
||||
}
|
||||
}
|
||||
|
||||
if force_render {
|
||||
// Re-enable drawing after a device loss recovery. The forced render
|
||||
// will rebuild the scene with fresh atlas textures.
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod clipboard;
|
||||
mod destination_list;
|
||||
mod direct_manipulation;
|
||||
mod direct_write;
|
||||
mod directx_atlas;
|
||||
mod directx_devices;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use windows::{
|
|||
core::*,
|
||||
};
|
||||
|
||||
use crate::direct_manipulation::DirectManipulationHandler;
|
||||
use crate::*;
|
||||
use gpui::*;
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ pub struct WindowsWindowState {
|
|||
pub last_reported_modifiers: Cell<Option<Modifiers>>,
|
||||
pub last_reported_capslock: Cell<Option<Capslock>>,
|
||||
pub hovered: Cell<bool>,
|
||||
pub direct_manipulation: DirectManipulationHandler,
|
||||
|
||||
pub renderer: RefCell<DirectXRenderer>,
|
||||
|
||||
|
|
@ -131,6 +133,9 @@ impl WindowsWindowState {
|
|||
let fullscreen = None;
|
||||
let initial_placement = None;
|
||||
|
||||
let direct_manipulation = DirectManipulationHandler::new(hwnd, scale_factor)
|
||||
.context("initializing Direct Manipulation")?;
|
||||
|
||||
Ok(Self {
|
||||
origin: Cell::new(origin),
|
||||
logical_size: Cell::new(logical_size),
|
||||
|
|
@ -157,6 +162,7 @@ impl WindowsWindowState {
|
|||
initial_placement: Cell::new(initial_placement),
|
||||
hwnd,
|
||||
invalidate_devices,
|
||||
direct_manipulation,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,12 @@ use std::path::Path;
|
|||
use anyhow::Context as _;
|
||||
use editor::{EditorSettings, items::entry_git_aware_label_color};
|
||||
use file_icons::FileIcons;
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
use gpui::PinchEvent;
|
||||
use gpui::{
|
||||
AnyElement, App, Bounds, Context, DispatchPhase, Element, ElementId, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, Font, GlobalElementId, InspectorElementId, InteractiveElement,
|
||||
IntoElement, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
ParentElement, Pixels, Point, Render, ScrollDelta, ScrollWheelEvent, Style, Styled, Task,
|
||||
WeakEntity, Window, actions, checkerboard, div, img, point, px, size,
|
||||
ParentElement, PinchEvent, Pixels, Point, Render, ScrollDelta, ScrollWheelEvent, Style, Styled,
|
||||
Task, WeakEntity, Window, actions, checkerboard, div, img, point, px, size,
|
||||
};
|
||||
use language::File as _;
|
||||
use persistence::ImageViewerDb;
|
||||
|
|
@ -263,7 +261,6 @@ impl ImageView {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
fn handle_pinch(&mut self, event: &PinchEvent, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
let zoom_factor = 1.0 + event.delta;
|
||||
self.set_zoom(self.zoom_level * zoom_factor, Some(event.position), cx);
|
||||
|
|
@ -685,7 +682,6 @@ impl Render for ImageView {
|
|||
.relative()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child({
|
||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||
let container = div()
|
||||
.id("image-container")
|
||||
.size_full()
|
||||
|
|
@ -704,24 +700,6 @@ impl Render for ImageView {
|
|||
.on_mouse_move(cx.listener(Self::handle_mouse_move))
|
||||
.child(ImageContentElement::new(cx.entity()));
|
||||
|
||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||
let container = div()
|
||||
.id("image-container")
|
||||
.size_full()
|
||||
.overflow_hidden()
|
||||
.cursor(if self.is_dragging() {
|
||||
gpui::CursorStyle::ClosedHand
|
||||
} else {
|
||||
gpui::CursorStyle::OpenHand
|
||||
})
|
||||
.on_scroll_wheel(cx.listener(Self::handle_scroll_wheel))
|
||||
.on_mouse_down(MouseButton::Left, cx.listener(Self::handle_mouse_down))
|
||||
.on_mouse_down(MouseButton::Middle, cx.listener(Self::handle_mouse_down))
|
||||
.on_mouse_up(MouseButton::Left, cx.listener(Self::handle_mouse_up))
|
||||
.on_mouse_up(MouseButton::Middle, cx.listener(Self::handle_mouse_up))
|
||||
.on_mouse_move(cx.listener(Self::handle_mouse_move))
|
||||
.child(ImageContentElement::new(cx.entity()));
|
||||
|
||||
container
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue