Repository navigation
Fix #4173, #4219: scroll the editor by lines and pages, not fixed pixels - #4252
christophwille wants to merge 4 commits into
Conversation
AvaloniaEdit's TextView reports a fixed 10x50 px ScrollSize and 10x100 px PageScrollSize through ILogicalScrollable, and Avalonia's ScrollViewer derives the wheel step, the scrollbar arrow step and the track-click step from exactly those two values. Shift+wheel therefore crawled 10 px per notch and a track click moved barely more than an arrow click, while the WPF AvalonEdit view scrolled WheelScrollLines lines per notch and one viewport per page. ScrollViewer's SmallChange/LargeChange are read-only, so the fix lives at the framework's extension point: a TextArea subclass re-implements ILogicalScrollable with text-metric based sizes and the editor is constructed with it. The subclass becomes redundant once AvaloniaEdit derives the sizes from its own metrics upstream. Assisted-by: Claude:claude-fable-5-1:Claude Code
Cross-platform follow-up: macOS, GNOME, KDEThe first commit fixes the increments against Windows conventions. This comment records what the other desktops do, how Avalonia's backends feed us, and which follow-up commits on this branch result from it. What Avalonia hands us
Avalonia 12.0 ships no Wayland backend, so every Linux session runs through X11 or XWayland. 12.1 adds an opt-in experimental one that divides touchpad pixels by 10, like GTK and Qt. Avalonia's non-logical default of 50 px per notch, and AvaloniaEdit's old Native conventionsKDE Plasma / Qt. GNOME / GTK. No lines-per-notch setting exists (mutter MR adding a scroll speed has been open since 2019). GtkScrolledWindow scrolls macOS / AppKit. Apple's rule for a classic wheel is " Follow-up commits on this branch
Out of ILSpy's hands
Assisted-by: Claude:claude-fable-5-1:Claude Code |
…ext area Plasma keeps its "wheel scroll lines" value in kdeglobals under [KDE], and both plasma-integration and Qt's built-in KDE theme read it (default 3), so Qt editors on KDE move that many lines per notch. Avalonia exposes no desktop settings, so the editor reads the file itself; GNOME has no such setting and stays at 3, and KWin's scroll-speed factor already arrives inside the wheel delta, so nothing else is applied on Linux. With a second desktop to query, the OS-settings code (the Windows SystemParametersInfo call and this lookup) moves into SystemScrollSettings, leaving DecompilerTextArea as the pure scroll-metric adapter it is meant to be. Assisted-by: Claude:claude-fable-5-1:Claude Code
Avalonia.Native scales macOS input before it reaches the controls: trackpad deltas are divided by 50 px and mouse-wheel deltas by 5 AppKit line units, and the resulting wheel events carry no precision flag, so a single step size has to serve both devices. Apple's rule for precise deltas is to scroll by the returned amount, which only a 50 px step honors at every font size and zoom level; a font-relative step made trackpads drift with the zoom. Mice end up at the speed every other Avalonia application has on macOS, which is slower than a Cocoa text view's one line per unit, but following Apple's mouse rule instead would make trackpads 1.7x too fast. Resolving that needs a precision flag from Avalonia. Shift+wheel, which macOS swaps to the horizontal axis itself, moves 50 px instead of 10. Assisted-by: Claude:claude-fable-5-1:Claude Code
A page scroll on Windows replaces the whole viewport, as WPF did. AppKit keeps NSScrollView.pageScroll points of the previous page visible and the macOS HIG suggests one line of overlap, and GTK's page_increment is 0.9 of the page, so readers on those desktops expect the last visible line to still be there after a track click. Windows keeps the full viewport. Assisted-by: Claude:claude-fable-5-1:Claude Code
Fixes #4173, fixes #4219.
Symptoms
Thumb dragging is proportional and was never affected.
Root cause
Both come from one spot in AvaloniaEdit 12.0.0,
AvaloniaEdit.Rendering.TextView:Avalonia's
ScrollViewerderives every increment from these two values (verified against the shipped 12.0 binaries, not just master):ScrollContentPresenter.OnPointerWheelChangedscrollsdelta * ScrollSize; with Shift held it swaps the axes, so a Shift+wheel notch movedScrollSize.Width= 10 px.ScrollViewer.CalculatedPropertiesChangedcopiesScrollSizeintoSmallChangeandPageScrollSizeintoLargeChange. Both are read-onlyDirectPropertys.ScrollBar.AttachToScrollViewerbinds its ownSmallChange/LargeChangeto those at Template priority, so a track click moved 100 px vertically and 10 px horizontally, against 50 px / 10 px for an arrow click.For comparison, WPF AvalonEdit's
IScrollInfoimplementation scrolledSystemParameters.WheelScrollLines * DefaultLineHeightper wheel notch,WheelScrollLines * WideSpaceWidthhorizontally, and the viewport size forPageUp/PageDown/PageLeft/PageRight. ILSpy 10'sZoomScrollVieweradded nothing beyond the Ctrl+wheel zoom, which the Avalonia port already has. The Avalonia docs offer no app-level knob here;ILogicalScrollableis the designated extension point.Fix
TextAreais the ScrollViewer's content and implementsILogicalScrollablepurely by forwarding to itsTextView, andTextEditorhas a protectedTextEditor(TextArea)constructor. So:DecompilerTextArea : TextArea, ILogicalScrollablere-implements the interface. Every member still forwards to the TextView except the two step sizes:ScrollSize: wheel-lines xDefaultLineHeight, used for both axes (a Shift+wheel notch moves the same distance as a vertical notch; decided over the WPF 3-character step because decompiled lines are long).PageScrollSize: the TextView's viewport.SystemParametersInfo(SPI_GETWHEELSCROLLLINES)on Windows (default 3), and are 3 on Linux/macOS. Windows' "one screen at a time" wheel mode (WHEEL_PAGESCROLL) maps to a viewport-sized step.StyleKeyOverride => typeof(TextArea)so the subclass keeps AvaloniaEdit's TextArea template (same trapDecompilerTextEditoralready documents).DecompilerTextEditoris constructed with it, so the main text view and the metadata row-detail editors both pick it up.No custom input handling and no scrollbar styling: the ScrollViewer, ScrollBar and wheel handler keep doing their jobs with correct inputs. Side effect: arrow-button clicks now move wheel-lines lines (same distance as before at the default font, now font- and zoom-relative).
Per-desktop follow-ups (one commit each, see the research comment below)
SystemScrollSettings(WindowsSystemParametersInfo, plus a lookup of[KDE] WheelScrollLinesinkdeglobals, which Qt applications honor; default 3). GNOME has no such setting; KWin's scroll-speed factor already arrives inside the wheel delta.pageScroll, GTK's 0.9 page), the full viewport on Windows as in WPF.The headless tests pin the platform through
SystemScrollSettings.IsWindows/IsMacOS, so the same assertions run on every CI runner.Tests
ILSpy.Tests/Editor/EditorScrollIncrementTests.cs(headless, red on the old 10/50/100 px values before the fix):ScrollViewer.LargeChangefollows it and exceedsSmallChange;Offset.Xby exactly one wheel step and leavesOffset.Yalone;ScrollBar.PageDown()moves one viewport;LargeChangefollows;kdeglobalsparser findsWheelScrollLinesonly in the[KDE]group and tolerates CRLF, missing and malformed values.The tests use a 40 px font so line-based values cannot coincide with the old pixel constants.
Preferred long-term solution: fix it in AvaloniaEdit
This subclass is a workaround for AvaloniaEdit reporting constants where WPF AvalonEdit reported metrics. The proper fix is an upstream PR against
src/AvaloniaEdit/Rendering/TextView.cs, after whichDecompilerTextAreacan be deleted andDecompilerTextEditorgoes back to the defaultTextArea:Points the upstream change has to settle:
WheelScrollLinescomes from. Avalonia delivers raw wheel notches and does not expose the OS "lines to scroll" setting, so AvaloniaEdit needs either a constant 3 (the Windows default WPF users effectively got) or a settable property, e.g.TextEditorOptions.WheelScrollLines(default 3), that hosts can feed fromSystemParametersInfoor leave alone.WheelScrollLines * WideSpaceWidth(3 characters). This PR uses the vertical distance instead; upstream should pick one and document it. Either is far better than 10 px.ScrollSizefeeds both the wheel notch andScrollBar.SmallChange, so arrow clicks moveWheelScrollLineslines rather than WPF's one line. Doing better would need Avalonia to split the two, which is out of AvaloniaEdit's hands._scrollViewportis zero before the first measure;ScrollViewerre-readsPageScrollSizeinCalculatedPropertiesChangedon every extent/viewport/offset change, so no extra invalidation is needed.DefaultLineHeightrecomputes lazily when the font changes.TextEditorin aWindowandHeadlessWindowExtensions.MouseWheel(point, delta, RawInputModifiers.Shift).Limitations
kdeglobalstakes effect after an ILSpy restart; WPF trackedWM_SETTINGCHANGEviaSystemParameters.page_size^(2/3)px, somewhat more on tall viewports). Touchpads on X11/XWayland scroll at the same speed as Qt applications.ScrollSizedual use).Assisted-by: Claude:claude-fable-5-1:Claude Code