Add basic mouse event handling

Nathan Sobo created

Change summary

crates/gpui3/src/elements.rs          |  2 
crates/gpui3/src/elements/div.rs      | 28 +++++++-----
crates/gpui3/src/gpui3.rs             |  2 
crates/gpui3/src/interactive.rs       | 65 ++++++++++++++++++----------
crates/gpui3/src/window.rs            |  9 ++-
crates/storybook2/src/collab_panel.rs |  7 ++
6 files changed, 70 insertions(+), 43 deletions(-)

Detailed changes

crates/gpui3/src/elements.rs 🔗

@@ -1,13 +1,11 @@
 mod div;
 mod img;
-mod interactive;
 mod stateless;
 mod svg;
 mod text;
 
 pub use div::*;
 pub use img::*;
-pub use interactive::*;
 pub use stateless::*;
 pub use svg::*;
 pub use text::*;

crates/gpui3/src/elements/div.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
-    AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable,
-    RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
+    AnyElement, Bounds, Element, Interactive, LayoutId, MouseEventListeners, Overflow,
+    ParentElement, Pixels, Point, Refineable, RefinementCascade, Result, Style, StyleHelpers,
+    Styled, ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -9,7 +10,7 @@ use util::ResultExt;
 
 pub struct Div<S: 'static> {
     styles: RefinementCascade<Style>,
-    // handlers: InteractionHandlers<V>,
+    listeners: MouseEventListeners<S>,
     children: SmallVec<[AnyElement<S>; 2]>,
     scroll_state: Option<ScrollState>,
 }
@@ -17,7 +18,7 @@ pub struct Div<S: 'static> {
 pub fn div<S>() -> Div<S> {
     Div {
         styles: Default::default(),
-        // handlers: Default::default(),
+        listeners: Default::default(),
         children: Default::default(),
         scroll_state: None,
     }
@@ -42,7 +43,7 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
         &mut self,
         bounds: Bounds<Pixels>,
         state: &mut S,
-        child_layouts: &mut Self::FrameState,
+        child_layout_ids: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
     ) -> Result<()> {
         let style = self.computed_style();
@@ -51,10 +52,13 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
         let overflow = &style.overflow;
         style.apply_text_style(cx, |cx| {
             cx.stack(1, |cx| {
-                style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx))
+                style.apply_overflow(bounds, cx, |cx| {
+                    self.listeners.paint(bounds, cx);
+                    self.paint_children(overflow, state, cx)
+                })
             })
         })?;
-        self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx);
+        self.handle_scroll(bounds, style.overflow.clone(), child_layout_ids, cx);
 
         // todo!("enable inspector")
         // if cx.is_inspector_enabled() {
