Route whether or not a window is fullscreen down into GPUI

ForLoveOfCats and Mikayla created

This still needs to be able to invalidate things to be useful
but it's a good first cut at just making the information available
to platform-agnostic code

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

crates/gpui/src/app.rs                 | 91 +++++++++++++++++++++++++++
crates/gpui/src/platform.rs            |  1 
crates/gpui/src/platform/mac/window.rs | 32 +++++++++
crates/gpui/src/platform/test.rs       |  6 +
4 files changed, 128 insertions(+), 2 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -1248,6 +1248,13 @@ impl MutableAppContext {
             .map_or(false, |window| window.is_active)
     }
 
+    pub fn window_is_fullscreen(&self, window_id: usize) -> bool {
+        self.cx
+            .windows
+            .get(&window_id)
+            .map_or(false, |window| window.is_fullscreen)
+    }
+
     pub fn render_view(&mut self, params: RenderParams) -> Result<ElementBox> {
         let window_id = params.window_id;
         let view_id = params.view_id;
@@ -1934,6 +1941,7 @@ impl MutableAppContext {
                     focused_view_id: Some(root_view.id()),
                     is_active: false,
                     invalidation: None,
+                    is_fullscreen: false,
                 },
             );
             root_view.update(this, |view, cx| view.on_focus(cx));
@@ -2010,6 +2018,13 @@ impl MutableAppContext {
             }));
         }
 
