diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index bfbdc6b4a69421605d0ced7c54280fe84e890293..8748a6024906307c9f8b0d581a390c8e9a3c179d 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -17,11 +17,10 @@ use time::UtcOffset; use crate::{ current_platform, image_cache::ImageCache, init_app_menus, Action, ActionRegistry, Any, AnyView, AnyWindowHandle, AppMetadata, AssetSource, BackgroundExecutor, ClipboardItem, Context, - DispatchPhase, DisplayId, Entity, EventEmitter, FocusEvent, FocusHandle, FocusId, - ForegroundExecutor, KeyBinding, Keymap, Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, - Platform, PlatformDisplay, Point, Render, SharedString, SubscriberSet, Subscription, - SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, ViewContext, Window, - WindowContext, WindowHandle, WindowId, + DispatchPhase, DisplayId, Entity, EventEmitter, ForegroundExecutor, KeyBinding, Keymap, + Keystroke, LayoutId, Menu, PathPromptOptions, Pixels, Platform, PlatformDisplay, Point, Render, + SharedString, SubscriberSet, Subscription, SvgRenderer, Task, TextStyle, TextStyleRefinement, + TextSystem, View, ViewContext, Window, WindowContext, WindowHandle, WindowId, }; use anyhow::{anyhow, Result}; use collections::{FxHashMap, FxHashSet, VecDeque}; @@ -572,28 +571,27 @@ impl AppContext { loop { self.release_dropped_entities(); self.release_dropped_focus_handles(); + if let Some(effect) = self.pending_effects.pop_front() { match effect { Effect::Notify { emitter } => { self.apply_notify_effect(emitter); } + Effect::Emit { emitter, event_type, event, } => self.apply_emit_effect(emitter, event_type, event), - Effect::FocusChanged { - window_handle, - focused, - } => { - self.apply_focus_changed_effect(window_handle, focused); - } + Effect::Refresh => { self.apply_refresh_effect(); } + Effect::NotifyGlobalObservers { global_type } => { self.apply_notify_global_observers_effect(global_type); } + Effect::Defer { callback } => { self.apply_defer_effect(callback); } @@ -613,7 +611,7 @@ impl AppContext { .values() .filter_map(|window| { let window = window.as_ref()?; - window.dirty.then_some(window.handle) + (window.dirty || window.focus_invalidated).then_some(window.handle) }) .collect::>() { @@ -693,51 +691,6 @@ impl AppContext { }); } - fn apply_focus_changed_effect( - &mut self, - window_handle: AnyWindowHandle, - focused: Option, - ) { - window_handle - .update(self, |_, cx| { - // The window might change focus multiple times in an effect cycle. - // We only honor effects for the most recently focused handle. - if cx.window.focus == focused { - // if someone calls focus multiple times in one frame with the same handle - // the first apply_focus_changed_effect will have taken the last blur already - // and run the rest of this, so we can return. - let Some(last_blur) = cx.window.last_blur.take() else { - return; - }; - - let focused = focused - .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); - - let blurred = - last_blur.and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - - let focus_changed = focused.is_some() || blurred.is_some(); - let event = FocusEvent { focused, blurred }; - - let mut listeners = mem::take(&mut cx.window.rendered_frame.focus_listeners); - if focus_changed { - for listener in &mut listeners { - listener(&event, cx); - } - } - cx.window.rendered_frame.focus_listeners = listeners; - - if focus_changed { - cx.window - .focus_listeners - .clone() - .retain(&(), |listener| listener(&event, cx)); - } - } - }) - .ok(); - } - fn apply_refresh_effect(&mut self) { for window in self.windows.values_mut() { if let Some(window) = window.as_mut() { @@ -1252,10 +1205,6 @@ pub(crate) enum Effect { event_type: TypeId, event: Box, }, - FocusChanged { - window_handle: AnyWindowHandle, - focused: Option, - }, Refresh, NotifyGlobalObservers { global_type: TypeId, diff --git a/crates/gpui2/src/elements/div.rs b/crates/gpui2/src/elements/div.rs index 0a8717ca2fdad11e1bdc636ebec8925c953c2412..1f56f44900477780085b556f46adc508f4ff119a 100644 --- a/crates/gpui2/src/elements/div.rs +++ b/crates/gpui2/src/elements/div.rs @@ -1,10 +1,9 @@ use crate::{ point, px, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, AppContext, BorrowAppContext, - BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusEvent, FocusHandle, - IntoElement, KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, - MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, - SharedString, Size, StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, - WindowContext, + BorrowWindow, Bounds, ClickEvent, DispatchPhase, Element, ElementId, FocusHandle, IntoElement, + KeyContext, KeyDownEvent, KeyUpEvent, LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, + MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, + StackingOrder, Style, StyleRefinement, Styled, Task, View, Visibility, WindowContext, }; use collections::HashMap; @@ -636,10 +635,6 @@ pub trait FocusableElement: InteractiveElement { } } -pub type FocusListeners = Vec; - -pub type FocusListener = Box; - pub type MouseDownListener = Box; pub type MouseUpListener = diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 1c6955111cd2f31870ff92fb2dfeff11db4b02ef..58ee849aad4a6d665c6ec14176f72eb457c9a82e 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -1,6 +1,5 @@ use crate::{ - div, point, Div, Element, FocusHandle, IntoElement, Keystroke, Modifiers, Pixels, Point, - Render, ViewContext, + div, point, Div, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; @@ -290,11 +289,6 @@ impl InputEvent { } } -pub struct FocusEvent { - pub blurred: Option, - pub focused: Option, -} - #[cfg(test)] mod test { use crate::{ diff --git a/crates/gpui2/src/key_dispatch.rs b/crates/gpui2/src/key_dispatch.rs index a44987aa39e65ecbabacbca40868fc27d61a3b49..ddb1f1e6ca8b4fd4db5d9c317144ca302519aa87 100644 --- a/crates/gpui2/src/key_dispatch.rs +++ b/crates/gpui2/src/key_dispatch.rs @@ -29,6 +29,7 @@ pub(crate) struct DispatchNode { pub key_listeners: Vec, pub action_listeners: Vec, pub context: Option, + focus_id: Option, parent: Option, } @@ -127,8 +128,9 @@ impl DispatchTree { } pub fn make_focusable(&mut self, focus_id: FocusId) { - self.focusable_node_ids - .insert(focus_id, self.active_node_id()); + let node_id = self.active_node_id(); + self.active_node().focus_id = Some(focus_id); + self.focusable_node_ids.insert(focus_id, node_id); } pub fn focus_contains(&self, parent: FocusId, child: FocusId) -> bool { @@ -247,6 +249,20 @@ impl DispatchTree { dispatch_path } + pub fn focus_path(&self, focus_id: FocusId) -> SmallVec<[FocusId; 8]> { + let mut focus_path: SmallVec<[FocusId; 8]> = SmallVec::new(); + let mut current_node_id = self.focusable_node_ids.get(&focus_id).copied(); + while let Some(node_id) = current_node_id { + let node = self.node(node_id); + if let Some(focus_id) = node.focus_id { + focus_path.push(focus_id); + } + current_node_id = node.parent; + } + focus_path.reverse(); // Reverse the path so it goes from the root to the focused node. + focus_path + } + pub fn node(&self, node_id: DispatchNodeId) -> &DispatchNode { &self.nodes[node_id.0] } diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index daa43804b00d1c8a240528b875ea9f7609a84e32..1c0849785b69b3553bed6fff451c47ecec7c2570 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2,14 +2,14 @@ use crate::{ key_dispatch::DispatchActionListener, px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, - EntityId, EventEmitter, FileDropEvent, Flatten, FocusEvent, FontId, GlobalElementId, GlyphId, - Hsla, ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, - LayoutId, Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, - MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, - PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, - RenderImageParams, RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, - Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, - UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, + ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, + Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, + Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, + RenderSvgParams, ScaledPixels, Scene, SceneBuilder, Shadow, SharedString, Size, Style, + SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, + VisualContext, WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashMap; @@ -86,9 +86,13 @@ impl DispatchPhase { type AnyObserver = Box bool + 'static>; type AnyMouseListener = Box; -type AnyFocusListener = Box; type AnyWindowFocusListener = Box bool + 'static>; +struct FocusEvent { + previous_focus_path: SmallVec<[FocusId; 8]>, + current_focus_path: SmallVec<[FocusId; 8]>, +} + slotmap::new_key_type! { pub struct FocusId; } impl FocusId { @@ -239,8 +243,8 @@ pub struct Window { pub(crate) rendered_frame: Frame, pub(crate) next_frame: Frame, pub(crate) focus_handles: Arc>>, - pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, - pub(crate) blur_listeners: SubscriberSet<(), AnyObserver>, + focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, + blur_listeners: SubscriberSet<(), AnyObserver>, default_prevented: bool, mouse_position: Point, modifiers: Modifiers, @@ -252,8 +256,10 @@ pub struct Window { pub(crate) dirty: bool, pub(crate) drawing: bool, activation_observers: SubscriberSet<(), AnyObserver>, - pub(crate) last_blur: Option>, pub(crate) focus: Option, + + #[cfg(any(test, feature = "test-support"))] + pub(crate) focus_invalidated: bool, } pub(crate) struct ElementStateBox { @@ -264,10 +270,10 @@ pub(crate) struct ElementStateBox { // #[derive(Default)] pub(crate) struct Frame { + focus: Option, pub(crate) element_states: FxHashMap, mouse_listeners: FxHashMap>, pub(crate) dispatch_tree: DispatchTree, - pub(crate) focus_listeners: Vec, pub(crate) scene_builder: SceneBuilder, pub(crate) depth_map: Vec<(StackingOrder, Bounds)>, pub(crate) z_index_stack: StackingOrder, @@ -278,10 +284,10 @@ pub(crate) struct Frame { impl Frame { fn new(dispatch_tree: DispatchTree) -> Self { Frame { + focus: None, element_states: FxHashMap::default(), mouse_listeners: FxHashMap::default(), dispatch_tree, - focus_listeners: Vec::new(), scene_builder: SceneBuilder::default(), z_index_stack: StackingOrder::default(), depth_map: Default::default(), @@ -293,10 +299,15 @@ impl Frame { fn clear(&mut self) { self.element_states.clear(); self.mouse_listeners.values_mut().for_each(Vec::clear); - self.focus_listeners.clear(); self.dispatch_tree.clear(); self.depth_map.clear(); } + + fn focus_path(&self) -> SmallVec<[FocusId; 8]> { + self.focus + .map(|focus_id| self.dispatch_tree.focus_path(focus_id)) + .unwrap_or_default() + } } impl Window { @@ -389,8 +400,10 @@ impl Window { dirty: false, drawing: false, activation_observers: SubscriberSet::new(), - last_blur: None, focus: None, + + #[cfg(any(test, feature = "test-support"))] + focus_invalidated: false, } } } @@ -468,35 +481,23 @@ impl<'a> WindowContext<'a> { return; } - let focus_id = handle.id; - - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - - self.window.focus = Some(focus_id); + self.window.focus = Some(handle.id); self.window .rendered_frame .dispatch_tree .clear_pending_keystrokes(); - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: Some(focus_id), - }); + + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = true; + } + self.notify(); } /// Remove focus from all elements within this context's window. pub fn blur(&mut self) { - if self.window.last_blur.is_none() { - self.window.last_blur = Some(self.window.focus); - } - self.window.focus = None; - self.app.push_effect(Effect::FocusChanged { - window_handle: self.window.handle, - focused: None, - }); self.notify(); } @@ -1258,16 +1259,11 @@ impl<'a> WindowContext<'a> { self.window.dirty = false; self.window.drawing = true; - let window_was_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .rendered_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); + #[cfg(any(test, feature = "test-support"))] + { + self.window.focus_invalidated = false; + } + self.text_system().start_frame(); self.window.platform_window.clear_input_handler(); self.window.layout_engine.as_mut().unwrap().clear(); @@ -1306,23 +1302,6 @@ impl<'a> WindowContext<'a> { }); } - let window_is_focused = self - .window - .focus - .and_then(|focus_id| { - self.window - .next_frame - .dispatch_tree - .focusable_node_id(focus_id) - }) - .is_some(); - if window_was_focused && !window_is_focused { - self.window - .blur_listeners - .clone() - .retain(&(), |listener| listener(self)); - } - self.window .next_frame .dispatch_tree @@ -1330,10 +1309,30 @@ impl<'a> WindowContext<'a> { &mut self.window.rendered_frame.dispatch_tree, self.window.focus, ); + self.window.next_frame.focus = self.window.focus; self.window.root_view = Some(root_view); - let window = &mut self.window; - mem::swap(&mut window.rendered_frame, &mut window.next_frame); + let previous_focus_path = self.window.rendered_frame.focus_path(); + mem::swap(&mut self.window.rendered_frame, &mut self.window.next_frame); + let current_focus_path = self.window.rendered_frame.focus_path(); + + if previous_focus_path != current_focus_path { + if !previous_focus_path.is_empty() && current_focus_path.is_empty() { + self.window + .blur_listeners + .clone() + .retain(&(), |listener| listener(self)); + } + + let event = FocusEvent { + previous_focus_path, + current_focus_path, + }; + self.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, self)); + } let scene = self.window.rendered_frame.scene_builder.build(); @@ -1741,22 +1740,6 @@ impl<'a> WindowContext<'a> { result } - /// Register a focus listener for the next frame only. It will be cleared - /// on the next frame render. You should use this method only from within elements, - /// and we may want to enforce that better via a different context type. - // todo!() Move this to `FrameContext` to emphasize its individuality? - pub fn on_focus_changed( - &mut self, - listener: impl Fn(&FocusEvent, &mut WindowContext) + 'static, - ) { - self.window - .next_frame - .focus_listeners - .push(Box::new(move |event, cx| { - listener(event, cx); - })); - } - /// Set an input handler, such as [ElementInputHandler], which interfaces with the /// platform to receive textual input with proper integration with concerns such /// as IME interactions. @@ -2433,7 +2416,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) { + if event.previous_focus_path.last() != Some(&focus_id) + && event.current_focus_path.last() == Some(&focus_id) + { listener(view, cx) } }) @@ -2458,10 +2443,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .focused - .as_ref() - .map_or(false, |focused| focus_id.contains(focused.id, cx)) + if !event.previous_focus_path.contains(&focus_id) + && event.current_focus_path.contains(&focus_id) { listener(view, cx) } @@ -2487,7 +2470,9 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event.blurred.as_ref().map(|blurred| blurred.id) == Some(focus_id) { + if event.previous_focus_path.last() == Some(&focus_id) + && event.current_focus_path.last() != Some(&focus_id) + { listener(view, cx) } }) @@ -2528,10 +2513,8 @@ impl<'a, V: 'static> ViewContext<'a, V> { (), Box::new(move |event, cx| { view.update(cx, |view, cx| { - if event - .blurred - .as_ref() - .map_or(false, |blurred| focus_id.contains(blurred.id, cx)) + if event.previous_focus_path.contains(&focus_id) + && !event.current_focus_path.contains(&focus_id) { listener(view, cx) } diff --git a/crates/project_panel2/src/project_panel.rs b/crates/project_panel2/src/project_panel.rs index 417b351df761afd6e094165e1b580524218a710d..2d18d2a6c769396d699ee3059e48f2736a85370a 100644 --- a/crates/project_panel2/src/project_panel.rs +++ b/crates/project_panel2/src/project_panel.rs @@ -1922,7 +1922,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); @@ -2275,7 +2280,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "root1", cx); @@ -2546,7 +2556,12 @@ mod tests { let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); let cx = &mut VisualTestContext::from_window(*workspace, cx); let panel = workspace - .update(cx, |workspace, cx| ProjectPanel::new(workspace, cx)) + .update(cx, |workspace, cx| { + let panel = ProjectPanel::new(workspace, cx); + workspace.add_panel(panel.clone(), cx); + workspace.toggle_dock(panel.read(cx).position(cx), cx); + panel + }) .unwrap(); select_path(&panel, "src/", cx); diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index ef7d7fe70ddae4ae1ea69f0696315fc19cb4a2c2..167c6fece282f9d7619780497d8c357f7af2eebf 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -277,13 +277,15 @@ pub enum ViewEvent { impl EventEmitter for ProjectSearchView {} impl Render for ProjectSearchView { - type Element = Div; + type Element = AnyElement; + fn render(&mut self, cx: &mut ViewContext) -> Self::Element { if self.has_matches() { div() .flex_1() .size_full() .child(self.results_editor.clone()) + .into_any() } else { let model = self.model.read(cx); let has_no_results = model.no_results.unwrap_or(false); @@ -356,21 +358,31 @@ impl Render for ProjectSearchView { .max_w_96() .child(Label::new(text).size(LabelSize::Small)) }); - v_stack().flex_1().size_full().justify_center().child( - h_stack() - .size_full() - .justify_center() - .child(h_stack().flex_1()) - .child(v_stack().child(major_text).children(minor_text)) - .child(h_stack().flex_1()), - ) + v_stack() + .track_focus(&self.query_editor.focus_handle(cx)) + .flex_1() + .size_full() + .justify_center() + .child( + h_stack() + .size_full() + .justify_center() + .child(h_stack().flex_1()) + .child(v_stack().child(major_text).children(minor_text)) + .child(h_stack().flex_1()), + ) + .into_any() } } } impl FocusableView for ProjectSearchView { fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle { - self.results_editor.focus_handle(cx) + if self.has_matches() { + self.results_editor.focus_handle(cx) + } else { + self.query_editor.focus_handle(cx) + } } } diff --git a/crates/workspace2/src/item.rs b/crates/workspace2/src/item.rs index 43597f21a79a0cc2b178a429fe28b4736a895c5f..4eadd4e7a3cbc08d33123c6f30f118bbf0c4479d 100644 --- a/crates/workspace2/src/item.rs +++ b/crates/workspace2/src/item.rs @@ -760,8 +760,9 @@ pub mod test { use super::{Item, ItemEvent}; use crate::{ItemId, ItemNavHistory, Pane, Workspace, WorkspaceId}; use gpui::{ - AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, FocusableView, - IntoElement, Model, Render, SharedString, Task, View, ViewContext, VisualContext, WeakView, + AnyElement, AppContext, Context as _, Div, EntityId, EventEmitter, Focusable, + FocusableView, InteractiveElement, IntoElement, Model, Render, SharedString, Task, View, + ViewContext, VisualContext, WeakView, }; use project::{Project, ProjectEntryId, ProjectPath, WorktreeId}; use std::{any::Any, cell::Cell, path::Path}; @@ -909,10 +910,10 @@ pub mod test { } impl Render for TestItem { - type Element = Div; + type Element = Focusable
; fn render(&mut self, _: &mut ViewContext) -> Self::Element { - gpui::div() + gpui::div().track_focus(&self.focus_handle) } } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 3e92469029d3032377b136a0956ad3dc6f4bf04d..a789809537d1bf5fdc530cf293d5f8b9d542c8b2 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -526,6 +526,7 @@ impl Workspace { cx.notify() }) .detach(); + cx.on_blur_window(|this, cx| { let focus_handle = this.focus_handle(cx); cx.focus(&focus_handle);