Make titlebar taller and position traffic lights accordingly

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/src/platform.rs            |  2 +
gpui/src/platform/mac/window.rs | 63 +++++++++++++++++++++++++++++++++-
zed/src/workspace.rs            |  3 +
3 files changed, 65 insertions(+), 3 deletions(-)

Detailed changes

gpui/src/platform.rs 🔗

@@ -105,6 +105,7 @@ pub struct WindowOptions<'a> {
     pub bounds: RectF,
     pub title: Option<&'a str>,
     pub titlebar_appears_transparent: bool,
+    pub traffic_light_position: Option<Vector2F>,
 }
 
 pub struct PathPromptOptions {
@@ -155,6 +156,7 @@ impl<'a> Default for WindowOptions<'a> {
             bounds: RectF::new(Default::default(), vec2f(1024.0, 768.0)),
             title: Default::default(),
             titlebar_appears_transparent: Default::default(),
+            traffic_light_position: Default::default(),
         }
     }
 }

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

@@ -8,8 +8,8 @@ use crate::{
 use block::ConcreteBlock;
 use cocoa::{
     appkit::{
-        NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
-        NSViewWidthSizable, NSWindow, NSWindowStyleMask,
+        CGPoint, NSApplication, NSBackingStoreBuffered, NSScreen, NSView, NSViewHeightSizable,
+        NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
     },
     base::{id, nil},
     foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString},
@@ -66,6 +66,10 @@ unsafe fn build_classes() {
             sel!(sendEvent:),
             send_event as extern "C" fn(&Object, Sel, id),
         );
+        decl.add_method(
+            sel!(windowDidResize:),
+            window_did_resize as extern "C" fn(&Object, Sel, id),
+        );
         decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel));
         decl.register()
     };
@@ -139,6 +143,7 @@ struct WindowState {
     command_queue: metal::CommandQueue,
     last_fresh_keydown: Option<(Keystroke, String)>,
     layer: id,
+    traffic_light_position: Option<Vector2F>,
 }
 
 impl Window {
@@ -204,12 +209,14 @@ impl Window {
                 command_queue: device.new_command_queue(),
                 last_fresh_keydown: None,
                 layer,
+                traffic_light_position: options.traffic_light_position,
             })));
 
             (*native_window).set_ivar(
                 WINDOW_STATE_IVAR,
                 Rc::into_raw(window.0.clone()) as *const c_void,
             );
+            native_window.setDelegate_(native_window);
             (*native_view).set_ivar(
                 WINDOW_STATE_IVAR,
                 Rc::into_raw(window.0.clone()) as *const c_void,
@@ -243,6 +250,7 @@ impl Window {
             native_window.center();
             native_window.makeKeyAndOrderFront_(nil);
 
+            window.0.borrow().move_traffic_light();
             pool.drain();
 
             window
@@ -343,6 +351,52 @@ impl platform::WindowContext for Window {
     }
 }
 
+impl WindowState {
+    fn move_traffic_light(&self) {
+        if let Some(traffic_light_position) = self.traffic_light_position {
+            let titlebar_height = self.titlebar_height();
+
+            unsafe {
+                let close_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowCloseButton
+                ];
+                let min_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowMiniaturizeButton
+                ];
+                let zoom_button: id = msg_send![
+                    self.native_window,
+                    standardWindowButton: NSWindowButton::NSWindowZoomButton
+                ];
+
+                let mut close_button_frame: CGRect = msg_send![close_button, frame];
+                let mut min_button_frame: CGRect = msg_send![min_button, frame];
+                let mut zoom_button_frame: CGRect = msg_send![zoom_button, frame];
+                let mut origin = vec2f(
+                    traffic_light_position.x(),
+                    titlebar_height
+                        - traffic_light_position.y()
+                        - close_button_frame.size.height as f32,
+                );
+                let button_spacing =
+                    (min_button_frame.origin.x - close_button_frame.origin.x) as f32;
+
+                close_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                let _: () = msg_send![close_button, setFrame: close_button_frame];
+                origin.set_x(origin.x() + button_spacing);
+
+                min_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                let _: () = msg_send![min_button, setFrame: min_button_frame];
+                origin.set_x(origin.x() + button_spacing);
+
+                zoom_button_frame.origin = CGPoint::new(origin.x() as f64, origin.y() as f64);
+                let _: () = msg_send![zoom_button, setFrame: zoom_button_frame];
+            }
+        }
+    }
+}
+
 impl platform::WindowContext for WindowState {
     fn size(&self) -> Vector2F {
         let NSSize { width, height, .. } =
@@ -462,6 +516,11 @@ extern "C" fn send_event(this: &Object, _: Sel, native_event: id) {
     }
 }
 
+extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
+    let window_state = unsafe { get_window_state(this) };
+    window_state.as_ref().borrow().move_traffic_light();
+}
+
 extern "C" fn close_window(this: &Object, _: Sel) {
     unsafe {
         let close_callback = {

zed/src/workspace.rs 🔗

@@ -142,6 +142,7 @@ fn window_options() -> WindowOptions<'static> {
         bounds: RectF::new(vec2f(0., 0.), vec2f(1024., 768.)),
         title: None,
         titlebar_appears_transparent: true,
+        traffic_light_position: Some(vec2f(8., 8.)),
     }
 }
 
@@ -952,7 +953,7 @@ impl View for Workspace {
             Flex::column()
                 .with_child(
                     ConstrainedBox::new(Empty::new().boxed())
-                        .with_height(cx.titlebar_height)
+                        .with_height(32.)
                         .named("titlebar"),
                 )
                 .with_child(