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,
 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
 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!(viewDidChangeEffectiveAppearance),
 92            view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
 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(YES);
148            let _: () = msg_send![
149                native_view,
150                setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
151            ];
152
153            parent_view.addSubview_(native_view);
154
155            {
156                let state = state.borrow();
157                let layer = state.renderer.layer();
158                let scale_factor = state.scale_factor();
159                let size = state.content_size() * scale_factor;
160                layer.set_contents_scale(scale_factor.into());
161                layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
162            }
163
164            Self(state)
165        }
166    }
167}
168
169impl platform::Window for StatusItem {
170    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
171        self
172    }
173
174    fn on_event(&mut self, callback: Box<dyn FnMut(crate::Event) -> bool>) {
175        self.0.borrow_mut().event_callback = Some(callback);
176    }
177
178    fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
179        self.0.borrow_mut().appearance_changed_callback = Some(callback);
180    }
181
182    fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
183
184    fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
185
186    fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
187
188    fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
189
190    fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
191
192    fn set_input_handler(&mut self, _: Box<dyn crate::InputHandler>) {}
193
194    fn prompt(
195        &self,
196        _: crate::PromptLevel,
197        _: &str,
198        _: &[&str],
199    ) -> postage::oneshot::Receiver<usize> {
200        unimplemented!()
201    }
202
203    fn activate(&self) {
204        unimplemented!()
205    }
206
207    fn set_title(&mut self, _: &str) {
208        unimplemented!()
209    }
210
211    fn set_edited(&mut self, _: bool) {
212        unimplemented!()
213    }
214
215    fn show_character_palette(&self) {
216        unimplemented!()
217    }
218
219    fn minimize(&self) {
220        unimplemented!()
221    }
222
223    fn zoom(&self) {
224        unimplemented!()
225    }
226
227    fn toggle_full_screen(&self) {
228        unimplemented!()
229    }
230
231    fn bounds(&self) -> RectF {
232        self.0.borrow().bounds()
233    }
234
235    fn content_size(&self) -> Vector2F {
236        self.0.borrow().content_size()
237    }
238
239    fn scale_factor(&self) -> f32 {
240        self.0.borrow().scale_factor()
241    }
242
243    fn titlebar_height(&self) -> f32 {
244        0.
245    }
246
247    fn present_scene(&mut self, scene: Scene) {
248        self.0.borrow_mut().scene = Some(scene);
249        unsafe {
250            let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
251        }
252    }
253
254    fn appearance(&self) -> crate::Appearance {
255        unsafe {
256            let appearance: id =
257                msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
258            crate::Appearance::from_native(appearance)
259        }
260    }
261}
262
263impl StatusItemState {
264    fn bounds(&self) -> RectF {
265        unsafe {
266            let window: id = msg_send![self.native_item.button(), window];
267            let screen_frame = window.screen().visibleFrame();
268            let window_frame = NSWindow::frame(window);
269            let origin = vec2f(
270                window_frame.origin.x as f32,
271                (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
272                    as f32,
273            );
274            let size = vec2f(
275                window_frame.size.width as f32,
276                window_frame.size.height as f32,
277            );
278            RectF::new(origin, size)
279        }
280    }
281
282    fn content_size(&self) -> Vector2F {
283        unsafe {
284            let NSSize { width, height, .. } =
285                NSView::frame(self.native_item.button().superview().superview()).size;
286            vec2f(width as f32, height as f32)
287        }
288    }
289
290    fn scale_factor(&self) -> f32 {
291        unsafe {
292            let window: id = msg_send![self.native_item.button(), window];
293            NSScreen::backingScaleFactor(window.screen()) as f32
294        }
295    }
296}
297
298extern "C" fn dealloc_view(this: &Object, _: Sel) {
299    unsafe {
300        drop_state(this);
301
302        let _: () = msg_send![super(this, class!(NSView)), dealloc];
303    }
304}
305
306extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
307    unsafe {
308        if let Some(state) = get_state(this).upgrade() {
309            let mut state_borrow = state.as_ref().borrow_mut();
310            if let Some(event) =
311                Event::from_native(native_event, Some(state_borrow.content_size().y()))
312            {
313                if let Some(mut callback) = state_borrow.event_callback.take() {
314                    drop(state_borrow);
315                    callback(event);
316                    state.borrow_mut().event_callback = Some(callback);
317                }
318            }
319        }
320    }
321}
322
323extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
324    if let Some(state) = unsafe { get_state(this).upgrade() } {
325        let state = state.borrow();
326        state.renderer.layer().as_ptr() as id
327    } else {
328        nil
329    }
330}
331
332extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
333    unsafe {
334        if let Some(state) = get_state(this).upgrade() {
335            let mut state = state.borrow_mut();
336            if let Some(scene) = state.scene.take() {
337                state.renderer.render(&scene);
338            }
339        }
340    }
341}
342
343extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
344    unsafe {
345        if let Some(state) = get_state(this).upgrade() {
346            let mut state_borrow = state.as_ref().borrow_mut();
347            if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
348                drop(state_borrow);
349                callback();
350                state.borrow_mut().appearance_changed_callback = Some(callback);
351            }
352        }
353    }
354}
355
356unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
357    let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
358    let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
359    let weak2 = weak1.clone();
360    let _ = Weak::into_raw(weak1);
361    weak2
362}
363
364unsafe fn drop_state(object: &Object) {
365    let raw: *const c_void = *object.get_ivar(STATE_IVAR);
366    Weak::from_raw(raw as *const RefCell<StatusItemState>);
367}