virtual_list: Fix scrollable size calculate in VirtualList. (#1087)
The `virtual_list` scroll range issue because recently GPUI changed the `ScrollHandle` API, now we only get the `max_offset`. So #1086 have fixed this to implementation a `content_size` method to calculate `max_offset` and `bounds` of the `ScrollHandle`. But I found that the `Interactivity` prepaint (This is used in VirtualList) was not to update the `bounds` of the `ScrollHandle`. Wait https://github.com/zed-industries/zed/pull/35013 to merge. | Before | After | | -- | --- | | <img width="620" alt="image" src="https://github.com/user-attachments/assets/6ffb8f30-93b4-42ec-a837-90f80ecb2bf5" /> | <img width="620" alt="image" src="https://github.com/user-attachments/assets/21466332-030c-495f-8c84-e3a2c6eeb403" /> | - Split `Scrollbale` and `VirtualList` story. ## Break Changes - The `content_size` argument has been removed from `virtual_list` callback.
This commit is contained in:
parent
56cf05b749
commit
579c7a3cbb
7 changed files with 347 additions and 231 deletions
24
Cargo.lock
generated
24
Cargo.lock
generated
|
|
@ -1432,7 +1432,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "collections"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"indexmap 2.9.0",
|
||||
"rustc-hash 2.1.0",
|
||||
|
|
@ -1990,7 +1990,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "derive_refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -3142,7 +3142,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gpui"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"as-raw-xcb-connection",
|
||||
|
|
@ -3327,7 +3327,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "gpui_macros"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
|
|
@ -3597,7 +3597,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "http_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
|
@ -3615,7 +3615,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "http_client_tls"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"rustls-platform-verifier",
|
||||
|
|
@ -4441,7 +4441,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "media"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bindgen 0.71.1",
|
||||
|
|
@ -6390,7 +6390,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "refineable"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"derive_refineable",
|
||||
"workspace-hack",
|
||||
|
|
@ -6545,7 +6545,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "reqwest_client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
|
@ -7121,7 +7121,7 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
|
|||
[[package]]
|
||||
name = "semantic_version"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"serde",
|
||||
|
|
@ -7704,7 +7704,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
|||
[[package]]
|
||||
name = "sum_tree"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"log",
|
||||
|
|
@ -8931,7 +8931,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|||
[[package]]
|
||||
name = "util"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#3e27fa1d92d084a3374691a83bd3ab5766b14ad8"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-fs 2.1.2",
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ mod themes;
|
|||
mod title_bar;
|
||||
mod toggle_story;
|
||||
mod tooltip_story;
|
||||
mod virtual_list_story;
|
||||
mod webview_story;
|
||||
mod welcome_story;
|
||||
|
||||
|
|
@ -100,6 +101,7 @@ pub use title_bar::AppTitleBar;
|
|||
pub use toggle_story::ToggleStory;
|
||||
pub use tooltip_story::TooltipStory;
|
||||
use tracing_subscriber::{layer::SubscriberExt as _, util::SubscriberInitExt as _};
|
||||
pub use virtual_list_story::VirtualListStory;
|
||||
pub use webview_story::WebViewStory;
|
||||
pub use welcome_story::WelcomeStory;
|
||||
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ impl Gallery {
|
|||
StoryContainer::panel::<DropdownStory>(window, cx),
|
||||
StoryContainer::panel::<FormStory>(window, cx),
|
||||
StoryContainer::panel::<IconStory>(window, cx),
|
||||
StoryContainer::panel::<IndicatorStory>(window, cx),
|
||||
StoryContainer::panel::<ImageStory>(window, cx),
|
||||
StoryContainer::panel::<IndicatorStory>(window, cx),
|
||||
StoryContainer::panel::<InputStory>(window, cx),
|
||||
StoryContainer::panel::<KbdStory>(window, cx),
|
||||
StoryContainer::panel::<LabelStory>(window, cx),
|
||||
|
|
@ -78,6 +78,7 @@ impl Gallery {
|
|||
StoryContainer::panel::<TagStory>(window, cx),
|
||||
StoryContainer::panel::<TextareaStory>(window, cx),
|
||||
StoryContainer::panel::<TooltipStory>(window, cx),
|
||||
StoryContainer::panel::<VirtualListStory>(window, cx),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,29 +1,23 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use gpui::{
|
||||
div, px, size, App, AppContext, Axis, Context, Entity, Focusable, InteractiveElement,
|
||||
IntoElement, ParentElement, Pixels, Render, ScrollHandle, SharedString, Size, Styled, Window,
|
||||
div, px, size, App, AppContext, Axis, Context, Entity, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Render, Size, Styled, Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
button::{Button, ButtonGroup},
|
||||
divider::Divider,
|
||||
h_flex,
|
||||
label::Label,
|
||||
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
|
||||
v_flex, v_virtual_list, ActiveTheme as _, Selectable, StyledExt as _,
|
||||
scroll::ScrollbarState,
|
||||
v_flex, ActiveTheme as _, Selectable, StyledExt as _,
|
||||
};
|
||||
|
||||
pub struct ScrollableStory {
|
||||
focus_handle: gpui::FocusHandle,
|
||||
scroll_handle: ScrollHandle,
|
||||
scroll_size: gpui::Size<Pixels>,
|
||||
focus_handle: FocusHandle,
|
||||
scroll_state: ScrollbarState,
|
||||
items: Vec<String>,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
test_width: Pixels,
|
||||
axis: ScrollbarAxis,
|
||||
size_mode: usize,
|
||||
message: SharedString,
|
||||
}
|
||||
|
||||
const ITEM_HEIGHT: Pixels = px(30.);
|
||||
|
|
@ -39,15 +33,11 @@ impl ScrollableStory {
|
|||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scroll_state: ScrollbarState::default(),
|
||||
scroll_size: gpui::Size::default(),
|
||||
items,
|
||||
item_sizes: Rc::new(item_sizes),
|
||||
test_width,
|
||||
axis: ScrollbarAxis::Both,
|
||||
size_mode: 0,
|
||||
message: SharedString::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,91 +73,45 @@ impl ScrollableStory {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn change_axis(&mut self, axis: ScrollbarAxis, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_message(&mut self, msg: &str, cx: &mut Context<Self>) {
|
||||
self.message = SharedString::from(msg.to_string());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
h_flex().gap_2().justify_between().child(
|
||||
h_flex().gap_2().child(
|
||||
ButtonGroup::new("test-cases")
|
||||
.outline()
|
||||
.compact()
|
||||
.child(
|
||||
ButtonGroup::new("test-cases")
|
||||
.outline()
|
||||
.compact()
|
||||
.child(
|
||||
Button::new("test-0")
|
||||
.label("Size 0")
|
||||
.selected(self.size_mode == 0),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-1")
|
||||
.label("Size 1")
|
||||
.selected(self.size_mode == 1),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-2")
|
||||
.label("Size 2")
|
||||
.selected(self.size_mode == 2),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-3")
|
||||
.label("Size 3")
|
||||
.selected(self.size_mode == 3),
|
||||
)
|
||||
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
|
||||
if clicks.contains(&0) {
|
||||
view.change_test_cases(0, cx)
|
||||
} else if clicks.contains(&1) {
|
||||
view.change_test_cases(1, cx)
|
||||
} else if clicks.contains(&2) {
|
||||
view.change_test_cases(2, cx)
|
||||
} else if clicks.contains(&3) {
|
||||
view.change_test_cases(3, cx)
|
||||
}
|
||||
})),
|
||||
Button::new("test-0")
|
||||
.label("Size 0")
|
||||
.selected(self.size_mode == 0),
|
||||
)
|
||||
.child(Divider::vertical().px_2())
|
||||
.child(
|
||||
ButtonGroup::new("scrollbars")
|
||||
.outline()
|
||||
.compact()
|
||||
.child(
|
||||
Button::new("test-axis-both")
|
||||
.label("Both Scrollbar")
|
||||
.selected(self.axis.is_both()),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-axis-vertical")
|
||||
.label("Vertical")
|
||||
.selected(self.axis.is_vertical()),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-axis-horizontal")
|
||||
.label("Horizontal")
|
||||
.selected(self.axis.is_horizontal()),
|
||||
)
|
||||
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
|
||||
if clicks.contains(&0) {
|
||||
view.change_axis(ScrollbarAxis::Both, cx)
|
||||
} else if clicks.contains(&1) {
|
||||
view.change_axis(ScrollbarAxis::Vertical, cx)
|
||||
} else if clicks.contains(&2) {
|
||||
view.change_axis(ScrollbarAxis::Horizontal, cx)
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(Label::new(self.message.clone()))
|
||||
Button::new("test-1")
|
||||
.label("Size 1")
|
||||
.selected(self.size_mode == 1),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-2")
|
||||
.label("Size 2")
|
||||
.selected(self.size_mode == 2),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-3")
|
||||
.label("Size 3")
|
||||
.selected(self.size_mode == 3),
|
||||
)
|
||||
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
|
||||
if clicks.contains(&0) {
|
||||
view.change_test_cases(0, cx)
|
||||
} else if clicks.contains(&1) {
|
||||
view.change_test_cases(1, cx)
|
||||
} else if clicks.contains(&2) {
|
||||
view.change_test_cases(2, cx)
|
||||
} else if clicks.contains(&3) {
|
||||
view.change_test_cases(3, cx)
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +121,7 @@ impl super::Story for ScrollableStory {
|
|||
}
|
||||
|
||||
fn description() -> &'static str {
|
||||
"Add vertical or horizontal, or both scrollbars to a container, \
|
||||
and use `virtual_list` to render a large number of items."
|
||||
"A scrollable container."
|
||||
}
|
||||
|
||||
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
|
||||
|
|
@ -198,79 +141,11 @@ impl Render for ScrollableStory {
|
|||
_: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let test_width = self.test_width;
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_4()
|
||||
.child(self.render_buttons(cx))
|
||||
.child(
|
||||
div().w_full().flex_1().min_h_64().child(
|
||||
div().relative().size_full().child(
|
||||
v_flex()
|
||||
.id("test-0")
|
||||
.relative()
|
||||
.size_full()
|
||||
.child(
|
||||
v_virtual_list(
|
||||
cx.entity().clone(),
|
||||
"items",
|
||||
self.item_sizes.clone(),
|
||||
move |story, visible_range, content_size, _, cx| {
|
||||
story.set_message(
|
||||
&format!("visible_range: {:?}", visible_range),
|
||||
cx,
|
||||
);
|
||||
story.scroll_size = content_size;
|
||||
visible_range
|
||||
.map(|ix| {
|
||||
h_flex()
|
||||
.h(ITEM_HEIGHT)
|
||||
.gap_1()
|
||||
.children(
|
||||
(0..(story.test_width.0 as i32 / 100))
|
||||
.map(|i| {
|
||||
div()
|
||||
.flex()
|
||||
.h_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_sm()
|
||||
.w(px(100.))
|
||||
.bg(cx.theme().secondary)
|
||||
.child(if i == 0 {
|
||||
format!("{}", ix)
|
||||
} else {
|
||||
format!("{}", i)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.items_center()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.v_flex()
|
||||
.gap_1(),
|
||||
)
|
||||
.child({
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.right_0()
|
||||
.bottom_0()
|
||||
.child(
|
||||
Scrollbar::both(&self.scroll_state, &self.scroll_handle)
|
||||
.axis(self.axis),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child({
|
||||
div()
|
||||
.relative()
|
||||
|
|
@ -282,7 +157,7 @@ impl Render for ScrollableStory {
|
|||
.child(
|
||||
v_flex()
|
||||
.p_3()
|
||||
.w(self.test_width)
|
||||
.w(test_width)
|
||||
.id("test-1")
|
||||
.scrollable(Axis::Vertical)
|
||||
.gap_1()
|
||||
|
|
|
|||
256
crates/story/src/virtual_list_story.rs
Normal file
256
crates/story/src/virtual_list_story.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
use std::{ops::Range, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
div, px, size, App, AppContext, Context, Div, Entity, FocusHandle, Focusable,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Render, ScrollHandle, Size, Styled,
|
||||
Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
button::{Button, ButtonGroup},
|
||||
divider::Divider,
|
||||
h_flex,
|
||||
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
|
||||
v_flex, v_virtual_list, ActiveTheme as _, Selectable,
|
||||
};
|
||||
|
||||
pub struct VirtualListStory {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: ScrollHandle,
|
||||
scroll_state: ScrollbarState,
|
||||
items: Vec<String>,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
columns_count: usize,
|
||||
axis: ScrollbarAxis,
|
||||
size_mode: usize,
|
||||
visible_range: Range<usize>,
|
||||
}
|
||||
|
||||
const ITEM_SIZE: Size<Pixels> = size(px(100.), px(30.));
|
||||
|
||||
impl VirtualListStory {
|
||||
fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
|
||||
let item_sizes = items.iter().map(|_| ITEM_SIZE).collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scroll_state: ScrollbarState::default(),
|
||||
items,
|
||||
item_sizes: Rc::new(item_sizes),
|
||||
columns_count: 100,
|
||||
axis: ScrollbarAxis::Both,
|
||||
size_mode: 0,
|
||||
visible_range: (0..0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| Self::new(window, cx))
|
||||
}
|
||||
|
||||
pub fn change_test_cases(&mut self, n: usize, cx: &mut Context<Self>) {
|
||||
self.size_mode = n;
|
||||
if n == 0 {
|
||||
self.items = (0..5000).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
|
||||
self.columns_count = 30;
|
||||
} else if n == 1 {
|
||||
self.items = (0..100).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
|
||||
self.columns_count = 100;
|
||||
} else if n == 2 {
|
||||
self.items = (0..500000)
|
||||
.map(|i| format!("Item {}", i))
|
||||
.collect::<Vec<_>>();
|
||||
self.columns_count = 100;
|
||||
} else {
|
||||
self.items = (0..5).map(|i| format!("Item {}", i)).collect::<Vec<_>>();
|
||||
self.columns_count = 10;
|
||||
}
|
||||
|
||||
self.item_sizes = Rc::new(self.items.iter().map(|_| ITEM_SIZE).collect());
|
||||
|
||||
self.scroll_state = ScrollbarState::default();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn change_axis(&mut self, axis: ScrollbarAxis, cx: &mut Context<Self>) {
|
||||
self.axis = axis;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
ButtonGroup::new("test-cases")
|
||||
.outline()
|
||||
.compact()
|
||||
.child(
|
||||
Button::new("test-0")
|
||||
.label("Size 0")
|
||||
.selected(self.size_mode == 0),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-1")
|
||||
.label("Size 1")
|
||||
.selected(self.size_mode == 1),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-2")
|
||||
.label("Size 2")
|
||||
.selected(self.size_mode == 2),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-3")
|
||||
.label("Size 3")
|
||||
.selected(self.size_mode == 3),
|
||||
)
|
||||
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
|
||||
if clicks.contains(&0) {
|
||||
view.change_test_cases(0, cx)
|
||||
} else if clicks.contains(&1) {
|
||||
view.change_test_cases(1, cx)
|
||||
} else if clicks.contains(&2) {
|
||||
view.change_test_cases(2, cx)
|
||||
} else if clicks.contains(&3) {
|
||||
view.change_test_cases(3, cx)
|
||||
}
|
||||
})),
|
||||
)
|
||||
.child(Divider::vertical().px_2())
|
||||
.child(
|
||||
ButtonGroup::new("scrollbars")
|
||||
.outline()
|
||||
.compact()
|
||||
.child(
|
||||
Button::new("test-axis-both")
|
||||
.label("Both Scrollbar")
|
||||
.selected(self.axis.is_both()),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-axis-vertical")
|
||||
.label("Vertical")
|
||||
.selected(self.axis.is_vertical()),
|
||||
)
|
||||
.child(
|
||||
Button::new("test-axis-horizontal")
|
||||
.label("Horizontal")
|
||||
.selected(self.axis.is_horizontal()),
|
||||
)
|
||||
.on_click(cx.listener(|view, clicks: &Vec<usize>, _, cx| {
|
||||
if clicks.contains(&0) {
|
||||
view.change_axis(ScrollbarAxis::Both, cx)
|
||||
} else if clicks.contains(&1) {
|
||||
view.change_axis(ScrollbarAxis::Vertical, cx)
|
||||
} else if clicks.contains(&2) {
|
||||
view.change_axis(ScrollbarAxis::Horizontal, cx)
|
||||
}
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(format!("visible_range: {:?}", self.visible_range))
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Story for VirtualListStory {
|
||||
fn title() -> &'static str {
|
||||
"VirtualList"
|
||||
}
|
||||
|
||||
fn description() -> &'static str {
|
||||
"Add vertical or horizontal, or both scrollbars to a container, \
|
||||
and use `virtual_list` to render a large number of items."
|
||||
}
|
||||
|
||||
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
|
||||
Self::view(window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for VirtualListStory {
|
||||
fn focus_handle(&self, _: &gpui::App) -> gpui::FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for VirtualListStory {
|
||||
fn render(
|
||||
&mut self,
|
||||
_: &mut gpui::Window,
|
||||
cx: &mut gpui::Context<Self>,
|
||||
) -> impl gpui::IntoElement {
|
||||
let columns_count = self.columns_count;
|
||||
|
||||
fn render_item(cx: &App) -> Div {
|
||||
div()
|
||||
.flex()
|
||||
.h_full()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_sm()
|
||||
.w(ITEM_SIZE.width)
|
||||
.h(ITEM_SIZE.height)
|
||||
.bg(cx.theme().secondary)
|
||||
}
|
||||
|
||||
v_flex()
|
||||
.size_full()
|
||||
.gap_4()
|
||||
.child(self.render_buttons(cx))
|
||||
.child(
|
||||
div().w_full().flex_1().min_h_64().child(
|
||||
div().relative().size_full().child(
|
||||
v_flex()
|
||||
.id("list")
|
||||
.relative()
|
||||
.size_full()
|
||||
.child(
|
||||
v_virtual_list(
|
||||
cx.entity().clone(),
|
||||
"items",
|
||||
self.item_sizes.clone(),
|
||||
move |story, visible_range, _, cx| {
|
||||
story.visible_range = visible_range.clone();
|
||||
|
||||
visible_range
|
||||
.map(|ix| {
|
||||
h_flex().gap_1().items_center().children(
|
||||
(0..columns_count).map(|i| {
|
||||
render_item(cx).child(if i == 0 {
|
||||
format!("row: {}", ix)
|
||||
} else {
|
||||
format!("{}", i)
|
||||
})
|
||||
}),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().border)
|
||||
.gap_1(),
|
||||
)
|
||||
.child({
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.left_0()
|
||||
.right_0()
|
||||
.bottom_0()
|
||||
.child(
|
||||
Scrollbar::both(&self.scroll_state, &self.scroll_handle)
|
||||
.axis(self.axis),
|
||||
)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1093,7 +1093,7 @@ where
|
|||
Axis::Horizontal,
|
||||
col_sizes,
|
||||
{
|
||||
move |table, visible_range: Range<usize>, _, window, cx| {
|
||||
move |table, visible_range: Range<usize>, window, cx| {
|
||||
table.update_visible_range_if_need(
|
||||
visible_range.clone(),
|
||||
Axis::Horizontal,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ pub fn v_virtual_list<R, V>(
|
|||
view: Entity<V>,
|
||||
id: impl Into<ElementId>,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
) -> VirtualList
|
||||
where
|
||||
R: IntoElement,
|
||||
|
|
@ -49,7 +49,7 @@ pub fn h_virtual_list<R, V>(
|
|||
view: Entity<V>,
|
||||
id: impl Into<ElementId>,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
) -> VirtualList
|
||||
where
|
||||
R: IntoElement,
|
||||
|
|
@ -63,7 +63,7 @@ pub(crate) fn virtual_list<R, V>(
|
|||
id: impl Into<ElementId>,
|
||||
axis: Axis,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, Size<Pixels>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
f: impl 'static + Fn(&mut V, Range<usize>, &mut Window, &mut Context<V>) -> Vec<R>,
|
||||
) -> VirtualList
|
||||
where
|
||||
R: IntoElement,
|
||||
|
|
@ -71,9 +71,9 @@ where
|
|||
{
|
||||
let id: ElementId = id.into();
|
||||
let scroll_handle = ScrollHandle::default();
|
||||
let render_range = move |visible_range, content_size, window: &mut Window, cx: &mut App| {
|
||||
let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
|
||||
view.update(cx, |this, cx| {
|
||||
f(this, visible_range, content_size, window, cx)
|
||||
f(this, visible_range, window, cx)
|
||||
.into_iter()
|
||||
.map(|component| component.into_any_element())
|
||||
.collect()
|
||||
|
|
@ -101,16 +101,10 @@ pub struct VirtualList {
|
|||
axis: Axis,
|
||||
base: Stateful<Div>,
|
||||
scroll_handle: ScrollHandle,
|
||||
// scroll_handle: ScrollHandle,
|
||||
items_count: usize,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
render_items: Box<
|
||||
dyn for<'a> Fn(
|
||||
Range<usize>,
|
||||
Size<Pixels>,
|
||||
&'a mut Window,
|
||||
&'a mut App,
|
||||
) -> SmallVec<[AnyElement; 64]>,
|
||||
dyn for<'a> Fn(Range<usize>, &'a mut Window, &'a mut App) -> SmallVec<[AnyElement; 64]>,
|
||||
>,
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +140,7 @@ impl VirtualList {
|
|||
// So we try to use the second item to measure, if there is no second item, use the first item.
|
||||
let item_ix = if self.items_count > 1 { 1 } else { 0 };
|
||||
|
||||
let mut items = (self.render_items)(item_ix..item_ix + 1, Size::default(), window, cx);
|
||||
let mut items = (self.render_items)(item_ix..item_ix + 1, window, cx);
|
||||
let Some(mut item_to_measure) = items.pop() else {
|
||||
return Size::default();
|
||||
};
|
||||
|
|
@ -165,7 +159,7 @@ pub struct VirtualListFrameState {
|
|||
#[derive(Default, Clone)]
|
||||
pub struct ItemSizeLayout {
|
||||
items_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
container_size: Size<Pixels>,
|
||||
content_size: Pixels,
|
||||
sizes: Vec<Pixels>,
|
||||
origins: Vec<Pixels>,
|
||||
}
|
||||
|
|
@ -197,18 +191,18 @@ impl Element for VirtualList {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
let rem_size = window.rem_size();
|
||||
let style = self
|
||||
.base
|
||||
.interactivity()
|
||||
.compute_style(global_id, None, window, cx);
|
||||
let font_size = window.text_style().font_size.to_pixels(window.rem_size());
|
||||
let font_size = window.text_style().font_size.to_pixels(rem_size);
|
||||
|
||||
// Including the gap between items for calculate the item size
|
||||
let gap = match self.axis {
|
||||
Axis::Horizontal => style.gap.width,
|
||||
Axis::Vertical => style.gap.height,
|
||||
}
|
||||
.to_pixels(font_size.into(), window.rem_size());
|
||||
let gap = style
|
||||
.gap
|
||||
.along(self.axis)
|
||||
.to_pixels(font_size.into(), rem_size);
|
||||
|
||||
let (layout_id, size_layout) = window.with_element_state(
|
||||
global_id.unwrap(),
|
||||
|
|
@ -249,14 +243,7 @@ impl Element for VirtualList {
|
|||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
state.container_size = Size {
|
||||
width: px(self.item_sizes.iter().map(|size| size.width.0).sum::<f32>()),
|
||||
height: px(self
|
||||
.item_sizes
|
||||
.iter()
|
||||
.map(|size| size.height.0)
|
||||
.sum::<f32>()),
|
||||
};
|
||||
state.content_size = px(state.sizes.iter().map(|size| size.0).sum::<f32>());
|
||||
}
|
||||
|
||||
let (layout_id, _) = self
|
||||
|
|
@ -285,6 +272,8 @@ impl Element for VirtualList {
|
|||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
let first_item_size = self.measure_item(window, cx);
|
||||
|
||||
let style = self
|
||||
.base
|
||||
.interactivity()
|
||||
|
|
@ -294,16 +283,14 @@ impl Element for VirtualList {
|
|||
.padding
|
||||
.to_pixels(bounds.size.into(), window.rem_size());
|
||||
|
||||
let first_item_size = self.measure_item(window, cx);
|
||||
|
||||
let padded_bounds = Bounds::from_corners(
|
||||
let container_with_padding_bounds = Bounds::from_corners(
|
||||
bounds.origin + point(border.left + padding.left, border.top + padding.top),
|
||||
bounds.bottom_right()
|
||||
- point(border.right + padding.right, border.bottom + padding.bottom),
|
||||
);
|
||||
|
||||
// Get border + padding pixel size
|
||||
let padding_size = match self.axis {
|
||||
let padding_width = match self.axis {
|
||||
Axis::Horizontal => border.left + padding.left + border.right + padding.right,
|
||||
Axis::Vertical => border.top + padding.top + border.bottom + padding.bottom,
|
||||
};
|
||||
|
|
@ -311,14 +298,16 @@ impl Element for VirtualList {
|
|||
let item_sizes = &layout.size_layout.sizes;
|
||||
let item_origins = &layout.size_layout.origins;
|
||||
|
||||
let content_size = match self.axis {
|
||||
let scroll_size = match self.axis {
|
||||
Axis::Horizontal => Size {
|
||||
width: layout.size_layout.container_size.width + padding_size,
|
||||
height: (first_item_size.height + padding_size).max(padded_bounds.size.height),
|
||||
width: layout.size_layout.content_size + padding_width,
|
||||
height: (first_item_size.height + padding_width)
|
||||
.max(container_with_padding_bounds.size.height),
|
||||
},
|
||||
Axis::Vertical => Size {
|
||||
width: (first_item_size.width + padding_size).max(padded_bounds.size.width),
|
||||
height: layout.size_layout.container_size.height + padding_size,
|
||||
width: (first_item_size.width + padding_width)
|
||||
.max(container_with_padding_bounds.size.width),
|
||||
height: layout.size_layout.content_size + padding_width,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -326,7 +315,7 @@ impl Element for VirtualList {
|
|||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
content_size,
|
||||
scroll_size,
|
||||
window,
|
||||
cx,
|
||||
|style, _, hitbox, window, cx| {
|
||||
|
|
@ -342,15 +331,9 @@ impl Element for VirtualList {
|
|||
);
|
||||
|
||||
if self.items_count > 0 {
|
||||
let is_scrolled = match self.axis {
|
||||
Axis::Horizontal => !scroll_offset.x.is_zero(),
|
||||
Axis::Vertical => !scroll_offset.y.is_zero(),
|
||||
};
|
||||
|
||||
let min_scroll_offset = match self.axis {
|
||||
Axis::Horizontal => padded_bounds.size.width - content_size.width,
|
||||
Axis::Vertical => padded_bounds.size.height - content_size.height,
|
||||
};
|
||||
let is_scrolled = !scroll_offset.along(self.axis).is_zero();
|
||||
let min_scroll_offset =
|
||||
padded_bounds.size.along(self.axis) - scroll_size.along(self.axis);
|
||||
|
||||
if is_scrolled {
|
||||
match self.axis {
|
||||
|
|
@ -425,8 +408,7 @@ impl Element for VirtualList {
|
|||
let visible_range = first_visible_element_ix
|
||||
..cmp::min(last_visible_element_ix, self.items_count);
|
||||
|
||||
let items =
|
||||
(self.render_items)(visible_range.clone(), content_size, window, cx);
|
||||
let items = (self.render_items)(visible_range.clone(), window, cx);
|
||||
|
||||
let content_mask = ContentMask { bounds };
|
||||
window.with_content_mask(Some(content_mask), |window| {
|
||||
|
|
|
|||
Loading…
Reference in a new issue