WIP

Nathan Sobo created

Change summary

crates/editor2/src/element.rs             |   9 +
crates/gpui2/src/element.rs               |  53 +++++++
crates/gpui2/src/elements/div.rs          |  12 +
crates/gpui2/src/elements/img.rs          |   9 +
crates/gpui2/src/elements/node.rs         | 172 ++++++++++++++++++++++--
crates/gpui2/src/elements/svg.rs          |   9 +
crates/gpui2/src/elements/text.rs         |   9 +
crates/gpui2/src/elements/uniform_list.rs |  12 +
crates/gpui2/src/interactive.rs           |   2 
crates/gpui2/src/view.rs                  |  29 ++++
crates/ui2/src/components/modal.rs        |   2 
11 files changed, 296 insertions(+), 22 deletions(-)

Detailed changes

crates/editor2/src/element.rs 🔗

@@ -2640,6 +2640,15 @@ impl Element<Editor> for EditorElement {
         cx.request_layout(&style, None)
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut Editor,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<Editor>,
+    ) {
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<gpui::Pixels>,

crates/gpui2/src/element.rs 🔗

@@ -26,6 +26,14 @@ pub trait Element<V: 'static> {
         cx: &mut ViewContext<V>,
     ) -> LayoutId;
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    );
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
@@ -62,6 +70,7 @@ pub trait ParentElement<V: 'static> {
 trait ElementObject<V> {
     fn initialize(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
     fn layout(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) -> LayoutId;
+    fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
     fn paint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>);
     fn measure(
         &mut self,
@@ -199,6 +208,36 @@ where
         };
     }
 
