From 4d7cf445f80307052918ae80fdd339cd4e2cfaf8 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 17 Dec 2024 13:01:01 -0700 Subject: [PATCH] Store focus handles in app This commit moves the responsibility of storing focus handles from individual windows to the app level. This will allow new focus handles to be created without the need to reference the window, which paves the way for a simplification of gpui's context types I'm working on. --- crates/gpui/src/app.rs | 55 ++++++++++++++++++++------------------ crates/gpui/src/element.rs | 2 +- crates/gpui/src/window.rs | 10 +++---- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index ca787587b917dc..426dcade384beb 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,9 +34,9 @@ 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, + Entity, EventEmitter, 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, }; @@ -242,6 +246,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 +307,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(), @@ -844,28 +850,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 f0c5119033460e..38c4c83904633a 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 5b88870e7c7a0b..f100e27a5c29ab 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, @@ -934,14 +932,14 @@ impl<'a> WindowContext<'a> { /// 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) + FocusHandle::new(&self.app.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`]. @@ -3023,7 +3021,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) @@ -4441,7 +4439,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)