Refresh windows when OS appearance changes

This commit is contained in:
Antonio Scandurra 2022-09-14 11:47:43 +02:00
parent 0f9ff57568
commit f67e2bea29
14 changed files with 304 additions and 67 deletions

View file

@ -1,4 +1,4 @@
use gpui::{elements::*, Entity, RenderContext, View}; use gpui::{color::Color, elements::*, Appearance, Entity, RenderContext, View};
pub struct ContactsStatusItem; pub struct ContactsStatusItem;
@ -11,8 +11,15 @@ impl View for ContactsStatusItem {
"ContactsStatusItem" "ContactsStatusItem"
} }
fn render(&mut self, _: &mut RenderContext<Self>) -> ElementBox { fn render(&mut self, cx: &mut RenderContext<Self>) -> ElementBox {
Svg::new("icons/zed_22.svg").aligned().boxed() let color = match cx.appearance {
Appearance::Light | Appearance::VibrantLight => Color::black(),
Appearance::Dark | Appearance::VibrantDark => Color::white(),
};
Svg::new("icons/zed_22.svg")
.with_color(color)
.aligned()
.boxed()
} }
} }

View file

@ -1149,7 +1149,7 @@ mod tests {
editor: &ViewHandle<Editor>, editor: &ViewHandle<Editor>,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) -> Vec<(u32, String)> { ) -> Vec<(u32, String)> {
let mut presenter = cx.build_presenter(editor.id(), 0.); let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
let mut cx = presenter.build_layout_context(Default::default(), false, cx); let mut cx = presenter.build_layout_context(Default::default(), false, cx);
cx.render(editor, |editor, cx| { cx.render(editor, |editor, cx| {
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);

View file

@ -2044,7 +2044,7 @@ mod tests {
let layouts = editor.update(cx, |editor, cx| { let layouts = editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(cx); let snapshot = editor.snapshot(cx);
let mut presenter = cx.build_presenter(window_id, 30.); let mut presenter = cx.build_presenter(window_id, 30., Default::default());
let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx); let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
element.layout_line_numbers(0..6, &Default::default(), &snapshot, &layout_cx) element.layout_line_numbers(0..6, &Default::default(), &snapshot, &layout_cx)
}); });
@ -2083,7 +2083,7 @@ mod tests {
); );
let mut scene = Scene::new(1.0); let mut scene = Scene::new(1.0);
let mut presenter = cx.build_presenter(window_id, 30.); let mut presenter = cx.build_presenter(window_id, 30., Default::default());
let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx); let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
let (size, mut state) = element.layout( let (size, mut state) = element.layout(
SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),

View file

@ -9,8 +9,8 @@ use crate::{
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
presenter::Presenter, presenter::Presenter,
util::post_inc, util::post_inc,
AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, MouseRegionId, Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton,
PathPromptOptions, TextLayoutCache, MouseRegionId, PathPromptOptions, TextLayoutCache,
}; };
pub use action::*; pub use action::*;
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
@ -579,6 +579,7 @@ impl TestAppContext {
hovered_region_ids: Default::default(), hovered_region_ids: Default::default(),
clicked_region_ids: None, clicked_region_ids: None,
refreshing: false, refreshing: false,
appearance: Appearance::Light,
}; };
f(view, &mut render_cx) f(view, &mut render_cx)
}) })
@ -1260,6 +1261,7 @@ impl MutableAppContext {
&mut self, &mut self,
window_id: usize, window_id: usize,
titlebar_height: f32, titlebar_height: f32,
appearance: Appearance,
) -> HashMap<usize, ElementBox> { ) -> HashMap<usize, ElementBox> {
self.start_frame(); self.start_frame();
#[allow(clippy::needless_collect)] #[allow(clippy::needless_collect)]
@ -1287,6 +1289,7 @@ impl MutableAppContext {
hovered_region_ids: Default::default(), hovered_region_ids: Default::default(),
clicked_region_ids: None, clicked_region_ids: None,
refreshing: false, refreshing: false,
appearance,
}) })
.unwrap(), .unwrap(),
) )
@ -1925,9 +1928,11 @@ impl MutableAppContext {
this.cx this.cx
.platform .platform
.open_window(window_id, window_options, this.foreground.clone()); .open_window(window_id, window_options, this.foreground.clone());
let presenter = Rc::new(RefCell::new( let presenter = Rc::new(RefCell::new(this.build_presenter(
this.build_presenter(window_id, window.titlebar_height()), window_id,
)); window.titlebar_height(),
window.appearance(),
)));
{ {
let mut app = this.upgrade(); let mut app = this.upgrade();
@ -1977,6 +1982,12 @@ impl MutableAppContext {
})); }));
} }
{
let mut app = this.upgrade();
window
.on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows())));
}
window.set_input_handler(Box::new(WindowInputHandler { window.set_input_handler(Box::new(WindowInputHandler {
app: this.upgrade().0, app: this.upgrade().0,
window_id, window_id,
@ -2019,7 +2030,11 @@ impl MutableAppContext {
root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx)); root_view.update(this, |view, cx| view.on_focus_in(cx.handle().into(), cx));
let mut status_item = this.cx.platform.add_status_item(); let mut status_item = this.cx.platform.add_status_item();
let presenter = Rc::new(RefCell::new(this.build_presenter(window_id, 0.))); let presenter = Rc::new(RefCell::new(this.build_presenter(
window_id,
0.,
status_item.appearance(),
)));
{ {
let mut app = this.upgrade(); let mut app = this.upgrade();
@ -2035,6 +2050,12 @@ impl MutableAppContext {
})); }));
} }
{
let mut app = this.upgrade();
status_item
.on_appearance_changed(Box::new(move || app.update(|cx| cx.refresh_windows())));
}
let scene = presenter.borrow_mut().build_scene( let scene = presenter.borrow_mut().build_scene(
status_item.size(), status_item.size(),
status_item.scale_factor(), status_item.scale_factor(),
@ -2071,10 +2092,16 @@ impl MutableAppContext {
self.flush_effects(); self.flush_effects();
} }
pub fn build_presenter(&mut self, window_id: usize, titlebar_height: f32) -> Presenter { pub fn build_presenter(
&mut self,
window_id: usize,
titlebar_height: f32,
appearance: Appearance,
) -> Presenter {
Presenter::new( Presenter::new(
window_id, window_id,
titlebar_height, titlebar_height,
appearance,
self.cx.font_cache.clone(), self.cx.font_cache.clone(),
TextLayoutCache::new(self.cx.platform.fonts()), TextLayoutCache::new(self.cx.platform.fonts()),
self.assets.clone(), self.assets.clone(),
@ -2412,7 +2439,7 @@ impl MutableAppContext {
{ {
{ {
let mut presenter = presenter.borrow_mut(); let mut presenter = presenter.borrow_mut();
presenter.invalidate(&mut invalidation, self); presenter.invalidate(&mut invalidation, window.appearance(), self);
let scene = let scene =
presenter.build_scene(window.size(), window.scale_factor(), false, self); presenter.build_scene(window.size(), window.scale_factor(), false, self);
window.present_scene(scene); window.present_scene(scene);
@ -2476,6 +2503,7 @@ impl MutableAppContext {
let mut presenter = presenter.borrow_mut(); let mut presenter = presenter.borrow_mut();
presenter.refresh( presenter.refresh(
invalidation.as_mut().unwrap_or(&mut Default::default()), invalidation.as_mut().unwrap_or(&mut Default::default()),
window.appearance(),
self, self,
); );
let scene = presenter.build_scene(window.size(), window.scale_factor(), true, self); let scene = presenter.build_scene(window.size(), window.scale_factor(), true, self);
@ -4082,6 +4110,7 @@ pub struct RenderParams {
pub hovered_region_ids: HashSet<MouseRegionId>, pub hovered_region_ids: HashSet<MouseRegionId>,
pub clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>, pub clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
pub refreshing: bool, pub refreshing: bool,
pub appearance: Appearance,
} }
pub struct RenderContext<'a, T: View> { pub struct RenderContext<'a, T: View> {
@ -4092,6 +4121,7 @@ pub struct RenderContext<'a, T: View> {
pub(crate) clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>, pub(crate) clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
pub app: &'a mut MutableAppContext, pub app: &'a mut MutableAppContext,
pub titlebar_height: f32, pub titlebar_height: f32,
pub appearance: Appearance,
pub refreshing: bool, pub refreshing: bool,
} }
@ -4112,6 +4142,7 @@ impl<'a, V: View> RenderContext<'a, V> {
hovered_region_ids: params.hovered_region_ids.clone(), hovered_region_ids: params.hovered_region_ids.clone(),
clicked_region_ids: params.clicked_region_ids.clone(), clicked_region_ids: params.clicked_region_ids.clone(),
refreshing: params.refreshing, refreshing: params.refreshing,
appearance: params.appearance,
} }
} }

View file

@ -659,7 +659,7 @@ mod tests {
#[crate::test(self)] #[crate::test(self)]
fn test_layout(cx: &mut crate::MutableAppContext) { fn test_layout(cx: &mut crate::MutableAppContext) {
let mut presenter = cx.build_presenter(0, 0.); let mut presenter = cx.build_presenter(0, 0., Default::default());
let (_, view) = cx.add_window(Default::default(), |_| TestView); let (_, view) = cx.add_window(Default::default(), |_| TestView);
let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
@ -759,7 +759,7 @@ mod tests {
.unwrap_or(10); .unwrap_or(10);
let (_, view) = cx.add_window(Default::default(), |_| TestView); let (_, view) = cx.add_window(Default::default(), |_| TestView);
let mut presenter = cx.build_presenter(0, 0.); let mut presenter = cx.build_presenter(0, 0., Default::default());
let mut next_id = 0; let mut next_id = 0;
let elements = Rc::new(RefCell::new( let elements = Rc::new(RefCell::new(
(0..rng.gen_range(0..=20)) (0..rng.gen_range(0..=20))

View file

@ -291,7 +291,7 @@ mod tests {
#[crate::test(self)] #[crate::test(self)]
fn test_soft_wrapping_with_carriage_returns(cx: &mut MutableAppContext) { fn test_soft_wrapping_with_carriage_returns(cx: &mut MutableAppContext) {
let (window_id, _) = cx.add_window(Default::default(), |_| TestView); let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
let mut presenter = cx.build_presenter(window_id, Default::default()); let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
fonts::with_font_cache(cx.font_cache().clone(), || { fonts::with_font_cache(cx.font_cache().clone(), || {
let mut text = Text::new("Hello\r\n".into(), Default::default()).with_soft_wrap(true); let mut text = Text::new("Hello\r\n".into(), Default::default()).with_soft_wrap(true);
let (_, state) = text.layout( let (_, state) = text.layout(

View file

@ -132,6 +132,8 @@ pub trait Window {
fn scale_factor(&self) -> f32; fn scale_factor(&self) -> f32;
fn titlebar_height(&self) -> f32; fn titlebar_height(&self) -> f32;
fn present_scene(&mut self, scene: Scene); fn present_scene(&mut self, scene: Scene);
fn appearance(&self) -> Appearance;
fn on_appearance_changed(&mut self, callback: Box<dyn FnMut()>);
} }
#[derive(Debug)] #[derive(Debug)]
@ -147,6 +149,20 @@ pub struct TitlebarOptions<'a> {
pub traffic_light_position: Option<Vector2F>, pub traffic_light_position: Option<Vector2F>,
} }
#[derive(Copy, Clone, Debug)]
pub enum Appearance {
Light,
VibrantLight,
Dark,
VibrantDark,
}
impl Default for Appearance {
fn default() -> Self {
Self::Light
}
}
#[derive(Debug)] #[derive(Debug)]
pub enum WindowBounds { pub enum WindowBounds {
Maximized, Maximized,
@ -173,6 +189,12 @@ pub enum CursorStyle {
IBeam, IBeam,
} }
impl Default for CursorStyle {
fn default() -> Self {
Self::Arrow
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct AppVersion { pub struct AppVersion {
major: usize, major: usize,
@ -180,12 +202,6 @@ pub struct AppVersion {
patch: usize, patch: usize,
} }
impl Default for CursorStyle {
fn default() -> Self {
Self::Arrow
}
}
impl FromStr for AppVersion { impl FromStr for AppVersion {
type Err = anyhow::Error; type Err = anyhow::Error;

View file

@ -1,3 +1,4 @@
mod appearance;
mod atlas; mod atlas;
mod dispatcher; mod dispatcher;
mod event; mod event;

View file

@ -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;
}

View file

@ -52,6 +52,11 @@ use time::UtcOffset;
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
const NSUTF8StringEncoding: NSUInteger = 4; 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"; const MAC_PLATFORM_IVAR: &str = "platform";
static mut APP_CLASS: *const Class = ptr::null(); static mut APP_CLASS: *const Class = ptr::null();
static mut APP_DELEGATE_CLASS: *const Class = ptr::null(); static mut APP_DELEGATE_CLASS: *const Class = ptr::null();

View file

@ -1,12 +1,18 @@
use crate::{ use crate::{
geometry::vector::{vec2f, Vector2F}, geometry::vector::{vec2f, Vector2F},
platform::{self, mac::renderer::Renderer}, platform::{
self,
mac::{
platform::{NSKeyValueObservingOptionNew, NSViewLayerContentsRedrawDuringViewResize},
renderer::Renderer,
},
},
Event, FontSystem, Scene, Event, FontSystem, Scene,
}; };
use cocoa::{ use cocoa::{
appkit::{NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow}, appkit::{NSSquareStatusItemLength, NSStatusBar, NSStatusItem, NSView, NSWindow},
base::{id, nil, YES}, base::{id, nil, YES},
foundation::{NSAutoreleasePool, NSPoint, NSRect, NSSize}, foundation::{NSPoint, NSRect, NSSize, NSString},
}; };
use ctor::ctor; use ctor::ctor;
use foreign_types::ForeignTypeRef; use foreign_types::ForeignTypeRef;
@ -15,7 +21,7 @@ use objc::{
declare::ClassDecl, declare::ClassDecl,
msg_send, msg_send,
rc::StrongPtr, rc::StrongPtr,
runtime::{Class, Object, Sel}, runtime::{Class, Object, Protocol, Sel},
sel, sel_impl, sel, sel_impl,
}; };
use std::{ use std::{
@ -77,6 +83,20 @@ unsafe fn build_classes() {
sel!(flagsChanged:), sel!(flagsChanged:),
handle_view_event as extern "C" fn(&Object, Sel, id), 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() decl.register()
}; };
@ -86,15 +106,16 @@ pub struct StatusItem(Rc<RefCell<StatusItemState>>);
struct StatusItemState { struct StatusItemState {
native_item: StrongPtr, native_item: StrongPtr,
native_view: StrongPtr,
renderer: Renderer, renderer: Renderer,
scene: Option<Scene>,
event_callback: Option<Box<dyn FnMut(Event) -> bool>>, event_callback: Option<Box<dyn FnMut(Event) -> bool>>,
appearance_changed_callback: Option<Box<dyn FnMut()>>,
} }
impl StatusItem { impl StatusItem {
pub fn add(fonts: Arc<dyn FontSystem>) -> Self { pub fn add(fonts: Arc<dyn FontSystem>) -> Self {
unsafe { unsafe {
let pool = NSAutoreleasePool::new(nil);
let renderer = Renderer::new(false, fonts); let renderer = Renderer::new(false, fonts);
let status_bar = NSStatusBar::systemStatusBar(nil); let status_bar = NSStatusBar::systemStatusBar(nil);
let native_item = let native_item =
@ -103,39 +124,51 @@ impl StatusItem {
let button = native_item.button(); let button = native_item.button();
let _: () = msg_send![button, setHidden: YES]; let _: () = msg_send![button, setHidden: YES];
let item = Self(Rc::new_cyclic(|state| { let native_view = msg_send![VIEW_CLASS, alloc];
let parent_view = button.superview().superview(); let state = Rc::new(RefCell::new(StatusItemState {
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, native_item,
native_view: StrongPtr::new(native_view),
renderer, renderer,
scene: None,
event_callback: 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 state = state.borrow();
let layer = item.renderer.layer(); let layer = state.renderer.layer();
let scale_factor = item.scale_factor(); let scale_factor = state.scale_factor();
let size = item.size() * scale_factor; let size = state.size() * scale_factor;
layer.set_contents_scale(scale_factor.into()); layer.set_contents_scale(scale_factor.into());
layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into())); layer.set_drawable_size(metal::CGSize::new(size.x().into(), size.y().into()));
} }
pool.drain(); Self(state)
item
} }
} }
} }
@ -149,6 +182,10 @@ impl platform::Window for StatusItem {
self.0.borrow_mut().event_callback = Some(callback); 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)>) { fn on_active_status_change(&mut self, _: Box<dyn FnMut(bool)>) {
unimplemented!() unimplemented!()
} }
@ -223,7 +260,18 @@ impl platform::Window for StatusItem {
} }
fn present_scene(&mut self, scene: Scene) { 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) { extern "C" fn dealloc_view(this: &Object, _: Sel) {
unsafe { unsafe {
drop_state(this); drop_state(this);
let _: () = msg_send![super(this, class!(NSView)), dealloc]; 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>> { unsafe fn get_state(object: &Object) -> Weak<RefCell<StatusItemState>> {
let raw: *mut c_void = *object.get_ivar(STATE_IVAR); let raw: *mut c_void = *object.get_ivar(STATE_IVAR);
let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>); let weak1 = Weak::from_raw(raw as *mut RefCell<StatusItemState>);

View file

@ -1,4 +1,3 @@
use super::{geometry::RectFExt, renderer::Renderer};
use crate::{ use crate::{
executor, executor,
geometry::{ geometry::{
@ -6,7 +5,12 @@ use crate::{
vector::{vec2f, Vector2F}, vector::{vec2f, Vector2F},
}, },
keymap::Keystroke, keymap::Keystroke,
platform::{self, Event, WindowBounds}, mac::platform::{NSKeyValueObservingOptionNew, NSViewLayerContentsRedrawDuringViewResize},
platform::{
self,
mac::{geometry::RectFExt, renderer::Renderer},
Event, WindowBounds,
},
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent, InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
MouseMovedEvent, Scene, MouseMovedEvent, Scene,
}; };
@ -102,9 +106,6 @@ unsafe impl objc::Encode for NSRange {
} }
} }
#[allow(non_upper_case_globals)]
const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
#[ctor] #[ctor]
unsafe fn build_classes() { unsafe fn build_classes() {
WINDOW_CLASS = { WINDOW_CLASS = {
@ -264,6 +265,10 @@ unsafe fn build_classes() {
attributed_substring_for_proposed_range attributed_substring_for_proposed_range
as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id, 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. // Suppress beep on keystrokes with modifier keys.
decl.add_method( decl.add_method(
@ -298,6 +303,7 @@ struct WindowState {
fullscreen_callback: Option<Box<dyn FnMut(bool)>>, fullscreen_callback: Option<Box<dyn FnMut(bool)>>,
should_close_callback: Option<Box<dyn FnMut() -> bool>>, should_close_callback: Option<Box<dyn FnMut() -> bool>>,
close_callback: Option<Box<dyn FnOnce()>>, close_callback: Option<Box<dyn FnOnce()>>,
appearance_changed_callback: Option<Box<dyn FnMut()>>,
input_handler: Option<Box<dyn InputHandler>>, input_handler: Option<Box<dyn InputHandler>>,
pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>, pending_key_down: Option<(KeyDownEvent, Option<InsertText>)>,
performed_key_equivalent: bool, performed_key_equivalent: bool,
@ -376,6 +382,7 @@ impl Window {
close_callback: None, close_callback: None,
activate_callback: None, activate_callback: None,
fullscreen_callback: None, fullscreen_callback: None,
appearance_changed_callback: None,
input_handler: None, input_handler: None,
pending_key_down: None, pending_key_down: None,
performed_key_equivalent: false, performed_key_equivalent: false,
@ -433,6 +440,13 @@ impl Window {
native_window.center(); native_window.center();
native_window.makeKeyAndOrderFront_(nil); 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(); window.0.borrow().move_traffic_light();
pool.drain(); pool.drain();
@ -634,6 +648,17 @@ impl platform::Window for Window {
fn titlebar_height(&self) -> f32 { fn titlebar_height(&self) -> f32 {
self.0.as_ref().borrow().titlebar_height() 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 { 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( async fn synthetic_drag(
window_state: Weak<RefCell<WindowState>>, window_state: Weak<RefCell<WindowState>>,
drag_id: usize, drag_id: usize,

View file

@ -298,6 +298,12 @@ impl super::Window for Window {
fn present_scene(&mut self, scene: crate::Scene) { fn present_scene(&mut self, scene: crate::Scene) {
self.current_scene = Some(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 { pub fn platform() -> Platform {

View file

@ -11,10 +11,10 @@ use crate::{
HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
}, },
text_layout::TextLayoutCache, text_layout::TextLayoutCache,
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, Appearance, AssetCache, ElementBox,
FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, ParentId, Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId,
ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle,
View, ViewHandle, WeakModelHandle, WeakViewHandle, UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
}; };
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use pathfinder_geometry::vector::{vec2f, Vector2F}; use pathfinder_geometry::vector::{vec2f, Vector2F};
@ -40,12 +40,14 @@ pub struct Presenter {
clicked_button: Option<MouseButton>, clicked_button: Option<MouseButton>,
mouse_position: Vector2F, mouse_position: Vector2F,
titlebar_height: f32, titlebar_height: f32,
appearance: Appearance,
} }
impl Presenter { impl Presenter {
pub fn new( pub fn new(
window_id: usize, window_id: usize,
titlebar_height: f32, titlebar_height: f32,
appearance: Appearance,
font_cache: Arc<FontCache>, font_cache: Arc<FontCache>,
text_layout_cache: TextLayoutCache, text_layout_cache: TextLayoutCache,
asset_cache: Arc<AssetCache>, asset_cache: Arc<AssetCache>,
@ -53,7 +55,7 @@ impl Presenter {
) -> Self { ) -> Self {
Self { Self {
window_id, window_id,
rendered_views: cx.render_views(window_id, titlebar_height), rendered_views: cx.render_views(window_id, titlebar_height, appearance),
cursor_regions: Default::default(), cursor_regions: Default::default(),
mouse_regions: Default::default(), mouse_regions: Default::default(),
font_cache, font_cache,
@ -65,15 +67,18 @@ impl Presenter {
clicked_button: None, clicked_button: None,
mouse_position: vec2f(0., 0.), mouse_position: vec2f(0., 0.),
titlebar_height, titlebar_height,
appearance,
} }
} }
pub fn invalidate( pub fn invalidate(
&mut self, &mut self,
invalidation: &mut WindowInvalidation, invalidation: &mut WindowInvalidation,
appearance: Appearance,
cx: &mut MutableAppContext, cx: &mut MutableAppContext,
) { ) {
cx.start_frame(); cx.start_frame();
self.appearance = appearance;
for view_id in &invalidation.removed { for view_id in &invalidation.removed {
invalidation.updated.remove(view_id); invalidation.updated.remove(view_id);
self.rendered_views.remove(view_id); self.rendered_views.remove(view_id);
@ -96,14 +101,20 @@ impl Presenter {
) )
}), }),
refreshing: false, refreshing: false,
appearance,
}) })
.unwrap(), .unwrap(),
); );
} }
} }
pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, cx: &mut MutableAppContext) { pub fn refresh(
self.invalidate(invalidation, cx); &mut self,
invalidation: &mut WindowInvalidation,
appearance: Appearance,
cx: &mut MutableAppContext,
) {
self.invalidate(invalidation, appearance, cx);
for (view_id, view) in &mut self.rendered_views { for (view_id, view) in &mut self.rendered_views {
if !invalidation.updated.contains(view_id) { if !invalidation.updated.contains(view_id) {
*view = cx *view = cx
@ -122,6 +133,7 @@ impl Presenter {
) )
}), }),
refreshing: true, refreshing: true,
appearance,
}) })
.unwrap(); .unwrap();
} }
@ -194,6 +206,7 @@ impl Presenter {
) )
}), }),
titlebar_height: self.titlebar_height, titlebar_height: self.titlebar_height,
appearance: self.appearance,
window_size, window_size,
app: cx, app: cx,
} }
@ -545,6 +558,7 @@ pub struct LayoutContext<'a> {
pub refreshing: bool, pub refreshing: bool,
pub window_size: Vector2F, pub window_size: Vector2F,
titlebar_height: f32, titlebar_height: f32,
appearance: Appearance,
hovered_region_ids: HashSet<MouseRegionId>, hovered_region_ids: HashSet<MouseRegionId>,
clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>, clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
} }
@ -619,6 +633,7 @@ impl<'a> LayoutContext<'a> {
hovered_region_ids: self.hovered_region_ids.clone(), hovered_region_ids: self.hovered_region_ids.clone(),
clicked_region_ids: self.clicked_region_ids.clone(), clicked_region_ids: self.clicked_region_ids.clone(),
refreshing: self.refreshing, refreshing: self.refreshing,
appearance: self.appearance,
}; };
f(view, &mut render_cx) f(view, &mut render_cx)
}) })