Checkpoint

Nathan Sobo and Antonio Scandurra created

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>

Change summary

crates/gpui/playground/src/adapter.rs       |  2 
crates/gpui/playground/src/div.rs           | 39 +++-----
crates/gpui/playground/src/element.rs       | 94 +++++++++++-----------
crates/gpui/playground/src/hoverable.rs     | 26 +++--
crates/gpui/playground/src/interactive.rs   | 34 ++++++++
crates/gpui/playground/src/paint_context.rs | 52 +++---------
crates/gpui/playground/src/playground.rs    |  1 
crates/gpui/src/app.rs                      | 37 ++++++--
crates/gpui/src/app/window.rs               | 39 +++------
crates/gpui/src/scene.rs                    | 32 ------
10 files changed, 172 insertions(+), 184 deletions(-)

Detailed changes

crates/gpui/playground/src/adapter.rs 🔗

@@ -47,7 +47,7 @@ impl<V: 'static> gpui::Element<V> for AdapterElement<V> {
         let (layout_engine, layout_id) = layout_data.take().unwrap();
         legacy_cx.push_layout_engine(layout_engine);
         let mut cx = PaintContext::new(legacy_cx, scene);
-        self.0.paint(view, layout_id, &mut cx);
+        self.0.paint(view, &mut cx);
         *layout_data = legacy_cx.pop_layout_engine().zip(Some(layout_id));
         debug_assert!(layout_data.is_some());
     }

crates/gpui/playground/src/div.rs 🔗

@@ -1,22 +1,24 @@
 use crate::{
     element::{AnyElement, Element, Layout, ParentElement},
+    interactive::{InteractionHandlers, Interactive},
     layout_context::LayoutContext,
     paint_context::PaintContext,
     style::{Style, StyleHelpers, StyleRefinement, Styleable},
 };
 use anyhow::Result;
-use gpui::{platform::MouseMovedEvent, EventContext, LayoutId};
+use gpui::LayoutId;
 use smallvec::SmallVec;
-use std::rc::Rc;
 
-pub struct Div<V> {
+pub struct Div<V: 'static> {
     style: StyleRefinement,
+    handlers: InteractionHandlers<V>,
     children: SmallVec<[AnyElement<V>; 2]>,
 }
 
 pub fn div<V>() -> Div<V> {
     Div {
         style: Default::default(),
+        handlers: Default::default(),
         children: Default::default(),
     }
 }
@@ -44,45 +46,34 @@ impl<V: 'static> Element<V> for Div<V> {
         let style = self.style();
 
         style.paint_background::<V, Self>(layout, cx);
+        for child in &mut self.children {
+            child.paint(view, cx);
+        }
     }
 }
 
 impl<V> Styleable for Div<V> {
     type Style = Style;
 
-    fn declared_style(&mut self) -> &mut crate::style::StyleRefinement {
+    fn declared_style(&mut self) -> &mut StyleRefinement {
         &mut self.style
     }
 }
 
 impl<V> StyleHelpers for Div<V> {}
 
-impl<V: 'static> ParentElement<V> for Div<V> {
-    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
-        &mut self.children
+impl<V> Interactive<V> for Div<V> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V> {
+        &mut self.handlers
     }
 }
 
-pub trait Interactive<V> {
-    fn declared_interactions(&mut self) -> &mut Interactions<V>;
-
-    fn on_mouse_move<H>(mut self, handler: H) -> Self
-    where
-        H: 'static + Fn(&mut V, &MouseMovedEvent, &mut EventContext<V>),
-        Self: Sized,
-    {
-        self.declared_interactions().mouse_moved = Some(Rc::new(move |view, event, cx| {
-            handler(view, event, cx);
-            cx.bubble
-        }));
-        self
+impl<V: 'static> ParentElement<V> for Div<V> {
+    fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]> {
+        &mut self.children
     }
 }
 
