Simplify ListState
API (#35685)
Follow up to: https://github.com/zed-industries/zed/pull/35670, simplifies the List state APIs so you no longer have to worry about strong vs. weak pointers when rendering list items. Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
parent
d0de81b0b4
commit
53175263a1
15 changed files with 322 additions and 403 deletions
|
@ -18,10 +18,16 @@ use refineable::Refineable as _;
|
|||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||
use sum_tree::{Bias, Dimensions, SumTree};
|
||||
|
||||
type RenderItemFn = dyn FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static;
|
||||
|
||||
/// Construct a new list element
|
||||
pub fn list(state: ListState) -> List {
|
||||
pub fn list(
|
||||
state: ListState,
|
||||
render_item: impl FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static,
|
||||
) -> List {
|
||||
List {
|
||||
state,
|
||||
render_item: Box::new(render_item),
|
||||
style: StyleRefinement::default(),
|
||||
sizing_behavior: ListSizingBehavior::default(),
|
||||
}
|
||||
|
@ -30,6 +36,7 @@ pub fn list(state: ListState) -> List {
|
|||
/// A list element
|
||||
pub struct List {
|
||||
state: ListState,
|
||||
render_item: Box<RenderItemFn>,
|
||||
style: StyleRefinement,
|
||||
sizing_behavior: ListSizingBehavior,
|
||||
}
|
||||
|
@ -55,7 +62,6 @@ impl std::fmt::Debug for ListState {
|
|||
struct StateInner {
|
||||
last_layout_bounds: Option<Bounds<Pixels>>,
|
||||
last_padding: Option<Edges<Pixels>>,
|
||||
render_item: Box<dyn FnMut(usize, &mut Window, &mut App) -> AnyElement>,
|
||||
items: SumTree<ListItem>,
|
||||
logical_scroll_top: Option<ListOffset>,
|
||||
alignment: ListAlignment,
|
||||
|
@ -186,19 +192,10 @@ impl ListState {
|
|||
/// above and below the visible area. Elements within this area will
|
||||
/// be measured even though they are not visible. This can help ensure
|
||||
/// that the list doesn't flicker or pop in when scrolling.
|
||||
pub fn new<R>(
|
||||
item_count: usize,
|
||||
alignment: ListAlignment,
|
||||
overdraw: Pixels,
|
||||
render_item: R,
|
||||
) -> Self
|
||||
where
|
||||
R: 'static + FnMut(usize, &mut Window, &mut App) -> AnyElement,
|
||||
{
|
||||
pub fn new(item_count: usize, alignment: ListAlignment, overdraw: Pixels) -> Self {
|
||||
let this = Self(Rc::new(RefCell::new(StateInner {
|
||||
last_layout_bounds: None,
|
||||
last_padding: None,
|
||||
render_item: Box::new(render_item),
|
||||
items: SumTree::default(),
|
||||
logical_scroll_top: None,
|
||||
alignment,
|
||||
|
@ -532,6 +529,7 @@ impl StateInner {
|
|||
available_width: Option<Pixels>,
|
||||
available_height: Pixels,
|
||||
padding: &Edges<Pixels>,
|
||||
render_item: &mut RenderItemFn,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> LayoutItemsResponse {
|
||||
|
@ -566,7 +564,7 @@ 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 item_index = scroll_top.item_ix + ix;
|
||||
let mut element = (self.render_item)(item_index, window, cx);
|
||||
let mut element = render_item(item_index, window, cx);
|
||||
let element_size = element.layout_as_root(available_item_space, window, cx);
|
||||
size = Some(element_size);
|
||||
if visible_height < available_height {
|
||||
|
@ -601,7 +599,7 @@ impl StateInner {
|
|||
cursor.prev();
|
||||
if let Some(item) = cursor.item() {
|
||||
let item_index = cursor.start().0;
|
||||
let mut element = (self.render_item)(item_index, window, cx);
|
||||
let mut element = render_item(item_index, window, cx);
|
||||
let element_size = element.layout_as_root(available_item_space, window, cx);
|
||||
let focus_handle = item.focus_handle();
|
||||
rendered_height += element_size.height;
|
||||
|
@ -650,7 +648,7 @@ impl StateInner {
|
|||
let size = if let ListItem::Measured { size, .. } = item {
|
||||
*size
|
||||
} else {
|
||||
let mut element = (self.render_item)(cursor.start().0, window, cx);
|
||||
let mut element = render_item(cursor.start().0, window, cx);
|
||||
element.layout_as_root(available_item_space, window, cx)
|
||||
};
|
||||
|
||||
|
@ -683,7 +681,7 @@ impl StateInner {
|
|||
while let Some(item) = cursor.item() {
|
||||
if item.contains_focused(window, cx) {
|
||||
let item_index = cursor.start().0;
|
||||
let mut element = (self.render_item)(cursor.start().0, window, cx);
|
||||
let mut element = render_item(cursor.start().0, window, cx);
|
||||
let size = element.layout_as_root(available_item_space, window, cx);
|
||||
item_layouts.push_back(ItemLayout {
|
||||
index: item_index,
|
||||
|
@ -708,6 +706,7 @@ impl StateInner {
|
|||
bounds: Bounds<Pixels>,
|
||||
padding: Edges<Pixels>,
|
||||
autoscroll: bool,
|
||||
render_item: &mut RenderItemFn,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Result<LayoutItemsResponse, ListOffset> {
|
||||
|
@ -716,6 +715,7 @@ impl StateInner {
|
|||
Some(bounds.size.width),
|
||||
bounds.size.height,
|
||||
&padding,
|
||||
render_item,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
@ -753,8 +753,7 @@ impl StateInner {
|
|||
let Some(item) = cursor.item() else { break };
|
||||
|
||||
let size = item.size().unwrap_or_else(|| {
|
||||
let mut item =
|
||||
(self.render_item)(cursor.start().0, window, cx);
|
||||
let mut item = render_item(cursor.start().0, window, cx);
|
||||
let item_available_size = size(
|
||||
bounds.size.width.into(),
|
||||
AvailableSpace::MinContent,
|
||||
|
@ -876,8 +875,14 @@ impl Element for List {
|
|||
window.rem_size(),
|
||||
);
|
||||
|
||||
let layout_response =
|
||||
state.layout_items(None, available_height, &padding, window, cx);
|
||||
let layout_response = state.layout_items(
|
||||
None,
|
||||
available_height,
|
||||
&padding,
|
||||
&mut self.render_item,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let max_element_width = layout_response.max_item_width;
|
||||
|
||||
let summary = state.items.summary();
|
||||
|
@ -951,15 +956,16 @@ impl Element for List {
|
|||
let padding = style
|
||||
.padding
|
||||
.to_pixels(bounds.size.into(), window.rem_size());
|
||||
let layout = match state.prepaint_items(bounds, padding, true, window, cx) {
|
||||
Ok(layout) => layout,
|
||||
Err(autoscroll_request) => {
|
||||
state.logical_scroll_top = Some(autoscroll_request);
|
||||
state
|
||||
.prepaint_items(bounds, padding, false, window, cx)
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
let layout =
|
||||
match state.prepaint_items(bounds, padding, true, &mut self.render_item, window, cx) {
|
||||
Ok(layout) => layout,
|
||||
Err(autoscroll_request) => {
|
||||
state.logical_scroll_top = Some(autoscroll_request);
|
||||
state
|
||||
.prepaint_items(bounds, padding, false, &mut self.render_item, window, cx)
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
state.last_layout_bounds = Some(bounds);
|
||||
state.last_padding = Some(padding);
|
||||
|
@ -1108,9 +1114,7 @@ mod test {
|
|||
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _, _| {
|
||||
div().h(px(10.)).w_full().into_any()
|
||||
});
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.));
|
||||
|
||||
// Ensure that the list is scrolled to the top
|
||||
state.scroll_to(gpui::ListOffset {
|
||||
|
@ -1121,7 +1125,11 @@ mod test {
|
|||
struct TestView(ListState);
|
||||
impl Render for TestView {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
list(self.0.clone()).w_full().h_full()
|
||||
list(self.0.clone(), |_, _, _| {
|
||||
div().h(px(10.)).w_full().into_any()
|
||||
})
|
||||
.w_full()
|
||||
.h_full()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1154,14 +1162,16 @@ mod test {
|
|||
|
||||
let cx = cx.add_empty_window();
|
||||
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _, _| {
|
||||
div().h(px(20.)).w_full().into_any()
|
||||
});
|
||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.));
|
||||
|
||||
struct TestView(ListState);
|
||||
impl Render for TestView {
|
||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||
list(self.0.clone()).w_full().h_full()
|
||||
list(self.0.clone(), |_, _, _| {
|
||||
div().h(px(20.)).w_full().into_any()
|
||||
})
|
||||
.w_full()
|
||||
.h_full()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue