mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-11 05:00:16 +00:00
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:
parent
2c36652be2
commit
086f4e63c5
1 changed files with 139 additions and 80 deletions
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue