Merge branch 'gpui2' into marshall/gpui2-playground

Marshall Bowers created

Change summary

crates/gpui3/build.rs                           |   5 
crates/gpui3/src/element.rs                     |  10 
crates/gpui3/src/elements.rs                    |   2 
crates/gpui3/src/elements/div.rs                |  34 
crates/gpui3/src/elements/hoverable.rs          | 114 ++--
crates/gpui3/src/elements/img.rs                |   6 
crates/gpui3/src/elements/pressable.rs          |   2 
crates/gpui3/src/elements/svg.rs                |   4 
crates/gpui3/src/events.rs                      | 135 ++---
crates/gpui3/src/geometry.rs                    |   6 
crates/gpui3/src/gpui3.rs                       |   4 
crates/gpui3/src/interactive.rs                 | 230 ++++++++++
crates/gpui3/src/platform.rs                    |  42 +
crates/gpui3/src/platform/mac/events.rs         |  18 
crates/gpui3/src/platform/mac/metal_atlas.rs    | 155 ++++-
crates/gpui3/src/platform/mac/metal_renderer.rs | 270 +++++++++++
crates/gpui3/src/platform/mac/platform.rs       |   2 
crates/gpui3/src/platform/mac/shaders.metal     |  88 +++
crates/gpui3/src/platform/mac/window.rs         |  12 
crates/gpui3/src/scene.rs                       | 434 +++++++++++++++---
crates/gpui3/src/style.rs                       |  59 -
crates/gpui3/src/style_helpers.rs               |   2 
crates/gpui3/src/styled.rs                      |  14 
crates/gpui3/src/window.rs                      | 304 ++++++++++--
crates/storybook2/src/collab_panel.rs           |  10 
25 files changed, 1,519 insertions(+), 443 deletions(-)

Detailed changes

crates/gpui3/build.rs 🔗

@@ -47,9 +47,11 @@ fn generate_shader_bindings() -> PathBuf {
         "Pixels".into(),
         "PointF".into(),
         "Hsla".into(),
-        "ScaledContentMask".into(),
+        "ContentMask".into(),
         "Uniforms".into(),
         "AtlasTile".into(),
+        "PathRasterizationInputIndex".into(),
+        "PathVertex_ScaledPixels".into(),
         "ShadowInputIndex".into(),
         "Shadow".into(),
         "QuadInputIndex".into(),
@@ -59,6 +61,7 @@ fn generate_shader_bindings() -> PathBuf {
         "SpriteInputIndex".into(),
         "MonochromeSprite".into(),
         "PolychromeSprite".into(),
+        "PathSprite".into(),
     ]);
     config.no_includes = true;
     config.enumeration.prefix_with_name = true;

crates/gpui3/src/element.rs 🔗

@@ -22,10 +22,12 @@ pub trait Element: 'static {
     ) -> Result<()>;
 }
 
-pub trait ParentElement<S> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]>;
+pub trait ParentElement {
+    type State;
+
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]>;
 
-    fn child(mut self, child: impl IntoAnyElement<S>) -> Self
+    fn child(mut self, child: impl IntoAnyElement<Self::State>) -> Self
     where
         Self: Sized,
     {
@@ -33,7 +35,7 @@ pub trait ParentElement<S> {
         self
     }
 
-    fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<S>>) -> Self
+    fn children(mut self, iter: impl IntoIterator<Item = impl IntoAnyElement<Self::State>>) -> Self
     where
         Self: Sized,
     {

crates/gpui3/src/elements.rs 🔗

@@ -1,10 +1,12 @@
 mod div;
+mod hoverable;
 mod img;
 mod stateless;
 mod svg;
 mod text;
 
 pub use div::*;
+pub use hoverable::*;
 pub use img::*;
 pub use stateless::*;
 pub use svg::*;

crates/gpui3/src/elements/div.rs 🔗

@@ -1,6 +1,7 @@
 use crate::{
-    AnyElement, Bounds, Element, LayoutId, Overflow, ParentElement, Pixels, Point, Refineable,
-    RefinementCascade, Result, Style, StyleHelpers, Styled, ViewContext,
+    AnyElement, Bounds, Element, Interactive, LayoutId, MouseEventListeners, Overflow,
+    ParentElement, Pixels, Point, Refineable, RefinementCascade, Result, Style, Styled,
+    ViewContext,
 };
 use parking_lot::Mutex;
 use smallvec::SmallVec;
@@ -9,7 +10,7 @@ use util::ResultExt;
 
 pub struct Div<S: 'static> {
     styles: RefinementCascade<Style>,
-    // handlers: InteractionHandlers<V>,
+    listeners: MouseEventListeners<S>,
     children: SmallVec<[AnyElement<S>; 2]>,
     scroll_state: Option<ScrollState>,
 }
@@ -17,7 +18,7 @@ pub struct Div<S: 'static> {
 pub fn div<S>() -> Div<S> {
     Div {
         styles: Default::default(),
-        // handlers: Default::default(),
+        listeners: Default::default(),
         children: Default::default(),
         scroll_state: None,
     }
@@ -42,7 +43,7 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
         &mut self,
         bounds: Bounds<Pixels>,
         state: &mut S,
-        child_layouts: &mut Self::FrameState,
+        child_layout_ids: &mut Self::FrameState,
         cx: &mut ViewContext<S>,
     ) -> Result<()> {
         let style = self.computed_style();
@@ -52,10 +53,13 @@ impl<S: 'static + Send + Sync> Element for Div<S> {
         let overflow = &style.overflow;
         style.apply_text_style(cx, |cx| {
             cx.stack(z_index + 1, |cx| {
-                style.apply_overflow(bounds, cx, |cx| self.paint_children(overflow, state, cx))
+                style.apply_overflow(bounds, cx, |cx| {
+                    self.listeners.paint(bounds, cx);
+                    self.paint_children(overflow, state, cx)
+                })
             })
         })?;
-        self.handle_scroll(bounds, style.overflow.clone(), child_layouts, cx);
+        self.handle_scroll(bounds, style.overflow.clone(), child_layout_ids, cx);
 
         // todo!("enable inspector")
         // if cx.is_inspector_enabled() {
@@ -251,16 +255,16 @@ impl<V> Styled for Div<V> {
     }
 }
 
-impl<V> StyleHelpers for Div<V> {}
+impl<V: Send + Sync + 'static> Interactive<V> for Div<V> {
+    fn listeners(&mut self) -> &mut MouseEventListeners<V> {
+        &mut self.listeners
+    }
+}
 
-// impl<V> Interactive<V> for Div<V> {
-//     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
-//         &mut self.handlers
-//     }
-// }
+impl<S: 'static> ParentElement for Div<S> {
+    type State = S;
 
-impl<V: 'static> ParentElement<V> for Div<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<S>; 2]> {
         &mut self.children
     }
 }

crates/gpui3/src/elements/hoverable.rs 🔗

@@ -1,105 +1,97 @@
 use crate::{
-    element::{AnyElement, Element, IntoElement, Layout, ParentElement},
-    interactive::{InteractionHandlers, Interactive},
-    style::{Style, StyleHelpers, Styleable},
-    ViewContext,
+    AnyElement, Bounds, DispatchPhase, Element, Interactive, MouseEventListeners, MouseMoveEvent,
+    ParentElement, Pixels, Styled, ViewContext,
 };
 use anyhow::Result;
-use gpui::{geometry::vector::Vector2F, platform::MouseMovedEvent, LayoutId};
 use refineable::{CascadeSlot, Refineable, RefinementCascade};
 use smallvec::SmallVec;
-use std::{cell::Cell, rc::Rc};
+use std::sync::{
+    atomic::{AtomicBool, Ordering::SeqCst},
+    Arc,
+};
 
-pub struct Hoverable<E: Styleable> {
-    hovered: Rc<Cell<bool>>,
+pub struct Hoverable<E: Styled> {
+    hovered: Arc<AtomicBool>,
     cascade_slot: CascadeSlot,
     hovered_style: <E::Style as Refineable>::Refinement,
     child: E,
 }
 
-pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
-    Hoverable {
-        hovered: Rc::new(Cell::new(false)),
-        cascade_slot: child.style_cascade().reserve(),
-        hovered_style: Default::default(),
-        child,
+impl<E: Styled> Hoverable<E> {
+    pub fn new(mut child: E) -> Self {
+        Self {
+            hovered: Arc::new(AtomicBool::new(false)),
+            cascade_slot: child.style_cascade().reserve(),
+            hovered_style: Default::default(),
+            child,
+        }
     }
 }
 
-impl<E: Styleable> Styleable for Hoverable<E> {
+impl<E> Styled for Hoverable<E>
+where
+    E: Styled,
+{
     type Style = E::Style;
 
-    fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
+    fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
         self.child.style_cascade()
     }
 
-    fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
+    fn declared_style(&mut self) -> &mut <Self::Style as refineable::Refineable>::Refinement {
         &mut self.hovered_style
     }
 }
 
-impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
-    type PaintState = E::PaintState;
+impl<S: 'static + Send + Sync, E: Interactive<S> + Styled> Interactive<S> for Hoverable<E> {
+    fn listeners(&mut self) -> &mut MouseEventListeners<S> {
+        self.child.listeners()
+    }
+}
+
+impl<E: Element + Styled> Element for Hoverable<E> {
+    type State = E::State;
+    type FrameState = E::FrameState;
 
     fn layout(
         &mut self,
-        view: &mut V,
-        cx: &mut ViewContext<V>,
-    ) -> Result<(LayoutId, Self::PaintState)>
-    where
-        Self: Sized,
-    {
-        Ok(self.child.layout(view, cx)?)
+        state: &mut Self::State,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<(crate::LayoutId, Self::FrameState)> {
+        Ok(self.child.layout(state, cx)?)
     }
 
     fn paint(
         &mut self,
-        view: &mut V,
-        parent_origin: Vector2F,
-        layout: &Layout,
-        paint_state: &mut Self::PaintState,
-        cx: &mut ViewContext<V>,
-    ) where
-        Self: Sized,
-    {
-        let bounds = layout.bounds + parent_origin;
-        self.hovered.set(bounds.contains_point(cx.mouse_position()));
-
+        bounds: Bounds<Pixels>,
+        state: &mut Self::State,
+        frame_state: &mut Self::FrameState,
+        cx: &mut ViewContext<Self::State>,
+    ) -> Result<()> {
+        let hovered = bounds.contains_point(cx.mouse_position());
         let slot = self.cascade_slot;
-        let style = self.hovered.get().then_some(self.hovered_style.clone());
+        let style = hovered.then_some(self.hovered_style.clone());
         self.style_cascade().set(slot, style);
+        self.hovered.store(hovered, SeqCst);
 
         let hovered = self.hovered.clone();
-        cx.on_event(layout.order, move |_view, _: &MouseMovedEvent, cx| {
-            cx.bubble_event();
-            if bounds.contains_point(cx.mouse_position()) != hovered.get() {
-                cx.repaint();
+        cx.on_mouse_event(move |event: &MouseMoveEvent, phase, cx| {
+            if phase == DispatchPhase::Capture {
+                if bounds.contains_point(event.position) != hovered.load(SeqCst) {
+                    cx.notify();
+                }
             }
         });
 
-        self.child
-            .paint(view, parent_origin, layout, paint_state, cx);
+        self.child.paint(bounds, state, frame_state, cx)?;
+        Ok(())
     }
 }
 
-impl<E: Styleable<Style = Style>> StyleHelpers for Hoverable<E> {}
-
-impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Hoverable<E> {
-    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
-        self.child.interaction_handlers()
-    }
-}
+impl<E: ParentElement + Styled> ParentElement for Hoverable<E> {
+    type State = E::State;
 
-impl<V: 'static, E: ParentElement<V> + Styleable> ParentElement<V> for Hoverable<E> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
         self.child.children_mut()
     }
 }
-
-impl<V: 'static, E: Element<V> + Styleable> IntoElement<V> for Hoverable<E> {
-    type Element = Self;
-
-    fn into_element(self) -> Self::Element {
-        self
-    }
-}

crates/gpui3/src/elements/img.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{
-    BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers,
-    Styled, ViewContext,
+    BorrowWindow, Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, Styled,
+    ViewContext,
 };
 use futures::FutureExt;
 use refineable::RefinementCascade;
@@ -98,5 +98,3 @@ impl<S> Styled for Img<S> {
         self.style.base()
     }
 }
-
-impl<S> StyleHelpers for Img<S> {}

crates/gpui3/src/elements/pressable.rs 🔗

@@ -85,8 +85,6 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
     }
 }
 
-impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}
-
 impl<V: 'static, E: Interactive<V> + Styleable> Interactive<V> for Pressable<E> {
     fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
         self.child.interaction_handlers()

crates/gpui3/src/elements/svg.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, StyleHelpers, Styled};
+use crate::{Bounds, Element, LayoutId, Pixels, Result, SharedString, Style, Styled};
 use refineable::RefinementCascade;
 use std::marker::PhantomData;
 
@@ -68,5 +68,3 @@ impl<S> Styled for Svg<S> {
         self.style.base()
     }
 }
-
-impl<S> StyleHelpers for Svg<S> {}

crates/gpui3/src/platform/events.rs → crates/gpui3/src/events.rs 🔗

@@ -34,61 +34,20 @@ pub enum TouchPhase {
     Ended,
 }
 
-#[derive(Clone, Copy, Debug)]
-pub enum ScrollDelta {
-    Pixels(Point<Pixels>),
-    Lines(Point<f32>),
-}
-
-impl Default for ScrollDelta {
-    fn default() -> Self {
-        Self::Lines(Default::default())
-    }
-}
-
-impl ScrollDelta {
-    pub fn precise(&self) -> bool {
-        match self {
-            ScrollDelta::Pixels(_) => true,
-            ScrollDelta::Lines(_) => false,
-        }
-    }
-
-    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
-        match self {
-            ScrollDelta::Pixels(delta) => *delta,
-            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
-        }
-    }
-}
-
 #[derive(Clone, Debug, Default)]
-pub struct ScrollWheelEvent {
+pub struct MouseDownEvent {
+    pub button: MouseButton,
     pub position: Point<Pixels>,
-    pub delta: ScrollDelta,
     pub modifiers: Modifiers,
-    /// If the platform supports returning the phase of a scroll wheel event, it will be stored here
-    pub phase: Option<TouchPhase>,
-}
-
-impl Deref for ScrollWheelEvent {
-    type Target = Modifiers;
-
-    fn deref(&self) -> &Self::Target {
-        &self.modifiers
-    }
-}
-
-#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
-pub enum NavigationDirection {
-    Back,
-    Forward,
+    pub click_count: usize,
 }
 
-impl Default for NavigationDirection {
-    fn default() -> Self {
-        Self::Back
-    }
+#[derive(Clone, Debug, Default)]
+pub struct MouseUpEvent {
+    pub button: MouseButton,
+    pub position: Point<Pixels>,
+    pub modifiers: Modifiers,
+    pub click_count: usize,
 }
 
 #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
@@ -117,45 +76,77 @@ impl Default for MouseButton {
     }
 }
 
-#[derive(Clone, Debug, Default)]
-pub struct MouseDownEvent {
-    pub button: MouseButton,
-    pub position: Point<Pixels>,
-    pub modifiers: Modifiers,
-    pub click_count: usize,
+#[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)]
+pub enum NavigationDirection {
+    Back,
+    Forward,
 }
 
-#[derive(Clone, Debug, Default)]
-pub struct MouseUpEvent {
-    pub button: MouseButton,
-    pub position: Point<Pixels>,
-    pub modifiers: Modifiers,
-    pub click_count: usize,
+impl Default for NavigationDirection {
+    fn default() -> Self {
+        Self::Back
+    }
 }
 
 #[derive(Clone, Debug, Default)]
-pub struct MouseUp {
-    pub button: MouseButton,
+pub struct MouseMoveEvent {
     pub position: Point<Pixels>,
+    pub pressed_button: Option<MouseButton>,
     pub modifiers: Modifiers,
-    pub click_count: usize,
 }
 
-#[derive(Clone, Debug, Default)]
-pub struct MouseMovedEvent {
+#[derive(Clone, Debug)]
+pub struct ScrollWheelEvent {
     pub position: Point<Pixels>,
-    pub pressed_button: Option<MouseButton>,
+    pub delta: ScrollDelta,
     pub modifiers: Modifiers,
+    pub touch_phase: TouchPhase,
+}
+
+impl Deref for ScrollWheelEvent {
+    type Target = Modifiers;
+
+    fn deref(&self) -> &Self::Target {
+        &self.modifiers
+    }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum ScrollDelta {
+    Pixels(Point<Pixels>),
+    Lines(Point<f32>),
+}
+
+impl Default for ScrollDelta {
+    fn default() -> Self {
+        Self::Lines(Default::default())
+    }
+}
+
+impl ScrollDelta {
+    pub fn precise(&self) -> bool {
+        match self {
+            ScrollDelta::Pixels(_) => true,
+            ScrollDelta::Lines(_) => false,
+        }
+    }
+
+    pub fn pixel_delta(&self, line_height: Pixels) -> Point<Pixels> {
+        match self {
+            ScrollDelta::Pixels(delta) => *delta,
+            ScrollDelta::Lines(delta) => point(line_height * delta.x, line_height * delta.y),
+        }
+    }
 }
 
 #[derive(Clone, Debug, Default)]
-pub struct MouseExitedEvent {
+pub struct MouseExitEvent {
     pub position: Point<Pixels>,
     pub pressed_button: Option<MouseButton>,
     pub modifiers: Modifiers,
 }
 
-impl Deref for MouseExitedEvent {
+impl Deref for MouseExitEvent {
     type Target = Modifiers;
 
     fn deref(&self) -> &Self::Target {
@@ -170,8 +161,8 @@ pub enum Event {
     ModifiersChanged(ModifiersChangedEvent),
     MouseDown(MouseDownEvent),
     MouseUp(MouseUpEvent),
-    MouseMoved(MouseMovedEvent),
-    MouseExited(MouseExitedEvent),
+    MouseMoved(MouseMoveEvent),
+    MouseExited(MouseExitEvent),
     ScrollWheel(ScrollWheelEvent),
 }
 

crates/gpui3/src/geometry.rs 🔗

@@ -289,6 +289,12 @@ impl<T: Clone + Debug + PartialOrd + Add<T, Output = T> + Sub<Output = T>> Bound
         let lower_right = self.lower_right().min(&other.lower_right());
         Self::from_corners(upper_left, lower_right)
     }
+
+    pub fn union(&self, other: &Self) -> Self {
+        let top_left = self.origin.min(&other.origin);
+        let bottom_right = self.lower_right().max(&other.lower_right());
+        Bounds::from_corners(top_left, bottom_right)
+    }
 }
 
 impl<T, Rhs> Mul<Rhs> for Bounds<T>

crates/gpui3/src/gpui3.rs 🔗

@@ -3,9 +3,11 @@ mod assets;
 mod color;
 mod element;
 mod elements;
+mod events;
 mod executor;
 mod geometry;
 mod image_cache;
+mod interactive;
 mod platform;
 mod scene;
 mod style;
@@ -24,10 +26,12 @@ pub use assets::*;
 pub use color::*;
 pub use element::*;
 pub use elements::*;
+pub use events::*;
 pub use executor::*;
 pub use geometry::*;
 pub use gpui3_macros::*;
 pub use image_cache::*;
+pub use interactive::*;
 pub use platform::*;
 pub use refineable::*;
 pub use scene::*;

crates/gpui3/src/interactive.rs 🔗

@@ -0,0 +1,230 @@
+use crate::{
+    Bounds, DispatchPhase, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels,
+    ScrollWheelEvent, ViewContext,
+};
+use parking_lot::Mutex;
+use smallvec::SmallVec;
+use std::sync::Arc;
+
+pub trait Interactive<S: 'static + Send + Sync> {
+    fn listeners(&mut self) -> &mut MouseEventListeners<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.listeners()
+            .mouse_down
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(event.position)
+                {
+                    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.listeners()
+            .mouse_up
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble
+                    && event.button == button
+                    && bounds.contains_point(event.position)
+                {
+                    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.listeners()
+            .mouse_down
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.contains_point(event.position)
+                {
+                    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.listeners()
+            .mouse_up
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Capture
+                    && event.button == button
+                    && !bounds.contains_point(event.position)
+                {
+                    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);
+            }
+        })
+    }
+
+    fn on_mouse_move(
+        mut self,
+        handler: impl Fn(&mut S, &MouseMoveEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.listeners()
+            .mouse_move
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            }));
+        self
+    }
+
+    fn on_scroll_wheel(
+        mut self,
+        handler: impl Fn(&mut S, &ScrollWheelEvent, &mut ViewContext<S>) + Send + Sync + 'static,
+    ) -> Self
+    where
+        Self: Sized,
+    {
+        self.listeners()
+            .scroll_wheel
+            .push(Arc::new(move |view, event, bounds, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(event.position) {
+                    handler(view, event, cx);
+                }
+            }));
+        self
+    }
+}
+
+type MouseDownHandler<V> = Arc<
+    dyn Fn(&mut V, &MouseDownEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+type MouseUpHandler<V> = Arc<
+    dyn Fn(&mut V, &MouseUpEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+
+type MouseMoveHandler<V> = Arc<
+    dyn Fn(&mut V, &MouseMoveEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+type ScrollWheelHandler<V> = Arc<
+    dyn Fn(&mut V, &ScrollWheelEvent, &Bounds<Pixels>, DispatchPhase, &mut ViewContext<V>)
+        + Send
+        + Sync
+        + 'static,
+>;
+
+pub struct MouseEventListeners<V: 'static> {
+    mouse_down: SmallVec<[MouseDownHandler<V>; 2]>,
+    mouse_up: SmallVec<[MouseUpHandler<V>; 2]>,
+    mouse_move: SmallVec<[MouseMoveHandler<V>; 2]>,
+    scroll_wheel: SmallVec<[ScrollWheelHandler<V>; 2]>,
+}
+
+impl<S: Send + Sync + 'static> MouseEventListeners<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| {
+                handler(view, event, &bounds, phase, cx);
+            })
+        }
+        for handler in self.mouse_up.iter().cloned() {
+            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
+                handler(view, event, &bounds, phase, cx);
+            })
+        }
+        for handler in self.mouse_move.iter().cloned() {
+            cx.on_mouse_event(move |view, event: &MouseMoveEvent, phase, cx| {
+                handler(view, event, &bounds, phase, cx);
+            })
+        }
+
+        for handler in self.scroll_wheel.iter().cloned() {
+            cx.on_mouse_event(move |view, event: &ScrollWheelEvent, phase, cx| {
+                handler(view, event, &bounds, phase, cx);
+            })
+        }
+    }
+}
+
+impl<V> Default for MouseEventListeners<V> {
+    fn default() -> Self {
+        Self {
+            mouse_down: Default::default(),
+            mouse_up: Default::default(),
+            mouse_move: Default::default(),
+            scroll_wheel: Default::default(),
+        }
+    }
+}

crates/gpui3/src/platform.rs 🔗

@@ -1,4 +1,3 @@
-mod events;
 mod keystroke;
 #[cfg(target_os = "macos")]
 mod mac;
@@ -6,9 +5,9 @@ mod mac;
 mod test;
 
 use crate::{
-    AnyWindowHandle, Bounds, DevicePixels, Executor, Font, FontId, FontMetrics, GlobalPixels,
-    GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene,
-    ShapedLine, SharedString, Size,
+    AnyWindowHandle, Bounds, DevicePixels, Event, Executor, Font, FontId, FontMetrics,
+    GlobalPixels, GlyphId, Pixels, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams,
+    Result, Scene, ShapedLine, SharedString, Size,
 };
 use anyhow::anyhow;
 use async_task::Runnable;
