diff --git a/winit-win32/src/event_loop.rs b/winit-win32/src/event_loop.rs index a14023f0c..526a5966c 100644 --- a/winit-win32/src/event_loop.rs +++ b/winit-win32/src/event_loop.rs @@ -42,22 +42,23 @@ use windows_sys::Win32::UI::Input::{ use windows_sys::Win32::UI::WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, DestroyWindow, DispatchMessageW, GetClientRect, GetCursorPos, GetMenu, LoadCursorW, MsgWaitForMultipleObjectsEx, PeekMessageW, PostMessageW, - RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, TranslateMessage, - CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, MNC_CLOSE, MSG, - MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT, RI_MOUSE_HWHEEL, - RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, SWP_NOACTIVATE, SWP_NOMOVE, - SWP_NOSIZE, SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, - WMSZ_BOTTOMRIGHT, WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, - WM_CAPTURECHANGED, WM_CLOSE, WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, - WM_EXITSIZEMOVE, WM_GETMINMAXINFO, WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, - WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, - WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, - WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, - WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, - WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, - WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, - WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, - WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, + RegisterClassExW, RegisterWindowMessageA, SetCursor, SetWindowPos, SystemParametersInfoW, + TranslateMessage, CREATESTRUCTW, GWL_STYLE, GWL_USERDATA, HTCAPTION, HTCLIENT, MINMAXINFO, + MNC_CLOSE, MSG, MWMO_INPUTAVAILABLE, NCCALCSIZE_PARAMS, PM_REMOVE, PT_TOUCH, QS_ALLINPUT, + RI_MOUSE_HWHEEL, RI_MOUSE_WHEEL, SC_MINIMIZE, SC_RESTORE, SIZE_MAXIMIZED, + SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SWP_NOACTIVATE, SWP_NOMOVE, SWP_NOSIZE, + SWP_NOZORDER, WHEEL_DELTA, WINDOWPOS, WMSZ_BOTTOM, WMSZ_BOTTOMLEFT, WMSZ_BOTTOMRIGHT, + WMSZ_LEFT, WMSZ_RIGHT, WMSZ_TOP, WMSZ_TOPLEFT, WMSZ_TOPRIGHT, WM_CAPTURECHANGED, WM_CLOSE, + WM_CREATE, WM_DESTROY, WM_DPICHANGED, WM_ENTERSIZEMOVE, WM_EXITSIZEMOVE, WM_GETMINMAXINFO, + WM_IME_COMPOSITION, WM_IME_ENDCOMPOSITION, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, + WM_INPUT, WM_KEYDOWN, WM_KEYUP, WM_KILLFOCUS, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, + WM_MBUTTONUP, WM_MENUCHAR, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_NCACTIVATE, + WM_NCCALCSIZE, WM_NCCREATE, WM_NCDESTROY, WM_NCLBUTTONDOWN, WM_PAINT, WM_POINTERDOWN, + WM_POINTERUP, WM_POINTERUPDATE, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SETCURSOR, WM_SETFOCUS, + WM_SETTINGCHANGE, WM_SIZE, WM_SIZING, WM_SYSCOMMAND, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_TOUCH, + WM_WINDOWPOSCHANGED, WM_WINDOWPOSCHANGING, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSEXW, + WS_EX_LAYERED, WS_EX_NOACTIVATE, WS_EX_TOOLWINDOW, WS_EX_TRANSPARENT, WS_OVERLAPPED, WS_POPUP, + WS_VISIBLE, }; use winit_core::application::ApplicationHandler; use winit_core::cursor::{CustomCursor, CustomCursorSource}; @@ -92,6 +93,13 @@ use crate::window::{InitData, Window}; use crate::window_state::{CursorFlags, ImeState, WindowFlags, WindowState}; use crate::{raw_input, util}; +// This is defined in `winuser.h` as a macro that expands to `UINT_MAX` +const WHEEL_PAGESCROLL: u32 = u32::MAX; +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLLINES +const DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA: isize = 3; +// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa#:~:text=SPI_GETWHEELSCROLLCHARS +const DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA: isize = 3; + pub(crate) struct WindowData { pub window_state: Arc>, pub event_loop_runner: Rc, @@ -1636,9 +1644,26 @@ unsafe fn public_window_callback_inner( update_modifiers(window, userdata); + let scroll_lines_multiplier = if userdata.window_state_lock().use_system_wheel_speed { + let mut scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; + let _ = SystemParametersInfoW( + SPI_GETWHEELSCROLLLINES, + 0, + &mut scroll_lines as *mut isize as *mut c_void, + 0, + ); + if scroll_lines as u32 == WHEEL_PAGESCROLL { + // TODO: figure out how to handle page scrolls + scroll_lines = DEFAULT_SCROLL_LINES_PER_WHEEL_DELTA; + } + scroll_lines + } else { + 1 + }; + userdata.send_window_event(window, WindowEvent::MouseWheel { device_id: None, - delta: LineDelta(0.0, value), + delta: LineDelta(0.0, value * scroll_lines_multiplier as f32), phase: TouchPhase::Moved, }); @@ -1653,9 +1678,23 @@ unsafe fn public_window_callback_inner( update_modifiers(window, userdata); + let scroll_characters_multiplier = + if userdata.window_state_lock().use_system_wheel_speed { + let mut scroll_characters = DEFAULT_SCROLL_CHARACTERS_PER_WHEEL_DELTA; + let _ = SystemParametersInfoW( + SPI_GETWHEELSCROLLCHARS, + 0, + &mut scroll_characters as *mut isize as *mut c_void, + 0, + ); + scroll_characters + } else { + 1 + }; + userdata.send_window_event(window, WindowEvent::MouseWheel { device_id: None, - delta: LineDelta(value, 0.0), + delta: LineDelta(value * scroll_characters_multiplier as f32, 0.0), phase: TouchPhase::Moved, }); diff --git a/winit-win32/src/lib.rs b/winit-win32/src/lib.rs index 4899e45f1..8a9474b51 100644 --- a/winit-win32/src/lib.rs +++ b/winit-win32/src/lib.rs @@ -285,6 +285,15 @@ pub trait WindowExtWindows { /// Supported starting with Windows 11 Build 22000. fn set_corner_preference(&self, preference: CornerPreference); + /// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event + /// should account for scroll speed system settings. + /// + /// The default scroll speed on Windows is 3 lines/characters per scroll, + /// this will be 1 if you set it to false. + /// + /// The default is `true`. + fn set_use_system_scroll_speed(&self, should_use: bool); + /// Get the raw window handle for this [`Window`] without checking for thread affinity. /// /// Window handles in Win32 have a property called "thread affinity" that ties them to their @@ -398,6 +407,11 @@ impl WindowExtWindows for dyn CoreWindow + '_ { window.set_corner_preference(preference) } + fn set_use_system_scroll_speed(&self, should_use: bool) { + let window = self.cast_ref::().unwrap(); + window.set_use_system_scroll_speed(should_use) + } + unsafe fn window_handle_any_thread( &self, ) -> Result, rwh_06::HandleError> { @@ -459,6 +473,7 @@ pub struct WindowAttributesWindows { pub(crate) title_background_color: Option, pub(crate) title_text_color: Option, pub(crate) corner_preference: Option, + pub(crate) use_system_wheel_speed: bool, } impl Default for WindowAttributesWindows { @@ -478,6 +493,7 @@ impl Default for WindowAttributesWindows { title_background_color: None, title_text_color: None, corner_preference: None, + use_system_wheel_speed: true, } } } @@ -610,6 +626,18 @@ impl WindowAttributesWindows { self.corner_preference = Some(corners); self } + + /// Sets if the reported [`winit_core::event::WindowEvent::MouseWheel`] event + /// should account for scroll speed system settings. + /// + /// The default scroll speed on Windows is 3 lines/characters per scroll, + /// this will be 1 if you set it to false. + /// + /// The default is `true`. + pub fn with_use_system_scroll_speed(mut self, should_use: bool) -> Self { + self.use_system_wheel_speed = should_use; + self + } } impl PlatformWindowAttributes for WindowAttributesWindows { diff --git a/winit-win32/src/window.rs b/winit-win32/src/window.rs index 4b1d45513..c1be6c97b 100644 --- a/winit-win32/src/window.rs +++ b/winit-win32/src/window.rs @@ -344,6 +344,11 @@ impl Window { } } + #[inline] + pub fn set_use_system_scroll_speed(&self, should_use: bool) { + self.window_state_lock().use_system_wheel_speed = should_use; + } + fn set_icon(&self, mut new_icon: Icon, icon_type: IconType) { if let Some(icon) = new_icon.cast_ref::() { let icon = match WinIcon::from_rgba(icon) { @@ -1303,6 +1308,7 @@ impl InitData<'_> { win.set_skip_taskbar(self.win_attributes.skip_taskbar); win.set_window_icon(self.attributes.window_icon.clone()); win.set_taskbar_icon(self.win_attributes.taskbar_icon.clone()); + win.set_use_system_scroll_speed(self.win_attributes.use_system_wheel_speed); let attributes = self.attributes.clone(); diff --git a/winit-win32/src/window_state.rs b/winit-win32/src/window_state.rs index 08dfe84e2..78b36e522 100644 --- a/winit-win32/src/window_state.rs +++ b/winit-win32/src/window_state.rs @@ -60,6 +60,8 @@ pub(crate) struct WindowState { pub dragging: bool, pub skip_taskbar: bool, + + pub use_system_wheel_speed: bool, } #[derive(Clone)] @@ -187,6 +189,8 @@ impl WindowState { dragging: false, skip_taskbar: false, + + use_system_wheel_speed: true, } } diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 006ed035d..7f5856fc6 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -256,3 +256,4 @@ changelog entry. - On macOS, fixed redundant `SurfaceResized` event at window creation. - On macOS, don't panic on monitors with unknown bit-depths. - On macOS, fixed crash when closing the window on macOS 26+. +- On Windows, account for mouse wheel lines per scroll setting for `WindowEvent::MouseWheel`.