@@ -247,11 +251,11 @@ impl<V> Styled for Div<V> {
 
 impl<V> StyleHelpers for Div<V> {}
 
-// impl<V> Interactive<V> for Div<V> {
-//     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
-//         &mut self.handlers
-//     }
-// }
+impl<V: Send + Sync + 'static> Interactive<V> for Div<V> {
+    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+        &mut self.listeners
+    }
+}
 
 impl<V: 'static> ParentElement<V> for Div<V> {
     fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {

crates/gpui3/src/gpui3.rs 🔗

@@ -6,6 +6,7 @@ mod elements;
 mod executor;
 mod geometry;
 mod image_cache;
+mod interactive;
 mod platform;
 mod scene;
 mod style;
@@ -28,6 +29,7 @@ pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
 pub use image_cache::*;
+pub use interactive::*;
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;

crates/gpui3/src/elements/interactive.rs → crates/gpui3/src/interactive.rs 🔗

@@ -6,7 +6,7 @@ use smallvec::SmallVec;
 use std::sync::Arc;
 
 pub trait Interactive<S: 'static + Send + Sync> {
-    fn interaction_listeners(&mut self) -> &mut InteractionHandlers<S>;
+    fn listeners(&mut self) -> &mut MouseEventListeners<S>;
 
     fn on_mouse_down(
         mut self,
@@ -16,10 +16,13 @@ pub trait Interactive<S: 'static + Send + Sync> {
     where
         Self: Sized,
     {
-        self.interaction_listeners()
+        self.listeners()
             .mouse_down
-            .push(Arc::new(move |view, event, phase, cx| {
-                if phase == DispatchPhase::Bubble && event.button == button {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(event.position)
+                {
                     handler(view, event, cx)
                 }
             }));
@@ -34,10 +37,13 @@ pub trait Interactive<S: 'static + Send + Sync> {
     where
         Self: Sized,
     {
-        self.interaction_listeners()
+        self.listeners()
             .mouse_up
-            .push(Arc::new(move |view, event, phase, cx| {
-                if phase == DispatchPhase::Bubble && event.button == button {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(event.position)
+                {
                     handler(view, event, cx)
                 }
             }));
@@ -52,10 +58,13 @@ pub trait Interactive<S: 'static + Send + Sync> {
     where
         Self: Sized,
     {
-        self.interaction_listeners()
+        self.listeners()
             .mouse_down
-            .push(Arc::new(move |view, event, phase, cx| {
-                if phase == DispatchPhase::Capture && event.button == button {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.contains_point(event.position)
+                {
                     handler(view, event, cx)
                 }
             }));
@@ -70,10 +79,13 @@ pub trait Interactive<S: 'static + Send + Sync> {
     where
         Self: Sized,
     {
-        self.interaction_listeners()
+        self.listeners()
             .mouse_up
-            .push(Arc::new(move |view, event, phase, cx| {
-                if event.button == button && phase == DispatchPhase::Capture {
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.contains_point(event.position)
+                {
                     handler(view, event, cx);
                 }
             }));
@@ -83,7 +95,7 @@ pub trait Interactive<S: 'static + Send + Sync> {
     fn on_click(
         self,
         button: MouseButton,
-        handler: impl Fn(&mut S, &MouseDownEvent, &MouseUpEvent, &mut ViewContext<S>)
+        handler: impl Fn(&mut S, (&MouseDownEvent, &MouseUpEvent), &mut ViewContext<S>)
             + Send
             + Sync
             + 'static,
@@ -106,43 +118,50 @@ pub trait Interactive<S: 'static + Send + Sync> {
         })
         .on_mouse_up(button, move |view, event, cx| {
             if let Some(down_event) = down_event.lock().take() {
-                handler(view, &down_event, event, cx);
+                handler(view, (&down_event, event), cx);
             }
         })
     }
 }
 
 type MouseDownHandler<V> = Arc<
-    dyn Fn(&mut V, &MouseDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static,
+    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+type MouseUpHandler<V> = Arc<
+    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
 >;
-type MouseUpHandler<V> =
-    Arc<dyn Fn(&mut V, &MouseUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
 
-pub struct InteractionHandlers<V: 'static> {
+pub struct MouseEventListeners<V: 'static> {
     mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
     mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
 }
 
-impl<S: Send + Sync + 'static> InteractionHandlers<S> {
+impl<S: Send + Sync + 'static> MouseEventListeners<S> {
     pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<S>) {
         for handler in self.mouse_down.iter().cloned() {
             cx.on_mouse_event(move |view, event: &MouseDownEvent, phase, cx| {
                 if bounds.contains_point(event.position) {
-                    handler(view, event, phase, cx);
+                    handler(view, event, &bounds, phase, cx);
                 }
             })
         }
         for handler in self.mouse_up.iter().cloned() {
             cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
                 if bounds.contains_point(event.position) {
-                    handler(view, event, phase, cx);
+                    handler(view, event, &bounds, phase, cx);
                 }
             })
         }
     }
 }
 
-impl<V> Default for InteractionHandlers<V> {
+impl<V> Default for MouseEventListeners<V> {
     fn default() -> Self {
         Self {
             mouse_down: Default::default(),

crates/gpui3/src/window.rs 🔗

@@ -278,7 +278,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         self.window.rem_size
     }
 
-    pub fn stop_event_propagation(&mut self) {
+    pub fn stop_propagation(&mut self) {
         self.window.propagate_event = false;
     }
 
@@ -625,10 +625,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 .mouse_event_handlers
                 .remove(&any_mouse_event.type_id())
             {
-                // We sort these every time, because handlers may add handlers. Probably fast enough.
+                // Because handlers may add other handlers, we sort every time.
                 handlers.sort_by(|(a, _), (b, _)| a.cmp(b));
 
-                // Handlers may set this to false by calling `stop_propagation`;
+                // Handlers may set this to false by calling `stop_propagation`
                 self.window.propagate_event = true;
 
                 // Capture phase, events bubble from back to front. Handlers for this phase are used for
@@ -640,7 +640,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                     }
                 }
 
-                // Bubble phase
+                // Bubble phase, where most normal handlers do their work.
                 if self.window.propagate_event {
                     for (_, handler) in handlers.iter().rev() {
                         handler(any_mouse_event, DispatchPhase::Bubble, self);
@@ -650,6 +650,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                     }
                 }
 
+                // Just in case any handlers added new handlers, which is weird, but possible.
                 handlers.extend(
                     self.window
                         .mouse_event_handlers

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,7 +1,7 @@
 use crate::theme::{theme, Theme};
 use gpui3::{
-    div, img, svg, view, AppContext, Context, Element, IntoAnyElement, ParentElement, ScrollState,
-    SharedString, StyleHelpers, View, ViewContext, WindowContext,
+    div, img, svg, view, AppContext, Context, Element, Interactive, IntoAnyElement, MouseButton,
+    ParentElement, ScrollState, SharedString, StyleHelpers, View, ViewContext, WindowContext,
 };
 
 pub struct CollabPanel {
@@ -44,6 +44,9 @@ impl CollabPanel {
                     // List Container
                     .child(
                         div()
+                            .on_click(MouseButton::Left, |_, _, _| {
+                                dbg!("click!");
+                            })
                             .fill(theme.lowest.base.default.background)
                             .pb_1()
                             .border_color(theme.lowest.base.default.border)