Synthesize a mouse moved event in the previous position after painting a scene

Nathan Sobo , Antonio Scandurra , and Max Brunsfeld created

This ensures that we correctly update the hover state of elements whose position has changed relative to the mouse cursor even though the mouse hasn't actually moved.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

gpui/src/app.rs            | 39 +++++++++++++--------------------------
gpui/src/platform/event.rs |  2 +-
gpui/src/presenter.rs      | 36 +++++++++++++++++++++++++++---------
3 files changed, 41 insertions(+), 36 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -584,6 +584,11 @@ impl MutableAppContext {
         );
     }
 
+    pub(crate) fn notify_view(&mut self, window_id: usize, view_id: usize) {
+        self.pending_effects
+            .push_back(Effect::ViewNotification { window_id, view_id });
+    }
+
     pub fn dispatch_action<T: 'static + Any>(
         &mut self,
         window_id: usize,
@@ -594,7 +599,7 @@ impl MutableAppContext {
         self.dispatch_action_any(window_id, &responder_chain, name, Box::new(arg).as_ref());
     }
 
-    fn dispatch_action_any(
+    pub(crate) fn dispatch_action_any(
         &mut self,
         window_id: usize,
         path: &[usize],
@@ -787,23 +792,7 @@ impl MutableAppContext {
                         }
                     }
 
-                    let (actions, invalidated_views) =
-                        presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
-
-                    ctx.window_invalidations
-                        .entry(window_id)
-                        .or_default()
-                        .updated
-                        .extend(invalidated_views.into_iter());
-
-                    for action in actions {
-                        ctx.dispatch_action_any(
-                            window_id,
-                            &action.path,
-                            action.name,
-                            action.arg.as_ref(),
-                        );
-                    }
+                    presenter.borrow_mut().dispatch_event(event, ctx);
                 })
             }));
         }
@@ -924,11 +913,14 @@ impl MutableAppContext {
                         self.focus(window_id, view_id);
                     }
                 }
+
+                if self.pending_effects.is_empty() {
+                    self.remove_dropped_entities();
+                    self.update_windows();
+                }
             }
 
             self.flushing_effects = false;
-            self.remove_dropped_entities();
-            self.update_windows();
         }
     }
 
@@ -1850,12 +1842,7 @@ impl<'a, T: View> ViewContext<'a, T> {
     }
 
     pub fn notify(&mut self) {
-        self.app
-            .pending_effects
-            .push_back(Effect::ViewNotification {
-                window_id: self.window_id,
-                view_id: self.view_id,
-            });
+        self.app.notify_view(self.window_id, self.view_id);
     }
 
     pub fn propagate_action(&mut self) {

gpui/src/platform/event.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{geometry::vector::Vector2F, keymap::Keystroke};
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub enum Event {
     KeyDown {
         keystroke: Keystroke,

gpui/src/presenter.rs 🔗

@@ -22,6 +22,7 @@ pub struct Presenter {
     font_cache: Arc<FontCache>,
     text_layout_cache: TextLayoutCache,
     asset_cache: Arc<AssetCache>,
+    last_mouse_moved_event: Option<Event>,
 }
 
 impl Presenter {
@@ -39,6 +40,7 @@ impl Presenter {
             font_cache,
             text_layout_cache,
             asset_cache,
+            last_mouse_moved_event: None,
         }
     }
 
@@ -84,6 +86,10 @@ impl Presenter {
             };
             ctx.paint(root_view_id, Vector2F::zero());
             self.text_layout_cache.finish_frame();
+
+            if let Some(event) = self.last_mouse_moved_event.clone() {
+                self.dispatch_event(event, app)
+            }
         } else {
             log::error!("could not find root_view_id for window {}", self.window_id);
         }
@@ -118,12 +124,12 @@ impl Presenter {
         }
     }
 
-    pub fn dispatch_event(
-        &mut self,
-        event: Event,
-        app: &AppContext,
-    ) -> (Vec<ActionToDispatch>, HashSet<usize>) {
+    pub fn dispatch_event(&mut self, event: Event, app: &mut MutableAppContext) {
         if let Some(root_view_id) = app.root_view_id(self.window_id) {
+            if matches!(event, Event::MouseMoved { .. }) {
+                self.last_mouse_moved_event = Some(event.clone());
+            }
+
             let mut ctx = EventContext {
                 rendered_views: &mut self.rendered_views,
                 actions: Default::default(),
@@ -131,12 +137,24 @@ impl Presenter {
                 text_layout_cache: &self.text_layout_cache,
                 view_stack: Default::default(),
                 invalidated_views: Default::default(),
-                app,
+                app: app.as_ref(),
             };
             ctx.dispatch_event(root_view_id, &event);
-            (ctx.actions, ctx.invalidated_views)
-        } else {
-            Default::default()
+
+            let invalidated_views = ctx.invalidated_views;
+            let actions = ctx.actions;
+
+            for view_id in invalidated_views {
+                app.notify_view(self.window_id, view_id);
+            }
+            for action in actions {
+                app.dispatch_action_any(
+                    self.window_id,
+                    &action.path,
+                    action.name,
+                    action.arg.as_ref(),
+                );
+            }
         }
     }