393 lines
11 KiB
Rust
393 lines
11 KiB
Rust
use crate::{
|
|
geometry::{
|
|
rect::RectF,
|
|
vector::{vec2f, Vector2F},
|
|
},
|
|
platform::{
|
|
self,
|
|
mac::{platform::NSViewLayerContentsRedrawDuringViewResize, renderer::Renderer},
|
|
Event, FontSystem, WindowBounds,
|
|
},
|
|
Scene,
|
|
};
|
|
use cocoa::{
|
|
appkit::{NSScreen, NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
|
|
base::{id, nil, YES},
|
|
foundation::{NSPoint, NSRect, NSSize},
|
|
};
|
|
use ctor::ctor;
|
|
use foreign_types::ForeignTypeRef;
|
|
use objc::{
|
|
class,
|
|
declare::ClassDecl,
|
|
msg_send,
|
|
rc::StrongPtr,
|
|
runtime::{Class, Object, Protocol, Sel},
|
|
sel, sel_impl,
|
|
};
|
|
use std::{
|
|
cell::RefCell,
|
|
ffi::c_void,
|
|
ptr,
|
|
rc::{Rc, Weak},
|
|
sync::Arc,
|
|
};
|
|
|
|
use super::screen::Screen;
|
|
|
|
static mut VIEW_CLASS: *const Class = ptr::null();
|
|
const STATE_IVAR: &str = "state";
|
|
|
|
#[ctor]
|
|
unsafe fn build_classes() {
|
|
VIEW_CLASS = {
|
|
let mut decl = ClassDecl::new("GPUIStatusItemView", class!(NSView)).unwrap();
|
|
decl.add_ivar::<*mut c_void>(STATE_IVAR);
|
|
|
|
decl.add_method(sel!(dealloc), dealloc_view as extern "C" fn(&Object, Sel));
|
|
|
|
decl.add_method(
|
|
sel!(mouseDown:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(mouseUp:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(rightMouseDown:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(rightMouseUp:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(otherMouseDown:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(otherMouseUp:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(mouseMoved:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(mouseDragged:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(scrollWheel:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(flagsChanged:),
|
|
handle_view_event as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
decl.add_method(
|
|
sel!(makeBackingLayer),
|
|
make_backing_layer as extern "C" fn(&Object, Sel) -> id,
|
|
);
|
|
decl.add_method(
|
|
sel!(viewDidChangeEffectiveAppearance),
|
|
view_did_change_effective_appearance as extern "C" fn(&Object, Sel),
|
|
);
|
|
|
|
decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
|
|
decl.add_method(
|
|
sel!(displayLayer:),
|
|
display_layer as extern "C" fn(&Object, Sel, id),
|
|
);
|
|
|
|
decl.register()
|
|
};
|
|
}
|
|
|
|
pub struct StatusItem(Rc<RefCell<StatusItemState>>);
|
|
|
|
struct StatusItemState {
|
|
native_item: StrongPtr,
|
|
native_view: StrongPtr,
|
|
renderer: Renderer,
|
|
scene: Option<Scene>,
|
|
event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
|
|
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
|
}
|
|
|
|
impl StatusItem {
|
|
pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
|
|
unsafe {
|
|
let renderer = Renderer::new(false, fonts);
|
|
let status_bar = NSStatusBar::systemStatusBar(nil);
|
|
let native_item =
|
|
StrongPtr::retain(status_bar.statusItemWithLength_(NSSquareStatusItemLength));
|
|
|
|
let button = native_item.button();
|
|
let _: () = msg_send![button, setHidden: YES];
|
|
|
|
let native_view = msg_send![VIEW_CLASS, alloc];
|
|
let state = Rc::new(RefCell::new(StatusItemState {
|
|
native_item,
|
|
native_view: StrongPtr::new(native_view),
|
|
renderer,
|
|
scene: None,
|
|
event_callback: None,
|
|
appearance_changed_callback: None,
|
|
}));
|
|
|
|
let parent_view = button.superview().superview();
|
|
NSView::initWithFrame_(
|
|
native_view,
|
|
NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
|
|
);
|
|
(*native_view).set_ivar(
|
|
STATE_IVAR,
|
|
Weak::into_raw(Rc::downgrade(&state)) as *const c_void,
|
|
);
|
|
native_view.setWantsBestResolutionOpenGLSurface_(YES);
|
|
native_view.setWantsLayer(YES);
|
|
let _: () = msg_send![
|
|
native_view,
|
|
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
|
|
];
|
|
|
|
parent_view.addSubview_(native_view);
|
|
|
|
{
|
|
let state = state.borrow();
|
|
let layer = state.renderer.layer();
|
|
let scale_factor = state.scale_factor();
|
|
let size = state.content_size() * scale_factor;
|
|
layer.set_contents_scale(scale_factor.into());
|
|
layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
|
|
}
|
|
|
|
Self(state)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl platform::Window for StatusItem {
|
|
fn bounds(&self) -> WindowBounds {
|
|
self.0.borrow().bounds()
|
|
}
|
|
|
|
fn content_size(&self) -> Vector2F {
|
|
self.0.borrow().content_size()
|
|
}
|
|
|
|
fn scale_factor(&self) -> f32 {
|
|
self.0.borrow().scale_factor()
|
|
}
|
|
|
|
fn titlebar_height(&self) -> f32 {
|
|
0.
|
|
}
|
|
|
|
fn appearance(&self) -> platform::Appearance {
|
|
unsafe {
|
|
let appearance: id =
|
|
msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
|
|
platform::Appearance::from_native(appearance)
|
|
}
|
|
}
|
|
|
|
fn screen(&self) -> Rc<dyn platform::Screen> {
|
|
unsafe {
|
|
Rc::new(Screen {
|
|
native_screen: self.0.borrow().native_window().screen(),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn mouse_position(&self) -> Vector2F {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
|
self
|
|
}
|
|
|
|
fn set_input_handler(&mut self, _: Box<dyn platform::InputHandler>) {}
|
|
|
|
fn prompt(
|
|
&self,
|
|
_: crate::platform::PromptLevel,
|
|
_: &str,
|
|
_: &[&str],
|
|
) -> postage::oneshot::Receiver<usize> {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn activate(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn set_title(&mut self, _: &str) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn set_edited(&mut self, _: bool) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn show_character_palette(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn minimize(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn zoom(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn present_scene(&mut self, scene: Scene) {
|
|
self.0.borrow_mut().scene = Some(scene);
|
|
unsafe {
|
|
let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
|
|
}
|
|
}
|
|
|
|
fn toggle_full_screen(&self) {
|
|
unimplemented!()
|
|
}
|
|
|
|
fn on_event(&mut self, callback: Box<dyn FnMut(platform::Event) -> bool>) {
|
|
self.0.borrow_mut().event_callback = Some(callback);
|
|
}
|
|
|
|
fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {}
|
|
|
|
fn on_resize(&mut self, _: Box<dyn FnMut()>) {}
|
|
|
|
fn on_fullscreen(&mut self, _: Box<dyn FnMut(bool)>) {}
|
|
|
|
fn on_moved(&mut self, _: Box<dyn FnMut()>) {}
|
|
|
|
fn on_should_close(&mut self, _: Box<dyn FnMut() -> bool>) {}
|
|
|
|
fn on_close(&mut self, _: Box<dyn FnOnce()>) {}
|
|
|
|
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
|
|
self.0.borrow_mut().appearance_changed_callback = Some(callback);
|
|
}
|
|
|
|
fn is_topmost_for_position(&self, _: Vector2F) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
impl StatusItemState {
|
|
fn bounds(&self) -> WindowBounds {
|
|
unsafe {
|
|
let window: id = self.native_window();
|
|
let screen_frame = window.screen().visibleFrame();
|
|
let window_frame = NSWindow::frame(window);
|
|
let origin = vec2f(
|
|
window_frame.origin.x as f32,
|
|
(window_frame.origin.y - screen_frame.size.height - window_frame.size.height)
|
|
as f32,
|
|
);
|
|
let size = vec2f(
|
|
window_frame.size.width as f32,
|
|
window_frame.size.height as f32,
|
|
);
|
|
WindowBounds::Fixed(RectF::new(origin, size))
|
|
}
|
|
}
|
|
|
|
fn content_size(&self) -> Vector2F {
|
|
unsafe {
|
|
let NSSize { width, height, .. } =
|
|
NSView::frame(self.native_item.button().superview().superview()).size;
|
|
vec2f(width as f32, height as f32)
|
|
}
|
|
}
|
|
|
|
fn scale_factor(&self) -> f32 {
|
|
unsafe {
|
|
let window: id = msg_send![self.native_item.button(), window];
|
|
NSScreen::backingScaleFactor(window.screen()) as f32
|
|
}
|
|
}
|
|
|
|
pub fn native_window(&self) -> id {
|
|
unsafe { msg_send![self.native_item.button(), window] }
|
|
}
|
|
}
|
|
|
|
extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
|
unsafe {
|
|
drop_state(this);
|
|
|
|
let _: () = msg_send![super(this, class!(NSView)), dealloc];
|
|
}
|
|
}
|
|
|
|
extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
|
unsafe {
|
|
if let Some(state) = get_state(this).upgrade() {
|
|
let mut state_borrow = state.as_ref().borrow_mut();
|
|
if let Some(event) =
|
|
Event::from_native(native_event, Some(state_borrow.content_size().y()))
|
|
{
|
|
if let Some(mut callback) = state_borrow.event_callback.take() {
|
|
drop(state_borrow);
|
|
callback(event);
|
|
state.borrow_mut().event_callback = Some(callback);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" fn make_backing_layer(this: &Object, _: Sel) -> id {
|
|
if let Some(state) = unsafe { get_state(this).upgrade() } {
|
|
let state = state.borrow();
|
|
state.renderer.layer().as_ptr() as id
|
|
} else {
|
|
nil
|
|
}
|
|
}
|
|
|
|
extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
|
unsafe {
|
|
if let Some(state) = get_state(this).upgrade() {
|
|
let mut state = state.borrow_mut();
|
|
if let Some(scene) = state.scene.take() {
|
|
state.renderer.render(&scene);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" fn view_did_change_effective_appearance(this: &Object, _: Sel) {
|
|
unsafe {
|
|
if let Some(state) = get_state(this).upgrade() {
|
|
let mut state_borrow = state.as_ref().borrow_mut();
|
|
if let Some(mut callback) = state_borrow.appearance_changed_callback.take() {
|
|
drop(state_borrow);
|
|
callback();
|
|
state.borrow_mut().appearance_changed_callback = Some(callback);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
|
|
let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
|
|
let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);
|
|
let weak2 = weak1.clone();
|
|
let _ = Weak::into_raw(weak1);
|
|
weak2
|
|
}
|
|
|
|
unsafe fn drop_state(object: &Object) {
|
|
let raw: *const c_void = *object.get_ivar(STATE_IVAR);
|
|
Weak::from_raw(raw as *const RefCell<StatusItemState>);
|
|
}
|