Diagonal (two-axis) precision-touchpad panning — step 4 of #33 (stacked on #34, #35)#36
Draft
andrewtheart wants to merge 3 commits into
Draft
Conversation
Introduce a small IScrollOffsetSource abstraction so the editor scroll position is expressed in pixels rather than legacy scrollbar units (SingleLineHeight / DefaultVerticalScrollSensitivity). ScrollManager routes every offset read/write through it; the ScrollBarOffsetSource adapter backs it with the existing two ScrollBar primitives and does the pixel<->scrollbar-unit conversion internally (ScrollOffsetMath). Behavior is unchanged. Foundation for word-wrap scrolling, long-line horizontal virtualization and diagonal 2-axis touchpad scrolling (FrozenAssassine#33). Adds ScrollOffsetMathTests.
On files with pathologically long lines (minified JS/CSS/JSON, JSONL logs), the renderer joined every visible line into one multi-megabyte string and laid it out on every frame, causing horizontal-scroll stutter. TextRenderer.Draw now lays out only a sliced window of each visible line when a visible line exceeds 50k chars; ordinary files keep the untouched render path. The uniform slice window is re-aligned to document coordinates via HorizontalOffset + a per-line prefix sum, and the caret, click hit-testing and selection map through GetRenderedCharacterIndexForDocumentCharacter / GetDocumentCharacterIndexFromRenderedIndex / GetRenderedLayoutIndexForDocument (all no-ops when inactive). The window/index arithmetic is extracted into a WinUI-free HorizontalSliceMath with unit tests. Step 2 of FrozenAssassine#33.
A composition InteractionTracker + VisualInteractionSource on the selection canvas reads the raw 2-D precision-touchpad manipulation delta and drives the pixel IScrollOffsetSource, enabling free diagonal panning that the single-axis wheel cannot express. Configured CapableTouchpadOnly so mouse wheel / Shift+wheel / Ctrl+wheel zoom / click / drag-select are untouched. Follows programmatic scrolls immediately via the offset source's ViewChanged (with a re-entrancy guard on its own writes), and is fully torn down in Unload so no timer/composition objects leak. Step 4 of FrozenAssassine#33.
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.
Step 4 of the editor scroll/render series (#33)
The headline feature: diagonal (two-axis) precision-touchpad panning.
The problem
The editor scrolls via two
ScrollBarprimitives fed by the mouse wheel, which is single-axis: you can scrollvertically or (with Shift) horizontally, but never both at once. On a precision touchpad, a diagonal
two-finger swipe should pan the document freely in both axes at the same time — as it does in every modern
scrolling surface. The wheel message stream physically can't express that 2-D delta.
The approach
A composition
InteractionTracker+VisualInteractionSourceon the selection canvas reads the raw 2-Dprecision-touchpad manipulation delta and drives the pixel
IScrollOffsetSource(#34). The twoScrollBarprimitives stay in sync automatically because the tracker writes through the same offset source they back.
The source is
VisualInteractionSourceRedirectionMode.CapableTouchpadOnly, so it captures only theprecision-touchpad pan:
PointerActionsManager.PointerWheelActionuntouched.ManipulationRedirectionMode— noTryRedirectForManipulationcall is needed (that's only for touch/pen).
A 50 ms timer keeps the tracker's
MaxPositionsized to the content extent, and — critically — the trackerfollows programmatic scrolls immediately: it subscribes to the offset source's
ViewChanged, so atouchpad pan started right after a caret-follow / page-key / go-to-line / find-reveal / wheel scroll begins
from the current position instead of snapping back to a stale one. A re-entrancy flag on the tracker's own
writes prevents that
ViewChangedfrom feeding back into a redundant reposition.Lifecycle is fully cleaned up:
SetupDiagonalScrollis wired from the control'sLoaded, andTeardownDiagonalScroll(fromUnload) stops the timer, unsubscribes, and disposes the composition objectsso an editor instance never leaks a forever-running
DispatcherQueueTimer. Everything is best-effort — acomposition failure just leaves the editor on wheel-only scrolling.
Changes
Core/CoreTextControlBox.DiagonalScroll.cs— theIInteractionTrackerOwnerpartial.Core/CoreTextControlBox.xaml.cs—Loaded += SetupDiagonalScrollandTeardownDiagonalScroll()inUnload.Verification & validation
TextControlBox(library) and the test project build clean (x64). This is pure interaction glue over thetested offset seam (#34's
ScrollOffsetMathcovers the offset↔scrollbar conversions), so there's no newheadless-testable math. It does need a physical precision trackpad to validate — it can't be exercised
over Remote Desktop / TeamViewer (those inject
WM_MOUSEWHEEL, a single-axis wheel, whichCapableTouchpadOnlyignores, so the tracker never engages). Happy to iterate on feel (e.g. enabling inertia,driving the scrollbar thumbs as indicators) once you've tried it.