diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs index c025838873..af99bea925 100644 --- a/crates/gpui/playground/src/element.rs +++ b/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 { - mouse_button: SmallVec<[Rc)>; 2]>, -} - pub struct ElementMetadata { pub style: ElementStyle, - pub handlers: ElementHandlers, + pub handlers: Vec>, +} + +pub struct EventHandler { + handler: Rc)>, + event_type: TypeId, + outside_bounds: bool, +} + +impl Clone for EventHandler { + fn clone(&self) -> Self { + Self { + handler: self.handler.clone(), + event_type: self.event_type, + outside_bounds: self.outside_bounds, + } + } } impl Default for ElementMetadata { fn default() -> Self { Self { style: ElementStyle::default(), - handlers: ElementHandlers::default(), - } - } -} - -impl Default for ElementHandlers { - fn default() -> Self { - ElementHandlers { - mouse_button: Default::default(), + handlers: Vec::new(), } } } @@ -52,7 +59,8 @@ pub trait Element: 'static { type Layout: 'static; fn style_mut(&mut self) -> &mut ElementStyle; - fn handlers_mut(&mut self) -> &mut ElementHandlers; + fn handlers_mut(&mut self) -> &mut Vec>; + fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result<(NodeId, Self::Layout)>; fn paint<'a>( @@ -96,6 +104,12 @@ pub trait Element: '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: '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::().unwrap(); if event.button == button && event.is_down { handler(view, event, event_cx); } - })); + }), + event_type: TypeId::of::(), + outside_bounds: false, + }); + self + } + + fn mouse_down_outside( + mut self, + button: MouseButton, + handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.handlers_mut().push(EventHandler { + handler: Rc::new(move |view, event, event_cx| { + let event = event.downcast_ref::().unwrap(); + if event.button == button && event.is_down { + handler(view, event, event_cx); + } + }), + event_type: TypeId::of::(), + outside_bounds: true, + }); self } @@ -130,13 +168,37 @@ pub trait Element: '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::().unwrap(); if event.button == button && !event.is_down { handler(view, event, event_cx); } - })); + }), + event_type: TypeId::of::(), + outside_bounds: false, + }); + self + } + + fn mouse_up_outside( + mut self, + button: MouseButton, + handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self + where + Self: Sized, + { + self.handlers_mut().push(EventHandler { + handler: Rc::new(move |view, event, event_cx| { + let event = event.downcast_ref::().unwrap(); + if event.button == button && !event.is_down { + handler(view, event, event_cx); + } + }), + event_type: TypeId::of::(), + outside_bounds: true, + }); self } @@ -362,7 +424,7 @@ pub trait Element: 'static { // Object-safe counterpart of Element used by AnyElement to store elements as trait objects. trait ElementObject { fn style_mut(&mut self) -> &mut ElementStyle; - fn handlers_mut(&mut self) -> &mut ElementHandlers; + fn handlers_mut(&mut self) -> &mut Vec>; fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result<(NodeId, Box)>; fn paint( @@ -378,7 +440,7 @@ impl> ElementObject for E { Element::style_mut(self) } - fn handlers_mut(&mut self) -> &mut ElementHandlers { + fn handlers_mut(&mut self) -> &mut Vec> { Element::handlers_mut(self) } @@ -454,19 +516,27 @@ impl AnyElement { 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 Element for AnyElement { self.element.style_mut() } - fn handlers_mut(&mut self) -> &mut ElementHandlers { + fn handlers_mut(&mut self) -> &mut Vec> { self.element.handlers_mut() } diff --git a/crates/gpui/playground/src/frame.rs b/crates/gpui/playground/src/frame.rs index 4b32bc33d6..8749d86199 100644 --- a/crates/gpui/playground/src/frame.rs +++ b/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 { style: ElementStyle, - handlers: ElementHandlers, + handlers: Vec>, children: Vec>, } pub fn frame() -> Frame { Frame { style: ElementStyle::default(), - handlers: ElementHandlers::default(), + handlers: Vec::new(), children: Vec::new(), } } @@ -32,7 +31,7 @@ impl Element for Frame { &mut self.style } - fn handlers_mut(&mut self) -> &mut ElementHandlers { + fn handlers_mut(&mut self) -> &mut Vec> { &mut self.handlers } diff --git a/crates/gpui/playground/src/paint_context.rs b/crates/gpui/playground/src/paint_context.rs index 5c5d12a82f..03d1e033bc 100644 --- a/crates/gpui/playground/src/paint_context.rs +++ b/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) + '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); diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index f22a3d4041..6549171792 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -49,12 +49,7 @@ fn playground(theme: &ThemeColors) -> impl Element { .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!() diff --git a/crates/gpui/playground/src/text.rs b/crates/gpui/playground/src/text.rs index 876edc0a3c..0c67eb1c2a 100644 --- a/crates/gpui/playground/src/text.rs +++ b/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 Element for Text { &mut self.metadata.style } - fn handlers_mut(&mut self) -> &mut crate::element::ElementHandlers { - &mut self.metadata.handlers - } - fn layout( &mut self, view: &mut V, @@ -95,6 +91,10 @@ impl Element for Text { ); Ok(()) } + + fn handlers_mut(&mut self) -> &mut Vec> { + &mut self.metadata.handlers + } } pub struct TextLayout { diff --git a/crates/gpui/playground_macros/src/derive_element.rs b/crates/gpui/playground_macros/src/derive_element.rs index 0d436c95af..6d5922c621 100644 --- a/crates/gpui/playground_macros/src/derive_element.rs +++ b/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 { + fn handlers_mut(&mut self) -> &mut Vec<#crate_name::element::EventHandler> { &mut self.metadata.handlers } diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 459697d29d..85528f8987 100644 --- a/crates/gpui/src/app/window.rs +++ b/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 { + 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(), diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 0e86e026f0..7b6c98d8e7 100644 --- a/crates/gpui/src/scene.rs +++ b/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, pub event_type: TypeId, pub view_id: usize,