Prefer revealing items in the middle of the list for outline and project panels (#20349)
Closes https://github.com/zed-industries/zed/issues/18255 Zed does not scroll always, but only if the item is out of sight, this is preserved for now. Otherwise, if the item is out of sight, project and outline panels + the syntax tree view now attempt to scroll it into the middle, if there's enough elements above and below. Release Notes: - Improved revealing items for outline and project panels (now center of the list is preferred)
This commit is contained in:
parent
16cbff9118
commit
083f06322d
6 changed files with 75 additions and 27 deletions
|
@ -76,8 +76,8 @@ use gpui::{
|
|||
ClipboardItem, Context, DispatchPhase, ElementId, EventEmitter, FocusHandle, FocusOutEvent,
|
||||
FocusableView, FontId, FontWeight, HighlightStyle, Hsla, InteractiveText, KeyContext,
|
||||
ListSizingBehavior, Model, ModelContext, MouseButton, PaintQuad, ParentElement, Pixels, Render,
|
||||
SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task, TextStyle,
|
||||
TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
|
||||
ScrollStrategy, SharedString, Size, StrikethroughStyle, Styled, StyledText, Subscription, Task,
|
||||
TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, View,
|
||||
ViewContext, ViewInputHandler, VisualContext, WeakFocusHandle, WeakView, WindowContext,
|
||||
};
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
|
@ -1016,7 +1016,8 @@ impl CompletionsMenu {
|
|||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.selected_item = 0;
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1031,7 +1032,8 @@ impl CompletionsMenu {
|
|||
} else {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
}
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1046,7 +1048,8 @@ impl CompletionsMenu {
|
|||
} else {
|
||||
self.selected_item = 0;
|
||||
}
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1057,7 +1060,8 @@ impl CompletionsMenu {
|
|||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.selected_item = self.matches.len() - 1;
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.attempt_resolve_selected_completion_documentation(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
@ -1538,7 +1542,8 @@ struct CodeActionsMenu {
|
|||
impl CodeActionsMenu {
|
||||
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
|
||||
self.selected_item = 0;
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
|
@ -1548,7 +1553,8 @@ impl CodeActionsMenu {
|
|||
} else {
|
||||
self.selected_item = self.actions.len() - 1;
|
||||
}
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
|
@ -1558,13 +1564,15 @@ impl CodeActionsMenu {
|
|||
} else {
|
||||
self.selected_item = 0;
|
||||
}
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
|
||||
self.selected_item = self.actions.len() - 1;
|
||||
self.scroll_handle.scroll_to_item(self.selected_item);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
|
|
|
@ -88,11 +88,22 @@ pub struct UniformListFrameState {
|
|||
#[derive(Clone, Debug, Default)]
|
||||
pub struct UniformListScrollHandle(pub Rc<RefCell<UniformListScrollState>>);
|
||||
|
||||
/// Where to place the element scrolled to.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum ScrollStrategy {
|
||||
/// Place the element at the top of the list's viewport.
|
||||
Top,
|
||||
/// Attempt to place the element in the middle of the list's viewport.
|
||||
/// May not be possible if there's not enough list items above the item scrolled to:
|
||||
/// in this case, the element will be placed at the closest possible position.
|
||||
Center,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[allow(missing_docs)]
|
||||
pub struct UniformListScrollState {
|
||||
pub base_handle: ScrollHandle,
|
||||
pub deferred_scroll_to_item: Option<usize>,
|
||||
pub deferred_scroll_to_item: Option<(usize, ScrollStrategy)>,
|
||||
/// Size of the item, captured during last layout.
|
||||
pub last_item_size: Option<ItemSize>,
|
||||
}
|
||||
|
@ -118,14 +129,16 @@ impl UniformListScrollHandle {
|
|||
}
|
||||
|
||||
/// Scroll the list to the given item index.
|
||||
pub fn scroll_to_item(&self, ix: usize) {
|
||||
self.0.borrow_mut().deferred_scroll_to_item = Some(ix);
|
||||
pub fn scroll_to_item(&self, ix: usize, strategy: ScrollStrategy) {
|
||||
self.0.borrow_mut().deferred_scroll_to_item = Some((ix, strategy));
|
||||
}
|
||||
|
||||
/// Get the index of the topmost visible child.
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn logical_scroll_top_index(&self) -> usize {
|
||||
let this = self.0.borrow();
|
||||
this.deferred_scroll_to_item
|
||||
.map(|(ix, _)| ix)
|
||||
.unwrap_or_else(|| this.base_handle.logical_scroll_top().0)
|
||||
}
|
||||
}
|
||||
|
@ -273,18 +286,40 @@ impl Element for UniformList {
|
|||
scroll_offset.x = Pixels::ZERO;
|
||||
}
|
||||
|
||||
if let Some(ix) = shared_scroll_to_item {
|
||||
if let Some((ix, scroll_strategy)) = shared_scroll_to_item {
|
||||
let list_height = padded_bounds.size.height;
|
||||
let mut updated_scroll_offset = shared_scroll_offset.borrow_mut();
|
||||
let item_top = item_height * ix + padding.top;
|
||||
let item_bottom = item_top + item_height;
|
||||
let scroll_top = -updated_scroll_offset.y;
|
||||
let mut scrolled_to_top = false;
|
||||
if item_top < scroll_top + padding.top {
|
||||
scrolled_to_top = true;
|
||||
updated_scroll_offset.y = -(item_top) + padding.top;
|
||||
} else if item_bottom > scroll_top + list_height - padding.bottom {
|
||||
scrolled_to_top = true;
|
||||
updated_scroll_offset.y = -(item_bottom - list_height) - padding.bottom;
|
||||
}
|
||||
scroll_offset = *updated_scroll_offset;
|
||||
|
||||
match scroll_strategy {
|
||||
ScrollStrategy::Top => {}
|
||||
ScrollStrategy::Center => {
|
||||
if scrolled_to_top {
|
||||
let item_center = item_top + item_height / 2.0;
|
||||
let target_scroll_top = item_center - list_height / 2.0;
|
||||
|
||||
if item_top < scroll_top
|
||||
|| item_bottom > scroll_top + list_height
|
||||
{
|
||||
updated_scroll_offset.y = -target_scroll_top
|
||||
.max(Pixels::ZERO)
|
||||
.min(content_height - list_height)
|
||||
.max(Pixels::ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scroll_offset = *updated_scroll_offset
|
||||
}
|
||||
|
||||
let first_visible_element_ix =
|
||||
|
|
|
@ -2,8 +2,8 @@ use editor::{scroll::Autoscroll, Anchor, Editor, ExcerptId};
|
|||
use gpui::{
|
||||
actions, div, rems, uniform_list, AppContext, Div, EventEmitter, FocusHandle, FocusableView,
|
||||
Hsla, InteractiveElement, IntoElement, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
ParentElement, Render, SharedString, Styled, UniformListScrollHandle, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
ParentElement, Render, ScrollStrategy, SharedString, Styled, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use language::{Buffer, OwnedSyntaxLayer};
|
||||
use std::{mem, ops::Range};
|
||||
|
@ -199,7 +199,8 @@ impl SyntaxTreeView {
|
|||
|
||||
let descendant_ix = cursor.descendant_index();
|
||||
self.selected_descendant_ix = Some(descendant_ix);
|
||||
self.list_scroll_handle.scroll_to_item(descendant_ix);
|
||||
self.list_scroll_handle
|
||||
.scroll_to_item(descendant_ix, ScrollStrategy::Center);
|
||||
|
||||
cx.notify();
|
||||
Some(())
|
||||
|
|
|
@ -27,7 +27,7 @@ use gpui::{
|
|||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, ElementId, EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement,
|
||||
IntoElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, Model, MouseButton,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful,
|
||||
MouseDownEvent, ParentElement, Pixels, Point, Render, ScrollStrategy, SharedString, Stateful,
|
||||
StatefulInteractiveElement as _, Styled, Subscription, Task, UniformListScrollHandle, View,
|
||||
ViewContext, VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
|
@ -1078,7 +1078,8 @@ impl OutlinePanel {
|
|||
.iter()
|
||||
.position(|cached_entry| &cached_entry.entry == selected_entry);
|
||||
if let Some(index) = index {
|
||||
self.scroll_handle.scroll_to_item(index);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Center);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use editor::{scroll::Autoscroll, Editor};
|
|||
use gpui::{
|
||||
actions, div, impl_actions, list, prelude::*, uniform_list, AnyElement, AppContext, ClickEvent,
|
||||
DismissEvent, EventEmitter, FocusHandle, FocusableView, Length, ListSizingBehavior, ListState,
|
||||
MouseButton, MouseUpEvent, Render, Task, UniformListScrollHandle, View, ViewContext,
|
||||
WindowContext,
|
||||
MouseButton, MouseUpEvent, Render, ScrollStrategy, Task, UniformListScrollHandle, View,
|
||||
ViewContext, WindowContext,
|
||||
};
|
||||
use head::Head;
|
||||
use serde::Deserialize;
|
||||
|
@ -495,7 +495,9 @@ impl<D: PickerDelegate> Picker<D> {
|
|||
fn scroll_to_item_index(&mut self, ix: usize) {
|
||||
match &mut self.element_container {
|
||||
ElementContainer::List(state) => state.scroll_to_reveal_item(ix),
|
||||
ElementContainer::UniformList(scroll_handle) => scroll_handle.scroll_to_item(ix),
|
||||
ElementContainer::UniformList(scroll_handle) => {
|
||||
scroll_handle.scroll_to_item(ix, ScrollStrategy::Top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ use gpui::{
|
|||
AnyElement, AppContext, AssetSource, AsyncWindowContext, Bounds, ClipboardItem, DismissEvent,
|
||||
Div, DragMoveEvent, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
|
||||
InteractiveElement, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior, Model,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, Stateful,
|
||||
Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext as _,
|
||||
WeakView, WindowContext,
|
||||
MouseButton, MouseDownEvent, ParentElement, Pixels, Point, PromptLevel, Render, ScrollStrategy,
|
||||
Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
|
||||
VisualContext as _, WeakView, WindowContext,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use menu::{Confirm, SelectFirst, SelectLast, SelectNext, SelectPrev};
|
||||
|
@ -1356,7 +1356,8 @@ impl ProjectPanel {
|
|||
|
||||
fn autoscroll(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some((_, _, index)) = self.selection.and_then(|s| self.index_for_selection(s)) {
|
||||
self.scroll_handle.scroll_to_item(index);
|
||||
self.scroll_handle
|
||||
.scroll_to_item(index, ScrollStrategy::Center);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue