Relay hover events to tabs

Nathan Sobo and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

gpui/src/app.rs                          | 10 ++++++++
gpui/src/elements/mouse_event_handler.rs | 22 +++++++++++---------
gpui/src/presenter.rs                    | 27 ++++++++++++++++++++-----
zed/src/workspace/pane.rs                |  3 +
4 files changed, 44 insertions(+), 18 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -788,7 +788,15 @@ impl MutableAppContext {
                         }
                     }
 
-                    let actions = presenter.borrow_mut().dispatch_event(event, ctx.as_ref());
+                    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,

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -10,7 +10,7 @@ pub struct MouseEventHandler {
     child: ElementBox,
 }
 
-#[derive(Clone, Copy, Default)]
+#[derive(Clone, Copy, Debug, Default)]
 pub struct MouseState {
     hovered: bool,
     clicked: bool,
@@ -66,36 +66,38 @@ impl Element for MouseEventHandler {
         _: &mut Self::PaintState,
         ctx: &mut EventContext,
     ) -> bool {
+        let handled_in_child = self.child.dispatch_event(event, ctx);
+
         self.state.map(ctx.app, |state| match event {
             Event::MouseMoved { position } => {
                 let mouse_in = bounds.contains_point(*position);
                 if state.hovered != mouse_in {
                     state.hovered = mouse_in;
-                    // ctx.notify();
+                    ctx.notify();
                     true
                 } else {
-                    false
+                    handled_in_child
                 }
             }
             Event::LeftMouseDown { position, .. } => {
-                if bounds.contains_point(*position) {
+                if !handled_in_child && bounds.contains_point(*position) {
                     state.clicked = true;
-                    // ctx.notify();
+                    ctx.notify();
                     true
                 } else {
-                    false
+                    handled_in_child
                 }
             }
             Event::LeftMouseUp { .. } => {
-                if state.clicked {
+                if !handled_in_child && state.clicked {
                     state.clicked = false;
-                    // ctx.notify();
+                    ctx.notify();
                     true
                 } else {
-                    false
+                    handled_in_child
                 }
             }
-            _ => false,
+            _ => handled_in_child,
         })
     }
 

gpui/src/presenter.rs 🔗

@@ -9,7 +9,11 @@ use crate::{
 };
 use pathfinder_geometry::vector::{vec2f, Vector2F};
 use serde_json::json;
-use std::{any::Any, collections::HashMap, sync::Arc};
+use std::{
+    any::Any,
+    collections::{HashMap, HashSet},
+    sync::Arc,
+};
 
 pub struct Presenter {
     window_id: usize,
@@ -114,20 +118,25 @@ impl Presenter {
         }
     }
 
-    pub fn dispatch_event(&mut self, event: Event, app: &AppContext) -> Vec<ActionToDispatch> {
+    pub fn dispatch_event(
+        &mut self,
+        event: Event,
+        app: &AppContext,
+    ) -> (Vec<ActionToDispatch>, HashSet<usize>) {
         if let Some(root_view_id) = app.root_view_id(self.window_id) {
             let mut ctx = EventContext {
                 rendered_views: &mut self.rendered_views,
-                actions: Vec::new(),
+                actions: Default::default(),
                 font_cache: &self.font_cache,
                 text_layout_cache: &self.text_layout_cache,
-                view_stack: Vec::new(),
+                view_stack: Default::default(),
+                invalidated_views: Default::default(),
                 app,
             };
             ctx.dispatch_event(root_view_id, &event);
-            ctx.actions
+            (ctx.actions, ctx.invalidated_views)
         } else {
-            Vec::new()
+            Default::default()
         }
     }
 
@@ -214,6 +223,7 @@ pub struct EventContext<'a> {
     pub text_layout_cache: &'a TextLayoutCache,
     pub app: &'a AppContext,
     view_stack: Vec<usize>,
+    invalidated_views: HashSet<usize>,
 }
 
 impl<'a> EventContext<'a> {
@@ -236,6 +246,11 @@ impl<'a> EventContext<'a> {
             arg: Box::new(arg),
         });
     }
+
+    pub fn notify(&mut self) {
+        self.invalidated_views
+            .insert(*self.view_stack.last().unwrap());
+    }
 }
 
 pub struct DebugContext<'a> {

zed/src/workspace/pane.rs 🔗

@@ -229,7 +229,8 @@ impl Pane {
             row.add_child(
                 Expanded::new(
                     1.0,
-                    MouseEventHandler::new::<Tab, _>(0, ctx, |mouse_state| {
+                    MouseEventHandler::new::<Tab, _>(item.id(), ctx, |mouse_state| {
+                        log::info!("mouse event handler {:?}", mouse_state);
                         ConstrainedBox::new(
                             EventHandler::new(container.boxed())
                                 .on_mouse_down(move |ctx| {