Refresh windows when OS appearance changes
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
mod appearance;
|
||||
mod atlas;
|
||||
mod dispatcher;
|
||||
mod event;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
use std::ffi::CStr;
|
||||
|
||||
use cocoa::{
|
||||
appkit::{NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight},
|
||||
base::id,
|
||||
foundation::NSString,
|
||||
};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
use crate::Appearance;
|
||||
|
||||
impl Appearance {
|
||||
pub unsafe fn from_native(appearance: id) -> Self {
|
||||
let name: id = msg_send![appearance, name];
|
||||
if name == NSAppearanceNameVibrantLight {
|
||||
Self::VibrantLight
|
||||
} else if name == NSAppearanceNameVibrantDark {
|
||||
Self::VibrantDark
|
||||
} else if name == NSAppearanceNameAqua {
|
||||
Self::Light
|
||||
} else if name == NSAppearanceNameDarkAqua {
|
||||
Self::Dark
|
||||
} else {
|
||||
println!(
|
||||
"unknown appearance: {:?}",
|
||||
CStr::from_ptr(name.UTF8String())
|
||||
);
|
||||
Self::Light
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[link(name = "AppKit", kind = "framework")]
|
||||
extern "C" {
|
||||
pub static NSAppearanceNameAqua: id;
|
||||
pub static NSAppearanceNameDarkAqua: id;
|
||||
}
|
||||
@@ -52,6 +52,11 @@ use time::UtcOffset;
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSUTF8StringEncoding: NSUInteger = 4;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const NSKeyValueObservingOptionNew: NSInteger = 1;
|
||||
|
||||
const MAC_PLATFORM_IVAR: &str = "platform";
|
||||
static mut APP_CLASS: *const Class = ptr::null();
|
||||
static mut APP_DELEGATE_CLASS: *const Class = ptr::null();
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
use crate::{
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
platform::{self, mac::renderer::Renderer},
|
||||
platform::{
|
||||
self,
|
||||
mac::{
|
||||
platform::{NSKeyValueObservingOptionNew, NSViewLayerContentsRedrawDuringViewResize},
|
||||
renderer::Renderer,
|
||||
},
|
||||
},
|
||||
Event, FontSystem, Scene,
|
||||
};
|
||||
use cocoa::{
|
||||
appkit::{NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
|
||||
base::{id, nil, YES},
|
||||
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize},
|
||||
foundation::{NSPoint, NSRect, NSSize, NSString},
|
||||
};
|
||||
use ctor::ctor;
|
||||
use foreign_types::ForeignTypeRef;
|
||||
@@ -15,7 +21,7 @@ use objc::{
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
rc::StrongPtr,
|
||||
runtime::{Class, Object, Sel},
|
||||
runtime::{Class, Object, Protocol, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use std::{
|
||||
@@ -77,6 +83,20 @@ unsafe fn build_classes() {
|
||||
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!(observeValueForKeyPath:ofObject:change:context:),
|
||||
appearance_changed as extern "C" fn(&Object, Sel, id, id, id, id),
|
||||
);
|
||||
|
||||
decl.add_protocol(Protocol::get("CALayerDelegate").unwrap());
|
||||
decl.add_method(
|
||||
sel!(displayLayer:),
|
||||
display_layer as extern "C" fn(&Object, Sel, id),
|
||||
);
|
||||
|
||||
decl.register()
|
||||
};
|
||||
@@ -86,15 +106,16 @@ 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 pool = NSAutoreleasePool::new(nil);
|
||||
|
||||
let renderer = Renderer::new(false, fonts);
|
||||
let status_bar = NSStatusBar::systemStatusBar(nil);
|
||||
let native_item =
|
||||
@@ -103,39 +124,51 @@ impl StatusItem {
|
||||
let button = native_item.button();
|
||||
let _: () = msg_send![button, setHidden: YES];
|
||||
|
||||
let item = Self(Rc::new_cyclic(|state| {
|
||||
let parent_view = button.superview().superview();
|
||||
|
||||
let view: id = msg_send![VIEW_CLASS, alloc];
|
||||
NSView::initWithFrame_(
|
||||
view,
|
||||
NSRect::new(NSPoint::new(0., 0.), NSView::frame(parent_view).size),
|
||||
);
|
||||
view.setWantsBestResolutionOpenGLSurface_(YES);
|
||||
view.setLayer(renderer.layer().as_ptr() as id);
|
||||
view.setWantsLayer(true);
|
||||
(*view).set_ivar(STATE_IVAR, Weak::into_raw(state.clone()) as *const c_void);
|
||||
parent_view.addSubview_(view.autorelease());
|
||||
|
||||
RefCell::new(StatusItemState {
|
||||
native_item,
|
||||
renderer,
|
||||
event_callback: None,
|
||||
})
|
||||
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(true);
|
||||
let _: () = msg_send![
|
||||
native_view,
|
||||
setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize
|
||||
];
|
||||
let _: () = msg_send![
|
||||
button,
|
||||
addObserver: native_view
|
||||
forKeyPath: NSString::alloc(nil).init_str("effectiveAppearance")
|
||||
options: NSKeyValueObservingOptionNew
|
||||
context: nil
|
||||
];
|
||||
|
||||
parent_view.addSubview_(native_view);
|
||||
|
||||
{
|
||||
let item = item.0.borrow();
|
||||
let layer = item.renderer.layer();
|
||||
let scale_factor = item.scale_factor();
|
||||
let size = item.size() * scale_factor;
|
||||
let state = state.borrow();
|
||||
let layer = state.renderer.layer();
|
||||
let scale_factor = state.scale_factor();
|
||||
let size = state.size() * scale_factor;
|
||||
layer.set_contents_scale(scale_factor.into());
|
||||
layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
|
||||
}
|
||||
|
||||
pool.drain();
|
||||
|
||||
item
|
||||
Self(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,6 +182,10 @@ impl platform::Window for StatusItem {
|
||||
self.0.borrow_mut().event_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
|
||||
self.0.borrow_mut().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
|
||||
fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {
|
||||
unimplemented!()
|
||||
}
|
||||
@@ -223,7 +260,18 @@ impl platform::Window for StatusItem {
|
||||
}
|
||||
|
||||
fn present_scene(&mut self, scene: Scene) {
|
||||
self.0.borrow_mut().renderer.render(&scene);
|
||||
self.0.borrow_mut().scene = Some(scene);
|
||||
unsafe {
|
||||
let _: () = msg_send![*self.0.borrow().native_view, setNeedsDisplay: YES];
|
||||
}
|
||||
}
|
||||
|
||||
fn appearance(&self) -> crate::Appearance {
|
||||
unsafe {
|
||||
let appearance: id =
|
||||
msg_send![self.0.borrow().native_item.button(), effectiveAppearance];
|
||||
crate::Appearance::from_native(appearance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -247,6 +295,7 @@ impl StatusItemState {
|
||||
extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
drop_state(this);
|
||||
|
||||
let _: () = msg_send![super(this, class!(NSView)), dealloc];
|
||||
}
|
||||
}
|
||||
@@ -266,6 +315,39 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||
}
|
||||
}
|
||||
|
||||
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 appearance_changed(this: &Object, _: Sel, _: id, _: id, _: id, _: id) {
|
||||
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>);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
use super::{geometry::RectFExt, renderer::Renderer};
|
||||
use crate::{
|
||||
executor,
|
||||
geometry::{
|
||||
@@ -6,7 +5,12 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
keymap::Keystroke,
|
||||
platform::{self, Event, WindowBounds},
|
||||
mac::platform::{NSKeyValueObservingOptionNew, NSViewLayerContentsRedrawDuringViewResize},
|
||||
platform::{
|
||||
self,
|
||||
mac::{geometry::RectFExt, renderer::Renderer},
|
||||
Event, WindowBounds,
|
||||
},
|
||||
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
|
||||
MouseMovedEvent, Scene,
|
||||
};
|
||||
@@ -102,9 +106,6 @@ unsafe impl objc::Encode for NSRange {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
|
||||
|
||||
#[ctor]
|
||||
unsafe fn build_classes() {
|
||||
WINDOW_CLASS = {
|
||||
@@ -264,6 +265,10 @@ unsafe fn build_classes() {
|
||||
attributed_substring_for_proposed_range
|
||||
as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(observeValueForKeyPath:ofObject:change:context:),
|
||||
appearance_changed as extern "C" fn(&Object, Sel, id, id, id, id),
|
||||
);
|
||||
|
||||
// Suppress beep on keystrokes with modifier keys.
|
||||
decl.add_method(
|
||||
@@ -298,6 +303,7 @@ struct WindowState {
|
||||
fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
|
||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||
close_callback: Option<Box<dyn FnOnce()>>,
|
||||
appearance_changed_callback: Option<Box<dyn FnMut()>>,
|
||||
input_handler: Option<Box<dyn InputHandler>>,
|
||||
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
|
||||
performed_key_equivalent: bool,
|
||||
@@ -376,6 +382,7 @@ impl Window {
|
||||
close_callback: None,
|
||||
activate_callback: None,
|
||||
fullscreen_callback: None,
|
||||
appearance_changed_callback: None,
|
||||
input_handler: None,
|
||||
pending_key_down: None,
|
||||
performed_key_equivalent: false,
|
||||
@@ -433,6 +440,13 @@ impl Window {
|
||||
|
||||
native_window.center();
|
||||
native_window.makeKeyAndOrderFront_(nil);
|
||||
let _: () = msg_send![
|
||||
native_window,
|
||||
addObserver: native_view
|
||||
forKeyPath: NSString::alloc(nil).init_str("effectiveAppearance")
|
||||
options: NSKeyValueObservingOptionNew
|
||||
context: nil
|
||||
];
|
||||
|
||||
window.0.borrow().move_traffic_light();
|
||||
pool.drain();
|
||||
@@ -634,6 +648,17 @@ impl platform::Window for Window {
|
||||
fn titlebar_height(&self) -> f32 {
|
||||
self.0.as_ref().borrow().titlebar_height()
|
||||
}
|
||||
|
||||
fn appearance(&self) -> crate::Appearance {
|
||||
unsafe {
|
||||
let appearance: id = msg_send![self.0.borrow().native_window, effectiveAppearance];
|
||||
crate::Appearance::from_native(appearance)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>) {
|
||||
self.0.borrow_mut().appearance_changed_callback = Some(callback);
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowState {
|
||||
@@ -1270,6 +1295,18 @@ extern "C" fn do_command_by_selector(this: &Object, _: Sel, _: Sel) {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn appearance_changed(this: &Object, _: Sel, _: id, _: id, _: id, _: id) {
|
||||
unsafe {
|
||||
let state = get_window_state(this);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn synthetic_drag(
|
||||
window_state: Weak<RefCell<WindowState>>,
|
||||
drag_id: usize,
|
||||
|
||||
@@ -298,6 +298,12 @@ impl super::Window for Window {
|
||||
fn present_scene(&mut self, scene: crate::Scene) {
|
||||
self.current_scene = Some(scene);
|
||||
}
|
||||
|
||||
fn appearance(&self) -> crate::Appearance {
|
||||
crate::Appearance::Light
|
||||
}
|
||||
|
||||
fn on_appearance_changed(&mut self, _: Box<dyn FnMut()>) {}
|
||||
}
|
||||
|
||||
pub fn platform() -> Platform {
|
||||
|
||||
Reference in New Issue
Block a user