-pub struct Interactions<V> {
-    mouse_moved: Option<Rc<dyn Fn(&mut V, &MouseMovedEvent, &mut EventContext<V>) -> bool>>,
-}
-
 #[test]
 fn test() {
     // let elt = div().w_auto();

crates/gpui/playground/src/element.rs 🔗

@@ -10,48 +10,6 @@ pub use crate::paint_context::PaintContext;
 
 type LayoutId = gpui::LayoutId;
 
-#[derive(Deref, DerefMut)]
-pub struct Layout<V, D> {
-    id: LayoutId,
-    engine_layout: Option<EngineLayout>,
-    #[deref]
-    #[deref_mut]
-    element_data: D,
-    view_type: PhantomData<V>,
-}
-
-impl<V: 'static, D> Layout<V, D> {
-    pub fn new(id: LayoutId, element_data: D) -> Self {
-        Self {
-            id,
-            engine_layout: None,
-            element_data: element_data,
-            view_type: PhantomData,
-        }
-    }
-
-    pub fn bounds(&mut self, cx: &mut PaintContext<V>) -> RectF {
-        self.engine_layout(cx).bounds
-    }
-
-    pub fn order(&mut self, cx: &mut PaintContext<V>) -> u32 {
-        self.engine_layout(cx).order
-    }
-
-    fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
-        self.engine_layout
-            .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
-    }
-}
-
-impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
-    pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
-        let mut element = self.element_data.take().unwrap();
-        element.paint(view, self.id, cx);
-        self.element_data = Some(element);
-    }
-}
-
 pub trait Element<V: 'static>: 'static {
     type Layout;
 
@@ -85,7 +43,7 @@ pub trait Element<V: 'static>: 'static {
 /// Used to make ElementState<V, E> into a trait object, so we can wrap it in AnyElement<V>.
 trait ElementStateObject<V> {
     fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<LayoutId>;
-    fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext<V>);
+    fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>);
 }
 
 /// A wrapper around an element that stores its layout state.
@@ -103,10 +61,10 @@ impl<V, E: Element<V>> ElementStateObject<V> for ElementState<V, E> {
         Ok(layout_id)
     }
 
-    fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext<V>) {
+    fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
         let layout = self.layout.as_mut().expect("paint called before layout");
         if layout.engine_layout.is_none() {
-            layout.engine_layout = cx.computed_layout(layout_id).log_err()
+            layout.engine_layout = cx.computed_layout(layout.id).log_err()
         }
         self.element.paint(view, layout, cx)
     }
@@ -120,8 +78,50 @@ impl<V> AnyElement<V> {
         self.0.layout(view, cx)
     }
 
-    pub fn paint(&mut self, view: &mut V, layout_id: LayoutId, cx: &mut PaintContext<V>) {
-        self.0.paint(view, layout_id, cx)
+    pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
+        self.0.paint(view, cx)
+    }
+}
+
+#[derive(Deref, DerefMut)]
+pub struct Layout<V, D> {
+    id: LayoutId,
+    engine_layout: Option<EngineLayout>,
+    #[deref]
+    #[deref_mut]
+    element_data: D,
+    view_type: PhantomData<V>,
+}
+
+impl<V: 'static, D> Layout<V, D> {
+    pub fn new(id: LayoutId, element_data: D) -> Self {
+        Self {
+            id,
+            engine_layout: None,
+            element_data: element_data,
+            view_type: PhantomData,
+        }
+    }
+
+    pub fn bounds(&mut self, cx: &mut PaintContext<V>) -> RectF {
+        self.engine_layout(cx).bounds
+    }
+
+    pub fn order(&mut self, cx: &mut PaintContext<V>) -> u32 {
+        self.engine_layout(cx).order
+    }
+
+    fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
+        self.engine_layout
+            .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
+    }
+}
+
+impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
+    pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
+        let mut element = self.element_data.take().unwrap();
+        element.paint(view, cx);
+        self.element_data = Some(element);
     }
 }
 

crates/gpui/playground/src/hoverable.rs 🔗

@@ -42,14 +42,6 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
     where
         Self: Sized,
     {
-        if self.hovered.get() {
-            // If hovered, refine the child's style with this element's style.
-            self.child.declared_style().refine(&self.hovered_style);
-        } else {
-            // Otherwise, set the child's style back to its original style.
-            *self.child.declared_style() = self.child_style.clone();
-        }
-
         self.child.layout(view, cx)
     }
 
