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 appearance(&self) -> platform::Appearance {
186 unsafe {
187 let appearance: id =
188 msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
189 platform::Appearance::from_native(appearance)
190 }
191 }
192
193 fn screen(&self) -> Rc<dyn platform::Screen> {
194 unsafe {
195 Rc::new(Screen {
196 native_screen: self.0.borrow().native_window().screen(),
197 })
198 }
199 }
200
201 fn mouse_position(&self) -> Vector2F {
202 unimplemented!()
203 }
204
205 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
206 self
207 }
208
209 fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
210
211 fn prompt(
212 &self,
213 _: crate::platform::PromptLevel,
214 _: &str,
215 _: &[&str],
216 ) -> postage::oneshot::Receiver<usize> {
217 unimplemented!()
218 }
219
220 fn activate(&self) {
221 unimplemented!()
222 }
223
224 fn set_title(&mut self, _: &str) {
225 unimplemented!()
226 }
227
228 fn set_edited(&mut self, _: bool) {
229 unimplemented!()
230 }
231
232 fn show_character_palette(&self) {
233 unimplemented!()
234 }
235
236 fn minimize(&self) {
237 unimplemented!()
238 }
239
240 fn zoom(&self) {
241 unimplemented!()
242 }
243
244 fn present_scene(&mut self, scene: Scene) {
245 self.0.borrow_mut().scene = Some(scene);
246 unsafe {
247 let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
248 }
249 }
250
251 fn toggle_fullscreen(&self) {
252 unimplemented!()
253 }
254
255 fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
256 self.0.borrow_mut().event_callback = Some(callback);
257 }
258
259 fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
260
261 fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
262
263 fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
264
265 fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
266
267 fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
268
269 fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
270
271 fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
272 self.0.borrow_mut().appearance_changed_callback = Some(callback);
273 }
274
275 fn is_topmost_for_position(&self, _: Vector2F) -> bool {
276 true
277 }
278}
279
280impl StatusItemState {
281 fn bounds(&self) -> WindowBounds {
282 unsafe {
283 let window: id = self.native_window();
284 let screen_frame = window.screen().visibleFrame();
285 let window_frame = NSWindow::frame(window);
286 let origin = vec2f(
287 window_frame.origin.x as f32,
288 (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
289 as f32,
290 );
291 let size = vec2f(
292 window_frame.size.width as f32,
293 window_frame.size.height as f32,
294 );
295 WindowBounds::Fixed(RectF::new(origin, size))
296 }
297 }
298
299 fn content_size(&self) -> Vector2F {
300 unsafe {
301 let NSSize { width, height, .. } =
302 NSView::frame(self.native_item.button().superview().superview()).size;
303 vec2f(width as f32, height as f32)
304 }
305 }
306
307 fn scale_factor(&self) -> f32 {
308 unsafe {
309 let window: id = msg_send![self.native_item.button(), window];
310 NSScreen::backingScaleFactor(window.screen()) as f32
311 }
312 }
313
314 pub fn native_window(&self) -> id {
315 unsafe { msg_send![self.native_item.button(), window] }
316 }
317}
318
319extern "C" fn dealloc_view(this: &Object, _: Sel) {
320 unsafe {
321 drop_state(this);
322
323 let _: () = msg_send![super(this, class!(NSView)), dealloc];
324 }
325}
326
327extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
328 unsafe {
329 if let Some(state) = get_state(this).upgrade() {
330 let mut state_borrow = state.as_ref().borrow_mut();
331 if let Some(event) =
332 Event::from_native(native_event, Some(state_borrow.content_size().y()))
333 {
334 if let Some(mut callback) = state_borrow.event_callback.take() {
335 drop(state_borrow);
336 callback(event);
337 state.borrow_mut().event_callback = Some(callback);
338 }
339 }
340 }
341 }
342}
343
344extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
345 if let Some(state) = unsafe { get_state(this).upgrade() } {
346 let state = state.borrow();
347 state.renderer.layer().as_ptr() as id
348 } else {
349 nil
350 }
351}
352
353extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
354 unsafe {
355 if let Some(state) = get_state(this).upgrade() {
356 let mut state = state.borrow_mut();
357 if let Some(scene) = state.scene.take() {
358 state.renderer.render(&scene);
359 }
360 }
361 }
362}
363
364extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
365 unsafe {
366 if let Some(state) = get_state(this).upgrade() {
367 let mut state_borrow = state.as_ref().borrow_mut();
368 if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
369 drop(state_borrow);
370 callback();
371 state.borrow_mut().appearance_changed_callback = Some(callback);
372 }
373 }
374 }
375}
376
377unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
378 let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
379 let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
380 let weak2 = weak1.clone();
381 let _ = Weak::into_raw(weak1);
382 weak2
383}
384
385unsafe fn drop_state(object: &Object) {
386 let raw: *const c_void = *object.get_ivar(STATE_IVAR);
387 Weak::from_raw(raw as *const RefCell<StatusItemState>);
388}