+    fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
+        self.phase = match mem::take(&mut self.phase) {
+            ElementRenderPhase::LayoutRequested {
+                layout_id,
+                mut frame_state,
+            }
+            | ElementRenderPhase::LayoutComputed {
+                layout_id,
+                mut frame_state,
+                ..
+            } => {
+                let bounds = cx.layout_bounds(layout_id);
+                if let Some(id) = self.element.id() {
+                    cx.with_element_state(id, |element_state, cx| {
+                        let mut element_state = element_state.unwrap();
+                        self.element
+                            .prepaint(bounds, view_state, &mut element_state, cx);
+                        ((), element_state)
+                    });
+                } else {
+                    self.element
+                        .prepaint(bounds, view_state, frame_state.as_mut().unwrap(), cx);
+                }
+                ElementRenderPhase::Painted
+            }
+
+            _ => panic!("must call layout before paint"),
+        };
+    }
+
     fn measure(
         &mut self,
         available_space: Size<AvailableSpace>,
@@ -283,6 +322,10 @@ impl<V> AnyElement<V> {
         self.0.paint(view_state, cx)
     }
 
+    pub fn prepaint(&mut self, view_state: &mut V, cx: &mut ViewContext<V>) {
+        self.0.prepaint(view_state, cx)
+    }
+
     /// Initializes this element and performs layout within the given available space to determine its size.
     pub fn measure(
         &mut self,
@@ -376,6 +419,16 @@ where
         rendered_element.layout(view_state, cx)
     }
 
+    fn prepaint(
+        &mut self,
+        _bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        rendered_element: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        rendered_element.prepaint(view_state, cx)
+    }
+
     fn paint(
         &mut self,
         _bounds: Bounds<Pixels>,

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

@@ -265,6 +265,16 @@ where
         })
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        todo!()
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
@@ -309,7 +319,7 @@ where
                 cx.with_z_index(0, |cx| {
                     style.paint(bounds, cx);
                     this.key_dispatch.paint(bounds, cx);
-                    this.interactivity.paint(
+                    this.interactivity.handle_events(
                         bounds,
                         content_size,
                         style.overflow,

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

@@ -94,6 +94,15 @@ where
         self.base.layout(view_state, element_state, cx)
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,

crates/gpui2/src/elements/node.rs 🔗

@@ -10,6 +10,7 @@ use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
     marker::PhantomData,
+    mem,
     sync::Arc,
 };
 
@@ -530,24 +531,6 @@ pub struct Node<V> {
     children: Vec<AnyElement<V>>,
 }
 
-pub struct Interactivity<V> {
-    group: Option<SharedString>,
-    pub dispatch_context: KeyContext,
-    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
-    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
-    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
-    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
-    pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
-    pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
-    pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
-    pub hover_style: StyleRefinement,
-    pub group_hover_style: Option<GroupStyle>,
-    drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
-    group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
-    drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
-    scroll_offset: Point<Pixels>,
-}
-
 impl<V> Node<V> {
     fn compute_style(&self) -> Style {
         let mut style = Style::default();
@@ -610,6 +593,20 @@ impl<V: 'static> Element<V> for Node<V> {
         })
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        _: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        for child in &mut self.children {
+            child.prepaint(view_state, cx);
+        }
+        self.interactivity
+            .refine_style(&mut self.style, bounds, view_state, cx);
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
@@ -652,6 +649,7 @@ impl<V: 'static> Element<V> for Node<V> {
         cx.with_z_index(z_index, |cx| {
             cx.with_z_index(0, |cx| {
                 style.paint(bounds, cx);
+                self.interactivity.paint(bounds, cx);
             });
             cx.with_z_index(1, |cx| {
                 style.with_text_style(cx, |cx| {
@@ -673,6 +671,144 @@ impl<V: 'static> Element<V> for Node<V> {
     }
 }
 
+pub struct Interactivity<V> {
+    pub hover_style: StyleRefinement,
+    pub group_hover_style: Option<GroupStyle>,
+    pub drag_over_styles: SmallVec<[(TypeId, StyleRefinement); 2]>,
+    pub group_drag_over_styles: SmallVec<[(TypeId, GroupStyle); 2]>,
+    group: Option<SharedString>,
+    pub dispatch_context: KeyContext,
+    pub mouse_down_listeners: SmallVec<[MouseDownListener<V>; 2]>,
+    pub mouse_up_listeners: SmallVec<[MouseUpListener<V>; 2]>,
+    pub mouse_move_listeners: SmallVec<[MouseMoveListener<V>; 2]>,
+    pub scroll_wheel_listeners: SmallVec<[ScrollWheelListener<V>; 2]>,
+    pub key_down_listeners: SmallVec<[KeyDownListener<V>; 2]>,
+    pub key_up_listeners: SmallVec<[KeyUpListener<V>; 2]>,
+    pub action_listeners: SmallVec<[(TypeId, ActionListener<V>); 8]>,
+    drop_listeners: SmallVec<[(TypeId, Box<DropListener<V>>); 2]>,
+    scroll_offset: Point<Pixels>,
+}
+
+impl<V: 'static> Interactivity<V> {
+    fn refine_style(
+        &self,
+        style: &mut StyleRefinement,
+        bounds: Bounds<Pixels>,
+        cx: &mut ViewContext<V>,
+    ) {
+        let mouse_position = cx.mouse_position();
+        if let Some(group_hover) = self.group_hover_style.as_ref() {
+            if let Some(group_bounds) = GroupBounds::get(&group_hover.group, cx) {
+                if group_bounds.contains_point(&mouse_position) {
+                    style.refine(&group_hover.style);
+                }
+            }
+        }
+        if bounds.contains_point(&mouse_position) {
+            style.refine(&self.hover_style);
+        }
+
+        if let Some(drag) = cx.active_drag.take() {
+            for (state_type, group_drag_style) in &self.group_drag_over_styles {
+                if let Some(group_bounds) = GroupBounds::get(&group_drag_style.group, cx) {
+                    if *state_type == drag.view.entity_type()
+                        && group_bounds.contains_point(&mouse_position)
+                    {
+                        style.refine(&group_drag_style.style);
+                    }
+                }
+            }
+
+            for (state_type, drag_over_style) in &self.drag_over_styles {
+                if *state_type == drag.view.entity_type() && bounds.contains_point(&mouse_position)
+                {
+                    style.refine(drag_over_style);
+                }
+            }
+
+            cx.active_drag = Some(drag);
+        }
+    }
+
+    fn paint(&mut self, bounds: Bounds<Pixels>, cx: &mut ViewContext<V>) {
+        for listener in self.mouse_down_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseDownEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.mouse_up_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseUpEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.mouse_move_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &MouseMoveEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        for listener in self.scroll_wheel_listeners.drain(..) {
+            cx.on_mouse_event(move |state, event: &ScrollWheelEvent, phase, cx| {
+                listener(state, event, &bounds, phase, cx);
+            })
+        }
+
+        let hover_group_bounds = self
+            .group_hover_style
+            .as_ref()
+            .and_then(|group_hover| GroupBounds::get(&group_hover.group, cx));
+
+        if let Some(group_bounds) = hover_group_bounds {
+            let hovered = group_bounds.contains_point(&cx.mouse_position());
+            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    if group_bounds.contains_point(&event.position) != hovered {
+                        cx.notify();
+                    }
+                }
+            });
+        }
+
+        if self.hover_style.is_some()
+            || (cx.active_drag.is_some() && !self.drag_over_styles.is_empty())
+        {
+            let hovered = bounds.contains_point(&cx.mouse_position());
+            cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| {
+                if phase == DispatchPhase::Capture {
+                    if bounds.contains_point(&event.position) != hovered {
+                        cx.notify();
+                    }
+                }
+            });
+        }
+
+        if cx.active_drag.is_some() {
+            let drop_listeners = mem::take(&mut self.drop_listeners);
+            cx.on_mouse_event(move |view, event: &MouseUpEvent, phase, cx| {
+                if phase == DispatchPhase::Bubble && bounds.contains_point(&event.position) {
+                    if let Some(drag_state_type) =
+                        cx.active_drag.as_ref().map(|drag| drag.view.entity_type())
+                    {
+                        for (drop_state_type, listener) in &drop_listeners {
+                            if *drop_state_type == drag_state_type {
+                                let drag = cx
+                                    .active_drag
+                                    .take()
+                                    .expect("checked for type drag state type above");
+                                listener(view, drag.view.clone(), cx);
+                                cx.notify();
+                                cx.stop_propagation();
+                            }
+                        }
+                    }
+                }
+            });
+        }
+    }
+}
+
 #[derive(Default)]
 pub struct GroupBounds(HashMap<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
 

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

@@ -84,6 +84,15 @@ where
         self.base.layout(view_state, element_state, cx)
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,

crates/gpui2/src/elements/text.rs 🔗

@@ -121,6 +121,15 @@ impl<V: 'static> Element<V> for Text<V> {
         layout_id
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+    }
+
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,

crates/gpui2/src/elements/uniform_list.rs 🔗

@@ -152,6 +152,16 @@ impl<V: 'static> Element<V> for UniformList<V> {
         )
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut V,
+        element_state: &mut Self::ElementState,
+        cx: &mut ViewContext<V>,
+    ) {
+        todo!()
+    }
+
     fn paint(
         &mut self,
         bounds: crate::Bounds<crate::Pixels>,
@@ -229,7 +239,7 @@ impl<V: 'static> Element<V> for UniformList<V> {
             let overflow = point(style.overflow.x, Overflow::Scroll);
 
             cx.with_z_index(0, |cx| {
-                self.interactivity.paint(
+                self.interactivity.handle_events(
                     bounds,
                     content_size,
                     overflow,

crates/gpui2/src/interactive.rs 🔗

@@ -450,7 +450,7 @@ pub trait ElementInteractivity<V: 'static>: 'static {
         }
     }
 
-    fn paint(
+    fn handle_events(
         &mut self,
         bounds: Bounds<Pixels>,
         content_size: Size<Pixels>,

crates/gpui2/src/view.rs 🔗

@@ -147,6 +147,7 @@ pub struct AnyView {
     model: AnyModel,
     initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
     layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId,
+    prepaint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
     paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
 }
 
@@ -156,6 +157,7 @@ impl AnyView {
             model: self.model.downgrade(),
             initialize: self.initialize,
             layout: self.layout,
+            prepaint: self.prepaint,
             paint: self.paint,
         }
     }
@@ -167,6 +169,7 @@ impl AnyView {
                 model,
                 initialize: self.initialize,
                 layout: self.layout,
+                prepaint: self.prepaint,
                 paint: self.paint,
             }),
         }
@@ -198,6 +201,7 @@ impl<V: Render> From<View<V>> for AnyView {
             model: value.model.into_any(),
             initialize: any_view::initialize::<V>,
             layout: any_view::layout::<V>,
+            prepaint: any_view::prepaint::<V>,
             paint: any_view::paint::<V>,
         }
     }
@@ -228,6 +232,16 @@ impl<ParentViewState: 'static> Element<ParentViewState> for AnyView {
         (self.layout)(self, rendered_element, cx)
     }
 
+    fn prepaint(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        view_state: &mut ParentViewState,
+        rendered_element: &mut Self::ElementState,
+        cx: &mut ViewContext<ParentViewState>,
+    ) {
+        (self.prepaint)(self, rendered_element, cx)
+    }
+
     fn paint(
         &mut self,
         _bounds: Bounds<Pixels>,
@@ -243,6 +257,7 @@ pub struct AnyWeakView {
     model: AnyWeakModel,
     initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
     layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId,
+    prepaint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
     paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
 }
 
@@ -253,6 +268,7 @@ impl AnyWeakView {
             model,
             initialize: self.initialize,
             layout: self.layout,
+            prepaint: self.prepaint,
             paint: self.paint,
         })
     }
@@ -264,6 +280,7 @@ impl<V: Render> From<WeakView<V>> for AnyWeakView {
             model: view.model.into(),
             initialize: any_view::initialize::<V>,
             layout: any_view::layout::<V>,
+            prepaint: any_view::prepaint::<V>,
             paint: any_view::paint::<V>,
         }
     }
@@ -309,6 +326,18 @@ mod any_view {
         })
     }
 
+    pub(crate) fn prepaint<V: Render>(
+        view: &AnyView,
+        element: &mut Box<dyn Any>,
+        cx: &mut WindowContext,
+    ) {
+        cx.with_element_id(view.model.entity_id, |_, cx| {
+            let view = view.clone().downcast::<V>().unwrap();
+            let element = element.downcast_mut::<AnyElement<V>>().unwrap();
+            view.update(cx, |view, cx| element.prepaint(view, cx))
+        })
+    }
+
     pub(crate) fn paint<V: Render>(
         view: &AnyView,
         element: &mut Box<dyn Any>,

crates/ui2/src/components/modal.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::AnyElement;
+use gpui::{AnyElement, Pixels};
 use smallvec::SmallVec;
 
 use crate::{h_stack, prelude::*, v_stack, Button, Icon, IconButton, Label};