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::*;
Nathan Sobo created
crates/gpui3/src/elements.rs | 2
crates/gpui3/src/elements/interactive.rs | 152 +++++++++++++++++++
crates/gpui3/src/platform/mac/metal_atlas.rs | 1
crates/gpui3/src/platform/mac/metal_renderer.rs | 11
crates/gpui3/src/platform/mac/platform.rs | 2
crates/gpui3/src/scene.rs | 13
crates/gpui3/src/window.rs | 150 +++++++++++++++++-
7 files changed, 309 insertions(+), 22 deletions(-)
@@ -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::*;
@@ -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<S: 'static + Send + Sync> {
+ fn interaction_listeners(&mut self) -> &mut InteractionHandlers<S>;
+
+ fn on_mouse_down(
+ mut self,
+ button: MouseButton,
+ handler: impl Fn(&mut S, &MouseDownEvent, &mut ViewContext<S>) + 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<S>) + 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<S>) + 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<S>) + 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<S>)
+ + 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<V> = Arc<
+ dyn Fn(&mut V, &MouseDownEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static,
+>;
+type MouseUpHandler<V> =
+ Arc<dyn Fn(&mut V, &MouseUpEvent, DispatchPhase, &mut ViewContext<V>) + Send + Sync + 'static>;
+
+pub struct InteractionHandlers<V: 'static> {
+ mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
+ mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
+}
+
+impl<S: Send + Sync + 'static> InteractionHandlers<S> {
+ pub fn paint(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<S>) {
+ 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<V> Default for InteractionHandlers<V> {
+ fn default() -> Self {
+ Self {
+ mouse_down: Default::default(),
+ mouse_up: Default::default(),
+ }
+ }
+}
@@ -27,6 +27,7 @@ impl MetalAtlas {
self.0.lock().texture(id).metal_texture.clone()
}
+ #[allow(dead_code)]
pub(crate) fn allocate(
&self,
size: Size<DevicePixels>,
@@ -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<ScaledPixels>],
- offset: &mut usize,
- command_buffer: &metal::CommandBufferRef,
+ _offset: &mut usize,
+ _command_buffer: &metal::CommandBufferRef,
) -> HashMap<PathId, AtlasTile> {
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::<PathVertex<ScaledPixels>>();
@@ -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;
}
}
@@ -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<f32>;
-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<ScaledPixels>] {
&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() {
@@ -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<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
pub struct Window {
handle: AnyWindowHandle,
@@ -25,9 +50,11 @@ pub struct Window {
content_size: Size<Pixels>,
layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView<()>>,
- mouse_position: Point<Pixels>,
current_stacking_order: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
+ mouse_event_handlers: HashMap<TypeId, Vec<(StackingOrder, MouseEventHandler)>>,
+ propagate_event: bool,
+ mouse_position: Point<Pixels>,
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<Event: 'static>(
+ &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::<Event>())
+ .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<Pixels> {
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<Event: 'static>(
+ &mut self,
+ handler: impl Fn(&mut S, &Event, DispatchPhase, &mut ViewContext<S>) + 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<R>(&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<SmallVec<[u32; 16]>> for StackingOrder {
+ fn from(small_vec: SmallVec<[u32; 16]>) -> Self {
+ StackingOrder(small_vec)
+ }
+}