Add option to reverse scroll direction on middle-click vector scrolling - #2
Merged
Merged
Conversation
The autoscroll delta math had no test coverage because it lived in the WinMice executable target. Move it into a library target alongside SwipeGesturePoster so the direction mapping can be tested directly. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Requested in #1: dragging down scrolled the opposite way to what some users expect. Both axes are exposed independently and default to off, so existing installs are unaffected. Co-authored-by: Cursor <cursoragent@cursor.com>
anibalribeiro
force-pushed
the
feature/reverse-scroll-direction
branch
from
September 3, 2026 21:49
c2457b7 to
3983db7
Compare
Keep the wheel2 baseline inversion separate from the user's reverse flag so that "reverse means -1" reads uniformly across both axes, unwrap the deltas in the cross-axis tests so they cannot pass vacuously, cover both flags at once, and narrow two engine knobs that no consumer reads. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Closes #1.
Summary
Adds two independent settings that reverse the direction of middle-click vector (autoscroll) scrolling, separately for the vertical and horizontal axes. Both default to
off, so existing installs keep today's behavior.Requested in #1: an option so that middle-click-dragging down scrolls the page down (and vice versa), rather than the default mapping.
Changes
ScrollEnginemoved to its own target — the autoscroll delta math had no test coverage because it lived in theWinMiceexecutable target. It now sits in a library target alongsideSwipeGesturePoster, with aScrollEngineTeststarget. This is a mechanical move with no behavior change, kept as its own commit.ScrollEngine— newreverseVertical/reverseHorizontalflags (defaultfalse) that flip the sign of the wheel deltas intick(offset:). The sign is applied before the sub-pixelremainderis added, so the carryover stays in the same sign convention as the deltas it accumulates toward. Flipping the sum instead would alternate the carryover's sign each tick and stall slow drags at zero — there's a test for exactly that.AppSettings— new persistedreverseScrollDirectionVertical/reverseScrollDirectionHorizontalpreferences (defaultfalse), registered inKey.allso Restore Defaults clears them.main.swift—applySettings()copies both flags onto the engine live, so toggling takes effect immediately without a restart, and even mid-scroll (same pattern asscroll.speed).Behavior
Both toggles off leaves today's mapping untouched. Turning on Reverse vertical makes dragging down scroll up; Reverse horizontal makes dragging right scroll left. Each axis is independent, and enabling one provably does not disturb the other. Choices persist across launches and are cleared by Restore Defaults.
Tests
ScrollEngineTestscovers 11 cases: the four baseline directions and the dead zone (characterizing pre-existing behavior), each reverse flag's effect, that each flag leaves the other axis byte-identical, both flags at once, and the reversed sub-pixel carryover on slow drags.Each new test was watched failing before the code that satisfies it existed. The two flag tests failed on their assertions against a stubbed no-op property, and the carryover test was verified by mutating the implementation to the plausible buggy variant, where the slow drag stalled at 0 while the other tests still passed.
Deliberate non-fix: toggling mid-scroll
If a flag is flipped while a fractional remainder is pending, the carryover briefly opposes the new direction. This is left alone on purpose. Because
|remainder| < 1always, it can never drag a truncated value across zero into the opposite sign, so the worst case is a single ~16 ms tick of sub-pixel lag. Resetting the remainder inapplySettings()would also clear it on unrelated writes such as the speed slider, and detecting a genuine flag change is more machinery than a sub-pixel justifies.Verification
swift test— 16 tests, 0 failures.swift build— clean under-warnings-as-errors../scripts/build-app.sh— bundles and signsdist/WinMice.app.Reviewed by two independent reviewers. Bugbot found no bugs. A senior-reviewer pass independently confirmed the sign-flip placement and both axes' wheel conventions against the y-up coordinate system, and mutation-tested the engine with five injected bugs, all of which the suite caught. Its minor feedback is applied in the final commit: the wheel2 baseline inversion is now written separately from the user flag so that "reverse means
-1" reads uniformly across both axes, the cross-axis tests unwrap their deltas so they cannot pass vacuously if both sides gonil, both flags are covered together, and two engine tuning knobs that no consumer reads were narrowed back frompublic.Manual verification still recommended
The toggles' appearance in the settings window and the end-to-end feel of reversed scrolling were not exercised here, since both need a GUI session with accessibility permissions granted. A quick check of both toggles against a scrollable view before merge is worthwhile, since the requester's underlying complaint is about feel rather than sign convention.
Made with Cursor