1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! 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.
//!
//! 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.
//!
//! ```ignore
//! 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`]; [`crate::AppExt::set_scrollbar_visibility`] updates all default overlays,
//! including Markdown code blocks and tables. [`Viewport`] also owns the handle.
pub
pub use *;
pub use *;
pub use *;
pub use *;
pub use ;
pub use Scroller;
use ;
use ;
use Painter;
use ink;
use Instant;
/// Shortest a thumb may get, however long the document — below this it stops
/// being something a pointer can catch.
pub const MIN_THUMB: Pixels = px;
/// Space between the overlay track and the viewport edges, along the axis the
/// bar runs.
const INSET: f32 = 4.0;
const BAR_INSET: Pixels = px;
/// Width of the strip the thumb sits in.
const TRACK: f32 = 10.0;
/// Gap between the thumb and the pane's edge across its axis, unless the
/// caller sets one.
const MARGIN: f32 = 2.0;
/// Width of the thumb itself.
pub const THUMB: f32 = 4.0;
/// Length of one [`rail`] mark, and its thickness.
const MARK: f32 = 16.0;
const MARK_THICK: f32 = 2.0;
/// Between two marks. The track's width, so a rail and a bar on the same pane
/// are cut to one rhythm.
const MARK_GAP: f32 = TRACK;
/// How far the rail stands off the edge it is pinned to.
const RAIL_INSET: f32 = 12.0;
/// What a rail needs beside the content before it will paint at all.
const RAIL_ROOM: f32 = RAIL_INSET + MARK;
// ---------------------------------------------------------------------------
// Pane — a scroll container whose axis is an argument, not a modifier
// ---------------------------------------------------------------------------
/// 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.
/// A scroll container, with the axis as an argument rather than a modifier you
/// can forget.
///
/// ```ignore
/// scroll::pane("log", Axes::Vertical)
/// .size_full()
/// .track_scroll(&self.scroll)
/// .child(content)
/// ```
///
/// Returns an element for the caller to fill, the way [`crate::stack::row`]
/// does — it takes no children and lays nothing out, so the pane stays the
/// app's and only its scroll behaviour is decided here. The id is gpui's
/// requirement, not ours: a scroll container has state to track.
///
/// A horizontal pane also contains a sideways gesture ([`contain_sideways`]).
///
/// `Axes::Both` inherits gpui's dominant-axis lock — a diagonal gesture moves
/// one axis, not two. gpui exposes no builder for `allow_concurrent_scroll`.
/// [`pane`], keeping the wheel it can act on: the pane a consumer nests inside
/// another and never wires a handle to.
///
/// [`claim_wheel`] asks the caller for a [`ScrollHandle`] and a [`ClaimState`],
/// because the caller usually has the handle already — it is scrolling the pane
/// from elsewhere. A bounded box inside someone else's page has neither, and a
/// pane that is only ever read by the wheel that moves it should not make a
/// consumer hold two fields to stop it dragging the page behind it. Both live
/// in keyed element state here, so the pane is still one call.
///
/// ```ignore
/// scroll::claiming_pane("output", Axes::Vertical, window, cx).child(text)
/// ```
///
/// The chaining is [`claim_wheel`]'s: at its ends the pane hands the wheel back
/// to the page.
/// What [`claiming_pane`] keeps between frames: the handle it reads its own
/// travel off, and where that travel stood before the wheel being dispatched.
/// [`pane`]'s answer applied to an element that already exists — a container
/// that scrolls only at some widths, or one another builder handed back.
///
/// ```ignore
/// strip.when(compact, |strip| scroll::scrolls(strip, Axes::Horizontal))
/// ```
/// Keep a sideways gesture inside the pane it started in.
///
/// The other half of [`pane`], and the half [`Axes::Vertical`] does not want:
/// a vertical pane at its end should hand the wheel to the page behind it
/// ([`claim_wheel`] is that chaining), but a sideways gesture bubbles on past
/// a pane that moved and pans any ancestor that scrolls sideways too.
///
/// Applied by [`pane`] for the axes that need it. Public because a consumer
/// wrapping bezel's content in a scroller of its own has the same problem and
/// the same fix.
///
/// Registered before the element's own handler and so run after it — gpui
/// bubbles the list backwards — which is why the pane has already moved by the
/// time the event stops here.
/// Keep every wheel inside the pane it landed on — `overscroll-behavior:
/// contain`, where [`claim_wheel`] is the chaining kind.
///
/// For a box with a cap on it, where the content is a program's output rather
/// than a document: it is a window onto something, and a wheel over a window
/// belongs to what is inside it. Chaining asks the pane to prove it moved,
/// which it reads off a handle carrying the previous frame's layout — under a
/// pane whose content is still arriving that reads as "did not move", and the
/// page takes the wheel while the box is still scrolling (user report,
/// DEV-13).
///
/// The page is still reachable: move the pointer off the box.