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