Get basic mouse_down and mouse_up working

Nathan Sobo created

Change summary

crates/gpui/playground/src/components.rs    |  8 +
crates/gpui/playground/src/element.rs       | 83 +++++++++++++++++-----
crates/gpui/playground/src/paint_context.rs | 54 +++-----------
crates/gpui/playground/src/playground.rs    |  9 +
crates/gpui/src/app.rs                      |  1 
crates/gpui/src/app/window.rs               | 36 +++++++++
crates/gpui/src/platform/event.rs           | 21 +++++
crates/gpui/src/platform/mac/event.rs       |  2 
crates/gpui/src/scene.rs                    | 31 ++++++++
crates/gpui/src/text_layout.rs              |  4 
10 files changed, 171 insertions(+), 78 deletions(-)

Detailed changes

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

@@ -4,7 +4,7 @@ use crate::{
     text::ArcCow,
     themes::rose_pine,
 };
-use gpui::ViewContext;
+use gpui::{platform::MouseButton, ViewContext};
 use playground_macros::Element;
 use std::{marker::PhantomData, rc::Rc};
 
@@ -69,7 +69,7 @@ impl<V: 'static, D: 'static> Button<V, D> {
 
     pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext<V>) + 'static) -> Self {
         let data = self.data.clone();
-        Element::left_click(self, move |view, _, cx| {
+        Element::click(self, MouseButton::Left, move |view, _, cx| {
             handler(view, data.as_ref(), cx);
         })
     }
@@ -89,7 +89,9 @@ impl<V: 'static, D: 'static> Button<V, D> {
 
         if let Some(handler) = self.handlers.click.clone() {
             let data = self.data.clone();
-            button.left_click(move |view, event, cx| handler(view, data.as_ref(), cx))
+            button.mouse_down(MouseButton::Left, move |view, event, cx| {
+                handler(view, data.as_ref(), cx)
+            })
         } else {
             button
         }

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

@@ -7,12 +7,12 @@ use anyhow::Result;
 pub use gpui::LayoutContext;
 use gpui::{
     geometry::{DefinedLength, Length},
-    platform::MouseButton,
-    scene::MouseClick,
-    EngineLayout, RenderContext, ViewContext, WindowContext,
+    platform::{MouseButton, MouseButtonEvent},
+    EngineLayout, EventContext, RenderContext, ViewContext,
 };
 use playground_macros::tailwind_lengths;
-use std::{any::Any, rc::Rc};
+use smallvec::SmallVec;
+use std::{any::Any, cell::Cell, rc::Rc};
 
 pub use crate::paint_context::PaintContext;
 pub use taffy::tree::NodeId;
@@ -23,7 +23,7 @@ pub struct Layout<'a, E: ?Sized> {
 }
 
 pub struct ElementHandlers<V> {
-    click: Option<Rc<dyn Fn(&mut V, MouseClick, &mut WindowContext, usize)>>,
+    mouse_button: SmallVec<[Rc<dyn Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>)>; 2]>,
 }
 
 pub struct ElementMetadata<V> {
@@ -42,7 +42,9 @@ impl<V> Default for ElementMetadata<V> {
 
 impl<V> Default for ElementHandlers<V> {
     fn default() -> Self {
-        ElementHandlers { click: None }
+        ElementHandlers {
+            mouse_button: Default::default(),
+        }
     }
 }
 
@@ -79,36 +81,62 @@ pub trait Element<V: 'static>: 'static {
         Adapter(self.into_any())
     }
 
-    fn left_click(self, handler: impl Fn(&mut V, MouseClick, &mut ViewContext<V>) + 'static) -> Self
+    fn click(
+        self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut ViewContext<V>) + 'static,
+    ) -> Self
     where
         Self: Sized,
     {
-        self.click(MouseButton::Left, handler)
+        let pressed: Rc<Cell<bool>> = Default::default();
+        self.mouse_down(button, {
+            let pressed = pressed.clone();
+            move |_, _, _| {
+                pressed.set(true);
+            }
+        })
+        .mouse_up(button, move |view, event, event_cx| {
+            if pressed.get() {
+                pressed.set(false);
+                handler(view, event, event_cx);
+            }
+        })
     }
 
-    fn right_click(
-        self,
-        handler: impl Fn(&mut V, MouseClick, &mut ViewContext<V>) + 'static,
+    fn mouse_down(
+        mut self,
+        button: MouseButton,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
     {
-        self.click(MouseButton::Right, handler)
+        self.handlers_mut()
+            .mouse_button
+            .push(Rc::new(move |view, event, event_cx| {
+                if event.button == button && event.is_down {
+                    handler(view, event, event_cx);
+                }
+            }));
+        self
     }
 
-    fn click(
+    fn mouse_up(
         mut self,
         button: MouseButton,
-        handler: impl Fn(&mut V, MouseClick, &mut ViewContext<V>) + 'static,
+        handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext<V>) + 'static,
     ) -> Self
     where
         Self: Sized,
     {
-        self.handlers_mut().click = Some(Rc::new(move |view, event, window_cx, view_id| {
-            if event.button == button {
-                handler(view, event, &mut ViewContext::mutable(window_cx, view_id));
-            }
-        }));
+        self.handlers_mut()
+            .mouse_button
+            .push(Rc::new(move |view, event, event_cx| {
+                if event.button == button && !event.is_down {
+                    handler(view, event, event_cx);
+                }
+            }));
         self
     }
 
@@ -385,7 +413,7 @@ pub struct AnyElement<V> {
     layout: Option<(NodeId, Box<dyn Any>)>,
 }
 
-impl<V> AnyElement<V> {
+impl<V: 'static> AnyElement<V> {
     pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
         let pushed_text_style = self.push_text_style(cx);
 
@@ -426,7 +454,20 @@ impl<V> AnyElement<V> {
             from_element: element_layout.as_mut(),
         };
 
-        if let Some(click_handler) = self.element.handlers_mut().click.clone() {}
+        for handler in self.element.handlers_mut().mouse_button.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);
+                },
+            );
+        }
 
         self.element.paint(layout, view, cx)?;
         if pushed_text_style {

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

@@ -1,11 +1,9 @@
-use std::{
-    any::{Any, TypeId},
-    collections::BTreeSet,
-    rc::Rc,
-};
+use std::{any::TypeId, rc::Rc};
 
 use derive_more::{Deref, DerefMut};
-use gpui::{geometry::rect::RectF, EventContext, RenderContext, ViewContext, WindowContext};
+use gpui::{
+    geometry::rect::RectF, scene::InteractiveRegion, EventContext, RenderContext, ViewContext,
+};
 pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
 pub use taffy::tree::NodeId;
 
@@ -15,7 +13,6 @@ pub struct PaintContext<'a, 'b, 'c, 'd, V> {
     #[deref_mut]
     pub(crate) legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
     pub(crate) scene: &'d mut gpui::SceneBuilder,
-    regions: BTreeSet<InteractiveRegion>,
 }
 
 impl<V> RenderContext for PaintContext<'_, '_, '_, '_, V> {
@@ -37,20 +34,17 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
         legacy_cx: &'d mut LegacyPaintContext<'a, 'b, 'c, V>,
         scene: &'d mut gpui::SceneBuilder,
     ) -> Self {
-        Self {
-            legacy_cx,
-            scene,
-            regions: BTreeSet::new(),
-        }
+        Self { legacy_cx, scene }
     }
 
-    pub fn paint_interactive<E: 'static>(
+    pub fn draw_interactive_region<E: 'static>(
         &mut self,
         order: u32,
         bounds: RectF,
-        handler: impl Fn(&mut V, E, &mut EventContext<V>) + 'static,
+        handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
     ) {
-        self.regions.insert(InteractiveRegion {
+        // We'll sort these by their order in `take_interactive_regions`.
+        self.scene.interactive_regions.push(InteractiveRegion {
             order,
             bounds,
             event_handler: Rc::new(move |view, event, window_cx, view_id| {
@@ -58,38 +52,12 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
                 let mut cx = EventContext::new(&mut cx);
                 handler(
                     view.downcast_mut().unwrap(),
-                    *event.downcast().unwrap(),
+                    event.downcast_ref().unwrap(),
                     &mut cx,
                 )
             }),
             event_type: TypeId::of::<E>(),
+            view_id: self.view_id(),
         });
     }
 }
-
-struct InteractiveRegion {
-    order: u32,
-    bounds: RectF,
-    event_handler: Rc<dyn Fn(&mut dyn Any, Box<dyn Any>, &mut WindowContext, usize)>,
-    event_type: TypeId,
-}
-
-impl Eq for InteractiveRegion {}
-
-impl PartialEq for InteractiveRegion {
-    fn eq(&self, other: &Self) -> bool {
-        self.order == other.order
-    }
-}
-
-impl PartialOrd for InteractiveRegion {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        todo!()
-    }
-}
-
-impl Ord for InteractiveRegion {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.order.cmp(&other.order)
-    }
-}

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