@@ -27,7 +26,6 @@ use std::{
     sync::Arc,
 };
 
-pub use events::*;
 pub use keystroke::*;
 #[cfg(target_os = "macos")]
 pub use mac::*;
@@ -40,7 +38,7 @@ pub(crate) fn current_platform() -> Arc<dyn Platform> {
     Arc::new(MacPlatform::new())
 }
 
-pub trait Platform: 'static {
+pub(crate) trait Platform: 'static {
     fn executor(&self) -> Executor;
     fn display_linker(&self) -> Arc<dyn PlatformDisplayLinker>;
     fn text_system(&self) -> Arc<dyn PlatformTextSystem>;
@@ -113,7 +111,7 @@ impl Debug for DisplayId {
 
 unsafe impl Send for DisplayId {}
 
-pub trait PlatformWindow {
+pub(crate) trait PlatformWindow {
     fn bounds(&self) -> WindowBounds;
     fn content_size(&self) -> Size<Pixels>;
     fn scale_factor(&self) -> f32;
@@ -194,11 +192,17 @@ pub enum AtlasKey {
 }
 
 impl AtlasKey {
-    pub fn is_monochrome(&self) -> bool {
+    pub(crate) fn texture_kind(&self) -> AtlasTextureKind {
         match self {
-            AtlasKey::Glyph(params) => !params.is_emoji,
-            AtlasKey::Svg(_) => true,
-            AtlasKey::Image(_) => false,
+            AtlasKey::Glyph(params) => {
+                if params.is_emoji {
+                    AtlasTextureKind::Polychrome
+                } else {
+                    AtlasTextureKind::Monochrome
+                }
+            }
+            AtlasKey::Svg(_) => AtlasTextureKind::Monochrome,
+            AtlasKey::Image(_) => AtlasTextureKind::Polychrome,
         }
     }
 }
@@ -239,9 +243,21 @@ pub struct AtlasTile {
     pub(crate) bounds: Bounds<DevicePixels>,
 }
 
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 #[repr(C)]
-pub(crate) struct AtlasTextureId(pub(crate) u32); // We use u32 instead of usize for Metal Shader Language compatibility
+pub(crate) struct AtlasTextureId {
+    // We use u32 instead of usize for Metal Shader Language compatibility
+    pub(crate) index: u32,
+    pub(crate) kind: AtlasTextureKind,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+#[repr(C)]
+pub(crate) enum AtlasTextureKind {
+    Monochrome = 0,
+    Polychrome = 1,
+    Path = 2,
+}
 
 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
 #[repr(C)]

crates/gpui3/src/platform/mac/events.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     point, px, Event, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent,
-    MouseButton, MouseDownEvent, MouseExitedEvent, MouseMovedEvent, MouseUpEvent,
-    NavigationDirection, Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
+    MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection,
+    Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase,
 };
 use cocoa::{
     appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType},
@@ -161,10 +161,10 @@ impl Event {
             NSEventType::NSScrollWheel => window_height.map(|window_height| {
                 let phase = match native_event.phase() {
                     NSEventPhase::NSEventPhaseMayBegin | NSEventPhase::NSEventPhaseBegan => {
-                        Some(TouchPhase::Started)
+                        TouchPhase::Started
                     }
-                    NSEventPhase::NSEventPhaseEnded => Some(TouchPhase::Ended),
-                    _ => Some(TouchPhase::Moved),
+                    NSEventPhase::NSEventPhaseEnded => TouchPhase::Ended,
+                    _ => TouchPhase::Moved,
                 };
 
                 let raw_data = point(
@@ -184,7 +184,7 @@ impl Event {
                         window_height - px(native_event.locationInWindow().y as f32),
                     ),
                     delta,
-                    phase,
+                    touch_phase: phase,
                     modifiers: read_modifiers(native_event),
                 })
             }),
@@ -202,7 +202,7 @@ impl Event {
                 };
 
                 window_height.map(|window_height| {
-                    Self::MouseMoved(MouseMovedEvent {
+                    Self::MouseMoved(MouseMoveEvent {
                         pressed_button: Some(pressed_button),
                         position: point(
                             px(native_event.locationInWindow().x as f32),
@@ -213,7 +213,7 @@ impl Event {
                 })
             }
             NSEventType::NSMouseMoved => window_height.map(|window_height| {
-                Self::MouseMoved(MouseMovedEvent {
+                Self::MouseMoved(MouseMoveEvent {
                     position: point(
                         px(native_event.locationInWindow().x as f32),
                         window_height - px(native_event.locationInWindow().y as f32),
@@ -223,7 +223,7 @@ impl Event {
                 })
             }),
             NSEventType::NSMouseExited => window_height.map(|window_height| {
-                Self::MouseExited(MouseExitedEvent {
+                Self::MouseExited(MouseExitEvent {
                     position: point(
                         px(native_event.locationInWindow().x as f32),
                         window_height - px(native_event.locationInWindow().y as f32),

crates/gpui3/src/platform/mac/metal_atlas.rs 🔗

@@ -1,14 +1,14 @@
-use std::borrow::Cow;
-
 use crate::{
-    AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, PlatformAtlas, Point, Size,
+    AtlasKey, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, DevicePixels, PlatformAtlas,
+    Point, Size,
 };
-use anyhow::{anyhow, Result};
+use anyhow::Result;
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
 use etagere::BucketedAtlasAllocator;
 use metal::Device;
 use parking_lot::Mutex;
+use std::borrow::Cow;
 
 pub struct MetalAtlas(Mutex<MetalAtlasState>);
 
@@ -16,19 +16,43 @@ impl MetalAtlas {
     pub fn new(device: Device) -> Self {
         MetalAtlas(Mutex::new(MetalAtlasState {
             device: AssertSend(device),
-            textures: Default::default(),
+            monochrome_textures: Default::default(),
+            polychrome_textures: Default::default(),
+            path_textures: Default::default(),
             tiles_by_key: Default::default(),
         }))
     }
 
-    pub(crate) fn texture(&self, id: AtlasTextureId) -> metal::Texture {
-        self.0.lock().textures[id.0 as usize].metal_texture.clone()
+    pub(crate) fn metal_texture(&self, id: AtlasTextureId) -> metal::Texture {
+        self.0.lock().texture(id).metal_texture.clone()
+    }
+
+    pub(crate) fn allocate(
+        &self,
+        size: Size<DevicePixels>,
+        texture_kind: AtlasTextureKind,
+    ) -> AtlasTile {
+        self.0.lock().allocate(size, texture_kind)
+    }
+
+    pub(crate) fn clear_textures(&self, texture_kind: AtlasTextureKind) {
+        let mut lock = self.0.lock();
+        let textures = match texture_kind {
+            AtlasTextureKind::Monochrome => &mut lock.monochrome_textures,
+            AtlasTextureKind::Polychrome => &mut lock.polychrome_textures,
+            AtlasTextureKind::Path => &mut lock.path_textures,
+        };
+        for texture in textures {
+            texture.clear();
+        }
     }
 }
 
 struct MetalAtlasState {
     device: AssertSend<Device>,
-    textures: Vec<MetalAtlasTexture>,
+    monochrome_textures: Vec<MetalAtlasTexture>,
+    polychrome_textures: Vec<MetalAtlasTexture>,
+    path_textures: Vec<MetalAtlasTexture>,
     tiles_by_key: HashMap<AtlasKey, AtlasTile>,
 }
 
@@ -43,37 +67,50 @@ impl PlatformAtlas for MetalAtlas {
             return Ok(tile.clone());
         } else {
             let (size, bytes) = build()?;
-            let tile = lock
-                .textures
-                .iter_mut()
-                .rev()
-                .find_map(|texture| {
-                    if texture.monochrome == key.is_monochrome() {
-                        texture.upload(size, &bytes)
-                    } else {
-                        None
-                    }
-                })
-                .or_else(|| {
-                    let texture = lock.push_texture(size, key.is_monochrome());
-                    texture.upload(size, &bytes)
-                })
-                .ok_or_else(|| anyhow!("could not allocate in new texture"))?;
+            let tile = lock.allocate(size, key.texture_kind());
+            let texture = lock.texture(tile.texture_id);
+            texture.upload(tile.bounds, &bytes);
             lock.tiles_by_key.insert(key.clone(), tile.clone());
             Ok(tile)
         }
     }
 
     fn clear(&self) {
-        self.0.lock().tiles_by_key.clear();
+        let mut lock = self.0.lock();
+        lock.tiles_by_key.clear();
+        for texture in &mut lock.monochrome_textures {
+            texture.clear();
+        }
+        for texture in &mut lock.polychrome_textures {
+            texture.clear();
+        }
+        for texture in &mut lock.path_textures {
+            texture.clear();
+        }
     }
 }
 
 impl MetalAtlasState {
+    fn allocate(&mut self, size: Size<DevicePixels>, texture_kind: AtlasTextureKind) -> AtlasTile {
+        let textures = match texture_kind {
+            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
+            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+            AtlasTextureKind::Path => &mut self.path_textures,
+        };
+        textures
+            .iter_mut()
+            .rev()
+            .find_map(|texture| texture.allocate(size))
+            .unwrap_or_else(|| {
+                let texture = self.push_texture(size, texture_kind);
+                texture.allocate(size).unwrap()
+            })
+    }
+
     fn push_texture(
         &mut self,
         min_size: Size<DevicePixels>,
-        monochrome: bool,
+        kind: AtlasTextureKind,
     ) -> &mut MetalAtlasTexture {
         const DEFAULT_ATLAS_SIZE: Size<DevicePixels> = Size {
             width: DevicePixels(1024),
@@ -84,21 +121,50 @@ impl MetalAtlasState {
         let texture_descriptor = metal::TextureDescriptor::new();
         texture_descriptor.set_width(size.width.into());
         texture_descriptor.set_height(size.height.into());
-        if monochrome {
-            texture_descriptor.set_pixel_format(metal::MTLPixelFormat::A8Unorm);
-        } else {
-            texture_descriptor.set_pixel_format(metal::MTLPixelFormat::BGRA8Unorm);
+        let pixel_format;
+        let usage;
+        match kind {
+            AtlasTextureKind::Monochrome => {
+                pixel_format = metal::MTLPixelFormat::A8Unorm;
+                usage = metal::MTLTextureUsage::ShaderRead;
+            }
+            AtlasTextureKind::Polychrome => {
+                pixel_format = metal::MTLPixelFormat::BGRA8Unorm;
+                usage = metal::MTLTextureUsage::ShaderRead;
+            }
+            AtlasTextureKind::Path => {
+                pixel_format = metal::MTLPixelFormat::R16Float;
+                usage = metal::MTLTextureUsage::RenderTarget | metal::MTLTextureUsage::ShaderRead;
+            }
         }
+        texture_descriptor.set_pixel_format(pixel_format);
+        texture_descriptor.set_usage(usage);
         let metal_texture = self.device.new_texture(&texture_descriptor);
 
+        let textures = match kind {
+            AtlasTextureKind::Monochrome => &mut self.monochrome_textures,
+            AtlasTextureKind::Polychrome => &mut self.polychrome_textures,
+            AtlasTextureKind::Path => &mut self.path_textures,
+        };
         let atlas_texture = MetalAtlasTexture {
-            id: AtlasTextureId(self.textures.len() as u32),
+            id: AtlasTextureId {
+                index: textures.len() as u32,
+                kind,
+            },
             allocator: etagere::BucketedAtlasAllocator::new(size.into()),
             metal_texture: AssertSend(metal_texture),
-            monochrome,
         };
-        self.textures.push(atlas_texture);
-        self.textures.last_mut().unwrap()
+        textures.push(atlas_texture);
+        textures.last_mut().unwrap()
+    }
+
+    fn texture(&self, id: AtlasTextureId) -> &MetalAtlasTexture {
+        let textures = match id.kind {
+            crate::AtlasTextureKind::Monochrome => &self.monochrome_textures,
+            crate::AtlasTextureKind::Polychrome => &self.polychrome_textures,
+            crate::AtlasTextureKind::Path => &self.path_textures,
+        };
+        &textures[id.index as usize]
     }
 }
 
@@ -106,11 +172,14 @@ struct MetalAtlasTexture {
     id: AtlasTextureId,
     allocator: BucketedAtlasAllocator,
     metal_texture: AssertSend<metal::Texture>,
-    monochrome: bool,
 }
 
 impl MetalAtlasTexture {
-    fn upload(&mut self, size: Size<DevicePixels>, bytes: &[u8]) -> Option<AtlasTile> {
+    fn clear(&mut self) {
+        self.allocator.clear();
+    }
+
+    fn allocate(&mut self, size: Size<DevicePixels>) -> Option<AtlasTile> {
         let allocation = self.allocator.allocate(size.into())?;
         let tile = AtlasTile {
             texture_id: self.id,
@@ -120,20 +189,22 @@ impl MetalAtlasTexture {
                 size,
             },
         };
+        Some(tile)
+    }
 
+    fn upload(&self, bounds: Bounds<DevicePixels>, bytes: &[u8]) {
         let region = metal::MTLRegion::new_2d(
-            tile.bounds.origin.x.into(),
-            tile.bounds.origin.y.into(),
-            tile.bounds.size.width.into(),
-            tile.bounds.size.height.into(),
+            bounds.origin.x.into(),
+            bounds.origin.y.into(),
+            bounds.size.width.into(),
+            bounds.size.height.into(),
         );
         self.metal_texture.replace_region(
             region,
             0,
             bytes.as_ptr() as *const _,
-            u32::from(tile.bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
+            u32::from(bounds.size.width.to_bytes(self.bytes_per_pixel())) as u64,
         );
-        Some(tile)
     }
 
     fn bytes_per_pixel(&self) -> u8 {

crates/gpui3/src/platform/mac/metal_renderer.rs 🔗

@@ -1,22 +1,27 @@
 use crate::{
-    point, size, AtlasTextureId, DevicePixels, MetalAtlas, MonochromeSprite, PolychromeSprite,
-    PrimitiveBatch, Quad, Scene, Shadow, Size, Underline,
+    point, size, AtlasTextureId, AtlasTextureKind, AtlasTile, Bounds, ContentMask, DevicePixels,
+    Hsla, MetalAtlas, MonochromeSprite, Path, PathId, PathVertex, PolychromeSprite, PrimitiveBatch,
+    Quad, ScaledPixels, Scene, Shadow, Size, Underline,
 };
 use cocoa::{
     base::{NO, YES},
     foundation::NSUInteger,
     quartzcore::AutoresizingMask,
 };
+use collections::HashMap;
 use metal::{CommandQueue, MTLPixelFormat, MTLResourceOptions, NSRange};
 use objc::{self, msg_send, sel, sel_impl};
+use smallvec::SmallVec;
 use std::{ffi::c_void, mem, ptr, sync::Arc};
 
 const SHADERS_METALLIB: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/shaders.metallib"));
 const INSTANCE_BUFFER_SIZE: usize = 8192 * 1024; // This is an arbitrary decision. There's probably a more optimal value.
 
-pub struct MetalRenderer {
+pub(crate) struct MetalRenderer {
     layer: metal::MetalLayer,
     command_queue: CommandQueue,
+    paths_rasterization_pipeline_state: metal::RenderPipelineState,
+    path_sprites_pipeline_state: metal::RenderPipelineState,
     shadows_pipeline_state: metal::RenderPipelineState,
     quads_pipeline_state: metal::RenderPipelineState,
     underlines_pipeline_state: metal::RenderPipelineState,
@@ -29,8 +34,6 @@ pub struct MetalRenderer {
 
 impl MetalRenderer {
     pub fn new(is_opaque: bool) -> Self {
-        const PIXEL_FORMAT: MTLPixelFormat = MTLPixelFormat::BGRA8Unorm;
-
         let device: metal::Device = if let Some(device) = metal::Device::system_default() {
             device
         } else {
@@ -40,7 +43,7 @@ impl MetalRenderer {
 
         let layer = metal::MetalLayer::new();
         layer.set_device(&device);
-        layer.set_pixel_format(PIXEL_FORMAT);
+        layer.set_pixel_format(MTLPixelFormat::BGRA8Unorm);
         layer.set_presents_with_transaction(true);
         layer.set_opaque(is_opaque);
         unsafe {
@@ -84,13 +87,29 @@ impl MetalRenderer {
             MTLResourceOptions::StorageModeManaged,
         );
 
+        let paths_rasterization_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "paths_rasterization",
+            "path_rasterization_vertex",
+            "path_rasterization_fragment",
+            MTLPixelFormat::R16Float,
+        );
+        let path_sprites_pipeline_state = build_pipeline_state(
+            &device,
+            &library,
+            "path_sprites",
+            "path_sprite_vertex",
+            "path_sprite_fragment",
+            MTLPixelFormat::BGRA8Unorm,
+        );
         let shadows_pipeline_state = build_pipeline_state(
             &device,
             &library,
             "shadows",
             "shadow_vertex",
             "shadow_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let quads_pipeline_state = build_pipeline_state(
             &device,
@@ -98,7 +117,7 @@ impl MetalRenderer {
             "quads",
             "quad_vertex",
             "quad_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let underlines_pipeline_state = build_pipeline_state(
             &device,
@@ -106,7 +125,7 @@ impl MetalRenderer {
             "underlines",
             "underline_vertex",
             "underline_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let monochrome_sprites_pipeline_state = build_pipeline_state(
             &device,
@@ -114,7 +133,7 @@ impl MetalRenderer {
             "monochrome_sprites",
             "monochrome_sprite_vertex",
             "monochrome_sprite_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
         let polychrome_sprites_pipeline_state = build_pipeline_state(
             &device,
@@ -122,7 +141,7 @@ impl MetalRenderer {
             "polychrome_sprites",
             "polychrome_sprite_vertex",
             "polychrome_sprite_fragment",
-            PIXEL_FORMAT,
+            MTLPixelFormat::BGRA8Unorm,
         );
 
         let command_queue = device.new_command_queue();
@@ -131,6 +150,8 @@ impl MetalRenderer {
         Self {
             layer,
             command_queue,
+            paths_rasterization_pipeline_state,
+            path_sprites_pipeline_state,
             shadows_pipeline_state,
             quads_pipeline_state,
             underlines_pipeline_state,
@@ -150,7 +171,7 @@ impl MetalRenderer {
         &self.sprite_atlas
     }
 
-    pub fn draw(&mut self, scene: &mut Scene) {
+    pub fn draw(&mut self, scene: &Scene) {
         let layer = self.layer.clone();
         let viewport_size = layer.drawable_size();
         let viewport_size: Size<DevicePixels> = size(
@@ -168,6 +189,9 @@ impl MetalRenderer {
         };
         let command_queue = self.command_queue.clone();
         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 render_pass_descriptor = metal::RenderPassDescriptor::new();
         let color_attachment = render_pass_descriptor
@@ -190,8 +214,6 @@ impl MetalRenderer {
             znear: 0.0,
             zfar: 1.0,
         });
-
-        let mut instance_offset = 0;
         for batch in scene.batches() {
             match batch {
                 PrimitiveBatch::Shadows(shadows) => {
@@ -205,6 +227,15 @@ impl MetalRenderer {
                 PrimitiveBatch::Quads(quads) => {
                     self.draw_quads(quads, &mut instance_offset, viewport_size, command_encoder);
                 }
+                PrimitiveBatch::Paths(paths) => {
+                    self.draw_paths(
+                        paths,
+                        &path_tiles,
+                        &mut instance_offset,
+                        viewport_size,
+                        command_encoder,
+                    );
+                }
                 PrimitiveBatch::Underlines(underlines) => {
                     self.draw_underlines(
                         underlines,
@@ -248,10 +279,97 @@ impl MetalRenderer {
         });
 
         command_buffer.commit();
+        self.sprite_atlas.clear_textures(AtlasTextureKind::Path);
         command_buffer.wait_until_completed();
         drawable.present();
     }
 
+    fn rasterize_paths(
+        &mut self,
+        paths: &[Path<ScaledPixels>],
+        offset: &mut usize,
+        command_buffer: &metal::CommandBufferRef,
+    ) -> HashMap<PathId, AtlasTile> {
+        let mut tiles = HashMap::default();
+        let mut vertices_by_texture_id = HashMap::default();
+        for path in paths {
+            let clipped_bounds = path.bounds.intersect(&path.content_mask.bounds);
+
+            let tile = self
+                .sprite_atlas
+                .allocate(clipped_bounds.size.map(Into::into), AtlasTextureKind::Path);
+            vertices_by_texture_id
+                .entry(tile.texture_id)
+                .or_insert(Vec::new())
+                .extend(path.vertices.iter().map(|vertex| PathVertex {
+                    xy_position: vertex.xy_position - path.bounds.origin
+                        + tile.bounds.origin.map(Into::into),
+                    st_position: vertex.st_position,
+                    content_mask: ContentMask {
+                        bounds: tile.bounds.map(Into::into),
+                    },
+                }));
+            tiles.insert(path.id, tile);
+        }
+
+        for (texture_id, vertices) in vertices_by_texture_id {
+            align_offset(offset);
+            let next_offset = *offset + vertices.len() * mem::size_of::<PathVertex<ScaledPixels>>();
+            assert!(
+                next_offset <= INSTANCE_BUFFER_SIZE,
+                "instance buffer exhausted"
+            );
+
+            let render_pass_descriptor = metal::RenderPassDescriptor::new();
+            let color_attachment = render_pass_descriptor
+                .color_attachments()
+                .object_at(0)
+                .unwrap();
+
+            let texture = self.sprite_atlas.metal_texture(texture_id);
+            color_attachment.set_texture(Some(&texture));
+            color_attachment.set_load_action(metal::MTLLoadAction::Clear);
+            color_attachment.set_store_action(metal::MTLStoreAction::Store);
+            color_attachment.set_clear_color(metal::MTLClearColor::new(0., 0., 0., 1.));
+            let command_encoder = command_buffer.new_render_command_encoder(render_pass_descriptor);
+            command_encoder.set_render_pipeline_state(&self.paths_rasterization_pipeline_state);
+            command_encoder.set_vertex_buffer(
+                PathRasterizationInputIndex::Vertices as u64,
+                Some(&self.instances),
+                *offset as u64,
+            );
+            let texture_size = Size {
+                width: DevicePixels::from(texture.width()),
+                height: DevicePixels::from(texture.height()),
+            };
+            command_encoder.set_vertex_bytes(
+                PathRasterizationInputIndex::AtlasTextureSize as u64,
+                mem::size_of_val(&texture_size) as u64,
+                &texture_size as *const Size<DevicePixels> as *const _,
+            );
+
+            let vertices_bytes_len = mem::size_of::<PathVertex<ScaledPixels>>() * vertices.len();
+            let buffer_contents = unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+            unsafe {
+                ptr::copy_nonoverlapping(
+                    vertices.as_ptr() as *const u8,
+                    buffer_contents,
+                    vertices_bytes_len,
+                );
+            }
+
+            command_encoder.draw_primitives(
+                metal::MTLPrimitiveType::Triangle,
+                0,
+                vertices.len() as u64,
+            );
+            command_encoder.end_encoding();
+            *offset = next_offset;
+        }
+
+        tiles
+    }
+
     fn draw_shadows(
         &mut self,
         shadows: &[Shadow],
@@ -368,6 +486,112 @@ impl MetalRenderer {
         *offset = next_offset;
     }
 
+    fn draw_paths(
+        &mut self,
+        paths: &[Path<ScaledPixels>],
+        tiles_by_path_id: &HashMap<PathId, AtlasTile>,
+        offset: &mut usize,
+        viewport_size: Size<DevicePixels>,
+        command_encoder: &metal::RenderCommandEncoderRef,
+    ) {
+        if paths.is_empty() {
+            return;
+        }
+
+        command_encoder.set_render_pipeline_state(&self.path_sprites_pipeline_state);
+        command_encoder.set_vertex_buffer(
+            SpriteInputIndex::Vertices as u64,
+            Some(&self.unit_vertices),
+            0,
+        );
+        command_encoder.set_vertex_bytes(
+            SpriteInputIndex::ViewportSize as u64,
+            mem::size_of_val(&viewport_size) as u64,
+            &viewport_size as *const Size<DevicePixels> as *const _,
+        );
+
+        let mut prev_texture_id = None;
+        let mut sprites = SmallVec::<[_; 1]>::new();
+        let mut paths_and_tiles = paths
+            .into_iter()
+            .map(|path| (path, tiles_by_path_id.get(&path.id).unwrap()))
+            .peekable();
+
+        loop {
+            if let Some((path, tile)) = paths_and_tiles.peek() {
+                if prev_texture_id.map_or(true, |texture_id| texture_id == tile.texture_id) {
+                    prev_texture_id = Some(tile.texture_id);
+                    sprites.push(PathSprite {
+                        bounds: Bounds {
+                            origin: path.bounds.origin.map(|p| p.floor()),
+                            size: tile.bounds.size.map(Into::into),
+                        },
+                        color: path.color,
+                        tile: (*tile).clone(),
+                    });
+                    paths_and_tiles.next();
+                    continue;
+                }
+            }
+
+            if sprites.is_empty() {
+                break;
+            } else {
+                align_offset(offset);
+                let texture_id = prev_texture_id.take().unwrap();
+                let texture: metal::Texture = self.sprite_atlas.metal_texture(texture_id);
+                let texture_size = size(
+                    DevicePixels(texture.width() as i32),
+                    DevicePixels(texture.height() as i32),
+                );
+
+                command_encoder.set_vertex_buffer(
+                    SpriteInputIndex::Sprites as u64,
+                    Some(&self.instances),
+                    *offset as u64,
+                );
+                command_encoder.set_vertex_bytes(
+                    SpriteInputIndex::AtlasTextureSize as u64,
+                    mem::size_of_val(&texture_size) as u64,
+                    &texture_size as *const Size<DevicePixels> as *const _,
+                );
+                command_encoder.set_fragment_buffer(
+                    SpriteInputIndex::Sprites as u64,
+                    Some(&self.instances),
+                    *offset as u64,
+                );
+                command_encoder
+                    .set_fragment_texture(SpriteInputIndex::AtlasTexture as u64, Some(&texture));
+
+                let sprite_bytes_len = mem::size_of::<MonochromeSprite>() * sprites.len();
+                let buffer_contents =
+                    unsafe { (self.instances.contents() as *mut u8).add(*offset) };
+                unsafe {
+                    ptr::copy_nonoverlapping(
+                        sprites.as_ptr() as *const u8,
+                        buffer_contents,
+                        sprite_bytes_len,
+                    );
+                }
+
+                let next_offset = *offset + sprite_bytes_len;
+                assert!(
+                    next_offset <= INSTANCE_BUFFER_SIZE,
+                    "instance buffer exhausted"
+                );
+
+                command_encoder.draw_primitives_instanced(
+                    metal::MTLPrimitiveType::Triangle,
+                    0,
+                    6,
+                    sprites.len() as u64,
+                );
+                *offset = next_offset;
+                sprites.clear();
+            }
+        }
+    }
+
     fn draw_underlines(
         &mut self,
         underlines: &[Underline],
@@ -441,7 +665,7 @@ impl MetalRenderer {
         }
         align_offset(offset);
 
-        let texture = self.sprite_atlas.texture(texture_id);
+        let texture = self.sprite_atlas.metal_texture(texture_id);
         let texture_size = size(
             DevicePixels(texture.width() as i32),
             DevicePixels(texture.height() as i32),
@@ -512,7 +736,7 @@ impl MetalRenderer {
         }
         align_offset(offset);
 
-        let texture = self.sprite_atlas.texture(texture_id);
+        let texture = self.sprite_atlas.metal_texture(texture_id);
         let texture_size = size(
             DevicePixels(texture.width() as i32),
             DevicePixels(texture.height() as i32),
@@ -640,3 +864,17 @@ enum SpriteInputIndex {
     AtlasTextureSize = 3,
     AtlasTexture = 4,
 }
+
+#[repr(C)]
+enum PathRasterizationInputIndex {
+    Vertices = 0,
+    AtlasTextureSize = 1,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+#[repr(C)]
+pub struct PathSprite {
+    pub bounds: Bounds<ScaledPixels>,
+    pub color: Hsla,
+    pub tile: AtlasTile,
+}

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;
                 }
             }

crates/gpui3/src/platform/mac/shaders.metal 🔗

@@ -336,6 +336,94 @@ fragment float4 polychrome_sprite_fragment(
   return color;
 }
 
+struct PathRasterizationVertexOutput {
+  float4 position [[position]];
+  float2 st_position;
+  float clip_rect_distance [[clip_distance]][4];
+};
+
+struct PathRasterizationFragmentInput {
+  float4 position [[position]];
+  float2 st_position;
+};
+
+vertex PathRasterizationVertexOutput path_rasterization_vertex(
+    uint vertex_id [[vertex_id]],
+    constant PathVertex_ScaledPixels *vertices
+    [[buffer(PathRasterizationInputIndex_Vertices)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
+  PathVertex_ScaledPixels v = vertices[vertex_id];
+  float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
+  float2 viewport_size = float2(atlas_size->width, atlas_size->height);
+  return PathRasterizationVertexOutput{
+      float4(vertex_position / viewport_size * float2(2., -2.) +
+                 float2(-1., 1.),
+             0., 1.),
+      float2(v.st_position.x, v.st_position.y),
+      {v.xy_position.x - v.content_mask.bounds.origin.x,
+       v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
+           v.xy_position.x,
+       v.xy_position.y - v.content_mask.bounds.origin.y,
+       v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
+           v.xy_position.y}};
+}
+
+fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
+                                            [[stage_in]]) {
+  float2 dx = dfdx(input.st_position);
+  float2 dy = dfdy(input.st_position);
+  float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
+                           (2. * input.st_position.x) * dy.x - dy.y);
+  float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
+  float distance = f / length(gradient);
+  float alpha = saturate(0.5 - distance);
+  return float4(alpha, 0., 0., 1.);
+}
+
+struct PathSpriteVertexOutput {
+  float4 position [[position]];
+  float2 tile_position;
+  float4 color [[flat]];
+  uint sprite_id [[flat]];
+};
+
+vertex PathSpriteVertexOutput path_sprite_vertex(
+    uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
+    constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
+    constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    constant Size_DevicePixels *viewport_size
+    [[buffer(SpriteInputIndex_ViewportSize)]],
+    constant Size_DevicePixels *atlas_size
+    [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
+
+  float2 unit_vertex = unit_vertices[unit_vertex_id];
+  PathSprite sprite = sprites[sprite_id];
+  // Don't apply content mask because it was already accounted for when
+  // rasterizing the path.
+  float4 device_position = to_device_position(unit_vertex, sprite.bounds,
+                                              sprite.bounds, viewport_size);
+  float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
+  float4 color = hsla_to_rgba(sprite.color);
+  return PathSpriteVertexOutput{device_position, tile_position, color,
+                                sprite_id};
+}
+
+fragment float4 path_sprite_fragment(
+    PathSpriteVertexOutput input [[stage_in]],
+    constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
+    texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
+  PathSprite sprite = sprites[input.sprite_id];
+  constexpr sampler atlas_texture_sampler(mag_filter::linear,
+                                          min_filter::linear);
+  float4 sample =
+      atlas_texture.sample(atlas_texture_sampler, input.tile_position);
+  float mask = 1. - abs(1. - fmod(sample.r, 2.));
+  float4 color = input.color;
+  color.a *= mask;
+  return color;
+}
+
 float4 hsla_to_rgba(Hsla hsla) {
   float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
   float s = hsla.s;

crates/gpui3/src/platform/mac/window.rs 🔗

@@ -2,7 +2,7 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NS
 use crate::{
     display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, Event, Executor,
     GlobalPixels, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton,
-    MouseDownEvent, MouseMovedEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
+    MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay,
     PlatformInputHandler, PlatformWindow, Point, Scene, Size, Timer, WindowAppearance,
     WindowBounds, WindowKind, WindowOptions, WindowPromptLevel,
 };
@@ -911,7 +911,7 @@ impl PlatformWindow for MacWindow {
         }
     }
 
-    fn draw(&self, scene: crate::Scene) {
+    fn draw(&self, scene: Scene) {
         let mut this = self.0.lock();
         this.scene_to_render = Some(scene);
         unsafe {
@@ -1141,7 +1141,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
 
         match &event {
             Event::MouseMoved(
-                event @ MouseMovedEvent {
+                event @ MouseMoveEvent {
                     pressed_button: Some(_),
                     ..
                 },
@@ -1395,8 +1395,8 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
     unsafe {
         let window_state = get_window_state(this);
         let mut window_state = window_state.as_ref().lock();
-        if let Some(mut scene) = window_state.scene_to_render.take() {
-            window_state.renderer.draw(&mut scene);
+        if let Some(scene) = window_state.scene_to_render.take() {
+            window_state.renderer.draw(&scene);
         }
     }
 }
@@ -1596,7 +1596,7 @@ extern "C" fn accepts_first_mouse(this: &Object, _: Sel, _: id) -> BOOL {
 async fn synthetic_drag(
     window_state: Weak<Mutex<MacWindowState>>,
     drag_id: usize,
-    event: MouseMovedEvent,
+    event: MouseMoveEvent,
 ) {
     loop {
         Timer::after(Duration::from_millis(16)).await;

crates/gpui3/src/scene.rs 🔗

@@ -1,106 +1,78 @@
 use crate::{
-    AtlasTextureId, AtlasTile, Bounds, Corners, Edges, Hsla, Point, ScaledContentMask, ScaledPixels,
+    point, AtlasTextureId, AtlasTile, Bounds, ContentMask, Corners, Edges, Hsla, Pixels, Point,
+    ScaledPixels, StackingOrder,
 };
 use collections::BTreeMap;
 use etagere::euclid::{Point3D, Vector3D};
 use plane_split::{BspSplitter, Polygon as BspPolygon};
-use smallvec::SmallVec;
-use std::{iter::Peekable, mem, slice};
+use std::{fmt::Debug, iter::Peekable, mem, slice};
 
 // Exported to metal
-pub type PointF = Point<f32>;
-pub type StackingOrder = SmallVec<[u32; 16]>;
+pub(crate) type PointF = Point<f32>;
+#[allow(non_camel_case_types, unused)]
+pub(crate) type PathVertex_ScaledPixels = PathVertex<ScaledPixels>;
+
 pub type LayerId = u32;
+
 pub type DrawOrder = u32;
 
-#[derive(Debug)]
-pub struct Scene {
-    pub(crate) scale_factor: f32,
-    pub(crate) layers: BTreeMap<StackingOrder, LayerId>,
-    pub shadows: Vec<Shadow>,
-    pub quads: Vec<Quad>,
-    pub underlines: Vec<Underline>,
-    pub monochrome_sprites: Vec<MonochromeSprite>,
-    pub polychrome_sprites: Vec<PolychromeSprite>,
+pub(crate) struct SceneBuilder {
+    layers_by_order: BTreeMap<StackingOrder, LayerId>,
+    splitter: BspSplitter<(PrimitiveKind, usize)>,
+    shadows: Vec<Shadow>,
+    quads: Vec<Quad>,
+    paths: Vec<Path<ScaledPixels>>,
+    underlines: Vec<Underline>,
+    monochrome_sprites: Vec<MonochromeSprite>,
+    polychrome_sprites: Vec<PolychromeSprite>,
 }
 
-impl Scene {
-    pub fn new(scale_factor: f32) -> Scene {
-        Scene {
-            scale_factor,
-            layers: BTreeMap::new(),
+impl SceneBuilder {
+    pub fn new() -> SceneBuilder {
+        SceneBuilder {
+            layers_by_order: BTreeMap::new(),
+            splitter: BspSplitter::new(),
             shadows: Vec::new(),
             quads: Vec::new(),
+            paths: Vec::new(),
             underlines: Vec::new(),
             monochrome_sprites: Vec::new(),
             polychrome_sprites: Vec::new(),
         }
     }
 
-    pub fn take(&mut self) -> Scene {
-        Scene {
-            scale_factor: self.scale_factor,
-            layers: mem::take(&mut self.layers),
-            shadows: mem::take(&mut self.shadows),
-            quads: mem::take(&mut self.quads),
-            underlines: mem::take(&mut self.underlines),
-            monochrome_sprites: mem::take(&mut self.monochrome_sprites),
-            polychrome_sprites: mem::take(&mut self.polychrome_sprites),
-        }
-    }
-
-    pub fn insert(&mut self, layer_id: StackingOrder, primitive: impl Into<Primitive>) {
-        let next_id = self.layers.len() as LayerId;
-        let layer_id = *self.layers.entry(layer_id).or_insert(next_id);
-        let primitive = primitive.into();
-        match primitive {
-            Primitive::Shadow(mut shadow) => {
-                shadow.order = layer_id;
-                self.shadows.push(shadow);
-            }
-            Primitive::Quad(mut quad) => {
-                quad.order = layer_id;
-                self.quads.push(quad);
-            }
-            Primitive::Underline(mut underline) => {
-                underline.order = layer_id;
-                self.underlines.push(underline);
-            }
-            Primitive::MonochromeSprite(mut sprite) => {
-                sprite.order = layer_id;
-                self.monochrome_sprites.push(sprite);
-            }
-            Primitive::PolychromeSprite(mut sprite) => {
-                sprite.order = layer_id;
-                self.polychrome_sprites.push(sprite);
-            }
-        }
-    }
-
-    pub(crate) fn batches(&mut self) -> impl Iterator<Item = PrimitiveBatch> {
+    pub fn build(&mut self) -> Scene {
         // Map each layer id to a float between 0. and 1., with 1. closer to the viewer.
-        let mut layer_z_values = vec![0.; self.layers.len()];
-        for (ix, layer_id) in self.layers.values().enumerate() {
-            layer_z_values[*layer_id as usize] = ix as f32 / self.layers.len() as f32;
+        let mut layer_z_values = vec![0.; self.layers_by_order.len()];
+        for (ix, layer_id) in self.layers_by_order.values().enumerate() {
+            layer_z_values[*layer_id as usize] = ix as f32 / self.layers_by_order.len() as f32;
         }
+        self.layers_by_order.clear();
 
         // Add all primitives to the BSP splitter to determine draw order
-        // todo!("reuse the same splitter")
-        let mut splitter = BspSplitter::new();
+        self.splitter.reset();
 
         for (ix, shadow) in self.shadows.iter().enumerate() {
             let z = layer_z_values[shadow.order as LayerId as usize];
-            splitter.add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
+            self.splitter
+                .add(shadow.bounds.to_bsp_polygon(z, (PrimitiveKind::Shadow, ix)));
         }
 
         for (ix, quad) in self.quads.iter().enumerate() {
             let z = layer_z_values[quad.order as LayerId as usize];
-            splitter.add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
+            self.splitter
+                .add(quad.bounds.to_bsp_polygon(z, (PrimitiveKind::Quad, ix)));
+        }
+
+        for (ix, path) in self.paths.iter().enumerate() {
+            let z = layer_z_values[path.order as LayerId as usize];
+            self.splitter
+                .add(path.bounds.to_bsp_polygon(z, (PrimitiveKind::Path, ix)));
         }
 
         for (ix, underline) in self.underlines.iter().enumerate() {
             let z = layer_z_values[underline.order as LayerId as usize];
-            splitter.add(
+            self.splitter.add(
                 underline
                     .bounds
                     .to_bsp_polygon(z, (PrimitiveKind::Underline, ix)),
@@ -109,7 +81,7 @@ impl Scene {
 
         for (ix, monochrome_sprite) in self.monochrome_sprites.iter().enumerate() {
             let z = layer_z_values[monochrome_sprite.order as LayerId as usize];
-            splitter.add(
+            self.splitter.add(
                 monochrome_sprite
                     .bounds
                     .to_bsp_polygon(z, (PrimitiveKind::MonochromeSprite, ix)),
@@ -118,7 +90,7 @@ impl Scene {
 
         for (ix, polychrome_sprite) in self.polychrome_sprites.iter().enumerate() {
             let z = layer_z_values[polychrome_sprite.order as LayerId as usize];
-            splitter.add(
+            self.splitter.add(
                 polychrome_sprite
                     .bounds
                     .to_bsp_polygon(z, (PrimitiveKind::PolychromeSprite, ix)),
@@ -127,10 +99,16 @@ impl Scene {
 
         // Sort all polygons, then reassign the order field of each primitive to `draw_order`
         // We need primitives to be repr(C), hence the weird reuse of the order field for two different types.
-        for (draw_order, polygon) in splitter.sort(Vector3D::new(0., 0., 1.)).iter().enumerate() {
+        for (draw_order, polygon) in self
+            .splitter
+            .sort(Vector3D::new(0., 0., 1.))
+            .iter()
+            .enumerate()
+        {
             match polygon.anchor {
                 (PrimitiveKind::Shadow, ix) => self.shadows[ix].order = draw_order as DrawOrder,
                 (PrimitiveKind::Quad, ix) => self.quads[ix].order = draw_order as DrawOrder,
+                (PrimitiveKind::Path, ix) => self.paths[ix].order = draw_order as DrawOrder,
                 (PrimitiveKind::Underline, ix) => {
                     self.underlines[ix].order = draw_order as DrawOrder
                 }
@@ -143,13 +121,88 @@ impl Scene {
             }
         }
 
-        // Sort the primitives
         self.shadows.sort_unstable();
         self.quads.sort_unstable();
+        self.paths.sort_unstable();
         self.underlines.sort_unstable();
         self.monochrome_sprites.sort_unstable();
         self.polychrome_sprites.sort_unstable();
 
+        Scene {
+            shadows: mem::take(&mut self.shadows),
+            quads: mem::take(&mut self.quads),
+            paths: mem::take(&mut self.paths),
+            underlines: mem::take(&mut self.underlines),
+            monochrome_sprites: mem::take(&mut self.monochrome_sprites),
+            polychrome_sprites: mem::take(&mut self.polychrome_sprites),
+        }
+    }
+
+    pub fn insert(&mut self, order: &StackingOrder, primitive: impl Into<Primitive>) {
+        let primitive = primitive.into();
+        let clipped_bounds = primitive
+            .bounds()
+            .intersect(&primitive.content_mask().bounds);
+        if clipped_bounds.size.width <= ScaledPixels(0.)
+            || clipped_bounds.size.height <= ScaledPixels(0.)
+        {
+            return;
+        }
+
+        let layer_id = if let Some(layer_id) = self.layers_by_order.get(order) {
+            *layer_id
+        } else {
+            let next_id = self.layers_by_order.len() as LayerId;
+            self.layers_by_order.insert(order.clone(), next_id);
+            next_id
+        };
+
+        match primitive {
+            Primitive::Shadow(mut shadow) => {
+                shadow.order = layer_id;
+                self.shadows.push(shadow);
+            }
+            Primitive::Quad(mut quad) => {
+                quad.order = layer_id;
+                self.quads.push(quad);
+            }
+            Primitive::Path(mut path) => {
+                path.order = layer_id;
+                path.id = PathId(self.paths.len());
+                self.paths.push(path);
+            }
+            Primitive::Underline(mut underline) => {
+                underline.order = layer_id;
+                self.underlines.push(underline);
+            }
+            Primitive::MonochromeSprite(mut sprite) => {
+                sprite.order = layer_id;
+                self.monochrome_sprites.push(sprite);
+            }
+            Primitive::PolychromeSprite(mut sprite) => {
+                sprite.order = layer_id;
+                self.polychrome_sprites.push(sprite);
+            }
+        }
+    }
+}
+
+pub(crate) struct Scene {
+    pub shadows: Vec<Shadow>,
+    pub quads: Vec<Quad>,
+    pub paths: Vec<Path<ScaledPixels>>,
+    pub underlines: Vec<Underline>,
+    pub monochrome_sprites: Vec<MonochromeSprite>,
+    pub polychrome_sprites: Vec<PolychromeSprite>,
+}
+
+impl Scene {
+    #[allow(dead_code)]
+    pub fn paths(&self) -> &[Path<ScaledPixels>] {
+        &self.paths
+    }
+
+    pub fn batches(&self) -> impl Iterator<Item = PrimitiveBatch> {
         BatchIterator {
             shadows: &self.shadows,
             shadows_start: 0,
@@ -157,6 +210,9 @@ impl Scene {
             quads: &self.quads,
             quads_start: 0,
             quads_iter: self.quads.iter().peekable(),
+            paths: &self.paths,
+            paths_start: 0,
+            paths_iter: self.paths.iter().peekable(),
             underlines: &self.underlines,
             underlines_start: 0,
             underlines_iter: self.underlines.iter().peekable(),
@@ -171,12 +227,15 @@ impl Scene {
 }
 
 struct BatchIterator<'a> {
-    quads: &'a [Quad],
-    quads_start: usize,
-    quads_iter: Peekable<slice::Iter<'a, Quad>>,
     shadows: &'a [Shadow],
     shadows_start: usize,
     shadows_iter: Peekable<slice::Iter<'a, Shadow>>,
+    quads: &'a [Quad],
+    quads_start: usize,
+    quads_iter: Peekable<slice::Iter<'a, Quad>>,
+    paths: &'a [Path<ScaledPixels>],
+    paths_start: usize,
+    paths_iter: Peekable<slice::Iter<'a, Path<ScaledPixels>>>,
     underlines: &'a [Underline],
     underlines_start: usize,
     underlines_iter: Peekable<slice::Iter<'a, Underline>>,
@@ -198,6 +257,7 @@ impl<'a> Iterator for BatchIterator<'a> {
                 PrimitiveKind::Shadow,
             ),
             (self.quads_iter.peek().map(|q| q.order), PrimitiveKind::Quad),
+            (self.paths_iter.peek().map(|q| q.order), PrimitiveKind::Path),
             (
                 self.underlines_iter.peek().map(|u| u.order),
                 PrimitiveKind::Underline,
@@ -250,6 +310,19 @@ impl<'a> Iterator for BatchIterator<'a> {
                 self.quads_start = quads_end;
                 Some(PrimitiveBatch::Quads(&self.quads[quads_start..quads_end]))
             }
+            PrimitiveKind::Path => {
+                let paths_start = self.paths_start;
+                let mut paths_end = paths_start;
+                while self
+                    .paths_iter
+                    .next_if(|path| path.order <= max_order)
+                    .is_some()
+                {
+                    paths_end += 1;
+                }
+                self.paths_start = paths_end;
+                Some(PrimitiveBatch::Paths(&self.paths[paths_start..paths_end]))
+            }
             PrimitiveKind::Underline => {
                 let underlines_start = self.underlines_start;
                 let mut underlines_end = underlines_start;
@@ -312,24 +385,50 @@ pub enum PrimitiveKind {
     Shadow,
     #[default]
     Quad,
+    Path,
     Underline,
     MonochromeSprite,
     PolychromeSprite,
 }
 
-#[derive(Clone, Debug)]
 pub enum Primitive {
     Shadow(Shadow),
     Quad(Quad),
+    Path(Path<ScaledPixels>),
     Underline(Underline),
     MonochromeSprite(MonochromeSprite),
     PolychromeSprite(PolychromeSprite),
 }
 
+impl Primitive {
+    pub fn bounds(&self) -> &Bounds<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.bounds,
+            Primitive::Quad(quad) => &quad.bounds,
+            Primitive::Path(path) => &path.bounds,
+            Primitive::Underline(underline) => &underline.bounds,
+            Primitive::MonochromeSprite(sprite) => &sprite.bounds,
+            Primitive::PolychromeSprite(sprite) => &sprite.bounds,
+        }
+    }
+
+    pub fn content_mask(&self) -> &ContentMask<ScaledPixels> {
+        match self {
+            Primitive::Shadow(shadow) => &shadow.content_mask,
+            Primitive::Quad(quad) => &quad.content_mask,
+            Primitive::Path(path) => &path.content_mask,
+            Primitive::Underline(underline) => &underline.content_mask,
+            Primitive::MonochromeSprite(sprite) => &sprite.content_mask,
+            Primitive::PolychromeSprite(sprite) => &sprite.content_mask,
+        }
+    }
+}
+
 #[derive(Debug)]
 pub(crate) enum PrimitiveBatch<'a> {
     Shadows(&'a [Shadow]),
     Quads(&'a [Quad]),
+    Paths(&'a [Path<ScaledPixels>]),
     Underlines(&'a [Underline]),
     MonochromeSprites {
         texture_id: AtlasTextureId,
@@ -346,7 +445,7 @@ pub(crate) enum PrimitiveBatch<'a> {
 pub struct Quad {
     pub order: u32, // Initially a LayerId, then a DrawOrder.
     pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ScaledContentMask,
+    pub content_mask: ContentMask<ScaledPixels>,
     pub background: Hsla,
     pub border_color: Hsla,
     pub corner_radii: Corners<ScaledPixels>,
@@ -376,7 +475,7 @@ impl From<Quad> for Primitive {
 pub struct Underline {
     pub order: u32,
     pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ScaledContentMask,
+    pub content_mask: ContentMask<ScaledPixels>,
     pub thickness: ScaledPixels,
     pub color: Hsla,
     pub wavy: bool,
@@ -406,7 +505,7 @@ pub struct Shadow {
     pub order: u32,
     pub bounds: Bounds<ScaledPixels>,
     pub corner_radii: Corners<ScaledPixels>,
-    pub content_mask: ScaledContentMask,
+    pub content_mask: ContentMask<ScaledPixels>,
     pub color: Hsla,
     pub blur_radius: ScaledPixels,
 }
@@ -434,7 +533,7 @@ impl From<Shadow> for Primitive {
 pub struct MonochromeSprite {
     pub order: u32,
     pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ScaledContentMask,
+    pub content_mask: ContentMask<ScaledPixels>,
     pub color: Hsla,
     pub tile: AtlasTile,
 }
@@ -465,7 +564,7 @@ impl From<MonochromeSprite> for Primitive {
 pub struct PolychromeSprite {
     pub order: u32,
     pub bounds: Bounds<ScaledPixels>,
-    pub content_mask: ScaledContentMask,
+    pub content_mask: ContentMask<ScaledPixels>,
     pub corner_radii: Corners<ScaledPixels>,
     pub tile: AtlasTile,
     pub grayscale: bool,
@@ -492,6 +591,167 @@ impl From<PolychromeSprite> for Primitive {
     }
 }
 
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub(crate) struct PathId(pub(crate) usize);
+
+#[derive(Debug)]
+pub struct Path<P: Clone + Debug> {
+    pub(crate) id: PathId,
+    order: u32,
+    pub(crate) bounds: Bounds<P>,
+    pub(crate) content_mask: ContentMask<P>,
+    pub(crate) vertices: Vec<PathVertex<P>>,
+    pub(crate) color: Hsla,
+    start: Point<P>,
+    current: Point<P>,
+    contour_count: usize,
+}
+
+impl Path<Pixels> {
+    pub fn new(start: Point<Pixels>) -> Self {
+        Self {
+            id: PathId(0),
+            order: 0,
+            vertices: Vec::new(),
+            start,
+            current: start,
+            bounds: Bounds {
+                origin: start,
+                size: Default::default(),
+            },
+            content_mask: Default::default(),
+            color: Default::default(),
+            contour_count: 0,
+        }
+    }
+
+    pub fn scale(&self, factor: f32) -> Path<ScaledPixels> {
+        Path {
+            id: self.id,
+            order: self.order,
+            bounds: self.bounds.scale(factor),
+            content_mask: self.content_mask.scale(factor),
+            vertices: self
+                .vertices
+                .iter()
+                .map(|vertex| vertex.scale(factor))
+                .collect(),
+            start: self.start.map(|start| start.scale(factor)),
+            current: self.current.scale(factor),
+            contour_count: self.contour_count,
+            color: self.color,
+        }
+    }
+
+    pub fn line_to(&mut self, to: Point<Pixels>) {
+        self.contour_count += 1;
+        if self.contour_count > 1 {
+            self.push_triangle(
+                (self.start, self.current, to),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
+        self.current = to;
+    }
+
+    pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
+        self.contour_count += 1;
+        if self.contour_count > 1 {
+            self.push_triangle(
+                (self.start, self.current, to),
+                (point(0., 1.), point(0., 1.), point(0., 1.)),
+            );
+        }
+
+        self.push_triangle(
+            (self.current, ctrl, to),
+            (point(0., 0.), point(0.5, 0.), point(1., 1.)),
+        );
+        self.current = to;
+    }
+
+    fn push_triangle(
+        &mut self,
+        xy: (Point<Pixels>, Point<Pixels>, Point<Pixels>),
+        st: (Point<f32>, Point<f32>, Point<f32>),
+    ) {
+        self.bounds = self
+            .bounds
+            .union(&Bounds {
+                origin: xy.0,
+                size: Default::default(),
+            })
+            .union(&Bounds {
+                origin: xy.1,
+                size: Default::default(),
+            })
+            .union(&Bounds {
+                origin: xy.2,
+                size: Default::default(),
+            });
+
+        self.vertices.push(PathVertex {
+            xy_position: xy.0,
+            st_position: st.0,
+            content_mask: Default::default(),
+        });
+        self.vertices.push(PathVertex {
+            xy_position: xy.1,
+            st_position: st.1,
+            content_mask: Default::default(),
+        });
+        self.vertices.push(PathVertex {
+            xy_position: xy.2,
+            st_position: st.2,
+            content_mask: Default::default(),
+        });
+    }
+}
+
+impl Eq for Path<ScaledPixels> {}
+
+impl PartialEq for Path<ScaledPixels> {
+    fn eq(&self, other: &Self) -> bool {
+        self.order == other.order
+    }
+}
+
+impl Ord for Path<ScaledPixels> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.order.cmp(&other.order)
+    }
+}
+
+impl PartialOrd for Path<ScaledPixels> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Path<ScaledPixels>> for Primitive {
+    fn from(path: Path<ScaledPixels>) -> Self {
+        Primitive::Path(path)
+    }
+}
+
+#[derive(Clone, Debug)]
+#[repr(C)]
+pub struct PathVertex<P: Clone + Debug> {
+    pub(crate) xy_position: Point<P>,
+    pub(crate) st_position: Point<f32>,
+    pub(crate) content_mask: ContentMask<P>,
+}
+
+impl PathVertex<Pixels> {
+    pub fn scale(&self, factor: f32) -> PathVertex<ScaledPixels> {
+        PathVertex {
+            xy_position: self.xy_position.scale(factor),
+            st_position: self.st_position,
+            content_mask: self.content_mask.scale(factor),
+        }
+    }
+}
+
 #[derive(Copy, Clone, Debug)]
 pub struct AtlasId(pub(crate) usize);
 
@@ -524,15 +784,15 @@ mod tests {
 
     #[test]
     fn test_scene() {
-        let mut scene = Scene::new(1.0);
-        assert_eq!(scene.layers.len(), 0);
+        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.batches() {
+        for _ in scene.build().batches() {
             batches_count += 1;
         }
         assert_eq!(batches_count, 3);

crates/gpui3/src/style.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
     phi, point, rems, AbsoluteLength, BorrowAppContext, BorrowWindow, Bounds, ContentMask, Corners,
     CornersRefinement, DefiniteLength, Edges, EdgesRefinement, Font, FontFeatures, FontStyle,
-    FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Quad, Rems, Result, RunStyle, Shadow,
-    SharedString, Size, SizeRefinement, ViewContext, WindowContext,
+    FontWeight, Hsla, Length, Pixels, Point, PointRefinement, Rems, Result, RunStyle, SharedString,
+    Size, SizeRefinement, ViewContext, WindowContext,
 };
 use refineable::Refineable;
 use smallvec::SmallVec;
@@ -245,51 +245,24 @@ impl Style {
     /// Paints the background of an element styled with this style.
     pub fn paint<V: 'static>(&self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
         let rem_size = cx.rem_size();
-        let scale = cx.scale_factor();
-
-        for shadow in &self.box_shadow {
-            let content_mask = cx.content_mask();
-            let mut shadow_bounds = bounds;
-            shadow_bounds.origin += shadow.offset;
-            shadow_bounds.dilate(shadow.spread_radius);
-            cx.stack(0, |cx| {
-                let layer_id = cx.current_stacking_order();
-                cx.scene().insert(
-                    layer_id,
-                    Shadow {
-                        order: 0,
-                        bounds: shadow_bounds.scale(scale),
-                        content_mask: content_mask.scale(scale),
-                        corner_radii: self
-                            .corner_radii
-                            .to_pixels(shadow_bounds.size, rem_size)
-                            .scale(scale),
-                        color: shadow.color,
-                        blur_radius: shadow.blur_radius.scale(scale),
-                    },
-                );
-            })
-        }
+
+        cx.stack(0, |cx| {
+            cx.paint_shadows(
+                bounds,
+                self.corner_radii.to_pixels(bounds.size, rem_size),
+                &self.box_shadow,
+            );
+        });
 
         let background_color = self.fill.as_ref().and_then(Fill::color);
         if background_color.is_some() || self.is_border_visible() {
-            let content_mask = cx.content_mask();
             cx.stack(1, |cx| {
-                let order = cx.current_stacking_order();
-                cx.scene().insert(
-                    order,
-                    Quad {
-                        order: 0,
-                        bounds: bounds.scale(scale),
-                        content_mask: content_mask.scale(scale),
-                        background: background_color.unwrap_or_default(),
-                        border_color: self.border_color.unwrap_or_default(),
-                        corner_radii: self
-                            .corner_radii
-                            .to_pixels(bounds.size, rem_size)
-                            .scale(scale),
-                        border_widths: self.border_widths.to_pixels(rem_size).scale(scale),
-                    },
+                cx.paint_quad(
+                    bounds,
+                    self.corner_radii.to_pixels(bounds.size, rem_size),
+                    background_color.unwrap_or_default(),
+                    self.border_widths.to_pixels(rem_size),
+                    self.border_color.unwrap_or_default(),
                 );
             });
         }

crates/gpui3/src/styled.rs 🔗

@@ -1,4 +1,4 @@
-use crate::{Refineable, RefinementCascade};
+use crate::{Hoverable, Refineable, RefinementCascade};
 
 pub trait Styled {
     type Style: Refineable + Default;
@@ -10,12 +10,12 @@ pub trait Styled {
         Self::Style::from_refinement(&self.style_cascade().merged())
     }
 
-    // fn hover(self) -> Hoverable<Self>
-    // where
-    //     Self: Sized,
-    // {
-    //     hoverable(self)
-    // }
+    fn hover(self) -> Hoverable<Self>
+    where
+        Self: Sized,
+    {
+        Hoverable::new(self)
+    }
 
     // fn active(self) -> Pressable<Self>
     // where

crates/gpui3/src/window.rs 🔗

@@ -1,18 +1,46 @@
 use crate::{
     image_cache::RenderImageParams, px, size, AnyView, AppContext, AsyncWindowContext,
-    AvailableSpace, BorrowAppContext, Bounds, Context, Corners, DevicePixels, DisplayId, Effect,
-    Element, EntityId, FontId, GlyphId, Handle, Hsla, ImageData, IsZero, LayoutId, MainThread,
-    MainThreadOnly, MonochromeSprite, Pixels, PlatformAtlas, PlatformWindow, Point,
-    PolychromeSprite, Reference, RenderGlyphParams, RenderSvgParams, ScaledPixels, Scene,
-    SharedString, Size, StackingOrder, Style, TaffyLayoutEngine, Task, Underline, UnderlineStyle,
-    WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+    AvailableSpace, BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId,
+    Edges, Effect, Element, EntityId, Event, FontId, GlyphId, Handle, Hsla, ImageData, IsZero,
+    LayoutId, MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, 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, future::Future, marker::PhantomData, mem, sync::Arc};
+use std::{
+    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,
@@ -23,10 +51,13 @@ 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>,
-    pub(crate) scene: Scene,
+    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,
 }
 
@@ -43,11 +74,11 @@ 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| {
-                    cx.window.scene = Scene::new(scale_factor);
+                    cx.window.scale_factor = scale_factor;
+                    cx.window.scene_builder = SceneBuilder::new();
                     cx.window.content_size = content_size;
                     cx.window.display_id = cx
                         .window
@@ -61,6 +92,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 {
@@ -72,23 +112,27 @@ 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(),
-            scene: Scene::new(scale_factor),
+            mouse_event_handlers: HashMap::default(),
+            propagate_event: true,
+            mouse_position,
+            scale_factor,
+            scene_builder: SceneBuilder::new(),
             dirty: true,
         }
     }
 }
 
-#[derive(Clone, Debug)]
-pub struct ContentMask {
-    pub bounds: Bounds<Pixels>,
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+#[repr(C)]
+pub struct ContentMask<P: Clone + Debug> {
+    pub bounds: Bounds<P>,
 }
 
-impl ContentMask {
-    pub fn scale(&self, factor: f32) -> ScaledContentMask {
-        ScaledContentMask {
+impl ContentMask<Pixels> {
+    pub fn scale(&self, factor: f32) -> ContentMask<ScaledPixels> {
+        ContentMask {
             bounds: self.bounds.scale(factor),
         }
     }
@@ -99,12 +143,6 @@ impl ContentMask {
     }
 }
 
-#[derive(Default, Clone, Debug, PartialEq, Eq)]
-#[repr(C)]
-pub struct ScaledContentMask {
-    bounds: Bounds<ScaledPixels>,
-}
-
 pub struct WindowContext<'a, 'w> {
     app: Reference<'a, AppContext>,
     window: Reference<'w, Window>,
@@ -234,19 +272,36 @@ impl<'a, 'w> WindowContext<'a, 'w> {
     }
 
     pub fn scale_factor(&self) -> f32 {
-        self.window.scene.scale_factor
+        self.window.scale_factor
     }
 
     pub fn rem_size(&self) -> Pixels {
         self.window.rem_size
     }
 
-    pub fn mouse_position(&self) -> Point<Pixels> {
-        self.window.mouse_position
+    pub fn stop_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 scene(&mut self) -> &mut Scene {
-        &mut self.window.scene
+    pub fn mouse_position(&self) -> Point<Pixels> {
+        self.window.mouse_position
     }
 
     pub fn stack<R>(&mut self, order: u32, f: impl FnOnce(&mut Self) -> R) -> R {
@@ -256,8 +311,68 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         result
     }
 
-    pub fn current_stacking_order(&self) -> StackingOrder {
-        self.window.current_stacking_order.clone()
+    pub fn paint_shadows(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        corner_radii: Corners<Pixels>,
+        shadows: &[BoxShadow],
+    ) {
+        let scale_factor = self.scale_factor();
+        let content_mask = self.content_mask();
+        let window = &mut *self.window;
+        for shadow in shadows {
+            let mut shadow_bounds = bounds;
+            shadow_bounds.origin += shadow.offset;
+            shadow_bounds.dilate(shadow.spread_radius);
+            window.scene_builder.insert(
+                &window.current_stacking_order,
+                Shadow {
+                    order: 0,
+                    bounds: shadow_bounds.scale(scale_factor),
+                    content_mask: content_mask.scale(scale_factor),
+                    corner_radii: corner_radii.scale(scale_factor),
+                    color: shadow.color,
+                    blur_radius: shadow.blur_radius.scale(scale_factor),
+                },
+            );
+        }
+    }
+
+    pub fn paint_quad(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        corner_radii: Corners<Pixels>,
+        background: impl Into<Hsla>,
+        border_widths: Edges<Pixels>,
+        border_color: impl Into<Hsla>,
+    ) {
+        let scale_factor = self.scale_factor();
+        let content_mask = self.content_mask();
+
+        let window = &mut *self.window;
+        window.scene_builder.insert(
+            &window.current_stacking_order,
+            Quad {
+                order: 0,
+                bounds: bounds.scale(scale_factor),
+                content_mask: content_mask.scale(scale_factor),
+                background: background.into(),
+                border_color: border_color.into(),
+                corner_radii: corner_radii.scale(scale_factor),
+                border_widths: border_widths.scale(scale_factor),
+            },
+        );
+    }
+
+    pub fn paint_path(&mut self, mut path: Path<Pixels>, color: impl Into<Hsla>) {
+        let scale_factor = self.scale_factor();
+        let content_mask = self.content_mask();
+        path.content_mask = content_mask;
+        path.color = color.into();
+        let window = &mut *self.window;
+        window
+            .scene_builder
+            .insert(&window.current_stacking_order, path.scale(scale_factor));
     }
 
     pub fn paint_underline(
@@ -277,9 +392,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
             size: size(width, height),
         };
         let content_mask = self.content_mask();
-        let layer_id = self.current_stacking_order();
-        self.window.scene.insert(
-            layer_id,
+        let window = &mut *self.window;
+        window.scene_builder.insert(
+            &window.current_stacking_order,
             Underline {
                 order: 0,
                 bounds: bounds.scale(scale_factor),
@@ -317,7 +432,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let layer_id = self.current_stacking_order();
             let tile =
                 self.window
                     .sprite_atlas
@@ -330,9 +444,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 size: tile.bounds.size.map(Into::into),
             };
             let content_mask = self.content_mask().scale(scale_factor);
-
-            self.window.scene.insert(
-                layer_id,
+            let window = &mut *self.window;
+            window.scene_builder.insert(
+                &window.current_stacking_order,
                 MonochromeSprite {
                     order: 0,
                     bounds,
@@ -366,7 +480,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
         let raster_bounds = self.text_system().raster_bounds(&params)?;
         if !raster_bounds.is_zero() {
-            let layer_id = self.current_stacking_order();
             let tile =
                 self.window
                     .sprite_atlas
@@ -379,9 +492,10 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 size: tile.bounds.size.map(Into::into),
             };
             let content_mask = self.content_mask().scale(scale_factor);
+            let window = &mut *self.window;
 
-            self.window.scene.insert(
-                layer_id,
+            window.scene_builder.insert(
+                &window.current_stacking_order,
                 PolychromeSprite {
                     order: 0,
                     bounds,
@@ -411,7 +525,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 .map(|pixels| DevicePixels::from((pixels.0 * 2.).ceil() as i32)),
         };
 
-        let layer_id = self.current_stacking_order();
         let tile =
             self.window
                 .sprite_atlas
@@ -421,8 +534,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 })?;
         let content_mask = self.content_mask().scale(scale_factor);
 
-        self.window.scene.insert(
-            layer_id,
+        let window = &mut *self.window;
+        window.scene_builder.insert(
+            &window.current_stacking_order,
             MonochromeSprite {
                 order: 0,
                 bounds,
@@ -446,7 +560,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         let bounds = bounds.scale(scale_factor);
         let params = RenderImageParams { image_id: data.id };
 
-        let order = self.current_stacking_order();
         let tile = self
             .window
             .sprite_atlas
@@ -456,8 +569,9 @@ impl<'a, 'w> WindowContext<'a, 'w> {
         let content_mask = self.content_mask().scale(scale_factor);
         let corner_radii = corner_radii.scale(scale_factor);
 
-        self.window.scene.insert(
-            order,
+        let window = &mut *self.window;
+        window.scene_builder.insert(
+            &window.current_stacking_order,
             PolychromeSprite {
                 order: 0,
                 bounds,
@@ -467,13 +581,17 @@ impl<'a, 'w> WindowContext<'a, 'w> {
                 grayscale,
             },
         );
-
         Ok(())
     }
 
     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);
@@ -485,7 +603,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
 
             root_view.paint(layout, &mut (), &mut frame_state, cx)?;
             cx.window.root_view = Some(root_view);
-            let scene = cx.window.scene.take();
+            let scene = cx.window.scene_builder.build();
 
             cx.run_on_main(view, |_, cx| {
                 cx.window
@@ -499,6 +617,59 @@ 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(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() {
+                self.window.mouse_position = *position;
+            }
+
+            if let Some(mut handlers) = self
+                .window
+                .mouse_event_handlers
+                .remove(&any_mouse_event.type_id())
+            {
+                // Because handlers may add other handlers, we sort every time.
+                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, where most normal handlers do their work.
+                if self.window.propagate_event {
+                    for (_, handler) in handlers.iter().rev() {
+                        handler(any_mouse_event, DispatchPhase::Bubble, self);
+                        if !self.window.propagate_event {
+                            break;
+                        }
+                    }
+                }
+
+                // Just in case any handlers added new handlers, which is weird, but possible.
+                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<'_, '_> {
@@ -557,7 +728,11 @@ pub trait BorrowWindow: BorrowAppContext {
     fn window(&self) -> &Window;
     fn window_mut(&mut self) -> &mut Window;
 
-    fn with_content_mask<R>(&mut self, mask: ContentMask, f: impl FnOnce(&mut Self) -> R) -> R {
+    fn with_content_mask<R>(
+        &mut self,
+        mask: ContentMask<Pixels>,
+        f: impl FnOnce(&mut Self) -> R,
+    ) -> R {
         let mask = mask.intersect(&self.content_mask());
         self.window_mut().content_mask_stack.push(mask);
         let result = f(self);
@@ -565,7 +740,7 @@ pub trait BorrowWindow: BorrowAppContext {
         result
     }
 
-    fn content_mask(&self) -> ContentMask {
+    fn content_mask(&self) -> ContentMask<Pixels> {
         self.window()
             .content_mask_stack
             .last()
@@ -727,6 +902,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(
@@ -815,3 +1002,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)
+    }
+}

crates/storybook2/src/collab_panel.rs 🔗

@@ -1,6 +1,7 @@
 use gpui3::{
-    div, img, svg, view, AppContext, Context, Element, IntoAnyElement, ParentElement, ScrollState,
-    SharedString, StyleHelpers, View, ViewContext, WindowContext,
+    div, img, svg, view, AppContext, Context, Element, Interactive, IntoAnyElement, MouseButton,
+    ParentElement, ScrollState, SharedString, StyleHelpers, Styled, View, ViewContext,
+    WindowContext,
 };
 use ui::{theme, Theme};
 
@@ -44,6 +45,9 @@ impl CollabPanel {
                     // List Container
                     .child(
                         div()
+                            .on_click(MouseButton::Left, |_, _, _| {
+                                dbg!("click!");
+                            })
                             .fill(theme.lowest.base.default.background)
                             .pb_1()
                             .border_color(theme.lowest.base.default.border)
@@ -126,6 +130,8 @@ impl CollabPanel {
             .flex()
             .justify_between()
             .items_center()
+            .hover()
+            .fill(theme.lowest.base.active.background)
             .child(div().flex().gap_1().text_sm().child(label))
             .child(
                 div().flex().h_full().gap_1().items_center().child(