diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 5a88d81c18db5e790b7bbed0fb9def23bc973e14..19307bc119be0e9ab6cafa25865f8e3bf73cea78 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -71,17 +71,17 @@ struct StateInner { scroll_handler: Option>, scrollbar_drag_start_height: Option, measuring_behavior: ListMeasuringBehavior, - pending_scroll: Option, + pending_scroll: Option, follow_state: FollowState, } -/// Keeps track of a fractional scroll position within an item for restoration -/// after remeasurement. -struct PendingScrollFraction { +/// Keeps track of a scroll position within an item for restoration after +/// remeasurement. +struct PendingScrollOffset { /// The index of the item to scroll within. item_ix: usize, - /// Fractional offset (0.0 to 1.0) within the item's height. - fraction: f32, + /// Pixel offset within the item. + offset_in_item: Pixels, } /// Controls whether the list automatically follows new content at the end. @@ -341,27 +341,20 @@ impl ListState { let state = &mut *self.0.borrow_mut(); // If the scroll-top item falls within the remeasured range, - // store a fractional offset so the layout can restore the - // proportional scroll position after the item is re-rendered - // at its new height. + // store its pixel offset so the layout can restore the same + // visible position after the item is re-rendered at its new height. if let Some(scroll_top) = state.logical_scroll_top { if range.contains(&scroll_top.item_ix) { let mut cursor = state.items.cursor::(()); cursor.seek(&Count(scroll_top.item_ix), Bias::Right); - if let Some(item) = cursor.item() { - if let Some(size) = item.size() { - let fraction = if size.height.0 > 0.0 { - (scroll_top.offset_in_item.0 / size.height.0).clamp(0.0, 1.0) - } else { - 0.0 - }; - - state.pending_scroll = Some(PendingScrollFraction { - item_ix: scroll_top.item_ix, - fraction, - }); - } + if let Some(item) = cursor.item() + && item.size().is_some() + { + state.pending_scroll = Some(PendingScrollOffset { + item_ix: scroll_top.item_ix, + offset_in_item: scroll_top.offset_in_item, + }); } } } @@ -872,13 +865,13 @@ impl StateInner { size = Some(element_size); // If there's a pending scroll adjustment for the scroll-top - // item, apply it, ensuring proportional scroll position is - // maintained after re-measuring. + // item, apply it so the visible position stays locked after + // re-measuring. if ix == 0 { if let Some(pending_scroll) = self.pending_scroll.take() { if pending_scroll.item_ix == scroll_top.item_ix { scroll_top.offset_in_item = - Pixels(pending_scroll.fraction * element_size.height.0); + pending_scroll.offset_in_item.min(element_size.height); self.logical_scroll_top = Some(scroll_top); } } @@ -1629,9 +1622,8 @@ mod test { assert_eq!(offset.offset_in_item, px(40.)); // Update the `item_height` to be 50px instead of 100px so we can assert - // that the scroll position is proportionally preserved, that is, - // instead of 40px from the top of item 2, it should be 20px, since the - // item's height has been halved. + // that the scroll position stays locked to the same absolute pixel offset + // within item 2 after remeasurement. item_height.set(50); state.remeasure(); @@ -1641,7 +1633,7 @@ mod test { let offset = state.logical_scroll_top(); assert_eq!(offset.item_ix, 2); - assert_eq!(offset.offset_in_item, px(20.)); + assert_eq!(offset.offset_in_item, px(40.)); } #[gpui::test]