Fix mouse-wheel zoom misclassification in camera controls#926
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes mouse-wheel zoom being misclassified as trackpad input in the camera wheel handler by removing delta-based “device classification” heuristics and instead using a modifier-key-only model (with special handling for macOS synthetic-Ctrl pinch).
Changes:
- Removed burst/stateful wheel-vs-trackpad classification heuristics and made bare scroll always zoom in orbit mode.
- Added physical Ctrl tracking (keydown/keyup + reset on blur/destroy) to distinguish macOS synthetic-Ctrl pinch from real Ctrl+scroll.
- Updated fly-mode wheel behavior to map physical Ctrl+scroll to look and Shift+scroll to pan.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the stateful, burst-based device classifier in the camera controller's wheel handler with a modifier-key-only model, mirroring the approach already shipped in supersplat-viewer. The previous classifier tried to distinguish a physical mouse wheel from a trackpad two-finger swipe using per-event heuristics (
wheelDelta % 120,deltaMode, fractional/diagonal deltas), but hi-res mice emit smooth deltas that are indistinguishable from a trackpad. On the reporter's Logitech G502 X (Windows 11), a bare wheel was misclassified as a trackpad ~95% of the time and fell through to theorbitbranch instead of zooming. The same misfire was reported on Windows Edge.Root cause
A bare wheel event and a bare trackpad swipe carry no reliable signal that tells them apart — every delta-based heuristic misfires on some hardware or in momentum tails. The old code's
elsefallback wasorbit(deltaX, deltaY), so any wheel that failed the "is this a mouse?" check orbited.Approach
The one trackpad signal the platform reliably provides is the macOS/Magic-Mouse pinch: a wheel event with
ctrlKeyset while the physical Ctrl key is not held (synthetic Ctrl). So we stop classifying the device entirely and drive everything from modifier keys, treating a bare scroll as zoom regardless of input device.To tell a synthetic-Ctrl pinch from a user physically holding Ctrl while scrolling, we track the physical Ctrl key with
keydown/keyuplisteners onwindow(cleared on blur and on destroy). These are registered directly rather than through thewraphelper, so Ctrl tracking does not firecamera.controlleron every keystroke.Input mapping: old vs new
Orbit mode
Fly mode
The Shift-remap workaround (reading
deltaXwhen macOS maps a Shift+wheel todeltaY === 0) is preserved.Trade-off
A bare macOS trackpad two-finger swipe now zooms instead of orbiting; trackpad orbit moves to Ctrl + swipe. This is unavoidable — a bare wheel and a bare swipe are physically indistinguishable, so fixing mouse-wheel zoom necessarily changes bare-swipe behavior. This matches the resolution already adopted in supersplat-viewer and the tension noted by the maintainer in the issue thread.
Test plan
Verified at runtime by dispatching synthetic wheel events against the live editor canvas and reading camera state before/after:
tsc --noEmitandeslintclean, no console errors