From 7f64076f8d63d06d64f47aeb25340d360ee5ef76 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 22 Apr 2022 14:18:50 -0700 Subject: [PATCH] Focus/blur views when application windows become active/inactive --- crates/gpui/src/app.rs | 64 +++++++++++++++++++++++++- crates/gpui/src/platform.rs | 1 + crates/gpui/src/platform/mac/window.rs | 34 ++++++++++++++ crates/gpui/src/platform/test.rs | 2 + 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 5765cdf524..3aba2cbffb 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -1591,6 +1591,7 @@ impl MutableAppContext { Window { root_view: root_view.clone().into(), focused_view_id: Some(root_view.id()), + is_active: true, invalidation: None, }, ); @@ -1638,10 +1639,17 @@ impl MutableAppContext { })); } + { + let mut app = self.upgrade(); + window.on_active_status_change(Box::new(move |is_active| { + app.update(|cx| cx.window_changed_active_status(window_id, is_active)) + })); + } + { let mut app = self.upgrade(); window.on_resize(Box::new(move || { - app.update(|cx| cx.resize_window(window_id)) + app.update(|cx| cx.window_was_resized(window_id)) })); } @@ -1856,6 +1864,10 @@ impl MutableAppContext { .get_or_insert(WindowInvalidation::default()); } } + Effect::ActivateWindow { + window_id, + is_active, + } => self.handle_activation_effect(window_id, is_active), Effect::RefreshWindows => { refreshing = true; } @@ -1914,11 +1926,18 @@ impl MutableAppContext { } } - fn resize_window(&mut self, window_id: usize) { + fn window_was_resized(&mut self, window_id: usize) { self.pending_effects .push_back(Effect::ResizeWindow { window_id }); } + fn window_changed_active_status(&mut self, window_id: usize, is_active: bool) { + self.pending_effects.push_back(Effect::ActivateWindow { + window_id, + is_active, + }); + } + pub fn refresh_windows(&mut self) { self.pending_effects.push_back(Effect::RefreshWindows); } @@ -2204,6 +2223,34 @@ impl MutableAppContext { } } + fn handle_activation_effect(&mut self, window_id: usize, active: bool) { + if self + .cx + .windows + .get(&window_id) + .map(|w| w.is_active) + .map_or(false, |cur_active| cur_active == active) + { + return; + } + + self.update(|this| { + let window = this.cx.windows.get_mut(&window_id)?; + window.is_active = active; + let view_id = window.focused_view_id?; + if let Some(mut view) = this.cx.views.remove(&(window_id, view_id)) { + if active { + view.on_focus(this, window_id, view_id); + } else { + view.on_blur(this, window_id, view_id); + } + this.cx.views.insert((window_id, view_id), view); + } + + Some(()) + }); + } + fn handle_focus_effect(&mut self, window_id: usize, focused_id: Option) { self.pending_focus_index.take(); @@ -2567,6 +2614,7 @@ impl ReadView for AppContext { struct Window { root_view: AnyViewHandle, focused_view_id: Option, + is_active: bool, invalidation: Option, } @@ -2633,6 +2681,10 @@ pub enum Effect { ResizeWindow { window_id: usize, }, + ActivateWindow { + window_id: usize, + is_active: bool, + }, RefreshWindows, } @@ -2714,6 +2766,14 @@ impl Debug for Effect { .debug_struct("Effect::RefreshWindow") .field("window_id", window_id) .finish(), + Effect::ActivateWindow { + window_id, + is_active, + } => f + .debug_struct("Effect::ActivateWindow") + .field("window_id", window_id) + .field("is_active", is_active) + .finish(), Effect::RefreshWindows => f.debug_struct("Effect::FullViewRefresh").finish(), } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 92530e26c3..5e4e639bc8 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -87,6 +87,7 @@ pub trait Dispatcher: Send + Sync { pub trait Window: WindowContext { fn as_any_mut(&mut self) -> &mut dyn Any; fn on_event(&mut self, callback: Box); + fn on_active_status_change(&mut self, callback: Box); fn on_resize(&mut self, callback: Box); fn on_close(&mut self, callback: Box); fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index f6798e15af..67a9a0939f 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -73,6 +73,14 @@ unsafe fn build_classes() { sel!(windowDidResize:), window_did_resize as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(windowDidBecomeKey:), + window_did_become_key as extern "C" fn(&Object, Sel, id), + ); + decl.add_method( + sel!(windowDidResignKey:), + window_did_resign_key as extern "C" fn(&Object, Sel, id), + ); decl.add_method(sel!(close), close_window as extern "C" fn(&Object, Sel)); decl.register() }; @@ -157,6 +165,7 @@ struct WindowState { id: usize, native_window: id, event_callback: Option>, + activate_callback: Option>, resize_callback: Option>, close_callback: Option>, synthetic_drag_counter: usize, @@ -234,6 +243,7 @@ impl Window { event_callback: None, resize_callback: None, close_callback: None, + activate_callback: None, synthetic_drag_counter: 0, executor, scene_to_render: Default::default(), @@ -333,6 +343,10 @@ impl platform::Window for Window { self.0.as_ref().borrow_mut().close_callback = Some(callback); } + fn on_active_status_change(&mut self, callback: Box) { + self.0.as_ref().borrow_mut().activate_callback = Some(callback); + } + fn prompt( &self, level: platform::PromptLevel, @@ -598,6 +612,26 @@ extern "C" fn window_did_resize(this: &Object, _: Sel, _: id) { window_state.as_ref().borrow().move_traffic_light(); } +extern "C" fn window_did_become_key(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut callback) = window_state_borrow.activate_callback.take() { + drop(window_state_borrow); + callback(true); + window_state.borrow_mut().activate_callback = Some(callback); + }; +} + +extern "C" fn window_did_resign_key(this: &Object, _: Sel, _: id) { + let window_state = unsafe { get_window_state(this) }; + let mut window_state_borrow = window_state.as_ref().borrow_mut(); + if let Some(mut callback) = window_state_borrow.activate_callback.take() { + drop(window_state_borrow); + callback(false); + window_state.borrow_mut().activate_callback = Some(callback); + }; +} + extern "C" fn close_window(this: &Object, _: Sel) { unsafe { let close_callback = { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 6804b39f7c..8786eff255 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -229,6 +229,8 @@ impl super::Window for Window { self.event_handlers.push(callback); } + fn on_active_status_change(&mut self, _: Box) {} + fn on_resize(&mut self, callback: Box) { self.resize_handlers.push(callback); }