diff --git a/crates/gpui3/src/elements.rs b/crates/gpui3/src/elements.rs index 5bd95ccebb69bdc6ac536d5bb89d990ae66aafb1..98f448138a215475e7db972d6746f5c97f5d18fe 100644 --- a/crates/gpui3/src/elements.rs +++ b/crates/gpui3/src/elements.rs @@ -1,11 +1,13 @@ mod div; mod img; +mod interactive; mod stateless; mod svg; mod text; pub use div::*; pub use img::*; +pub use interactive::*; pub use stateless::*; pub use svg::*; pub use text::*; diff --git a/crates/gpui3/src/elements/interactive.rs b/crates/gpui3/src/elements/interactive.rs new file mode 100644 index 0000000000000000000000000000000000000000..dcdfa0fc92471025fb1584883597786bf37d10f7 --- /dev/null +++ b/crates/gpui3/src/elements/interactive.rs @@ -0,0 +1,152 @@ +use crate::{ + Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseUpEvent, Pixels, ViewContext, +}; +use parking_lot::Mutex; +use smallvec::SmallVec; +use std::sync::Arc; + +pub trait Interactive { + fn interaction_listeners(&mut self) -> &mut InteractionHandlers; + + fn on_mouse_down( + mut self, + button: MouseButton, + handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext) + Send + Sync + 'static, + ) -> Self + where + Self: Sized, + { + self.interaction_listeners() + .mouse_down + .push(Arc::new(move |view, event, phase, cx| { + if phase == DispatchPhase::Bubble && event.button == button { + handler(view, event, cx) + } + })); + self + } + + fn on_mouse_up( + mut self, + button: MouseButton, + handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext) + Send + Sync + 'static, + ) -> Self + where + Self: Sized, + { + self.interaction_listeners() + .mouse_up + .push(Arc::new(move |view, event, phase, cx| { + if phase == DispatchPhase::Bubble && event.button == button { + handler(view, event, cx) + } + })); + self + } + + fn on_mouse_down_out( + mut self, + button: MouseButton, + handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext) + Send + Sync + 'static, + ) -> Self + where + Self: Sized, + { + self.interaction_listeners() + .mouse_down + .push(Arc::new(move |view, event, phase, cx| { + if phase == DispatchPhase::Capture && event.button == button { + handler(view, event, cx) + } + })); + self + } + + fn on_mouse_up_out( + mut self, + button: MouseButton, + handler: impl Fn(&mut S, &MouseUpEvent, &mut ViewContext) + Send + Sync + 'static, + ) -> Self + where + Self: Sized, + { + self.interaction_listeners() + .mouse_up + .push(Arc::new(move |view, event, phase, cx| { + if event.button == button && phase == DispatchPhase::Capture { + handler(view, event, cx); + } + })); + self + } + + fn on_click( + self, + button: MouseButton, + handler: impl Fn(&mut S, &MouseDownEvent, &MouseUpEvent, &mut ViewContext) + + Send + + Sync + + 'static, + ) -> Self + where + Self: Sized, + { + let down_event = Arc::new(Mutex::new(None)); + self.on_mouse_down(button, { + let down_event = down_event.clone(); + move |_, event, _| { + down_event.lock().replace(event.clone()); + } + }) + .on_mouse_up_out(button, { + let down_event = down_event.clone(); + move |_, _, _| { + down_event.lock().take(); + } + }) + .on_mouse_up(button, move |view, event, cx| { + if let Some(down_event) = down_event.lock().take() { + handler(view, &down_event, event, cx); + } + }) + } +} + +type MouseDownHandler = Arc< + dyn Fn(&mut V, &MouseDownEvent, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, +>; +type MouseUpHandler = + Arc) + Send + Sync + 'static>; + +pub struct InteractionHandlers { + mouse_down: SmallVec<[MouseDownHandler; 2]>, + mouse_up: SmallVec<[MouseUpHandler; 2]>, +} + +impl InteractionHandlers { + pub fn paint(&self, bounds: Bounds, cx: &mut ViewContext) { + for handler in self.mouse_down.iter().cloned() { + cx.on_mouse_event(move |view, event: &MouseDownEvent, phase, cx| { + if bounds.contains_point(event.position) { + handler(view, event, phase, cx); + } + }) + } + for handler in self.mouse_up.iter().cloned() { + cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| { + if bounds.contains_point(event.position) { + handler(view, event, phase, cx); + } + }) + } + } +} + +impl Default for InteractionHandlers { + fn default() -> Self { + Self { + mouse_down: Default::default(), + mouse_up: Default::default(), + } + } +} diff --git a/crates/gpui3/src/platform/mac/metal_atlas.rs b/crates/gpui3/src/platform/mac/metal_atlas.rs index f6392d2fc0570054dcdd922b753365ba058b3d8a..6e7baf076320e0a252bc663ab7d5b6a7e0fecd37 100644 --- a/crates/gpui3/src/platform/mac/metal_atlas.rs +++ b/crates/gpui3/src/platform/mac/metal_atlas.rs @@ -27,6 +27,7 @@ impl MetalAtlas { self.0.lock().texture(id).metal_texture.clone() } + #[allow(dead_code)] pub(crate) fn allocate( &self, size: Size, diff --git a/crates/gpui3/src/platform/mac/metal_renderer.rs b/crates/gpui3/src/platform/mac/metal_renderer.rs index 4dd3250f08c4b0acdd11189e61273c13e71942a9..187f1b82ce0879e10e53024655e06b7b82546a15 100644 --- a/crates/gpui3/src/platform/mac/metal_renderer.rs +++ b/crates/gpui3/src/platform/mac/metal_renderer.rs @@ -172,7 +172,7 @@ impl MetalRenderer { let command_buffer = command_queue.new_command_buffer(); let mut instance_offset = 0; - let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer); + // let path_tiles = self.rasterize_paths(scene.paths(), &mut instance_offset, &command_buffer); let render_pass_descriptor = metal::RenderPassDescriptor::new(); let color_attachment = render_pass_descriptor @@ -208,7 +208,7 @@ impl MetalRenderer { PrimitiveBatch::Quads(quads) => { self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder); } - PrimitiveBatch::Paths(paths) => { + PrimitiveBatch::Paths(_paths) => { // self.draw_paths(paths, &mut instance_offset, viewport_size, command_encoder); } PrimitiveBatch::Underlines(underlines) => { @@ -258,11 +258,12 @@ impl MetalRenderer { drawable.present(); } + #[allow(dead_code)] fn rasterize_paths( &mut self, paths: &[Path], - offset: &mut usize, - command_buffer: &metal::CommandBufferRef, + _offset: &mut usize, + _command_buffer: &metal::CommandBufferRef, ) -> HashMap { let mut tiles = HashMap::default(); let mut vertices_by_texture_id = HashMap::default(); @@ -288,7 +289,7 @@ impl MetalRenderer { tiles.insert(path.id, tile); } - for (texture_id, vertices) in vertices_by_texture_id { + for (_texture_id, _vertices) in vertices_by_texture_id { todo!(); // align_offset(offset); // let next_offset = *offset + vertices.len() * mem::size_of::>(); diff --git a/crates/gpui3/src/platform/mac/platform.rs b/crates/gpui3/src/platform/mac/platform.rs index a3f6fbfbe693d2902320bb892e2868dde146990f..f2d21c7068c3b34c252c6e0ffc8bf69f613cd40d 100644 --- a/crates/gpui3/src/platform/mac/platform.rs +++ b/crates/gpui3/src/platform/mac/platform.rs @@ -923,7 +923,7 @@ extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { if let Some(event) = Event::from_native(native_event, None) { let platform = get_foreground_platform(this); if let Some(callback) = platform.0.lock().event.as_mut() { - if callback(event) { + if !callback(event) { return; } } diff --git a/crates/gpui3/src/scene.rs b/crates/gpui3/src/scene.rs index aa5bf002ddc9a1aebdf0d62c6c1a38c776d344c2..19948b9ffa68f9a137fc1be50a845b68a1210a7c 100644 --- a/crates/gpui3/src/scene.rs +++ b/crates/gpui3/src/scene.rs @@ -1,17 +1,17 @@ use crate::{ point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point, - ScaledPixels, + ScaledPixels, StackingOrder, }; use collections::BTreeMap; use etagere::euclid::{Point3D, Vector3D}; use plane_split::{BspSplitter, Polygon as BspPolygon}; -use smallvec::SmallVec; use std::{fmt::Debug, iter::Peekable, mem, slice}; // Exported to metal pub type PointF = Point; -pub type StackingOrder = SmallVec<[u32; 16]>; + pub type LayerId = u32; + pub type DrawOrder = u32; pub(crate) struct SceneBuilder { @@ -180,6 +180,7 @@ pub(crate) struct Scene { } impl Scene { + #[allow(dead_code)] pub fn paths(&self) -> &[Path] { &self.paths } @@ -731,9 +732,9 @@ mod tests { let mut scene = SceneBuilder::new(); assert_eq!(scene.layers_by_order.len(), 0); - scene.insert(&smallvec![1], quad()); - scene.insert(&smallvec![2], shadow()); - scene.insert(&smallvec![3], quad()); + scene.insert(&smallvec![1].into(), quad()); + scene.insert(&smallvec![2].into(), shadow()); + scene.insert(&smallvec![3].into(), quad()); let mut batches_count = 0; for _ in scene.build().batches() { diff --git a/crates/gpui3/src/window.rs b/crates/gpui3/src/window.rs index 96b97427afa39c0393d97c5e496b39c037d43e20..0aa5584d9bb7e3218271378c9999c61d49a927e8 100644 --- a/crates/gpui3/src/window.rs +++ b/crates/gpui3/src/window.rs @@ -1,20 +1,45 @@ use crate::{ image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, - Edges, Effect, Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, - MainThread, MainThreadOnly, MonochromeSprite, Path, Pixels, PlatformAtlas, PlatformWindow, - Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, - SceneBuilder, Shadow, SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, + Edges, Effect, Element, EntityId, Event, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, + LayoutId, MainThread, MainThreadOnly, MonochromeSprite, Path, Pixels, PlatformAtlas, + PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderSvgParams, + ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS, }; use anyhow::Result; +use collections::HashMap; +use derive_more::{Deref, DerefMut}; use smallvec::SmallVec; use std::{ - any::TypeId, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, mem, sync::Arc, + any::{Any, TypeId}, + borrow::Cow, + fmt::Debug, + future::Future, + marker::PhantomData, + mem, + sync::Arc, }; use util::ResultExt; -pub struct AnyWindow {} +#[derive(Deref, DerefMut, Ord, PartialOrd, Eq, PartialEq, Clone, Default)] +pub struct StackingOrder(pub(crate) SmallVec<[u32; 16]>); + +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)] +pub enum DispatchPhase { + /// After the capture phase comes the bubble phase, in which event handlers are + /// invoked front to back. This is the phase you'll usually want to use for event handlers. + #[default] + Bubble, + /// During the initial capture phase, event handlers are invoked back to front. This phase + /// is used for special purposes such as clearing the "pressed" state for click events. If + /// you stop event propagation during this phase, you need to know what you're doing. Handlers + /// outside of the immediate region may rely on detecting non-local events during this phase. + Capture, +} + +type MouseEventHandler = + Arc; pub struct Window { handle: AnyWindowHandle, @@ -25,9 +50,11 @@ pub struct Window { content_size: Size, layout_engine: TaffyLayoutEngine, pub(crate) root_view: Option>, - mouse_position: Point, current_stacking_order: StackingOrder, content_mask_stack: Vec>, + mouse_event_handlers: HashMap>, + propagate_event: bool, + mouse_position: Point, scale_factor: f32, pub(crate) scene_builder: SceneBuilder, pub(crate) dirty: bool, @@ -46,7 +73,6 @@ impl Window { let content_size = platform_window.content_size(); let scale_factor = platform_window.scale_factor(); platform_window.on_resize(Box::new({ - let handle = handle; let cx = cx.to_async(); move |content_size, scale_factor| { cx.update_window(handle, |cx| { @@ -65,6 +91,15 @@ impl Window { } })); + platform_window.on_event({ + let cx = cx.to_async(); + Box::new(move |event| { + cx.update_window(handle, |cx| cx.dispatch_event(event)) + .log_err() + .unwrap_or(true) + }) + }); + let platform_window = MainThreadOnly::new(Arc::new(platform_window), cx.executor.clone()); Window { @@ -76,9 +111,11 @@ impl Window { content_size, layout_engine: TaffyLayoutEngine::new(), root_view: None, - mouse_position, - current_stacking_order: SmallVec::new(), + current_stacking_order: StackingOrder(SmallVec::new()), content_mask_stack: Vec::new(), + mouse_event_handlers: HashMap::default(), + propagate_event: true, + mouse_position, scale_factor, scene_builder: SceneBuilder::new(), dirty: true, @@ -241,6 +278,27 @@ impl<'a, 'w> WindowContext<'a, 'w> { self.window.rem_size } + pub fn stop_event_propagation(&mut self) { + self.window.propagate_event = false; + } + + pub fn on_mouse_event( + &mut self, + handler: impl Fn(&Event, DispatchPhase, &mut WindowContext) + Send + Sync + 'static, + ) { + let order = self.window.current_stacking_order.clone(); + self.window + .mouse_event_handlers + .entry(TypeId::of::()) + .or_default() + .push(( + order, + Arc::new(move |event: &dyn Any, phase, cx| { + handler(event.downcast_ref().unwrap(), phase, cx) + }), + )) + } + pub fn mouse_position(&self) -> Point { self.window.mouse_position } @@ -529,6 +587,11 @@ impl<'a, 'w> WindowContext<'a, 'w> { pub(crate) fn draw(&mut self) -> Result<()> { let unit_entity = self.unit_entity.clone(); self.update_entity(&unit_entity, |view, cx| { + cx.window + .mouse_event_handlers + .values_mut() + .for_each(Vec::clear); + let mut root_view = cx.window.root_view.take().unwrap(); let (root_layout_id, mut frame_state) = root_view.layout(&mut (), cx)?; let available_space = cx.window.content_size.map(Into::into); @@ -554,6 +617,54 @@ impl<'a, 'w> WindowContext<'a, 'w> { Ok(()) }) } + + fn dispatch_event(&mut self, event: Event) -> bool { + if let Some(any_mouse_event) = event.mouse_event() { + if let Some(mut handlers) = self + .window + .mouse_event_handlers + .remove(&any_mouse_event.type_id()) + { + // We sort these every time, because handlers may add handlers. Probably fast enough. + handlers.sort_by(|(a, _), (b, _)| a.cmp(b)); + + // Handlers may set this to false by calling `stop_propagation`; + self.window.propagate_event = true; + + // Capture phase, events bubble from back to front. Handlers for this phase are used for + // special purposes, such as detecting events outside of a given Bounds. + for (_, handler) in &handlers { + handler(any_mouse_event, DispatchPhase::Capture, self); + if !self.window.propagate_event { + break; + } + } + + // Bubble phase + if self.window.propagate_event { + for (_, handler) in handlers.iter().rev() { + handler(any_mouse_event, DispatchPhase::Bubble, self); + if !self.window.propagate_event { + break; + } + } + } + + handlers.extend( + self.window + .mouse_event_handlers + .get_mut(&any_mouse_event.type_id()) + .into_iter() + .flat_map(|handlers| handlers.drain(..)), + ); + self.window + .mouse_event_handlers + .insert(any_mouse_event.type_id(), handlers); + } + } + + true + } } impl Context for WindowContext<'_, '_> { @@ -786,6 +897,18 @@ impl<'a, 'w, S: Send + Sync + 'static> ViewContext<'a, 'w, S> { }) } + pub fn on_mouse_event( + &mut self, + handler: impl Fn(&mut S, &Event, DispatchPhase, &mut ViewContext) + Send + Sync + 'static, + ) { + let handle = self.handle().upgrade(self).unwrap(); + self.window_cx.on_mouse_event(move |event, phase, cx| { + handle.update(cx, |view, cx| { + handler(view, event, phase, cx); + }) + }); + } + pub(crate) fn erase_state(&mut self, f: impl FnOnce(&mut ViewContext<()>) -> R) -> R { let entity_id = self.unit_entity.id; let mut cx = ViewContext::mutable( @@ -874,3 +997,10 @@ pub struct AnyWindowHandle { pub(crate) id: WindowId, state_type: TypeId, } + +#[cfg(any(test, feature = "test"))] +impl From> for StackingOrder { + fn from(small_vec: SmallVec<[u32; 16]>) -> Self { + StackingOrder(small_vec) + } +}