Skip to content

Commit 1cedd62

Browse files
committed
🎨 fmt scroll spec
1 parent 6faca4b commit 1cedd62

1 file changed

Lines changed: 73 additions & 67 deletions

File tree

specs/scroll-spec.md

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66

77
## 1. Purpose
88

9-
This specification defines how clipped elements scroll their children. It
10-
covers the clip configuration API, the two scroll modes (manual and
11-
pointer-driven), and the interaction between them.
9+
This specification defines how clipped elements scroll their children. It covers
10+
the clip configuration API, the two scroll modes (manual and pointer-driven),
11+
and the interaction between them.
1212

13-
Scrolling builds on the existing clip infrastructure described in the
14-
renderer spec (Section 12.2). This specification promotes clip/scroll from
15-
an elastic surface to a specified contract.
13+
Scrolling builds on the existing clip infrastructure described in the renderer
14+
spec (Section 12.2). This specification promotes clip/scroll from an elastic
15+
surface to a specified contract.
1616

1717
---
1818

@@ -49,17 +49,17 @@ clip?: {
4949

5050
Each axis is independently configured:
5151

52-
- **`undefined`** (or omitted) — No clipping or scrolling on this axis.
53-
Children may overflow visually.
54-
- **`number`** — Clipping is enabled. Children are offset by the given
55-
value (in layout-engine units). The caller controls the offset each frame.
56-
Typically negative to scroll content upward/leftward.
57-
- **`"pointer"`** — Clipping is enabled. The offset is managed by the
58-
underlying layout engine's scroll system, driven by wheel events passed
59-
via `RenderOptions.event`.
52+
- **`undefined`** (or omitted) — No clipping or scrolling on this axis. Children
53+
may overflow visually.
54+
- **`number`** — Clipping is enabled. Children are offset by the given value (in
55+
layout-engine units). The caller controls the offset each frame. Typically
56+
negative to scroll content upward/leftward.
57+
- **`"pointer"`** — Clipping is enabled. The offset is managed by the underlying
58+
layout engine's scroll system, driven by wheel events passed via
59+
`RenderOptions.event`.
6060

61-
A bare `clip` value is always an object with optional `x` and `y` fields.
62-
There is no shorthand form.
61+
A bare `clip` value is always an object with optional `x` and `y` fields. There
62+
is no shorthand form.
6363

6464
### 3.1 Examples
6565

@@ -69,7 +69,7 @@ Vertical pointer scrolling (typical scroll container):
6969
open("list", {
7070
layout: { width: grow(), height: fixed(20) },
7171
clip: { y: "pointer" },
72-
})
72+
});
7373
```
7474

7575
Manual horizontal offset (e.g., programmatic scroll-to):
@@ -78,7 +78,7 @@ Manual horizontal offset (e.g., programmatic scroll-to):
7878
open("viewport", {
7979
layout: { width: fixed(40), height: fixed(10) },
8080
clip: { x: -scrollX },
81-
})
81+
});
8282
```
8383

