From 5a8297a02ffe5e8eca8040af8178eafe7dbeed50 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 14 Apr 2022 18:22:32 +0200 Subject: [PATCH] Introduce `ViewContext::observe_focus` --- crates/gpui/src/app.rs | 196 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 189 insertions(+), 7 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index b70602f2ca..d553b9ba26 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -716,6 +716,7 @@ type GlobalActionCallback = dyn FnMut(&dyn Action, &mut MutableAppContext); type SubscriptionCallback = Box bool>; type GlobalSubscriptionCallback = Box; type ObservationCallback = Box bool>; +type FocusObservationCallback = Box bool>; type GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; type DeserializeActionCallback = fn(json: &str) -> anyhow::Result>; @@ -738,6 +739,8 @@ pub struct MutableAppContext { global_subscriptions: Arc>>>>, observations: Arc>>>>, + focus_observations: + Arc>>>>, global_observations: Arc>>>>, release_observations: Arc>>>, @@ -791,6 +794,7 @@ impl MutableAppContext { subscriptions: Default::default(), global_subscriptions: Default::default(), observations: Default::default(), + focus_observations: Default::default(), release_observations: Default::default(), global_observations: Default::default(), presenters_and_platform_windows: HashMap::new(), @@ -1185,6 +1189,32 @@ impl MutableAppContext { } } + fn observe_focus(&mut self, handle: &ViewHandle, mut callback: F) -> Subscription + where + F: 'static + FnMut(ViewHandle, &mut MutableAppContext) -> bool, + V: View, + { + let subscription_id = post_inc(&mut self.next_subscription_id); + let observed = handle.downgrade(); + let view_id = handle.id(); + self.pending_effects.push_back(Effect::FocusObservation { + view_id, + subscription_id, + callback: Box::new(move |cx| { + if let Some(observed) = observed.upgrade(cx) { + callback(observed, cx) + } else { + false + } + }), + }); + Subscription::FocusObservation { + id: subscription_id, + view_id, + observations: Some(Arc::downgrade(&self.focus_observations)), + } + } + pub fn observe_global(&mut self, mut observe: F) -> Subscription where G: Any, @@ -1748,6 +1778,13 @@ impl MutableAppContext { Effect::Focus { window_id, view_id } => { self.handle_focus_effect(window_id, view_id); } + Effect::FocusObservation { + view_id, + subscription_id, + callback, + } => { + self.handle_focus_observation_effect(view_id, subscription_id, callback) + } Effect::ResizeWindow { window_id } => { if let Some(window) = self.cx.windows.get_mut(&window_id) { window @@ -1978,6 +2015,30 @@ impl MutableAppContext { } } + fn handle_focus_observation_effect( + &mut self, + view_id: usize, + subscription_id: usize, + callback: FocusObservationCallback, + ) { + match self + .focus_observations + .lock() + .entry(view_id) + .or_default() + .entry(subscription_id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + // Observation was dropped before effect was processed + btree_map::Entry::Occupied(entry) => { + debug_assert!(entry.get().is_none()); + entry.remove(); + } + } + } + fn handle_model_notification_effect(&mut self, observed_id: usize) { let callbacks = self.observations.lock().remove(&observed_id); if let Some(callbacks) = callbacks { @@ -2102,8 +2163,6 @@ impl MutableAppContext { return; } - log::info!("process focus effect {:?}", focused_id); - self.update(|this| { let blurred_id = this.cx.windows.get_mut(&window_id).and_then(|window| { let blurred_id = window.focused_view_id; @@ -2122,6 +2181,31 @@ impl MutableAppContext { if let Some(mut focused_view) = this.cx.views.remove(&(window_id, focused_id)) { focused_view.on_focus(this, window_id, focused_id); this.cx.views.insert((window_id, focused_id), focused_view); + + let callbacks = this.focus_observations.lock().remove(&focused_id); + if let Some(callbacks) = callbacks { + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(this); + if alive { + match this + .focus_observations + .lock() + .entry(focused_id) + .or_default() + .entry(id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } + } + } } } }) @@ -2487,6 +2571,11 @@ pub enum Effect { window_id: usize, view_id: Option, }, + FocusObservation { + view_id: usize, + subscription_id: usize, + callback: FocusObservationCallback, + }, ResizeWindow { window_id: usize, }, @@ -2558,6 +2647,15 @@ impl Debug for Effect { .field("window_id", window_id) .field("view_id", view_id) .finish(), + Effect::FocusObservation { + view_id, + subscription_id, + .. + } => f + .debug_struct("Effect::FocusObservation") + .field("view_id", view_id) + .field("subscription_id", subscription_id) + .finish(), Effect::ResizeWindow { window_id } => f .debug_struct("Effect::RefreshWindow") .field("window_id", window_id) @@ -3045,6 +3143,24 @@ impl<'a, T: View> ViewContext<'a, T> { }) } + pub fn observe_focus(&mut self, handle: &ViewHandle, mut callback: F) -> Subscription + where + F: 'static + FnMut(&mut T, ViewHandle, &mut ViewContext), + V: View, + { + let observer = self.weak_handle(); + self.app.observe_focus(handle, move |observed, cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, observed, cx); + }); + true + } else { + false + } + }) + } + pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription where E: Entity, @@ -4309,6 +4425,12 @@ pub enum Subscription { Weak>>>>, >, }, + FocusObservation { + id: usize, + view_id: usize, + observations: + Option>>>>>, + }, ReleaseObservation { id: usize, entity_id: usize, @@ -4335,6 +4457,9 @@ impl Subscription { Subscription::ReleaseObservation { observations, .. } => { observations.take(); } + Subscription::FocusObservation { observations, .. } => { + observations.take(); + } } } } @@ -4427,6 +4552,22 @@ impl Drop for Subscription { } } } + Subscription::FocusObservation { + id, + view_id, + observations, + } => { + if let Some(observations) = observations.as_ref().and_then(Weak::upgrade) { + match observations.lock().entry(*view_id).or_default().entry(*id) { + btree_map::Entry::Vacant(entry) => { + entry.insert(None); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } } } } @@ -5715,23 +5856,56 @@ mod tests { } } - let events: Arc>> = Default::default(); + let view_events: Arc>> = Default::default(); let (window_id, view_1) = cx.add_window(Default::default(), |_| View { - events: events.clone(), + events: view_events.clone(), name: "view 1".to_string(), }); let view_2 = cx.add_view(window_id, |_| View { - events: events.clone(), + events: view_events.clone(), name: "view 2".to_string(), }); - view_1.update(cx, |_, cx| cx.focus(&view_2)); + let observed_events: Arc>> = Default::default(); + view_1.update(cx, |_, cx| { + cx.observe_focus(&view_2, { + let observed_events = observed_events.clone(); + move |this, view, cx| { + observed_events.lock().push(format!( + "{} observed {} focus", + this.name, + view.read(cx).name + )) + } + }) + .detach(); + }); + view_2.update(cx, |_, cx| { + cx.observe_focus(&view_1, { + let observed_events = observed_events.clone(); + move |this, view, cx| { + observed_events.lock().push(format!( + "{} observed {}'s focus", + this.name, + view.read(cx).name + )) + } + }) + .detach(); + }); + + view_1.update(cx, |_, cx| { + // Ensure only the latest focus is honored. + cx.focus(&view_2); + cx.focus(&view_1); + cx.focus(&view_2); + }); view_1.update(cx, |_, cx| cx.focus(&view_1)); view_1.update(cx, |_, cx| cx.focus(&view_2)); view_1.update(cx, |_, _| drop(view_2)); assert_eq!( - *events.lock(), + *view_events.lock(), [ "view 1 focused".to_string(), "view 1 blurred".to_string(), @@ -5743,6 +5917,14 @@ mod tests { "view 1 focused".to_string(), ], ); + assert_eq!( + *observed_events.lock(), + [ + "view 1 observed view 2's focus".to_string(), + "view 2 observed view 1's focus".to_string(), + "view 1 observed view 2's focus".to_string(), + ] + ); } #[crate::test(self)]