Introduce MouseEventHandler

Nathan Sobo created

Still need to give elements the ability to re-render their parent view. Once that is in place, I think we can implement hoverable close tab buttons.

Change summary

gpui/src/app.rs                          |  13 ++
gpui/src/elements/mod.rs                 |   1 
gpui/src/elements/mouse_event_handler.rs | 118 ++++++++++++++++++++++++++
gpui/src/platform/event.rs               |   3 
gpui/src/platform/mac/event.rs           |   6 +
5 files changed, 138 insertions(+), 3 deletions(-)

Detailed changes

gpui/src/app.rs 🔗

@@ -866,7 +866,8 @@ impl MutableAppContext {
 
     fn remove_dropped_entities(&mut self) {
         loop {
-            let (dropped_models, dropped_views, dropped_values) = self.ctx.ref_counts.lock().take_dropped();
+            let (dropped_models, dropped_views, dropped_values) =
+                self.ctx.ref_counts.lock().take_dropped();
             if dropped_models.is_empty() && dropped_views.is_empty() && dropped_values.is_empty() {
                 break;
             }
@@ -1361,7 +1362,7 @@ impl AppContext {
         &self.thread_pool
     }
 
-    pub fn value<T: 'static + Default, Tag: 'static>(&self, id: usize) -> ValueHandle<T> {
+    pub fn value<Tag: 'static, T: 'static + Default>(&self, id: usize) -> ValueHandle<T> {
         let key = (TypeId::of::<Tag>(), id);
         let mut values = self.values.lock();
         values.entry(key).or_insert_with(|| Box::new(T::default()));
@@ -2439,7 +2440,13 @@ impl RefCounts {
         }
     }
 
-    fn take_dropped(&mut self) -> (HashSet<usize>, HashSet<(usize, usize)>, HashSet<(TypeId, usize)>) {
+    fn take_dropped(
+        &mut self,
+    ) -> (
+        HashSet<usize>,
+        HashSet<(usize, usize)>,
+        HashSet<(TypeId, usize)>,
+    ) {
         let mut dropped_models = HashSet::new();
         let mut dropped_views = HashSet::new();
         let mut dropped_values = HashSet::new();

gpui/src/elements/mod.rs 🔗

@@ -7,6 +7,7 @@ mod event_handler;
 mod flex;
 mod label;
 mod line_box;
+mod mouse_event_handler;
 mod new;
 mod stack;
 mod svg;

gpui/src/elements/mouse_event_handler.rs 🔗

@@ -0,0 +1,118 @@
+use crate::{
+    geometry::{rect::RectF, vector::Vector2F},
+    AfterLayoutContext, AppContext, DebugContext, Element, ElementBox, Event, EventContext,
+    LayoutContext, PaintContext, SizeConstraint, ValueHandle,
+};
+use serde_json::json;
+
+pub struct MouseEventHandler {
+    state: ValueHandle<MouseState>,
+    child: ElementBox,
+}
+
+#[derive(Clone, Copy, Default)]
+pub struct MouseState {
+    hovered: bool,
+    clicked: bool,
+}
+
+impl MouseEventHandler {
+    pub fn new<Tag: 'static>(
+        id: usize,
+        ctx: &AppContext,
+        render_child: impl FnOnce(MouseState) -> ElementBox,
+    ) -> Self {
+        let state = ctx.value::<Tag, _>(id);
+        let child = state.map(ctx, |state| render_child(*state));
+        Self { state, child }
+    }
+}
+
+impl Element for MouseEventHandler {
+    type LayoutState = ();
+
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        ctx: &mut LayoutContext,
+    ) -> (Vector2F, Self::LayoutState) {
+        (self.child.layout(constraint, ctx), ())
+    }
+
+    fn after_layout(
+        &mut self,
+        _: Vector2F,
+        _: &mut Self::LayoutState,
+        ctx: &mut AfterLayoutContext,
+    ) {
+        self.child.after_layout(ctx);
+    }
+
+    fn paint(
+        &mut self,
+        bounds: RectF,
+        _: &mut Self::LayoutState,
+        ctx: &mut PaintContext,
+    ) -> Self::PaintState {
+        self.child.paint(bounds.origin(), ctx);
+    }
+
+    fn dispatch_event(
+        &mut self,
+        event: &Event,
+        bounds: RectF,
+        _: &mut Self::LayoutState,
+        _: &mut Self::PaintState,
+        ctx: &mut EventContext,
+    ) -> bool {
+        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;
+                    log::info!("hovered {}", state.hovered);
+                    // ctx.notify();
+                    true
+                } else {
+                    false
+                }
+            }
+            Event::LeftMouseDown { position, .. } => {
+                if bounds.contains_point(*position) {
+                    log::info!("clicked");
+                    state.clicked = true;
+                    // ctx.notify();
+                    true
+                } else {
+                    false
+                }
+            }
+            Event::LeftMouseUp { .. } => {
+                if state.clicked {
+                    log::info!("unclicked");
+                    state.clicked = false;
+                    // ctx.notify();
+                    true
+                } else {
+                    false
+                }
+            }
+            _ => false,
+        })
+    }
+
+    fn debug(
+        &self,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        ctx: &DebugContext,
+    ) -> serde_json::Value {
+        json!({
+            "type": "MouseEventHandler",
+            "child": self.child.debug(ctx),
+        })
+    }
+}

gpui/src/platform/event.rs 🔗

@@ -21,4 +21,7 @@ pub enum Event {
     LeftMouseDragged {
         position: Vector2F,
     },
+    MouseMoved {
+        position: Vector2F,
+    },
 }

gpui/src/platform/mac/event.rs 🔗

@@ -108,6 +108,12 @@ impl Event {
                 ),
                 precise: native_event.hasPreciseScrollingDeltas() == YES,
             }),
+            NSEventType::NSMouseMoved => window_height.map(|window_height| Self::MouseMoved {
+                position: vec2f(
+                    native_event.locationInWindow().x as f32,
+                    window_height - native_event.locationInWindow().y as f32,
+                ),
+            }),
             _ => None,
         }
     }