Detailed changes
@@ -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();
}
}