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