Make drag and double click enabled on the whole title bar on macOS (#41839)

Ivan Pasquariello created

Closes #4947

Taken inspiration from @tasuren implementation, plus the addition for
the double click enabled on the whole title bar too to
maximizes/restores the window.

I was not able to test the application on Linux, no need to test on
Windows since the feature is enabled by the OS.

Release Notes:

- Fixed title bar not fully draggable on macOS
- Fixed not being able to maximizes/restores the window with double
click on the whole title bar on macOS

Change summary

crates/gpui/src/platform/mac/window.rs     | 11 +++
crates/gpui/src/window.rs                  |  1 
crates/title_bar/src/platform_title_bar.rs | 77 ++++++++++++-----------
3 files changed, 53 insertions(+), 36 deletions(-)

Detailed changes

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

@@ -1543,6 +1543,17 @@ impl PlatformWindow for MacWindow {
             })
             .detach();
     }
+
+    fn start_window_move(&self) {
+        let this = self.0.lock();
+        let window = this.native_window;
+
+        unsafe {
+            let app = NSApplication::sharedApplication(nil);
+            let mut event: id = msg_send![app, currentEvent];
+            let _: () = msg_send![window, performWindowDragWithEvent: event];
+        }
+    }
 }
 
 impl rwh::HasWindowHandle for MacWindow {

crates/gpui/src/window.rs 🔗

@@ -1819,6 +1819,7 @@ impl Window {
         self.platform_window.show_window_menu(position)
     }
 
+    /// Handle window movement for Linux and macOS.
     /// Tells the compositor to take control of window movement (Wayland and X11)
     ///
     /// Events may not be received during a move operation.

crates/title_bar/src/platform_title_bar.rs 🔗

@@ -77,6 +77,47 @@ impl Render for PlatformTitleBar {
             .window_control_area(WindowControlArea::Drag)
             .w_full()
             .h(height)
+            .map(|this| {
+                this.on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
+                    this.should_move = false;
+                }))
+                .on_mouse_up(
+                    gpui::MouseButton::Left,
+                    cx.listener(move |this, _ev, _window, _cx| {
+                        this.should_move = false;
+                    }),
+                )
+                .on_mouse_down(
+                    gpui::MouseButton::Left,
+                    cx.listener(move |this, _ev, _window, _cx| {
+                        this.should_move = true;
+                    }),
+                )
+                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
+                    if this.should_move {
+                        this.should_move = false;
+                        window.start_window_move();
+                    }
+                }))
+            })
+            .map(|this| {
+                // Note: On Windows the title bar behavior is handled by the platform implementation.
+                this.id(self.id.clone())
+                    .when(self.platform_style == PlatformStyle::Mac, |this| {
+                        this.on_click(|event, window, _| {
+                            if event.click_count() == 2 {
+                                window.titlebar_double_click();
+                            }
+                        })
+                    })
+                    .when(self.platform_style == PlatformStyle::Linux, |this| {
+                        this.on_click(|event, window, _| {
+                            if event.click_count() == 2 {
+                                window.zoom_window();
+                            }
+                        })
+                    })
+            })
             .map(|this| {
                 if window.is_fullscreen() {
                     this.pl_2()
@@ -112,21 +153,6 @@ impl Render for PlatformTitleBar {
                     .justify_between()
                     .overflow_x_hidden()
                     .w_full()
-                    // Note: On Windows the title bar behavior is handled by the platform implementation.
-                    .when(self.platform_style == PlatformStyle::Mac, |this| {
-                        this.on_click(|event, window, _| {
-                            if event.click_count() == 2 {
-                                window.titlebar_double_click();
-                            }
-                        })
-                    })
-                    .when(self.platform_style == PlatformStyle::Linux, |this| {
-                        this.on_click(|event, window, _| {
-                            if event.click_count() == 2 {
-                                window.zoom_window();
-                            }
-                        })
-                    })
                     .children(children),
             )
             .when(!window.is_fullscreen(), |title_bar| {
@@ -142,27 +168,6 @@ impl Render for PlatformTitleBar {
                                             window.show_window_menu(ev.position)
                                         })
                                 })
-                                .on_mouse_move(cx.listener(move |this, _ev, window, _| {
-                                    if this.should_move {
-                                        this.should_move = false;
-                                        window.start_window_move();
-                                    }
-                                }))
-                                .on_mouse_down_out(cx.listener(move |this, _ev, _window, _cx| {
-                                    this.should_move = false;
-                                }))
-                                .on_mouse_up(
-                                    MouseButton::Left,
-                                    cx.listener(move |this, _ev, _window, _cx| {
-                                        this.should_move = false;
-                                    }),
-                                )
-                                .on_mouse_down(
-                                    MouseButton::Left,
-                                    cx.listener(move |this, _ev, _window, _cx| {
-                                        this.should_move = true;
-                                    }),
-                                )
                         } else {
                             title_bar
                         }