From f67e2bea29f286d41b36ffccfde0fa9cbfb04bfa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 14 Sep 2022 11:47:43 +0200 Subject: [PATCH] Refresh windows when OS appearance changes --- .../src/contacts_status_item.rs | 13 +- crates/diagnostics/src/diagnostics.rs | 2 +- crates/editor/src/element.rs | 4 +- crates/gpui/src/app.rs | 47 +++++- crates/gpui/src/elements/list.rs | 4 +- crates/gpui/src/elements/text.rs | 2 +- crates/gpui/src/platform.rs | 28 +++- crates/gpui/src/platform/mac.rs | 1 + crates/gpui/src/platform/mac/appearance.rs | 37 +++++ crates/gpui/src/platform/mac/platform.rs | 5 + crates/gpui/src/platform/mac/status_item.rs | 146 ++++++++++++++---- crates/gpui/src/platform/mac/window.rs | 47 +++++- crates/gpui/src/platform/test.rs | 6 + crates/gpui/src/presenter.rs | 29 +++- 14 files changed, 304 insertions(+), 67 deletions(-) create mode 100644 crates/gpui/src/platform/mac/appearance.rs diff --git a/crates/contacts_status_item/src/contacts_status_item.rs b/crates/contacts_status_item/src/contacts_status_item.rs index c7cd9c3a4a..f3363f18d3 100644 --- a/crates/contacts_status_item/src/contacts_status_item.rs +++ b/crates/contacts_status_item/src/contacts_status_item.rs @@ -1,4 +1,4 @@ -use gpui::{elements::*, Entity, RenderContext, View}; +use gpui::{color::Color, elements::*, Appearance, Entity, RenderContext, View}; pub struct ContactsStatusItem; @@ -11,8 +11,15 @@ impl View for ContactsStatusItem { "ContactsStatusItem" } - fn render(&mut self, _: &mut RenderContext) -> ElementBox { - Svg::new("icons/zed_22.svg").aligned().boxed() + fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + 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() } } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 271843dc69..25db51e5e8 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -1149,7 +1149,7 @@ mod tests { editor: &ViewHandle, cx: &mut MutableAppContext, ) -> 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); cx.render(editor, |editor, cx| { let snapshot = editor.snapshot(cx); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d9d0c3990c..5146be120a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -2044,7 +2044,7 @@ mod tests { let layouts = editor.update(cx, |editor, 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); 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 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 (size, mut state) = element.layout( SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)), diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index a4d067fb4f..0baff67e94 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -9,8 +9,8 @@ use crate::{ platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions}, presenter::Presenter, util::post_inc, - AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, MouseRegionId, - PathPromptOptions, TextLayoutCache, + Appearance, AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, + MouseRegionId, PathPromptOptions, TextLayoutCache, }; pub use action::*; use anyhow::{anyhow, Context, Result}; @@ -579,6 +579,7 @@ impl TestAppContext { hovered_region_ids: Default::default(), clicked_region_ids: None, refreshing: false, + appearance: Appearance::Light, }; f(view, &mut render_cx) }) @@ -1260,6 +1261,7 @@ impl MutableAppContext { &mut self, window_id: usize, titlebar_height: f32, + appearance: Appearance, ) -> HashMap { self.start_frame(); #[allow(clippy::needless_collect)] @@ -1287,6 +1289,7 @@ impl MutableAppContext { hovered_region_ids: Default::default(), clicked_region_ids: None, refreshing: false, + appearance, }) .unwrap(), ) @@ -1925,9 +1928,11 @@ impl MutableAppContext { this.cx .platform .open_window(window_id, window_options, this.foreground.clone()); - let presenter = Rc::new(RefCell::new( - this.build_presenter(window_id, window.titlebar_height()), - )); + let presenter = Rc::new(RefCell::new(this.build_presenter( + window_id, + window.titlebar_height(), + window.appearance(), + ))); { 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 { app: this.upgrade().0, window_id, @@ -2019,7 +2030,11 @@ impl MutableAppContext { 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 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(); @@ -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( status_item.size(), status_item.scale_factor(), @@ -2071,10 +2092,16 @@ impl MutableAppContext { 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( window_id, titlebar_height, + appearance, self.cx.font_cache.clone(), TextLayoutCache::new(self.cx.platform.fonts()), self.assets.clone(), @@ -2412,7 +2439,7 @@ impl MutableAppContext { { { let mut presenter = presenter.borrow_mut(); - presenter.invalidate(&mut invalidation, self); + presenter.invalidate(&mut invalidation, window.appearance(), self); let scene = presenter.build_scene(window.size(), window.scale_factor(), false, self); window.present_scene(scene); @@ -2476,6 +2503,7 @@ impl MutableAppContext { let mut presenter = presenter.borrow_mut(); presenter.refresh( invalidation.as_mut().unwrap_or(&mut Default::default()), + window.appearance(), 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, pub clicked_region_ids: Option<(Vec, MouseButton)>, pub refreshing: bool, + pub appearance: Appearance, } pub struct RenderContext<'a, T: View> { @@ -4092,6 +4121,7 @@ pub struct RenderContext<'a, T: View> { pub(crate) clicked_region_ids: Option<(Vec, MouseButton)>, pub app: &'a mut MutableAppContext, pub titlebar_height: f32, + pub appearance: Appearance, pub refreshing: bool, } @@ -4112,6 +4142,7 @@ impl<'a, V: View> RenderContext<'a, V> { hovered_region_ids: params.hovered_region_ids.clone(), clicked_region_ids: params.clicked_region_ids.clone(), refreshing: params.refreshing, + appearance: params.appearance, } } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index df480005b5..57436c256b 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -659,7 +659,7 @@ mod tests { #[crate::test(self)] 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 constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); @@ -759,7 +759,7 @@ mod tests { .unwrap_or(10); 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 elements = Rc::new(RefCell::new( (0..rng.gen_range(0..=20)) diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 92e38894c1..45dc2dbae3 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -291,7 +291,7 @@ mod tests { #[crate::test(self)] fn test_soft_wrapping_with_carriage_returns(cx: &mut MutableAppContext) { 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(), || { let mut text = Text::new("Hello\r\n".into(), Default::default()).with_soft_wrap(true); let (_, state) = text.layout( diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b739ace947..d2ae7d8d17 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -132,6 +132,8 @@ pub trait Window { fn scale_factor(&self) -> f32; fn titlebar_height(&self) -> f32; fn present_scene(&mut self, scene: Scene); + fn appearance(&self) -> Appearance; + fn on_appearance_changed(&mut self, callback: Box); } #[derive(Debug)] @@ -147,6 +149,20 @@ pub struct TitlebarOptions<'a> { pub traffic_light_position: Option, } +#[derive(Copy, Clone, Debug)] +pub enum Appearance { + Light, + VibrantLight, + Dark, + VibrantDark, +} + +impl Default for Appearance { + fn default() -> Self { + Self::Light + } +} + #[derive(Debug)] pub enum WindowBounds { Maximized, @@ -173,6 +189,12 @@ pub enum CursorStyle { IBeam, } +impl Default for CursorStyle { + fn default() -> Self { + Self::Arrow + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct AppVersion { major: usize, @@ -180,12 +202,6 @@ pub struct AppVersion { patch: usize, } -impl Default for CursorStyle { - fn default() -> Self { - Self::Arrow - } -} - impl FromStr for AppVersion { type Err = anyhow::Error; diff --git a/crates/gpui/src/platform/mac.rs b/crates/gpui/src/platform/mac.rs index bd3c4eaf20..90b378e4a6 100644 --- a/crates/gpui/src/platform/mac.rs +++ b/crates/gpui/src/platform/mac.rs @@ -1,3 +1,4 @@ +mod appearance; mod atlas; mod dispatcher; mod event; diff --git a/crates/gpui/src/platform/mac/appearance.rs b/crates/gpui/src/platform/mac/appearance.rs new file mode 100644 index 0000000000..c0b8258a0e --- /dev/null +++ b/crates/gpui/src/platform/mac/appearance.rs @@ -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; +} diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index d1a3b7a797..29e6b58e59 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -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(); diff --git a/crates/gpui/src/platform/mac/status_item.rs b/crates/gpui/src/platform/mac/status_item.rs index c369ab4245..a6570e4890 100644 --- a/crates/gpui/src/platform/mac/status_item.rs +++ b/crates/gpui/src/platform/mac/status_item.rs @@ -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>); struct StatusItemState { native_item: StrongPtr, + native_view: StrongPtr, renderer: Renderer, + scene: Option, event_callback: Option bool>>, + appearance_changed_callback: Option>, } impl StatusItem { pub fn add(fonts: Arc) -> 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) { + self.0.borrow_mut().appearance_changed_callback = Some(callback); + } + fn on_active_status_change(&mut self, _: Box) { 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> { let raw: *mut c_void = *object.get_ivar(STATE_IVAR); let weak1 = Weak::from_raw(raw as *mut RefCell); diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index 4abee8b961..5cb45719eb 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -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>, should_close_callback: Option bool>>, close_callback: Option>, + appearance_changed_callback: Option>, input_handler: Option>, pending_key_down: Option<(KeyDownEvent, Option)>, 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) { + 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>, drag_id: usize, diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 0ce44f0817..9a4f3638be 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -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) {} } pub fn platform() -> Platform { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 859ba46375..2943fda647 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -11,10 +11,10 @@ use crate::{ HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, }, text_layout::TextLayoutCache, - Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, - FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, ParentId, - ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle, - View, ViewHandle, WeakModelHandle, WeakViewHandle, + Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, Appearance, AssetCache, ElementBox, + Entity, FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, + ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, + UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle, }; use collections::{HashMap, HashSet}; use pathfinder_geometry::vector::{vec2f, Vector2F}; @@ -40,12 +40,14 @@ pub struct Presenter { clicked_button: Option, mouse_position: Vector2F, titlebar_height: f32, + appearance: Appearance, } impl Presenter { pub fn new( window_id: usize, titlebar_height: f32, + appearance: Appearance, font_cache: Arc, text_layout_cache: TextLayoutCache, asset_cache: Arc, @@ -53,7 +55,7 @@ impl Presenter { ) -> Self { Self { 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(), mouse_regions: Default::default(), font_cache, @@ -65,15 +67,18 @@ impl Presenter { clicked_button: None, mouse_position: vec2f(0., 0.), titlebar_height, + appearance, } } pub fn invalidate( &mut self, invalidation: &mut WindowInvalidation, + appearance: Appearance, cx: &mut MutableAppContext, ) { cx.start_frame(); + self.appearance = appearance; for view_id in &invalidation.removed { invalidation.updated.remove(view_id); self.rendered_views.remove(view_id); @@ -96,14 +101,20 @@ impl Presenter { ) }), refreshing: false, + appearance, }) .unwrap(), ); } } - pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, cx: &mut MutableAppContext) { - self.invalidate(invalidation, cx); + pub fn refresh( + &mut self, + invalidation: &mut WindowInvalidation, + appearance: Appearance, + cx: &mut MutableAppContext, + ) { + self.invalidate(invalidation, appearance, cx); for (view_id, view) in &mut self.rendered_views { if !invalidation.updated.contains(view_id) { *view = cx @@ -122,6 +133,7 @@ impl Presenter { ) }), refreshing: true, + appearance, }) .unwrap(); } @@ -194,6 +206,7 @@ impl Presenter { ) }), titlebar_height: self.titlebar_height, + appearance: self.appearance, window_size, app: cx, } @@ -545,6 +558,7 @@ pub struct LayoutContext<'a> { pub refreshing: bool, pub window_size: Vector2F, titlebar_height: f32, + appearance: Appearance, hovered_region_ids: HashSet, clicked_region_ids: Option<(Vec, MouseButton)>, } @@ -619,6 +633,7 @@ impl<'a> LayoutContext<'a> { hovered_region_ids: self.hovered_region_ids.clone(), clicked_region_ids: self.clicked_region_ids.clone(), refreshing: self.refreshing, + appearance: self.appearance, }; f(view, &mut render_cx) })