diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 505f609f57..fd447e2469 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -811,7 +811,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 FocusObservationCallback = Box bool>; type GlobalObservationCallback = Box; type ReleaseObservationCallback = Box; type ActionObservationCallback = Box; @@ -1305,7 +1305,7 @@ impl MutableAppContext { fn observe_focus(&mut self, handle: &ViewHandle, mut callback: F) -> Subscription where - F: 'static + FnMut(ViewHandle, &mut MutableAppContext) -> bool, + F: 'static + FnMut(ViewHandle, bool, &mut MutableAppContext) -> bool, V: View, { let subscription_id = post_inc(&mut self.next_subscription_id); @@ -1314,9 +1314,9 @@ impl MutableAppContext { self.pending_effects.push_back(Effect::FocusObservation { view_id, subscription_id, - callback: Box::new(move |cx| { + callback: Box::new(move |focused, cx| { if let Some(observed) = observed.upgrade(cx) { - callback(observed, cx) + callback(observed, focused, cx) } else { false } @@ -2525,6 +2525,31 @@ impl MutableAppContext { if let Some(mut blurred_view) = this.cx.views.remove(&(window_id, blurred_id)) { blurred_view.on_blur(this, window_id, blurred_id); this.cx.views.insert((window_id, blurred_id), blurred_view); + + let callbacks = this.focus_observations.lock().remove(&blurred_id); + if let Some(callbacks) = callbacks { + for (id, callback) in callbacks { + if let Some(mut callback) = callback { + let alive = callback(false, this); + if alive { + match this + .focus_observations + .lock() + .entry(blurred_id) + .or_default() + .entry(id) + { + btree_map::Entry::Vacant(entry) => { + entry.insert(Some(callback)); + } + btree_map::Entry::Occupied(entry) => { + entry.remove(); + } + } + } + } + } + } } } @@ -2537,7 +2562,7 @@ impl MutableAppContext { if let Some(callbacks) = callbacks { for (id, callback) in callbacks { if let Some(mut callback) = callback { - let alive = callback(this); + let alive = callback(true, this); if alive { match this .focus_observations @@ -3598,20 +3623,21 @@ 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), + F: 'static + FnMut(&mut T, ViewHandle, bool, &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 - } - }) + self.app + .observe_focus(handle, move |observed, focused, cx| { + if let Some(observer) = observer.upgrade(cx) { + observer.update(cx, |observer, cx| { + callback(observer, observed, focused, cx); + }); + true + } else { + false + } + }) } pub fn observe_release(&mut self, handle: &H, mut callback: F) -> Subscription @@ -6448,11 +6474,13 @@ mod tests { view_1.update(cx, |_, cx| { cx.observe_focus(&view_2, { let observed_events = observed_events.clone(); - move |this, view, cx| { + move |this, view, focused, cx| { + let label = if focused { "focus" } else { "blur" }; observed_events.lock().push(format!( - "{} observed {}'s focus", + "{} observed {}'s {}", this.name, - view.read(cx).name + view.read(cx).name, + label )) } }) @@ -6461,16 +6489,20 @@ mod tests { view_2.update(cx, |_, cx| { cx.observe_focus(&view_1, { let observed_events = observed_events.clone(); - move |this, view, cx| { + move |this, view, focused, cx| { + let label = if focused { "focus" } else { "blur" }; observed_events.lock().push(format!( - "{} observed {}'s focus", + "{} observed {}'s {}", this.name, - view.read(cx).name + view.read(cx).name, + label )) } }) .detach(); }); + assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]); + assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new()); view_1.update(cx, |_, cx| { // Ensure only the latest focus is honored. @@ -6478,31 +6510,47 @@ mod tests { 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!( - *view_events.lock(), - [ - "view 1 focused".to_string(), - "view 1 blurred".to_string(), - "view 2 focused".to_string(), - "view 2 blurred".to_string(), - "view 1 focused".to_string(), - "view 1 blurred".to_string(), - "view 2 focused".to_string(), - "view 1 focused".to_string(), - ], + mem::take(&mut *view_events.lock()), + ["view 1 blurred", "view 2 focused"], ); assert_eq!( - *observed_events.lock(), + mem::take(&mut *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(), + "view 2 observed view 1's blur", + "view 1 observed view 2's focus" ] ); + + view_1.update(cx, |_, cx| cx.focus(&view_1)); + assert_eq!( + mem::take(&mut *view_events.lock()), + ["view 2 blurred", "view 1 focused"], + ); + assert_eq!( + mem::take(&mut *observed_events.lock()), + [ + "view 1 observed view 2's blur", + "view 2 observed view 1's focus" + ] + ); + + view_1.update(cx, |_, cx| cx.focus(&view_2)); + assert_eq!( + mem::take(&mut *view_events.lock()), + ["view 1 blurred", "view 2 focused"], + ); + assert_eq!( + mem::take(&mut *observed_events.lock()), + [ + "view 2 observed view 1's blur", + "view 1 observed view 2's focus" + ] + ); + + view_1.update(cx, |_, _| drop(view_2)); + assert_eq!(mem::take(&mut *view_events.lock()), ["view 1 focused"]); + assert_eq!(mem::take(&mut *observed_events.lock()), Vec::<&str>::new()); } #[crate::test(self)] diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index bf862f0d9d..2aa8993285 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -365,8 +365,10 @@ impl ProjectSearchView { cx.emit(ViewEvent::EditorEvent(event.clone())) }) .detach(); - cx.observe_focus(&query_editor, |this, _, _| { - this.results_editor_was_focused = false; + cx.observe_focus(&query_editor, |this, _, focused, _| { + if focused { + this.results_editor_was_focused = false; + } }) .detach(); @@ -377,8 +379,10 @@ impl ProjectSearchView { }); cx.observe(&results_editor, |_, _, cx| cx.emit(ViewEvent::UpdateTab)) .detach(); - cx.observe_focus(&results_editor, |this, _, _| { - this.results_editor_was_focused = true; + cx.observe_focus(&results_editor, |this, _, focused, _| { + if focused { + this.results_editor_was_focused = true; + } }) .detach(); cx.subscribe(&results_editor, |this, _, event, cx| {