Fix blinking in `editor2` (#3272)

Max Brunsfeld created

This also introduces new APIs in `ViewContext` for observing window
focus changes.

Release Notes:

- N/A

Change summary

crates/editor2/src/blink_manager.rs |   4 
crates/editor2/src/editor.rs        |  47 ++++++++
crates/gpui2/src/app.rs             |  20 ++
crates/gpui2/src/window.rs          | 175 +++++++++++++++++++++++++++---
4 files changed, 218 insertions(+), 28 deletions(-)

Detailed changes

@@ -85,6 +85,10 @@ impl BlinkManager {
     }
 
     pub fn enable(&mut self, cx: &mut ModelContext<Self>) {
+        if self.enabled {
+            return;
+        }
+
         self.enabled = true;
         // Set cursors as invisible and start blinking: this causes cursors
         // to be visible during the next render.

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<Self>) {
+        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<Self>) {
+        // 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 {

crates/gpui2/src/app.rs 🔗

@@ -617,8 +617,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
@@ -627,15 +628,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();

crates/gpui2/src/window.rs 🔗

@@ -60,7 +60,7 @@ pub enum DispatchPhase {
 }
 
 type AnyObserver = Box<dyn FnMut(&mut WindowContext) -> bool + 'static>;
-type AnyListener = Box<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
+type AnyListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut WindowContext) + 'static>;
 type AnyKeyListener = Box<
     dyn Fn(
             &dyn Any,
@@ -71,9 +71,49 @@ type AnyKeyListener = Box<
         + 'static,
 >;
 type AnyFocusListener = Box<dyn Fn(&FocusEvent, &mut WindowContext) + 'static>;
+type AnyWindowFocusListener = Box<dyn FnMut(&FocusEvent, &mut WindowContext) -> 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<RwLock<SlotMap<FocusId, AtomicUsize>>>,
+    pub(crate) focus_listeners: SubscriberSet<(), AnyWindowFocusListener>,
     default_prevented: bool,
     mouse_position: Point<Pixels>,
     requested_cursor_style: Option<CursorStyle>,
@@ -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;
@@ -1880,6 +1907,110 @@ 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<V>) + '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<V>) + '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 loses focus.
+    /// Unlike [on_focus_changed], returns a subscription and persists until the subscription
+    /// is dropped.
+    pub fn on_blur(
+        &mut self,
+        handle: &FocusHandle,
+        mut listener: impl FnMut(&mut V, &mut ViewContext<V>) + '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(|blurred| blurred.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 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<V>) + '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, |blurred| focus_id.contains(blurred.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<V>) + 'static,