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}