Centered horizontal reveal (follow-up to #33, stacked on #34/#35/#37)#39
Draft
andrewtheart wants to merge 4 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.
Introduce word wrap on the previously no-wrap-only control. A document line can span multiple visual rows, so scrolling, caret, selection, click hit-testing and arrow navigation work in visual-row space via a WrapRowMetrics prefix-sum model (document line <-> visual row, with cheap incremental updates) and WrapGeometry pointer math, both pure and unit-tested. Excludes very-long-line wrapped-line virtualization (row count falls back to an estimate above 100k chars). Behavior is unchanged when WordWrap is false. Step 3 of FrozenAssassine#33.
New public ScrollIntoViewHorizontallyCentered() centers the current cursor column in the viewport instead of pinning it to an edge (what jump-to-match wants), and the existing edge-reveal is refactored onto a shared slice-aware GetCurrentCursorPixelPositionInLine() so it is correct on horizontally-virtualized long lines. Follow-up to 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.
Follow-up to #33: centered horizontal reveal
Adds a public
ScrollIntoViewHorizontallyCentered()that horizontally centers the current cursor columnin the viewport, and fixes the existing edge-reveal to account for horizontal virtualization.
Why
ScrollIntoViewHorizontalreveals a column at the nearest edge — so jumping to a search match far to theright lands it pinned against the right edge, which is awkward to read. Centering puts the match in the middle
of the visible width, which is what "jump to match" wants.
While adding it I also had the two share a single slice-aware
GetCurrentCursorPixelPositionInLine()helper:the old edge-reveal computed the column straight from
CurrentLineTextLayoutand so mis-placed the reveal ona horizontally-virtualized long line (#35), where the layout is a moving slice. Now both agree.
Changes
Core/ScrollManager.cs— newScrollCursorIntoViewHorizontallyCentered(...);ScrollIntoViewHorizontalrefactored onto the shared
GetCurrentCursorPixelPositionInLine()(slice-aware). Word-wrap pins horizontalat 0 (no horizontal scroll), matching the rest of the wrap path.
Core/CoreTextControlBox.xaml.cs/TextControlBox.cs— publicScrollIntoViewHorizontallyCentered().Core/Renderer/TextRenderer.cs—CachedCharWidthmadeinternalso the shared helper can estimate thecolumn when the cursor is outside the virtualized slice.
The resolved column is recorded in
OldHorizontalScrollValueso the per-drawEnsureHorizontalScrollBounds→ScrollIntoViewHorizontalpass keeps the centered offset instead ofre-revealing at the edge.
Verification
This is scroll/interaction logic over the tested offset seam, so there's no new headless-testable math.
Library + test project build clean (x64).