diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 94311a1339..46b9b2c949 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -282,9 +282,12 @@ enum ElementDrawPhase { Painted, } +/// todo!() #[derive(Default)] pub struct ElementMeasurement { + /// The size of the element. pub size: Size, + /// The bounds for the focus target inside of the measured element. pub focus_target_bounds: Option>, } @@ -604,7 +607,7 @@ impl Element for Empty { &mut self, _bounds: Bounds, _before_layout: &mut Self::BeforeLayout, - cx: &mut ElementContext, + _cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { (None, ()) } diff --git a/crates/gpui/src/elements/anchored.rs b/crates/gpui/src/elements/anchored.rs index 162b036511..62e40d88df 100644 --- a/crates/gpui/src/elements/anchored.rs +++ b/crates/gpui/src/elements/anchored.rs @@ -8,7 +8,6 @@ use crate::{ /// The state that the anchored element element uses to track its children. pub struct AnchoredState { - layout_id: LayoutId, child_layout_ids: SmallVec<[LayoutId; 4]>, } @@ -89,19 +88,13 @@ impl Element for Anchored { let layout_id = cx.request_layout(&anchored_style, child_layout_ids.iter().copied()); - ( - layout_id, - AnchoredState { - layout_id, - child_layout_ids, - }, - ) + (layout_id, AnchoredState { child_layout_ids }) } fn after_layout( &mut self, _bounds: Bounds, - before_layout: &mut Self::BeforeLayout, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { let mut focus_target_bounds = None; diff --git a/crates/gpui/src/elements/canvas.rs b/crates/gpui/src/elements/canvas.rs index 39b720a342..41c99588f7 100644 --- a/crates/gpui/src/elements/canvas.rs +++ b/crates/gpui/src/elements/canvas.rs @@ -40,9 +40,9 @@ impl Element for Canvas { fn after_layout( &mut self, - bounds: Bounds, + _bounds: Bounds, _before_layout: &mut Self::BeforeLayout, - cx: &mut ElementContext, + _cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { (None, ()) } diff --git a/crates/gpui/src/elements/div.rs b/crates/gpui/src/elements/div.rs index ad9e908ae9..449d5fcd67 100644 --- a/crates/gpui/src/elements/div.rs +++ b/crates/gpui/src/elements/div.rs @@ -1361,6 +1361,7 @@ impl Interactivity { ) } + /// todo!() pub fn after_layout( &mut self, bounds: Bounds, @@ -1411,7 +1412,6 @@ impl Interactivity { ) } - // self.content_size = content_size; /// Commit the bounds of this element according to this interactivity state's configured styles. pub fn before_paint( &mut self, diff --git a/crates/gpui/src/elements/img.rs b/crates/gpui/src/elements/img.rs index 61dd602eea..0171d72043 100644 --- a/crates/gpui/src/elements/img.rs +++ b/crates/gpui/src/elements/img.rs @@ -260,7 +260,7 @@ impl Element for Img { fn after_layout( &mut self, bounds: Bounds, - before_layout: &mut Self::BeforeLayout, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { self.interactivity diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index b58e0236f0..0aa62f5164 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -89,17 +89,28 @@ pub enum ListSizingBehavior { Auto, } -struct LayoutItemsResponse { +/// Layout information computed by the [List] element during `after_layout`. +pub struct ListLayout { max_item_width: Pixels, scroll_top: ListOffset, - available_item_space: Size, - item_elements: VecDeque, + items: VecDeque, + focus_target: Option, } -/// Frame state used by the [List] element after layout. +struct MeasuredItem { + element: AnyElement, + size: Size, +} + +struct FocusTarget { + item_index: usize, + bounds_in_item: Bounds, +} + +/// Frame state used by the [List] element during paint. pub struct ListBeforePaintState { hitbox: Hitbox, - layout: LayoutItemsResponse, + layout: ListLayout, } #[derive(Clone)] @@ -376,13 +387,14 @@ impl StateInner { available_height: Pixels, padding: &Edges, cx: &mut ElementContext, - ) -> LayoutItemsResponse { + ) -> ListLayout { let old_items = self.items.clone(); let mut measured_items = VecDeque::new(); - let mut item_elements = VecDeque::new(); + let mut items = 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 focus_target = None; let available_item_space = size( available_width.map_or(AvailableSpace::MinContent, |width| { @@ -411,10 +423,20 @@ impl StateInner { // 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.layout(available_item_space, cx); - size = Some(element_size); + let element_measurement = element.layout(available_item_space, cx); + size = Some(element_measurement.size); if visible_height < available_height { - item_elements.push_back(element); + if let Some(focus_target_bounds) = element_measurement.focus_target_bounds { + focus_target = Some(FocusTarget { + item_index: items.len(), + bounds_in_item: focus_target_bounds, + }); + } + + items.push_back(MeasuredItem { + element, + size: element_measurement.size, + }); } } @@ -435,11 +457,26 @@ impl StateInner { cursor.prev(&()); if cursor.item().is_some() { let mut element = (self.render_item)(cursor.start().0, cx); - let element_size = element.layout(available_item_space, cx); + let element_measurement = element.layout(available_item_space, cx); - rendered_height += element_size.height; - measured_items.push_front(ListItem::Rendered { size: element_size }); - item_elements.push_front(element) + rendered_height += element_measurement.size.height; + measured_items.push_front(ListItem::Rendered { + size: element_measurement.size, + }); + + items.push_front(MeasuredItem { + element, + size: element_measurement.size, + }); + + if let Some(focus_target_bounds) = element_measurement.focus_target_bounds { + focus_target = Some(FocusTarget { + item_index: 0, + bounds_in_item: focus_target_bounds, + }); + } else if let Some(focus_target) = focus_target.as_mut() { + focus_target.item_index += 1; + } } else { break; } @@ -474,7 +511,7 @@ impl StateInner { *size } else { let mut element = (self.render_item)(cursor.start().0, cx); - element.layout(available_item_space, cx) + element.layout(available_item_space, cx).size }; leading_overdraw += size.height; @@ -493,11 +530,11 @@ impl StateInner { self.items = new_items; - LayoutItemsResponse { + ListLayout { max_item_width, scroll_top, - available_item_space, - item_elements, + items, + focus_target, } } } @@ -523,6 +560,7 @@ pub struct ListOffset { impl Element for List { type BeforeLayout = (); + type AfterLayout = Option; type BeforePaint = ListBeforePaintState; fn before_layout( @@ -589,20 +627,18 @@ impl Element for List { (layout_id, ()) } - fn before_paint( + fn after_layout( &mut self, bounds: Bounds, - _: &mut Self::BeforeLayout, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, - ) -> ListBeforePaintState { + ) -> (Option>, Self::AfterLayout) { let state = &mut *self.state.0.borrow_mut(); state.reset = false; let mut style = Style::default(); style.refine(&self.style); - let hitbox = cx.insert_hitbox(bounds, false); - // If the width of the list has changed, invalidate all cached item heights if state.last_layout_bounds.map_or(true, |last_bounds| { last_bounds.size.width != bounds.size.width @@ -614,41 +650,70 @@ impl Element for List { } 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); + let layout = state.layout_items(Some(bounds.size.width), bounds.size.height, &padding, cx); + let focus_target_bounds = layout.focus_target.as_ref().map(|focus_target| { + let mut item_origin = + bounds.origin + Point::new(px(0.), padding.top - layout.scroll_top.offset_in_item); + item_origin.y -= layout.scroll_top.offset_in_item; + for item in layout.items.iter().take(focus_target.item_index) { + item_origin.y += item.size.height; + } + Bounds::new( + item_origin + focus_target.bounds_in_item.origin, + focus_target.bounds_in_item.size, + ) + }); + (focus_target_bounds, Some(layout)) + } + + fn before_paint( + &mut self, + bounds: Bounds, + _: &mut Self::BeforeLayout, + layout: &mut Self::AfterLayout, + cx: &mut ElementContext, + ) -> ListBeforePaintState { + let mut layout = layout.take().unwrap(); + let state = &mut *self.state.0.borrow_mut(); + state.reset = false; + + let mut style = Style::default(); + style.refine(&self.style); + let padding = style.padding.to_pixels(bounds.size.into(), cx.rem_size()); + + let hitbox = cx.insert_hitbox(bounds, false); // 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.layout(layout_response.available_item_space, cx); - item_element.layout(item_origin, layout_response.available_item_space, cx); - item_origin.y += item_size.height; + item_origin.y -= layout.scroll_top.offset_in_item; + for item in &mut layout.items { + cx.with_absolute_element_offset(item_origin, |cx| { + item.element.before_paint(cx) + }); + item_origin.y += item.size.height; } }); } state.last_layout_bounds = Some(bounds); state.last_padding = Some(padding); - ListBeforePaintState { - hitbox, - layout: layout_response, - } + ListBeforePaintState { hitbox, layout } } fn paint( &mut self, bounds: Bounds, _: &mut Self::BeforeLayout, + _: &mut Self::AfterLayout, before_paint: &mut Self::BeforePaint, cx: &mut crate::ElementContext, ) { cx.with_content_mask(Some(ContentMask { bounds }), |cx| { - for item in &mut before_paint.layout.item_elements { - item.paint(cx); + for item in &mut before_paint.layout.items { + item.element.paint(cx); } }); diff --git a/crates/gpui/src/elements/svg.rs b/crates/gpui/src/elements/svg.rs index 9e8086b34c..8bb3240299 100644 --- a/crates/gpui/src/elements/svg.rs +++ b/crates/gpui/src/elements/svg.rs @@ -51,7 +51,7 @@ impl Element for Svg { fn after_layout( &mut self, bounds: Bounds, - before_layout: &mut Self::BeforeLayout, + _before_layout: &mut Self::BeforeLayout, cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { self.interactivity diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 4179e7ace2..9cc47101a3 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -29,9 +29,9 @@ impl Element for &'static str { fn after_layout( &mut self, - bounds: Bounds, - before_layout: &mut Self::BeforeLayout, - cx: &mut ElementContext, + _bounds: Bounds, + _before_layout: &mut Self::BeforeLayout, + _cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { (None, ()) } @@ -88,7 +88,7 @@ impl Element for SharedString { &mut self, _bounds: Bounds, _before_layout: &mut Self::BeforeLayout, - cx: &mut ElementContext, + _cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { (None, ()) } @@ -186,7 +186,7 @@ impl Element for StyledText { &mut self, _bounds: Bounds, _before_layout: &mut Self::BeforeLayout, - cx: &mut ElementContext, + _cx: &mut ElementContext, ) -> (Option>, Self::AfterLayout) { (None, ()) } diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index 949146fb82..cf5dc99ffe 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -224,8 +224,8 @@ impl Element for UniformList { ); for mut item in items { let measurement = item.layout(available_space, cx); - if let Some(child_focus_target_bounds) = measurement.focus_target_bounds { - focus_target_bounds = Some(child_focus_target_bounds); + if measurement.focus_target_bounds.is_some() { + focus_target_bounds = measurement.focus_target_bounds; } state.items.push(item); }