From 2fd8b1f4893dee41ea167ab4ab612711bd0dc8fe Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 8 Nov 2023 19:03:57 +0100 Subject: [PATCH] Fix blinking behavior in editor when receiving/losing focus Co-Authored-By: Marshall --- crates/editor2/src/blink_manager.rs | 4 + crates/editor2/src/editor.rs | 47 ++++++++- crates/gpui2/src/app.rs | 20 +++- crates/gpui2/src/window.rs | 152 ++++++++++++++++++++++++---- 4 files changed, 195 insertions(+), 28 deletions(-) diff --git a/crates/editor2/src/blink_manager.rs b/crates/editor2/src/blink_manager.rs index d25e30f649..0fc748f48a 100644 --- a/crates/editor2/src/blink_manager.rs +++ b/crates/editor2/src/blink_manager.rs @@ -85,6 +85,10 @@ impl BlinkManager { } pub fn enable(&mut self, cx: &mut ModelContext) { + if self.enabled { + return; + } + self.enabled = true; // Set cursors as invisible and start blinking: this causes cursors // to be visible during the next render. diff --git a/crates/editor2/src/editor.rs b/crates/editor2/src/editor.rs index ea76a7b57d..049d304750 100644 --- a/crates/editor2/src/editor.rs +++ b/crates/editor2/src/editor.rs @@ -1920,9 +1920,15 @@ impl Editor { cx, ); + let focus_handle = cx.focus_handle(); + cx.on_focus_in(&focus_handle, Self::handle_focus_in) + .detach(); + cx.on_focus_out(&focus_handle, Self::handle_focus_out) + .detach(); + let mut this = Self { handle: cx.view().downgrade(), - focus_handle: cx.focus_handle(), + focus_handle, buffer: buffer.clone(), display_map: display_map.clone(), selections, @@ -9195,6 +9201,45 @@ impl Editor { pub fn focus(&self, cx: &mut WindowContext) { cx.focus(&self.focus_handle) } + + fn handle_focus_in(&mut self, cx: &mut ViewContext) { + if self.focus_handle.is_focused(cx) { + // todo!() + // let focused_event = EditorFocused(cx.handle()); + // cx.emit_global(focused_event); + cx.emit(Event::Focused); + } + if let Some(rename) = self.pending_rename.as_ref() { + let rename_editor_focus_handle = rename.editor.read(cx).focus_handle.clone(); + cx.focus(&rename_editor_focus_handle); + } else if self.focus_handle.is_focused(cx) { + self.blink_manager.update(cx, BlinkManager::enable); + self.buffer.update(cx, |buffer, cx| { + buffer.finalize_last_transaction(cx); + if self.leader_peer_id.is_none() { + buffer.set_active_selections( + &self.selections.disjoint_anchors(), + self.selections.line_mode, + self.cursor_shape, + cx, + ); + } + }); + } + } + + fn handle_focus_out(&mut self, cx: &mut ViewContext) { + // todo!() + // let blurred_event = EditorBlurred(cx.handle()); + // cx.emit_global(blurred_event); + self.blink_manager.update(cx, BlinkManager::disable); + self.buffer + .update(cx, |buffer, cx| buffer.remove_active_selections(cx)); + self.hide_context_menu(cx); + hide_hover(self, cx); + cx.emit(Event::Blurred); + cx.notify(); + } } pub trait CollaborationHub { diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 79f80f474d..b38c403cbc 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -518,8 +518,9 @@ impl AppContext { ) { 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 { - let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); let focused = focused .map(|id| FocusHandle::for_id(id, &cx.window.focus_handles).unwrap()); let blurred = cx @@ -528,15 +529,24 @@ impl AppContext { .take() .unwrap() .and_then(|id| FocusHandle::for_id(id, &cx.window.focus_handles)); - if focused.is_some() || blurred.is_some() { - let event = FocusEvent { focused, blurred }; - for listener in &listeners { + let focus_changed = focused.is_some() || blurred.is_some(); + let event = FocusEvent { focused, blurred }; + + let mut listeners = mem::take(&mut cx.window.current_frame.focus_listeners); + if focus_changed { + for listener in &mut listeners { listener(&event, cx); } } - listeners.extend(cx.window.current_frame.focus_listeners.drain(..)); cx.window.current_frame.focus_listeners = listeners; + + if focus_changed { + cx.window + .focus_listeners + .clone() + .retain(&(), |listener| listener(&event, cx)); + } } }) .ok(); diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 0dae6171d9..f9a5d59acd 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -60,7 +60,7 @@ pub enum DispatchPhase { } type AnyObserver = Box bool + 'static>; -type AnyListener = Box; +type AnyListener = Box; type AnyKeyListener = Box< dyn Fn( &dyn Any, @@ -71,9 +71,49 @@ type AnyKeyListener = Box< + 'static, >; type AnyFocusListener = Box; +type AnyWindowFocusListener = Box bool + 'static>; slotmap::new_key_type! { pub struct FocusId; } +impl FocusId { + /// Obtains whether the element associated with this handle is currently focused. + pub fn is_focused(&self, cx: &WindowContext) -> bool { + cx.window.focus == Some(*self) + } + + /// Obtains whether the element associated with this handle contains the focused + /// element or is itself focused. + pub fn contains_focused(&self, cx: &WindowContext) -> bool { + cx.focused() + .map_or(false, |focused| self.contains(focused.id, cx)) + } + + /// Obtains whether the element associated with this handle is contained within the + /// focused element or is itself focused. + pub fn within_focused(&self, cx: &WindowContext) -> bool { + let focused = cx.focused(); + focused.map_or(false, |focused| focused.id.contains(*self, cx)) + } + + /// Obtains whether this handle contains the given handle in the most recently rendered frame. + pub(crate) fn contains(&self, other: Self, cx: &WindowContext) -> bool { + let mut ancestor = Some(other); + while let Some(ancestor_id) = ancestor { + if *self == ancestor_id { + return true; + } else { + ancestor = cx + .window + .current_frame + .focus_parents_by_child + .get(&ancestor_id) + .copied(); + } + } + false + } +} + /// A handle which can be used to track and manipulate the focused element in a window. pub struct FocusHandle { pub(crate) id: FocusId, @@ -108,39 +148,24 @@ impl FocusHandle { /// Obtains whether the element associated with this handle is currently focused. pub fn is_focused(&self, cx: &WindowContext) -> bool { - cx.window.focus == Some(self.id) + self.id.is_focused(cx) } /// Obtains whether the element associated with this handle contains the focused /// element or is itself focused. pub fn contains_focused(&self, cx: &WindowContext) -> bool { - cx.focused() - .map_or(false, |focused| self.contains(&focused, cx)) + self.id.contains_focused(cx) } /// Obtains whether the element associated with this handle is contained within the /// focused element or is itself focused. pub fn within_focused(&self, cx: &WindowContext) -> bool { - let focused = cx.focused(); - focused.map_or(false, |focused| focused.contains(self, cx)) + self.id.within_focused(cx) } /// Obtains whether this handle contains the given handle in the most recently rendered frame. pub(crate) fn contains(&self, other: &Self, cx: &WindowContext) -> bool { - let mut ancestor = Some(other.id); - while let Some(ancestor_id) = ancestor { - if self.id == ancestor_id { - return true; - } else { - ancestor = cx - .window - .current_frame - .focus_parents_by_child - .get(&ancestor_id) - .copied(); - } - } - false + self.id.contains(other.id, cx) } } @@ -183,6 +208,7 @@ pub struct Window { pub(crate) previous_frame: Frame, pub(crate) current_frame: Frame, pub(crate) focus_handles: Arc>>, + pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>, default_prevented: bool, mouse_position: Point, requested_cursor_style: Option, @@ -282,6 +308,7 @@ impl Window { previous_frame: Frame::default(), current_frame: Frame::default(), focus_handles: Arc::new(RwLock::new(SlotMap::with_key())), + focus_listeners: SubscriberSet::new(), default_prevented: true, mouse_position, requested_cursor_style: None, @@ -1116,7 +1143,7 @@ impl<'a> WindowContext<'a> { // Capture phase, events bubble from back to front. Handlers for this phase are used for // special purposes, such as detecting events outside of a given Bounds. - for (_, handler) in &handlers { + for (_, handler) in &mut handlers { handler(any_mouse_event, DispatchPhase::Capture, self); if !self.app.propagate_event { break; @@ -1125,7 +1152,7 @@ impl<'a> WindowContext<'a> { // Bubble phase, where most normal handlers do their work. if self.app.propagate_event { - for (_, handler) in handlers.iter().rev() { + for (_, handler) in handlers.iter_mut().rev() { handler(any_mouse_event, DispatchPhase::Bubble, self); if !self.app.propagate_event { break; @@ -1872,6 +1899,87 @@ impl<'a, V: 'static> ViewContext<'a, V> { ) } + /// Register a listener to be called when the given focus handle receives focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event.focused.as_ref().map(|focused| focused.id) == Some(focus_id) { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a listener to be called when the given focus handle or one of its descendants receives focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus_in( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + 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)) + { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a listener to be called when the given focus handle or one of its descendants loses focus. + /// Unlike [on_focus_changed], returns a subscription and persists until the subscription + /// is dropped. + pub fn on_focus_out( + &mut self, + handle: &FocusHandle, + mut listener: impl FnMut(&mut V, &mut ViewContext) + 'static, + ) -> Subscription { + let view = self.view.downgrade(); + let focus_id = handle.id; + self.window.focus_listeners.insert( + (), + Box::new(move |event, cx| { + view.update(cx, |view, cx| { + if event + .blurred + .as_ref() + .map_or(false, |focused| focus_id.contains(focused.id, cx)) + { + listener(view, cx) + } + }) + .is_ok() + }), + ) + } + + /// Register a focus listener for the current 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(&mut V, &FocusEvent, &mut ViewContext) + 'static,