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 fn is_topmost_for_position(&self, _: Vector2F) -> bool {
263 true
264 }
265}
266
267impl StatusItemState {
268 fn bounds(&self) -> RectF {
269 unsafe {
270 let window: id = msg_send![self.native_item.button(), window];
271 let screen_frame = window.screen().visibleFrame();
272 let window_frame = NSWindow::frame(window);
273 let origin = vec2f(
274 window_frame.origin.x as f32,
275 (window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
276 as f32,
277 );
278 let size = vec2f(
279 window_frame.size.width as f32,
280 window_frame.size.height as f32,
281 );
282 RectF::new(origin, size)
283 }
284 }
285
286 fn content_size(&self) -> Vector2F {
287 unsafe {
288 let NSSize { width, height, .. } =
289 NSView::frame(self.native_item.button().superview().superview()).size;
290 vec2f(width as f32, height as f32)
291 }
292 }
293
294 fn scale_factor(&self) -> f32 {
295 unsafe {
296 let window: id = msg_send![self.native_item.button(), window];
297 NSScreen::backingScaleFactor(window.screen()) as f32
298 }
299 }
300}
301
302extern "C" fn dealloc_view(this: &Object, _: Sel) {
303 unsafe {
304 drop_state(this);
305
306 let _: () = msg_send![super(this, class!(NSView)), dealloc];
307 }
308}
309
310extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
311 unsafe {
312 if let Some(state) = get_state(this).upgrade() {
313 let mut state_borrow = state.as_ref().borrow_mut();
314 if let Some(event) =
315 Event::from_native(native_event, Some(state_borrow.content_size().y()))
316 {
317 if let Some(mut callback) = state_borrow.event_callback.take() {
318 drop(state_borrow);
319 callback(event);
320 state.borrow_mut().event_callback = Some(callback);
321 }
322 }
323 }
324 }
325}
326
327extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
328 if let Some(state) = unsafe { get_state(this).upgrade() } {
329 let state = state.borrow();
330 state.renderer.layer().as_ptr() as id
331 } else {
332 nil
333 }
334}
335
336extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
337 unsafe {
338 if let Some(state) = get_state(this).upgrade() {
339 let mut state = state.borrow_mut();
340 if let Some(scene) = state.scene.take() {
341 state.renderer.render(&scene);
342 }
343 }
344 }
345}
346
347extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
348 unsafe {
349 if let Some(state) = get_state(this).upgrade() {
350 let mut state_borrow = state.as_ref().borrow_mut();
351 if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
352 drop(state_borrow);
353 callback();
354 state.borrow_mut().appearance_changed_callback = Some(callback);
355 }
356 }
357 }
358}
359
360unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
361 let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
362 let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
363 let weak2 = weak1.clone();
364 let _ = Weak::into_raw(weak1);
365 weak2
366}
367
368unsafe fn drop_state(object: &Object) {
369 let raw: *const c_void = *object.get_ivar(STATE_IVAR);
370 Weak::from_raw(raw as *const RefCell<StatusItemState>);
371}