mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-31 19:05:00 +07:00
gpui: Add item_is_above_viewport and item_is_below_viewport APIs to ListState (#58061)
In prep for handling the above-viewport case in https://github.com/zed-industries/zed/pull/57632, which currently only handles below case. This PR adds `ListState::item_is_above_viewport` and `ListState::item_is_below_viewport` methods, which report whether a given list item is entirely outside the current viewport. Both return `None` when the list has not measured enough layout to answer. Release Notes: - N/A
This commit is contained in:
parent
81f818aa86
commit
a1d2ef6514
1 changed files with 146 additions and 0 deletions
|
|
@ -741,6 +741,44 @@ impl ListState {
|
|||
pub fn viewport_bounds(&self) -> Bounds<Pixels> {
|
||||
self.0.borrow().last_layout_bounds.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns whether the item is entirely above the viewport, or `None` if
|
||||
/// the list has not measured enough layout to know.
|
||||
pub fn item_is_above_viewport(&self, ix: usize) -> Option<bool> {
|
||||
let viewport_bounds = self.viewport_bounds();
|
||||
if viewport_bounds.size.height == px(0.0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scroll_top = self.logical_scroll_top();
|
||||
if ix < scroll_top.item_ix {
|
||||
// Rows before the logical scroll top have no item bounds, but
|
||||
// their position relative to the viewport is known from scroll state.
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
let item_bounds = self.bounds_for_item(ix)?;
|
||||
Some(item_bounds.bottom() <= viewport_bounds.top())
|
||||
}
|
||||
|
||||
/// Returns whether the item is entirely below the viewport, or `None` if
|
||||
/// the list has not measured enough layout to know.
|
||||
pub fn item_is_below_viewport(&self, ix: usize) -> Option<bool> {
|
||||
let viewport_bounds = self.viewport_bounds();
|
||||
if viewport_bounds.size.height == px(0.0) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let scroll_top = self.logical_scroll_top();
|
||||
if ix < scroll_top.item_ix {
|
||||
// Rows before the logical scroll top have no item bounds, but
|
||||
// their position relative to the viewport is known from scroll state.
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
let item_bounds = self.bounds_for_item(ix)?;
|
||||
Some(item_bounds.top() >= viewport_bounds.bottom())
|
||||
}
|
||||
}
|
||||
|
||||
impl StateInner {
|
||||
|
|
@ -1644,6 +1682,114 @@ mod test {
|
|||
assert_eq!(offset.offset_in_item, px(0.));
|
||||
}
|
||||
|
||||
struct TestListView(ListState);
|
||||
impl Render for TestListView {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
list(self.0.clone(), |_, _, _| {
|
||||
div().h(px(20.)).w_full().into_any()
|
||||
})
|
||||
.w_full()
|
||||
.h_full()
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_return_none_before_layout(_cx: &mut TestAppContext) {
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
assert_eq!(state.item_is_above_viewport(0), None);
|
||||
assert_eq!(state.item_is_below_viewport(0), None);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_before_logical_scroll_top(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
state.scroll_to(gpui::ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: px(0.),
|
||||
});
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
|
||||
cx.new(|_| TestListView(state.clone())).into_any_element()
|
||||
});
|
||||
|
||||
assert_eq!(state.item_is_above_viewport(1), Some(true));
|
||||
assert_eq!(state.item_is_below_viewport(1), Some(false));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_measured_item_inside_viewport(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
state.scroll_to(gpui::ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: px(0.),
|
||||
});
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
|
||||
cx.new(|_| TestListView(state.clone())).into_any_element()
|
||||
});
|
||||
|
||||
assert_eq!(state.item_is_above_viewport(2), Some(false));
|
||||
assert_eq!(state.item_is_below_viewport(2), Some(false));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_measured_item_above_viewport(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
state.scroll_to(gpui::ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: px(20.),
|
||||
});
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
|
||||
cx.new(|_| TestListView(state.clone())).into_any_element()
|
||||
});
|
||||
|
||||
assert_eq!(state.item_is_above_viewport(2), Some(true));
|
||||
assert_eq!(state.item_is_below_viewport(2), Some(false));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_measured_item_below_viewport(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
state.scroll_to(gpui::ListOffset {
|
||||
item_ix: 2,
|
||||
offset_in_item: px(0.),
|
||||
});
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
|
||||
cx.new(|_| TestListView(state.clone())).into_any_element()
|
||||
});
|
||||
|
||||
assert_eq!(state.item_is_above_viewport(3), Some(false));
|
||||
assert_eq!(state.item_is_below_viewport(3), Some(true));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_item_viewport_queries_after_scroll_to_end_before_layout(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.)).measure_all();
|
||||
|
||||
cx.draw(point(px(0.), px(0.)), size(px(100.), px(20.)), |_, cx| {
|
||||
cx.new(|_| TestListView(state.clone())).into_any_element()
|
||||
});
|
||||
|
||||
state.scroll_to_end();
|
||||
|
||||
assert_eq!(state.logical_scroll_top().item_ix, state.item_count());
|
||||
assert_eq!(state.item_is_above_viewport(0), Some(true));
|
||||
assert_eq!(state.item_is_below_viewport(0), Some(false));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_measure_all_after_width_change(cx: &mut TestAppContext) {
|
||||
let cx = cx.add_empty_window();
|
||||
|
|
|
|||
Loading…
Reference in a new issue