Refactor the scrollbar component (#36105)
Closes https://github.com/zed-industries/zed/issues/37621 Improves https://github.com/zed-industries/zed/issues/24623 Adding scrollbars withing Zed's UI currently is rather cumbersome, as it requires the copying of a lot of code in order for these to work. Wiring up settings for scrollbar visibilty always has to be done at the call site and the state has to be saved and maintained by the caller as well. Similarly, reserving space has to also be handled by the caller. This PR changes the way scrollbars work in Zed fundamentally by making use of the new `use_keyed_state` APIs: Instead of saving the state at the call site, the window now keeps track of the state corresponding to scrollbars. This enables us to add scrollbars with e.g. one simple call on divs: ```rust div() .vertical_scrollbar(window, cx) ``` will add a scrollbar to the corresponding container. There are some more improvements regarding tracking of scrollbar visibility settings (which is now handled by a trait for each setting that supports this) as well as reserving space. Additionally, all needed stuff for layouting, catching events and reserving space is also now managed by the scrollbar component instead. This drastically reduces the amount of event listeners and makes layouting of two scrollbars easier. Furthermore, this paves the way for more improvements to scrollbars, such as graceful auto-hide. Only downsight here is that we lose some customizability in a few areas. However, once this lands, we gain the ability to quickly follow these up without breaking stuff elsewhere. This also already fixes a few bugs: - Scrollbars no longer flicker on first render. - Auto-hide now properly works for all scrollbars. - If the content size changes, the scrollbar is updated on the same frame. Both of these happened because we were computing the scrollbar sizes too early, causing us to use the sizes from the previous frame or unitialized sizes. - The project panel no longer jumps if scrolled all the way to the bottom and the scrollbar actually auto-hides. Still TODO: - [x] Fix scrolling in the debugger memory view - [x] Clean up some more in the scrollbar component and reduce clones there - [x] Ensure we don't over-notify the entity the scrollbar is rendered within - [x] Make sure auto-hide properly works for all cases - [x] Check whether we want to implement the scrollbar trait for `UniformList`s as well - ~~ [ ] Use for uniformlist where possible~~ Postponed - [x] Improve layout for cases where we render both scrollbars. Release Notes: - N/A
This commit is contained in:
@@ -16,10 +16,10 @@
|
||||
//! constructed by combining these two systems into an all-in-one element.
|
||||
|
||||
use crate::{
|
||||
Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
|
||||
Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox, HitboxBehavior,
|
||||
HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
|
||||
KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
|
||||
AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
|
||||
DispatchPhase, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox,
|
||||
HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
|
||||
KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
|
||||
MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Overflow, ParentElement, Pixels,
|
||||
Point, Render, ScrollWheelEvent, SharedString, Size, Style, StyleRefinement, Styled, Task,
|
||||
TooltipId, Visibility, Window, WindowControlArea, point, px, size,
|
||||
@@ -1036,6 +1036,15 @@ pub trait StatefulInteractiveElement: InteractiveElement {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the space to be reserved for rendering the scrollbar.
|
||||
///
|
||||
/// This will only affect the layout of the element when overflow for this element is set to
|
||||
/// `Overflow::Scroll`.
|
||||
fn scrollbar_width(mut self, width: impl Into<AbsoluteLength>) -> Self {
|
||||
self.interactivity().base_style.scrollbar_width = Some(width.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Track the scroll state of this element with the given handle.
|
||||
fn track_scroll(mut self, scroll_handle: &ScrollHandle) -> Self {
|
||||
self.interactivity().tracked_scroll_handle = Some(scroll_handle.clone());
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
//! elements with uniform height.
|
||||
|
||||
use crate::{
|
||||
AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
|
||||
Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement, IsZero, LayoutId,
|
||||
ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size, StyleRefinement, Styled,
|
||||
Window, point, size,
|
||||
AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, Entity,
|
||||
GlobalElementId, Hitbox, InspectorElementId, InteractiveElement, Interactivity, IntoElement,
|
||||
IsZero, LayoutId, ListSizingBehavior, Overflow, Pixels, Point, ScrollHandle, Size,
|
||||
StyleRefinement, Styled, Window, point, size,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
|
||||
@@ -71,7 +71,7 @@ pub struct UniformList {
|
||||
/// Frame state used by the [UniformList].
|
||||
pub struct UniformListFrameState {
|
||||
items: SmallVec<[AnyElement; 32]>,
|
||||
decorations: SmallVec<[AnyElement; 1]>,
|
||||
decorations: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
/// A handle for controlling the scroll position of a uniform list.
|
||||
@@ -529,6 +529,31 @@ pub trait UniformListDecoration {
|
||||
) -> AnyElement;
|
||||
}
|
||||
|
||||
impl<T: UniformListDecoration + 'static> UniformListDecoration for Entity<T> {
|
||||
fn compute(
|
||||
&self,
|
||||
visible_range: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
scroll_offset: Point<Pixels>,
|
||||
item_height: Pixels,
|
||||
item_count: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> AnyElement {
|
||||
self.update(cx, |inner, cx| {
|
||||
inner.compute(
|
||||
visible_range,
|
||||
bounds,
|
||||
scroll_offset,
|
||||
item_height,
|
||||
item_count,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl UniformList {
|
||||
/// Selects a specific list item for measurement.
|
||||
pub fn with_width_from_item(mut self, item_index: Option<usize>) -> Self {
|
||||
|
||||
@@ -153,7 +153,7 @@ pub struct Style {
|
||||
#[refineable]
|
||||
pub overflow: Point<Overflow>,
|
||||
/// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes.
|
||||
pub scrollbar_width: f32,
|
||||
pub scrollbar_width: AbsoluteLength,
|
||||
/// Whether both x and y axis should be scrollable at the same time.
|
||||
pub allow_concurrent_scroll: bool,
|
||||
/// Whether scrolling should be restricted to the axis indicated by the mouse wheel.
|
||||
@@ -745,7 +745,7 @@ impl Default for Style {
|
||||
},
|
||||
allow_concurrent_scroll: false,
|
||||
restrict_scroll_to_axis: false,
|
||||
scrollbar_width: 0.0,
|
||||
scrollbar_width: AbsoluteLength::default(),
|
||||
position: Position::Relative,
|
||||
inset: Edges::auto(),
|
||||
margin: Edges::<Length>::zero(),
|
||||
|
||||
@@ -277,7 +277,7 @@ impl ToTaffy<taffy::style::Style> for Style {
|
||||
taffy::style::Style {
|
||||
display: self.display.into(),
|
||||
overflow: self.overflow.into(),
|
||||
scrollbar_width: self.scrollbar_width,
|
||||
scrollbar_width: self.scrollbar_width.to_taffy(rem_size),
|
||||
position: self.position.into(),
|
||||
inset: self.inset.to_taffy(rem_size),
|
||||
size: self.size.to_taffy(rem_size),
|
||||
@@ -314,6 +314,15 @@ impl ToTaffy<taffy::style::Style> for Style {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<f32> for AbsoluteLength {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> f32 {
|
||||
match self {
|
||||
AbsoluteLength::Pixels(pixels) => pixels.into(),
|
||||
AbsoluteLength::Rems(rems) => (*rems * rem_size).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
|
||||
fn to_taffy(&self, rem_size: Pixels) -> taffy::prelude::LengthPercentageAuto {
|
||||
match self {
|
||||
|
||||
@@ -4960,6 +4960,12 @@ impl<T: Into<SharedString>> From<(ElementId, T)> for ElementId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static core::panic::Location<'static>> for ElementId {
|
||||
fn from(location: &'static core::panic::Location<'static>) -> Self {
|
||||
ElementId::CodeLocation(*location)
|
||||
}
|
||||
}
|
||||
|
||||
/// A rectangle to be rendered in the window at the given position and size.
|
||||
/// Passed as an argument [`Window::paint_quad`].
|
||||
#[derive(Clone)]
|
||||
|
||||
Reference in New Issue
Block a user