8484
Mixed — horizontal manual, vertical pointer:
@@ -87,22 +87,22 @@ Mixed — horizontal manual, vertical pointer:
8787
open("editor", {
8888
layout: { width: grow(), height: grow() },
8989
clip: { x: -columnOffset, y: "pointer" },
90-
})
90+
});
9191
```
9292

9393
---
9494

9595
## 4. Pointer-Driven Scrolling
9696

97-
When an axis is set to `"pointer"`, the underlying layout engine manages
98-
scroll state for that axis across frames.
97+
When an axis is set to `"pointer"`, the underlying layout engine manages scroll
98+
state for that axis across frames.
9999

100100
### 4.1 Wheel scrolling
101101

102-
When a `WheelEvent` is passed via `RenderOptions.event` and the wheel
103-
event's coordinates fall within a clip element that has a `"pointer"` axis,
104-
the layout engine applies the scroll delta to that axis. The innermost
105-
scroll container at the event coordinates takes priority.
102+
When a `WheelEvent` is passed via `RenderOptions.event` and the wheel event's
103+
coordinates fall within a clip element that has a `"pointer"` axis, the layout
104+
engine applies the scroll delta to that axis. The innermost scroll container at
105+
the event coordinates takes priority.
106106

107107
### 4.2 Clamping
108108

@@ -113,19 +113,19 @@ within the container on a given axis cannot be scrolled on that axis.
113113
### 4.3 Future: drag scrolling and momentum
114114

115115
The clip configuration and rendering pipeline are designed to support
116-
pointer-driven drag scrolling with momentum in the future. The underlying
117-
layout engine already supports this via `Clay_UpdateScrollContainers`. When
118-
added, it would be an opt-in behavior — the current wheel-only scrolling
119-
would remain the default.
116+
pointer-driven drag scrolling with momentum in the future. The underlying layout
117+
engine already supports this via `Clay_UpdateScrollContainers`. When added, it
118+
would be an opt-in behavior — the current wheel-only scrolling would remain the
119+
default.
120120

121121
---
122122

123123
## 5. Input Event Integration
124124

125-
Wheel events reach the scroll system via `RenderOptions.event`, which
126-
accepts a single `InputEvent` per render transaction. When the event is a
127-
`WheelEvent` whose coordinates fall within a pointer-driven scroll
128-
container, the layout engine applies the scroll delta.
125+
Wheel events reach the scroll system via `RenderOptions.event`, which accepts a
126+
single `InputEvent` per render transaction. When the event is a `WheelEvent`
127+
whose coordinates fall within a pointer-driven scroll container, the layout
128+
engine applies the scroll delta.
129129

130130
The `event` field, its semantics, and the one-event-per-render contract are
131131
defined in the [Renderer Specification](renderer-spec.md), Section 8.2.3.
@@ -139,8 +139,8 @@ changes to support per-axis modes.
139139

140140
For the clip configuration, the transfer encoding includes:
141141

142-
- A packed word with axis modes: low byte = x mode, next byte = y mode.
143-
Mode values: `0` = off, `1` = manual, `2` = pointer.
142+
- A packed word with axis modes: low byte = x mode, next byte = y mode. Mode
143+
values: `0` = off, `1` = manual, `2` = pointer.
144144
- For each axis in mode `1` (manual): a float32 offset value.
145145

146146
When mode is `2`, no offset value is encoded. The C side reads the scroll
@@ -154,37 +154,36 @@ while the element is open.
154154
When decoding a clip configuration, the C side:
155155

156156
1. Reads axis modes from the packed word.
157-
2. Sets `decl.clip.horizontal` / `decl.clip.vertical` to true for any
158-
non-zero mode.
157+
2. Sets `decl.clip.horizontal` / `decl.clip.vertical` to true for any non-zero
158+
mode.
159159
3. For manual axes, reads the offset from the buffer.
160160
4. For pointer axes, reads the offset from `Clay_GetScrollOffset()`.
161161
5. Combines both into `decl.clip.childOffset`.
162162

163-
This happens while the element is open (between `Clay__OpenElementWithId`
164-
and `Clay__ConfigureOpenElement`), so `Clay_GetScrollOffset()` resolves to
165-
the correct element.
163+
This happens while the element is open (between `Clay__OpenElementWithId` and
164+
`Clay__ConfigureOpenElement`), so `Clay_GetScrollOffset()` resolves to the
165+
correct element.
166166

167167
---
168168

169169
## 8. Invariants
170170

171-
**SCROLL-1.** A clip axis set to `undefined` MUST NOT clip children on that
172-
axis and MUST NOT participate in scroll input handling.
171+
**SCROLL-1.** A clip axis set to `undefined` MUST NOT clip children on that axis
172+
and MUST NOT participate in scroll input handling.
173173

174-
**SCROLL-2.** A numeric clip axis MUST clip children and offset them by
175-
exactly the provided value each frame. The layout engine's internal scroll
176-
state for that axis MUST NOT affect the rendered offset.
174+
**SCROLL-2.** A numeric clip axis MUST clip children and offset them by exactly
175+
the provided value each frame. The layout engine's internal scroll state for
176+
that axis MUST NOT affect the rendered offset.
177177

178-
**SCROLL-3.** A `"pointer"` clip axis MUST clip children and derive the
179-
offset from the layout engine's scroll state, which is updated by wheel
180-
events.
178+
**SCROLL-3.** A `"pointer"` clip axis MUST clip children and derive the offset
179+
from the layout engine's scroll state, which is updated by wheel events.
181180

182-
**SCROLL-4.** Manual and pointer modes MAY be mixed on different axes of
183-
the same element.
181+
**SCROLL-4.** Manual and pointer modes MAY be mixed on different axes of the
182+
same element.
184183

185-
**SCROLL-5.** Scroll position on pointer axes MUST be clamped to content
186-
bounds. A container whose content fits within it on a given axis MUST NOT
187-
scroll on that axis.
184+
**SCROLL-5.** Scroll position on pointer axes MUST be clamped to content bounds.
185+
A container whose content fits within it on a given axis MUST NOT scroll on that
186+
axis.
188187

189188
**SCROLL-6.** When `RenderOptions.event` carries a `WheelEvent`, the scroll
190189
delta MUST be applied to the innermost pointer-driven scroll container whose
@@ -238,40 +237,47 @@ logical scroll position as a positive number. Clayterm negates it internally
238237
before passing to the layout engine.
239238

240239
```ts
241-
clip: { y: 20 } // "show content starting at position 20"
240+
clip: {
241+
y: 20;
242+
} // "show content starting at position 20"
242243
```
243244

244245
Pros:
246+
245247
- Matches `scrollTop` / `scrollLeft` conventions from the browser.
246248
- More intuitive — "scroll to 20" rather than "offset by -20".
247-
- Callers never deal with negative values for a conceptually positive
248-
quantity.
249+
- Callers never deal with negative values for a conceptually positive quantity.
249250

250251
Cons:
251-
- Introduces a sign inversion between the API and the underlying layout
252-
engine, which could confuse contributors reading the C code.
252+
253+
- Introduces a sign inversion between the API and the underlying layout engine,
254+
which could confuse contributors reading the C code.
253255
- Inconsistent with Clay's `childOffset` model, which uses negative values
254256
natively.
255-
- Clipping without scrolling (`clip: { y: 0 }`) works identically either
256-
way, but the mental model differs — 0 means "scroll position zero" vs
257-
"zero offset."
257+
- Clipping without scrolling (`clip: { y: 0 }`) works identically either way,
258+
but the mental model differs — 0 means "scroll position zero" vs "zero
259+
offset."
258260

259-
**Option B: Negative (raw offset semantics).** The caller provides the raw
260-
pixel offset, typically negative. Clayterm passes it through to the layout
261-
engine unchanged.
261+
**Option B: Negative (raw offset semantics).** The caller provides the raw pixel
262+
offset, typically negative. Clayterm passes it through to the layout engine
263+
unchanged.
262264

263265
```ts
264-
clip: { y: -20 } // "offset children by -20 pixels"
266+
clip: {
267+
y: -20;
268+
} // "offset children by -20 pixels"
265269
```
266270

267271
Pros:
272+
268273
- Direct mapping to Clay's `childOffset` — no hidden transformation.
269274
- Transparent to anyone reading the implementation.
270275
- Allows positive offsets if the caller genuinely wants to shift content
271276
downward/rightward (unusual but not impossible).
272277

273278
Cons:
279+
274280
- Unnatural for the common case — scrolling down requires negative numbers.
275-
- Easy to get the sign wrong, especially for callers unfamiliar with the
276-
layout engine internals.
281+
- Easy to get the sign wrong, especially for callers unfamiliar with the layout
282+
engine internals.
277283
- Every scroll-position calculation needs manual negation.

0 commit comments

Comments
 (0)