fix(settings): give the page back its scroll range, and hold the bar off the window corner

The centring added in #631 turned the settings content box into a flex
column, and that cost the page most of its scroll range: the box is an
item of the scroll pane, which is itself a flex column, so its height
came out of a negotiation with the pane rather than from the rows it
stacks. `content_size` is just that box's laid-out bounds, so the range
ended a screen short of the last row — dragging to the bottom still left
content cut off. `flex_shrink_0` does not help; the height is agreed,
not squeezed. Centre with `mx_auto` on the column instead and leave the
box a block, which reports the full height it stacks.

While there, hold the content scrollbar 12px clear of the top and
bottom. Every other list this bar serves sits in a bordered panel where
running the full height is right; this pane is the window, and a bar
drawn to the last pixel lands on the rounded corner. New
`with_inset_vertical_scrollbar` takes the inset, and the existing
`with_vertical_scrollbar` keeps its behaviour for the other twelve
call sites.
This commit is contained in:
l0ng-ai
2026-08-14 21:47:08 +08:00
parent f08d8c2764
commit 3d6528737a
2 changed files with 47 additions and 14 deletions
+18 -3
View File
@@ -1,4 +1,4 @@
use gpui::{AnyElement, ElementId, ScrollHandle, div, prelude::*};
use gpui::{AnyElement, ElementId, Pixels, ScrollHandle, div, prelude::*, px};
use gpui_component::scroll::Scrollbar;
use gpui_component::v_flex;
@@ -13,6 +13,21 @@ pub(crate) fn with_vertical_scrollbar(
id: impl Into<ElementId>,
scroll_area: impl IntoElement,
handle: &ScrollHandle,
) -> AnyElement {
with_inset_vertical_scrollbar(id, scroll_area, handle, px(0.))
}
/// The same bar, held `inset_y` clear of the top and bottom of the area.
///
/// Every list inside a bordered panel wants the bar to run the full height —
/// the border is already the edge. A scroll area that *is* the window has no
/// such edge, and a bar that runs to the last pixel lands on the rounded
/// corner and reads as if it had been clipped.
pub(crate) fn with_inset_vertical_scrollbar(
id: impl Into<ElementId>,
scroll_area: impl IntoElement,
handle: &ScrollHandle,
inset_y: Pixels,
) -> AnyElement {
v_flex()
.relative()
@@ -28,10 +43,10 @@ pub(crate) fn with_vertical_scrollbar(
.child(
div()
.absolute()
.top_0()
.top(inset_y)
.left_0()
.right_0()
.bottom_0()
.bottom(inset_y)
// No `scrollbar_show` override: it falls back to
// `cx.theme().scrollbar_show`, which `apply_theme` pins to
// `Scrolling` for every list in the app. Overriding it here
+29 -11
View File
@@ -78,6 +78,11 @@ const THEME_PANEL_W_MIN: f32 = 240.;
/// a description stops being a paragraph and becomes a line to scan across.
const READING_COLUMN: f32 = 640.;
/// How far the content scrollbar stays clear of the top and bottom of the
/// window. The page has no chrome of its own to stop at, so a bar drawn to the
/// last pixel runs into the window's rounded corner and looks cut off.
const SCROLLBAR_WINDOW_INSET: f32 = 12.;
/// What the page gets before any list does, and the floor it may be pushed to
/// when even that cannot be had — the numbers this file did not have.
///
@@ -1706,19 +1711,29 @@ impl Tty7App {
// Chinese and Japanese pages while every other row stopped
// at the column.
//
// `items_center` centres the column across the page's
// cross axis. The cap keeps a description a paragraph
// rather than a line to scan across, but left-aligning what
// it caps put the whole page against the nav: on a window
// as wide as the display it was made for, 640 points of
// settings sat beside 1600 points of nothing. Centred, the
// page is one column with air on both sides at every width,
// and below the cap — where the column is the page — this
// does nothing at all.
v_flex().w_full().items_center().px_10().py_8().child(
// `mx_auto` on the column centres it across the page. The
// cap keeps a description a paragraph rather than a line to
// scan across, but left-aligning what it caps put the whole
// page against the nav: on a window as wide as the display
// it was made for, 640 points of settings sat beside 1600
// points of nothing. Centred, the page is one column with
// air on both sides at every width, and below the cap —
// where the column is the page — this does nothing at all.
//
// The centring has to come from the margin, not from an
// `items_center` on a flex box here. The scroll pane is a
// flex column and this box is its item: as a block it
// reports the full height of the page it stacks, but as a
// flex box it negotiates a height with the pane and lands
// near the viewport, and `content_size` — which is just
// this box's laid-out bounds — then leaves most of the page
// outside the scroll range. `flex_shrink_0` does not buy
// its way out of that; only staying a block does.
div().w_full().px_10().py_8().child(
div()
.w_full()
.max_w(px(READING_COLUMN * ui_scale))
.mx_auto()
.children(no_match_note)
.child(content),
),
@@ -1731,10 +1746,13 @@ impl Tty7App {
// the pane that covers most of the page would hide the theme
// image behind it.
.when_some(self.active_settings(), |pane, s| {
pane.child(crate::ui::scrollbar::with_vertical_scrollbar(
// Inset: this pane reaches both ends of the window, so a
// full-height bar ends up on the rounded corner.
pane.child(crate::ui::scrollbar::with_inset_vertical_scrollbar(
"settings-content-scrollbar",
body,
&s.content_scroll,
px(SCROLLBAR_WINDOW_INSET),
))
})
.into_any_element()