mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-26 03:59:55 +00:00
windows: implement mouse double click event (#9642)
Release Notes: - N/A
This commit is contained in:
parent
fb6cff89d7
commit
f495ee0848
1 changed files with 140 additions and 2 deletions
|
@ -10,6 +10,7 @@ use std::{
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
sync::{Arc, Once},
|
sync::{Arc, Once},
|
||||||
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ::util::ResultExt;
|
use ::util::ResultExt;
|
||||||
|
@ -52,6 +53,7 @@ pub(crate) struct WindowsWindowInner {
|
||||||
hide_title_bar: bool,
|
hide_title_bar: bool,
|
||||||
display: RefCell<Rc<WindowsDisplay>>,
|
display: RefCell<Rc<WindowsDisplay>>,
|
||||||
last_ime_input: RefCell<Option<String>>,
|
last_ime_input: RefCell<Option<String>>,
|
||||||
|
click_state: RefCell<ClickState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsWindowInner {
|
impl WindowsWindowInner {
|
||||||
|
@ -112,6 +114,7 @@ impl WindowsWindowInner {
|
||||||
let callbacks = RefCell::new(Callbacks::default());
|
let callbacks = RefCell::new(Callbacks::default());
|
||||||
let display = RefCell::new(display);
|
let display = RefCell::new(display);
|
||||||
let last_ime_input = RefCell::new(None);
|
let last_ime_input = RefCell::new(None);
|
||||||
|
let click_state = RefCell::new(ClickState::new());
|
||||||
Self {
|
Self {
|
||||||
hwnd,
|
hwnd,
|
||||||
origin,
|
origin,
|
||||||
|
@ -125,6 +128,7 @@ impl WindowsWindowInner {
|
||||||
hide_title_bar,
|
hide_title_bar,
|
||||||
display,
|
display,
|
||||||
last_ime_input,
|
last_ime_input,
|
||||||
|
click_state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,12 +592,14 @@ impl WindowsWindowInner {
|
||||||
if let Some(callback) = callbacks.input.as_mut() {
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
let x = lparam.signed_loword() as f32;
|
let x = lparam.signed_loword() as f32;
|
||||||
let y = lparam.signed_hiword() as f32;
|
let y = lparam.signed_hiword() as f32;
|
||||||
|
let physical_point = point(GlobalPixels(x), GlobalPixels(y));
|
||||||
|
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||||
let scale_factor = self.scale_factor.get();
|
let scale_factor = self.scale_factor.get();
|
||||||
let event = MouseDownEvent {
|
let event = MouseDownEvent {
|
||||||
button,
|
button,
|
||||||
position: logical_point(x, y, scale_factor),
|
position: logical_point(x, y, scale_factor),
|
||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
click_count: 1,
|
click_count,
|
||||||
first_mouse: false,
|
first_mouse: false,
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||||
|
@ -1010,12 +1016,17 @@ impl WindowsWindowInner {
|
||||||
y: lparam.signed_hiword().into(),
|
y: lparam.signed_hiword().into(),
|
||||||
};
|
};
|
||||||
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
|
||||||
|
let physical_point = point(
|
||||||
|
GlobalPixels(cursor_point.x as f32),
|
||||||
|
GlobalPixels(cursor_point.y as f32),
|
||||||
|
);
|
||||||
|
let click_count = self.click_state.borrow_mut().update(button, physical_point);
|
||||||
let scale_factor = self.scale_factor.get();
|
let scale_factor = self.scale_factor.get();
|
||||||
let event = MouseDownEvent {
|
let event = MouseDownEvent {
|
||||||
button,
|
button,
|
||||||
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
|
||||||
modifiers: self.current_modifiers(),
|
modifiers: self.current_modifiers(),
|
||||||
click_count: 1,
|
click_count,
|
||||||
first_mouse: false,
|
first_mouse: false,
|
||||||
};
|
};
|
||||||
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
if callback(PlatformInput::MouseDown(event)).default_prevented {
|
||||||
|
@ -1595,6 +1606,48 @@ impl IDropTarget_Impl for WindowsDragDropHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ClickState {
|
||||||
|
button: MouseButton,
|
||||||
|
last_click: Instant,
|
||||||
|
last_position: Point<GlobalPixels>,
|
||||||
|
current_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClickState {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ClickState {
|
||||||
|
button: MouseButton::Left,
|
||||||
|
last_click: Instant::now(),
|
||||||
|
last_position: Point::default(),
|
||||||
|
current_count: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// update self and return the needed click count
|
||||||
|
pub fn update(&mut self, button: MouseButton, new_position: Point<GlobalPixels>) -> usize {
|
||||||
|
if self.button == button && self.is_double_click(new_position) {
|
||||||
|
self.current_count += 1;
|
||||||
|
} else {
|
||||||
|
self.current_count = 1;
|
||||||
|
}
|
||||||
|
self.last_click = Instant::now();
|
||||||
|
self.last_position = new_position;
|
||||||
|
self.button = button;
|
||||||
|
|
||||||
|
self.current_count
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_double_click(&self, new_position: Point<GlobalPixels>) -> bool {
|
||||||
|
let diff = self.last_position - new_position;
|
||||||
|
|
||||||
|
self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
|
||||||
|
&& diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
|
||||||
|
&& diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
|
fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
|
||||||
const CLASS_NAME: PCWSTR = w!("Zed::Window");
|
const CLASS_NAME: PCWSTR = w!("Zed::Window");
|
||||||
|
|
||||||
|
@ -1739,3 +1792,88 @@ fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
|
||||||
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
|
// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
|
||||||
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
|
||||||
|
const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
|
||||||
|
const DOUBLE_CLICK_SPATIAL_TOLERANCE: f32 = 4.0;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ClickState;
|
||||||
|
use crate::{point, GlobalPixels, MouseButton};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_double_click_interval() {
|
||||||
|
let mut state = ClickState::new();
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Right,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
state.last_click -= Duration::from_millis(700);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_double_click_spatial_tolerance() {
|
||||||
|
let mut state = ClickState::new();
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(-3.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Left,
|
||||||
|
point(GlobalPixels(0.0), GlobalPixels(3.0))
|
||||||
|
),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Right,
|
||||||
|
point(GlobalPixels(3.0), GlobalPixels(2.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.update(
|
||||||
|
MouseButton::Right,
|
||||||
|
point(GlobalPixels(10.0), GlobalPixels(0.0))
|
||||||
|
),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue