diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index b265fb390e9083acc9139e243c82d2c15bc83224..949d4339e616cd9f49b3783f46da0f80424c474f 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -138,7 +138,11 @@ impl UniformListScrollHandle { }))) } - /// Scroll the list so that the given item index is onscreen. + /// Scroll the list so that the given item index is visible. + /// + /// This uses non-strict scrolling: if the item is already fully visible, no scrolling occurs. + /// If the item is out of view, it scrolls the minimum amount to bring it into view according + /// to the strategy. pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) { self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem { item_index: ix, @@ -149,6 +153,9 @@ impl UniformListScrollHandle { } /// Scroll the list so that the given item index is at scroll strategy position. + /// + /// This uses strict scrolling: the item will always be scrolled to match the strategy position, + /// even if it's already visible. Use this when you need precise positioning. pub fn scroll_to_item_strict(&self, ix: usize, strategy: ScrollStrategy) { self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem { item_index: ix, @@ -158,11 +165,16 @@ impl UniformListScrollHandle { }); } - /// Scroll the list to the given item index with an offset. + /// Scroll the list to the given item index with an offset in number of items. /// - /// For ScrollStrategy::Top, the item will be placed at the offset position from the top. + /// This uses non-strict scrolling: if the item is already visible within the offset region, + /// no scrolling occurs. /// - /// For ScrollStrategy::Center, the item will be centered between offset and the last visible item. + /// The offset parameter shrinks the effective viewport by the specified number of items + /// from the corresponding edge, then applies the scroll strategy within that reduced viewport: + /// - `ScrollStrategy::Top`: Shrinks from top, positions item at the new top + /// - `ScrollStrategy::Center`: Shrinks from top, centers item in the reduced viewport + /// - `ScrollStrategy::Bottom`: Shrinks from bottom, positions item at the new bottom pub fn scroll_to_item_with_offset(&self, ix: usize, strategy: ScrollStrategy, offset: usize) { self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem { item_index: ix, @@ -172,6 +184,30 @@ impl UniformListScrollHandle { }); } + /// Scroll the list so that the given item index is at the exact scroll strategy position with an offset. + /// + /// This uses strict scrolling: the item will always be scrolled to match the strategy position, + /// even if it's already visible. + /// + /// The offset parameter shrinks the effective viewport by the specified number of items + /// from the corresponding edge, then applies the scroll strategy within that reduced viewport: + /// - `ScrollStrategy::Top`: Shrinks from top, positions item at the new top + /// - `ScrollStrategy::Center`: Shrinks from top, centers item in the reduced viewport + /// - `ScrollStrategy::Bottom`: Shrinks from bottom, positions item at the new bottom + pub fn scroll_to_item_strict_with_offset( + &self, + ix: usize, + strategy: ScrollStrategy, + offset: usize, + ) { + self.0.borrow_mut().deferred_scroll_to_item = Some(DeferredScrollToItem { + item_index: ix, + strategy, + offset, + scroll_strict: true, + }); + } + /// Check if the list is flipped vertically. pub fn y_flipped(&self) -> bool { self.0.borrow().y_flipped @@ -392,7 +428,7 @@ impl Element for UniformList { { match deferred_scroll.strategy { ScrollStrategy::Top => { - updated_scroll_offset.y = -item_top + updated_scroll_offset.y = -(item_top - offset_pixels) .max(Pixels::ZERO) .min(content_height - list_height) .max(Pixels::ZERO); @@ -410,7 +446,8 @@ impl Element for UniformList { .max(Pixels::ZERO); } ScrollStrategy::Bottom => { - updated_scroll_offset.y = -(item_bottom - list_height) + updated_scroll_offset.y = -(item_bottom - list_height + + offset_pixels) .max(Pixels::ZERO) .min(content_height - list_height) .max(Pixels::ZERO); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index e9843d06d43be1376bddf6e57c1a71952c1e1fa0..c50b491a102ef2bc1ff65a4bafebab62e17a996f 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -4624,7 +4624,7 @@ impl ProjectPanel { project_panel.marked_entries.clear(); if is_sticky && let Some((_, _, index)) = project_panel.index_for_entry(entry_id, worktree_id) { - project_panel.scroll_handle.scroll_to_item_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0)); + project_panel.scroll_handle.scroll_to_item_strict_with_offset(index, ScrollStrategy::Top, sticky_index.unwrap_or(0)); cx.notify(); // move down by 1px so that clicked item // don't count as sticky anymore