Introduce autoscroll support for elements (#10889)

Antonio Scandurra and Nathan created

This pull request introduces the new
`ElementContext::request_autoscroll(bounds)` and
`ElementContext::take_autoscroll()` methods in GPUI. These new APIs
enable container elements such as `List` to change their scroll position
if one of their children requested an autoscroll. We plan to use this in
the revamped assistant.

As a drive-by, we also:

- Renamed `Element::before_layout` to `Element::request_layout`
- Renamed `Element::after_layout` to `Element::prepaint`
- Introduced a new `List::splice_focusable` method to splice focusable
elements into the list, which enables rendering offscreen elements that
are focused.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>

Change summary

crates/assistant/src/assistant_panel.rs       |   2 
crates/editor/src/editor.rs                   |   2 
crates/editor/src/element.rs                  | 106 ++++--
crates/editor/src/scroll.rs                   |   2 
crates/editor/src/scroll/autoscroll.rs        |   4 
crates/extensions_ui/src/extensions_ui.rs     |   2 
crates/gpui/src/app/test_context.rs           |   3 
crates/gpui/src/element.rs                    | 208 ++++++------
crates/gpui/src/elements/anchored.rs          |  25 
crates/gpui/src/elements/animation.rs         |  22 
crates/gpui/src/elements/canvas.rs            |  27 
crates/gpui/src/elements/deferred.rs          |  16 
crates/gpui/src/elements/div.rs               |  76 ++--
crates/gpui/src/elements/img.rs               |  18 
crates/gpui/src/elements/list.rs              | 343 ++++++++++++++------
crates/gpui/src/elements/svg.rs               |  16 
crates/gpui/src/elements/text.rs              |  54 +-
crates/gpui/src/elements/uniform_list.rs      |  27 
crates/gpui/src/key_dispatch.rs               |  13 
crates/gpui/src/taffy.rs                      |   2 
crates/gpui/src/text_system.rs                |   4 
crates/gpui/src/text_system/line_layout.rs    |   8 
crates/gpui/src/view.rs                       |  63 +-
crates/gpui/src/window.rs                     |   7 
crates/gpui/src/window/element_cx.rs          | 114 ++++--
crates/language_tools/src/syntax_tree_view.rs |   2 
crates/terminal_view/src/terminal_element.rs  |  22 
crates/ui/src/components/popover_menu.rs      |  33 +
crates/ui/src/components/right_click_menu.rs  |  35 +
crates/workspace/src/pane_group.rs            |  19 
crates/workspace/src/workspace.rs             |  18 
31 files changed, 780 insertions(+), 513 deletions(-)

Detailed changes

crates/assistant/src/assistant_panel.rs 🔗

@@ -1108,7 +1108,7 @@ impl AssistantPanel {
                             )
                             .track_scroll(scroll_handle)
                             .into_any_element();
-                            saved_conversations.layout(
+                            saved_conversations.prepaint_as_root(
                                 bounds.origin,
                                 bounds.size.map(AvailableSpace::Definite),
                                 cx,

crates/editor/src/editor.rs 🔗

@@ -10801,7 +10801,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren
 
         let icon_size = buttons(&diagnostic, cx.block_id)
             .into_any_element()
-            .measure(AvailableSpace::min_size(), cx);
+            .layout_as_root(AvailableSpace::min_size(), cx);
 
         h_flex()
             .id(cx.block_id)

crates/editor/src/element.rs 🔗

@@ -864,7 +864,7 @@ impl EditorElement {
                         }),
                     )
                     .into_any();
-                hover_element.layout(fold_bounds.origin, fold_bounds.size.into(), cx);
+                hover_element.prepaint_as_root(fold_bounds.origin, fold_bounds.size.into(), cx);
                 Some(FoldLayout {
                     display_range,
                     hover_element,
@@ -882,12 +882,15 @@ impl EditorElement {
         line_layouts: &[LineWithInvisibles],
         text_hitbox: &Hitbox,
         content_origin: gpui::Point<Pixels>,
+        scroll_position: gpui::Point<f32>,
         scroll_pixel_position: gpui::Point<Pixels>,
         line_height: Pixels,
         em_width: Pixels,
+        autoscroll_containing_element: bool,
         cx: &mut ElementContext,
     ) -> Vec<CursorLayout> {
-        self.editor.update(cx, |editor, cx| {
+        let mut autoscroll_bounds = None;
+        let cursor_layouts = self.editor.update(cx, |editor, cx| {
             let mut cursors = Vec::new();
             for (player_color, selections) in selections {
                 for selection in selections {
@@ -932,7 +935,7 @@ impl EditorElement {
                                         cursor_row_layout.font_size,
                                         &[TextRun {
                                             len,
-                                            font: font,
+                                            font,
                                             color: self.style.background,
                                             background_color: None,
                                             strikethrough: None,
@@ -953,7 +956,27 @@ impl EditorElement {
                         editor.pixel_position_of_newest_cursor = Some(point(
                             text_hitbox.origin.x + x + block_width / 2.,
                             text_hitbox.origin.y + y + line_height / 2.,
-                        ))
+                        ));
+
+                        if autoscroll_containing_element {
+                            let top = text_hitbox.origin.y
+                                + (cursor_position.row() as f32 - scroll_position.y - 3.).max(0.)
+                                    * line_height;
+                            let left = text_hitbox.origin.x
+                                + (cursor_position.column() as f32 - scroll_position.x - 3.)
+                                    .max(0.)
+                                    * em_width;
+
+                            let bottom = text_hitbox.origin.y
+                                + (cursor_position.row() as f32 - scroll_position.y + 4.)
+                                    * line_height;
+                            let right = text_hitbox.origin.x
+                                + (cursor_position.column() as f32 - scroll_position.x + 4.)
+                                    * em_width;
+
+                            autoscroll_bounds =
+                                Some(Bounds::from_corners(point(left, top), point(right, bottom)))
+                        }
                     }
 
                     let mut cursor = CursorLayout {
@@ -975,7 +998,13 @@ impl EditorElement {
                 }
             }
             cursors
-        })
+        });
+
+        if let Some(bounds) = autoscroll_bounds {
+            cx.request_autoscroll(bounds);
+        }
+
+        cursor_layouts
     }
 
     fn layout_scrollbar(
@@ -1073,7 +1102,7 @@ impl EditorElement {
                     AvailableSpace::MinContent,
                     AvailableSpace::Definite(line_height * 0.55),
                 );
-                let fold_indicator_size = fold_indicator.measure(available_space, cx);
+                let fold_indicator_size = fold_indicator.layout_as_root(available_space, cx);
 
                 let position = point(
                     gutter_dimensions.width - gutter_dimensions.right_padding,
@@ -1086,7 +1115,7 @@ impl EditorElement {
                     (line_height - fold_indicator_size.height) / 2.,
                 );
                 let origin = gutter_hitbox.origin + position + centering_offset;
-                fold_indicator.layout(origin, available_space, cx);
+                fold_indicator.prepaint_as_root(origin, available_space, cx);
             }
         }
 
@@ -1177,7 +1206,7 @@ impl EditorElement {
         let absolute_offset = point(start_x, start_y);
         let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
 
-        element.layout(absolute_offset, available_space, cx);
+        element.prepaint_as_root(absolute_offset, available_space, cx);
 
         Some(element)
     }
@@ -1233,7 +1262,11 @@ impl EditorElement {
                     let start_y = ix as f32 * line_height - (scroll_top % line_height);
                     let absolute_offset = gutter_hitbox.origin + point(start_x, start_y);
 
-                    element.layout(absolute_offset, size(width, AvailableSpace::MinContent), cx);
+                    element.prepaint_as_root(
+                        absolute_offset,
+                        size(width, AvailableSpace::MinContent),
+                        cx,
+                    );
 
                     Some(element)
                 } else {
@@ -1269,7 +1302,7 @@ impl EditorElement {
             AvailableSpace::MinContent,
             AvailableSpace::Definite(line_height),
         );
-        let indicator_size = button.measure(available_space, cx);
+        let indicator_size = button.layout_as_root(available_space, cx);
 
         let blame_width = gutter_dimensions
             .git_blame_entries_width
@@ -1284,7 +1317,7 @@ impl EditorElement {
         let mut y = newest_selection_head.row() as f32 * line_height - scroll_pixel_position.y;
         y += (line_height - indicator_size.height) / 2.;
 
-        button.layout(gutter_hitbox.origin + point(x, y), available_space, cx);
+        button.prepaint_as_root(gutter_hitbox.origin + point(x, y), available_space, cx);
         Some(button)
     }
 
@@ -1773,7 +1806,7 @@ impl EditorElement {
                 }
             };
 
-            let size = element.measure(available_space, cx);
+            let size = element.layout_as_root(available_space, cx);
             (element, size)
         };
 
@@ -1843,7 +1876,9 @@ impl EditorElement {
             if !matches!(block.style, BlockStyle::Sticky) {
                 origin += point(-scroll_pixel_position.x, Pixels::ZERO);
             }
-            block.element.layout(origin, block.available_space, cx);
+            block
+                .element
+                .prepaint_as_root(origin, block.available_space, cx);
         }
     }
 
@@ -1875,7 +1910,7 @@ impl EditorElement {
         };
 
         let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent);
-        let context_menu_size = context_menu.measure(available_space, cx);
+        let context_menu_size = context_menu.layout_as_root(available_space, cx);
 
         let cursor_row_layout = &line_layouts[(position.row() - start_row) as usize].line;
         let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_pixel_position.x;
@@ -1910,7 +1945,7 @@ impl EditorElement {
         .with_priority(1)
         .into_any();
 
-        element.layout(gpui::Point::default(), AvailableSpace::min_size(), cx);
+        element.prepaint_as_root(gpui::Point::default(), AvailableSpace::min_size(), cx);
         Some(element)
     }
 
@@ -1972,7 +2007,7 @@ impl EditorElement {
         let mut overall_height = Pixels::ZERO;
         let mut measured_hover_popovers = Vec::new();
         for mut hover_popover in hover_popovers {
-            let size = hover_popover.measure(available_space, cx);
+            let size = hover_popover.layout_as_root(available_space, cx);
             let horizontal_offset =
                 (text_hitbox.upper_right().x - (hovered_point.x + size.width)).min(Pixels::ZERO);
 
@@ -1992,7 +2027,7 @@ impl EditorElement {
                 .occlude()
                 .on_mouse_move(|_, cx| cx.stop_propagation())
                 .into_any_element();
-            occlusion.measure(size(width, HOVER_POPOVER_GAP).into(), cx);
+            occlusion.layout_as_root(size(width, HOVER_POPOVER_GAP).into(), cx);
             cx.defer_draw(occlusion, origin, 2);
         }
 
@@ -3327,10 +3362,10 @@ enum Invisible {
 }
 
 impl Element for EditorElement {
-    type BeforeLayout = ();
-    type AfterLayout = EditorLayout;
+    type RequestLayoutState = ();
+    type PrepaintState = EditorLayout;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, ()) {
         self.editor.update(cx, |editor, cx| {
             editor.set_style(self.style.clone(), cx);
 
@@ -3377,12 +3412,12 @@ impl Element for EditorElement {
         })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
+        _: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> Self::AfterLayout {
+    ) -> Self::PrepaintState {
         let text_style = TextStyleRefinement {
             font_size: Some(self.style.text.font_size),
             line_height: Some(self.style.text.line_height),
@@ -3466,11 +3501,12 @@ impl Element for EditorElement {
                 let content_origin =
                     text_hitbox.origin + point(gutter_dimensions.margin, Pixels::ZERO);
 
-                let autoscroll_horizontally = self.editor.update(cx, |editor, cx| {
-                    let autoscroll_horizontally =
-                        editor.autoscroll_vertically(bounds, line_height, cx);
+                let mut autoscroll_requested = false;
+                let mut autoscroll_horizontally = false;
+                self.editor.update(cx, |editor, cx| {
+                    autoscroll_requested = editor.autoscroll_requested();
+                    autoscroll_horizontally = editor.autoscroll_vertically(bounds, line_height, cx);
                     snapshot = editor.snapshot(cx);
-                    autoscroll_horizontally
                 });
 
                 let mut scroll_position = snapshot.scroll_position();
@@ -3643,9 +3679,11 @@ impl Element for EditorElement {
                     &line_layouts,
                     &text_hitbox,
                     content_origin,
+                    scroll_position,
                     scroll_pixel_position,
                     line_height,
                     em_width,
+                    autoscroll_requested,
                     cx,
                 );
 
@@ -3806,8 +3844,8 @@ impl Element for EditorElement {
     fn paint(
         &mut self,
         bounds: Bounds<gpui::Pixels>,
-        _: &mut Self::BeforeLayout,
-        layout: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        layout: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         let focus_handle = self.editor.focus_handle(cx);
@@ -4187,7 +4225,7 @@ impl CursorLayout {
                 .child(cursor_name.string.clone())
                 .into_any_element();
 
-            name_element.layout(
+            name_element.prepaint_as_root(
                 name_origin,
                 size(AvailableSpace::MinContent, AvailableSpace::MinContent),
                 cx,
@@ -4467,7 +4505,7 @@ mod tests {
         let state = cx
             .update_window(window.into(), |_view, cx| {
                 cx.with_element_context(|cx| {
-                    element.after_layout(
+                    element.prepaint(
                         Bounds {
                             origin: point(px(500.), px(500.)),
                             size: size(px(500.), px(500.)),
@@ -4562,7 +4600,7 @@ mod tests {
         let state = cx
             .update_window(window.into(), |_view, cx| {
                 cx.with_element_context(|cx| {
-                    element.after_layout(
+                    element.prepaint(
                         Bounds {
                             origin: point(px(500.), px(500.)),
                             size: size(px(500.), px(500.)),
@@ -4627,7 +4665,7 @@ mod tests {
         let state = cx
             .update_window(window.into(), |_view, cx| {
                 cx.with_element_context(|cx| {
-                    element.after_layout(
+                    element.prepaint(
                         Bounds {
                             origin: point(px(500.), px(500.)),
                             size: size(px(500.), px(500.)),
@@ -4823,7 +4861,7 @@ mod tests {
         let layout_state = cx
             .update_window(window.into(), |_, cx| {
                 cx.with_element_context(|cx| {
-                    element.after_layout(
+                    element.prepaint(
                         Bounds {
                             origin: point(px(500.), px(500.)),
                             size: size(px(500.), px(500.)),

crates/editor/src/scroll.rs 🔗

@@ -275,7 +275,7 @@ impl ScrollManager {
         self.show_scrollbars
     }
 
-    pub fn has_autoscroll_request(&self) -> bool {
+    pub fn autoscroll_requested(&self) -> bool {
         self.autoscroll_request.is_some()
     }
 

crates/editor/src/scroll/autoscroll.rs 🔗

@@ -61,6 +61,10 @@ impl AutoscrollStrategy {
 }
 
 impl Editor {
+    pub fn autoscroll_requested(&self) -> bool {
+        self.scroll_manager.autoscroll_requested()
+    }
+
     pub fn autoscroll_vertically(
         &mut self,
         bounds: Bounds<Pixels>,

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -948,7 +948,7 @@ impl Render for ExtensionsPage {
                             .pb_4()
                             .track_scroll(scroll_handle)
                             .into_any_element();
-                            list.layout(bounds.origin, bounds.size.into(), cx);
+                            list.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
                             list
                         },
                         |_bounds, mut list, cx| list.paint(cx),

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

@@ -734,7 +734,8 @@ impl VisualTestContext {
         self.update(|cx| {
             cx.with_element_context(|cx| {
                 let mut element = f(cx);
-                element.layout(origin, space, cx);
+                element.layout_as_root(space, cx);
+                cx.with_absolute_element_offset(origin, |cx| element.prepaint(cx));
                 element.paint(cx);
             });
 

crates/gpui/src/element.rs 🔗

@@ -44,34 +44,34 @@ use std::{any::Any, fmt::Debug, mem, ops::DerefMut};
 /// You can create custom elements by implementing this trait, see the module-level documentation
 /// for more details.
 pub trait Element: 'static + IntoElement {
-    /// The type of state returned from [`Element::before_layout`]. A mutable reference to this state is subsequently
-    /// provided to [`Element::after_layout`] and [`Element::paint`].
-    type BeforeLayout: 'static;
+    /// The type of state returned from [`Element::request_layout`]. A mutable reference to this state is subsequently
+    /// provided to [`Element::prepaint`] and [`Element::paint`].
+    type RequestLayoutState: 'static;
 
-    /// The type of state returned from [`Element::after_layout`]. A mutable reference to this state is subsequently
+    /// The type of state returned from [`Element::prepaint`]. A mutable reference to this state is subsequently
     /// provided to [`Element::paint`].
-    type AfterLayout: 'static;
+    type PrepaintState: 'static;
 
     /// Before an element can be painted, we need to know where it's going to be and how big it is.
     /// Use this method to request a layout from Taffy and initialize the element's state.
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout);
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState);
 
     /// After laying out an element, we need to commit its bounds to the current frame for hitbox
-    /// purposes. The state argument is the same state that was returned from [`Element::before_layout()`].
-    fn after_layout(
+    /// purposes. The state argument is the same state that was returned from [`Element::request_layout()`].
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> Self::AfterLayout;
+    ) -> Self::PrepaintState;
 
     /// Once layout has been completed, this method will be called to paint the element to the screen.
-    /// The state argument is the same state that was returned from [`Element::before_layout()`].
+    /// The state argument is the same state that was returned from [`Element::request_layout()`].
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
-        after_layout: &mut Self::AfterLayout,
+        request_layout: &mut Self::RequestLayoutState,
+        prepaint: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     );
 
@@ -161,34 +161,29 @@ impl<C: RenderOnce> Component<C> {
 }
 
 impl<C: RenderOnce> Element for Component<C> {
-    type BeforeLayout = AnyElement;
-    type AfterLayout = ();
+    type RequestLayoutState = AnyElement;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut element = self
             .0
             .take()
             .unwrap()
             .render(cx.deref_mut())
             .into_any_element();
-        let layout_id = element.before_layout(cx);
+        let layout_id = element.request_layout(cx);
         (layout_id, element)
     }
 
-    fn after_layout(
-        &mut self,
-        _: Bounds<Pixels>,
-        element: &mut AnyElement,
-        cx: &mut ElementContext,
-    ) {
-        element.after_layout(cx);
+    fn prepaint(&mut self, _: Bounds<Pixels>, element: &mut AnyElement, cx: &mut ElementContext) {
+        element.prepaint(cx);
     }
 
     fn paint(
         &mut self,
         _: Bounds<Pixels>,
-        element: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        element: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         element.paint(cx)
@@ -210,13 +205,13 @@ pub(crate) struct GlobalElementId(SmallVec<[ElementId; 32]>);
 trait ElementObject {
     fn inner_element(&mut self) -> &mut dyn Any;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
+    fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId;
 
-    fn after_layout(&mut self, cx: &mut ElementContext);
+    fn prepaint(&mut self, cx: &mut ElementContext);
 
     fn paint(&mut self, cx: &mut ElementContext);
 
-    fn measure(
+    fn layout_as_root(
         &mut self,
         available_space: Size<AvailableSpace>,
         cx: &mut ElementContext,
@@ -227,27 +222,27 @@ trait ElementObject {
 pub struct Drawable<E: Element> {
     /// The drawn element.
     pub element: E,
-    phase: ElementDrawPhase<E::BeforeLayout, E::AfterLayout>,
+    phase: ElementDrawPhase<E::RequestLayoutState, E::PrepaintState>,
 }
 
 #[derive(Default)]
-enum ElementDrawPhase<BeforeLayout, AfterLayout> {
+enum ElementDrawPhase<RequestLayoutState, PrepaintState> {
     #[default]
     Start,
-    BeforeLayout {
+    RequestLayoutState {
         layout_id: LayoutId,
-        before_layout: BeforeLayout,
+        request_layout: RequestLayoutState,
     },
     LayoutComputed {
         layout_id: LayoutId,
         available_space: Size<AvailableSpace>,
-        before_layout: BeforeLayout,
+        request_layout: RequestLayoutState,
     },
-    AfterLayout {
+    PrepaintState {
         node_id: DispatchNodeId,
         bounds: Bounds<Pixels>,
-        before_layout: BeforeLayout,
-        after_layout: AfterLayout,
+        request_layout: RequestLayoutState,
+        prepaint: PrepaintState,
     },
     Painted,
 }
@@ -261,91 +256,91 @@ impl<E: Element> Drawable<E> {
         }
     }
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
         match mem::take(&mut self.phase) {
             ElementDrawPhase::Start => {
-                let (layout_id, before_layout) = self.element.before_layout(cx);
-                self.phase = ElementDrawPhase::BeforeLayout {
+                let (layout_id, request_layout) = self.element.request_layout(cx);
+                self.phase = ElementDrawPhase::RequestLayoutState {
                     layout_id,
-                    before_layout,
+                    request_layout,
                 };
                 layout_id
             }
-            _ => panic!("must call before_layout only once"),
+            _ => panic!("must call request_layout only once"),
         }
     }
 
-    fn after_layout(&mut self, cx: &mut ElementContext) {
+    fn prepaint(&mut self, cx: &mut ElementContext) {
         match mem::take(&mut self.phase) {
-            ElementDrawPhase::BeforeLayout {
+            ElementDrawPhase::RequestLayoutState {
                 layout_id,
-                mut before_layout,
+                mut request_layout,
             }
             | ElementDrawPhase::LayoutComputed {
                 layout_id,
-                mut before_layout,
+                mut request_layout,
                 ..
             } => {
                 let bounds = cx.layout_bounds(layout_id);
                 let node_id = cx.window.next_frame.dispatch_tree.push_node();
-                let after_layout = self.element.after_layout(bounds, &mut before_layout, cx);
-                self.phase = ElementDrawPhase::AfterLayout {
+                let prepaint = self.element.prepaint(bounds, &mut request_layout, cx);
+                self.phase = ElementDrawPhase::PrepaintState {
                     node_id,
                     bounds,
-                    before_layout,
-                    after_layout,
+                    request_layout,
+                    prepaint,
                 };
                 cx.window.next_frame.dispatch_tree.pop_node();
             }
-            _ => panic!("must call before_layout before after_layout"),
+            _ => panic!("must call request_layout before prepaint"),
         }
     }
 
-    fn paint(&mut self, cx: &mut ElementContext) -> E::BeforeLayout {
+    fn paint(&mut self, cx: &mut ElementContext) -> E::RequestLayoutState {
         match mem::take(&mut self.phase) {
-            ElementDrawPhase::AfterLayout {
+            ElementDrawPhase::PrepaintState {
                 node_id,
                 bounds,
-                mut before_layout,
-                mut after_layout,
+                mut request_layout,
+                mut prepaint,
                 ..
             } => {
                 cx.window.next_frame.dispatch_tree.set_active_node(node_id);
                 self.element
-                    .paint(bounds, &mut before_layout, &mut after_layout, cx);
+                    .paint(bounds, &mut request_layout, &mut prepaint, cx);
                 self.phase = ElementDrawPhase::Painted;
-                before_layout
+                request_layout
             }
-            _ => panic!("must call after_layout before paint"),
+            _ => panic!("must call prepaint before paint"),
         }
     }
 
-    fn measure(
+    fn layout_as_root(
         &mut self,
         available_space: Size<AvailableSpace>,
         cx: &mut ElementContext,
     ) -> Size<Pixels> {
         if matches!(&self.phase, ElementDrawPhase::Start) {
-            self.before_layout(cx);
+            self.request_layout(cx);
         }
 
         let layout_id = match mem::take(&mut self.phase) {
-            ElementDrawPhase::BeforeLayout {
+            ElementDrawPhase::RequestLayoutState {
                 layout_id,
-                before_layout,
+                request_layout,
             } => {
                 cx.compute_layout(layout_id, available_space);
                 self.phase = ElementDrawPhase::LayoutComputed {
                     layout_id,
                     available_space,
-                    before_layout,
+                    request_layout,
                 };
                 layout_id
             }
             ElementDrawPhase::LayoutComputed {
                 layout_id,
                 available_space: prev_available_space,
-                before_layout,
+                request_layout,
             } => {
                 if available_space != prev_available_space {
                     cx.compute_layout(layout_id, available_space);
@@ -353,7 +348,7 @@ impl<E: Element> Drawable<E> {
                 self.phase = ElementDrawPhase::LayoutComputed {
                     layout_id,
                     available_space,
-                    before_layout,
+                    request_layout,
                 };
                 layout_id
             }
@@ -367,30 +362,30 @@ impl<E: Element> Drawable<E> {
 impl<E> ElementObject for Drawable<E>
 where
     E: Element,
-    E::BeforeLayout: 'static,
+    E::RequestLayoutState: 'static,
 {
     fn inner_element(&mut self) -> &mut dyn Any {
         &mut self.element
     }
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
-        Drawable::before_layout(self, cx)
+    fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
+        Drawable::request_layout(self, cx)
     }
 
-    fn after_layout(&mut self, cx: &mut ElementContext) {
-        Drawable::after_layout(self, cx);
+    fn prepaint(&mut self, cx: &mut ElementContext) {
+        Drawable::prepaint(self, cx);
     }
 
     fn paint(&mut self, cx: &mut ElementContext) {
         Drawable::paint(self, cx);
     }
 
-    fn measure(
+    fn layout_as_root(
         &mut self,
         available_space: Size<AvailableSpace>,
         cx: &mut ElementContext,
     ) -> Size<Pixels> {
-        Drawable::measure(self, available_space, cx)
+        Drawable::layout_as_root(self, available_space, cx)
     }
 }
 
@@ -401,7 +396,7 @@ impl AnyElement {
     pub(crate) fn new<E>(element: E) -> Self
     where
         E: 'static + Element,
-        E::BeforeLayout: Any,
+        E::RequestLayoutState: Any,
     {
         let element = ELEMENT_ARENA
             .with_borrow_mut(|arena| arena.alloc(|| Drawable::new(element)))
@@ -416,13 +411,14 @@ impl AnyElement {
 
     /// Request the layout ID of the element stored in this `AnyElement`.
     /// Used for laying out child elements in a parent element.
-    pub fn before_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
-        self.0.before_layout(cx)
+    pub fn request_layout(&mut self, cx: &mut ElementContext) -> LayoutId {
+        self.0.request_layout(cx)
     }
 
-    /// Commits the element bounds of this [AnyElement] for hitbox purposes.
-    pub fn after_layout(&mut self, cx: &mut ElementContext) {
-        self.0.after_layout(cx)
+    /// Prepares the element to be painted by storing its bounds, giving it a chance to draw hitboxes and
+    /// request autoscroll before the final paint pass is confirmed.
+    pub fn prepaint(&mut self, cx: &mut ElementContext) {
+        self.0.prepaint(cx)
     }
 
     /// Paints the element stored in this `AnyElement`.
@@ -430,51 +426,55 @@ impl AnyElement {
         self.0.paint(cx)
     }
 
-    /// Initializes this element and performs layout within the given available space to determine its size.
-    pub fn measure(
+    /// Performs layout for this element within the given available space and returns its size.
+    pub fn layout_as_root(
         &mut self,
         available_space: Size<AvailableSpace>,
         cx: &mut ElementContext,
     ) -> Size<Pixels> {
-        self.0.measure(available_space, cx)
+        self.0.layout_as_root(available_space, cx)
     }
 
-    /// Initializes this element, performs layout if needed and commits its bounds for hitbox purposes.
-    pub fn layout(
+    /// Prepaints this element at the given absolute origin.
+    pub fn prepaint_at(&mut self, origin: Point<Pixels>, cx: &mut ElementContext) {
+        cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
+    }
+
+    /// Performs layout on this element in the available space, then prepaints it at the given absolute origin.
+    pub fn prepaint_as_root(
         &mut self,
-        absolute_offset: Point<Pixels>,
+        origin: Point<Pixels>,
         available_space: Size<AvailableSpace>,
         cx: &mut ElementContext,
-    ) -> Size<Pixels> {
-        let size = self.measure(available_space, cx);
-        cx.with_absolute_element_offset(absolute_offset, |cx| self.after_layout(cx));
-        size
+    ) {
+        self.layout_as_root(available_space, cx);
+        cx.with_absolute_element_offset(origin, |cx| self.0.prepaint(cx));
     }
 }
 
 impl Element for AnyElement {
-    type BeforeLayout = ();
-    type AfterLayout = ();
+    type RequestLayoutState = ();
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
-        let layout_id = self.before_layout(cx);
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
+        let layout_id = self.request_layout(cx);
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
+        _: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) {
-        self.after_layout(cx)
+        self.prepaint(cx)
     }
 
     fn paint(
         &mut self,
         _: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         self.paint(cx)
@@ -505,17 +505,17 @@ impl IntoElement for Empty {
 }
 
 impl Element for Empty {
-    type BeforeLayout = ();
-    type AfterLayout = ();
+    type RequestLayoutState = ();
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         (cx.request_layout(&crate::Style::default(), None), ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _state: &mut Self::BeforeLayout,
+        _state: &mut Self::RequestLayoutState,
         _cx: &mut ElementContext,
     ) {
     }
@@ -523,8 +523,8 @@ impl Element for Empty {
     fn paint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
-        _after_layout: &mut Self::AfterLayout,
+        _request_layout: &mut Self::RequestLayoutState,
+        _prepaint: &mut Self::PrepaintState,
         _cx: &mut ElementContext,
     ) {
     }

crates/gpui/src/elements/anchored.rs 🔗

@@ -69,14 +69,17 @@ impl ParentElement for Anchored {
 }
 
 impl Element for Anchored {
-    type BeforeLayout = AnchoredState;
-    type AfterLayout = ();
+    type RequestLayoutState = AnchoredState;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
+    fn request_layout(
+        &mut self,
+        cx: &mut ElementContext,
+    ) -> (crate::LayoutId, Self::RequestLayoutState) {
         let child_layout_ids = self
             .children
             .iter_mut()
-            .map(|child| child.before_layout(cx))
+            .map(|child| child.request_layout(cx))
             .collect::<SmallVec<_>>();
 
         let anchored_style = Style {
@@ -90,19 +93,19 @@ impl Element for Anchored {
         (layout_id, AnchoredState { child_layout_ids })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) {
-        if before_layout.child_layout_ids.is_empty() {
+        if request_layout.child_layout_ids.is_empty() {
             return;
         }
 
         let mut child_min = point(Pixels::MAX, Pixels::MAX);
         let mut child_max = Point::default();
-        for child_layout_id in &before_layout.child_layout_ids {
+        for child_layout_id in &request_layout.child_layout_ids {
             let child_bounds = cx.layout_bounds(*child_layout_id);
             child_min = child_min.min(&child_bounds.origin);
             child_max = child_max.max(&child_bounds.lower_right());
@@ -167,7 +170,7 @@ impl Element for Anchored {
 
         cx.with_element_offset(offset, |cx| {
             for child in &mut self.children {
-                child.after_layout(cx);
+                child.prepaint(cx);
             }
         })
     }
@@ -175,8 +178,8 @@ impl Element for Anchored {
     fn paint(
         &mut self,
         _bounds: crate::Bounds<crate::Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
-        _after_layout: &mut Self::AfterLayout,
+        _request_layout: &mut Self::RequestLayoutState,
+        _prepaint: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         for child in &mut self.children {

crates/gpui/src/elements/animation.rs 🔗

@@ -85,14 +85,14 @@ struct AnimationState {
 }
 
 impl<E: IntoElement + 'static> Element for AnimationElement<E> {
-    type BeforeLayout = AnyElement;
+    type RequestLayoutState = AnyElement;
 
-    type AfterLayout = ();
+    type PrepaintState = ();
 
-    fn before_layout(
+    fn request_layout(
         &mut self,
         cx: &mut crate::ElementContext,
-    ) -> (crate::LayoutId, Self::BeforeLayout) {
+    ) -> (crate::LayoutId, Self::RequestLayoutState) {
         cx.with_element_state(Some(self.id.clone()), |state, cx| {
             let state = state.unwrap().unwrap_or_else(|| AnimationState {
                 start: Instant::now(),
@@ -130,24 +130,24 @@ impl<E: IntoElement + 'static> Element for AnimationElement<E> {
                 })
             }
 
-            ((element.before_layout(cx), element), Some(state))
+            ((element.request_layout(cx), element), Some(state))
         })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: crate::Bounds<crate::Pixels>,
-        element: &mut Self::BeforeLayout,
+        element: &mut Self::RequestLayoutState,
         cx: &mut crate::ElementContext,
-    ) -> Self::AfterLayout {
-        element.after_layout(cx);
+    ) -> Self::PrepaintState {
+        element.prepaint(cx);
     }
 
     fn paint(
         &mut self,
         _bounds: crate::Bounds<crate::Pixels>,
-        element: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        element: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut crate::ElementContext,
     ) {
         element.paint(cx);

crates/gpui/src/elements/canvas.rs 🔗

@@ -5,11 +5,11 @@ use crate::{Bounds, Element, ElementContext, IntoElement, Pixels, Style, StyleRe
 /// Construct a canvas element with the given paint callback.
 /// Useful for adding short term custom drawing to a view.
 pub fn canvas<T>(
-    after_layout: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
+    prepaint: impl 'static + FnOnce(Bounds<Pixels>, &mut ElementContext) -> T,
     paint: impl 'static + FnOnce(Bounds<Pixels>, T, &mut ElementContext),
 ) -> Canvas<T> {
     Canvas {
-        after_layout: Some(Box::new(after_layout)),
+        prepaint: Some(Box::new(prepaint)),
         paint: Some(Box::new(paint)),
         style: StyleRefinement::default(),
     }
@@ -18,7 +18,7 @@ pub fn canvas<T>(
 /// A canvas element, meant for accessing the low level paint API without defining a whole
 /// custom element
 pub struct Canvas<T> {
-    after_layout: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
+    prepaint: Option<Box<dyn FnOnce(Bounds<Pixels>, &mut ElementContext) -> T>>,
     paint: Option<Box<dyn FnOnce(Bounds<Pixels>, T, &mut ElementContext)>>,
     style: StyleRefinement,
 }
@@ -32,35 +32,38 @@ impl<T: 'static> IntoElement for Canvas<T> {
 }
 
 impl<T: 'static> Element for Canvas<T> {
-    type BeforeLayout = Style;
-    type AfterLayout = Option<T>;
+    type RequestLayoutState = Style;
+    type PrepaintState = Option<T>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (crate::LayoutId, Self::BeforeLayout) {
+    fn request_layout(
+        &mut self,
+        cx: &mut ElementContext,
+    ) -> (crate::LayoutId, Self::RequestLayoutState) {
         let mut style = Style::default();
         style.refine(&self.style);
         let layout_id = cx.request_layout(&style, []);
         (layout_id, style)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _before_layout: &mut Style,
+        _request_layout: &mut Style,
         cx: &mut ElementContext,
     ) -> Option<T> {
-        Some(self.after_layout.take().unwrap()(bounds, cx))
+        Some(self.prepaint.take().unwrap()(bounds, cx))
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
         style: &mut Style,
-        after_layout: &mut Self::AfterLayout,
+        prepaint: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
-        let after_layout = after_layout.take().unwrap();
+        let prepaint = prepaint.take().unwrap();
         style.paint(bounds, cx, |cx| {
-            (self.paint.take().unwrap())(bounds, after_layout, cx)
+            (self.paint.take().unwrap())(bounds, prepaint, cx)
         });
     }
 }

crates/gpui/src/elements/deferred.rs 🔗

@@ -26,18 +26,18 @@ impl Deferred {
 }
 
 impl Element for Deferred {
-    type BeforeLayout = ();
-    type AfterLayout = ();
+    type RequestLayoutState = ();
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
-        let layout_id = self.child.as_mut().unwrap().before_layout(cx);
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, ()) {
+        let layout_id = self.child.as_mut().unwrap().request_layout(cx);
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
+        _request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) {
         let child = self.child.take().unwrap();
@@ -48,8 +48,8 @@ impl Element for Deferred {
     fn paint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
-        _after_layout: &mut Self::AfterLayout,
+        _request_layout: &mut Self::RequestLayoutState,
+        _prepaint: &mut Self::PrepaintState,
         _cx: &mut ElementContext,
     ) {
     }

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

@@ -1120,17 +1120,17 @@ impl ParentElement for Div {
 }
 
 impl Element for Div {
-    type BeforeLayout = DivFrameState;
-    type AfterLayout = Option<Hitbox>;
+    type RequestLayoutState = DivFrameState;
+    type PrepaintState = Option<Hitbox>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut child_layout_ids = SmallVec::new();
-        let layout_id = self.interactivity.before_layout(cx, |style, cx| {
+        let layout_id = self.interactivity.request_layout(cx, |style, cx| {
             cx.with_text_style(style.text_style().cloned(), |cx| {
                 child_layout_ids = self
                     .children
                     .iter_mut()
-                    .map(|child| child.before_layout(cx))
+                    .map(|child| child.request_layout(cx))
                     .collect::<SmallVec<_>>();
                 cx.request_layout(&style, child_layout_ids.iter().copied())
             })
@@ -1138,23 +1138,23 @@ impl Element for Div {
         (layout_id, DivFrameState { child_layout_ids })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<Hitbox> {
         let mut child_min = point(Pixels::MAX, Pixels::MAX);
         let mut child_max = Point::default();
-        let content_size = if before_layout.child_layout_ids.is_empty() {
+        let content_size = if request_layout.child_layout_ids.is_empty() {
             bounds.size
         } else if let Some(scroll_handle) = self.interactivity.tracked_scroll_handle.as_ref() {
             let mut state = scroll_handle.0.borrow_mut();
-            state.child_bounds = Vec::with_capacity(before_layout.child_layout_ids.len());
+            state.child_bounds = Vec::with_capacity(request_layout.child_layout_ids.len());
             state.bounds = bounds;
             let requested = state.requested_scroll_top.take();
 
-            for (ix, child_layout_id) in before_layout.child_layout_ids.iter().enumerate() {
+            for (ix, child_layout_id) in request_layout.child_layout_ids.iter().enumerate() {
                 let child_bounds = cx.layout_bounds(*child_layout_id);
                 child_min = child_min.min(&child_bounds.origin);
                 child_max = child_max.max(&child_bounds.lower_right());
@@ -1169,7 +1169,7 @@ impl Element for Div {
             }
             (child_max - child_min).into()
         } else {
-            for child_layout_id in &before_layout.child_layout_ids {
+            for child_layout_id in &request_layout.child_layout_ids {
                 let child_bounds = cx.layout_bounds(*child_layout_id);
                 child_min = child_min.min(&child_bounds.origin);
                 child_max = child_max.max(&child_bounds.lower_right());
@@ -1177,14 +1177,14 @@ impl Element for Div {
             (child_max - child_min).into()
         };
 
-        self.interactivity.after_layout(
+        self.interactivity.prepaint(
             bounds,
             content_size,
             cx,
             |_style, scroll_offset, hitbox, cx| {
                 cx.with_element_offset(scroll_offset, |cx| {
                     for child in &mut self.children {
-                        child.after_layout(cx);
+                        child.prepaint(cx);
                     }
                 });
                 hitbox
@@ -1195,7 +1195,7 @@ impl Element for Div {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
+        _request_layout: &mut Self::RequestLayoutState,
         hitbox: &mut Option<Hitbox>,
         cx: &mut ElementContext,
     ) {
@@ -1274,7 +1274,7 @@ pub struct Interactivity {
 
 impl Interactivity {
     /// Layout this element according to this interactivity state's configured styles
-    pub fn before_layout(
+    pub fn request_layout(
         &mut self,
         cx: &mut ElementContext,
         f: impl FnOnce(Style, &mut ElementContext) -> LayoutId,
@@ -1337,7 +1337,7 @@ impl Interactivity {
     }
 
     /// Commit the bounds of this element according to this interactivity state's configured styles.
-    pub fn after_layout<R>(
+    pub fn prepaint<R>(
         &mut self,
         bounds: Bounds<Pixels>,
         content_size: Size<Pixels>,
@@ -2261,30 +2261,30 @@ impl<E> Element for Focusable<E>
 where
     E: Element,
 {
-    type BeforeLayout = E::BeforeLayout;
-    type AfterLayout = E::AfterLayout;
+    type RequestLayoutState = E::RequestLayoutState;
+    type PrepaintState = E::PrepaintState;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
-        self.element.before_layout(cx)
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
+        self.element.request_layout(cx)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        state: &mut Self::BeforeLayout,
+        state: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> E::AfterLayout {
-        self.element.after_layout(bounds, state, cx)
+    ) -> E::PrepaintState {
+        self.element.prepaint(bounds, state, cx)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
-        after_layout: &mut Self::AfterLayout,
+        request_layout: &mut Self::RequestLayoutState,
+        prepaint: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
-        self.element.paint(bounds, before_layout, after_layout, cx)
+        self.element.paint(bounds, request_layout, prepaint, cx)
     }
 }
 
@@ -2344,30 +2344,30 @@ impl<E> Element for Stateful<E>
 where
     E: Element,
 {
-    type BeforeLayout = E::BeforeLayout;
-    type AfterLayout = E::AfterLayout;
+    type RequestLayoutState = E::RequestLayoutState;
+    type PrepaintState = E::PrepaintState;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
-        self.element.before_layout(cx)
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
+        self.element.request_layout(cx)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        state: &mut Self::BeforeLayout,
+        state: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> E::AfterLayout {
-        self.element.after_layout(bounds, state, cx)
+    ) -> E::PrepaintState {
+        self.element.prepaint(bounds, state, cx)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
-        after_layout: &mut Self::AfterLayout,
+        request_layout: &mut Self::RequestLayoutState,
+        prepaint: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
-        self.element.paint(bounds, before_layout, after_layout, cx);
+        self.element.paint(bounds, request_layout, prepaint, cx);
     }
 }
 

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

@@ -229,11 +229,11 @@ impl Img {
 }
 
 impl Element for Img {
-    type BeforeLayout = ();
-    type AfterLayout = Option<Hitbox>;
+    type RequestLayoutState = ();
+    type PrepaintState = Option<Hitbox>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
-        let layout_id = self.interactivity.before_layout(cx, |mut style, cx| {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
+        let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
             if let Some(data) = self.source.data(cx) {
                 let image_size = data.size();
                 match (style.size.width, style.size.height) {
@@ -256,21 +256,21 @@ impl Element for Img {
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
+        _request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<Hitbox> {
         self.interactivity
-            .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
+            .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
-        hitbox: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        hitbox: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         let source = self.source.clone();

crates/gpui/src/elements/list.rs 🔗

@@ -8,8 +8,8 @@
 
 use crate::{
     point, px, size, AnyElement, AvailableSpace, Bounds, ContentMask, DispatchPhase, Edges,
-    Element, ElementContext, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style,
-    StyleRefinement, Styled, WindowContext,
+    Element, ElementContext, FocusHandle, Hitbox, IntoElement, Pixels, Point, ScrollWheelEvent,
+    Size, Style, StyleRefinement, Styled, WindowContext,
 };
 use collections::VecDeque;
 use refineable::Refineable as _;
@@ -92,20 +92,58 @@ pub enum ListSizingBehavior {
 struct LayoutItemsResponse {
     max_item_width: Pixels,
     scroll_top: ListOffset,
-    available_item_space: Size<AvailableSpace>,
-    item_elements: VecDeque<AnyElement>,
+    item_layouts: VecDeque<ItemLayout>,
+}
+
+struct ItemLayout {
+    index: usize,
+    element: AnyElement,
+    size: Size<Pixels>,
 }
 
 /// Frame state used by the [List] element after layout.
-pub struct ListAfterLayoutState {
+pub struct ListPrepaintState {
     hitbox: Hitbox,
     layout: LayoutItemsResponse,
 }
 
 #[derive(Clone)]
 enum ListItem {
-    Unrendered,
-    Rendered { size: Size<Pixels> },
+    Unmeasured {
+        focus_handle: Option<FocusHandle>,
+    },
+    Measured {
+        size: Size<Pixels>,
+        focus_handle: Option<FocusHandle>,
+    },
+}
+
+impl ListItem {
+    fn size(&self) -> Option<Size<Pixels>> {
+        if let ListItem::Measured { size, .. } = self {
+            Some(*size)
+        } else {
+            None
+        }
+    }
+
+    fn focus_handle(&self) -> Option<FocusHandle> {
+        match self {
+            ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
+                focus_handle.clone()
+            }
+        }
+    }
+
+    fn contains_focused(&self, cx: &WindowContext) -> bool {
+        match self {
+            ListItem::Unmeasured { focus_handle } | ListItem::Measured { focus_handle, .. } => {
+                focus_handle
+                    .as_ref()
+                    .is_some_and(|handle| handle.contains_focused(cx))
+            }
+        }
+    }
 }
 
 #[derive(Clone, Debug, Default, PartialEq)]
@@ -114,6 +152,7 @@ struct ListItemSummary {
     rendered_count: usize,
     unrendered_count: usize,
     height: Pixels,
+    has_focus_handles: bool,
 }
 
 #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
@@ -131,45 +170,45 @@ struct Height(Pixels);
 impl ListState {
     /// Construct a new list state, for storage on a view.
     ///
-    /// the overdraw parameter controls how much extra space is rendered
-    /// above and below the visible area. This can help ensure that the list
-    /// doesn't flicker or pop in when scrolling.
-    pub fn new<F>(
-        element_count: usize,
-        orientation: ListAlignment,
+    /// The overdraw parameter controls how much extra space is rendered
+    /// above and below the visible area. Elements within this area will
+    /// be measured even though they are not visible. This can help ensure
+    /// that the list doesn't flicker or pop in when scrolling.
+    pub fn new<R>(
+        item_count: usize,
+        alignment: ListAlignment,
         overdraw: Pixels,
-        render_item: F,
+        render_item: R,
     ) -> Self
     where
-        F: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
+        R: 'static + FnMut(usize, &mut WindowContext) -> AnyElement,
     {
-        let mut items = SumTree::new();
-        items.extend((0..element_count).map(|_| ListItem::Unrendered), &());
-        Self(Rc::new(RefCell::new(StateInner {
+        let this = Self(Rc::new(RefCell::new(StateInner {
             last_layout_bounds: None,
             last_padding: None,
             render_item: Box::new(render_item),
-            items,
+            items: SumTree::new(),
             logical_scroll_top: None,
-            alignment: orientation,
+            alignment,
             overdraw,
             scroll_handler: None,
             reset: false,
-        })))
+        })));
+        this.splice(0..0, item_count);
+        this
     }
 
     /// Reset this instantiation of the list state.
     ///
     /// Note that this will cause scroll events to be dropped until the next paint.
     pub fn reset(&self, element_count: usize) {
-        let state = &mut *self.0.borrow_mut();
-        state.reset = true;
+        {
+            let state = &mut *self.0.borrow_mut();
+            state.reset = true;
+            state.logical_scroll_top = None;
+        }
 
-        state.logical_scroll_top = None;
-        state.items = SumTree::new();
-        state
-            .items
-            .extend((0..element_count).map(|_| ListItem::Unrendered), &());
+        self.splice(0..element_count, element_count);
     }
 
     /// The number of items in this list.
@@ -177,11 +216,39 @@ impl ListState {
         self.0.borrow().items.summary().count
     }
 
-    /// Register with the list state that the items in `old_range` have been replaced
+    /// Inform the list state that the items in `old_range` have been replaced
     /// by `count` new items that must be recalculated.
     pub fn splice(&self, old_range: Range<usize>, count: usize) {
+        self.splice_focusable(old_range, (0..count).map(|_| None))
+    }
+
+    /// Register with the list state that the items in `old_range` have been replaced
+    /// by new items. As opposed to [`splice`], this method allows an iterator of optional focus handles
+    /// to be supplied to properly integrate with items in the list that can be focused. If a focused item
+    /// is scrolled out of view, the list will continue to render it to allow keyboard interaction.
+    pub fn splice_focusable(
+        &self,
+        old_range: Range<usize>,
+        focus_handles: impl IntoIterator<Item = Option<FocusHandle>>,
+    ) {
         let state = &mut *self.0.borrow_mut();
 
+        let mut old_items = state.items.cursor::<Count>();
+        let mut new_items = old_items.slice(&Count(old_range.start), Bias::Right, &());
+        old_items.seek_forward(&Count(old_range.end), Bias::Right, &());
+
+        let mut spliced_count = 0;
+        new_items.extend(
+            focus_handles.into_iter().map(|focus_handle| {
+                spliced_count += 1;
+                ListItem::Unmeasured { focus_handle }
+            }),
+            &(),
+        );
+        new_items.append(old_items.suffix(&()), &());
+        drop(old_items);
+        state.items = new_items;
+
         if let Some(ListOffset {
             item_ix,
             offset_in_item,
@@ -191,18 +258,9 @@ impl ListState {
                 *item_ix = old_range.start;
                 *offset_in_item = px(0.);
             } else if old_range.end <= *item_ix {
-                *item_ix = *item_ix - (old_range.end - old_range.start) + count;
+                *item_ix = *item_ix - (old_range.end - old_range.start) + spliced_count;
             }
         }
-
-        let mut old_heights = state.items.cursor::<Count>();
-        let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &());
-        old_heights.seek_forward(&Count(old_range.end), Bias::Right, &());
-
-        new_heights.extend((0..count).map(|_| ListItem::Unrendered), &());
-        new_heights.append(old_heights.suffix(&()), &());
-        drop(old_heights);
-        state.items = new_heights;
     }
 
     /// Set a handler that will be called when the list is scrolled.
@@ -279,7 +337,7 @@ impl ListState {
         let scroll_top = cursor.start().1 .0 + scroll_top.offset_in_item;
 
         cursor.seek_forward(&Count(ix), Bias::Right, &());
-        if let Some(&ListItem::Rendered { size }) = cursor.item() {
+        if let Some(&ListItem::Measured { size, .. }) = cursor.item() {
             let &(Count(count), Height(top)) = cursor.start();
             if count == ix {
                 let top = bounds.top() + top - scroll_top;
@@ -379,10 +437,11 @@ impl StateInner {
     ) -> LayoutItemsResponse {
         let old_items = self.items.clone();
         let mut measured_items = VecDeque::new();
-        let mut item_elements = VecDeque::new();
+        let mut item_layouts = VecDeque::new();
         let mut rendered_height = padding.top;
         let mut max_item_width = px(0.);
         let mut scroll_top = self.logical_scroll_top();
+        let mut rendered_focused_item = false;
 
         let available_item_space = size(
             available_width.map_or(AvailableSpace::MinContent, |width| {
@@ -401,27 +460,34 @@ impl StateInner {
                 break;
             }
 
-            // Use the previously cached height if available
-            let mut size = if let ListItem::Rendered { size } = item {
-                Some(*size)
-            } else {
-                None
-            };
+            // Use the previously cached height and focus handle if available
+            let mut size = item.size();
 
             // If we're within the visible area or the height wasn't cached, render and measure the item's element
             if visible_height < available_height || size.is_none() {
-                let mut element = (self.render_item)(scroll_top.item_ix + ix, cx);
-                let element_size = element.measure(available_item_space, cx);
+                let item_index = scroll_top.item_ix + ix;
+                let mut element = (self.render_item)(item_index, cx);
+                let element_size = element.layout_as_root(available_item_space, cx);
                 size = Some(element_size);
                 if visible_height < available_height {
-                    item_elements.push_back(element);
+                    item_layouts.push_back(ItemLayout {
+                        index: item_index,
+                        element,
+                        size: element_size,
+                    });
+                    if item.contains_focused(cx) {
+                        rendered_focused_item = true;
+                    }
                 }
             }
 
             let size = size.unwrap();
             rendered_height += size.height;
             max_item_width = max_item_width.max(size.width);
-            measured_items.push_back(ListItem::Rendered { size });
+            measured_items.push_back(ListItem::Measured {
+                size,
+                focus_handle: item.focus_handle(),
+            });
         }
         rendered_height += padding.bottom;
 
@@ -433,13 +499,24 @@ impl StateInner {
         if rendered_height - scroll_top.offset_in_item < available_height {
             while rendered_height < available_height {
                 cursor.prev(&());
-                if cursor.item().is_some() {
-                    let mut element = (self.render_item)(cursor.start().0, cx);
-                    let element_size = element.measure(available_item_space, cx);
-
+                if let Some(item) = cursor.item() {
+                    let item_index = cursor.start().0;
+                    let mut element = (self.render_item)(item_index, cx);
+                    let element_size = element.layout_as_root(available_item_space, cx);
+                    let focus_handle = item.focus_handle();
                     rendered_height += element_size.height;
-                    measured_items.push_front(ListItem::Rendered { size: element_size });
-                    item_elements.push_front(element)
+                    measured_items.push_front(ListItem::Measured {
+                        size: element_size,
+                        focus_handle,
+                    });
+                    item_layouts.push_front(ItemLayout {
+                        index: item_index,
+                        element,
+                        size: element_size,
+                    });
+                    if item.contains_focused(cx) {
+                        rendered_focused_item = true;
+                    }
                 } else {
                     break;
                 }
@@ -470,15 +547,18 @@ impl StateInner {
         while leading_overdraw < self.overdraw {
             cursor.prev(&());
             if let Some(item) = cursor.item() {
-                let size = if let ListItem::Rendered { size } = item {
+                let size = if let ListItem::Measured { size, .. } = item {
                     *size
                 } else {
                     let mut element = (self.render_item)(cursor.start().0, cx);
-                    element.measure(available_item_space, cx)
+                    element.layout_as_root(available_item_space, cx)
                 };
 
                 leading_overdraw += size.height;
-                measured_items.push_front(ListItem::Rendered { size });
+                measured_items.push_front(ListItem::Measured {
+                    size,
+                    focus_handle: item.focus_handle(),
+                });
             } else {
                 break;
             }
@@ -490,23 +570,83 @@ impl StateInner {
         new_items.extend(measured_items, &());
         cursor.seek(&Count(measured_range.end), Bias::Right, &());
         new_items.append(cursor.suffix(&()), &());
-
         self.items = new_items;
 
+        // If none of the visible items are focused, check if an off-screen item is focused
+        // and include it to be rendered after the visible items so keyboard interaction continues
+        // to work for it.
+        if !rendered_focused_item {
+            let mut cursor = self
+                .items
+                .filter::<_, Count>(|summary| summary.has_focus_handles);
+            cursor.next(&());
+            while let Some(item) = cursor.item() {
+                if item.contains_focused(cx) {
+                    let item_index = cursor.start().0;
+                    let mut element = (self.render_item)(cursor.start().0, cx);
+                    let size = element.layout_as_root(available_item_space, cx);
+                    item_layouts.push_back(ItemLayout {
+                        index: item_index,
+                        element,
+                        size,
+                    });
+                    break;
+                }
+                cursor.next(&());
+            }
+        }
+
         LayoutItemsResponse {
             max_item_width,
             scroll_top,
-            available_item_space,
-            item_elements,
+            item_layouts,
         }
     }
+
+    fn prepaint_items(
+        &mut self,
+        bounds: Bounds<Pixels>,
+        padding: Edges<Pixels>,
+        cx: &mut ElementContext,
+    ) -> Result<LayoutItemsResponse, ListOffset> {
+        cx.transact(|cx| {
+            let mut layout_response =
+                self.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
+
+            // Only paint the visible items, if there is actually any space for them (taking padding into account)
+            if bounds.size.height > padding.top + padding.bottom {
+                let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
+                item_origin.y -= layout_response.scroll_top.offset_in_item;
+                for item in &mut layout_response.item_layouts {
+                    cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
+                        item.element.prepaint_at(item_origin, cx);
+                    });
+
+                    if let Some(autoscroll_bounds) = cx.take_autoscroll() {
+                        if bounds.intersect(&autoscroll_bounds) != autoscroll_bounds {
+                            return Err(ListOffset {
+                                item_ix: item.index,
+                                offset_in_item: autoscroll_bounds.origin.y - item_origin.y,
+                            });
+                        }
+                    }
+
+                    item_origin.y += item.size.height;
+                }
+            } else {
+                layout_response.item_layouts.clear();
+            }
+
+            Ok(layout_response)
+        })
+    }
 }
 
 impl std::fmt::Debug for ListItem {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         match self {
-            Self::Unrendered => write!(f, "Unrendered"),
-            Self::Rendered { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
+            Self::Unmeasured { .. } => write!(f, "Unrendered"),
+            Self::Measured { size, .. } => f.debug_struct("Rendered").field("size", size).finish(),
         }
     }
 }
@@ -522,13 +662,13 @@ pub struct ListOffset {
 }
 
 impl Element for List {
-    type BeforeLayout = ();
-    type AfterLayout = ListAfterLayoutState;
+    type RequestLayoutState = ();
+    type PrepaintState = ListPrepaintState;
 
-    fn before_layout(
+    fn request_layout(
         &mut self,
         cx: &mut crate::ElementContext,
-    ) -> (crate::LayoutId, Self::BeforeLayout) {
+    ) -> (crate::LayoutId, Self::RequestLayoutState) {
         let layout_id = match self.sizing_behavior {
             ListSizingBehavior::Infer => {
                 let mut style = Style::default();
@@ -589,12 +729,12 @@ impl Element for List {
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
+        _: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> ListAfterLayoutState {
+    ) -> ListPrepaintState {
         let state = &mut *self.state.0.borrow_mut();
         state.reset = false;
 
@@ -607,55 +747,47 @@ impl Element for List {
         if state.last_layout_bounds.map_or(true, |last_bounds| {
             last_bounds.size.width != bounds.size.width
         }) {
-            state.items = SumTree::from_iter(
-                (0..state.items.summary().count).map(|_| ListItem::Unrendered),
+            let new_items = SumTree::from_iter(
+                state.items.iter().map(|item| ListItem::Unmeasured {
+                    focus_handle: item.focus_handle(),
+                }),
                 &(),
-            )
+            );
+
+            state.items = new_items;
         }
 
         let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size());
-        let mut layout_response =
-            state.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx);
-
-        // Only paint the visible items, if there is actually any space for them (taking padding into account)
-        if bounds.size.height > padding.top + padding.bottom {
-            // Paint the visible items
-            cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
-                let mut item_origin = bounds.origin + Point::new(px(0.), padding.top);
-                item_origin.y -= layout_response.scroll_top.offset_in_item;
-                for mut item_element in &mut layout_response.item_elements {
-                    let item_size = item_element.measure(layout_response.available_item_space, cx);
-                    item_element.layout(item_origin, layout_response.available_item_space, cx);
-                    item_origin.y += item_size.height;
-                }
-            });
-        }
+        let layout = match state.prepaint_items(bounds, padding, cx) {
+            Ok(layout) => layout,
+            Err(autoscroll_request) => {
+                state.logical_scroll_top = Some(autoscroll_request);
+                state.prepaint_items(bounds, padding, cx).unwrap()
+            }
+        };
 
         state.last_layout_bounds = Some(bounds);
         state.last_padding = Some(padding);
-        ListAfterLayoutState {
-            hitbox,
-            layout: layout_response,
-        }
+        ListPrepaintState { hitbox, layout }
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<crate::Pixels>,
-        _: &mut Self::BeforeLayout,
-        after_layout: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        prepaint: &mut Self::PrepaintState,
         cx: &mut crate::ElementContext,
     ) {
         cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
-            for item in &mut after_layout.layout.item_elements {
-                item.paint(cx);
+            for item in &mut prepaint.layout.item_layouts {
+                item.element.paint(cx);
             }
         });
 
         let list_state = self.state.clone();
         let height = bounds.size.height;
-        let scroll_top = after_layout.layout.scroll_top;
-        let hitbox_id = after_layout.hitbox.id;
+        let scroll_top = prepaint.layout.scroll_top;
+        let hitbox_id = prepaint.hitbox.id;
         cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
             if phase == DispatchPhase::Bubble && hitbox_id.is_hovered(cx) {
                 list_state.0.borrow_mut().scroll(
@@ -688,17 +820,21 @@ impl sum_tree::Item for ListItem {
 
     fn summary(&self) -> Self::Summary {
         match self {
-            ListItem::Unrendered => ListItemSummary {
+            ListItem::Unmeasured { focus_handle } => ListItemSummary {
                 count: 1,
                 rendered_count: 0,
                 unrendered_count: 1,
                 height: px(0.),
+                has_focus_handles: focus_handle.is_some(),
             },
-            ListItem::Rendered { size } => ListItemSummary {
+            ListItem::Measured {
+                size, focus_handle, ..
+            } => ListItemSummary {
                 count: 1,
                 rendered_count: 1,
                 unrendered_count: 0,
                 height: size.height,
+                has_focus_handles: focus_handle.is_some(),
             },
         }
     }
@@ -712,6 +848,7 @@ impl sum_tree::Summary for ListItemSummary {
         self.rendered_count += summary.rendered_count;
         self.unrendered_count += summary.unrendered_count;
         self.height += summary.height;
+        self.has_focus_handles |= summary.has_focus_handles;
     }
 }
 

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

@@ -37,30 +37,30 @@ impl Svg {
 }
 
 impl Element for Svg {
-    type BeforeLayout = ();
-    type AfterLayout = Option<Hitbox>;
+    type RequestLayoutState = ();
+    type PrepaintState = Option<Hitbox>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let layout_id = self
             .interactivity
-            .before_layout(cx, |style, cx| cx.request_layout(&style, None));
+            .request_layout(cx, |style, cx| cx.request_layout(&style, None));
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
+        _request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<Hitbox> {
         self.interactivity
-            .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
+            .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox)
     }
 
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _before_layout: &mut Self::BeforeLayout,
+        _request_layout: &mut Self::RequestLayoutState,
         hitbox: &mut Option<Hitbox>,
         cx: &mut ElementContext,
     ) where

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

@@ -17,19 +17,19 @@ use std::{
 use util::ResultExt;
 
 impl Element for &'static str {
-    type BeforeLayout = TextState;
-    type AfterLayout = ();
+    type RequestLayoutState = TextState;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut state = TextState::default();
         let layout_id = state.layout(SharedString::from(*self), None, cx);
         (layout_id, state)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _text_state: &mut Self::BeforeLayout,
+        _text_state: &mut Self::RequestLayoutState,
         _cx: &mut ElementContext,
     ) {
     }
@@ -62,19 +62,19 @@ impl IntoElement for String {
 }
 
 impl Element for SharedString {
-    type BeforeLayout = TextState;
-    type AfterLayout = ();
+    type RequestLayoutState = TextState;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut state = TextState::default();
         let layout_id = state.layout(self.clone(), None, cx);
         (layout_id, state)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _text_state: &mut Self::BeforeLayout,
+        _text_state: &mut Self::RequestLayoutState,
         _cx: &mut ElementContext,
     ) {
     }
@@ -82,8 +82,8 @@ impl Element for SharedString {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        text_state: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        text_state: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         let text_str: &str = self.as_ref();
@@ -148,19 +148,19 @@ impl StyledText {
 }
 
 impl Element for StyledText {
-    type BeforeLayout = TextState;
-    type AfterLayout = ();
+    type RequestLayoutState = TextState;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut state = TextState::default();
         let layout_id = state.layout(self.text.clone(), self.runs.take(), cx);
         (layout_id, state)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _state: &mut Self::BeforeLayout,
+        _state: &mut Self::RequestLayoutState,
         _cx: &mut ElementContext,
     ) {
     }
@@ -168,8 +168,8 @@ impl Element for StyledText {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        text_state: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        text_state: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         text_state.paint(bounds, &self.text, cx)
@@ -402,17 +402,17 @@ impl InteractiveText {
 }
 
 impl Element for InteractiveText {
-    type BeforeLayout = TextState;
-    type AfterLayout = Hitbox;
+    type RequestLayoutState = TextState;
+    type PrepaintState = Hitbox;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
-        self.text.before_layout(cx)
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
+        self.text.request_layout(cx)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        state: &mut Self::BeforeLayout,
+        state: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Hitbox {
         cx.with_element_state::<InteractiveTextState, _>(
@@ -430,7 +430,7 @@ impl Element for InteractiveText {
                     }
                 }
 
-                self.text.after_layout(bounds, state, cx);
+                self.text.prepaint(bounds, state, cx);
                 let hitbox = cx.insert_hitbox(bounds, false);
                 (hitbox, interactive_state)
             },
@@ -440,7 +440,7 @@ impl Element for InteractiveText {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        text_state: &mut Self::BeforeLayout,
+        text_state: &mut Self::RequestLayoutState,
         hitbox: &mut Hitbox,
         cx: &mut ElementContext,
     ) {

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

@@ -104,13 +104,13 @@ impl Styled for UniformList {
 }
 
 impl Element for UniformList {
-    type BeforeLayout = UniformListFrameState;
-    type AfterLayout = Option<Hitbox>;
+    type RequestLayoutState = UniformListFrameState;
+    type PrepaintState = Option<Hitbox>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let max_items = self.item_count;
         let item_size = self.measure_item(None, cx);
-        let layout_id = self.interactivity.before_layout(cx, |style, cx| {
+        let layout_id = self.interactivity.request_layout(cx, |style, cx| {
             cx.request_measured_layout(style, move |known_dimensions, available_space, _cx| {
                 let desired_height = item_size.height * max_items;
                 let width = known_dimensions
@@ -137,10 +137,10 @@ impl Element for UniformList {
         )
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        frame_state: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<Hitbox> {
         let style = self.interactivity.compute_style(None, cx);
@@ -155,7 +155,7 @@ impl Element for UniformList {
 
         let content_size = Size {
             width: padded_bounds.size.width,
-            height: before_layout.item_size.height * self.item_count + padding.top + padding.bottom,
+            height: frame_state.item_size.height * self.item_count + padding.top + padding.bottom,
         };
 
         let shared_scroll_offset = self.interactivity.scroll_offset.clone().unwrap();
@@ -166,7 +166,7 @@ impl Element for UniformList {
             .as_mut()
             .and_then(|handle| handle.deferred_scroll_to_item.take());
 
-        self.interactivity.after_layout(
+        self.interactivity.prepaint(
             bounds,
             content_size,
             cx,
@@ -222,8 +222,9 @@ impl Element for UniformList {
                                 AvailableSpace::Definite(padded_bounds.size.width),
                                 AvailableSpace::Definite(item_height),
                             );
-                            item.layout(item_origin, available_space, cx);
-                            before_layout.items.push(item);
+                            item.layout_as_root(available_space, cx);
+                            item.prepaint_at(item_origin, cx);
+                            frame_state.items.push(item);
                         }
                     });
                 }
@@ -236,13 +237,13 @@ impl Element for UniformList {
     fn paint(
         &mut self,
         bounds: Bounds<crate::Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         hitbox: &mut Option<Hitbox>,
         cx: &mut ElementContext,
     ) {
         self.interactivity
             .paint(bounds, hitbox.as_ref(), cx, |_, cx| {
-                for item in &mut before_layout.items {
+                for item in &mut request_layout.items {
                     item.paint(cx);
                 }
             })
@@ -278,7 +279,7 @@ impl UniformList {
             }),
             AvailableSpace::MinContent,
         );
-        item_to_measure.measure(available_space, cx)
+        item_to_measure.layout_as_root(available_space, cx)
     }
 
     /// Track and render scroll state of this list with reference to the given scroll handle.

crates/gpui/src/key_dispatch.rs 🔗

@@ -283,6 +283,19 @@ impl DispatchTree {
         }
     }
 
+    pub fn truncate(&mut self, index: usize) {
+        for node in &self.nodes[index..] {
+            if let Some(focus_id) = node.focus_id {
+                self.focusable_node_ids.remove(&focus_id);
+            }
+
+            if let Some(view_id) = node.view_id {
+                self.view_node_ids.remove(&view_id);
+            }
+        }
+        self.nodes.truncate(index);
+    }
+
     pub fn clear_pending_keystrokes(&mut self) {
         self.keystroke_matchers.clear();
     }

crates/gpui/src/taffy.rs 🔗

@@ -47,7 +47,7 @@ impl TaffyLayoutEngine {
         self.styles.clear();
     }
 
-    pub fn before_layout(
+    pub fn request_layout(
         &mut self,
         style: &Style,
         rem_size: Pixels,

crates/gpui/src/text_system.rs 🔗

@@ -311,6 +311,10 @@ impl WindowTextSystem {
         self.line_layout_cache.reuse_layouts(index)
     }
 
+    pub(crate) fn truncate_layouts(&self, index: LineLayoutIndex) {
+        self.line_layout_cache.truncate_layouts(index)
+    }
+
     /// Shape the given line, at the given font_size, for painting to the screen.
     /// Subsets of the line can be styled independently with the `runs` parameter.
     ///

crates/gpui/src/text_system/line_layout.rs 🔗

@@ -347,6 +347,14 @@ impl LineLayoutCache {
         }
     }
 
+    pub fn truncate_layouts(&self, index: LineLayoutIndex) {
+        let mut current_frame = &mut *self.current_frame.write();
+        current_frame.used_lines.truncate(index.lines_index);
+        current_frame
+            .used_wrapped_lines
+            .truncate(index.wrapped_lines_index);
+    }
+
     pub fn finish_frame(&self) {
         let mut prev_frame = self.previous_frame.lock();
         let mut curr_frame = self.current_frame.write();

crates/gpui/src/view.rs 🔗

@@ -1,8 +1,8 @@
 use crate::{
-    seal::Sealed, AfterLayoutIndex, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds,
-    ContentMask, Element, ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle,
-    FocusableView, IntoElement, LayoutId, Model, PaintIndex, Pixels, Render, Style,
-    StyleRefinement, TextStyle, ViewContext, VisualContext, WeakModel,
+    seal::Sealed, AnyElement, AnyModel, AnyWeakModel, AppContext, Bounds, ContentMask, Element,
+    ElementContext, ElementId, Entity, EntityId, Flatten, FocusHandle, FocusableView, IntoElement,
+    LayoutId, Model, PaintIndex, Pixels, PrepaintStateIndex, Render, Style, StyleRefinement,
+    TextStyle, ViewContext, VisualContext, WeakModel,
 };
 use anyhow::{Context, Result};
 use refineable::Refineable;
@@ -23,7 +23,7 @@ pub struct View<V> {
 impl<V> Sealed for View<V> {}
 
 struct AnyViewState {
-    after_layout_range: Range<AfterLayoutIndex>,
+    prepaint_range: Range<PrepaintStateIndex>,
     paint_range: Range<PaintIndex>,
     cache_key: ViewCacheKey,
 }
@@ -90,34 +90,34 @@ impl<V: 'static> View<V> {
 }
 
 impl<V: Render> Element for View<V> {
-    type BeforeLayout = AnyElement;
-    type AfterLayout = ();
+    type RequestLayoutState = AnyElement;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
             let mut element = self.update(cx, |view, cx| view.render(cx).into_any_element());
-            let layout_id = element.before_layout(cx);
+            let layout_id = element.request_layout(cx);
             (layout_id, element)
         })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _: Bounds<Pixels>,
-        element: &mut Self::BeforeLayout,
+        element: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) {
         cx.set_view_id(self.entity_id());
         cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
-            element.after_layout(cx)
+            element.prepaint(cx)
         })
     }
 
     fn paint(
         &mut self,
         _: Bounds<Pixels>,
-        element: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        element: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
@@ -276,10 +276,10 @@ impl<V: Render> From<View<V>> for AnyView {
 }
 
 impl Element for AnyView {
-    type BeforeLayout = Option<AnyElement>;
-    type AfterLayout = Option<AnyElement>;
+    type RequestLayoutState = Option<AnyElement>;
+    type PrepaintState = Option<AnyElement>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         if let Some(style) = self.cached_style.as_ref() {
             let mut root_style = Style::default();
             root_style.refine(style);
@@ -288,16 +288,16 @@ impl Element for AnyView {
         } else {
             cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
                 let mut element = (self.render)(self, cx);
-                let layout_id = element.before_layout(cx);
+                let layout_id = element.request_layout(cx);
                 (layout_id, Some(element))
             })
         }
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        element: &mut Self::BeforeLayout,
+        element: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<AnyElement> {
         cx.set_view_id(self.entity_id());
@@ -317,23 +317,24 @@ impl Element for AnyView {
                             && !cx.window.dirty_views.contains(&self.entity_id())
                             && !cx.window.refreshing
                         {
-                            let after_layout_start = cx.after_layout_index();
-                            cx.reuse_after_layout(element_state.after_layout_range.clone());
-                            let after_layout_end = cx.after_layout_index();
-                            element_state.after_layout_range = after_layout_start..after_layout_end;
+                            let prepaint_start = cx.prepaint_index();
+                            cx.reuse_prepaint(element_state.prepaint_range.clone());
+                            let prepaint_end = cx.prepaint_index();
+                            element_state.prepaint_range = prepaint_start..prepaint_end;
                             return (None, Some(element_state));
                         }
                     }
 
-                    let after_layout_start = cx.after_layout_index();
+                    let prepaint_start = cx.prepaint_index();
                     let mut element = (self.render)(self, cx);
-                    element.layout(bounds.origin, bounds.size.into(), cx);
-                    let after_layout_end = cx.after_layout_index();
+                    element.layout_as_root(bounds.size.into(), cx);
+                    element.prepaint_at(bounds.origin, cx);
+                    let prepaint_end = cx.prepaint_index();
 
                     (
                         Some(element),
                         Some(AnyViewState {
-                            after_layout_range: after_layout_start..after_layout_end,
+                            prepaint_range: prepaint_start..prepaint_end,
                             paint_range: PaintIndex::default()..PaintIndex::default(),
                             cache_key: ViewCacheKey {
                                 bounds,
@@ -347,7 +348,7 @@ impl Element for AnyView {
         } else {
             cx.with_element_id(Some(ElementId::View(self.entity_id())), |cx| {
                 let mut element = element.take().unwrap();
-                element.after_layout(cx);
+                element.prepaint(cx);
                 Some(element)
             })
         }
@@ -356,8 +357,8 @@ impl Element for AnyView {
     fn paint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
-        element: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        element: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         if self.cached_style.is_some() {

crates/gpui/src/window.rs 🔗

@@ -284,6 +284,9 @@ pub struct Window {
     pub(crate) root_view: Option<AnyView>,
     pub(crate) element_id_stack: GlobalElementId,
     pub(crate) text_style_stack: Vec<TextStyleRefinement>,
+    pub(crate) element_offset_stack: Vec<Point<Pixels>>,
+    pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
+    pub(crate) requested_autoscroll: Option<Bounds<Pixels>>,
     pub(crate) rendered_frame: Frame,
     pub(crate) next_frame: Frame,
     pub(crate) next_hitbox_id: HitboxId,
@@ -549,6 +552,9 @@ impl Window {
             root_view: None,
             element_id_stack: GlobalElementId::default(),
             text_style_stack: Vec::new(),
+            element_offset_stack: Vec::new(),
+            content_mask_stack: Vec::new(),
+            requested_autoscroll: None,
             rendered_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
             next_frame: Frame::new(DispatchTree::new(cx.keymap.clone(), cx.actions.clone())),
             next_frame_callbacks,
@@ -1023,6 +1029,7 @@ impl<'a> WindowContext<'a> {
     #[profiling::function]
     pub fn draw(&mut self) {
         self.window.dirty.set(false);
+        self.window.requested_autoscroll = None;
 
         // Restore the previously-used input handler.
         if let Some(input_handler) = self.window.platform_window.take_input_handler() {

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

@@ -121,7 +121,7 @@ pub(crate) struct DeferredDraw {
     text_style_stack: Vec<TextStyleRefinement>,
     element: Option<AnyElement>,
     absolute_offset: Point<Pixels>,
-    layout_range: Range<AfterLayoutIndex>,
+    prepaint_range: Range<PrepaintStateIndex>,
     paint_range: Range<PaintIndex>,
 }
 
@@ -135,8 +135,6 @@ pub(crate) struct Frame {
     pub(crate) scene: Scene,
     pub(crate) hitboxes: Vec<Hitbox>,
     pub(crate) deferred_draws: Vec<DeferredDraw>,
-    pub(crate) content_mask_stack: Vec<ContentMask<Pixels>>,
-    pub(crate) element_offset_stack: Vec<Point<Pixels>>,
     pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
     pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
     pub(crate) cursor_styles: Vec<CursorStyleRequest>,
@@ -145,7 +143,7 @@ pub(crate) struct Frame {
 }
 
 #[derive(Clone, Default)]
-pub(crate) struct AfterLayoutIndex {
+pub(crate) struct PrepaintStateIndex {
     hitboxes_index: usize,
     tooltips_index: usize,
     deferred_draws_index: usize,
@@ -176,8 +174,6 @@ impl Frame {
             scene: Scene::default(),
             hitboxes: Vec::new(),
             deferred_draws: Vec::new(),
-            content_mask_stack: Vec::new(),
-            element_offset_stack: Vec::new(),
             input_handlers: Vec::new(),
             tooltip_requests: Vec::new(),
             cursor_styles: Vec::new(),
@@ -399,29 +395,29 @@ impl<'a> ElementContext<'a> {
 
         // Layout all root elements.
         let mut root_element = self.window.root_view.as_ref().unwrap().clone().into_any();
-        root_element.layout(Point::default(), self.window.viewport_size.into(), self);
+        root_element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self);
 
         let mut sorted_deferred_draws =
             (0..self.window.next_frame.deferred_draws.len()).collect::<SmallVec<[_; 8]>>();
         sorted_deferred_draws.sort_by_key(|ix| self.window.next_frame.deferred_draws[*ix].priority);
-        self.layout_deferred_draws(&sorted_deferred_draws);
+        self.prepaint_deferred_draws(&sorted_deferred_draws);
 
         let mut prompt_element = None;
         let mut active_drag_element = None;
         let mut tooltip_element = None;
         if let Some(prompt) = self.window.prompt.take() {
             let mut element = prompt.view.any_view().into_any();
-            element.layout(Point::default(), self.window.viewport_size.into(), self);
+            element.prepaint_as_root(Point::default(), self.window.viewport_size.into(), self);
             prompt_element = Some(element);
             self.window.prompt = Some(prompt);
         } else if let Some(active_drag) = self.app.active_drag.take() {
             let mut element = active_drag.view.clone().into_any();
             let offset = self.mouse_position() - active_drag.cursor_offset;
-            element.layout(offset, AvailableSpace::min_size(), self);
+            element.prepaint_as_root(offset, AvailableSpace::min_size(), self);
             active_drag_element = Some(element);
             self.app.active_drag = Some(active_drag);
         } else {
-            tooltip_element = self.layout_tooltip();
+            tooltip_element = self.prepaint_tooltip();
         }
 
         self.window.mouse_hit_test = self.window.next_frame.hit_test(self.window.mouse_position);
@@ -441,12 +437,12 @@ impl<'a> ElementContext<'a> {
         }
     }
 
-    fn layout_tooltip(&mut self) -> Option<AnyElement> {
+    fn prepaint_tooltip(&mut self) -> Option<AnyElement> {
         let tooltip_request = self.window.next_frame.tooltip_requests.last().cloned()?;
         let tooltip_request = tooltip_request.unwrap();
         let mut element = tooltip_request.tooltip.view.clone().into_any();
         let mouse_position = tooltip_request.tooltip.mouse_position;
-        let tooltip_size = element.measure(AvailableSpace::min_size(), self);
+        let tooltip_size = element.layout_as_root(AvailableSpace::min_size(), self);
 
         let mut tooltip_bounds = Bounds::new(mouse_position + point(px(1.), px(1.)), tooltip_size);
         let window_bounds = Bounds {
@@ -478,7 +474,7 @@ impl<'a> ElementContext<'a> {
             }
         }
 
-        self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.after_layout(cx));
+        self.with_absolute_element_offset(tooltip_bounds.origin, |cx| element.prepaint(cx));
 
         self.window.tooltip_bounds = Some(TooltipBounds {
             id: tooltip_request.id,
@@ -487,7 +483,7 @@ impl<'a> ElementContext<'a> {
         Some(element)
     }
 
-    fn layout_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
+    fn prepaint_deferred_draws(&mut self, deferred_draw_indices: &[usize]) {
         assert_eq!(self.window.element_id_stack.len(), 0);
 
         let mut deferred_draws = mem::take(&mut self.window.next_frame.deferred_draws);
@@ -500,16 +496,16 @@ impl<'a> ElementContext<'a> {
                 .dispatch_tree
                 .set_active_node(deferred_draw.parent_node);
 
-            let layout_start = self.after_layout_index();
+            let prepaint_start = self.prepaint_index();
             if let Some(element) = deferred_draw.element.as_mut() {
                 self.with_absolute_element_offset(deferred_draw.absolute_offset, |cx| {
-                    element.after_layout(cx)
+                    element.prepaint(cx)
                 });
             } else {
-                self.reuse_after_layout(deferred_draw.layout_range.clone());
+                self.reuse_prepaint(deferred_draw.prepaint_range.clone());
             }
-            let layout_end = self.after_layout_index();
-            deferred_draw.layout_range = layout_start..layout_end;
+            let prepaint_end = self.prepaint_index();
+            deferred_draw.prepaint_range = prepaint_start..prepaint_end;
         }
         assert_eq!(
             self.window.next_frame.deferred_draws.len(),
@@ -546,8 +542,8 @@ impl<'a> ElementContext<'a> {
         self.window.element_id_stack.clear();
     }
 
-    pub(crate) fn after_layout_index(&self) -> AfterLayoutIndex {
-        AfterLayoutIndex {
+    pub(crate) fn prepaint_index(&self) -> PrepaintStateIndex {
+        PrepaintStateIndex {
             hitboxes_index: self.window.next_frame.hitboxes.len(),
             tooltips_index: self.window.next_frame.tooltip_requests.len(),
             deferred_draws_index: self.window.next_frame.deferred_draws.len(),
@@ -557,7 +553,7 @@ impl<'a> ElementContext<'a> {
         }
     }
 
-    pub(crate) fn reuse_after_layout(&mut self, range: Range<AfterLayoutIndex>) {
+    pub(crate) fn reuse_prepaint(&mut self, range: Range<PrepaintStateIndex>) {
         let window = &mut self.window;
         window.next_frame.hitboxes.extend(
             window.rendered_frame.hitboxes[range.start.hitboxes_index..range.end.hitboxes_index]
@@ -595,7 +591,7 @@ impl<'a> ElementContext<'a> {
                     priority: deferred_draw.priority,
                     element: None,
                     absolute_offset: deferred_draw.absolute_offset,
-                    layout_range: deferred_draw.layout_range.clone(),
+                    prepaint_range: deferred_draw.prepaint_range.clone(),
                     paint_range: deferred_draw.paint_range.clone(),
                 }),
         );
@@ -715,9 +711,9 @@ impl<'a> ElementContext<'a> {
     ) -> R {
         if let Some(mask) = mask {
             let mask = mask.intersect(&self.content_mask());
-            self.window_mut().next_frame.content_mask_stack.push(mask);
+            self.window_mut().content_mask_stack.push(mask);
             let result = f(self);
-            self.window_mut().next_frame.content_mask_stack.pop();
+            self.window_mut().content_mask_stack.pop();
             result
         } else {
             f(self)
@@ -746,15 +742,61 @@ impl<'a> ElementContext<'a> {
         offset: Point<Pixels>,
         f: impl FnOnce(&mut Self) -> R,
     ) -> R {
-        self.window_mut()
-            .next_frame
-            .element_offset_stack
-            .push(offset);
+        self.window_mut().element_offset_stack.push(offset);
         let result = f(self);
-        self.window_mut().next_frame.element_offset_stack.pop();
+        self.window_mut().element_offset_stack.pop();
         result
     }
 
+    /// Perform prepaint on child elements in a "retryable" manner, so that any side effects
+    /// of prepaints can be discarded before prepainting again. This is used to support autoscroll
+    /// where we need to prepaint children to detect the autoscroll bounds, then adjust the
+    /// element offset and prepaint again. See [`List`] for an example.
+    pub fn transact<T, U>(&mut self, f: impl FnOnce(&mut Self) -> Result<T, U>) -> Result<T, U> {
+        let index = self.prepaint_index();
+        let result = f(self);
+        if result.is_err() {
+            self.window
+                .next_frame
+                .hitboxes
+                .truncate(index.hitboxes_index);
+            self.window
+                .next_frame
+                .tooltip_requests
+                .truncate(index.tooltips_index);
+            self.window
+                .next_frame
+                .deferred_draws
+                .truncate(index.deferred_draws_index);
+            self.window
+                .next_frame
+                .dispatch_tree
+                .truncate(index.dispatch_tree_index);
+            self.window
+                .next_frame
+                .accessed_element_states
+                .truncate(index.accessed_element_states_index);
+            self.window
+                .text_system
+                .truncate_layouts(index.line_layout_index);
+        }
+        result
+    }
+
+    /// When you call this method during [`prepaint`], containing elements will attempt to
+    /// scroll to cause the specified bounds to become visible. When they decide to autoscroll, they will call
+    /// [`prepaint`] again with a new set of bounds. See [`List`] for an example of an element
+    /// that supports this method being called on the elements it contains.
+    pub fn request_autoscroll(&mut self, bounds: Bounds<Pixels>) {
+        self.window.requested_autoscroll = Some(bounds);
+    }
+
+    /// This method can be called from a containing element such as [`List`] to support the autoscroll behavior
+    /// described in [`request_autoscroll`].
+    pub fn take_autoscroll(&mut self) -> Option<Bounds<Pixels>> {
+        self.window.requested_autoscroll.take()
+    }
+
     /// Remove an asset from GPUI's cache
     pub fn remove_cached_asset<A: Asset + 'static>(
         &mut self,
@@ -835,7 +877,6 @@ impl<'a> ElementContext<'a> {
     /// Obtain the current element offset.
     pub fn element_offset(&self) -> Point<Pixels> {
         self.window()
-            .next_frame
             .element_offset_stack
             .last()
             .copied()
@@ -845,7 +886,6 @@ impl<'a> ElementContext<'a> {
     /// Obtain the current content mask.
     pub fn content_mask(&self) -> ContentMask<Pixels> {
         self.window()
-            .next_frame
             .content_mask_stack
             .last()
             .cloned()
@@ -974,7 +1014,7 @@ impl<'a> ElementContext<'a> {
         assert_eq!(
             window.draw_phase,
             DrawPhase::Layout,
-            "defer_draw can only be called during before_layout or after_layout"
+            "defer_draw can only be called during request_layout or prepaint"
         );
         let parent_node = window.next_frame.dispatch_tree.active_node_id().unwrap();
         window.next_frame.deferred_draws.push(DeferredDraw {
@@ -984,7 +1024,7 @@ impl<'a> ElementContext<'a> {
             priority,
             element: Some(element),
             absolute_offset,
-            layout_range: AfterLayoutIndex::default()..AfterLayoutIndex::default(),
+            prepaint_range: PrepaintStateIndex::default()..PrepaintStateIndex::default(),
             paint_range: PaintIndex::default()..PaintIndex::default(),
         });
     }
@@ -1349,7 +1389,7 @@ impl<'a> ElementContext<'a> {
             .layout_engine
             .as_mut()
             .unwrap()
-            .before_layout(style, rem_size, &self.cx.app.layout_id_buffer)
+            .request_layout(style, rem_size, &self.cx.app.layout_id_buffer)
     }
 
     /// Add a node to the layout tree for the current frame. Instead of taking a `Style` and children,
@@ -1397,7 +1437,7 @@ impl<'a> ElementContext<'a> {
         bounds
     }
 
-    /// This method should be called during `after_layout`. You can use
+    /// This method should be called during `prepaint`. You can use
     /// the returned [Hitbox] during `paint` or in an event handler
     /// to determine whether the inserted hitbox was the topmost.
     pub fn insert_hitbox(&mut self, bounds: Bounds<Pixels>, opaque: bool) -> Hitbox {

crates/language_tools/src/syntax_tree_view.rs 🔗

@@ -365,7 +365,7 @@ impl Render for SyntaxTreeView {
             rendered = rendered.child(
                 canvas(
                     move |bounds, cx| {
-                        list.layout(bounds.origin, bounds.size.into(), cx);
+                        list.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
                         list
                     },
                     |_, mut list, cx| list.paint(cx),

crates/terminal_view/src/terminal_element.rs 🔗

@@ -541,12 +541,12 @@ impl TerminalElement {
 }
 
 impl Element for TerminalElement {
-    type BeforeLayout = ();
-    type AfterLayout = LayoutState;
+    type RequestLayoutState = ();
+    type PrepaintState = LayoutState;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         self.interactivity.occlude_mouse();
-        let layout_id = self.interactivity.before_layout(cx, |mut style, cx| {
+        let layout_id = self.interactivity.request_layout(cx, |mut style, cx| {
             style.size.width = relative(1.).into();
             style.size.height = relative(1.).into();
             let layout_id = cx.request_layout(&style, None);
@@ -556,14 +556,14 @@ impl Element for TerminalElement {
         (layout_id, ())
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
+        _: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
-    ) -> Self::AfterLayout {
+    ) -> Self::PrepaintState {
         self.interactivity
-            .after_layout(bounds, bounds.size, cx, |_, _, hitbox, cx| {
+            .prepaint(bounds, bounds.size, cx, |_, _, hitbox, cx| {
                 let hitbox = hitbox.unwrap();
                 let settings = ThemeSettings::get_global(cx).clone();
 
@@ -669,7 +669,7 @@ impl Element for TerminalElement {
                         .id("terminal-element")
                         .tooltip(move |cx| Tooltip::text(hovered_word.word.clone(), cx))
                         .into_any_element();
-                    element.layout(offset, bounds.size.into(), cx);
+                    element.prepaint_as_root(offset, bounds.size.into(), cx);
                     element
                 });
 
@@ -775,8 +775,8 @@ impl Element for TerminalElement {
     fn paint(
         &mut self,
         bounds: Bounds<Pixels>,
-        _: &mut Self::BeforeLayout,
-        layout: &mut Self::AfterLayout,
+        _: &mut Self::RequestLayoutState,
+        layout: &mut Self::PrepaintState,
         cx: &mut ElementContext<'_>,
     ) {
         cx.paint_quad(fill(bounds, layout.background_color));

crates/ui/src/components/popover_menu.rs 🔗

@@ -168,10 +168,13 @@ pub struct PopoverMenuFrameState {
 }
 
 impl<M: ManagedView> Element for PopoverMenu<M> {
-    type BeforeLayout = PopoverMenuFrameState;
-    type AfterLayout = Option<HitboxId>;
+    type RequestLayoutState = PopoverMenuFrameState;
+    type PrepaintState = Option<HitboxId>;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
+    fn request_layout(
+        &mut self,
+        cx: &mut ElementContext,
+    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
         self.with_element_state(cx, |this, element_state, cx| {
             let mut menu_layout_id = None;
 
@@ -186,7 +189,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
                     .with_priority(1)
                     .into_any();
 
-                menu_layout_id = Some(element.before_layout(cx));
+                menu_layout_id = Some(element.request_layout(cx));
                 element
             });
 
@@ -196,7 +199,7 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
 
             let child_layout_id = child_element
                 .as_mut()
-                .map(|child_element| child_element.before_layout(cx));
+                .map(|child_element| child_element.request_layout(cx));
 
             let layout_id = cx.request_layout(
                 &gpui::Style::default(),
@@ -214,22 +217,22 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
         })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         _bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Option<HitboxId> {
         self.with_element_state(cx, |_this, element_state, cx| {
-            if let Some(child) = before_layout.child_element.as_mut() {
-                child.after_layout(cx);
+            if let Some(child) = request_layout.child_element.as_mut() {
+                child.prepaint(cx);
             }
 
-            if let Some(menu) = before_layout.menu_element.as_mut() {
-                menu.after_layout(cx);
+            if let Some(menu) = request_layout.menu_element.as_mut() {
+                menu.prepaint(cx);
             }
 
-            before_layout.child_layout_id.map(|layout_id| {
+            request_layout.child_layout_id.map(|layout_id| {
                 let bounds = cx.layout_bounds(layout_id);
                 element_state.child_bounds = Some(bounds);
                 cx.insert_hitbox(bounds, false).id
@@ -240,16 +243,16 @@ impl<M: ManagedView> Element for PopoverMenu<M> {
     fn paint(
         &mut self,
         _: Bounds<gpui::Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         child_hitbox: &mut Option<HitboxId>,
         cx: &mut ElementContext,
     ) {
         self.with_element_state(cx, |_this, _element_state, cx| {
-            if let Some(mut child) = before_layout.child_element.take() {
+            if let Some(mut child) = request_layout.child_element.take() {
                 child.paint(cx);
             }
 
-            if let Some(mut menu) = before_layout.menu_element.take() {
+            if let Some(mut menu) = request_layout.menu_element.take() {
                 menu.paint(cx);
 
                 if let Some(child_hitbox) = *child_hitbox {

crates/ui/src/components/right_click_menu.rs 🔗

@@ -96,10 +96,13 @@ pub struct MenuHandleFrameState {
 }
 
 impl<M: ManagedView> Element for RightClickMenu<M> {
-    type BeforeLayout = MenuHandleFrameState;
-    type AfterLayout = Hitbox;
+    type RequestLayoutState = MenuHandleFrameState;
+    type PrepaintState = Hitbox;
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (gpui::LayoutId, Self::BeforeLayout) {
+    fn request_layout(
+        &mut self,
+        cx: &mut ElementContext,
+    ) -> (gpui::LayoutId, Self::RequestLayoutState) {
         self.with_element_state(cx, |this, element_state, cx| {
             let mut menu_layout_id = None;
 
@@ -114,7 +117,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
                     .with_priority(1)
                     .into_any();
 
-                menu_layout_id = Some(element.before_layout(cx));
+                menu_layout_id = Some(element.request_layout(cx));
                 element
             });
 
@@ -125,7 +128,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
 
             let child_layout_id = child_element
                 .as_mut()
-                .map(|child_element| child_element.before_layout(cx));
+                .map(|child_element| child_element.request_layout(cx));
 
             let layout_id = cx.request_layout(
                 &gpui::Style::default(),
@@ -143,21 +146,21 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
         })
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        before_layout: &mut Self::BeforeLayout,
+        request_layout: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) -> Hitbox {
         cx.with_element_id(Some(self.id.clone()), |cx| {
             let hitbox = cx.insert_hitbox(bounds, false);
 
-            if let Some(child) = before_layout.child_element.as_mut() {
-                child.after_layout(cx);
+            if let Some(child) = request_layout.child_element.as_mut() {
+                child.prepaint(cx);
             }
 
-            if let Some(menu) = before_layout.menu_element.as_mut() {
-                menu.after_layout(cx);
+            if let Some(menu) = request_layout.menu_element.as_mut() {
+                menu.prepaint(cx);
             }
 
             hitbox
@@ -167,16 +170,16 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
     fn paint(
         &mut self,
         _bounds: Bounds<gpui::Pixels>,
-        before_layout: &mut Self::BeforeLayout,
-        hitbox: &mut Self::AfterLayout,
+        request_layout: &mut Self::RequestLayoutState,
+        hitbox: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         self.with_element_state(cx, |this, element_state, cx| {
-            if let Some(mut child) = before_layout.child_element.take() {
+            if let Some(mut child) = request_layout.child_element.take() {
                 child.paint(cx);
             }
 
-            if let Some(mut menu) = before_layout.menu_element.take() {
+            if let Some(mut menu) = request_layout.menu_element.take() {
                 menu.paint(cx);
                 return;
             }
@@ -188,7 +191,7 @@ impl<M: ManagedView> Element for RightClickMenu<M> {
             let attach = this.attach;
             let menu = element_state.menu.clone();
             let position = element_state.position.clone();
-            let child_layout_id = before_layout.child_layout_id;
+            let child_layout_id = request_layout.child_layout_id;
             let child_bounds = cx.layout_bounds(child_layout_id.unwrap());
 
             let hitbox_id = hitbox.id;

crates/workspace/src/pane_group.rs 🔗

@@ -792,13 +792,13 @@ mod element {
     }
 
     impl Element for PaneAxisElement {
-        type BeforeLayout = ();
-        type AfterLayout = PaneAxisLayout;
+        type RequestLayoutState = ();
+        type PrepaintState = PaneAxisLayout;
 
-        fn before_layout(
+        fn request_layout(
             &mut self,
             cx: &mut ui::prelude::ElementContext,
-        ) -> (gpui::LayoutId, Self::BeforeLayout) {
+        ) -> (gpui::LayoutId, Self::RequestLayoutState) {
             let mut style = Style::default();
             style.flex_grow = 1.;
             style.flex_shrink = 1.;
@@ -808,10 +808,10 @@ mod element {
             (cx.request_layout(&style, None), ())
         }
 
-        fn after_layout(
+        fn prepaint(
             &mut self,
             bounds: Bounds<Pixels>,
-            _state: &mut Self::BeforeLayout,
+            _state: &mut Self::RequestLayoutState,
             cx: &mut ElementContext,
         ) -> PaneAxisLayout {
             let dragged_handle = cx.with_element_state::<Rc<RefCell<Option<usize>>>, _>(
@@ -872,7 +872,8 @@ mod element {
                     size: child_size,
                 };
                 bounding_boxes.push(Some(child_bounds));
-                child.layout(origin, child_size.into(), cx);
+                child.layout_as_root(child_size.into(), cx);
+                child.prepaint_at(origin, cx);
 
                 origin = origin.apply_along(self.axis, |val| val + child_size.along(self.axis));
                 layout.children.push(PaneAxisChildLayout {
@@ -897,8 +898,8 @@ mod element {
         fn paint(
             &mut self,
             bounds: gpui::Bounds<ui::prelude::Pixels>,
-            _: &mut Self::BeforeLayout,
-            layout: &mut Self::AfterLayout,
+            _: &mut Self::RequestLayoutState,
+            layout: &mut Self::PrepaintState,
             cx: &mut ui::prelude::ElementContext,
         ) {
             for child in &mut layout.children {

crates/workspace/src/workspace.rs 🔗

@@ -4971,10 +4971,10 @@ fn parse_pixel_size_env_var(value: &str) -> Option<Size<DevicePixels>> {
 struct DisconnectedOverlay;
 
 impl Element for DisconnectedOverlay {
-    type BeforeLayout = AnyElement;
-    type AfterLayout = ();
+    type RequestLayoutState = AnyElement;
+    type PrepaintState = ();
 
-    fn before_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::BeforeLayout) {
+    fn request_layout(&mut self, cx: &mut ElementContext) -> (LayoutId, Self::RequestLayoutState) {
         let mut background = cx.theme().colors().elevated_surface_background;
         background.fade_out(0.2);
         let mut overlay = div()
@@ -4992,24 +4992,24 @@ impl Element for DisconnectedOverlay {
                 "Your connection to the remote project has been lost.",
             ))
             .into_any();
-        (overlay.before_layout(cx), overlay)
+        (overlay.request_layout(cx), overlay)
     }
 
-    fn after_layout(
+    fn prepaint(
         &mut self,
         bounds: Bounds<Pixels>,
-        overlay: &mut Self::BeforeLayout,
+        overlay: &mut Self::RequestLayoutState,
         cx: &mut ElementContext,
     ) {
         cx.insert_hitbox(bounds, true);
-        overlay.after_layout(cx);
+        overlay.prepaint(cx);
     }
 
     fn paint(
         &mut self,
         _: Bounds<Pixels>,
-        overlay: &mut Self::BeforeLayout,
-        _: &mut Self::AfterLayout,
+        overlay: &mut Self::RequestLayoutState,
+        _: &mut Self::PrepaintState,
         cx: &mut ElementContext,
     ) {
         overlay.paint(cx)