From 3d6528737aa59136e238a3520fca5ac5abc5f1f4 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Fri, 14 Aug 2026 21:47:08 +0800 Subject: [PATCH] fix(settings): give the page back its scroll range, and hold the bar off the window corner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- src/ui/scrollbar.rs | 21 ++++++++++++++++++--- src/ui/settings.rs | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/ui/scrollbar.rs b/src/ui/scrollbar.rs index 26a74e48..3e6efea4 100644 --- a/src/ui/scrollbar.rs +++ b/src/ui/scrollbar.rs @@ -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, 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, + 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 diff --git a/src/ui/settings.rs b/src/ui/settings.rs index e0cd9668..a7b462e6 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -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()