diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 61c5c38d12..cfb8b983bb 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/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, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6cd9680d0e..d7dc3caed7 100644 --- a/crates/editor/src/editor.rs +++ b/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) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 98837be7b9..b9fed082fc 100644 --- a/crates/editor/src/element.rs +++ b/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, + scroll_position: gpui::Point, scroll_pixel_position: gpui::Point, line_height: Pixels, em_width: Pixels, + autoscroll_containing_element: bool, cx: &mut ElementContext, ) -> Vec { - 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, - _: &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, - _: &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.)), diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index cb62d30d42..14f6edc1d4 100644 --- a/crates/editor/src/scroll.rs +++ b/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() } diff --git a/crates/editor/src/scroll/autoscroll.rs b/crates/editor/src/scroll/autoscroll.rs index b5708649cc..ccf0126b1e 100644 --- a/crates/editor/src/scroll/autoscroll.rs +++ b/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, diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 25ef796784..fcc1fd8695 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/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), diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index d049ceef2f..7a6f3b9e27 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/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); }); diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index ccb4a6249d..ddb728e437 100644 --- a/crates/gpui/src/element.rs +++ b/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, - 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, - 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 Component { } impl Element for Component { - 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, - element: &mut AnyElement, - cx: &mut ElementContext, - ) { - element.after_layout(cx); + fn prepaint(&mut self, _: Bounds, element: &mut AnyElement, cx: &mut ElementContext) { + element.prepaint(cx); } fn paint( &mut self, _: Bounds, - 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, cx: &mut ElementContext, @@ -227,27 +222,27 @@ trait ElementObject { pub struct Drawable { /// The drawn element. pub element: E, - phase: ElementDrawPhase, + phase: ElementDrawPhase, } #[derive(Default)] -enum ElementDrawPhase { +enum ElementDrawPhase { #[default] Start, - BeforeLayout { + RequestLayoutState { layout_id: LayoutId, - before_layout: BeforeLayout, + request_layout: RequestLayoutState, }, LayoutComputed { layout_id: LayoutId, available_space: Size, - before_layout: BeforeLayout, + request_layout: RequestLayoutState, }, - AfterLayout { + PrepaintState { node_id: DispatchNodeId, bounds: Bounds, - before_layout: BeforeLayout, - after_layout: AfterLayout, + request_layout: RequestLayoutState, + prepaint: PrepaintState, }, Painted, } @@ -261,91 +256,91 @@ impl Drawable { } } - 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, cx: &mut ElementContext, ) -> Size { 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 Drawable { self.phase = ElementDrawPhase::LayoutComputed { layout_id, available_space, - before_layout, + request_layout, }; layout_id } @@ -367,30 +362,30 @@ impl Drawable { impl ElementObject for Drawable 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, cx: &mut ElementContext, ) -> Size { - Drawable::measure(self, available_space, cx) + Drawable::layout_as_root(self, available_space, cx) } } @@ -401,7 +396,7 @@ impl AnyElement { pub(crate) fn new(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, cx: &mut ElementContext, ) -> Size { - 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, 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, + origin: Point, available_space: Size, cx: &mut ElementContext, - ) -> Size { - 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, - _: &mut Self::BeforeLayout, + _: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) { - self.after_layout(cx) + self.prepaint(cx) } fn paint( &mut self, _: Bounds, - _: &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, - _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, - _before_layout: &mut Self::BeforeLayout, - _after_layout: &mut Self::AfterLayout, + _request_layout: &mut Self::RequestLayoutState, + _prepaint: &mut Self::PrepaintState, _cx: &mut ElementContext, ) { } diff --git a/crates/gpui/src/elements/anchored.rs b/crates/gpui/src/elements/anchored.rs index 1915131277..9f4d342716 100644 --- a/crates/gpui/src/elements/anchored.rs +++ b/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::>(); 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, - 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, - _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 { diff --git a/crates/gpui/src/elements/animation.rs b/crates/gpui/src/elements/animation.rs index 4c3379cfb1..586b26f7e9 100644 --- a/crates/gpui/src/elements/animation.rs +++ b/crates/gpui/src/elements/animation.rs @@ -85,14 +85,14 @@ struct AnimationState { } impl Element for AnimationElement { - 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 Element for AnimationElement { }) } - ((element.before_layout(cx), element), Some(state)) + ((element.request_layout(cx), element), Some(state)) }) } - fn after_layout( + fn prepaint( &mut self, _bounds: crate::Bounds, - 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, - element: &mut Self::BeforeLayout, - _: &mut Self::AfterLayout, + element: &mut Self::RequestLayoutState, + _: &mut Self::PrepaintState, cx: &mut crate::ElementContext, ) { element.paint(cx); diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 623dfa2280..c0bfc044ab 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/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( - after_layout: impl 'static + FnOnce(Bounds, &mut ElementContext) -> T, + prepaint: impl 'static + FnOnce(Bounds, &mut ElementContext) -> T, paint: impl 'static + FnOnce(Bounds, T, &mut ElementContext), ) -> Canvas { 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( /// A canvas element, meant for accessing the low level paint API without defining a whole /// custom element pub struct Canvas { - after_layout: Option, &mut ElementContext) -> T>>, + prepaint: Option, &mut ElementContext) -> T>>, paint: Option, T, &mut ElementContext)>>, style: StyleRefinement, } @@ -32,35 +32,38 @@ impl IntoElement for Canvas { } impl Element for Canvas { - type BeforeLayout = Style; - type AfterLayout = Option; + type RequestLayoutState = Style; + type PrepaintState = Option; - 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, - _before_layout: &mut Style, + _request_layout: &mut Style, cx: &mut ElementContext, ) -> Option { - Some(self.after_layout.take().unwrap()(bounds, cx)) + Some(self.prepaint.take().unwrap()(bounds, cx)) } fn paint( &mut self, bounds: Bounds, 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) }); } } diff --git a/crates/gpui/src/elements/deferred.rs b/crates/gpui/src/elements/deferred.rs index 32a775f00a..30643bdc2a 100644 --- a/crates/gpui/src/elements/deferred.rs +++ b/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, - _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, - _before_layout: &mut Self::BeforeLayout, - _after_layout: &mut Self::AfterLayout, + _request_layout: &mut Self::RequestLayoutState, + _prepaint: &mut Self::PrepaintState, _cx: &mut ElementContext, ) { } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index 708aea464a..92c3420206 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1120,17 +1120,17 @@ impl ParentElement for Div { } impl Element for Div { - type BeforeLayout = DivFrameState; - type AfterLayout = Option; + type RequestLayoutState = DivFrameState; + type PrepaintState = Option; - 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::>(); 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, - before_layout: &mut Self::BeforeLayout, + request_layout: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { 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, - _before_layout: &mut Self::BeforeLayout, + _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, 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( + pub fn prepaint( &mut self, bounds: Bounds, content_size: Size, @@ -2261,30 +2261,30 @@ impl Element for Focusable 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, - 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, - 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 Element for Stateful 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, - 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, - 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); } } diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 67c7c0a4ee..fad4a49fee 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -229,11 +229,11 @@ impl Img { } impl Element for Img { - type BeforeLayout = (); - type AfterLayout = Option; + type RequestLayoutState = (); + type PrepaintState = Option; - 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, - _before_layout: &mut Self::BeforeLayout, + _request_layout: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { self.interactivity - .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) + .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, bounds: Bounds, - _: &mut Self::BeforeLayout, - hitbox: &mut Self::AfterLayout, + _: &mut Self::RequestLayoutState, + hitbox: &mut Self::PrepaintState, cx: &mut ElementContext, ) { let source = self.source.clone(); diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index b353da5e63..784955a5d7 100644 --- a/crates/gpui/src/elements/list.rs +++ b/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, - item_elements: VecDeque, + item_layouts: VecDeque, +} + +struct ItemLayout { + index: usize, + element: AnyElement, + size: Size, } /// 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 }, + Unmeasured { + focus_handle: Option, + }, + Measured { + size: Size, + focus_handle: Option, + }, +} + +impl ListItem { + fn size(&self) -> Option> { + if let ListItem::Measured { size, .. } = self { + Some(*size) + } else { + None + } + } + + fn focus_handle(&self) -> Option { + 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( - 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( + 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, 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, + focus_handles: impl IntoIterator>, + ) { let state = &mut *self.0.borrow_mut(); + let mut old_items = state.items.cursor::(); + 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::(); - 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, + padding: Edges, + cx: &mut ElementContext, + ) -> Result { + 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, - _: &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, - _: &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; } } diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index d00c47e317..ae2f4c2074 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -37,30 +37,30 @@ impl Svg { } impl Element for Svg { - type BeforeLayout = (); - type AfterLayout = Option; + type RequestLayoutState = (); + type PrepaintState = Option; - 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, - _before_layout: &mut Self::BeforeLayout, + _request_layout: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { self.interactivity - .after_layout(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) + .prepaint(bounds, bounds.size, cx, |_, _, hitbox, _| hitbox) } fn paint( &mut self, bounds: Bounds, - _before_layout: &mut Self::BeforeLayout, + _request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, cx: &mut ElementContext, ) where diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 4645404c29..565638bfb4 100644 --- a/crates/gpui/src/elements/text.rs +++ b/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, - _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, - _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, - 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, - _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, - 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, - state: &mut Self::BeforeLayout, + state: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Hitbox { cx.with_element_state::( @@ -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, - text_state: &mut Self::BeforeLayout, + text_state: &mut Self::RequestLayoutState, hitbox: &mut Hitbox, cx: &mut ElementContext, ) { diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 82a9a48c01..2813043c7a 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/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; + type RequestLayoutState = UniformListFrameState; + type PrepaintState = Option; - 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, - before_layout: &mut Self::BeforeLayout, + frame_state: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { 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, - before_layout: &mut Self::BeforeLayout, + request_layout: &mut Self::RequestLayoutState, hitbox: &mut Option, 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. diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 2aa6931fab..ff0b995693 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/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(); } diff --git a/crates/gpui/src/taffy.rs b/crates/gpui/src/taffy.rs index 248948d071..d5abd9add4 100644 --- a/crates/gpui/src/taffy.rs +++ b/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, diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 4d044a2837..a03031600a 100644 --- a/crates/gpui/src/text_system.rs +++ b/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. /// diff --git a/crates/gpui/src/text_system/line_layout.rs b/crates/gpui/src/text_system/line_layout.rs index c98e304c5f..067dbca17d 100644 --- a/crates/gpui/src/text_system/line_layout.rs +++ b/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(); diff --git a/crates/gpui/src/view.rs b/crates/gpui/src/view.rs index 2475f379f1..3d9fb82cd5 100644 --- a/crates/gpui/src/view.rs +++ b/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 { impl Sealed for View {} struct AnyViewState { - after_layout_range: Range, + prepaint_range: Range, paint_range: Range, cache_key: ViewCacheKey, } @@ -90,34 +90,34 @@ impl View { } impl Element for View { - 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, - 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, - 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 From> for AnyView { } impl Element for AnyView { - type BeforeLayout = Option; - type AfterLayout = Option; + type RequestLayoutState = Option; + type PrepaintState = Option; - 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, - element: &mut Self::BeforeLayout, + element: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { 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, - _: &mut Self::BeforeLayout, - element: &mut Self::AfterLayout, + _: &mut Self::RequestLayoutState, + element: &mut Self::PrepaintState, cx: &mut ElementContext, ) { if self.cached_style.is_some() { diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index abddf3e3a7..3c6160515f 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -284,6 +284,9 @@ pub struct Window { pub(crate) root_view: Option, pub(crate) element_id_stack: GlobalElementId, pub(crate) text_style_stack: Vec, + pub(crate) element_offset_stack: Vec>, + pub(crate) content_mask_stack: Vec>, + pub(crate) requested_autoscroll: Option>, 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() { diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 15758fb357..22dc083a19 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -121,7 +121,7 @@ pub(crate) struct DeferredDraw { text_style_stack: Vec, element: Option, absolute_offset: Point, - layout_range: Range, + prepaint_range: Range, paint_range: Range, } @@ -135,8 +135,6 @@ pub(crate) struct Frame { pub(crate) scene: Scene, pub(crate) hitboxes: Vec, pub(crate) deferred_draws: Vec, - pub(crate) content_mask_stack: Vec>, - pub(crate) element_offset_stack: Vec>, pub(crate) input_handlers: Vec>, pub(crate) tooltip_requests: Vec>, pub(crate) cursor_styles: Vec, @@ -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::>(); 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 { + fn prepaint_tooltip(&mut self) -> Option { 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) { + pub(crate) fn reuse_prepaint(&mut self, range: Range) { 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, 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(&mut self, f: impl FnOnce(&mut Self) -> Result) -> Result { + 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) { + 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> { + self.window.requested_autoscroll.take() + } + /// Remove an asset from GPUI's cache pub fn remove_cached_asset( &mut self, @@ -835,7 +877,6 @@ impl<'a> ElementContext<'a> { /// Obtain the current element offset. pub fn element_offset(&self) -> Point { 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 { 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, opaque: bool) -> Hitbox { diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index a8a3906b53..f4cd38f2ca 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/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), diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 5f06d7f038..7547c9603f 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/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, - _: &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, - _: &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)); diff --git a/crates/ui/src/components/popover_menu.rs b/crates/ui/src/components/popover_menu.rs index 6c16bc565d..a3a9eba8f2 100644 --- a/crates/ui/src/components/popover_menu.rs +++ b/crates/ui/src/components/popover_menu.rs @@ -168,10 +168,13 @@ pub struct PopoverMenuFrameState { } impl Element for PopoverMenu { - type BeforeLayout = PopoverMenuFrameState; - type AfterLayout = Option; + type RequestLayoutState = PopoverMenuFrameState; + type PrepaintState = Option; - 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 Element for PopoverMenu { .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 Element for PopoverMenu { 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 Element for PopoverMenu { }) } - fn after_layout( + fn prepaint( &mut self, _bounds: Bounds, - before_layout: &mut Self::BeforeLayout, + request_layout: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> Option { 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 Element for PopoverMenu { fn paint( &mut self, _: Bounds, - before_layout: &mut Self::BeforeLayout, + request_layout: &mut Self::RequestLayoutState, child_hitbox: &mut Option, 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 { diff --git a/crates/ui/src/components/right_click_menu.rs b/crates/ui/src/components/right_click_menu.rs index b14963d07d..5382236271 100644 --- a/crates/ui/src/components/right_click_menu.rs +++ b/crates/ui/src/components/right_click_menu.rs @@ -96,10 +96,13 @@ pub struct MenuHandleFrameState { } impl Element for RightClickMenu { - 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 Element for RightClickMenu { .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 Element for RightClickMenu { 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 Element for RightClickMenu { }) } - fn after_layout( + fn prepaint( &mut self, bounds: Bounds, - 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 Element for RightClickMenu { fn paint( &mut self, _bounds: Bounds, - 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 Element for RightClickMenu { 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; diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 3897dca354..faf25457d9 100644 --- a/crates/workspace/src/pane_group.rs +++ b/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, - _state: &mut Self::BeforeLayout, + _state: &mut Self::RequestLayoutState, cx: &mut ElementContext, ) -> PaneAxisLayout { let dragged_handle = cx.with_element_state::>>, _>( @@ -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, - _: &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 { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 303323f735..5f11c39446 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -4971,10 +4971,10 @@ fn parse_pixel_size_env_var(value: &str) -> Option> { 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, - 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, - overlay: &mut Self::BeforeLayout, - _: &mut Self::AfterLayout, + overlay: &mut Self::RequestLayoutState, + _: &mut Self::PrepaintState, cx: &mut ElementContext, ) { overlay.paint(cx)