mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat(terminal): gentler glide start and fractional wheel accumulation
Two smoothness refinements on the local wheel path: the drain factor drops from 35% to 22% per frame, so the first frame of a notch takes a smaller step and the glide lasts longer; and local scrolling accumulates FRACTIONAL lines (_wheelScrollLinesFloat) instead of rounding every event, so a slow macOS trackpad drag no longer snaps a whole line per tiny delta (the old ±1 fallback made slow drags scroll faster than the finger). Sub-line residuals stay pending until further input crosses a whole line. Forwarded SGR ticks keep the rounded integer path. Probe: a 20-line notch now glides through 14 positions to an exact landing; the 9-check scroll matrix still passes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -451,11 +451,14 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
if (this.terminal?.buffer?.active?.type === 'alternate') return;
|
if (this.terminal?.buffer?.active?.type === 'alternate') return;
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const lines = this._wheelScrollLines(ev);
|
|
||||||
if (this._shouldForwardWheelToApp(ev)) {
|
if (this._shouldForwardWheelToApp(ev)) {
|
||||||
this._forwardScrollToApp(ev.clientX, ev.clientY, lines);
|
this._forwardScrollToApp(ev.clientX, ev.clientY, this._wheelScrollLines(ev));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Local scrolling accumulates FRACTIONAL lines: a macOS trackpad emits
|
||||||
|
// a stream of tiny pixel deltas, and rounding each one to a whole line
|
||||||
|
// (the ±1 fallback) made slow drags scroll faster than the finger.
|
||||||
|
const lines = this._wheelScrollLinesFloat(ev);
|
||||||
this._noteTerminalUserScroll(lines);
|
this._noteTerminalUserScroll(lines);
|
||||||
this._smoothScrollBy(lines);
|
this._smoothScrollBy(lines);
|
||||||
},
|
},
|
||||||
@@ -2081,11 +2084,15 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
* wheel handler owns local scrolling (xterm's own smooth scroller is
|
* wheel handler owns local scrolling (xterm's own smooth scroller is
|
||||||
* bypassed, see the listener comment), so without this every notch was an
|
* bypassed, see the listener comment), so without this every notch was an
|
||||||
* instant multi-line jump. Wheel deltas accumulate into a pending line
|
* instant multi-line jump. Wheel deltas accumulate into a pending line
|
||||||
* count and drain ~35% per animation frame (minimum one line, so it always
|
* count (fractional — see _wheelScrollLinesFloat) and drain ~22% per
|
||||||
* terminates); more notches mid-glide just deepen the pending count, which
|
* animation frame with a one-line floor, so a single notch starts with a
|
||||||
* reads as natural acceleration. Direction reversals cancel arithmetically.
|
* gentle step and glides to an exact landing; more notches mid-glide deepen
|
||||||
* The pending amount is dropped when the active session changes mid-glide —
|
* the pending count, which reads as natural acceleration. A sub-line
|
||||||
* leftover momentum must never scroll the tab the user just switched to.
|
* residual stays pending until further input pushes it past a whole line
|
||||||
|
* (that is what makes slow trackpad drags track the finger). Direction
|
||||||
|
* reversals cancel arithmetically. The pending amount is dropped when the
|
||||||
|
* active session changes mid-glide — leftover momentum must never scroll
|
||||||
|
* the tab the user just switched to.
|
||||||
*/
|
*/
|
||||||
_smoothScrollBy(lines) {
|
_smoothScrollBy(lines) {
|
||||||
if (!lines) return;
|
if (!lines) return;
|
||||||
@@ -2100,12 +2107,13 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
this._smoothScrollPending = 0;
|
this._smoothScrollPending = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const move =
|
if (Math.abs(pending) < 1) return; // sub-line residual: wait for more input
|
||||||
pending > 0 ? Math.max(1, Math.floor(pending * 0.35)) : Math.min(-1, Math.ceil(pending * 0.35));
|
const eased = pending * 0.22;
|
||||||
|
const move = pending > 0 ? Math.max(1, Math.floor(eased)) : Math.min(-1, Math.ceil(eased));
|
||||||
this._smoothScrollPending = pending - move;
|
this._smoothScrollPending = pending - move;
|
||||||
this.terminal.scrollLines(move);
|
this.terminal.scrollLines(move);
|
||||||
this._maybeLoadMoreHistoryOnScroll(move);
|
this._maybeLoadMoreHistoryOnScroll(move);
|
||||||
if (this._smoothScrollPending) this._smoothScrollFrame = requestAnimationFrame(step);
|
if (Math.abs(this._smoothScrollPending) >= 1) this._smoothScrollFrame = requestAnimationFrame(step);
|
||||||
};
|
};
|
||||||
this._smoothScrollFrame = requestAnimationFrame(step);
|
this._smoothScrollFrame = requestAnimationFrame(step);
|
||||||
},
|
},
|
||||||
@@ -2937,15 +2945,22 @@ Object.assign(CodemanApp.prototype, {
|
|||||||
// the ±1 fallback — one line per notch, versus 4-5 for Chrome's ~110px. In
|
// the ±1 fallback — one line per notch, versus 4-5 for Chrome's ~110px. In
|
||||||
// Claude mode the same value also capped the forwarded SGR report at one tick.
|
// Claude mode the same value also capped the forwarded SGR report at one tick.
|
||||||
_wheelScrollLines(ev) {
|
_wheelScrollLines(ev) {
|
||||||
|
const lines = this._wheelScrollLinesFloat(ev);
|
||||||
|
if (!lines) return 0; // pure horizontal swipe: don't fall through to -1
|
||||||
|
return Math.round(lines) || (lines > 0 ? 1 : -1);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Unrounded variant for the smooth local-scroll path, which accumulates
|
||||||
|
* sub-line fractions across events instead of forcing every tiny trackpad
|
||||||
|
* delta to a whole ±1 line. Same unit handling and Shift-axis trap. */
|
||||||
|
_wheelScrollLinesFloat(ev) {
|
||||||
const delta = ev.shiftKey && Math.abs(ev.deltaX) > Math.abs(ev.deltaY) ? ev.deltaX : ev.deltaY;
|
const delta = ev.shiftKey && Math.abs(ev.deltaX) > Math.abs(ev.deltaY) ? ev.deltaX : ev.deltaY;
|
||||||
if (!delta) return 0; // pure horizontal swipe: don't fall through to -1
|
if (!delta) return 0;
|
||||||
const lines =
|
return ev.deltaMode === 1 // DOM_DELTA_LINE (Firefox mouse wheel)
|
||||||
ev.deltaMode === 1 // DOM_DELTA_LINE (Firefox mouse wheel)
|
? delta
|
||||||
? delta
|
: ev.deltaMode === 2 // DOM_DELTA_PAGE
|
||||||
: ev.deltaMode === 2 // DOM_DELTA_PAGE
|
? delta * (this.terminal?.rows || 24)
|
||||||
? delta * (this.terminal?.rows || 24)
|
: delta / 25; // DOM_DELTA_PIXEL (Chrome/WebKit, and every trackpad)
|
||||||
: delta / 25; // DOM_DELTA_PIXEL (Chrome/WebKit, and every trackpad)
|
|
||||||
return Math.round(lines) || (delta > 0 ? 1 : -1);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_shouldForwardWheelToApp(ev) {
|
_shouldForwardWheelToApp(ev) {
|
||||||
|
|||||||
Reference in New Issue
Block a user