project_panel: Only show sticky item shadow when list is scrolled (#34050)

Follow up: https://github.com/zed-industries/zed/pull/34042

- Removes `top_slot_items` from `uniform_list` in favor of using
existing `decorations`
- Add condition to only show shadow for sticky item when list is
scrolled and scrollable

Release Notes:

- N/A
This commit is contained in:
Smit Barmase 2025-07-08 14:22:24 +05:30 committed by GitHub
parent f1db3b4e1d
commit 1f3575ad6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 180 additions and 178 deletions

View file

@ -147,6 +147,7 @@ mod uniform_list {
&self,
visible_range: Range<usize>,
bounds: Bounds<Pixels>,
_scroll_offset: Point<Pixels>,
item_height: Pixels,
item_count: usize,
window: &mut Window,

View file

@ -1,7 +1,8 @@
use std::ops::Range;
use std::{ops::Range, rc::Rc};
use gpui::{
AnyElement, App, AvailableSpace, Bounds, Context, Entity, Pixels, Render, UniformListTopSlot,
AnyElement, App, AvailableSpace, Bounds, Context, Element, ElementId, Entity, GlobalElementId,
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Render, Style, UniformListDecoration,
Window, point, size,
};
use smallvec::SmallVec;
@ -10,16 +11,16 @@ pub trait StickyCandidate {
fn depth(&self) -> usize;
}
#[derive(Clone)]
pub struct StickyItems<T> {
compute_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<T>>,
render_fn: Box<dyn Fn(T, &mut Window, &mut App) -> SmallVec<[AnyElement; 8]>>,
last_item_is_drifting: bool,
anchor_index: Option<usize>,
compute_fn: Rc<dyn Fn(Range<usize>, &mut Window, &mut App) -> SmallVec<[T; 8]>>,
render_fn: Rc<dyn Fn(T, &mut Window, &mut App) -> SmallVec<[AnyElement; 8]>>,
}
pub fn sticky_items<V, T>(
entity: Entity<V>,
compute_fn: impl Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<T> + 'static,
compute_fn: impl Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> SmallVec<[T; 8]>
+ 'static,
render_fn: impl Fn(&mut V, T, &mut Window, &mut Context<V>) -> SmallVec<[AnyElement; 8]> + 'static,
) -> StickyItems<T>
where
@ -29,37 +30,106 @@ where
let entity_compute = entity.clone();
let entity_render = entity.clone();
let compute_fn = Box::new(
move |range: Range<usize>, window: &mut Window, cx: &mut App| -> Vec<T> {
let compute_fn = Rc::new(
move |range: Range<usize>, window: &mut Window, cx: &mut App| -> SmallVec<[T; 8]> {
entity_compute.update(cx, |view, cx| compute_fn(view, range, window, cx))
},
);
let render_fn = Box::new(
let render_fn = Rc::new(
move |entry: T, window: &mut Window, cx: &mut App| -> SmallVec<[AnyElement; 8]> {
entity_render.update(cx, |view, cx| render_fn(view, entry, window, cx))
},
);
StickyItems {
compute_fn,
render_fn,
last_item_is_drifting: false,
anchor_index: None,
}
}
impl<T> UniformListTopSlot for StickyItems<T>
struct StickyItemsElement {
elements: SmallVec<[AnyElement; 8]>,
}
impl IntoElement for StickyItemsElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl Element for StickyItemsElement {
type RequestLayoutState = ();
type PrepaintState = ();
fn id(&self) -> Option<ElementId> {
None
}
fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
None
}
fn request_layout(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, Self::RequestLayoutState) {
(window.request_layout(Style::default(), [], cx), ())
}
fn prepaint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_window: &mut Window,
_cx: &mut App,
) -> Self::PrepaintState {
()
}
fn paint(
&mut self,
_id: Option<&GlobalElementId>,
_inspector_id: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>,
_request_layout: &mut Self::RequestLayoutState,
_prepaint: &mut Self::PrepaintState,
window: &mut Window,
cx: &mut App,
) {
// reverse so that last item is bottom most among sticky items
for item in self.elements.iter_mut().rev() {
item.paint(window, cx);
}
}
}
impl<T> UniformListDecoration for StickyItems<T>
where
T: StickyCandidate + Clone + 'static,
{
fn compute(
&mut self,
&self,
visible_range: Range<usize>,
bounds: Bounds<Pixels>,
scroll_offset: Point<Pixels>,
item_height: Pixels,
_item_count: usize,
window: &mut Window,
cx: &mut App,
) -> SmallVec<[AnyElement; 8]> {
) -> AnyElement {
let entries = (self.compute_fn)(visible_range.clone(), window, cx);
let mut elements = SmallVec::new();
let mut anchor_entry = None;
let mut last_item_is_drifting = false;
let mut anchor_index = None;
let mut iter = entries.iter().enumerate().peekable();
while let Some((ix, current_entry)) = iter.next() {
@ -75,8 +145,8 @@ where
let next_depth = next_entry.depth();
if next_depth < current_depth && next_depth < index_in_range {
self.last_item_is_drifting = true;
self.anchor_index = Some(visible_range.start + ix);
last_item_is_drifting = true;
anchor_index = Some(visible_range.start + ix);
anchor_entry = Some(current_entry.clone());
break;
}
@ -84,67 +154,36 @@ where
}
if let Some(anchor_entry) = anchor_entry {
(self.render_fn)(anchor_entry, window, cx)
} else {
SmallVec::new()
}
}
elements = (self.render_fn)(anchor_entry, window, cx);
let items_count = elements.len();
fn prepaint(
&self,
items: &mut SmallVec<[AnyElement; 8]>,
bounds: Bounds<Pixels>,
item_height: Pixels,
scroll_offset: gpui::Point<Pixels>,
padding: gpui::Edges<Pixels>,
can_scroll_horizontally: bool,
window: &mut Window,
cx: &mut App,
) {
let items_count = items.len();
for (ix, element) in elements.iter_mut().enumerate() {
let mut item_y_offset = None;
if ix == items_count - 1 && last_item_is_drifting {
if let Some(anchor_index) = anchor_index {
let scroll_top = -scroll_offset.y;
let anchor_top = item_height * anchor_index;
let sticky_area_height = item_height * items_count;
item_y_offset =
Some((anchor_top - scroll_top - sticky_area_height).min(Pixels::ZERO));
};
}
for (ix, item) in items.iter_mut().enumerate() {
let mut item_y_offset = None;
if ix == items_count - 1 && self.last_item_is_drifting {
if let Some(anchor_index) = self.anchor_index {
let scroll_top = -scroll_offset.y;
let anchor_top = item_height * anchor_index;
let sticky_area_height = item_height * items_count;
item_y_offset =
Some((anchor_top - scroll_top - sticky_area_height).min(Pixels::ZERO));
};
}
let sticky_origin = bounds.origin
+ point(
-scroll_offset.x,
-scroll_offset.y + item_height * ix + item_y_offset.unwrap_or(Pixels::ZERO),
);
let sticky_origin = bounds.origin
+ point(
if can_scroll_horizontally {
scroll_offset.x + padding.left
} else {
scroll_offset.x
},
item_height * ix + padding.top + item_y_offset.unwrap_or(Pixels::ZERO),
let available_space = size(
AvailableSpace::Definite(bounds.size.width),
AvailableSpace::Definite(item_height),
);
let available_width = if can_scroll_horizontally {
bounds.size.width + scroll_offset.x.abs()
} else {
bounds.size.width
};
let available_space = size(
AvailableSpace::Definite(available_width),
AvailableSpace::Definite(item_height),
);
item.layout_as_root(available_space, window, cx);
item.prepaint_at(sticky_origin, window, cx);
element.layout_as_root(available_space, window, cx);
element.prepaint_at(sticky_origin, window, cx);
}
}
}
fn paint(&self, items: &mut SmallVec<[AnyElement; 8]>, window: &mut Window, cx: &mut App) {
// reverse so that last item is bottom most among sticky items
for item in items.iter_mut().rev() {
item.paint(window, cx);
}
StickyItemsElement { elements }.into_any_element()
}
}