wheel.go RawHistoryFolder
1package webterm
2
3// Wheel quantization converts raw browser wheel deltas into discrete
4// terminal wheel reports. Browsers emit wheel events as a scroll distance,
5// not as physical notches: high-resolution wheels and trackpads produce a
6// burst of micro-events per notch, and forwarding one SGR report per event
7// scrolls dozens of lines per notch. The accumulator below merges fractions
8// until they form whole notches, so one notch yields one report, like an
9// ordinary terminal. The remainder is kept per terminal and bounded.
10const (
11 // wheelPixelsPerNotch matches the Chromium convention of 100 CSS pixels
12 // per wheel notch in pixel (DOM_DELTA_PIXEL) mode.
13 wheelPixelsPerNotch = 100
14 // wheelLinesPerNotch matches the browser convention of 3 lines per wheel
15 // notch in line (DOM_DELTA_LINE) mode.
16 wheelLinesPerNotch = 3
17 // wheelNotchesPerPage converts page (DOM_DELTA_PAGE) mode into notches.
18 wheelNotchesPerPage = 10
19 // wheelMaxNotchesPerEvent caps the reports emitted for a single event.
20 wheelMaxNotchesPerEvent = 10
21 // wheelMaxAccumulated caps the retained remainder in notch units, so a
22 // violent momentum gesture cannot bank phantom scrolling.
23 wheelMaxAccumulated = 10.0
24 // wheelShiftMultiplier is the fast-scroll factor while Shift is held.
25 wheelShiftMultiplier = 10
26)
27
28// consumeWheel converts one wheel delta into whole-notch SGR reports. It
29// returns the report count and the SGR button (64 for up, 65 for down).
30// Sub-notch fractions accumulate on the terminal; a zero count means the
31// delta was banked for later. Shift multiplies the notch count for
32// predictable fast scrolling.
33func (t *Terminal) consumeWheel(deltaY float64, deltaMode int, shift bool) (int, int) {
34 t.mu.Lock()
35 defer t.mu.Unlock()
36 t.wheelAcc += wheelNotches(deltaY, deltaMode)
37 if t.wheelAcc > wheelMaxAccumulated {
38 t.wheelAcc = wheelMaxAccumulated
39 } else if t.wheelAcc < -wheelMaxAccumulated {
40 t.wheelAcc = -wheelMaxAccumulated
41 }
42 notches := int(t.wheelAcc)
43 t.wheelAcc -= float64(notches)
44 if t.wheelAcc < 1e-9 && t.wheelAcc > -1e-9 {
45 t.wheelAcc = 0
46 }
47 if notches == 0 {
48 return 0, 0
49 }
50 magnitude := notches
51 if magnitude < 0 {
52 magnitude = -magnitude
53 }
54 if magnitude > wheelMaxNotchesPerEvent {
55 magnitude = wheelMaxNotchesPerEvent
56 if notches < 0 {
57 notches = -magnitude
58 } else {
59 notches = magnitude
60 }
61 }
62 if shift {
63 magnitude *= wheelShiftMultiplier
64 if magnitude > wheelMaxNotchesPerEvent*wheelShiftMultiplier {
65 magnitude = wheelMaxNotchesPerEvent * wheelShiftMultiplier
66 }
67 }
68 button := 65
69 if notches < 0 {
70 button = 64
71 }
72 return magnitude, button
73}
74
75// resetWheel drops the banked wheel remainder. It runs when wheel input is
76// ignored (mouse tracking off, out-of-bounds cell) so a stale remainder
77// cannot leak into a later, unrelated scroll gesture.
78func (t *Terminal) resetWheel() {
79 t.mu.Lock()
80 defer t.mu.Unlock()
81 t.wheelAcc = 0
82}
83
84// wheelNotches converts a raw wheel delta into notch units. Unknown delta
85// modes contribute nothing rather than guessing a scale.
86func wheelNotches(deltaY float64, deltaMode int) float64 {
87 switch deltaMode {
88 case 0:
89 return deltaY / wheelPixelsPerNotch
90 case 1:
91 return deltaY / wheelLinesPerNotch
92 case 2:
93 return deltaY * wheelNotchesPerPage
94 default:
95 return 0
96 }
97}