Store generic mouse regions on window that contain their event type id

Nathan Sobo created

Change summary

crates/gpui/playground/src/element.rs               | 142 +++++++++++---
crates/gpui/playground/src/frame.rs                 |   9 
crates/gpui/playground/src/paint_context.rs         |   2 
crates/gpui/playground/src/playground.rs            |   7 
crates/gpui/playground/src/text.rs                  |  10 
crates/gpui/playground_macros/src/derive_element.rs |   2 
crates/gpui/src/app/window.rs                       |  10 
crates/gpui/src/scene.rs                            |   1 
8 files changed, 128 insertions(+), 55 deletions(-)

Detailed changes

crates/gpui/playground/src/element.rs 🔗

@@ -11,8 +11,11 @@ use gpui::{
     EngineLayout, EventContext, RenderContext, ViewContext,
 };
 use playground_macros::tailwind_lengths;
-use smallvec::SmallVec;
-use std::{any::Any, cell::Cell, rc::Rc};
+use std::{
+    any::{Any, TypeId},
+    cell::Cell,
+    rc::Rc,
+};
 
 pub use crate::paint_context::PaintContext;
 pub use taffy::tree::NodeId;
@@ -22,28 +25,32 @@ pub struct Layout<'a, E: ?Sized> {
     pub from_element: &'a mut E,
 }
 
-pub struct ElementHandlers<V> {
-    mouse_button: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
-}
-
 pub struct ElementMetadata<V> {
     pub style: ElementStyle,
-    pub handlers: ElementHandlers<V>,
+    pub handlers: Vec<EventHandler<V>>,
 }
 
