virtual_list: Add VirtualListScrollHandle to support scroll_to_item. (#1108)
- Also fixed Table scroll to selected column support. https://github.com/user-attachments/assets/3b7c51a7-addf-4657-9f9f-de9047fffab8
This commit is contained in:
parent
cb6695c774
commit
efa2c7c9ef
5 changed files with 309 additions and 100 deletions
28
Cargo.lock
generated
28
Cargo.lock
generated
|
|
@ -733,7 +733,7 @@ dependencies = [
|
|||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
|
|
@ -756,7 +756,7 @@ dependencies = [
|
|||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.10.5",
|
||||
"itertools 0.11.0",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
|
|
@ -1432,7 +1432,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "collections"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zed-industries/zed.git#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
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#65759d43163bd36cce8f8aa492ba2628ca005a7a"
|
||||
source = "git+https://github.com/zed-industries/zed.git#5488398986487afa6e5b38ea55f768271117b18b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-fs 2.1.2",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ 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,
|
||||
InteractiveElement, IntoElement, ParentElement, Pixels, Render, ScrollStrategy, Size, Styled,
|
||||
Window,
|
||||
};
|
||||
use gpui_component::{
|
||||
|
|
@ -10,12 +10,12 @@ use gpui_component::{
|
|||
divider::Divider,
|
||||
h_flex,
|
||||
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
|
||||
v_flex, v_virtual_list, ActiveTheme as _, Selectable,
|
||||
v_flex, v_virtual_list, ActiveTheme as _, Selectable, Sizable, VirtualListScrollHandle,
|
||||
};
|
||||
|
||||
pub struct VirtualListStory {
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: ScrollHandle,
|
||||
scroll_handle: VirtualListScrollHandle,
|
||||
scroll_state: ScrollbarState,
|
||||
items: Vec<String>,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
|
|
@ -34,7 +34,7 @@ impl VirtualListStory {
|
|||
|
||||
Self {
|
||||
focus_handle: cx.focus_handle(),
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
scroll_handle: VirtualListScrollHandle::new(),
|
||||
scroll_state: ScrollbarState::default(),
|
||||
items,
|
||||
item_sizes: Rc::new(item_sizes),
|
||||
|
|
@ -79,80 +79,125 @@ impl VirtualListStory {
|
|||
}
|
||||
|
||||
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
h_flex()
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.child(
|
||||
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)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
ButtonGroup::new("test-cases")
|
||||
Button::new("scroll-to0")
|
||||
.small()
|
||||
.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)
|
||||
}
|
||||
.label("Scroll to Top")
|
||||
.on_click(cx.listener(|this, _, _, _| {
|
||||
this.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);
|
||||
})),
|
||||
)
|
||||
.child(Divider::vertical().px_2())
|
||||
.child(
|
||||
ButtonGroup::new("scrollbars")
|
||||
Button::new("scroll-to1")
|
||||
.small()
|
||||
.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)
|
||||
}
|
||||
.label("Scroll to 50")
|
||||
.on_click(cx.listener(|this, _, _, _| {
|
||||
this.scroll_handle.scroll_to_item(50, ScrollStrategy::Top);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("scroll-to2")
|
||||
.small()
|
||||
.outline()
|
||||
.label("Scroll to 25 (center)")
|
||||
.on_click(cx.listener(|this, _, _, _| {
|
||||
this.scroll_handle
|
||||
.scroll_to_item(25, ScrollStrategy::Center);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("scroll-to-bottom")
|
||||
.small()
|
||||
.outline()
|
||||
.label("Scroll to Bottom")
|
||||
.on_click(cx.listener(|this, _, _, _| {
|
||||
this.scroll_handle.scroll_to_bottom();
|
||||
})),
|
||||
),
|
||||
)
|
||||
.child(format!("visible_range: {:?}", self.visible_range))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ pub use root::{ContextModal, Root};
|
|||
pub use styled::*;
|
||||
pub use time::*;
|
||||
pub use title_bar::*;
|
||||
pub use virtual_list::{h_virtual_list, v_virtual_list, VirtualList};
|
||||
pub use virtual_list::{h_virtual_list, v_virtual_list, VirtualList, VirtualListScrollHandle};
|
||||
pub use window_border::{window_border, window_paddings, WindowBorder};
|
||||
|
||||
pub use icon::*;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ use crate::{
|
|||
popup_menu::PopupMenu,
|
||||
scroll::{self, ScrollableMask, Scrollbar, ScrollbarState},
|
||||
v_flex, ActiveTheme, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt,
|
||||
VirtualListScrollHandle,
|
||||
};
|
||||
use gpui::{
|
||||
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, App, AppContext, Axis, Bounds,
|
||||
Context, Div, DragMoveEvent, Edges, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, KeyBinding, ListSizingBehavior, MouseButton, MouseDownEvent, ParentElement,
|
||||
Pixels, Point, Render, ScrollHandle, ScrollStrategy, ScrollWheelEvent, SharedString,
|
||||
Pixels, Point, Render, ScrollStrategy, ScrollWheelEvent, SharedString,
|
||||
StatefulInteractiveElement as _, Styled, Task, UniformListScrollHandle, Window,
|
||||
};
|
||||
|
||||
|
|
@ -103,7 +104,7 @@ pub struct Table<D: TableDelegate> {
|
|||
|
||||
pub vertical_scroll_handle: UniformListScrollHandle,
|
||||
pub vertical_scroll_state: ScrollbarState,
|
||||
pub horizontal_scroll_handle: ScrollHandle,
|
||||
pub horizontal_scroll_handle: VirtualListScrollHandle,
|
||||
pub horizontal_scroll_state: ScrollbarState,
|
||||
|
||||
scrollbar_visible: Edges<bool>,
|
||||
|
|
@ -137,7 +138,7 @@ where
|
|||
focus_handle: cx.focus_handle(),
|
||||
delegate,
|
||||
col_groups: Vec::new(),
|
||||
horizontal_scroll_handle: ScrollHandle::new(),
|
||||
horizontal_scroll_handle: VirtualListScrollHandle::new(),
|
||||
vertical_scroll_handle: UniformListScrollHandle::new(),
|
||||
vertical_scroll_state: ScrollbarState::default(),
|
||||
horizontal_scroll_state: ScrollbarState::default(),
|
||||
|
|
@ -269,6 +270,17 @@ where
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn fixed_left_cols_count(&self) -> usize {
|
||||
if !self.col_fixed {
|
||||
return 0;
|
||||
}
|
||||
|
||||
self.col_groups
|
||||
.iter()
|
||||
.filter(|col| col.column.fixed == Some(ColumnFixed::Left))
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Scroll to the row at the given index.
|
||||
pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
|
||||
self.vertical_scroll_handle
|
||||
|
|
@ -277,11 +289,13 @@ where
|
|||
}
|
||||
|
||||
// Scroll to the column at the given index.
|
||||
// TODO: Fix scroll to selected col, this was not working after fixed col.
|
||||
// pub fn scroll_to_col(&mut self, col_ix: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
// self.horizontal_scroll_handle.scroll_to_item(col_ix);
|
||||
// cx.notify();
|
||||
// }
|
||||
pub fn scroll_to_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
|
||||
let col_ix = col_ix.saturating_sub(self.fixed_left_cols_count());
|
||||
|
||||
self.horizontal_scroll_handle
|
||||
.scroll_to_item(col_ix, ScrollStrategy::Top);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Returns the selected row index.
|
||||
pub fn selected_row(&self) -> Option<usize> {
|
||||
|
|
@ -310,11 +324,8 @@ where
|
|||
pub fn set_selected_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
|
||||
self.selection_state = SelectionState::Column;
|
||||
self.selected_col = Some(col_ix);
|
||||
if let Some(_col_ix) = self.selected_col {
|
||||
// TODO: Fix scroll to selected col, this was not working after fixed col.
|
||||
// if self.col_groups[col_ix].fixed.is_none() {
|
||||
// self.horizontal_scroll_handle.scroll_to_item(col_ix);
|
||||
// }
|
||||
if let Some(col_ix) = self.selected_col {
|
||||
self.scroll_to_col(col_ix, cx);
|
||||
}
|
||||
cx.emit(TableEvent::SelectColumn(col_ix));
|
||||
cx.notify();
|
||||
|
|
|
|||
|
|
@ -10,16 +10,147 @@
|
|||
//! Unlike the `uniform_list`, the each item can have different size.
|
||||
//!
|
||||
//! This is useful for more complex layout, for example, a table with different row height.
|
||||
use std::{cmp, ops::Range, rc::Rc};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp,
|
||||
ops::{Deref, Range},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use gpui::{
|
||||
div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
|
||||
Context, Div, Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement,
|
||||
IntoElement, IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful,
|
||||
Context, Div, Element, ElementId, Entity, GlobalElementId, Half, Hitbox, InteractiveElement,
|
||||
IntoElement, IsZero as _, Pixels, Point, Render, ScrollHandle, ScrollStrategy, Size, Stateful,
|
||||
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{scroll::ScrollHandleOffsetable, AxisExt};
|
||||
|
||||
struct VirtualListScrollHandleState {
|
||||
axis: Axis,
|
||||
items_bounds: Vec<Bounds<Pixels>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VirtualListScrollHandle {
|
||||
state: Rc<RefCell<VirtualListScrollHandleState>>,
|
||||
base_handle: ScrollHandle,
|
||||
}
|
||||
|
||||
impl From<ScrollHandle> for VirtualListScrollHandle {
|
||||
fn from(handle: ScrollHandle) -> Self {
|
||||
let mut this = VirtualListScrollHandle::new();
|
||||
this.base_handle = handle;
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<ScrollHandle> for VirtualListScrollHandle {
|
||||
fn as_ref(&self) -> &ScrollHandle {
|
||||
&self.base_handle
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollHandleOffsetable for VirtualListScrollHandle {
|
||||
fn offset(&self) -> Point<Pixels> {
|
||||
self.base_handle.offset()
|
||||
}
|
||||
|
||||
fn set_offset(&self, offset: Point<Pixels>) {
|
||||
self.base_handle.set_offset(offset);
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.base_handle.content_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for VirtualListScrollHandle {
|
||||
type Target = ScrollHandle;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.base_handle
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualListScrollHandle {
|
||||
pub fn new() -> Self {
|
||||
VirtualListScrollHandle {
|
||||
state: Rc::new(RefCell::new(VirtualListScrollHandleState {
|
||||
axis: Axis::Vertical,
|
||||
items_bounds: vec![],
|
||||
})),
|
||||
base_handle: ScrollHandle::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn base_handle(&self) -> &ScrollHandle {
|
||||
&self.base_handle
|
||||
}
|
||||
|
||||
fn set_items_bounds(&self, items_bounds: Vec<Bounds<Pixels>>) {
|
||||
self.state.borrow_mut().items_bounds = items_bounds;
|
||||
}
|
||||
|
||||
fn set_axis(&self, axis: Axis) {
|
||||
self.state.borrow_mut().axis = axis;
|
||||
}
|
||||
|
||||
/// Scroll to the item at the given index.
|
||||
pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
|
||||
let state = self.state.borrow();
|
||||
let Some(bounds) = state.items_bounds.get(ix) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let axis = state.axis;
|
||||
let mut scroll_offset = self.base_handle.offset();
|
||||
let container_bounds = self.base_handle().bounds();
|
||||
|
||||
match strategy {
|
||||
ScrollStrategy::Center => {
|
||||
if axis.is_vertical() {
|
||||
scroll_offset.y = container_bounds.top() + container_bounds.size.height.half()
|
||||
- bounds.top()
|
||||
- bounds.size.height.half()
|
||||
} else {
|
||||
scroll_offset.x = container_bounds.left() + container_bounds.size.width.half()
|
||||
- bounds.left()
|
||||
- bounds.size.width.half()
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Ref: https://github.com/zed-industries/zed/blob/0d145289e0867a8d5d63e5e1397a5ca69c9d49c3/crates/gpui/src/elements/div.rs#L3026
|
||||
if axis.is_vertical() {
|
||||
if bounds.top() + scroll_offset.y < container_bounds.top() {
|
||||
scroll_offset.y = container_bounds.top() - bounds.top();
|
||||
} else if bounds.bottom() + scroll_offset.y > container_bounds.bottom() {
|
||||
scroll_offset.y = container_bounds.bottom() - bounds.bottom();
|
||||
}
|
||||
} else {
|
||||
if bounds.left() + scroll_offset.x < container_bounds.left() {
|
||||
scroll_offset.x = container_bounds.left() - bounds.left();
|
||||
} else if bounds.right() + scroll_offset.x > container_bounds.right() {
|
||||
scroll_offset.x = container_bounds.right() - bounds.right();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.base_handle.set_offset(scroll_offset);
|
||||
}
|
||||
|
||||
/// Scrolls to the bottom of the list.
|
||||
pub fn scroll_to_bottom(&self) {
|
||||
let state = self.state.borrow();
|
||||
self.scroll_to_item(
|
||||
state.items_bounds.len().saturating_sub(1),
|
||||
ScrollStrategy::Top,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a [`VirtualList`] in vertical direction.
|
||||
///
|
||||
/// This is like `uniform_list` in GPUI, but support two axis.
|
||||
|
|
@ -70,7 +201,7 @@ where
|
|||
V: Render,
|
||||
{
|
||||
let id: ElementId = id.into();
|
||||
let scroll_handle = ScrollHandle::default();
|
||||
let scroll_handle = VirtualListScrollHandle::new();
|
||||
let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
|
||||
view.update(cx, |this, cx| {
|
||||
f(this, visible_range, window, cx)
|
||||
|
|
@ -100,7 +231,7 @@ pub struct VirtualList {
|
|||
id: ElementId,
|
||||
axis: Axis,
|
||||
base: Stateful<Div>,
|
||||
scroll_handle: ScrollHandle,
|
||||
scroll_handle: VirtualListScrollHandle,
|
||||
items_count: usize,
|
||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||
render_items: Box<
|
||||
|
|
@ -115,7 +246,7 @@ impl Styled for VirtualList {
|
|||
}
|
||||
|
||||
impl VirtualList {
|
||||
pub fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
|
||||
pub fn track_scroll(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
|
||||
self.base = self.base.track_scroll(&scroll_handle);
|
||||
self.scroll_handle = scroll_handle.clone();
|
||||
self
|
||||
|
|
@ -124,7 +255,7 @@ impl VirtualList {
|
|||
/// Specify for table.
|
||||
///
|
||||
/// Table is special, because the `scroll_handle` is based on Table head (That is not a virtual list).
|
||||
pub(crate) fn with_scroll_handle(mut self, scroll_handle: &ScrollHandle) -> Self {
|
||||
pub(crate) fn with_scroll_handle(mut self, scroll_handle: &VirtualListScrollHandle) -> Self {
|
||||
self.base = div().id(self.id.clone()).size_full();
|
||||
self.scroll_handle = scroll_handle.clone();
|
||||
self
|
||||
|
|
@ -311,6 +442,28 @@ impl Element for VirtualList {
|
|||
},
|
||||
};
|
||||
|
||||
// Update scroll_handle with the item bounds
|
||||
let items_bounds = item_origins
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &origin)| {
|
||||
let item_size = item_sizes[i];
|
||||
|
||||
Bounds {
|
||||
origin: match self.axis {
|
||||
Axis::Horizontal => point(bounds.left() + origin + padding.left, px(0.)),
|
||||
Axis::Vertical => point(px(0.), bounds.top() + origin + padding.top),
|
||||
},
|
||||
size: match self.axis {
|
||||
Axis::Horizontal => size(item_size, bounds.size.height),
|
||||
Axis::Vertical => size(bounds.size.width, item_size),
|
||||
},
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
self.scroll_handle.set_axis(self.axis);
|
||||
self.scroll_handle.set_items_bounds(items_bounds);
|
||||
|
||||
self.base.interactivity().prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
|
|
|
|||
Loading…
Reference in a new issue