@@ -5,7 +5,7 @@ use element::Element;
 use frame::frame;
 use gpui::{
     geometry::{rect::RectF, vector::vec2f},
-    platform::WindowOptions,
+    platform::{MouseButton, WindowOptions},
 };
 use log::LevelFilter;
 use simplelog::SimpleLogger;
@@ -49,7 +49,12 @@ fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
         .h_full()
         .w_half()
         .fill(theme.success(0.5))
-        .child(button().label("Hello").click(|_, _, _| (println!("hey!"))))
+        .child(
+            button()
+                .label("Hello")
+                .mouse_up(MouseButton::Left, |_, _, _| (println!("up!")))
+                .mouse_down(MouseButton::Left, |_, _, _| (println!("down!"))),
+        )
 }
 
 //     todo!()

crates/gpui/src/app.rs 🔗

@@ -5216,6 +5216,7 @@ mod tests {
                     button: MouseButton::Left,
                     modifiers: Default::default(),
                     click_count: 1,
+                    is_down: true,
                 }),
                 false,
             );

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

@@ -8,8 +8,9 @@ use crate::{
         MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
     },
     scene::{
-        CursorRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag, MouseEvent,
-        MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
+        CursorRegion, InteractiveRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut,
+        MouseDrag, MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp,
+        MouseUpOut, Scene,
     },
     text_layout::TextLayoutCache,
     util::post_inc,