@@ -61,10 +53,24 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<V, E> {
     ) where
         Self: Sized,
     {
+        if self.hovered.get() {
+            // If hovered, refine the child's style with this element's style.
+            self.child.declared_style().refine(&self.hovered_style);
+        } else {
+            // Otherwise, set the child's style back to its original style.
+            *self.child.declared_style() = self.child_style.clone();
+        }
+
         let bounds = layout.bounds(cx);
         let order = layout.order(cx);
         self.hovered.set(bounds.contains_point(cx.mouse_position()));
-        let hovered = self.hovered.clone();
-        cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {});
+        let was_hovered = self.hovered.clone();
+        cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {
+            let is_hovered = bounds.contains_point(event.position);
+            if is_hovered != was_hovered.get() {
+                was_hovered.set(is_hovered);
+                cx.repaint();
+            }
+        });
     }
 }

crates/gpui/playground/src/interactive.rs 🔗

@@ -0,0 +1,34 @@
+use gpui::{platform::MouseMovedEvent, EventContext};
+use smallvec::SmallVec;
+use std::rc::Rc;
+
+pub trait Interactive<V: 'static> {
+    fn interaction_handlers(&mut self) -> &mut InteractionHandlers<V>;
+
+    fn on_mouse_move<H>(mut self, handler: H) -> Self
+    where
+        H: 'static + Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>),
+        Self: Sized,
+    {
+        self.interaction_handlers()
+            .mouse_moved
+            .push(Rc::new(move |view, event, hit_test, cx| {
+                handler(view, event, hit_test, cx);
+                cx.bubble
+            }));
+        self
+    }
+}
+
+pub struct InteractionHandlers<V: 'static> {
+    mouse_moved:
+        SmallVec<[Rc<dyn Fn(&mut V, &MouseMovedEvent, bool, &mut EventContext<V>) -> bool>; 2]>,
+}
+
+impl<V> Default for InteractionHandlers<V> {
+    fn default() -> Self {
+        Self {
+            mouse_moved: Default::default(),
+        }
+    }
+}

crates/gpui/playground/src/paint_context.rs 🔗

@@ -1,9 +1,6 @@
 use anyhow::{anyhow, Result};
 use derive_more::{Deref, DerefMut};
-use gpui::{
-    geometry::rect::RectF, scene::EventHandler, EngineLayout, EventContext, LayoutId,
-    RenderContext, ViewContext,
-};
+use gpui::{scene::EventHandler, EngineLayout, EventContext, LayoutId, RenderContext, ViewContext};
 pub use gpui::{LayoutContext, PaintContext as LegacyPaintContext};
 use std::{any::TypeId, rc::Rc};
 pub use taffy::tree::NodeId;
@@ -47,48 +44,25 @@ impl<'a, 'b, 'c, 'd, V: 'static> PaintContext<'a, 'b, 'c, 'd, V> {
         order: u32,
         handler: impl Fn(&mut V, &E, &mut ViewContext<V>) + 'static,
     ) {
+        let view = self.weak_handle();
+
         self.scene.event_handlers.push(EventHandler {
             order,
-            handler: Rc::new(move |view, event, window_cx, view_id| {
-                let mut view_context = ViewContext::mutable(window_cx, view_id);
-                handler(
-                    view.downcast_mut().unwrap(),
-                    event.downcast_ref().unwrap(),
-                    &mut view_context,
-                );
+            handler: Rc::new(move |event, window_cx| {
+                if let Some(view) = view.upgrade(window_cx) {
+                    view.update(window_cx, |view, view_cx| {
+                        let mut event_cx = EventContext::new(view_cx);
+                        handler(view, event.downcast_ref().unwrap(), &mut event_cx);
+                        event_cx.bubble
+                    })
+                } else {
+                    true
+                }
             }),
             event_type: TypeId::of::<E>(),
         })
     }
 
-    pub fn draw_interactive_region<E: 'static>(
-        &mut self,
-        order: u32,
-        bounds: RectF,
-        outside_bounds: bool,
-        event_handler: impl Fn(&mut V, &E, &mut EventContext<V>) + 'static,
-    ) {
-        // We'll sort these later when `take_interactive_regions` is called.
-        self.scene
-            .interactive_regions
-            .push(gpui::scene::InteractiveRegion {
-                order,
-                bounds,
-                outside_bounds,
-                event_handler: Rc::new(move |view, event, window_cx, view_id| {
-                    let mut view_context = ViewContext::mutable(window_cx, view_id);
-                    let mut event_context = EventContext::new(&mut view_context);
-                    event_handler(
-                        view.downcast_mut().unwrap(),
-                        event.downcast_ref().unwrap(),
-                        &mut event_context,
-                    );
-                }),
-                event_type: TypeId::of::<E>(),
-                view_id: self.view_id(),
-            });
-    }
-
     pub(crate) fn computed_layout(&mut self, layout_id: LayoutId) -> Result<EngineLayout> {
         self.layout_engine()
             .ok_or_else(|| anyhow!("no layout engine present"))?

crates/gpui/src/app.rs 🔗

@@ -4680,18 +4680,37 @@ impl<V: 'static> WeakViewHandle<V> {
         })
     }
 
