gpui(windows): Fix top resize edge being only 1px tall (#43995)

Lukas Wirth created

Closes https://github.com/zed-industries/zed/issues/41693

Release Notes:

- Fixed an issue on windows where the resize area on the title bar is
only 1px tall

Change summary

crates/gpui/src/platform/windows/events.rs | 96 ++++++++++++-----------
1 file changed, 49 insertions(+), 47 deletions(-)

Detailed changes

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

@@ -51,7 +51,7 @@ impl WindowsWindowInner {
             WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam),
             WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam),
             WM_DISPLAYCHANGE => self.handle_display_change_msg(handle),
-            WM_NCHITTEST => self.handle_hit_test_msg(handle, msg, wparam, lparam),
+            WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam),
             WM_PAINT => self.handle_paint_msg(handle),
             WM_CLOSE => self.handle_close_msg(),
             WM_DESTROY => self.handle_destroy_msg(handle),
@@ -834,73 +834,69 @@ impl WindowsWindowInner {
         Some(0)
     }
 
-    fn handle_hit_test_msg(
-        &self,
-        handle: HWND,
-        msg: u32,
-        wparam: WPARAM,
-        lparam: LPARAM,
-    ) -> Option<isize> {
+    fn handle_hit_test_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
         if !self.is_movable || self.state.borrow().is_fullscreen() {
             return None;
         }
 
-        let mut lock = self.state.borrow_mut();
-        if let Some(mut callback) = lock.callbacks.hit_test_window_control.take() {
-            drop(lock);
+        let callback = self
+            .state
+            .borrow_mut()
+            .callbacks
+            .hit_test_window_control
+            .take();
+        let drag_area = if let Some(mut callback) = callback {
             let area = callback();
             self.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
             if let Some(area) = area {
-                return match area {
+                match area {
                     WindowControlArea::Drag => Some(HTCAPTION as _),
-                    WindowControlArea::Close => Some(HTCLOSE as _),
-                    WindowControlArea::Max => Some(HTMAXBUTTON as _),
-                    WindowControlArea::Min => Some(HTMINBUTTON as _),
-                };
+                    WindowControlArea::Close => return Some(HTCLOSE as _),
+                    WindowControlArea::Max => return Some(HTMAXBUTTON as _),
+                    WindowControlArea::Min => return Some(HTMINBUTTON as _),
+                }
+            } else {
+                None
             }
         } else {
-            drop(lock);
-        }
+            None
+        };
 
         if !self.hide_title_bar {
             // If the OS draws the title bar, we don't need to handle hit test messages.
-            return None;
-        }
-
-        // default handler for resize areas
-        let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
-        if matches!(
-            hit.0 as u32,
-            HTNOWHERE
-                | HTRIGHT
-                | HTLEFT
-                | HTTOPLEFT
-                | HTTOP
-                | HTTOPRIGHT
-                | HTBOTTOMRIGHT
-                | HTBOTTOM
-                | HTBOTTOMLEFT
-        ) {
-            return Some(hit.0);
-        }
-
-        if self.state.borrow().is_fullscreen() {
-            return Some(HTCLIENT as _);
+            return drag_area;
         }
 
         let dpi = unsafe { GetDpiForWindow(handle) };
-        let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
-
+        // We do not use the OS title bar, so the default `DefWindowProcW` will only register a 1px edge for resizes
+        // We need to calculate the frame thickness ourselves and do the hit test manually.
+        let frame_y = get_frame_thicknessx(dpi);
+        let frame_x = get_frame_thicknessy(dpi);
         let mut cursor_point = POINT {
             x: lparam.signed_loword().into(),
             y: lparam.signed_hiword().into(),
         };
+
         unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
-        if !self.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y {
-            return Some(HTTOP as _);
+        if !self.state.borrow().is_maximized() && 0 <= cursor_point.y && cursor_point.y <= frame_y {
+            // x-axis actually goes from -frame_x to 0
+            return Some(if cursor_point.x <= 0 {
+                HTTOPLEFT
+            } else {
+                let mut rect = Default::default();
+                unsafe { GetWindowRect(handle, &mut rect) }.log_err();
+                // right and bottom bounds of RECT are exclusive, thus `-1`
+                let right = rect.right - rect.left - 1;
+                // the bounds include the padding frames, so accomodate for both of them
+                if right - 2 * frame_x <= cursor_point.x {
+                    HTTOPRIGHT
+                } else {
+                    HTTOP
+                }
+            } as _);
         }
 
-        Some(HTCLIENT as _)
+        drag_area
     }
 
     fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
@@ -1527,7 +1523,7 @@ fn get_client_area_insets(
     // The top inset is calculated using an empirical formula that I derived through various
     // tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
     let dpi = unsafe { GetDpiForWindow(handle) };
-    let frame_thickness = get_frame_thickness(dpi);
+    let frame_thickness = get_frame_thicknessx(dpi);
     let top_insets = if is_maximized {
         frame_thickness
     } else {
@@ -1548,12 +1544,18 @@ fn get_client_area_insets(
 // borders on Windows:
 // - SM_CXSIZEFRAME: The resize handle.
 // - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
-fn get_frame_thickness(dpi: u32) -> i32 {
+fn get_frame_thicknessx(dpi: u32) -> i32 {
     let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
     let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
     resize_frame_thickness + padding_thickness
 }
 
+fn get_frame_thicknessy(dpi: u32) -> i32 {
+    let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) };
+    let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
+    resize_frame_thickness + padding_thickness
+}
+
 fn notify_frame_changed(handle: HWND) {
     unsafe {
         SetWindowPos(