@@ -56,6 +57,7 @@ pub struct Window {
     appearance: Appearance,
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
+    interactive_regions: Vec<InteractiveRegion>,
     last_mouse_moved_event: Option<Event>,
     pub(crate) hovered_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region_ids: Vec<MouseRegionId>,
@@ -89,6 +91,7 @@ impl Window {
             rendered_views: Default::default(),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
+            interactive_regions: Vec::new(),
             text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
             last_mouse_moved_event: None,
             hovered_region_ids: Default::default(),
@@ -490,6 +493,8 @@ impl<'a> WindowContext<'a> {
     }
 
     pub(crate) fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
+        self.dispatch_to_interactive_regions(&event);
+
         let mut mouse_events = SmallVec::<[_; 2]>::new();
         let mut notified_views: HashSet<usize> = Default::default();
         let handle = self.window_handle;
@@ -867,6 +872,30 @@ impl<'a> WindowContext<'a> {
         any_event_handled
     }
 
+    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);
+
+            for region in interactive_regions.iter().rev() {
+                if region.event_type == mouse_event.type_id() {
+                    if region.bounds.contains_point(mouse_position) {
+                        self.update_any_view(region.view_id, |view, window_cx| {
+                            (region.event_handler)(
+                                view.as_any_mut(),
+                                mouse_event,
+                                window_cx,
+                                region.view_id,
+                            )
+                        });
+                    }
+                }
+            }
+
+            self.window.interactive_regions = interactive_regions;
+        }
+    }
+
     pub(crate) fn dispatch_key_down(&mut self, event: &KeyDownEvent) -> bool {
         let handle = self.window_handle;
         if let Some(focused_view_id) = self.window.focused_view_id {
@@ -1018,9 +1047,10 @@ impl<'a> WindowContext<'a> {
             .insert(root_view_id, rendered_root);
 
         self.window.text_layout_cache.finish_frame();
-        let scene = scene_builder.build();
+        let mut scene = scene_builder.build();
         self.window.cursor_regions = scene.cursor_regions();
         self.window.mouse_regions = scene.mouse_regions();
+        self.window.interactive_regions = scene.take_interactive_regions();
 
         if self.window_is_active() {
             if let Some(event) = self.window.last_mouse_moved_event.clone() {

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

@@ -1,4 +1,4 @@
-use std::ops::Deref;
+use std::{any::Any, ops::Deref};
 
 use pathfinder_geometry::vector::vec2f;
 
@@ -142,6 +142,7 @@ pub struct MouseButtonEvent {
     pub position: Vector2F,
     pub modifiers: Modifiers,
     pub click_count: usize,
+    pub is_down: bool,
 }
 
 impl Deref for MouseButtonEvent {
@@ -174,6 +175,7 @@ impl MouseMovedEvent {
             button: self.pressed_button.unwrap_or(button),
             modifiers: self.modifiers,
             click_count: 0,
+            is_down: self.pressed_button.is_some(),
         }
     }
 }
@@ -211,10 +213,25 @@ impl Event {
             Event::KeyDown { .. } => None,
             Event::KeyUp { .. } => None,
             Event::ModifiersChanged { .. } => None,
-            Event::MouseDown(event) | Event::MouseUp(event) => Some(event.position),
+            Event::MouseDown(event) => Some(event.position),
+            Event::MouseUp(event) => Some(event.position),
             Event::MouseMoved(event) => Some(event.position),
             Event::MouseExited(event) => Some(event.position),
             Event::ScrollWheel(event) => Some(event.position),
         }
     }
+
+    pub fn mouse_event<'a>(&'a self) -> Option<&'a dyn Any> {
+        match self {
+            Event::KeyDown { .. } => None,
+            Event::KeyUp { .. } => None,
+            Event::ModifiersChanged { .. } => None,
+            Event::MouseDown(event) => Some(event),
+            Event::MouseUp(event) => Some(event),
+            _ => None,
+            // Event::MouseMoved(event) => Some(event),
+            // Event::MouseExited(event) => Some(event),
+            // Event::ScrollWheel(event) => Some(event),
+        }
+    }
 }

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

