From 742180a3a8963635e396410e7282ad710ddae016 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 7 Nov 2023 10:45:38 -0800 Subject: [PATCH] Implement list scroll tracking Co-authored-by: Mikayla --- crates/gpui2/src/elements/list.rs | 53 ++++++++++++++++++++++++++++--- crates/gpui2/src/interactive.rs | 6 ++++ crates/picker2/src/picker2.rs | 45 ++++++++++++-------------- 3 files changed, 75 insertions(+), 29 deletions(-) diff --git a/crates/gpui2/src/elements/list.rs b/crates/gpui2/src/elements/list.rs index 0719f2a92e..acd1a524f3 100644 --- a/crates/gpui2/src/elements/list.rs +++ b/crates/gpui2/src/elements/list.rs @@ -1,11 +1,12 @@ use crate::{ point, px, AnyElement, AvailableSpace, BorrowWindow, Bounds, Component, Element, ElementId, - ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Size, StatefulInteractive, - StatefulInteractivity, StatelessInteractive, StatelessInteractivity, StyleRefinement, Styled, - ViewContext, + ElementInteractivity, InteractiveElementState, LayoutId, Pixels, Point, Size, + StatefulInteractive, StatefulInteractivity, StatelessInteractive, StatelessInteractivity, + StyleRefinement, Styled, ViewContext, }; +use parking_lot::Mutex; use smallvec::SmallVec; -use std::{cmp, ops::Range}; +use std::{cmp, ops::Range, sync::Arc}; use taffy::style::Overflow; pub fn list( @@ -30,6 +31,7 @@ where .collect() }), interactivity: id.into(), + scroll_handle: None, } } @@ -45,6 +47,37 @@ pub struct List { ) -> SmallVec<[AnyElement; 64]>, >, interactivity: StatefulInteractivity, + scroll_handle: Option, +} + +#[derive(Clone)] +pub struct ListScrollHandle(Arc>>); + +#[derive(Clone, Debug)] +struct ListScrollHandleState { + item_height: Pixels, + list_height: Pixels, + scroll_offset: Arc>>, +} + +impl ListScrollHandle { + pub fn new() -> Self { + Self(Arc::new(Mutex::new(None))) + } + + pub fn scroll_to_item(&self, ix: usize) { + if let Some(state) = &*self.0.lock() { + let mut scroll_offset = state.scroll_offset.lock(); + let item_top = state.item_height * ix; + let item_bottom = item_top + state.item_height; + let scroll_top = -scroll_offset.y; + if item_top < scroll_top { + scroll_offset.y = -item_top; + } else if item_bottom > scroll_top + state.list_height { + scroll_offset.y = -(item_bottom - state.list_height); + } + } + } } #[derive(Default)] @@ -107,6 +140,13 @@ impl Element for List { let content_size; if self.item_count > 0 { let item_height = self.measure_item_height(view_state, padded_bounds, cx); + if let Some(scroll_handle) = self.scroll_handle.clone() { + scroll_handle.0.lock().replace(ListScrollHandleState { + item_height, + list_height: padded_bounds.size.height, + scroll_offset: element_state.interactive.track_scroll_offset(), + }); + } let visible_item_count = (padded_bounds.size.height / item_height).ceil() as usize + 1; let scroll_offset = element_state @@ -187,6 +227,11 @@ impl List { ); cx.layout_bounds(layout_id).size.height } + + pub fn track_scroll(mut self, handle: ListScrollHandle) -> Self { + self.scroll_handle = Some(handle); + self + } } impl StatelessInteractive for List { diff --git a/crates/gpui2/src/interactive.rs b/crates/gpui2/src/interactive.rs index 2fa3ceeac2..e38a158eef 100644 --- a/crates/gpui2/src/interactive.rs +++ b/crates/gpui2/src/interactive.rs @@ -900,6 +900,12 @@ impl InteractiveElementState { .as_ref() .map(|offset| offset.lock().clone()) } + + pub fn track_scroll_offset(&mut self) -> Arc>> { + self.scroll_offset + .get_or_insert_with(|| Arc::new(Mutex::new(Default::default()))) + .clone() + } } impl Default for StatelessInteractivity { diff --git a/crates/picker2/src/picker2.rs b/crates/picker2/src/picker2.rs index 982d8360fb..dafe45e4e9 100644 --- a/crates/picker2/src/picker2.rs +++ b/crates/picker2/src/picker2.rs @@ -1,28 +1,8 @@ -// use editor::Editor; -// use gpui::{ -// elements::*, -// geometry::vector::{vec2f, Vector2F}, -// keymap_matcher::KeymapContext, -// platform::{CursorStyle, MouseButton}, -// AnyElement, AnyViewHandle, AppContext, Axis, Entity, MouseState, Task, View, ViewContext, -// ViewHandle, -// }; -// use menu::{Cancel, Confirm, SecondaryConfirm, SelectFirst, SelectLast, SelectNext, SelectPrev}; -// use parking_lot::Mutex; -// use std::{cmp, sync::Arc}; -// use util::ResultExt; -// use workspace::Modal; - -// #[derive(Clone, Copy)] -// pub enum PickerEvent { -// Dismiss, -// } - use std::cmp; use gpui::{ - div, list, Component, ElementId, FocusHandle, Focusable, ParentElement, StatelessInteractive, - Styled, ViewContext, + div, list, Component, ElementId, FocusHandle, Focusable, ListScrollHandle, ParentElement, + StatelessInteractive, Styled, ViewContext, }; // pub struct Picker { @@ -99,6 +79,7 @@ impl Picker { impl Picker { pub fn render(self, view: &mut V, _cx: &mut ViewContext) -> impl Component { let id = self.id.clone(); + let scroll_handle = ListScrollHandle::new(); div() .size_full() .id(self.id.clone()) @@ -112,36 +93,49 @@ impl Picker { }) .on_action({ let id = id.clone(); + let scroll_handle = scroll_handle.clone(); move |view: &mut V, _: &menu::SelectNext, cx| { let count = view.match_count(id.clone()); if count > 0 { let index = view.selected_index(id.clone()); - view.set_selected_index(cmp::min(index + 1, count - 1), id.clone(), cx); + let ix = cmp::min(index + 1, count - 1); + view.set_selected_index(ix, id.clone(), cx); + scroll_handle.scroll_to_item(ix); } } }) .on_action({ let id = id.clone(); + let scroll_handle = scroll_handle.clone(); move |view, _: &menu::SelectPrev, cx| { let count = view.match_count(id.clone()); if count > 0 { let index = view.selected_index(id.clone()); - view.set_selected_index(index.saturating_sub(1), id.clone(), cx); + let ix = index.saturating_sub(1); + view.set_selected_index(ix, id.clone(), cx); + scroll_handle.scroll_to_item(ix); } } }) .on_action({ let id = id.clone(); + let scroll_handle = scroll_handle.clone(); move |view: &mut V, _: &menu::SelectFirst, cx| { - view.set_selected_index(0, id.clone(), cx); + let count = view.match_count(id.clone()); + if count > 0 { + view.set_selected_index(0, id.clone(), cx); + scroll_handle.scroll_to_item(0); + } } }) .on_action({ let id = id.clone(); + let scroll_handle = scroll_handle.clone(); move |view: &mut V, _: &menu::SelectLast, cx| { let count = view.match_count(id.clone()); if count > 0 { view.set_selected_index(count - 1, id.clone(), cx); + scroll_handle.scroll_to_item(count - 1); } } }) @@ -174,6 +168,7 @@ impl Picker { .collect() }, ) + .track_scroll(scroll_handle.clone()) .size_full(), ) }