From 81c118d67dcde34daf4ddaaa38ebe6cb1686664e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Dec 2024 14:41:00 -0700 Subject: [PATCH] Store focus handles in AppContext instead of Window (#22158) Previously, each window stored its own collection of focus handles. This meant that to create a focus handle, you needed to have access to a Window. I'm working on a simplification to gpui's context types that removes `WindowContext` and `ViewContext` in favor of passing a window reference explicitly when rendering or handling events. You'll still need a window to manipulate focus, but it will be helpful to be able to create focus handles without a window. cc @mgsloan Release Notes: - N/A --- crates/gpui/src/app.rs | 67 ++++++++++++++++++++++---------------- crates/gpui/src/element.rs | 2 +- crates/gpui/src/window.rs | 14 ++------ 3 files changed, 43 insertions(+), 40 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ca787587b917dcb6d44849e2c51b6c58e3c7a2af..33cabc000f47acd208414b0c348da3312597649d 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5,7 +5,10 @@ use std::{ ops::{Deref, DerefMut}, path::{Path, PathBuf}, rc::{Rc, Weak}, - sync::{atomic::Ordering::SeqCst, Arc}, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, time::Duration, }; @@ -16,6 +19,7 @@ use futures::{ future::{LocalBoxFuture, Shared}, Future, FutureExt, }; +use parking_lot::RwLock; use slotmap::SlotMap; pub use async_context::*; @@ -30,11 +34,12 @@ use util::ResultExt; use crate::{ current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, - Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, - Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, - PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, - ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, - View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, WindowId, + Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding, Keymap, + Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, + PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render, + RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet, + Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance, + WindowContext, WindowHandle, WindowId, }; mod async_context; @@ -242,6 +247,7 @@ pub struct AppContext { pub(crate) new_view_observers: SubscriberSet, pub(crate) windows: SlotMap>, pub(crate) window_handles: FxHashMap, + pub(crate) focus_handles: Arc>>, pub(crate) keymap: Rc>, pub(crate) keyboard_layout: SharedString, pub(crate) global_action_listeners: @@ -302,8 +308,9 @@ impl AppContext { entities, new_view_observers: SubscriberSet::new(), new_model_observers: SubscriberSet::new(), - window_handles: FxHashMap::default(), windows: SlotMap::with_key(), + window_handles: FxHashMap::default(), + focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), keymap: Rc::new(RefCell::new(Keymap::default())), keyboard_layout, global_action_listeners: FxHashMap::default(), @@ -439,6 +446,7 @@ impl AppContext { self.defer(move |_| activate()); subscription } + pub(crate) fn observe_internal( &mut self, entity: &E, @@ -569,6 +577,12 @@ impl AppContext { }) } + /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus + /// for elements rendered within this window. + pub fn focus_handle(&self) -> FocusHandle { + FocusHandle::new(&self.focus_handles) + } + /// Instructs the platform to activate the application by bringing it to the foreground. pub fn activate(&self, ignoring_other_apps: bool) { self.platform.activate(ignoring_other_apps); @@ -844,28 +858,25 @@ impl AppContext { /// Repeatedly called during `flush_effects` to handle a focused handle being dropped. fn release_dropped_focus_handles(&mut self) { - for window_handle in self.windows() { - window_handle - .update(self, |_, cx| { - let mut blur_window = false; - let focus = cx.window.focus; - cx.window.focus_handles.write().retain(|handle_id, count| { - if count.load(SeqCst) == 0 { - if focus == Some(handle_id) { - blur_window = true; - } - false - } else { - true - } - }); - - if blur_window { - cx.blur(); + self.focus_handles + .clone() + .write() + .retain(|handle_id, count| { + if count.load(SeqCst) == 0 { + for window_handle in self.windows() { + window_handle + .update(self, |_, cx| { + if cx.window.focus == Some(handle_id) { + cx.blur(); + } + }) + .unwrap(); } - }) - .unwrap(); - } + false + } else { + true + } + }); } fn apply_notify_effect(&mut self, emitter: EntityId) { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index f0c5119033460eeb4ae941fdc3e7d741a18c7eba..38c4c83904633a8767db26a97bb79c90906d821e 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -500,7 +500,7 @@ impl AnyElement { if !focus_assigned { if let Some(focus_id) = cx.window.next_frame.focus { - return FocusHandle::for_id(focus_id, &cx.window.focus_handles); + return FocusHandle::for_id(focus_id, &cx.focus_handles); } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index d3c53cd5e7bb733559510a5a0a9f422cf2ef3a1a..4775022431111b10dad5b8cb20e016b87ce0d172 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -531,7 +531,6 @@ pub struct Window { pub(crate) tooltip_bounds: Option, next_frame_callbacks: Rc>>, pub(crate) dirty_views: FxHashSet, - pub(crate) focus_handles: Arc>>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, focus_lost_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, @@ -809,7 +808,6 @@ impl Window { next_tooltip_id: TooltipId::default(), tooltip_bounds: None, dirty_views: FxHashSet::default(), - focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), focus_listeners: SubscriberSet::new(), focus_lost_listeners: SubscriberSet::new(), default_prevented: true, @@ -931,17 +929,11 @@ impl<'a> WindowContext<'a> { self.window.removed = true; } - /// Obtain a new [`FocusHandle`], which allows you to track and manipulate the keyboard focus - /// for elements rendered within this window. - pub fn focus_handle(&self) -> FocusHandle { - FocusHandle::new(&self.window.focus_handles) - } - /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`. pub fn focused(&self) -> Option { self.window .focus - .and_then(|id| FocusHandle::for_id(id, &self.window.focus_handles)) + .and_then(|id| FocusHandle::for_id(id, &self.app.focus_handles)) } /// Move focus to the element associated with the given [`FocusHandle`]. @@ -3021,7 +3013,7 @@ impl<'a> WindowContext<'a> { let event = FocusOutEvent { blurred: WeakFocusHandle { id: blurred_id, - handles: Arc::downgrade(&cx.window.focus_handles), + handles: Arc::downgrade(&cx.app.focus_handles), }, }; listener(event, cx) @@ -4439,7 +4431,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { let event = FocusOutEvent { blurred: WeakFocusHandle { id: blurred_id, - handles: Arc::downgrade(&cx.window.focus_handles), + handles: Arc::downgrade(&cx.app.focus_handles), }, }; listener(view, event, cx)