-    pub fn update<T>(
+    pub fn update<T, B>(
         &self,
-        cx: &mut AsyncAppContext,
+        cx: &mut B,
         update: impl FnOnce(&mut V, &mut ViewContext<V>) -> T,
-    ) -> Result<T> {
-        cx.update(|cx| {
-            let handle = cx
-                .upgrade_view_handle(self)
-                .ok_or_else(|| anyhow!("view was dropped"))?;
-            cx.update_window(self.window, |cx| handle.update(cx, update))
-                .ok_or_else(|| anyhow!("window was removed"))
+    ) -> Result<T>
+    where
+        B: BorrowWindowContext,
+        B::Result<Option<T>>: Flatten<T>,
+    {
+        cx.update_window(self.window(), |cx| {
+            cx.upgrade_view_handle(self)
+                .map(|handle| handle.update(cx, update))
         })
+        .flatten()
+        .ok_or_else(|| anyhow!("window was removed"))
+    }
+}
+
+pub trait Flatten<T> {
+    fn flatten(self) -> Option<T>;
+}
+
+impl<T> Flatten<T> for Option<Option<T>> {
+    fn flatten(self) -> Option<T> {
+        self.flatten()
+    }
+}
+
+impl<T> Flatten<T> for Option<T> {
+    fn flatten(self) -> Option<T> {
+        self
     }
 }
 

crates/gpui/src/app/window.rs 🔗

@@ -8,9 +8,9 @@ use crate::{
         MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
     },
     scene::{
-        CursorRegion, InteractiveRegion, MouseClick, MouseClickOut, MouseDown, MouseDownOut,
-        MouseDrag, MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp,
-        MouseUpOut, Scene,
+        CursorRegion, EventHandler, MouseClick, MouseClickOut, MouseDown, MouseDownOut, MouseDrag,
+        MouseEvent, MouseHover, MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut,
+        Scene,
     },
     text_layout::TextLayoutCache,
     util::post_inc,
@@ -57,7 +57,7 @@ pub struct Window {
     appearance: Appearance,
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
-    interactive_regions: Vec<InteractiveRegion>,
+    event_handlers: Vec<EventHandler>,
     last_mouse_moved_event: Option<Event>,
     pub(crate) hovered_region_ids: Vec<MouseRegionId>,
     pub(crate) clicked_region_ids: Vec<MouseRegionId>,
@@ -91,7 +91,7 @@ impl Window {
             rendered_views: Default::default(),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
-            interactive_regions: Vec::new(),
+            event_handlers: Default::default(),
             text_layout_cache: TextLayoutCache::new(cx.font_system.clone()),
             last_mouse_moved_event: None,
             hovered_region_ids: Default::default(),
@@ -119,8 +119,8 @@ impl Window {
             .expect("root_view called during window construction")
     }
 
-    pub fn take_interactive_regions(&mut self) -> Vec<InteractiveRegion> {
-        mem::take(&mut self.interactive_regions)
+    pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
+        mem::take(&mut self.event_handlers)
     }
 }
 
@@ -889,26 +889,13 @@ impl<'a> WindowContext<'a> {
     fn dispatch_to_interactive_regions(&mut self, event: &Event) {
         if let Some(mouse_event) = event.mouse_event() {
             let mouse_position = event.position().expect("mouse events must have a position");
-            let interactive_regions = self.window.take_interactive_regions();
-
-            for region in interactive_regions.iter().rev() {
-                if region.event_type == mouse_event.type_id() {
-                    let in_bounds = region.bounds.contains_point(mouse_position);
-
-                    if in_bounds == !region.outside_bounds {
-                        self.update_any_view(region.view_id, |view, window_cx| {
-                            (region.event_handler)(
-                                view.as_any_mut(),
-                                mouse_event,
-                                window_cx,
-                                region.view_id,
-                            );
-                        });
-                    }
+            let event_handlers = self.window.take_event_handlers();
+            for event_handler in event_handlers.iter().rev() {
+                if event_handler.event_type == mouse_event.type_id() {
+                    (event_handler.handler)(mouse_event, self);
                 }
             }
-
-            self.window.interactive_regions = interactive_regions;
+            self.window.event_handlers = event_handlers;
         }
     }
 
@@ -1066,7 +1053,7 @@ impl<'a> WindowContext<'a> {
         let mut scene = scene_builder.build();
         self.window.cursor_regions = scene.cursor_regions();
         self.window.mouse_regions = scene.mouse_regions();
-        self.window.interactive_regions = scene.take_interactive_regions();
+        self.window.event_handlers = scene.take_event_handlers();
 
         if self.window_is_active() {
             if let Some(event) = self.window.last_mouse_moved_event.clone() {

crates/gpui/src/scene.rs 🔗

@@ -31,8 +31,6 @@ pub struct SceneBuilder {
     scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
     active_stacking_context_stack: Vec<usize>,
-    /// Used by the playground crate. I hope to replace it with event_handlers.
-    pub interactive_regions: Vec<InteractiveRegion>,
     /// Used by the playground crate.
     pub event_handlers: Vec<EventHandler>,
     #[cfg(debug_assertions)]
@@ -42,7 +40,6 @@ pub struct SceneBuilder {
 pub struct Scene {
     scale_factor: f32,
     stacking_contexts: Vec<StackingContext>,
-    interactive_regions: Vec<InteractiveRegion>,
     event_handlers: Vec<EventHandler>,
 }
 
@@ -285,15 +282,9 @@ impl Scene {
             .collect()
     }
 
-    /// TODO: Hoping to replace this with take_event_handlers
-    pub fn take_interactive_regions(&mut self) -> Vec<InteractiveRegion> {
-        self.interactive_regions
-            .sort_by(|a, b| a.order.cmp(&b.order));
-        std::mem::take(&mut self.interactive_regions)
-    }
-
     pub fn take_event_handlers(&mut self) -> Vec<EventHandler> {
-        self.event_handlers.sort_by(|a, b| a.order.cmp(&b.order));
+        self.event_handlers
+            .sort_by(|a, b| a.order.cmp(&b.order).reverse());
         std::mem::take(&mut self.event_handlers)
     }
 }
@@ -307,7 +298,6 @@ impl SceneBuilder {
             active_stacking_context_stack: vec![0],
             #[cfg(debug_assertions)]
             mouse_region_ids: Default::default(),
-            interactive_regions: Vec::new(),
             event_handlers: Vec::new(),
         }
     }
@@ -318,7 +308,6 @@ impl SceneBuilder {
         Scene {
             scale_factor: self.scale_factor,
             stacking_contexts: self.stacking_contexts,
-            interactive_regions: self.interactive_regions,
             event_handlers: self.event_handlers,
         }
     }
@@ -716,23 +705,10 @@ impl MouseRegion {
     }
 }
 
-/// This is currently only used in the playground crate. It represents a region
-/// with which the user can interact via a pointing device. It aims to replace
-/// MouseRegion and CursorRegion.
-pub struct InteractiveRegion {
-    pub order: u32,
-    pub bounds: RectF,
-    pub outside_bounds: bool,
-    pub event_handler: Rc<dyn Fn(&mut dyn Any, &dyn Any, &mut WindowContext, usize)>,
-    pub event_type: TypeId,
-    pub view_id: usize,
-}
-
 pub struct EventHandler {
     pub order: u32,
-    // First param is a dynamic view reference
-    // Second param is a dynamic event reference
-    pub handler: Rc<dyn Fn(&mut dyn Any, &dyn Any, &mut WindowContext, usize)>,
+    // The &dyn Any parameter below expects an event.
+    pub handler: Rc<dyn Fn(&dyn Any, &mut WindowContext) -> bool>,
     pub event_type: TypeId,
 }