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
This commit is contained in:
Nathan Sobo 2024-12-17 14:41:00 -07:00 committed by GitHub
parent e1ca5ed836
commit 81c118d67d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 43 additions and 40 deletions

View file

@ -5,7 +5,10 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
path::{Path, PathBuf}, path::{Path, PathBuf},
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::{atomic::Ordering::SeqCst, Arc}, sync::{
atomic::{AtomicUsize, Ordering::SeqCst},
Arc,
},
time::Duration, time::Duration,
}; };
@ -16,6 +19,7 @@ use futures::{
future::{LocalBoxFuture, Shared}, future::{LocalBoxFuture, Shared},
Future, FutureExt, Future, FutureExt,
}; };
use parking_lot::RwLock;
use slotmap::SlotMap; use slotmap::SlotMap;
pub use async_context::*; pub use async_context::*;
@ -30,11 +34,12 @@ use util::ResultExt;
use crate::{ use crate::{
current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, current_platform, hash, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle,
Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId, Asset, AssetSource, BackgroundExecutor, ClipboardItem, Context, DispatchPhase, DisplayId,
Entity, EventEmitter, ForegroundExecutor, Global, KeyBinding, Keymap, Keystroke, LayoutId, Entity, EventEmitter, FocusHandle, FocusId, ForegroundExecutor, Global, KeyBinding, Keymap,
Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Keystroke, LayoutId, Menu, MenuItem, OwnedMenu, PathPromptOptions, Pixels, Platform,
PromptBuilder, PromptHandle, PromptLevel, Render, RenderablePromptHandle, Reservation, PlatformDisplay, Point, PromptBuilder, PromptHandle, PromptLevel, Render,
ScreenCaptureSource, SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextSystem, RenderablePromptHandle, Reservation, ScreenCaptureSource, SharedString, SubscriberSet,
View, ViewContext, Window, WindowAppearance, WindowContext, WindowHandle, WindowId, Subscription, SvgRenderer, Task, TextSystem, View, ViewContext, Window, WindowAppearance,
WindowContext, WindowHandle, WindowId,
}; };
mod async_context; mod async_context;
@ -242,6 +247,7 @@ pub struct AppContext {
pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>, pub(crate) new_view_observers: SubscriberSet<TypeId, NewViewListener>,
pub(crate) windows: SlotMap<WindowId, Option<Window>>, pub(crate) windows: SlotMap<WindowId, Option<Window>>,
pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>, pub(crate) window_handles: FxHashMap<WindowId, AnyWindowHandle>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
pub(crate) keymap: Rc<RefCell<Keymap>>, pub(crate) keymap: Rc<RefCell<Keymap>>,
pub(crate) keyboard_layout: SharedString, pub(crate) keyboard_layout: SharedString,
pub(crate) global_action_listeners: pub(crate) global_action_listeners:
@ -302,8 +308,9 @@ impl AppContext {
entities, entities,
new_view_observers: SubscriberSet::new(), new_view_observers: SubscriberSet::new(),
new_model_observers: SubscriberSet::new(), new_model_observers: SubscriberSet::new(),
window_handles: FxHashMap::default(),
windows: SlotMap::with_key(), windows: SlotMap::with_key(),
window_handles: FxHashMap::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
keymap: Rc::new(RefCell::new(Keymap::default())), keymap: Rc::new(RefCell::new(Keymap::default())),
keyboard_layout, keyboard_layout,
global_action_listeners: FxHashMap::default(), global_action_listeners: FxHashMap::default(),
@ -439,6 +446,7 @@ impl AppContext {
self.defer(move |_| activate()); self.defer(move |_| activate());
subscription subscription
} }
pub(crate) fn observe_internal<W, E>( pub(crate) fn observe_internal<W, E>(
&mut self, &mut self,
entity: &E, 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. /// Instructs the platform to activate the application by bringing it to the foreground.
pub fn activate(&self, ignoring_other_apps: bool) { pub fn activate(&self, ignoring_other_apps: bool) {
self.platform.activate(ignoring_other_apps); self.platform.activate(ignoring_other_apps);
@ -844,28 +858,25 @@ impl AppContext {
/// Repeatedly called during `flush_effects` to handle a focused handle being dropped. /// Repeatedly called during `flush_effects` to handle a focused handle being dropped.
fn release_dropped_focus_handles(&mut self) { fn release_dropped_focus_handles(&mut self) {
for window_handle in self.windows() { self.focus_handles
window_handle .clone()
.update(self, |_, cx| { .write()
let mut blur_window = false; .retain(|handle_id, count| {
let focus = cx.window.focus; if count.load(SeqCst) == 0 {
cx.window.focus_handles.write().retain(|handle_id, count| { for window_handle in self.windows() {
if count.load(SeqCst) == 0 { window_handle
if focus == Some(handle_id) { .update(self, |_, cx| {
blur_window = true; if cx.window.focus == Some(handle_id) {
} cx.blur();
false }
} else { })
true .unwrap();
}
});
if blur_window {
cx.blur();
} }
}) false
.unwrap(); } else {
} true
}
});
} }
fn apply_notify_effect(&mut self, emitter: EntityId) { fn apply_notify_effect(&mut self, emitter: EntityId) {

View file

@ -500,7 +500,7 @@ impl AnyElement {
if !focus_assigned { if !focus_assigned {
if let Some(focus_id) = cx.window.next_frame.focus { 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);
} }
} }

View file

@ -531,7 +531,6 @@ pub struct Window {
pub(crate) tooltip_bounds: Option<TooltipBounds>, pub(crate) tooltip_bounds: Option<TooltipBounds>,
next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>, next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>>,
pub(crate) dirty_views: FxHashSet<EntityId>, pub(crate) dirty_views: FxHashSet<EntityId>,
pub(crate) focus_handles: Arc<RwLock<SlotMap<FocusId, AtomicUsize>>>,
focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
focus_lost_listeners: SubscriberSet<(), AnyObserver>, focus_lost_listeners: SubscriberSet<(), AnyObserver>,
default_prevented: bool, default_prevented: bool,
@ -809,7 +808,6 @@ impl Window {
next_tooltip_id: TooltipId::default(), next_tooltip_id: TooltipId::default(),
tooltip_bounds: None, tooltip_bounds: None,
dirty_views: FxHashSet::default(), dirty_views: FxHashSet::default(),
focus_handles: Arc::new(RwLock::new(SlotMap::with_key())),
focus_listeners: SubscriberSet::new(), focus_listeners: SubscriberSet::new(),
focus_lost_listeners: SubscriberSet::new(), focus_lost_listeners: SubscriberSet::new(),
default_prevented: true, default_prevented: true,
@ -931,17 +929,11 @@ impl<'a> WindowContext<'a> {
self.window.removed = true; 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`. /// Obtain the currently focused [`FocusHandle`]. If no elements are focused, returns `None`.
pub fn focused(&self) -> Option<FocusHandle> { pub fn focused(&self) -> Option<FocusHandle> {
self.window self.window
.focus .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`]. /// Move focus to the element associated with the given [`FocusHandle`].
@ -3021,7 +3013,7 @@ impl<'a> WindowContext<'a> {
let event = FocusOutEvent { let event = FocusOutEvent {
blurred: WeakFocusHandle { blurred: WeakFocusHandle {
id: blurred_id, id: blurred_id,
handles: Arc::downgrade(&cx.window.focus_handles), handles: Arc::downgrade(&cx.app.focus_handles),
}, },
}; };
listener(event, cx) listener(event, cx)
@ -4439,7 +4431,7 @@ impl<'a, V: 'static> ViewContext<'a, V> {
let event = FocusOutEvent { let event = FocusOutEvent {
blurred: WeakFocusHandle { blurred: WeakFocusHandle {
id: blurred_id, id: blurred_id,
handles: Arc::downgrade(&cx.window.focus_handles), handles: Arc::downgrade(&cx.app.focus_handles),
}, },
}; };
listener(view, event, cx) listener(view, event, cx)