terminal_view: Ensure reported size does not change once content becomes scrollable

MrSubidubi created

Change summary

crates/gpui/src/elements/div.rs           | 21 ++++++---------------
crates/gpui/src/styled.rs                 |  9 +++++++++
crates/terminal_view/src/terminal_view.rs |  2 +-
crates/ui/src/components/scrollbar.rs     | 22 +++++++++++++++++-----
4 files changed, 33 insertions(+), 21 deletions(-)

Detailed changes

crates/gpui/src/elements/div.rs 🔗

@@ -16,12 +16,12 @@
 //! constructed by combining these two systems into an all-in-one element.
 
 use crate::{
-    AbsoluteLength, Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent,
-    DispatchPhase, Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId,
-    Hitbox, HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext,
-    KeyDownEvent, KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent,
-    MouseButton, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent,
-    Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
+    Action, AnyDrag, AnyElement, AnyTooltip, AnyView, App, Bounds, ClickEvent, DispatchPhase,
+    Display, Element, ElementId, Entity, FocusHandle, Global, GlobalElementId, Hitbox,
+    HitboxBehavior, HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent,
+    KeyUpEvent, KeyboardButton, KeyboardClickEvent, LayoutId, ModifiersChangedEvent, MouseButton,
+    MouseClickEvent, MouseDownEvent, MouseMoveEvent, MousePressureEvent, MouseUpEvent, Overflow,
+    ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
     StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px,
     size,
 };
@@ -1146,15 +1146,6 @@ 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());

crates/gpui/src/styled.rs 🔗

@@ -61,6 +61,15 @@ pub trait Styled: Sized {
         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.style().scrollbar_width = Some(width.into());
+        self
+    }
+
     /// Sets the whitespace of the element to `normal`.
     /// [Docs](https://tailwindcss.com/docs/whitespace#normal)
     fn whitespace_normal(mut self) -> Self {

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1260,7 +1260,7 @@ impl Render for TerminalView {
                         div.custom_scrollbars(
                             Scrollbars::for_settings::<TerminalScrollbarSettingsWrapper>()
                                 .show_along(ScrollAxes::Vertical)
-                                .with_track_along(
+                                .with_stable_track_along(
                                     ScrollAxes::Vertical,
                                     cx.theme().colors().editor_background,
                                 )

crates/ui/src/components/scrollbar.rs 🔗

@@ -343,6 +343,7 @@ enum ReservedSpace {
     None,
     Thumb,
     Track,
+    StableTrack,
 }
 
 impl ReservedSpace {
@@ -353,6 +354,14 @@ impl ReservedSpace {
     fn needs_scroll_track(&self) -> bool {
         *self == ReservedSpace::Track
     }
+
+    fn needs_space_reserved(&self, max_offset: Pixels) -> bool {
+        match self {
+            Self::StableTrack => true,
+            Self::Track => !max_offset.is_zero(),
+            _ => false,
+        }
+    }
 }
 
 #[derive(Debug, Default, Clone, Copy)]
@@ -479,6 +488,12 @@ impl<ScrollHandle: ScrollableHandle> Scrollbars<ScrollHandle> {
         self
     }
 
+    pub fn with_stable_track_along(mut self, along: ScrollAxes, background_color: Hsla) -> Self {
+        self.visibility = along.apply_to(self.visibility, ReservedSpace::StableTrack);
+        self.track_color = Some(background_color);
+        self
+    }
+
     pub fn width_sm(mut self) -> Self {
         self.scrollbar_width = ScrollbarWidth::Small;
         self
@@ -713,13 +728,10 @@ impl<T: ScrollableHandle> ScrollbarState<T> {
 
     fn space_to_reserve_for(&self, axis: ScrollbarAxis) -> Option<Pixels> {
         (self.show_state.is_disabled().not()
-            && self.visibility.along(axis).needs_scroll_track()
             && self
-                .scroll_handle()
-                .max_offset()
+                .visibility
                 .along(axis)
-                .is_zero()
-                .not())
+                .needs_space_reserved(self.scroll_handle().max_offset().along(axis)))
         .then(|| self.space_to_reserve())
     }