From 086f4e63c5c2c7287e3322728d7d2f29386b7192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Wed, 20 Mar 2024 03:39:36 +0800 Subject: [PATCH] windows: Properly handle `DPI` (#9456) As I mentioned before, there are the following issues with how GPUI handles scale factors greater than 1.0: 1. The title bar buttons do not function correctly, with minimizing button performing maximization and maximizing button performing closure. 2. As discussed in #8809, setting a scale factor greater than 1.0 causes GPUI's drawing content to be pushed off the screen. This PR introduces `LogicalSize` and `PhysicalSize` to differentiate between coordinate systems for proper GPUI rendering, and now scale factors above 1.5 are working correctly. `Zed` with a scale factor equals 1.5, and change between different scale factors: https://github.com/zed-industries/zed/assets/14981363/3348536d-8bd3-41dd-82f6-052723312a5b Release Notes: - N/A --- crates/gpui/src/platform/windows/window.rs | 219 +++++++++++++-------- 1 file changed, 139 insertions(+), 80 deletions(-) diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 8dae41bb8bfe4..8c4b0081e5e23 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -13,6 +13,7 @@ use std::{ }; use ::util::ResultExt; +use anyhow::Context; use blade_graphics as gpu; use futures::channel::oneshot::{self, Receiver}; use itertools::Itertools; @@ -41,14 +42,13 @@ use crate::*; pub(crate) struct WindowsWindowInner { hwnd: HWND, origin: Cell>, - size: Cell>, - mouse_position: Cell>, + physical_size: Cell>, + scale_factor: Cell, input_handler: Cell>, renderer: RefCell, callbacks: RefCell, platform_inner: Rc, pub(crate) handle: AnyWindowHandle, - scale_factor: f32, hide_title_bar: bool, display: RefCell>, } @@ -62,12 +62,16 @@ impl WindowsWindowInner { hide_title_bar: bool, display: Rc, ) -> Self { - let origin = Cell::new(Point::new((cs.x as f64).into(), (cs.y as f64).into())); - let size = Cell::new(Size { - width: (cs.cx as f64).into(), - height: (cs.cy as f64).into(), + let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32; + let origin = Cell::new(Point { + x: GlobalPixels(cs.x as f32), + y: GlobalPixels(cs.y as f32), }); - let mouse_position = Cell::new(Point::default()); + let physical_size = Cell::new(Size { + width: GlobalPixels(cs.cx as f32), + height: GlobalPixels(cs.cy as f32), + }); + let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32); let input_handler = Cell::new(None); struct RawWindow { hwnd: *mut c_void, @@ -109,14 +113,13 @@ impl WindowsWindowInner { Self { hwnd, origin, - size, - mouse_position, + physical_size, + scale_factor, input_handler, renderer, callbacks, platform_inner, handle, - scale_factor: 1.0, hide_title_bar, display, } @@ -133,6 +136,7 @@ impl WindowsWindowInner { fn get_titlebar_rect(&self) -> anyhow::Result { let top_and_bottom_borders = 2; + let scale_factor = self.scale_factor.get(); let theme = unsafe { OpenThemeData(self.hwnd, w!("WINDOW")) }; let title_bar_size = unsafe { GetThemePartSize( @@ -147,7 +151,7 @@ impl WindowsWindowInner { unsafe { CloseThemeData(theme) }?; let mut height = - (title_bar_size.cy as f32 * self.scale_factor).round() as i32 + top_and_bottom_borders; + (title_bar_size.cy as f32 * scale_factor).round() as i32 + top_and_bottom_borders; if self.is_maximized() { let dpi = unsafe { GetDpiForWindow(self.hwnd) }; @@ -189,7 +193,7 @@ impl WindowsWindowInner { WM_MOVE => self.handle_move_msg(lparam), WM_SIZE => self.handle_size_msg(lparam), WM_NCCALCSIZE => self.handle_calc_client_size(msg, wparam, lparam), - WM_DPICHANGED => self.handle_dpi_changed_msg(msg, wparam, lparam), + WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam), WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam), WM_PAINT => self.handle_paint_msg(), WM_CLOSE => self.handle_close_msg(msg, wparam, lparam), @@ -255,12 +259,15 @@ impl WindowsWindowInner { } fn handle_move_msg(&self, lparam: LPARAM) -> LRESULT { - let x = lparam.signed_loword() as f64; - let y = lparam.signed_hiword() as f64; - self.origin.set(Point::new(x.into(), y.into())); - let size = self.size.get(); - let center_x = x as f32 + size.width.0 / 2.0; - let center_y = y as f32 + size.height.0 / 2.0; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + self.origin.set(Point { + x: GlobalPixels(x), + y: GlobalPixels(y), + }); + let size = self.physical_size.get(); + let center_x = x + size.width.0 / 2.0; + let center_y = y + size.height.0 / 2.0; let monitor_bounds = self.display.borrow().bounds(); if center_x < monitor_bounds.left().0 || center_x > monitor_bounds.right().0 @@ -282,23 +289,22 @@ impl WindowsWindowInner { } fn handle_size_msg(&self, lparam: LPARAM) -> LRESULT { - let width = lparam.loword().max(1) as f64; - let height = lparam.hiword().max(1) as f64; - self.renderer - .borrow_mut() - .update_drawable_size(Size { width, height }); - let width = width.into(); - let height = height.into(); - self.size.set(Size { width, height }); + let width = lparam.loword().max(1) as f32; + let height = lparam.hiword().max(1) as f32; + let scale_factor = self.scale_factor.get(); + let new_physical_size = Size { + width: GlobalPixels(width), + height: GlobalPixels(height), + }; + self.physical_size.set(new_physical_size); + self.renderer.borrow_mut().update_drawable_size(Size { + width: width as f64, + height: height as f64, + }); let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.resize.as_mut() { - callback( - Size { - width: Pixels(width.0), - height: Pixels(height.0), - }, - 1.0, - ); + let logical_size = logical_size(new_physical_size, scale_factor); + callback(logical_size, scale_factor); } self.invalidate_client_area(); LRESULT(0) @@ -351,9 +357,6 @@ impl WindowsWindowInner { } fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> LRESULT { - let x = Pixels::from(lparam.signed_loword() as f32); - let y = Pixels::from(lparam.signed_hiword() as f32); - self.mouse_position.set(Point { x, y }); let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) { @@ -368,8 +371,11 @@ impl WindowsWindowInner { } _ => None, }; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let scale_factor = self.scale_factor.get(); let event = MouseMoveEvent { - position: Point { x, y }, + position: logical_point(x, y, scale_factor), pressed_button, modifiers: self.current_modifiers(), }; @@ -601,11 +607,12 @@ impl WindowsWindowInner { fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { - let x = Pixels::from(lparam.signed_loword() as f32); - let y = Pixels::from(lparam.signed_hiword() as f32); + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let scale_factor = self.scale_factor.get(); let event = MouseDownEvent { button, - position: Point { x, y }, + position: logical_point(x, y, scale_factor), modifiers: self.current_modifiers(), click_count: 1, }; @@ -619,11 +626,12 @@ impl WindowsWindowInner { fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { - let x = Pixels::from(lparam.signed_loword() as f32); - let y = Pixels::from(lparam.signed_hiword() as f32); + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let scale_factor = self.scale_factor.get(); let event = MouseUpEvent { button, - position: Point { x, y }, + position: logical_point(x, y, scale_factor), modifiers: self.current_modifiers(), click_count: 1, }; @@ -637,12 +645,13 @@ impl WindowsWindowInner { fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { - let x = Pixels::from(lparam.signed_loword() as f32); - let y = Pixels::from(lparam.signed_hiword() as f32); let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * self.platform_inner.settings.borrow().wheel_scroll_lines as f32; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let scale_factor = self.scale_factor.get(); let event = crate::ScrollWheelEvent { - position: Point { x, y }, + position: logical_point(x, y, scale_factor), delta: ScrollDelta::Lines(Point { x: 0.0, y: wheel_distance, @@ -659,12 +668,13 @@ impl WindowsWindowInner { fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { - let x = Pixels::from(lparam.signed_loword() as f32); - let y = Pixels::from(lparam.signed_hiword() as f32); let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * self.platform_inner.settings.borrow().wheel_scroll_chars as f32; + let x = lparam.signed_loword() as f32; + let y = lparam.signed_hiword() as f32; + let scale_factor = self.scale_factor.get(); let event = crate::ScrollWheelEvent { - position: Point { x, y }, + position: logical_point(x, y, scale_factor), delta: ScrollDelta::Lines(Point { x: wheel_distance, y: 0.0, @@ -819,12 +829,13 @@ impl WindowsWindowInner { fn handle_create_msg(&self, _lparam: LPARAM) -> LRESULT { let mut size_rect = RECT::default(); unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() }; + let width = size_rect.right - size_rect.left; let height = size_rect.bottom - size_rect.top; - self.size.set(Size { - width: GlobalPixels::from(width as f64), - height: GlobalPixels::from(height as f64), + self.physical_size.set(Size { + width: GlobalPixels(width as f32), + height: GlobalPixels(height as f32), }); if self.hide_title_bar { @@ -847,8 +858,31 @@ impl WindowsWindowInner { LRESULT(0) } - fn handle_dpi_changed_msg(&self, _msg: u32, _wparam: WPARAM, _lparam: LPARAM) -> LRESULT { - LRESULT(1) + fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + let new_dpi = wparam.loword() as f32; + let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32; + self.scale_factor.set(scale_factor); + let rect = unsafe { &*(lparam.0 as *const RECT) }; + let width = rect.right - rect.left; + let height = rect.bottom - rect.top; + // this will emit `WM_SIZE` and `WM_MOVE` right here + // even before this funtion returns + // the new size is handled in `WM_SIZE` + unsafe { + SetWindowPos( + self.hwnd, + None, + rect.left, + rect.top, + width, + height, + SWP_NOZORDER | SWP_NOACTIVATE, + ) + .context("unable to set window position after dpi has changed") + .log_err(); + } + self.invalidate_client_area(); + LRESULT(0) } fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { @@ -910,18 +944,16 @@ impl WindowsWindowInner { return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; } - let mut cursor_point = POINT { - x: lparam.signed_loword().into(), - y: lparam.signed_hiword().into(), - }; - unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let x = Pixels::from(cursor_point.x as f32); - let y = Pixels::from(cursor_point.y as f32); - self.mouse_position.set(Point { x, y }); let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { + let mut cursor_point = POINT { + x: lparam.signed_loword().into(), + y: lparam.signed_hiword().into(), + }; + unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; + let scale_factor = self.scale_factor.get(); let event = MouseMoveEvent { - position: Point { x, y }, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), pressed_button: None, modifiers: self.current_modifiers(), }; @@ -951,11 +983,10 @@ impl WindowsWindowInner { y: lparam.signed_hiword().into(), }; unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let x = Pixels::from(cursor_point.x as f32); - let y = Pixels::from(cursor_point.y as f32); + let scale_factor = self.scale_factor.get(); let event = MouseDownEvent { button, - position: Point { x, y }, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), modifiers: self.current_modifiers(), click_count: 1, }; @@ -990,11 +1021,10 @@ impl WindowsWindowInner { y: lparam.signed_hiword().into(), }; unsafe { ScreenToClient(self.hwnd, &mut cursor_point) }; - let x = Pixels::from(cursor_point.x as f32); - let y = Pixels::from(cursor_point.y as f32); + let scale_factor = self.scale_factor.get(); let event = MouseUpEvent { button, - position: Point { x, y }, + position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor), modifiers: self.current_modifiers(), click_count: 1, }; @@ -1194,7 +1224,7 @@ impl PlatformWindow for WindowsWindow { fn bounds(&self) -> Bounds { Bounds { origin: self.inner.origin.get(), - size: self.inner.size.get(), + size: self.inner.physical_size.get(), } } @@ -1202,18 +1232,19 @@ impl PlatformWindow for WindowsWindow { self.inner.is_maximized() } - // todo(windows) + /// get the logical size of the app's drawable area. + /// + /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as + /// whether the mouse collides with other elements of GPUI). fn content_size(&self) -> Size { - let size = self.inner.size.get(); - Size { - width: size.width.0.into(), - height: size.height.0.into(), - } + logical_size( + self.inner.physical_size.get(), + self.inner.scale_factor.get(), + ) } - // todo(windows) fn scale_factor(&self) -> f32 { - self.inner.scale_factor + self.inner.scale_factor.get() } // todo(windows) @@ -1226,7 +1257,19 @@ impl PlatformWindow for WindowsWindow { } fn mouse_position(&self) -> Point { - self.inner.mouse_position.get() + let point = unsafe { + let mut point: POINT = std::mem::zeroed(); + GetCursorPos(&mut point) + .context("unable to get cursor position") + .log_err(); + ScreenToClient(self.inner.hwnd, &mut point); + point + }; + logical_point( + point.x as f32, + point.y as f32, + self.inner.scale_factor.get(), + ) } // todo(windows) @@ -1657,5 +1700,21 @@ fn oemkey_vkcode_to_string(code: u16) -> Option { } } +#[inline] +fn logical_size(physical_size: Size, scale_factor: f32) -> Size { + Size { + width: px(physical_size.width.0 / scale_factor), + height: px(physical_size.height.0 / scale_factor), + } +} + +#[inline] +fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point { + Point { + x: px(x / scale_factor), + y: px(y / scale_factor), + } +} + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;