WIP: Start rendering GPUI views to macOS status bar

Antonio Scandurra created

Change summary

crates/gpui/src/app.rs                      | 202 ++++++++++++++--------
crates/gpui/src/platform.rs                 |   4 
crates/gpui/src/platform/mac/platform.rs    |   4 
crates/gpui/src/platform/mac/status_item.rs | 155 ++++++++++++-----
crates/gpui/src/platform/test.rs            |   4 
5 files changed, 236 insertions(+), 133 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -1920,19 +1920,134 @@ impl MutableAppContext {
                 },
             );
             root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
-            this.open_platform_window(window_id, window_options);
+
+            let mut window =
+                this.cx
+                    .platform
+                    .open_window(window_id, window_options, this.foreground.clone());
+            let presenter = Rc::new(RefCell::new(
+                this.build_presenter(window_id, window.titlebar_height()),
+            ));
+
+            {
+                let mut app = this.upgrade();
+                let presenter = Rc::downgrade(&presenter);
+                window.on_event(Box::new(move |event| {
+                    app.update(|cx| {
+                        if let Some(presenter) = presenter.upgrade() {
+                            if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
+                                if cx.dispatch_keystroke(window_id, keystroke) {
+                                    return true;
+                                }
+                            }
+
+                            presenter.borrow_mut().dispatch_event(event, false, cx)
+                        } else {
+                            false
+                        }
+                    })
+                }));
+            }
+
+            {
+                let mut app = this.upgrade();
+                window.on_active_status_change(Box::new(move |is_active| {
+                    app.update(|cx| cx.window_changed_active_status(window_id, is_active))
+                }));
+            }
+
+            {
+                let mut app = this.upgrade();
+                window.on_resize(Box::new(move || {
+                    app.update(|cx| cx.window_was_resized(window_id))
+                }));
+            }
+
+            {
+                let mut app = this.upgrade();
+                window.on_fullscreen(Box::new(move |is_fullscreen| {
+                    app.update(|cx| cx.window_was_fullscreen_changed(window_id, is_fullscreen))
+                }));
+            }
+
+            {
+                let mut app = this.upgrade();
+                window.on_close(Box::new(move || {
+                    app.update(|cx| cx.remove_window(window_id));
+                }));
+            }
+
+            window.set_input_handler(Box::new(WindowInputHandler {
+                app: this.upgrade().0,
+                window_id,
+            }));
+
+            let scene = presenter.borrow_mut().build_scene(
+                window.size(),
+                window.scale_factor(),
+                false,
+                this,
+            );
+            window.present_scene(scene);
+            this.presenters_and_platform_windows
+                .insert(window_id, (presenter.clone(), window));
 
             (window_id, root_view)
         })
     }
 
-    // pub fn add_status_bar_item<I, F>(&mut self, build_item: F)
-    // where
-    //     I: View,
-    //     F: FnOnce(&mut ViewContext<I>) -> I,
-    // {
-    //     mem::forget(self.platform.add_status_item());
-    // }
+    pub fn add_status_bar_item<T, F>(&mut self, build_root_view: F) -> (usize, ViewHandle<T>)
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        self.update(|this| {
+            let window_id = post_inc(&mut this.next_window_id);
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
+            this.cx.windows.insert(
+                window_id,
+                Window {
+                    root_view: root_view.clone().into(),
+                    focused_view_id: Some(root_view.id()),
+                    is_active: false,
+                    invalidation: None,
+                    is_fullscreen: false,
+                },
+            );
+            root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
+
+            let mut status_item = this.cx.platform.add_status_item();
+            let presenter = Rc::new(RefCell::new(this.build_presenter(window_id, 0.)));
+
+            {
+                let mut app = this.upgrade();
+                let presenter = Rc::downgrade(&presenter);
+                status_item.on_event(Box::new(move |event| {
+                    app.update(|cx| {
+                        if let Some(presenter) = presenter.upgrade() {
+                            presenter.borrow_mut().dispatch_event(event, cx)
+                        } else {
+                            false
+                        }
+                    })
+                }));
+            }
+
+            let scene = presenter.borrow_mut().build_scene(
+                status_item.size(),
+                status_item.scale_factor(),
+                false,
+                this,
+            );
+            status_item.present_scene(scene);
+            this.presenters_and_platform_windows
+                .insert(window_id, (presenter.clone(), status_item));
+
+            (window_id, root_view)
+        })
+    }
 
     pub fn replace_root_view<T, F>(&mut self, window_id: usize, build_root_view: F) -> ViewHandle<T>
     where
@@ -1956,77 +2071,6 @@ impl MutableAppContext {
         self.flush_effects();
     }
 
-    fn open_platform_window(&mut self, window_id: usize, window_options: WindowOptions) {
-        let mut window =
-            self.cx
-                .platform
-                .open_window(window_id, window_options, self.foreground.clone());
-        let presenter = Rc::new(RefCell::new(
-            self.build_presenter(window_id, window.titlebar_height()),
-        ));
-
-        {
-            let mut app = self.upgrade();
-            let presenter = Rc::downgrade(&presenter);
-            window.on_event(Box::new(move |event| {
-                app.update(|cx| {
-                    if let Some(presenter) = presenter.upgrade() {
-                        if let Event::KeyDown(KeyDownEvent { keystroke, .. }) = &event {
-                            if cx.dispatch_keystroke(window_id, keystroke) {
-                                return true;
-                            }
-                        }
-
-                        presenter.borrow_mut().dispatch_event(event, false, cx)
-                    } else {
-                        false
-                    }
-                })
-            }));
-        }
-
-        {
-            let mut app = self.upgrade();
-            window.on_active_status_change(Box::new(move |is_active| {
-                app.update(|cx| cx.window_changed_active_status(window_id, is_active))
-            }));
-        }
-
-        {
-            let mut app = self.upgrade();
-            window.on_resize(Box::new(move || {
-                app.update(|cx| cx.window_was_resized(window_id))
-            }));
-        }
-
-        {
-            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 || {
-                app.update(|cx| cx.remove_window(window_id));
-            }));
-        }
-
-        window.set_input_handler(Box::new(WindowInputHandler {
-            app: self.upgrade().0,
-            window_id,
-        }));
-
-        let scene =
-            presenter
-                .borrow_mut()
-                .build_scene(window.size(), window.scale_factor(), false, self);
-        window.present_scene(scene);
-        self.presenters_and_platform_windows
-            .insert(window_id, (presenter.clone(), window));
-    }
-
     pub fn build_presenter(&mut self, window_id: usize, titlebar_height: f32) -> Presenter {
         Presenter::new(
             window_id,

crates/gpui/src/platform.rs 🔗

@@ -52,7 +52,7 @@ pub trait Platform: Send + Sync {
     ) -> Box<dyn Window>;
     fn key_window_id(&self) -> Option<usize>;
 
-    fn add_status_item(&self) -> Box<dyn StatusItem>;
+    fn add_status_item(&self) -> Box<dyn Window>;
 
     fn write_to_clipboard(&self, item: ClipboardItem);
     fn read_from_clipboard(&self) -> Option<ClipboardItem>;
@@ -134,8 +134,6 @@ pub trait Window {
     fn present_scene(&mut self, scene: Scene);
 }
 
-pub trait StatusItem {}
-
 #[derive(Debug)]
 pub struct WindowOptions<'a> {
     pub bounds: WindowBounds,

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

@@ -495,8 +495,8 @@ impl platform::Platform for MacPlatform {
         Window::key_window_id()
     }
 
-    fn add_status_item(&self) -> Box<dyn platform::StatusItem> {
-        Box::new(StatusItem::add())
+    fn add_status_item(&self) -> Box<dyn platform::Window> {
+        Box::new(StatusItem::add(self.fonts()))
     }
 
     fn fonts(&self) -> Arc<dyn platform::FontSystem> {

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

@@ -1,60 +1,121 @@
+use crate::{
+    geometry::vector::{vec2f, Vector2F},
+    platform::{self, mac::renderer::Renderer},
+    Event, FontSystem, Scene,
+};
 use cocoa::{
-    appkit::{NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView},
-    base::{id, nil, NO, YES},
-    quartzcore::AutoresizingMask,
+    appkit::{
+        NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSViewHeightSizable,
+        NSViewWidthSizable, NSWindow,
+    },
+    base::{id, nil, YES},
+    foundation::NSSize,
 };
-use core_foundation::base::TCFType;
-use core_graphics::color::CGColor;
-use foreign_types::ForeignType;
-use objc::{class, msg_send, rc::StrongPtr, sel, sel_impl};
+use foreign_types::ForeignTypeRef;
+use objc::{msg_send, rc::StrongPtr, sel, sel_impl};
+use std::{cell::RefCell, rc::Rc, sync::Arc};
 
-pub struct StatusItem(StrongPtr);
+pub struct StatusItem(Rc<RefCell<StatusItemState>>);
 
-impl StatusItem {
-    pub fn add() -> Self {
-        const PIXEL_FORMAT: metal::MTLPixelFormat = metal::MTLPixelFormat::BGRA8Unorm;
+struct StatusItemState {
+    native_item: StrongPtr,
+    renderer: Renderer,
+    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
+}
 
+impl StatusItem {
+    pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
         unsafe {
+            let renderer = Renderer::new(fonts);
             let status_bar = NSStatusBar::systemStatusBar(nil);
             let native_item =
                 StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
-            native_item.button().setWantsLayer(true);
-
-            let device: metal::Device = if let Some(device) = metal::Device::system_default() {
-                device
-            } else {
-                log::error!("unable to access a compatible graphics device");
-                std::process::exit(1);
-            };
-
-            let layer: id = msg_send![class!(CAMetalLayer), layer];
-            let _: () = msg_send![layer, setDevice: device.as_ptr()];
-            let _: () = msg_send![layer, setPixelFormat: PIXEL_FORMAT];
-            let _: () = msg_send![layer, setAllowsNextDrawableTimeout: NO];
-            let _: () = msg_send![layer, setNeedsDisplayOnBoundsChange: YES];
-            let _: () = msg_send![layer, setPresentsWithTransaction: YES];
-            let _: () = msg_send![
-                layer,
-                setAutoresizingMask: AutoresizingMask::WIDTH_SIZABLE
-                    | AutoresizingMask::HEIGHT_SIZABLE
-            ];
-            let _: () = msg_send![
-                layer,
-                setBackgroundColor: CGColor::rgb(1., 0., 0., 1.).as_concrete_TypeRef()
-            ];
-
-            let _: () = msg_send![native_item.button(), setLayer: layer];
-            let native_item_window: id = msg_send![native_item.button(), window];
-
-            dbg!(native_item_window.frame().as_CGRect());
-            // let rect_in_window: NSRect = msg_send![native_item.button(), convertRect: native_item.button().bounds() toView: nil];
-            // let screen_rect: NSRect =
-            //     msg_send![native_item_window, convertRectToScreen: rect_in_window];
-            // dbg!(screen_rect.as_CGRect());
-
-            StatusItem(native_item)
+
+            let button = native_item.button();
+            button.setAutoresizingMask_(NSViewWidthSizable | NSViewHeightSizable);
+            button.setWantsBestResolutionOpenGLSurface_(YES);
+            button.setLayer(renderer.layer().as_ptr() as id);
+
+            Self(Rc::new(RefCell::new(StatusItemState {
+                native_item,
+                renderer,
+                event_callback: None,
+            })))
         }
     }
 }
 
-impl crate::StatusItem for StatusItem {}
+impl platform::Window for StatusItem {
+    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
+        self
+    }
+
+    fn on_event(&mut self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
+        self.0.borrow_mut().event_callback = Some(callback);
+    }
+
+    fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
+
+    fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
+
+    fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
+
+    fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
+
+    fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
+
+    fn set_input_handler(&mut self, _: Box<dyn crate::InputHandler>) {}
+
+    fn prompt(
+        &self,
+        _: crate::PromptLevel,
+        _: &str,
+        _: &[&str],
+    ) -> postage::oneshot::Receiver<usize> {
+        panic!()
+    }
+
+    fn activate(&self) {}
+
+    fn set_title(&mut self, _: &str) {}
+
+    fn set_edited(&mut self, _: bool) {}
+
+    fn show_character_palette(&self) {}
+
+    fn minimize(&self) {}
+
+    fn zoom(&self) {}
+
+    fn toggle_full_screen(&self) {}
+
+    fn size(&self) -> Vector2F {
+        self.0.borrow().size()
+    }
+
+    fn scale_factor(&self) -> f32 {
+        self.0.borrow().scale_factor()
+    }
+
+    fn titlebar_height(&self) -> f32 {
+        0.
+    }
+
+    fn present_scene(&mut self, scene: Scene) {
+        self.0.borrow_mut().renderer.render(&scene);
+    }
+}
+
+impl StatusItemState {
+    fn size(&self) -> Vector2F {
+        let NSSize { width, height, .. } = unsafe { NSView::frame(self.native_item.button()) }.size;
+        vec2f(width as f32, height as f32)
+    }
+
+    fn scale_factor(&self) -> f32 {
+        unsafe {
+            let window: id = msg_send![self.native_item.button(), window];
+            window.screen().backingScaleFactor() as f32
+        }
+    }
+}

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

@@ -144,8 +144,8 @@ impl super::Platform for Platform {
         None
     }
 
-    fn add_status_item(&self) -> Box<dyn crate::StatusItem> {
-        todo!()
+    fn add_status_item(&self) -> Box<dyn crate::Window> {
+        Box::new(Window::new(vec2f(24., 24.)))
     }
 
     fn write_to_clipboard(&self, item: ClipboardItem) {