Skip to main content

Module scroll

Module scroll 

Source
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§

ClaimState
Where a claim_wheel pane was before the wheel that is being dispatched.
DriftState
Where a drift was last aimed, and when it last moved.
FollowState
Whether a follow view is still pinned, and the overflow it last saw.
Overlay
ScrollbarDrag
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_move filters by type alone — without the id every bar in the window would answer one thumb’s gesture.
ScrollbarState
Where in the thumb a drag was grabbed.
TransientState
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 — AnimationElement pins 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 pane scrolls. SwiftUI’s Axis.Set, which gpui’s Axis has 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 Animation has no delay.

Functions§

at_bottom
Whether offset is at the end of the scrollable range, within slack.
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, where claim_wheel is the chaining kind.
drift
Move handle while 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 pointer between edges start and end.
follow
Keep handle pinned 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 room beside the content is enough for a rail to paint in. A hand-rolled rail asks this to land on the same floor as rail.
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 viewport length, as a range from the track’s start — or None when 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 over TRANSIENT_IDLE once the scrolling stops. Hovering the strip or dragging the thumb holds it up. With reduce_motion there is nothing to animate, so it renders as the always-on bar.
visibility