status_item.rs

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