mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-20 02:47:34 +03:00
Fire focus handlers on draw to avoid timing with newly created item (#3640)
See title Any focus changes which fire from within a focus handler should be handled in the next frame Release Notes: - N/A
This commit is contained in:
commit
62d655183b
@ -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::<Vec<_>>()
|
||||
{
|
||||
@ -693,51 +691,6 @@ impl AppContext {
|
||||
});
|
||||
}
|
||||
|
||||
fn apply_focus_changed_effect(
|
||||
&mut self,
|
||||
window_handle: AnyWindowHandle,
|
||||
focused: Option<FocusId>,
|
||||
) {
|
||||
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<dyn Any>,
|
||||
},
|
||||
FocusChanged {
|
||||
window_handle: AnyWindowHandle,
|
||||
focused: Option<FocusId>,
|
||||
},
|
||||
Refresh,
|
||||
NotifyGlobalObservers {
|
||||
global_type: TypeId,
|
||||
|
@ -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<FocusListener>;
|
||||
|
||||
pub type FocusListener = Box<dyn Fn(&FocusHandle, &FocusEvent, &mut WindowContext) + 'static>;
|
||||
|
||||
pub type MouseDownListener =
|
||||
Box<dyn Fn(&MouseDownEvent, &InteractiveBounds, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
pub type MouseUpListener =
|
||||
|
@ -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<FocusHandle>,
|
||||
pub focused: Option<FocusHandle>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::{
|
||||
|
@ -29,6 +29,7 @@ pub(crate) struct DispatchNode {
|
||||
pub key_listeners: Vec<KeyListener>,
|
||||
pub action_listeners: Vec<DispatchActionListener>,
|
||||
pub context: Option<KeyContext>,
|
||||
focus_id: Option<FocusId>,
|
||||
parent: Option<DispatchNodeId>,
|
||||
}
|
||||
|
||||
@ -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]
|
||||
}
|
||||
|
@ -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<dyn FnMut(&mut WindowContext) -> bool + 'static>;
|
||||
type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
|
||||
type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
|
||||
type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> 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<RwLock<SlotMap<FocusId, AtomicUsize>>>,
|
||||
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<Pixels>,
|
||||
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<Option<FocusId>>,
|
||||
pub(crate) focus: Option<FocusId>,
|
||||
|
||||
#[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<FocusId>,
|
||||
pub(crate) element_states: FxHashMap<GlobalElementId, ElementStateBox>,
|
||||
mouse_listeners: FxHashMap<TypeId, Vec<(StackingOrder, AnyMouseListener)>>,
|
||||
pub(crate) dispatch_tree: DispatchTree,
|
||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||
pub(crate) scene_builder: SceneBuilder,
|
||||
pub(crate) depth_map: Vec<(StackingOrder, Bounds<Pixels>)>,
|
||||
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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -277,13 +277,15 @@ pub enum ViewEvent {
|
||||
impl EventEmitter<ViewEvent> for ProjectSearchView {}
|
||||
|
||||
impl Render for ProjectSearchView {
|
||||
type Element = Div;
|
||||
type Element = AnyElement;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Div>;
|
||||
|
||||
fn render(&mut self, _: &mut ViewContext<Self>) -> Self::Element {
|
||||
gpui::div()
|
||||
gpui::div().track_focus(&self.focus_handle)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user