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
This commit is contained in:
张小白 2024-03-20 03:39:36 +08:00 committed by GitHub
parent 2c36652be2
commit 086f4e63c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -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<Point<GlobalPixels>>,
size: Cell<Size<GlobalPixels>>,
mouse_position: Cell<Point<Pixels>>,
physical_size: Cell<Size<GlobalPixels>>,
scale_factor: Cell<f32>,
input_handler: Cell<Option<PlatformInputHandler>>,
renderer: RefCell<BladeRenderer>,
callbacks: RefCell<Callbacks>,
platform_inner: Rc<WindowsPlatformInner>,
pub(crate) handle: AnyWindowHandle,
scale_factor: f32,
hide_title_bar: bool,
display: RefCell<Rc<WindowsDisplay>>,
}
@ -62,12 +62,16 @@ impl WindowsWindowInner {
hide_title_bar: bool,
display: Rc<WindowsDisplay>,
) -> 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<RECT> {
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<GlobalPixels> {
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<Pixels> {
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<Pixels> {
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<String> {
}
}
#[inline]
fn logical_size(physical_size: Size<GlobalPixels>, scale_factor: f32) -> Size<Pixels> {
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<Pixels> {
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;