diff --git a/crates/gpui/playground/src/components.rs b/crates/gpui/playground/src/components.rs index ba90fcb58f..eecbc3104b 100644 --- a/crates/gpui/playground/src/components.rs +++ b/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 Button { pub fn click(self, handler: impl Fn(&mut V, &D, &mut ViewContext) + '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 Button { 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 } diff --git a/crates/gpui/playground/src/element.rs b/crates/gpui/playground/src/element.rs index 78a203a7a9..c025838873 100644 --- a/crates/gpui/playground/src/element.rs +++ b/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 { - click: Option>, + mouse_button: SmallVec<[Rc)>; 2]>, } pub struct ElementMetadata { @@ -42,7 +42,9 @@ impl Default for ElementMetadata { impl Default for ElementHandlers { fn default() -> Self { - ElementHandlers { click: None } + ElementHandlers { + mouse_button: Default::default(), + } } } @@ -79,36 +81,62 @@ pub trait Element: 'static { Adapter(self.into_any()) } - fn left_click(self, handler: impl Fn(&mut V, MouseClick, &mut ViewContext) + 'static) -> Self - where - Self: Sized, - { - self.click(MouseButton::Left, handler) - } - - fn right_click( + fn click( self, - handler: impl Fn(&mut V, MouseClick, &mut ViewContext) + 'static, + button: MouseButton, + handler: impl Fn(&mut V, &MouseButtonEvent, &mut ViewContext) + 'static, ) -> Self where Self: Sized, { - self.click(MouseButton::Right, handler) + let pressed: Rc> = 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 click( + fn mouse_down( mut self, button: MouseButton, - handler: impl Fn(&mut V, MouseClick, &mut ViewContext) + 'static, + handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + '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 + } + + fn mouse_up( + mut self, + button: MouseButton, + handler: impl Fn(&mut V, &MouseButtonEvent, &mut EventContext) + 'static, + ) -> Self + where + Self: Sized, + { + 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 { layout: Option<(NodeId, Box)>, } -impl AnyElement { +impl AnyElement { pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext) -> Result { let pushed_text_style = self.push_text_style(cx); @@ -426,7 +454,20 @@ impl AnyElement { 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 { diff --git a/crates/gpui/playground/src/paint_context.rs b/crates/gpui/playground/src/paint_context.rs index 9190a38d38..5c5d12a82f 100644 --- a/crates/gpui/playground/src/paint_context.rs +++ b/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, } impl 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( + pub fn draw_interactive_region( &mut self, order: u32, bounds: RectF, - handler: impl Fn(&mut V, E, &mut EventContext) + 'static, + handler: impl Fn(&mut V, &E, &mut EventContext) + '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::(), + view_id: self.view_id(), }); } } - -struct InteractiveRegion { - order: u32, - bounds: RectF, - event_handler: Rc, &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 { - todo!() - } -} - -impl Ord for InteractiveRegion { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.order.cmp(&other.order) - } -} diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 2abb368ba8..f22a3d4041 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/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(theme: &ThemeColors) -> impl Element { .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!() diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 230f11f220..227a1d20ea 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -5216,6 +5216,7 @@ mod tests { button: MouseButton::Left, modifiers: Default::default(), click_count: 1, + is_down: true, }), false, ); diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index f2475b1372..459697d29d 100644 --- a/crates/gpui/src/app/window.rs +++ b/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, mouse_regions: Vec<(MouseRegion, usize)>, + interactive_regions: Vec, last_mouse_moved_event: Option, pub(crate) hovered_region_ids: Vec, pub(crate) clicked_region_ids: Vec, @@ -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 = 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() { diff --git a/crates/gpui/src/platform/event.rs b/crates/gpui/src/platform/event.rs index 4456db9a51..bb76bb995e 100644 --- a/crates/gpui/src/platform/event.rs +++ b/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), + } + } } diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index f25b27f067..7710834f53 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/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, }) }) } diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 593d42cc20..0e86e026f0 100644 --- a/crates/gpui/src/scene.rs +++ b/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, active_stacking_context_stack: Vec, + /// Used by the playground crate. + pub interactive_regions: Vec, #[cfg(debug_assertions)] mouse_region_ids: HashSet, } @@ -33,6 +40,7 @@ pub struct SceneBuilder { pub struct Scene { scale_factor: f32, stacking_contexts: Vec, + interactive_regions: Vec, } struct StackingContext { @@ -273,6 +281,12 @@ impl Scene { }) .collect() } + + pub fn take_interactive_regions(&mut self) -> Vec { + 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, + pub event_type: TypeId, + pub view_id: usize, +} + fn can_draw(bounds: RectF) -> bool { let size = bounds.size(); size.x() > 0. && size.y() > 0. diff --git a/crates/gpui/src/text_layout.rs b/crates/gpui/src/text_layout.rs index 7b79376434..d287d639ab 100644 --- a/crates/gpui/src/text_layout.rs +++ b/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, - })); + }); } } }