mirror of
https://github.com/stablyai/orca.git
synced 2026-10-17 08:03:14 +00:00
Suppress TUI trackpad momentum tails
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -258,6 +258,36 @@ describe('terminal mouse wheel multiplier', () => {
|
||||
expect(reports).toEqual([0, 0, 0, 1])
|
||||
})
|
||||
|
||||
it('suppresses a decaying trackpad-like momentum tail after active scrolling', () => {
|
||||
const state = createTerminalTuiMouseWheelDistanceState()
|
||||
|
||||
const reports = [16, 20, 24, 28, 20, 12, 6, 3].map((deltaY, index) =>
|
||||
resolveTerminalTuiMouseWheelReportCount(
|
||||
{ deltaY, deltaMode: DOM_DELTA_PIXEL, timeStamp: index * 16 },
|
||||
1,
|
||||
state,
|
||||
{ cellHeight: 16 }
|
||||
)
|
||||
)
|
||||
|
||||
expect(reports).toEqual([1, 1, 2, 2, 0, 0, 0, 0])
|
||||
})
|
||||
|
||||
it('suppresses alternating trackpad-like momentum after direction changes', () => {
|
||||
const state = createTerminalTuiMouseWheelDistanceState()
|
||||
|
||||
const reports = [16, 20, 24, -16, -20, -24, -18, -10, 6, -4].map((deltaY, index) =>
|
||||
resolveTerminalTuiMouseWheelReportCount(
|
||||
{ deltaY, deltaMode: DOM_DELTA_PIXEL, timeStamp: index * 16 },
|
||||
1,
|
||||
state,
|
||||
{ cellHeight: 16 }
|
||||
)
|
||||
)
|
||||
|
||||
expect(reports).toEqual([1, 1, 2, 1, 1, 2, 0, 0, 0, 0])
|
||||
})
|
||||
|
||||
it('resets pending fractional distance when the user changes direction', () => {
|
||||
const state = createTerminalTuiMouseWheelDistanceState()
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ const TUI_WHEEL_BURST_MAX_INTERVAL_MS = 45
|
||||
const TUI_WHEEL_BURST_MAX_BONUS_ROWS = 3
|
||||
const TUI_WHEEL_BURST_RAMP_EVENTS = 4
|
||||
const TUI_WHEEL_MOMENTUM_TAIL_DECAY_RATIO = 0.85
|
||||
const TUI_TRACKPAD_MOMENTUM_TAIL_DECAY_RATIO = 0.85
|
||||
const TUI_TRACKPAD_TAIL_IDLE_MS = 120
|
||||
const TUI_TRACKPAD_TAIL_REENGAGE_DISTANCE_ROWS = 1
|
||||
const TUI_TRACKPAD_TAIL_REENGAGE_RATIO = 1.5
|
||||
const TUI_WHEEL_COMPRESSED_MAX_DISTANCE_ROWS_PER_EVENT = 6
|
||||
const TUI_WHEEL_BURST_MAX_DISTANCE_ROWS_PER_EVENT = 9
|
||||
|
||||
@@ -40,6 +44,10 @@ export type TerminalTuiMouseWheelDistanceState = {
|
||||
lastInputAt: number | null
|
||||
pendingDirection: -1 | 0 | 1
|
||||
pendingRows: number
|
||||
trackpadLastDirection: -1 | 0 | 1
|
||||
trackpadLastDistanceRows: number | null
|
||||
trackpadLastInputAt: number | null
|
||||
trackpadTailActive: boolean
|
||||
}
|
||||
|
||||
export function createTerminalTuiMouseWheelDistanceState(): TerminalTuiMouseWheelDistanceState {
|
||||
@@ -48,7 +56,11 @@ export function createTerminalTuiMouseWheelDistanceState(): TerminalTuiMouseWhee
|
||||
lastDistanceRows: null,
|
||||
lastInputAt: null,
|
||||
pendingDirection: 0,
|
||||
pendingRows: 0
|
||||
pendingRows: 0,
|
||||
trackpadLastDirection: 0,
|
||||
trackpadLastDistanceRows: null,
|
||||
trackpadLastInputAt: null,
|
||||
trackpadTailActive: false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +104,13 @@ function canBurstBoostWheelEvent(event: TerminalTuiWheelEventInput): boolean {
|
||||
return hasDiscreteLegacyWheelDelta(event)
|
||||
}
|
||||
|
||||
function isTrackpadLikePixelWheelEvent(event: TerminalTuiWheelEventInput): boolean {
|
||||
return (
|
||||
(event.deltaMode ?? DOM_DELTA_PIXEL) === DOM_DELTA_PIXEL &&
|
||||
!isDiscreteTerminalTuiWheelEvent(event)
|
||||
)
|
||||
}
|
||||
|
||||
function wheelInputTime(event: TerminalTuiWheelEventInput): number | null {
|
||||
if (typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)) {
|
||||
return event.timeStamp
|
||||
@@ -99,6 +118,13 @@ function wheelInputTime(event: TerminalTuiWheelEventInput): number | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function resetTrackpadMomentumState(state: TerminalTuiMouseWheelDistanceState): void {
|
||||
state.trackpadLastDirection = 0
|
||||
state.trackpadLastDistanceRows = null
|
||||
state.trackpadLastInputAt = null
|
||||
state.trackpadTailActive = false
|
||||
}
|
||||
|
||||
function normalizeCellHeight(cellHeight: number | undefined): number {
|
||||
if (typeof cellHeight === 'number' && Number.isFinite(cellHeight) && cellHeight > 0) {
|
||||
return cellHeight
|
||||
@@ -184,6 +210,60 @@ function resolveBurstWheelDistanceRows(
|
||||
return TUI_WHEEL_BURST_MAX_BONUS_ROWS * cadence * (state.fastStreak / TUI_WHEEL_BURST_RAMP_EVENTS)
|
||||
}
|
||||
|
||||
function shouldSuppressTrackpadMomentumTail(
|
||||
event: TerminalTuiWheelEventInput,
|
||||
state: TerminalTuiMouseWheelDistanceState,
|
||||
distanceRows: number
|
||||
): boolean {
|
||||
if (!isTrackpadLikePixelWheelEvent(event)) {
|
||||
resetTrackpadMomentumState(state)
|
||||
return false
|
||||
}
|
||||
|
||||
const currentInputAt = wheelInputTime(event)
|
||||
const elapsedMs =
|
||||
currentInputAt === null || state.trackpadLastInputAt === null
|
||||
? null
|
||||
: currentInputAt - state.trackpadLastInputAt
|
||||
if (elapsedMs !== null && elapsedMs > TUI_TRACKPAD_TAIL_IDLE_MS) {
|
||||
resetTrackpadMomentumState(state)
|
||||
}
|
||||
|
||||
const direction = resolveTerminalWheelDirection(event)
|
||||
const lastDistanceRows = state.trackpadLastDistanceRows
|
||||
const isDecayingSameDirection =
|
||||
state.trackpadLastDirection === direction &&
|
||||
lastDistanceRows !== null &&
|
||||
distanceRows < lastDistanceRows * TUI_TRACKPAD_MOMENTUM_TAIL_DECAY_RATIO
|
||||
const isStrongNewGesture =
|
||||
lastDistanceRows !== null &&
|
||||
distanceRows >= TUI_TRACKPAD_TAIL_REENGAGE_DISTANCE_ROWS &&
|
||||
distanceRows > lastDistanceRows * TUI_TRACKPAD_TAIL_REENGAGE_RATIO
|
||||
|
||||
if (state.trackpadTailActive && !isStrongNewGesture) {
|
||||
state.trackpadLastDirection = direction
|
||||
state.trackpadLastDistanceRows = distanceRows
|
||||
state.trackpadLastInputAt = currentInputAt
|
||||
state.pendingRows = 0
|
||||
return true
|
||||
}
|
||||
|
||||
if (isDecayingSameDirection) {
|
||||
state.trackpadTailActive = true
|
||||
state.trackpadLastDirection = direction
|
||||
state.trackpadLastDistanceRows = distanceRows
|
||||
state.trackpadLastInputAt = currentInputAt
|
||||
state.pendingRows = 0
|
||||
return true
|
||||
}
|
||||
|
||||
state.trackpadTailActive = false
|
||||
state.trackpadLastDirection = direction
|
||||
state.trackpadLastDistanceRows = distanceRows
|
||||
state.trackpadLastInputAt = currentInputAt
|
||||
return false
|
||||
}
|
||||
|
||||
export function normalizeTerminalTuiMouseWheelMultiplier(value: number | undefined): number {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
||||
return TERMINAL_TUI_MOUSE_WHEEL_MULTIPLIER
|
||||
@@ -212,6 +292,10 @@ export function resolveTerminalTuiMouseWheelReportCount(
|
||||
state.pendingDirection = direction
|
||||
|
||||
const distanceRows = resolveWheelDistanceRows(event, metrics)
|
||||
if (shouldSuppressTrackpadMomentumTail(event, state, distanceRows)) {
|
||||
return 0
|
||||
}
|
||||
|
||||
const rows =
|
||||
Math.min(
|
||||
TUI_WHEEL_BURST_MAX_DISTANCE_ROWS_PER_EVENT,
|
||||
|
||||
Reference in New Issue
Block a user