Add an API for setting a window's title

Max Brunsfeld created

This controls how the window appears in the Window menu.

Change summary

crates/gpui/src/app.rs                   | 22 ++++++++++++++++++++--
crates/gpui/src/platform.rs              |  1 +
crates/gpui/src/platform/mac/platform.rs |  5 +++++
crates/gpui/src/platform/mac/window.rs   |  9 ++++++++-
crates/gpui/src/platform/test.rs         | 10 ++++++++++
5 files changed, 44 insertions(+), 3 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -542,12 +542,23 @@ impl TestAppContext {
         !prompts.is_empty()
     }
 
-    #[cfg(any(test, feature = "test-support"))]
+    pub fn current_window_title(&self, window_id: usize) -> Option<String> {
+        let mut state = self.cx.borrow_mut();
+        let (_, window) = state
+            .presenters_and_platform_windows
+            .get_mut(&window_id)
+            .unwrap();
+        let test_window = window
+            .as_any_mut()
+            .downcast_mut::<platform::test::Window>()
+            .unwrap();
+        test_window.title.clone()
+    }
+
     pub fn leak_detector(&self) -> Arc<Mutex<LeakDetector>> {
         self.cx.borrow().leak_detector()
     }
 
-    #[cfg(any(test, feature = "test-support"))]
     pub fn assert_dropped(&self, handle: impl WeakHandle) {
         self.cx
             .borrow()
@@ -3265,6 +3276,13 @@ impl<'a, T: View> ViewContext<'a, T> {
         self.app.focus(self.window_id, None);
     }
 
+    pub fn set_window_title(&mut self, title: &str) {
+        let window_id = self.window_id();
+        if let Some((_, window)) = self.presenters_and_platform_windows.get_mut(&window_id) {
+            window.set_title(title);
+        }
+    }
+
     pub fn add_model<S, F>(&mut self, build_model: F) -> ModelHandle<S>
     where
         S: Entity,

crates/gpui/src/platform.rs 🔗

@@ -96,6 +96,7 @@ pub trait Window: WindowContext {
     fn on_close(&mut self, callback: Box<dyn FnOnce()>);
     fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
     fn activate(&self);
+    fn set_title(&mut self, title: &str);
 }
 
 pub trait WindowContext {

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

@@ -202,6 +202,11 @@ impl MacForegroundPlatform {
 
             menu_bar_item.setSubmenu_(menu);
             menu_bar.addItem_(menu_bar_item);
+
+            if menu_name == "Window" {
+                let app: id = msg_send![APP_CLASS, sharedApplication];
+                app.setWindowsMenu_(menu);
+            }
         }
 
         menu_bar

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

@@ -386,8 +386,15 @@ impl platform::Window for Window {
     }
 
     fn activate(&self) {
+        unsafe { msg_send![self.0.borrow().native_window, makeKeyAndOrderFront: nil] }
+    }
+
+    fn set_title(&mut self, title: &str) {
         unsafe {
-            let _: () = msg_send![self.0.borrow().native_window, makeKeyAndOrderFront: nil];
+            let app = NSApplication::sharedApplication(nil);
+            let window = self.0.borrow().native_window;
+            let title = ns_string(title);
+            msg_send![app, changeWindowsItem:window title:title filename:false]
         }
     }
 }

crates/gpui/src/platform/test.rs 🔗

@@ -37,6 +37,7 @@ pub struct Window {
     event_handlers: Vec<Box<dyn FnMut(super::Event)>>,
     resize_handlers: Vec<Box<dyn FnMut()>>,
     close_handlers: Vec<Box<dyn FnOnce()>>,
+    pub(crate) title: Option<String>,
     pub(crate) pending_prompts: RefCell<VecDeque<oneshot::Sender<usize>>>,
 }
 
@@ -189,9 +190,14 @@ impl Window {
             close_handlers: Vec::new(),
             scale_factor: 1.0,
             current_scene: None,
+            title: None,
             pending_prompts: Default::default(),
         }
     }
+
+    pub fn title(&self) -> Option<String> {
+        self.title.clone()
+    }
 }
 
 impl super::Dispatcher for Dispatcher {
@@ -248,6 +254,10 @@ impl super::Window for Window {
     }
 
     fn activate(&self) {}
+
+    fn set_title(&mut self, title: &str) {
+        self.title = Some(title.to_string())
+    }
 }
 
 pub fn platform() -> Platform {