-impl<V> Default for ElementMetadata<V> {
-    fn default() -> Self {
+pub struct EventHandler<V> {
+    handler: Rc<dyn Fn(&mut V, &dyn Any, &mut EventContext<V>)>,
+    event_type: TypeId,
+    outside_bounds: bool,
+}
+
+impl<V> Clone for EventHandler<V> {
+    fn clone(&self) -> Self {
         Self {
-            style: ElementStyle::default(),
-            handlers: ElementHandlers::default(),
+            handler: self.handler.clone(),
+            event_type: self.event_type,
+            outside_bounds: self.outside_bounds,
         }
     }
 }
 
-impl<V> Default for ElementHandlers<V> {
+impl<V> Default for ElementMetadata<V> {
     fn default() -> Self {
-        ElementHandlers {
-            mouse_button: Default::default(),
+        Self {
+            style: ElementStyle::default(),
+            handlers: Vec::new(),
         }
     }
 }
@@ -52,7 +59,8 @@ pub trait Element<V: 'static>: 'static {
     type Layout: 'static;
 
     fn style_mut(&mut self) -> &mut ElementStyle;
-    fn handlers_mut(&mut self) -> &mut ElementHandlers<V>;
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
+
     fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>)
         -> Result<(NodeId, Self::Layout)>;
     fn paint<'a>(
@@ -96,6 +104,12 @@ pub trait Element<V: 'static>: 'static {
                 pressed.set(true);
             }
         })
+        .mouse_up_outside(button, {
+            let pressed = pressed.clone();
+            move |_, _, _| {
+                pressed.set(false);
+            }
+        })
         .mouse_up(button, move |view, event, event_cx| {
             if pressed.get() {
                 pressed.set(false);
@@ -112,13 +126,37 @@ pub trait Element<V: 'static>: 'static {
     where
         Self: Sized,
     {
-        self.handlers_mut()
-            .mouse_button
-            .push(Rc::new(move |view, event, event_cx| {
+        self.handlers_mut().push(EventHandler {
+            handler: Rc::new(move |view, event, event_cx| {
+                let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
                 if event.button == button && event.is_down {
                     handler(view, event, event_cx);
                 }
-            }));
+            }),
+            event_type: TypeId::of::<MouseButtonEvent>(),
+            outside_bounds: false,
+        });
+        self
+    }
+
+    fn mouse_down_outside(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.handlers_mut().push(EventHandler {
+            handler: Rc::new(move |view, event, event_cx| {
+                let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                if event.button == button && event.is_down {
+                    handler(view, event, event_cx);
+                }
+            }),
+            event_type: TypeId::of::<MouseButtonEvent>(),
+            outside_bounds: true,
+        });
         self
     }
 
@@ -130,13 +168,37 @@ pub trait Element<V: 'static>: 'static {
     where
         Self: Sized,
     {
-        self.handlers_mut()
-            .mouse_button
-            .push(Rc::new(move |view, event, event_cx| {
+        self.handlers_mut().push(EventHandler {
+            handler: Rc::new(move |view, event, event_cx| {
+                let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
+                if event.button == button && !event.is_down {
+                    handler(view, event, event_cx);
+                }
+            }),
+            event_type: TypeId::of::<MouseButtonEvent>(),
+            outside_bounds: false,
+        });
+        self
+    }
+
+    fn mouse_up_outside(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.handlers_mut().push(EventHandler {
+            handler: Rc::new(move |view, event, event_cx| {
+                let event = event.downcast_ref::<MouseButtonEvent>().unwrap();
                 if event.button == button && !event.is_down {
                     handler(view, event, event_cx);
                 }
-            }));
+            }),
+            event_type: TypeId::of::<MouseButtonEvent>(),
+            outside_bounds: true,
+        });
         self
     }
 
@@ -362,7 +424,7 @@ pub trait Element<V: 'static>: 'static {
 // Object-safe counterpart of Element used by AnyElement to store elements as trait objects.
 trait ElementObject<V> {
     fn style_mut(&mut self) -> &mut ElementStyle;
-    fn handlers_mut(&mut self) -> &mut ElementHandlers<V>;
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>>;
     fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>)
         -> Result<(NodeId, Box<dyn Any>)>;
     fn paint(
@@ -378,7 +440,7 @@ impl<V: 'static, E: Element<V>> ElementObject<V> for E {
         Element::style_mut(self)
     }
 
-    fn handlers_mut(&mut self) -> &mut ElementHandlers<V> {
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
         Element::handlers_mut(self)
     }
 
@@ -454,19 +516,27 @@ impl<V: 'static> AnyElement<V> {
             from_element: element_layout.as_mut(),
         };
 
-        for handler in self.element.handlers_mut().mouse_button.iter().cloned() {
+        for event_handler in self.element.handlers_mut().iter().cloned() {
             let EngineLayout { order, bounds } = layout.from_engine;
 
             let view_id = cx.view_id();
-            cx.draw_interactive_region(
-                order,
-                bounds,
-                move |view, event: &MouseButtonEvent, window_cx| {
-                    let mut view_cx = ViewContext::mutable(window_cx, view_id);
-                    let mut event_cx = EventContext::new(&mut view_cx);
-                    (handler)(view, event, &mut event_cx);
-                },
-            );
+            let view_event_handler = event_handler.handler.clone();
+
+            // TODO: Tuck this into a method on PaintContext.
+            cx.scene
+                .interactive_regions
+                .push(gpui::scene::InteractiveRegion {
+                    order,
+                    bounds,
+                    outside_bounds: event_handler.outside_bounds,
+                    event_handler: Rc::new(move |view, event, window_cx, view_id| {
+                        let mut view_context = ViewContext::mutable(window_cx, view_id);
+                        let mut event_context = EventContext::new(&mut view_context);
+                        view_event_handler(view.downcast_mut().unwrap(), event, &mut event_context);
+                    }),
+                    event_type: event_handler.event_type,
+                    view_id,
+                });
         }
 
         self.element.paint(layout, view, cx)?;
@@ -485,7 +555,7 @@ impl<V: 'static> Element<V> for AnyElement<V> {
         self.element.style_mut()
     }
 
-    fn handlers_mut(&mut self) -> &mut ElementHandlers<V> {
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
         self.element.handlers_mut()
     }
 

crates/gpui/playground/src/frame.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{
     element::{
-        AnyElement, Element, ElementHandlers, IntoElement, Layout, LayoutContext, NodeId,
-        PaintContext,
+        AnyElement, Element, EventHandler, IntoElement, Layout, LayoutContext, NodeId, PaintContext,
     },
     style::ElementStyle,
 };
@@ -13,14 +12,14 @@ use playground_macros::IntoElement;
 #[element_crate = "crate"]
 pub struct Frame<V: 'static> {
     style: ElementStyle,
-    handlers: ElementHandlers<V>,
+    handlers: Vec<EventHandler<V>>,
     children: Vec<AnyElement<V>>,
 }
 
 pub fn frame<V>() -> Frame<V> {
     Frame {
         style: ElementStyle::default(),
-        handlers: ElementHandlers::default(),
+        handlers: Vec::new(),
         children: Vec::new(),
     }
 }
@@ -32,7 +31,7 @@ impl<V: 'static> Element<V> for Frame<V> {
         &mut self.style
     }
 
-    fn handlers_mut(&mut self) -> &mut ElementHandlers<V> {
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
         &mut self.handlers
     }
 

crates/gpui/playground/src/paint_context.rs 🔗

@@ -41,12 +41,14 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
         &mut self,
         order: u32,
         bounds: RectF,
+        outside_bounds: bool,
         handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
     ) {
         // We'll sort these by their order in `take_interactive_regions`.
         self.scene.interactive_regions.push(InteractiveRegion {
             order,
             bounds,
+            outside_bounds,
             event_handler: Rc::new(move |view, event, window_cx, view_id| {
                 let mut cx = ViewContext::mutable(window_cx, view_id);
                 let mut cx = EventContext::new(&mut cx);

crates/gpui/playground/src/playground.rs 🔗

@@ -49,12 +49,7 @@ fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
         .h_full()
         .w_half()
         .fill(theme.success(0.5))
-        .child(
-            button()
-                .label("Hello")
-                .mouse_up(MouseButton::Left, |_, _, _| (println!("up!")))
-                .mouse_down(MouseButton::Left, |_, _, _| (println!("down!"))),
-        )
+        .child(button().label("Hello").click(|_, _, _| println!("click!")))
 }
 
 //     todo!()

crates/gpui/playground/src/text.rs 🔗

@@ -1,4 +1,4 @@
-use crate::element::{Element, ElementMetadata, IntoElement};
+use crate::element::{Element, ElementMetadata, EventHandler, IntoElement};
 use gpui::{geometry::Size, text_layout::LineLayout, RenderContext};
 use parking_lot::Mutex;
 use std::sync::Arc;
@@ -26,10 +26,6 @@ impl<V: 'static> Element<V> for Text<V> {
         &mut self.metadata.style
     }
 
-    fn handlers_mut(&mut self) -> &mut crate::element::ElementHandlers<V> {
-        &mut self.metadata.handlers
-    }
-
     fn layout(
         &mut self,
         view: &mut V,
@@ -95,6 +91,10 @@ impl<V: 'static> Element<V> for Text<V> {
         );
         Ok(())
     }
+
+    fn handlers_mut(&mut self) -> &mut Vec<EventHandler<V>> {
+        &mut self.metadata.handlers
+    }
 }
 
 pub struct TextLayout {

crates/gpui/playground_macros/src/derive_element.rs 🔗

@@ -83,7 +83,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
                 &mut self.metadata.style
             }
 
-            fn handlers_mut(&mut self) -> &mut #crate_name::element::ElementHandlers<V> {
+            fn handlers_mut(&mut self) -> &mut Vec<#crate_name::element::EventHandler<V>> {
                 &mut self.metadata.handlers
             }
 

crates/gpui/src/app/window.rs 🔗

@@ -118,6 +118,10 @@ impl Window {
             .as_ref()
             .expect("root_view called during window construction")
     }
+
+    pub fn take_interactive_regions(&mut self) -> Vec<InteractiveRegion> {
+        mem::take(&mut self.interactive_regions)
+    }
 }
 
 pub struct WindowContext<'a> {
@@ -875,11 +879,13 @@ impl<'a> WindowContext<'a> {
     fn dispatch_to_interactive_regions(&mut self, event: &Event) {
         if let Some(mouse_event) = event.mouse_event() {
             let mouse_position = event.position().expect("mouse events must have a position");
-            let interactive_regions = std::mem::take(&mut self.window.interactive_regions);
+            let interactive_regions = self.window.take_interactive_regions();
 
             for region in interactive_regions.iter().rev() {
                 if region.event_type == mouse_event.type_id() {
-                    if region.bounds.contains_point(mouse_position) {
+                    let in_bounds = region.bounds.contains_point(mouse_position);
+
+                    if in_bounds == !region.outside_bounds {
                         self.update_any_view(region.view_id, |view, window_cx| {
                             (region.event_handler)(
                                 view.as_any_mut(),

crates/gpui/src/scene.rs 🔗

@@ -711,6 +711,7 @@ impl MouseRegion {
 pub struct InteractiveRegion {
     pub order: u32,
     pub bounds: RectF,
+    pub outside_bounds: bool,
     pub event_handler: Rc<dyn Fn(&mut dyn Any, &dyn Any, &mut WindowContext, usize)>,
     pub event_type: TypeId,
     pub view_id: usize,