@@ -132,6 +132,7 @@ impl Event {
                         ),
                         modifiers: read_modifiers(native_event),
                         click_count: native_event.clickCount() as usize,
+                        is_down: true,
                     })
                 })
             }
@@ -158,6 +159,7 @@ impl Event {
                         ),
                         modifiers: read_modifiers(native_event),
                         click_count: native_event.clickCount() as usize,
+                        is_down: false,
                     })
                 })
             }

crates/gpui/src/scene.rs 🔗

@@ -9,7 +9,12 @@ use schemars::JsonSchema;
 use serde::Deserialize;
 use serde_derive::Serialize;
 use serde_json::json;
-use std::{borrow::Cow, sync::Arc};
+use std::{
+    any::{Any, TypeId},
+    borrow::Cow,
+    rc::Rc,
+    sync::Arc,
+};
 
 use crate::{
     color::Color,
@@ -17,7 +22,7 @@ use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
     platform::{current::Surface, CursorStyle},
-    ImageData,
+    ImageData, WindowContext,
 };
 pub use mouse_event::*;
 pub use mouse_region::*;
@@ -26,6 +31,8 @@ pub struct SceneBuilder {
     scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
     active_stacking_context_stack: Vec<usize>,
+    /// Used by the playground crate.
+    pub interactive_regions: Vec<InteractiveRegion>,
     #[cfg(debug_assertions)]
     mouse_region_ids: HashSet<MouseRegionId>,
 }
@@ -33,6 +40,7 @@ pub struct SceneBuilder {
 pub struct Scene {
     scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
+    interactive_regions: Vec<InteractiveRegion>,
 }
 
 struct StackingContext {
@@ -273,6 +281,12 @@ impl Scene {
             })
             .collect()
     }
+
+    pub fn take_interactive_regions(&mut self) -> Vec<InteractiveRegion> {
+        self.interactive_regions
+            .sort_by(|a, b| a.order.cmp(&b.order));
+        std::mem::take(&mut self.interactive_regions)
+    }
 }
 
 impl SceneBuilder {
@@ -284,6 +298,7 @@ impl SceneBuilder {
             active_stacking_context_stack: vec![0],
             #[cfg(debug_assertions)]
             mouse_region_ids: Default::default(),
+            interactive_regions: Vec::new(),
         }
     }
 
@@ -293,6 +308,7 @@ impl SceneBuilder {
         Scene {
             scale_factor: self.scale_factor,
             stacking_contexts: self.stacking_contexts,
+            interactive_regions: self.interactive_regions,
         }
     }
 
@@ -689,6 +705,17 @@ impl MouseRegion {
     }
 }
 
+/// This is currently only used in the playground crate. It represents a region
+/// with which the user can interact via a pointing device. It aims to replace
+/// MouseRegion and CursorRegion.
+pub struct InteractiveRegion {
+    pub order: u32,
+    pub bounds: RectF,
+    pub event_handler: Rc<dyn Fn(&mut dyn Any, &dyn Any, &mut WindowContext, usize)>,
+    pub event_type: TypeId,
+    pub view_id: usize,
+}
+
 fn can_draw(bounds: RectF) -> bool {
     let size = bounds.size();
     size.x() > 0. && size.y() > 0.

crates/gpui/src/text_layout.rs 🔗

@@ -364,13 +364,13 @@ impl Line {
                         origin: glyph_origin,
                     });
                 } else {
-                    scene.push_glyph(dbg!(scene::Glyph {
+                    scene.push_glyph(scene::Glyph {
                         font_id: run.font_id,
                         font_size: self.layout.font_size,
                         id: glyph.id,
                         origin: glyph_origin,
                         color,
-                    }));
+                    });
                 }
             }
         }