From 6083cd57211fb05129f7b0cacb541cad70261d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20K=C3=A5re=20Alsaker?= Date: Wed, 3 Jan 2024 14:36:26 +0100 Subject: [PATCH] Add `virtual_list` with selections --- examples/layout/src/draggable_sidebar.rs | 8 +- examples/layout/src/holy_grail.rs | 12 +- examples/layout/src/left_sidebar.rs | 8 +- examples/layout/src/right_sidebar.rs | 8 +- examples/virtual_list/src/main.rs | 6 +- examples/widget-gallery/src/lists.rs | 70 ++---- examples/widget-gallery/src/main.rs | 6 +- src/context.rs | 1 + src/id.rs | 6 +- src/update.rs | 3 +- src/view.rs | 8 +- src/views/list.rs | 12 +- src/views/mod.rs | 3 + src/views/scroll.rs | 28 ++- src/views/virtual_list.rs | 276 +++++++++++++++++++++++ src/views/virtual_stack.rs | 73 ++++-- src/widgets/mod.rs | 3 + src/widgets/virtual_list.rs | 35 +++ src/window_handle.rs | 4 +- 19 files changed, 444 insertions(+), 126 deletions(-) create mode 100644 src/views/virtual_list.rs create mode 100644 src/widgets/virtual_list.rs diff --git a/examples/layout/src/draggable_sidebar.rs b/examples/layout/src/draggable_sidebar.rs index abb1c717..dfbe71b3 100644 --- a/examples/layout/src/draggable_sidebar.rs +++ b/examples/layout/src/draggable_sidebar.rs @@ -5,8 +5,8 @@ use floem::{ style::{CursorStyle, Position}, view::View, views::{ - container, h_stack, label, scroll, virtual_stack, Decorators, VirtualStackDirection, - VirtualStackItemSize, + container, h_stack, label, scroll, virtual_stack, Decorators, VirtualDirection, + VirtualItemSize, }, EventPropagation, }; @@ -21,8 +21,8 @@ pub fn draggable_sidebar_view() -> impl View { let side_bar = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 22.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 22.0)), move || long_list.get(), move |item| *item, move |item| { diff --git a/examples/layout/src/holy_grail.rs b/examples/layout/src/holy_grail.rs index 0ec5b370..e06e72a8 100644 --- a/examples/layout/src/holy_grail.rs +++ b/examples/layout/src/holy_grail.rs @@ -5,8 +5,8 @@ use floem::{ style::Position, view::View, views::{ - container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, - VirtualStackDirection, VirtualStackItemSize, + container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, VirtualDirection, + VirtualItemSize, }, }; @@ -22,8 +22,8 @@ pub fn holy_grail_view() -> impl View { let side_bar_right = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 22.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 22.0)), move || long_list.get(), move |item| *item, move |item| { @@ -49,8 +49,8 @@ pub fn holy_grail_view() -> impl View { let side_bar_left = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 22.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 22.0)), move || long_list.get(), move |item| *item, move |item| { diff --git a/examples/layout/src/left_sidebar.rs b/examples/layout/src/left_sidebar.rs index 120c1e4a..bf2014aa 100644 --- a/examples/layout/src/left_sidebar.rs +++ b/examples/layout/src/left_sidebar.rs @@ -5,8 +5,8 @@ use floem::{ style::Position, view::View, views::{ - container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, - VirtualStackDirection, VirtualStackItemSize, + container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, VirtualDirection, + VirtualItemSize, }, }; @@ -22,8 +22,8 @@ pub fn left_sidebar_view() -> impl View { let side_bar = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 22.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 22.0)), move || long_list.get(), move |item| *item, move |item| { diff --git a/examples/layout/src/right_sidebar.rs b/examples/layout/src/right_sidebar.rs index 7c7bf54a..862068ab 100644 --- a/examples/layout/src/right_sidebar.rs +++ b/examples/layout/src/right_sidebar.rs @@ -5,8 +5,8 @@ use floem::{ style::Position, view::View, views::{ - container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, - VirtualStackDirection, VirtualStackItemSize, + container, h_stack, label, scroll, v_stack, virtual_stack, Decorators, VirtualDirection, + VirtualItemSize, }, }; @@ -22,8 +22,8 @@ pub fn right_sidebar_view() -> impl View { let side_bar = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 22.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 22.0)), move || long_list.get(), move |item| *item, move |item| { diff --git a/examples/virtual_list/src/main.rs b/examples/virtual_list/src/main.rs index d768bf4d..7a502d74 100644 --- a/examples/virtual_list/src/main.rs +++ b/examples/virtual_list/src/main.rs @@ -4,7 +4,7 @@ use floem::{ view::View, views::virtual_stack, views::Decorators, - views::{container, label, scroll, VirtualStackDirection, VirtualStackItemSize}, + views::{container, label, scroll, VirtualDirection, VirtualItemSize}, }; fn app_view() -> impl View { @@ -14,8 +14,8 @@ fn app_view() -> impl View { container( scroll( virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 20.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 20.0)), move || long_list.get(), move |item| *item, move |item| label(move || item.to_string()).style(|s| s.height(20.0)), diff --git a/examples/widget-gallery/src/lists.rs b/examples/widget-gallery/src/lists.rs index 64e1ba16..fe06e92f 100644 --- a/examples/widget-gallery/src/lists.rs +++ b/examples/widget-gallery/src/lists.rs @@ -1,17 +1,14 @@ use floem::{ cosmic_text::Weight, - event::{Event, EventListener}, - keyboard::{Key, NamedKey}, peniko::Color, reactive::create_signal, - style::{CursorStyle, JustifyContent}, + style::JustifyContent, view::View, views::{ - container, label, scroll, stack, virtual_stack, Decorators, VirtualStackDirection, - VirtualStackItemSize, + container, label, scroll, stack, Decorators, VirtualDirection, VirtualItemSize, + VirtualVector, }, - widgets::{checkbox, list}, - EventPropagation, + widgets::{checkbox, list, virtual_list}, }; use crate::form::{form, form_item}; @@ -37,21 +34,15 @@ fn enhanced_list() -> impl View { let long_list: im::Vector = (0..100).collect(); let (long_list, set_long_list) = create_signal(long_list); - let (selected, set_selected) = create_signal(0); let list_width = 180.0; let item_height = 32.0; scroll( - virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 32.0)), - move || long_list.get(), - move |item| *item, - move |item| { - let index = long_list - .get_untracked() - .iter() - .position(|it| *it == item) - .unwrap(); + virtual_list( + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 32.0)), + move || long_list.get().enumerate(), + move |(_, item)| *item, + move |(index, item)| { let (is_checked, set_is_checked) = create_signal(true); container({ stack({ @@ -87,45 +78,12 @@ fn enhanced_list() -> impl View { }), ) }) - .style(move |s| s.height(item_height).width_full().items_center()) + .style(move |s| s.height_full().width_full().items_center()) }) - .on_click_stop(move |_| { - set_selected.update(|v: &mut usize| { - *v = long_list.get().iter().position(|it| *it == item).unwrap(); - }); - }) - .on_event(EventListener::KeyDown, move |e| { - if let Event::KeyDown(key_event) = e { - let sel = selected.get(); - match key_event.key.logical_key { - Key::Named(NamedKey::ArrowUp) => { - if sel > 0 { - set_selected.update(|v| *v -= 1); - } - EventPropagation::Stop - } - Key::Named(NamedKey::ArrowDown) => { - if sel < long_list.get().len() - 1 { - set_selected.update(|v| *v += 1); - } - EventPropagation::Stop - } - _ => EventPropagation::Continue, - } - } else { - EventPropagation::Continue - } - }) - .keyboard_navigatable() .style(move |s| { - s.flex_row() - .height(item_height) - .apply_if(index == selected.get(), |s| s.background(Color::GRAY)) - .apply_if(index != 0, |s| { - s.border_top(1.0).border_color(Color::LIGHT_GRAY) - }) - .focus_visible(|s| s.border(2.).border_color(Color::BLUE)) - .hover(|s| s.background(Color::LIGHT_GRAY).cursor(CursorStyle::Pointer)) + s.flex_row().height(item_height).apply_if(index != 0, |s| { + s.border_top(1.0).border_color(Color::LIGHT_GRAY) + }) }) }, ) diff --git a/examples/widget-gallery/src/main.rs b/examples/widget-gallery/src/main.rs index 6055e36d..8ca03833 100644 --- a/examples/widget-gallery/src/main.rs +++ b/examples/widget-gallery/src/main.rs @@ -19,7 +19,7 @@ use floem::{ view::View, views::{ container, container_box, h_stack, label, scroll, stack, tab, v_stack, virtual_stack, - Decorators, VirtualStackDirection, VirtualStackItemSize, + Decorators, VirtualDirection, VirtualItemSize, }, widgets::button, EventPropagation, @@ -45,8 +45,8 @@ fn app_view() -> impl View { let list = scroll({ virtual_stack( - VirtualStackDirection::Vertical, - VirtualStackItemSize::Fixed(Box::new(|| 36.0)), + VirtualDirection::Vertical, + VirtualItemSize::Fixed(Box::new(|| 36.0)), move || tabs.get(), move |item| *item, move |item| { diff --git a/src/context.rs b/src/context.rs index ed4ca0c6..a2dabf98 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1238,6 +1238,7 @@ impl<'a> ComputeLayoutCx<'a> { .get_mut(&id) .and_then(|s| s.move_listener.as_mut()) } + /// Internal method used by Floem. This method derives its calculations based on the [Taffy Node](taffy::prelude::Node) returned by the `View::layout` method. /// /// It's responsible for: diff --git a/src/id.rs b/src/id.rs index 3af485d1..60518e0c 100644 --- a/src/id.rs +++ b/src/id.rs @@ -10,7 +10,7 @@ use std::{any::Any, cell::RefCell, collections::HashMap, sync::atomic::AtomicU64}; -use kurbo::Point; +use kurbo::{Point, Rect}; use crate::{ animate::Animation, @@ -217,8 +217,8 @@ impl Id { self.add_update_message(UpdateMessage::PopoutMenu { id: *self, menu }); } - pub fn scroll_to(&self) { - self.add_update_message(UpdateMessage::ScrollTo { id: *self }); + pub fn scroll_to(&self, rect: Option) { + self.add_update_message(UpdateMessage::ScrollTo { id: *self, rect }); } pub fn inspect(&self) { diff --git a/src/update.rs b/src/update.rs index 0dfb4928..a891b2fe 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,6 +1,6 @@ use std::{any::Any, cell::RefCell, collections::HashMap}; -use kurbo::{Point, Size, Vec2}; +use kurbo::{Point, Rect, Size, Vec2}; use winit::window::ResizeDirection; use crate::{ @@ -124,6 +124,7 @@ pub(crate) enum UpdateMessage { Inspect, ScrollTo { id: Id, + rect: Option, }, FocusWindow, SetImeAllowed { diff --git a/src/view.rs b/src/view.rs index 91e4c31a..c154c1b8 100644 --- a/src/view.rs +++ b/src/view.rs @@ -235,13 +235,13 @@ pub trait View { /// Scrolls the view and all direct and indirect children to bring the `target` view to be /// visible. Returns true if this view contains or is the target. - fn scroll_to(&mut self, cx: &mut AppState, target: Id) -> bool { + fn scroll_to(&mut self, cx: &mut AppState, target: Id, rect: Option) -> bool { if self.id() == target { return true; } let mut found = false; self.for_each_child_mut(&mut |child| { - found |= child.scroll_to(cx, target); + found |= child.scroll_to(cx, target, rect); found }); found @@ -700,7 +700,7 @@ impl View for Box { (**self).paint(cx) } - fn scroll_to(&mut self, cx: &mut AppState, target: Id) -> bool { - (**self).scroll_to(cx, target) + fn scroll_to(&mut self, cx: &mut AppState, target: Id, rect: Option) -> bool { + (**self).scroll_to(cx, target, rect) } } diff --git a/src/views/list.rs b/src/views/list.rs index 01959bcc..7d7ffd81 100644 --- a/src/views/list.rs +++ b/src/views/list.rs @@ -16,11 +16,11 @@ enum ListUpdate { ScrollToSelected, } -struct Item { - data: ViewData, - index: usize, - selection: RwSignal>, - child: Box, +pub(crate) struct Item { + pub(crate) data: ViewData, + pub(crate) index: usize, + pub(crate) selection: RwSignal>, + pub(crate) child: Box, } pub struct List { @@ -171,7 +171,7 @@ impl View for List { } ListUpdate::ScrollToSelected => { if let Some(index) = self.selection.get_untracked() { - self.child.children[index].id().scroll_to(); + self.child.children[index].id().scroll_to(None); } } } diff --git a/src/views/mod.rs b/src/views/mod.rs index c42d0235..84b4b2d9 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -33,6 +33,9 @@ pub use decorator::*; mod list; pub use list::*; +mod virtual_list; +pub use virtual_list::*; + mod virtual_stack; pub use virtual_stack::*; diff --git a/src/views/scroll.rs b/src/views/scroll.rs index 706f06e5..e5db05a8 100644 --- a/src/views/scroll.rs +++ b/src/views/scroll.rs @@ -580,9 +580,25 @@ impl Scroll { } } - fn do_scroll_to_view(&mut self, app_state: &mut AppState, target: Id) { + fn do_scroll_to_view( + &mut self, + app_state: &mut AppState, + target: Id, + target_rect: Option, + ) { if app_state.get_layout(target).is_some() && !app_state.is_hidden_recursive(target) { - let rect = app_state.get_layout_rect(target); + let mut rect = app_state.get_layout_rect(target); + + if let Some(target_rect) = target_rect { + rect = rect + target_rect.origin().to_vec2(); + + let new_size = target_rect + .size() + .to_rect() + .intersect(rect.size().to_rect()) + .size(); + rect = rect.with_size(new_size); + } // `get_layout_rect` is window-relative so we have to // convert it to child view relative. @@ -651,7 +667,7 @@ impl View for Scroll { self.scroll_to(cx.app_state, point); } ScrollState::ScrollToView(id) => { - self.do_scroll_to_view(cx.app_state, id); + self.do_scroll_to_view(cx.app_state, id, None); } ScrollState::HiddenBar(hide) => { self.hide = hide; @@ -667,10 +683,10 @@ impl View for Scroll { } } - fn scroll_to(&mut self, cx: &mut AppState, target: Id) -> bool { - let found = self.child.scroll_to(cx, target); + fn scroll_to(&mut self, cx: &mut AppState, target: Id, rect: Option) -> bool { + let found = self.child.scroll_to(cx, target, rect); if found { - self.do_scroll_to_view(cx, target); + self.do_scroll_to_view(cx, target, rect); } found } diff --git a/src/views/virtual_list.rs b/src/views/virtual_list.rs new file mode 100644 index 00000000..eac29029 --- /dev/null +++ b/src/views/virtual_list.rs @@ -0,0 +1,276 @@ +use super::{ + virtual_stack, Decorators, Item, VirtualDirection, VirtualItemSize, VirtualStack, VirtualVector, +}; +use crate::context::ComputeLayoutCx; +use crate::reactive::create_effect; +use crate::EventPropagation; +use crate::{ + event::{Event, EventListener}, + id::Id, + keyboard::{Key, NamedKey}, + view::{View, ViewData}, +}; +use floem_reactive::{create_rw_signal, RwSignal}; +use kurbo::{Rect, Size}; +use std::hash::Hash; +use std::rc::Rc; + +enum ListUpdate { + SelectionChanged, + ScrollToSelected, +} + +pub struct VirtualList { + data: ViewData, + direction: VirtualDirection, + child_size: Size, + selection: RwSignal>, + offsets: RwSignal>, + child: VirtualStack, +} + +impl VirtualList { + pub fn selection(&self) -> RwSignal> { + self.selection + } + + pub fn on_select(self, on_select: impl Fn(Option) + 'static) -> Self { + create_effect(move |_| { + let selection = self.selection.get(); + on_select(selection); + }); + self + } +} + +pub fn virtual_list( + direction: VirtualDirection, + item_size: VirtualItemSize, + each_fn: IF, + key_fn: KF, + view_fn: VF, +) -> VirtualList +where + T: 'static, + IF: Fn() -> I + 'static, + I: VirtualVector, + KF: Fn(&T) -> K + 'static, + K: Eq + Hash + 'static, + VF: Fn(T) -> V + 'static, + V: View + 'static, +{ + let id = Id::next(); + let selection = create_rw_signal(None); + let length = create_rw_signal(0); + let offsets = create_rw_signal(Vec::new()); + create_effect(move |_| { + selection.track(); + id.update_state(ListUpdate::SelectionChanged, false); + }); + + let shared = Rc::new((each_fn, item_size)); + let shared_ = shared.clone(); + + create_effect(move |_| { + let mut items = (shared_.0)(); + + let mut new_offsets = Vec::with_capacity(items.total_len()); + let mut current = 0.0; + + match &shared_.1 { + VirtualItemSize::Fixed(item_size) => { + let item_size = item_size(); + for _ in 0..items.total_len() { + new_offsets.push(current); + current += item_size; + } + } + VirtualItemSize::Fn(size_fn) => { + for item in items.slice(0..(items.total_len())) { + new_offsets.push(current); + current += size_fn(&item); + } + } + }; + + new_offsets.push(current); + + offsets.set(new_offsets); + }); + + let shared_ = shared.clone(); + let item_size = match shared.1 { + VirtualItemSize::Fixed(..) => VirtualItemSize::Fixed(Box::new(move || match shared_.1 { + VirtualItemSize::Fixed(ref f) => f(), + VirtualItemSize::Fn(..) => panic!(), + })), + VirtualItemSize::Fn(..) => VirtualItemSize::Fn(Box::new(move |(_, e)| match shared_.1 { + VirtualItemSize::Fixed(..) => panic!(), + VirtualItemSize::Fn(ref f) => f(e), + })), + }; + let stack = virtual_stack( + direction, + item_size, + move || { + let vector = (shared.0)().enumerate(); + length.set(vector.total_len()); + vector + }, + move |(_, e)| key_fn(e), + move |(index, e)| { + Item { + data: ViewData::new(Id::next()), + selection, + index, + child: Box::new(view_fn(e)), + } + .on_click_stop(move |_| { + if selection.get_untracked() != Some(index) { + selection.set(Some(index)) + } + }) + .style(|s| s.width_full()) + }, + ) + .style(move |s| match direction { + VirtualDirection::Horizontal => s.flex_row(), + VirtualDirection::Vertical => s.flex_col(), + }); + VirtualList { + data: ViewData::new(id), + selection, + direction, + offsets, + child_size: Size::ZERO, + child: stack, + } + .keyboard_navigatable() + .on_event(EventListener::KeyDown, move |e| { + if let Event::KeyDown(key_event) = e { + match key_event.key.logical_key { + Key::Named(NamedKey::Home) => { + if length.get_untracked() > 0 { + selection.set(Some(0)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + EventPropagation::Stop + } + Key::Named(NamedKey::End) => { + let length = length.get_untracked(); + if length > 0 { + selection.set(Some(length - 1)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + EventPropagation::Stop + } + Key::Named(NamedKey::ArrowUp) => { + let current = selection.get_untracked(); + match current { + Some(i) => { + if i > 0 { + selection.set(Some(i - 1)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + } + None => { + let length = length.get_untracked(); + if length > 0 { + selection.set(Some(length - 1)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + } + } + EventPropagation::Stop + } + Key::Named(NamedKey::ArrowDown) => { + let current = selection.get_untracked(); + match current { + Some(i) => { + if i < length.get_untracked() - 1 { + selection.set(Some(i + 1)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + } + None => { + if length.get_untracked() > 0 { + selection.set(Some(0)); + id.update_state(ListUpdate::ScrollToSelected, false); + } + } + } + EventPropagation::Stop + } + _ => EventPropagation::Continue, + } + } else { + EventPropagation::Continue + } + }) +} + +impl View for VirtualList { + fn view_data(&self) -> &ViewData { + &self.data + } + + fn view_data_mut(&mut self) -> &mut ViewData { + &mut self.data + } + + fn for_each_child<'a>(&'a self, for_each: &mut dyn FnMut(&'a dyn View) -> bool) { + for_each(&self.child); + } + + fn for_each_child_mut<'a>(&'a mut self, for_each: &mut dyn FnMut(&'a mut dyn View) -> bool) { + for_each(&mut self.child); + } + + fn for_each_child_rev_mut<'a>( + &'a mut self, + for_each: &mut dyn FnMut(&'a mut dyn View) -> bool, + ) { + for_each(&mut self.child); + } + + fn debug_name(&self) -> std::borrow::Cow<'static, str> { + "VirtualList".into() + } + + fn update(&mut self, cx: &mut crate::context::UpdateCx, state: Box) { + if let Ok(change) = state.downcast::() { + match *change { + ListUpdate::SelectionChanged => { + cx.app_state_mut().request_style_recursive(self.id()) + } + ListUpdate::ScrollToSelected => { + if let Some(index) = self.selection.get_untracked() { + self.offsets.with_untracked(|offsets| { + if let Some([before, after]) = offsets.get(index..index + 2) { + let rect = match self.direction { + VirtualDirection::Vertical => { + Rect::new(0.0, *before, self.child_size.width, *after) + } + VirtualDirection::Horizontal => { + Rect::new(*before, 0.0, *after, self.child_size.height) + } + }; + self.child.id().scroll_to(Some(rect)); + } + }); + } + } + } + } + } + + fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option { + self.child_size = cx + .app_state + .get_layout(self.child.id()) + .map(|layout| Size::new(layout.size.width as f64, layout.size.height as f64)) + .unwrap(); + + cx.compute_view_layout(&mut self.child) + } +} diff --git a/src/views/virtual_stack.rs b/src/views/virtual_stack.rs index 6672f631..c1412625 100644 --- a/src/views/virtual_stack.rs +++ b/src/views/virtual_stack.rs @@ -14,19 +14,17 @@ use crate::{ use super::{apply_diff, diff, Diff, DiffOpAdd, FxIndexSet, HashRun}; #[derive(Clone, Copy)] -pub enum VirtualStackDirection { +pub enum VirtualDirection { Vertical, Horizontal, } -pub enum VirtualStackItemSize { +pub enum VirtualItemSize { Fn(Box f64>), Fixed(Box f64>), } -pub trait VirtualStackVector { - type ItemIterator: Iterator; - +pub trait VirtualVector { fn total_len(&self) -> usize; fn total_size(&self) -> Option { @@ -37,7 +35,17 @@ pub trait VirtualStackVector { self.total_len() == 0 } - fn slice(&mut self, range: Range) -> Self::ItemIterator; + fn slice(&mut self, range: Range) -> impl Iterator; + + fn enumerate(self) -> Enumerate + where + Self: Sized, + { + Enumerate { + inner: self, + phantom: PhantomData, + } + } } pub struct VirtualStack @@ -45,7 +53,7 @@ where T: 'static, { data: ViewData, - direction: VirtualStackDirection, + direction: VirtualDirection, children: Vec>, viewport: Rect, set_viewport: WriteSignal, @@ -64,8 +72,8 @@ struct VirtualStackState { } pub fn virtual_stack( - direction: VirtualStackDirection, - item_size: VirtualStackItemSize, + direction: VirtualDirection, + item_size: VirtualItemSize, each_fn: IF, key_fn: KF, view_fn: VF, @@ -73,7 +81,7 @@ pub fn virtual_stack( where T: 'static, IF: Fn() -> I + 'static, - I: VirtualStackVector, + I: VirtualVector, KF: Fn(&T) -> K + 'static, K: Eq + Hash + 'static, VF: Fn(T) -> V + 'static, @@ -87,19 +95,19 @@ where let mut items_vector = each_fn(); let viewport = viewport.get(); let min = match direction { - VirtualStackDirection::Vertical => viewport.y0, - VirtualStackDirection::Horizontal => viewport.x0, + VirtualDirection::Vertical => viewport.y0, + VirtualDirection::Horizontal => viewport.x0, }; let max = match direction { - VirtualStackDirection::Vertical => viewport.height() + viewport.y0, - VirtualStackDirection::Horizontal => viewport.width() + viewport.x0, + VirtualDirection::Vertical => viewport.height() + viewport.y0, + VirtualDirection::Horizontal => viewport.width() + viewport.x0, }; let mut items = Vec::new(); let mut before_size = 0.0; let mut after_size = 0.0; match &item_size { - VirtualStackItemSize::Fixed(item_size) => { + VirtualItemSize::Fixed(item_size) => { let item_size = item_size(); let total_len = items_vector.total_len(); let start = if item_size > 0.0 { @@ -121,7 +129,7 @@ where after_size = item_size * (total_len.saturating_sub(start).saturating_sub(items.len())) as f64; } - VirtualStackItemSize::Fn(size_fn) => { + VirtualItemSize::Fn(size_fn) => { let mut main_axis = 0.0; let total_len = items_vector.total_len(); let total_size = items_vector.total_size(); @@ -274,21 +282,21 @@ impl View for VirtualStack { .filter_map(|child| Some(cx.layout_view(&mut child.as_mut()?.0))) .collect::>(); let before_size = match self.direction { - VirtualStackDirection::Vertical => taffy::prelude::Size { + VirtualDirection::Vertical => taffy::prelude::Size { width: Dimension::Percent(1.0), height: Dimension::Points(self.before_size as f32), }, - VirtualStackDirection::Horizontal => taffy::prelude::Size { + VirtualDirection::Horizontal => taffy::prelude::Size { width: Dimension::Points(self.before_size as f32), height: Dimension::Percent(1.0), }, }; let after_size = match self.direction { - VirtualStackDirection::Vertical => taffy::prelude::Size { + VirtualDirection::Vertical => taffy::prelude::Size { width: Dimension::Percent(1.0), height: Dimension::Points(self.after_size as f32), }, - VirtualStackDirection::Horizontal => taffy::prelude::Size { + VirtualDirection::Horizontal => taffy::prelude::Size { width: Dimension::Points(self.after_size as f32), height: Dimension::Percent(1.0), }, @@ -347,14 +355,31 @@ impl View for VirtualStack { } } -impl VirtualStackVector for im::Vector { - type ItemIterator = im::vector::ConsumingIter; - +impl VirtualVector for im::Vector { fn total_len(&self) -> usize { self.len() } - fn slice(&mut self, range: Range) -> Self::ItemIterator { + fn slice(&mut self, range: Range) -> impl Iterator { self.slice(range).into_iter() } } + +pub struct Enumerate, T> { + inner: V, + phantom: PhantomData, +} + +impl, T> VirtualVector<(usize, T)> for Enumerate { + fn total_len(&self) -> usize { + self.inner.total_len() + } + + fn slice(&mut self, range: Range) -> impl Iterator { + let start = range.start; + self.inner + .slice(range) + .enumerate() + .map(move |(i, e)| (i + start, e)) + } +} diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 843395e0..4b15b7b1 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -18,6 +18,9 @@ pub use checkbox::*; mod list; pub use list::*; +mod virtual_list; +pub use virtual_list::*; + mod toggle_button; pub use toggle_button::*; diff --git a/src/widgets/virtual_list.rs b/src/widgets/virtual_list.rs new file mode 100644 index 00000000..420dac16 --- /dev/null +++ b/src/widgets/virtual_list.rs @@ -0,0 +1,35 @@ +use super::{ListClass, ListItemClass}; +use crate::{ + view::View, + views::{ + self, container, Decorators, VirtualDirection, VirtualItemSize, VirtualList, VirtualVector, + }, +}; +use std::hash::Hash; + +pub fn virtual_list( + direction: VirtualDirection, + item_size: VirtualItemSize, + each_fn: IF, + key_fn: KF, + view_fn: VF, +) -> VirtualList +where + T: 'static, + IF: Fn() -> I + 'static, + I: VirtualVector, + KF: Fn(&T) -> K + 'static, + K: Eq + Hash + 'static, + VF: Fn(T) -> V + 'static, + V: View + 'static, +{ + views::virtual_list(direction, item_size, each_fn, key_fn, move |e| { + container(view_fn(e)) + .class(ListItemClass) + .style(move |s| match direction { + VirtualDirection::Horizontal => s.flex_row(), + VirtualDirection::Vertical => s.flex_col(), + }) + }) + .class(ListClass) +} diff --git a/src/window_handle.rs b/src/window_handle.rs index e1ae665b..c337a6d6 100644 --- a/src/window_handle.rs +++ b/src/window_handle.rs @@ -770,8 +770,8 @@ impl WindowHandle { cx.app_state.request_style_recursive(id); } } - UpdateMessage::ScrollTo { id } => { - self.view.scroll_to(cx.app_state, id); + UpdateMessage::ScrollTo { id, rect } => { + self.view.scroll_to(cx.app_state, id, rect); } UpdateMessage::Disabled { id, is_disabled } => { if is_disabled {