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}