+        {
+            let mut app = self.upgrade();
+            window.on_fullscreen(Box::new(move |is_fullscreen| {
+                app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+            }));
+        }
+
         {
             let mut app = self.upgrade();
             window.on_close(Box::new(move || {
@@ -2151,7 +2166,9 @@ impl MutableAppContext {
                             subscription_id,
                             callback,
                         } => self.handle_subscription_effect(entity_id, subscription_id, callback),
+
                         Effect::Event { entity_id, payload } => self.emit_event(entity_id, payload),
+
                         Effect::GlobalSubscription {
                             type_id,
                             subscription_id,
@@ -2161,21 +2178,27 @@ impl MutableAppContext {
                             subscription_id,
                             callback,
                         ),
+
                         Effect::GlobalEvent { payload } => self.emit_global_event(payload),
+
                         Effect::Observation {
                             entity_id,
                             subscription_id,
                             callback,
                         } => self.handle_observation_effect(entity_id, subscription_id, callback),
+
                         Effect::ModelNotification { model_id } => {
                             self.handle_model_notification_effect(model_id)
                         }
+
                         Effect::ViewNotification { window_id, view_id } => {
                             self.handle_view_notification_effect(window_id, view_id)
                         }
+
                         Effect::GlobalNotification { type_id } => {
                             self.handle_global_notification_effect(type_id)
                         }
+
                         Effect::Deferred {
                             callback,
                             after_window_update,
@@ -2186,15 +2209,19 @@ impl MutableAppContext {
                                 callback(self)
                             }
                         }
+
                         Effect::ModelRelease { model_id, model } => {
                             self.handle_entity_release_effect(model_id, model.as_any())
                         }
+
                         Effect::ViewRelease { view_id, view } => {
                             self.handle_entity_release_effect(view_id, view.as_any())
                         }
+
                         Effect::Focus { window_id, view_id } => {
                             self.handle_focus_effect(window_id, view_id);
                         }
+
                         Effect::FocusObservation {
                             view_id,
                             subscription_id,
@@ -2202,6 +2229,7 @@ impl MutableAppContext {
                         } => {
                             self.handle_focus_observation_effect(view_id, subscription_id, callback)
                         }
+
                         Effect::ResizeWindow { window_id } => {
                             if let Some(window) = self.cx.windows.get_mut(&window_id) {
                                 window
@@ -2209,6 +2237,7 @@ impl MutableAppContext {
                                     .get_or_insert(WindowInvalidation::default());
                             }
                         }
+
                         Effect::WindowActivationObservation {
                             window_id,
                             subscription_id,
@@ -2218,10 +2247,17 @@ impl MutableAppContext {
                             subscription_id,
                             callback,
                         ),
+
                         Effect::ActivateWindow {
                             window_id,
                             is_active,
                         } => self.handle_window_activation_effect(window_id, is_active),
+
+                        Effect::FullscreenWindow {
+                            window_id,
+                            is_fullscreen,
+                        } => self.handle_fullscreen_effect(window_id, is_fullscreen),
+
                         Effect::RefreshWindows => {
                             refreshing = true;
                         }
@@ -2294,6 +2330,13 @@ impl MutableAppContext {
             .push_back(Effect::ResizeWindow { window_id });
     }
 
+    fn window_was_fullscreen_changed(&mut self, window_id: usize, is_fullscreen: bool) {
+        self.pending_effects.push_back(Effect::FullscreenWindow {
+            window_id,
+            is_fullscreen,
+        });
+    }
+
     fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) {
         self.pending_effects.push_back(Effect::ActivateWindow {
             window_id,
@@ -2608,13 +2651,36 @@ impl MutableAppContext {
         }
     }
 
+    fn handle_fullscreen_effect(&mut self, window_id: usize, is_fullscreen: bool) {
+        //Short circuit evaluation if we're already g2g
+        if self
+            .cx
+            .windows
+            .get(&window_id)
+            .map(|w| w.is_fullscreen == is_fullscreen)
+            .unwrap_or(false)
+        {
+            return;
+        }
+
+        self.update(|this| {
+            let window = this.cx.windows.get_mut(&window_id)?;
+            window.is_fullscreen = is_fullscreen;
+
+            // self.emit_event(entity_id, payload);
+
+            Some(())
+        });
+    }
+
     fn handle_window_activation_effect(&mut self, window_id: usize, active: bool) {
+        //Short circuit evaluation if we're already g2g
         if self
             .cx
             .windows
             .get(&window_id)
-            .map(|w| w.is_active)
-            .map_or(false, |cur_active| cur_active == active)
+            .map(|w| w.is_active == active)
+            .unwrap_or(false)
         {
             return;
         }
@@ -2622,6 +2688,8 @@ impl MutableAppContext {
         self.update(|this| {
             let window = this.cx.windows.get_mut(&window_id)?;
             window.is_active = active;
+
+            //Handle focus
             let view_id = window.focused_view_id?;
             if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) {
                 if active {
@@ -2632,6 +2700,7 @@ impl MutableAppContext {
                 this.cx.views.insert((window_id, view_id), view);
             }
 
+            //Deliver events
             let callbacks = this
                 .window_activation_observations
                 .lock()
@@ -2640,6 +2709,7 @@ impl MutableAppContext {
                 for (id, callback) in callbacks {
                     if let Some(mut callback) = callback {
                         let alive = callback(active, this);
+                        //Put entry back
                         if alive {
                             match this
                                 .window_activation_observations
@@ -3072,6 +3142,7 @@ struct Window {
     root_view: AnyViewHandle,
     focused_view_id: Option<usize>,
     is_active: bool,
+    is_fullscreen: bool,
     invalidation: Option<WindowInvalidation>,
 }
 
@@ -3138,6 +3209,10 @@ pub enum Effect {
     ResizeWindow {
         window_id: usize,
     },
+    FullscreenWindow {
+        window_id: usize,
+        is_fullscreen: bool,
+    },
     ActivateWindow {
         window_id: usize,
         is_active: bool,
@@ -3256,6 +3331,14 @@ impl Debug for Effect {
                 .field("window_id", window_id)
                 .field("is_active", is_active)
                 .finish(),
+            Effect::FullscreenWindow {
+                window_id,
+                is_fullscreen,
+            } => f
+                .debug_struct("Effect::FullscreenWindow")
+                .field("window_id", window_id)
+                .field("is_fullscreen", is_fullscreen)
+                .finish(),
             Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(),
             Effect::WindowShouldCloseSubscription { window_id, .. } => f
                 .debug_struct("Effect::WindowShouldCloseSubscription")
@@ -4032,6 +4115,10 @@ impl<'a, V: View> RenderContext<'a, V> {
         WeakViewHandle::new(self.window_id, self.view_id)
     }
 
+    pub fn window_id(&self) -> usize {
+        self.window_id
+    }
+
     pub fn view_id(&self) -> usize {
         self.view_id
     }

crates/gpui/src/platform.rs 🔗

@@ -112,6 +112,7 @@ pub trait Window: WindowContext {
     fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
     fn on_active_status_change(&mut self, callback: Box<dyn FnMut(bool)>);
     fn on_resize(&mut self, callback: Box<dyn FnMut()>);
+    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>);
     fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
     fn on_close(&mut self, callback: Box<dyn FnOnce()>);
     fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);

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

@@ -128,6 +128,14 @@ unsafe fn build_classes() {
             sel!(windowDidResize:),
             window_did_resize as extern "C" fn(&Object, Sel, id),
         );
+        decl.add_method(
+            sel!(windowDidEnterFullScreen:),
+            window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
+        );
+        decl.add_method(
+            sel!(windowDidExitFullScreen:),
+            window_did_exit_fullscreen as extern "C" fn(&Object, Sel, id),
+        );
         decl.add_method(
             sel!(windowDidBecomeKey:),
             window_did_change_key_status as extern "C" fn(&Object, Sel, id),
@@ -276,6 +284,7 @@ struct WindowState {
     event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
     activate_callback: Option<Box<dyn FnMut(bool)>>,
     resize_callback: Option<Box<dyn FnMut()>>,
+    fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
     should_close_callback: Option<Box<dyn FnMut() -> bool>>,
     close_callback: Option<Box<dyn FnOnce()>>,
     input_handler: Option<Box<dyn InputHandler>>,
@@ -368,6 +377,7 @@ impl Window {
                 should_close_callback: None,
                 close_callback: None,
                 activate_callback: None,
+                fullscreen_callback: None,
                 input_handler: None,
                 pending_key_down: None,
                 performed_key_equivalent: false,
@@ -467,6 +477,10 @@ impl platform::Window for Window {
         self.0.as_ref().borrow_mut().resize_callback = Some(callback);
     }
 
+    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
+        self.0.as_ref().borrow_mut().fullscreen_callback = Some(callback);
+    }
+
     fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
         self.0.as_ref().borrow_mut().should_close_callback = Some(callback);
     }
@@ -908,6 +922,24 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) {
     window_state.as_ref().borrow().move_traffic_light();
 }
 
+extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, true);
+}
+
+extern "C" fn window_did_exit_fullscreen(this: &Object, _: Sel, _: id) {
+    window_fullscreen_changed(this, false);
+}
+
+fn window_fullscreen_changed(this: &Object, is_fullscreen: bool) {
+    let window_state = unsafe { get_window_state(this) };
+    let mut window_state_borrow = window_state.as_ref().borrow_mut();
+    if let Some(mut callback) = window_state_borrow.fullscreen_callback.take() {
+        drop(window_state_borrow);
+        callback(is_fullscreen);
+        window_state.borrow_mut().fullscreen_callback = Some(callback);
+    }
+}
+
 extern "C" fn window_did_change_key_status(this: &Object, selector: Sel, _: id) {
     let is_active = if selector == sel!(windowDidBecomeKey:) {
         true

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

@@ -37,6 +37,7 @@ pub struct Window {
     event_handlers: Vec<Box<dyn FnMut(super::Event) -> bool>>,
     resize_handlers: Vec<Box<dyn FnMut()>>,
     close_handlers: Vec<Box<dyn FnOnce()>>,
+    fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>,
     pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>,
     pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
     pub(crate) title: Option<String>,
@@ -199,6 +200,7 @@ impl Window {
             close_handlers: Default::default(),
             should_close_handler: Default::default(),
             active_status_change_handlers: Default::default(),
+            fullscreen_handlers: Default::default(),
             scale_factor: 1.0,
             current_scene: None,
             title: None,
@@ -253,6 +255,10 @@ impl super::Window for Window {
         self.active_status_change_handlers.push(callback);
     }
 
+    fn on_fullscreen(&mut self, callback: Box<dyn FnMut(bool)>) {
+        self.fullscreen_handlers.push(callback)
+    }
+
     fn on_resize(&mut self, callback: Box<dyn FnMut()>) {
         self.resize_handlers.push(callback);
     }