Files
tty7/src/ui/scrollbar.rs
T
l0ng-ai a0d153c546 feat(ui): give the right panel and tab rail an overlay scrollbar
The detail panel (Info / Outline / Changes / Files), the remote SFTP
listing and the tab rail all scrolled with gpui's `overflow_y_scroll()`,
which paints nothing: a deep file tree or a long tab list gave no hint
that there was more content, or where in it you were (issue #185).

Hang gpui-component's `Scrollbar` over each of those containers through
one shared helper (`ui::scrollbar`). The handle stays app-owned so
`scroll_to_item` keeps working — that rules out gpui-component's
`overflow_y_scrollbar()`, which mints its own handle internally.

Theme the bar from the active theme's own background→foreground mix
ladder rather than the stock fixed greys, and keep the track transparent
so the thumb floats instead of laying a slab down the panel edge.

Show mode follows the platform: auto-hide on macOS (and anywhere else the
OS uses overlay scrollbars), permanently visible on Windows and Linux.
gpui-component's own default picks `Hover` there, which only reveals the
bar within 16px of the edge — that is the "no scrollbar at all" the issue
reports from Windows.
2026-07-26 20:46:00 +08:00

60 lines
2.6 KiB
Rust

//! The overlay scrollbar the app's own scroll areas wear (issue #185).
//!
//! gpui's `overflow_y_scroll()` scrolls but paints nothing, so a long file tree
//! or tab list gave no hint that there was more content — or where in it you
//! were. gpui-component ships the [`Scrollbar`] element for exactly this; the
//! only thing missing was a house shape for hanging it off our containers.
//!
//! Why not gpui-component's own `overflow_y_scrollbar()` wrapper: it mints its
//! own `ScrollHandle` internally via `use_keyed_state`, which leaves nothing for
//! `scroll_to_item` to aim at. Our lists need programmatic scrolling (activating
//! a tab pulls its row into view), so the handle stays app-owned and the
//! scrollbar is layered on top of it.
//!
//! Appearance (thumb colour, whether it auto-hides) comes from the theme — see
//! the scrollbar block in [`crate::ui::theme::apply_theme`].
//!
//! One behaviour worth knowing: while the bar is live it claims mouse-downs in
//! the 16px strip along the container's right edge, so the trailing few pixels
//! of a row stop being clickable there. That is inherent to an overlay
//! scrollbar (macOS's own behave the same way) and the reason the bar is only
//! permanently live on the platforms whose scrollbars are permanently visible.
use gpui::{AnyElement, ElementId, ScrollHandle, div, prelude::*};
use gpui_component::scroll::Scrollbar;
use gpui_component::v_flex;
/// Wrap a scrolling column so a vertical scrollbar floats over its right edge.
///
/// `scroll_area` must be the element that carries `.overflow_y_scroll()` and
/// `.track_scroll(handle)`; this only adds the positioned parent the scrollbar
/// measures against and the absolute layer it paints into. The scrollbar draws
/// *over* the content rather than reserving a gutter, so adopting it never
/// reflows the wrapped list.
///
/// `id` names the scrollbar's element state (hover/drag/fade), so it must be
/// unique per scroll area — the helper can't fall back to `Location::caller`
/// the way [`Scrollbar::vertical`] does, since every call site would then share
/// this function's line.
pub(crate) fn with_vertical_scrollbar(
id: impl Into<ElementId>,
scroll_area: impl IntoElement,
handle: &ScrollHandle,
) -> AnyElement {
v_flex()
.relative()
.flex_1()
.min_h_0()
.child(scroll_area)
.child(
div()
.absolute()
.top_0()
.left_0()
.right_0()
.bottom_0()
.child(Scrollbar::vertical(handle).id(id)),
)
.into_any_element()
}