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",
|
"bitflags 2.9.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -756,7 +756,7 @@ dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.10.5",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1432,7 +1432,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"indexmap 2.9.0",
|
"indexmap 2.9.0",
|
||||||
"rustc-hash 2.1.0",
|
"rustc-hash 2.1.0",
|
||||||
|
|
@ -1990,7 +1990,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -3142,7 +3142,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
|
|
@ -3327,7 +3327,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -3597,7 +3597,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -3615,7 +3615,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
|
|
@ -4441,7 +4441,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
|
|
@ -6390,7 +6390,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
|
|
@ -6545,7 +6545,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -7121,7 +7121,7 @@ checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -7704,7 +7704,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -8931,7 +8931,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
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 = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs 2.1.2",
|
"async-fs 2.1.2",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::{ops::Range, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, px, size, App, AppContext, Context, Div, Entity, FocusHandle, Focusable,
|
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,
|
Window,
|
||||||
};
|
};
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
|
|
@ -10,12 +10,12 @@ use gpui_component::{
|
||||||
divider::Divider,
|
divider::Divider,
|
||||||
h_flex,
|
h_flex,
|
||||||
scroll::{Scrollbar, ScrollbarAxis, ScrollbarState},
|
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 {
|
pub struct VirtualListStory {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: VirtualListScrollHandle,
|
||||||
scroll_state: ScrollbarState,
|
scroll_state: ScrollbarState,
|
||||||
items: Vec<String>,
|
items: Vec<String>,
|
||||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||||
|
|
@ -34,7 +34,7 @@ impl VirtualListStory {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: ScrollHandle::new(),
|
scroll_handle: VirtualListScrollHandle::new(),
|
||||||
scroll_state: ScrollbarState::default(),
|
scroll_state: ScrollbarState::default(),
|
||||||
items,
|
items,
|
||||||
item_sizes: Rc::new(item_sizes),
|
item_sizes: Rc::new(item_sizes),
|
||||||
|
|
@ -79,6 +79,9 @@ impl VirtualListStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_buttons(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
|
@ -152,7 +155,49 @@ impl VirtualListStory {
|
||||||
})),
|
})),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(format!("visible_range: {:?}", self.visible_range))
|
.child(format!("visible_range: {:?}", self.visible_range)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Button::new("scroll-to0")
|
||||||
|
.small()
|
||||||
|
.outline()
|
||||||
|
.label("Scroll to Top")
|
||||||
|
.on_click(cx.listener(|this, _, _, _| {
|
||||||
|
this.scroll_handle.scroll_to_item(0, ScrollStrategy::Top);
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("scroll-to1")
|
||||||
|
.small()
|
||||||
|
.outline()
|
||||||
|
.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();
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ pub use root::{ContextModal, Root};
|
||||||
pub use styled::*;
|
pub use styled::*;
|
||||||
pub use time::*;
|
pub use time::*;
|
||||||
pub use title_bar::*;
|
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 window_border::{window_border, window_paddings, WindowBorder};
|
||||||
|
|
||||||
pub use icon::*;
|
pub use icon::*;
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,13 @@ use crate::{
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
scroll::{self, ScrollableMask, Scrollbar, ScrollbarState},
|
scroll::{self, ScrollableMask, Scrollbar, ScrollbarState},
|
||||||
v_flex, ActiveTheme, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt,
|
v_flex, ActiveTheme, Icon, IconName, Sizable, Size, StyleSized as _, StyledExt,
|
||||||
|
VirtualListScrollHandle,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, App, AppContext, Axis, Bounds,
|
actions, canvas, div, prelude::FluentBuilder, px, uniform_list, App, AppContext, Axis, Bounds,
|
||||||
Context, Div, DragMoveEvent, Edges, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
Context, Div, DragMoveEvent, Edges, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
||||||
IntoElement, KeyBinding, ListSizingBehavior, MouseButton, MouseDownEvent, ParentElement,
|
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,
|
StatefulInteractiveElement as _, Styled, Task, UniformListScrollHandle, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ pub struct Table<D: TableDelegate> {
|
||||||
|
|
||||||
pub vertical_scroll_handle: UniformListScrollHandle,
|
pub vertical_scroll_handle: UniformListScrollHandle,
|
||||||
pub vertical_scroll_state: ScrollbarState,
|
pub vertical_scroll_state: ScrollbarState,
|
||||||
pub horizontal_scroll_handle: ScrollHandle,
|
pub horizontal_scroll_handle: VirtualListScrollHandle,
|
||||||
pub horizontal_scroll_state: ScrollbarState,
|
pub horizontal_scroll_state: ScrollbarState,
|
||||||
|
|
||||||
scrollbar_visible: Edges<bool>,
|
scrollbar_visible: Edges<bool>,
|
||||||
|
|
@ -137,7 +138,7 @@ where
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
delegate,
|
delegate,
|
||||||
col_groups: Vec::new(),
|
col_groups: Vec::new(),
|
||||||
horizontal_scroll_handle: ScrollHandle::new(),
|
horizontal_scroll_handle: VirtualListScrollHandle::new(),
|
||||||
vertical_scroll_handle: UniformListScrollHandle::new(),
|
vertical_scroll_handle: UniformListScrollHandle::new(),
|
||||||
vertical_scroll_state: ScrollbarState::default(),
|
vertical_scroll_state: ScrollbarState::default(),
|
||||||
horizontal_scroll_state: ScrollbarState::default(),
|
horizontal_scroll_state: ScrollbarState::default(),
|
||||||
|
|
@ -269,6 +270,17 @@ where
|
||||||
cx.notify();
|
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.
|
/// Scroll to the row at the given index.
|
||||||
pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
|
pub fn scroll_to_row(&mut self, row_ix: usize, cx: &mut Context<Self>) {
|
||||||
self.vertical_scroll_handle
|
self.vertical_scroll_handle
|
||||||
|
|
@ -277,11 +289,13 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll to the column at the given index.
|
// 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, cx: &mut Context<Self>) {
|
||||||
// pub fn scroll_to_col(&mut self, col_ix: usize, window: &mut Window, 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);
|
|
||||||
// cx.notify();
|
self.horizontal_scroll_handle
|
||||||
// }
|
.scroll_to_item(col_ix, ScrollStrategy::Top);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the selected row index.
|
/// Returns the selected row index.
|
||||||
pub fn selected_row(&self) -> Option<usize> {
|
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>) {
|
pub fn set_selected_col(&mut self, col_ix: usize, cx: &mut Context<Self>) {
|
||||||
self.selection_state = SelectionState::Column;
|
self.selection_state = SelectionState::Column;
|
||||||
self.selected_col = Some(col_ix);
|
self.selected_col = Some(col_ix);
|
||||||
if let Some(_col_ix) = self.selected_col {
|
if let Some(col_ix) = self.selected_col {
|
||||||
// TODO: Fix scroll to selected col, this was not working after fixed col.
|
self.scroll_to_col(col_ix, cx);
|
||||||
// if self.col_groups[col_ix].fixed.is_none() {
|
|
||||||
// self.horizontal_scroll_handle.scroll_to_item(col_ix);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
cx.emit(TableEvent::SelectColumn(col_ix));
|
cx.emit(TableEvent::SelectColumn(col_ix));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,147 @@
|
||||||
//! Unlike the `uniform_list`, the each item can have different size.
|
//! 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.
|
//! 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::{
|
use gpui::{
|
||||||
div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
|
div, point, px, size, Along, AnyElement, App, AvailableSpace, Axis, Bounds, ContentMask,
|
||||||
Context, Div, Element, ElementId, Entity, GlobalElementId, Hitbox, InteractiveElement,
|
Context, Div, Element, ElementId, Entity, GlobalElementId, Half, Hitbox, InteractiveElement,
|
||||||
IntoElement, IsZero as _, Pixels, Render, ScrollHandle, Size, Stateful,
|
IntoElement, IsZero as _, Pixels, Point, Render, ScrollHandle, ScrollStrategy, Size, Stateful,
|
||||||
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
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.
|
/// Create a [`VirtualList`] in vertical direction.
|
||||||
///
|
///
|
||||||
/// This is like `uniform_list` in GPUI, but support two axis.
|
/// This is like `uniform_list` in GPUI, but support two axis.
|
||||||
|
|
@ -70,7 +201,7 @@ where
|
||||||
V: Render,
|
V: Render,
|
||||||
{
|
{
|
||||||
let id: ElementId = id.into();
|
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| {
|
let render_range = move |visible_range, window: &mut Window, cx: &mut App| {
|
||||||
view.update(cx, |this, cx| {
|
view.update(cx, |this, cx| {
|
||||||
f(this, visible_range, window, cx)
|
f(this, visible_range, window, cx)
|
||||||
|
|
@ -100,7 +231,7 @@ pub struct VirtualList {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
base: Stateful<Div>,
|
base: Stateful<Div>,
|
||||||
scroll_handle: ScrollHandle,
|
scroll_handle: VirtualListScrollHandle,
|
||||||
items_count: usize,
|
items_count: usize,
|
||||||
item_sizes: Rc<Vec<Size<Pixels>>>,
|
item_sizes: Rc<Vec<Size<Pixels>>>,
|
||||||
render_items: Box<
|
render_items: Box<
|
||||||
|
|
@ -115,7 +246,7 @@ impl Styled for VirtualList {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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.base = self.base.track_scroll(&scroll_handle);
|
||||||
self.scroll_handle = scroll_handle.clone();
|
self.scroll_handle = scroll_handle.clone();
|
||||||
self
|
self
|
||||||
|
|
@ -124,7 +255,7 @@ impl VirtualList {
|
||||||
/// Specify for table.
|
/// Specify for table.
|
||||||
///
|
///
|
||||||
/// Table is special, because the `scroll_handle` is based on Table head (That is not a virtual list).
|
/// 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.base = div().id(self.id.clone()).size_full();
|
||||||
self.scroll_handle = scroll_handle.clone();
|
self.scroll_handle = scroll_handle.clone();
|
||||||
self
|
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(
|
self.base.interactivity().prepaint(
|
||||||
global_id,
|
global_id,
|
||||||
inspector_id,
|
inspector_id,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue