windows: Return client size and position from `window_bounds` (#14228)

张小白 created

This is a follow up of #14218 , since we open the window based on the
size of the client area, `window_bounds` should also return the size of
the client area to maintain consistency.

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/events.rs |  14 ++
crates/gpui/src/platform/windows/window.rs | 123 ++++++++++++++++-------
2 files changed, 94 insertions(+), 43 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/events.rs 🔗

@@ -82,7 +82,7 @@ pub(crate) fn handle_msg(
         WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
         WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
         WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
-        WM_SETTINGCHANGE => handle_system_settings_changed(state_ptr),
+        WM_SETTINGCHANGE => handle_system_settings_changed(handle, state_ptr),
         CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
         _ => None,
     };
@@ -732,7 +732,10 @@ fn handle_dpi_changed_msg(
     state_ptr: Rc<WindowsWindowStatePtr>,
 ) -> Option<isize> {
     let new_dpi = wparam.loword() as f32;
-    state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
+    let mut lock = state_ptr.state.borrow_mut();
+    lock.scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
+    lock.border_offset.udpate(handle).log_err();
+    drop(lock);
 
     let rect = unsafe { &*(lparam.0 as *const RECT) };
     let width = rect.right - rect.left;
@@ -1050,12 +1053,17 @@ fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Op
     Some(1)
 }
 
-fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
+fn handle_system_settings_changed(
+    handle: HWND,
+    state_ptr: Rc<WindowsWindowStatePtr>,
+) -> Option<isize> {
     let mut lock = state_ptr.state.borrow_mut();
     // mouse wheel
     lock.system_settings.mouse_wheel_settings.update();
     // mouse double click
     lock.click_state.system_update();
+    // window border offset
+    lock.border_offset.udpate(handle).log_err();
     Some(0)
 }
 

crates/gpui/src/platform/windows/window.rs 🔗

@@ -35,6 +35,7 @@ pub struct WindowsWindowState {
     pub origin: Point<Pixels>,
     pub logical_size: Size<Pixels>,
     pub fullscreen_restore_bounds: Bounds<Pixels>,
+    pub border_offset: WindowBorderOffset,
     pub scale_factor: f32,
 
     pub callbacks: Callbacks,
@@ -82,6 +83,7 @@ impl WindowsWindowState {
             origin,
             size: logical_size,
         };
+        let border_offset = WindowBorderOffset::default();
         let renderer = windows_renderer::windows_renderer(hwnd, transparent);
         let callbacks = Callbacks::default();
         let input_handler = None;
@@ -94,6 +96,7 @@ impl WindowsWindowState {
             origin,
             logical_size,
             fullscreen_restore_bounds,
+            border_offset,
             scale_factor,
             callbacks,
             input_handler,
@@ -124,7 +127,8 @@ impl WindowsWindowState {
         }
     }
 
-    fn window_bounds(&self) -> WindowBounds {
+    // Calculate the bounds used for saving and whether the window is maximized.
+    fn calculate_window_bounds(&self) -> (Bounds<Pixels>, bool) {
         let placement = unsafe {
             let mut placement = WINDOWPLACEMENT {
                 length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
@@ -133,22 +137,22 @@ impl WindowsWindowState {
             GetWindowPlacement(self.hwnd, &mut placement).log_err();
             placement
         };
-        let physical_size = size(
-            DevicePixels(placement.rcNormalPosition.right - placement.rcNormalPosition.left),
-            DevicePixels(placement.rcNormalPosition.bottom - placement.rcNormalPosition.top),
-        );
-        let bounds = Bounds {
-            origin: logical_point(
-                placement.rcNormalPosition.left as f32,
-                placement.rcNormalPosition.top as f32,
+        (
+            calculate_client_rect(
+                placement.rcNormalPosition,
+                self.border_offset,
                 self.scale_factor,
             ),
-            size: physical_size.to_pixels(self.scale_factor),
-        };
+            placement.showCmd == SW_SHOWMAXIMIZED.0 as u32,
+        )
+    }
+
+    fn window_bounds(&self) -> WindowBounds {
+        let (bounds, maximized) = self.calculate_window_bounds();
 
         if self.is_fullscreen() {
             WindowBounds::Fullscreen(self.fullscreen_restore_bounds)
-        } else if placement.showCmd == SW_SHOWMAXIMIZED.0 as u32 {
+        } else if maximized {
             WindowBounds::Maximized(bounds)
         } else {
             WindowBounds::Windowed(bounds)
@@ -308,7 +312,6 @@ impl WindowsWindow {
         };
         let state_ptr = Rc::clone(context.inner.as_ref().unwrap());
         register_drag_drop(state_ptr.clone());
-        let wnd = Self(state_ptr);
 
         unsafe {
             let mut placement = WINDOWPLACEMENT {
@@ -322,13 +325,16 @@ impl WindowsWindow {
             } else {
                 display.default_bounds()
             };
-            let bounds = bounds.to_device_pixels(wnd.0.state.borrow().scale_factor);
-            placement.rcNormalPosition = calcualte_window_position(bounds, raw_hwnd).unwrap();
+            let mut lock = state_ptr.state.borrow_mut();
+            let bounds = bounds.to_device_pixels(lock.scale_factor);
+            lock.border_offset.udpate(raw_hwnd).unwrap();
+            placement.rcNormalPosition = calcualte_window_rect(bounds, lock.border_offset);
+            drop(lock);
             SetWindowPlacement(raw_hwnd, &placement).log_err();
         }
         unsafe { ShowWindow(raw_hwnd, SW_SHOW).ok().log_err() };
 
-        wnd
+        Self(state_ptr)
     }
 }
 
@@ -544,10 +550,6 @@ impl PlatformWindow for WindowsWindow {
             .executor
             .spawn(async move {
                 let mut lock = state_ptr.state.borrow_mut();
-                lock.fullscreen_restore_bounds = Bounds {
-                    origin: lock.origin,
-                    size: lock.logical_size,
-                };
                 let StyleAndBounds {
                     style,
                     x,
@@ -557,6 +559,8 @@ impl PlatformWindow for WindowsWindow {
                 } = if let Some(state) = lock.fullscreen.take() {
                     state
                 } else {
+                    let (window_bounds, _) = lock.calculate_window_bounds();
+                    lock.fullscreen_restore_bounds = window_bounds;
                     let style =
                         WINDOW_STYLE(unsafe { get_window_long(state_ptr.hwnd, GWL_STYLE) } as _);
                     let mut rc = RECT::default();
@@ -861,6 +865,32 @@ struct StyleAndBounds {
     cy: i32,
 }
 
+#[derive(Debug, Default, Clone, Copy)]
+pub(crate) struct WindowBorderOffset {
+    width_offset: i32,
+    height_offset: i32,
+}
+
+impl WindowBorderOffset {
+    pub(crate) fn udpate(&mut self, hwnd: HWND) -> anyhow::Result<()> {
+        let window_rect = unsafe {
+            let mut rect = std::mem::zeroed();
+            GetWindowRect(hwnd, &mut rect)?;
+            rect
+        };
+        let client_rect = unsafe {
+            let mut rect = std::mem::zeroed();
+            GetClientRect(hwnd, &mut rect)?;
+            rect
+        };
+        self.width_offset =
+            (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
+        self.height_offset =
+            (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
+        Ok(())
+    }
+}
+
 fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
     const CLASS_NAME: PCWSTR = w!("Zed::Window");
 
@@ -955,36 +985,49 @@ fn register_drag_drop(state_ptr: Rc<WindowsWindowStatePtr>) {
     };
 }
 
-fn calcualte_window_position(bounds: Bounds<DevicePixels>, hwnd: HWND) -> anyhow::Result<RECT> {
+fn calcualte_window_rect(bounds: Bounds<DevicePixels>, border_offset: WindowBorderOffset) -> RECT {
+    // NOTE:
+    // The reason that not using `AdjustWindowRectEx()` here is
+    // that the size reported by this function is incorrect.
+    // You can test it, and there are similar discussions online.
+    // See: https://stackoverflow.com/questions/12423584/how-to-set-exact-client-size-for-overlapped-window-winapi
+    //
+    // So we manually calculate these values here.
     let mut rect = RECT {
         left: bounds.left().0,
         top: bounds.top().0,
         right: bounds.right().0,
         bottom: bounds.bottom().0,
     };
-    let window_rect = unsafe {
-        let mut rect = std::mem::zeroed();
-        GetWindowRect(hwnd, &mut rect)?;
-        rect
-    };
-    let client_rect = unsafe {
-        let mut rect = std::mem::zeroed();
-        GetClientRect(hwnd, &mut rect)?;
-        rect
-    };
-    let width_offset =
-        (window_rect.right - window_rect.left) - (client_rect.right - client_rect.left);
-    let height_offset =
-        (window_rect.bottom - window_rect.top) - (client_rect.bottom - client_rect.top);
-    let left_offset = width_offset / 2;
-    let top_offset = height_offset / 2;
-    let right_offset = width_offset - left_offset;
-    let bottom_offet = height_offset - top_offset;
+    let left_offset = border_offset.width_offset / 2;
+    let top_offset = border_offset.height_offset / 2;
+    let right_offset = border_offset.width_offset - left_offset;
+    let bottom_offet = border_offset.height_offset - top_offset;
     rect.left -= left_offset;
     rect.top -= top_offset;
     rect.right += right_offset;
     rect.bottom += bottom_offet;
-    Ok(rect)
+    rect
+}
+
+fn calculate_client_rect(
+    rect: RECT,
+    border_offset: WindowBorderOffset,
+    scale_factor: f32,
+) -> Bounds<Pixels> {
+    let left_offset = border_offset.width_offset / 2;
+    let top_offset = border_offset.height_offset / 2;
+    let right_offset = border_offset.width_offset - left_offset;
+    let bottom_offet = border_offset.height_offset - top_offset;
+    let left = rect.left + left_offset;
+    let top = rect.top + top_offset;
+    let right = rect.right - right_offset;
+    let bottom = rect.bottom - bottom_offet;
+    let physical_size = size(DevicePixels(right - left), DevicePixels(bottom - top));
+    Bounds {
+        origin: logical_point(left as f32, top as f32, scale_factor),
+        size: physical_size.to_pixels(scale_factor),
+    }
 }
 
 // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew