status_item.rs

  1use crate::{
  2    geometry::{
  3        rect::RectF,
  4        vector::{vec2f, Vector2F},
  5    },
  6    platform::{
  7        self,
  8        mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
  9        Event, FontSystem, WindowBounds,
 10    },
 11    Scene,
 12};
 13use cocoa::{
 14    appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
 15    base::{id, nil, YES},
 16    foundation::{NSPoint, NSRect, NSSize},
 17};
 18use ctor::ctor;
 19use foreign_types::ForeignTypeRef;
 20use objc::{
 21    class,
 22    declare::ClassDecl,
 23    msg_send,
 24    rc::StrongPtr,
 25    runtime::{Class, Object, Protocol, Sel},
 26    sel, sel_impl,
 27};
 28use std::{
 29    cell::RefCell,
 30    ffi::c_void,
 31    ptr,
 32    rc::{Rc, Weak},
 33    sync::Arc,
 34};
 35
 36use super::screen::Screen;
 37
 38static mut VIEW_CLASS: *const Class = ptr::null();
 39const STATE_IVAR: &str = "state";
 40
 41#[ctor]
 42unsafe fn build_classes() {
 43    VIEW_CLASS = {
 44        let mut decl = ClassDecl::new("GPUIStatusItemView", class!(NSView)).unwrap();
 45        decl.add_ivar::<*mut c_void>(STATE_IVAR);
 46
 47        decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
 48
 49        decl.add_method(
 50            sel!(mouseDown:),
 51            handle_view_event as extern "C" fn(&Object, Sel, id),
 52        );
 53        decl.add_method(
 54            sel!(mouseUp:),
 55            handle_view_event as extern "C" fn(&Object, Sel, id),
 56        );
 57        decl.add_method(
 58            sel!(rightMouseDown:),
 59            handle_view_event as extern "C" fn(&Object, Sel, id),
 60        );
 61        decl.add_method(
 62            sel!(rightMouseUp:),
 63            handle_view_event as extern "C" fn(&Object, Sel, id),
 64        );
 65        decl.add_method(
 66            sel!(otherMouseDown:),
 67            handle_view_event as extern "C" fn(&Object, Sel, id),
 68        );
 69        decl.add_method(
 70            sel!(otherMouseUp:),
 71            handle_view_event as extern "C" fn(&Object, Sel, id),
 72        );
 73        decl.add_method(
 74            sel!(mouseMoved:),
 75            handle_view_event as extern "C" fn(&Object, Sel, id),
 76        );
 77        decl.add_method(
 78            sel!(mouseDragged:),
 79            handle_view_event as extern "C" fn(&Object, Sel, id),
 80        );
 81        decl.add_method(
 82            sel!(scrollWheel:),
 83            handle_view_event as extern "C" fn(&Object, Sel, id),
 84        );
 85        decl.add_method(
 86            sel!(flagsChanged:),
 87            handle_view_event as extern "C" fn(&Object, Sel, id),
 88        );
 89        decl.add_method(
 90            sel!(makeBackingLayer),
 91            make_backing_layer as extern "C" fn(&Object, Sel) -> id,
 92        );
 93        decl.add_method(
 94            sel!(viewDidChangeEffectiveAppearance),
 95            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
 96        );
 97
 98        decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
 99        decl.add_method(
100            sel!(displayLayer:),
101            display_layer as extern "C" fn(&Object, Sel, id),
102        );
103
104        decl.register()
105    };
106}
107
108pub struct StatusItem(Rc<RefCell<StatusItemState>>);
109
110struct StatusItemState {
111    native_item: StrongPtr,
112    native_view: StrongPtr,
113    renderer: Renderer,
114    scene: Option<Scene>,
115    event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
116    appearance_changed_callback: Option<Box<dyn FnMut()>>,
117}
118
119impl StatusItem {
120    pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
121        unsafe {
122            let renderer = Renderer::new(false, fonts);
123            let status_bar = NSStatusBar::systemStatusBar(nil);
124            let native_item =
125                StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
126
127            let button = native_item.button();
128            let _: () = msg_send![button, setHidden: YES];
129
130            let native_view = msg_send![VIEW_CLASS, alloc];
131            let state = Rc::new(RefCell::new(StatusItemState {
132                native_item,
133                native_view: StrongPtr::new(native_view),
134                renderer,
135                scene: None,
136                event_callback: None,
137                appearance_changed_callback: None,
138            }));
139
140            let parent_view = button.superview().superview();
141            NSView::initWithFrame_(
142                native_view,
143                NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
144            );
145            (*native_view).set_ivar(
146                STATE_IVAR,
147                Weak::into_raw(Rc::downgrade(&state)) as *const c_void,
148            );
149            native_view.setWantsBestResolutionOpenGLSurface_(YES);
150            native_view.setWantsLayer(YES);
151            let _: () = msg_send![
152                native_view,
153                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
154            ];
155
156            parent_view.addSubview_(native_view);
157
158            {
159                let state = state.borrow();
160                let layer = state.renderer.layer();
161                let scale_factor = state.scale_factor();
162                let size = state.content_size() * scale_factor;
163                layer.set_contents_scale(scale_factor.into());
164                layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
165            }
166
167            Self(state)
168        }
169    }
170}
171
172impl platform::Window for StatusItem {
173    fn bounds(&self) -> WindowBounds {
174        self.0.borrow().bounds()
175    }
176
177    fn content_size(&self) -> Vector2F {
178        self.0.borrow().content_size()
179    }
180
181    fn scale_factor(&self) -> f32 {
182        self.0.borrow().scale_factor()
183    }
184
185    fn titlebar_height(&self) -> f32 {
186        0.
187    }
188
189    fn appearance(&self) -> platform::Appearance {
190        unsafe {
191            let appearance: id =
192                msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
193            platform::Appearance::from_native(appearance)
194        }
195    }
196
197    fn screen(&self) -> Rc<dyn platform::Screen> {
198        unsafe {
199            Rc::new(Screen {
200                native_screen: self.0.borrow().native_window().screen(),
201            })
202        }
203    }
204
205    fn mouse_position(&self) -> Vector2F {
206        unimplemented!()
207    }
208
209    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
210        self
211    }
212
213    fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
214
215    fn prompt(
216        &self,
217        _: crate::platform::PromptLevel,
218        _: &str,
219        _: &[&str],
220    ) -> postage::oneshot::Receiver<usize> {
221        unimplemented!()
222    }
223
224    fn activate(&self) {
225        unimplemented!()
226    }
227
228    fn set_title(&mut self, _: &str) {
229        unimplemented!()
230    }
231
232    fn set_edited(&mut self, _: bool) {
233        unimplemented!()
234    }
235
236    fn show_character_palette(&self) {
237        unimplemented!()
238    }
239
240    fn minimize(&self) {
241        unimplemented!()
242    }
243
244    fn zoom(&self) {
245        unimplemented!()
246    }
247
248    fn present_scene(&mut self, scene: Scene) {
249        self.0.borrow_mut().scene = Some(scene);
250        unsafe {
251            let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
252        }
253    }
254
255    fn toggle_full_screen(&self) {
256        unimplemented!()
257    }
258
259    fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
260        self.0.borrow_mut().event_callback = Some(callback);
261    }
262
263    fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
264
265    fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
266
267    fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
268
269    fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
270
271    fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
272
273    fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
274
275    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
276        self.0.borrow_mut().appearance_changed_callback = Some(callback);
277    }
278
279    fn is_topmost_for_position(&self, _: Vector2F) -> bool {
280        true
281    }
282}
283
284impl StatusItemState {
285    fn bounds(&self) -> WindowBounds {
286        unsafe {
287            let window: id = self.native_window();
288            let screen_frame = window.screen().visibleFrame();
289            let window_frame = NSWindow::frame(window);
290            let origin = vec2f(
291                window_frame.origin.x as f32,
292                (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
293                    as f32,
294            );
295            let size = vec2f(
296                window_frame.size.width as f32,
297                window_frame.size.height as f32,
298            );
299            WindowBounds::Fixed(RectF::new(origin, size))
300        }
301    }
302
303    fn content_size(&self) -> Vector2F {
304        unsafe {
305            let NSSize { width, height, .. } =
306                NSView::frame(self.native_item.button().superview().superview()).size;
307            vec2f(width as f32, height as f32)
308        }
309    }
310
311    fn scale_factor(&self) -> f32 {
312        unsafe {
313            let window: id = msg_send![self.native_item.button(), window];
314            NSScreen::backingScaleFactor(window.screen()) as f32
315        }
316    }
317
318    pub fn native_window(&self) -> id {
319        unsafe { msg_send![self.native_item.button(), window] }
320    }
321}
322
323extern "C" fn dealloc_view(this: &Object, _: Sel) {
324    unsafe {
325        drop_state(this);
326
327        let _: () = msg_send![super(this, class!(NSView)), dealloc];
328    }
329}
330
331extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
332    unsafe {
333        if let Some(state) = get_state(this).upgrade() {
334            let mut state_borrow = state.as_ref().borrow_mut();
335            if let Some(event) =
336                Event::from_native(native_event, Some(state_borrow.content_size().y()))
337            {
338                if let Some(mut callback) = state_borrow.event_callback.take() {
339                    drop(state_borrow);
340                    callback(event);
341                    state.borrow_mut().event_callback = Some(callback);
342                }
343            }
344        }
345    }
346}
347
348extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
349    if let Some(state) = unsafe { get_state(this).upgrade() } {
350        let state = state.borrow();
351        state.renderer.layer().as_ptr() as id
352    } else {
353        nil
354    }
355}
356
357extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
358    unsafe {
359        if let Some(state) = get_state(this).upgrade() {
360            let mut state = state.borrow_mut();
361            if let Some(scene) = state.scene.take() {
362                state.renderer.render(&scene);
363            }
364        }
365    }
366}
367
368extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
369    unsafe {
370        if let Some(state) = get_state(this).upgrade() {
371            let mut state_borrow = state.as_ref().borrow_mut();
372            if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
373                drop(state_borrow);
374                callback();
375                state.borrow_mut().appearance_changed_callback = Some(callback);
376            }
377        }
378    }
379}
380
381unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
382    let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
383    let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
384    let weak2 = weak1.clone();
385    let _ = Weak::into_raw(weak1);
386    weak2
387}
388
389unsafe fn drop_state(object: &Object) {
390    let raw: *const c_void = *object.get_ivar(STATE_IVAR);
391    Weak::from_raw(raw as *const RefCell<StatusItemState>);
392}