Expand description
What gpui’s own scroll handles leave to the app: a container that scrolls the way it was asked to, a bar to show the position, and a rule for which pane a wheel belongs to.
pane is the container — an axis given at construction, the way
SwiftUI’s ScrollView takes one, because gpui’s is a style field that
defaults to unset and gets guessed at. Read its docs before reaching for
div().overflow_y_scroll(); the guess is a real bug and not a small one.
gpui scrolls that pane perfectly well and draws nothing while it does, so a
bezel app has no way to show how far down it is. That is scrollbar: an
overlay the caller lays over its own pane, because a wrapper that swallowed
the content would have to re-implement layout for it. Nesting two panes is
claim_wheel’s business.
div().relative() // the bar is absolute in here
.child(
scroll::pane("pane", Axes::Vertical)
.size_full()
.track_scroll(&self.scroll) // gpui's handle, the app's field
.child(content),
)
.child(scroll::scrollbar("pane-bar", &self.scroll, &self.scroll_bar))The bar must span the container it reports on — its track is the viewport,
in the coordinates thumb answers in.
The geometry is transcribed from zed’s own scrollbar (thumb_ranges in
crates/ui/src/components/scrollbar.rs), which is 1722 lines of settings
system around the fifteen that matter. Two of gpui’s conventions are easy to
get backwards and both are load-bearing here: max_offset is the overflow
(content minus viewport, not content), and offset is negative as you
scroll down.
transient is the same bar, shown only while its content moves.
Overlay manages its own state and supports either axis. Its default is
Visibility::Scrolling; set_visibility updates all default overlays,
including Markdown code blocks and tables. Viewport also owns the handle.
Structs§
- Claim
State - Where a
claim_wheelpane was before the wheel that is being dispatched. - Drift
State - Where a drift was last aimed, and when it last moved.
- Follow
State - Whether a
followview is still pinned, and the overflow it last saw. - Overlay
- Scrollbar
Drag - The drag payload. Carries the bar’s id because, unlike a split, an app has
several of these on screen at once and
on_drag_movefilters by type alone — without the id every bar in the window would answer one thumb’s gesture. - Scrollbar
State - Where in the thumb a drag was grabbed.
- Transient
State - The show-and-fade state behind
transient: the last frame’s scroll state (a change is activity), a generation counter (a fresh animation id restarts the fade —AnimationElementpins its clock to the id it first laid out with), and the hover flag that holds the thumb up while the pointer is on the strip. - Viewport
- An intrinsically sized scroll container with its own handle and overlay.
Enums§
- Axes
- Which way a
panescrolls. SwiftUI’sAxis.Set, which gpui’sAxishas no spelling for: a pane that scrolls both ways is not one of two directions. - Visibility
- Visibility for overflowing panes; content that fits never draws a bar.
Constants§
- DRIFT_
EDGE - How close to an edge a held drag starts the pane moving. Read off
../desktop’s board (2026-05): a third of a column, wide enough to reach while aiming at a card and narrow enough to leave the middle still. - DRIFT_
SPEED - How fast a pane travels with the pointer at the very edge, in pixels a second. The same board’s 22px per frame, said in a unit that does not double on a 120Hz display.
- FOLLOW_
SLACK - How close to the bottom still counts as following. A wheel lands on fractional offsets and a re-layout can move the end by a hair; without slack a view would unpin itself for a rounding error nobody asked for.
- MIN_
THUMB - Shortest a thumb may get, however long the document — below this it stops being something a pointer can catch.
- TRANSIENT_
IDLE - How long the thumb stays after the last scroll before fading — the idle
window is the fade, because the fork’s
Animationhas no delay.
Functions§
- at_
bottom - Whether
offsetis at the end of the scrollable range, withinslack. - claim_
wheel - Let a pane keep the wheel it can act on, instead of passing it to the pane behind as well.
- claiming_
pane pane, keeping the wheel it can act on: the pane a consumer nests inside another and never wires a handle to.- contain_
sideways - Keep a sideways gesture inside the pane it started in.
- contain_
wheel - Keep every wheel inside the pane it landed on —
overscroll-behavior: contain, whereclaim_wheelis the chaining kind. - drift
- Move
handlewhile a drag is held near its edge, for as long as it is held there. - drift_
velocity - How far a pane should travel in a second, for a pointer at
pointerbetween edgesstartandend. - follow
- Keep
handlepinned to the bottom of its content while the user leaves it there, and get out of the way the moment they scroll up. - offset_
for_ thumb - The inverse: the scroll offset that puts the thumb’s top at
top. - pane
- A scroll container, with the axis as an argument rather than a modifier you can forget.
- rail
- A mark per item, the one at the top of the viewport lit — for a pane whose content comes in countable pieces (a transcript’s turns) rather than as one continuous document, where how far down you are matters less than which piece you are on. A press jumps to that piece.
- rail_
fits - Whether
roombeside the content is enough for a rail to paint in. A hand-rolled rail asks this to land on the same floor asrail. - scrollbar
- The bar: an overlay strip along the right edge of whatever it is laid over, showing nothing at all when the content fits.
- scrolls
pane’s answer applied to an element that already exists — a container that scrolls only at some widths, or one another builder handed back.- set_
visibility - Set the default for overlays, including those inside Markdown blocks.
- thumb
- Where the thumb sits in a track of
viewportlength, as a range from the track’s start — orNonewhen there is nothing to scroll. - transient
- The same bar as
scrollbar, but it only earns its place while the content moves: activity raises the thumb, and it fades out overTRANSIENT_IDLEonce the scrolling stops. Hovering the strip or dragging the thumb holds it up. Withreduce_motionthere is nothing to animate, so it renders as the always-on bar. - visibility