This document captures design and implementation notes for ScrollViewer.
For end-user usage and examples, see ScrollViewer.
HorizontalOffset/VerticalOffset)IScrollable / ScrollModel) without fighting the content’s own layoutScrollViewer : Visual (sealed)ScrollViewer() defaults to Focusable = true and HorizontalAlignment/VerticalAlignment = Align.Stretch.Visual? content, bool focusable, or a Func<Visual?> content factory.Content : Visual?
PrepareChildren (it does not auto-attach directly to the ScrollViewer visual tree).HorizontalScrollEnabled : bool
false, horizontal bar is hidden and HorizontalOffset is forced to 0.VerticalScrollEnabled : bool
false, vertical bar is hidden and VerticalOffset is forced to 0.HorizontalOffset : int, VerticalOffset : int
ScrollModel.ScrollModel, offsets are kept in sync with it.ViewportWidth / ViewportHeight report the internal content host viewport size (excluding scroll bars).ContentScrollable exposes the IScrollable interface when the content implements it.ScrollViewer attaches four internal children:
ContentViewportHost: hosts and clips/translates the content by arranging it at (x - HorizontalOffset, y - VerticalOffset).VScrollBar and HScrollBar: internal scroll bars (non-focusable) whose style is bridged from ScrollViewerStyle.ScrollCornerVisual: fills the bottom-right corner when both bars are visible.ScrollViewer supports two modes depending on whether the content implements IScrollable:
ScrollModel (preferred for rich editors)ScrollModel.ViewportWidth/ViewportHeight and typically updates them from its own Arrange.ScrollViewer does not call ScrollModel.SetViewport(...) in this mode (to avoid oscillations and “caret pinning” issues).Extent* vs content-owned Viewport*.ScrollViewer keeps its HorizontalOffset/VerticalOffset synchronized with the model (even without a TerminalApp, e.g. unit tests).ScrollViewer measures content with “infinite” constraints on the scrolling axes:
ScrollViewer owns the offsets and passes them into ContentViewportHost.UpdateLayout(...).PrepareChildren:
Content to the internal host and updates the cached IScrollable / ScrollModelHorizontalOffsetFromPrepare/VerticalOffsetFromPrepare)The snapshot avoids reading and then writing the same bindable value during Arrange within the same tracking context.
ScrollViewer measures content with the incoming constraints (content is responsible for clamping)._extentHints (the content SizeHints)_contentWidth / _contentHeight from hints.NaturalSizeHints.Flex(...) with:
min = (1,1) when content existsgrowX/growY when aligned Align.StretchArrange computes:
thickness = max(1, ScrollViewerStyle.ScrollBarThickness)_showVerticalBar/_showHorizontalBar to avoid oscillation and runs up to 3 passesextent - viewportMinimum=0, Maximum=maxOffset, ViewportSize=viewport)ScrollViewerStyle is bridged to ScrollBarStyle for the internal scroll bars (thickness, track style, thumb style)ScrollViewer itself does not paint content directly; rendering is performed by its internal visuals:
ContentViewportHost renders the content subtreeScrollCornerVisual fills the corner using Theme.ScrollBars.Track glyph and the resolved track styleWhen scroll is enabled, OnKeyDown supports:
Up/Down, PageUp/PageDown, Home/EndLeft/RightOffsets are clamped to valid ranges derived from extent and viewport sizes (content model or internal cached values depending on scrolling mode).
Shift scrolls horizontally.ScrollViewerStyle defines:
ScrollBarThicknessTrackStyle and ThumbStyleResolveTrackStyle, ResolveThumbStyle)The internal scroll bars receive a bridged ScrollBarStyle during Arrange so changes to thickness/styling apply without requiring a custom scroll bar instance.
src/XenoAtom.Terminal.UI.Tests/ScrollViewerInteractionTests.cssrc/XenoAtom.Terminal.UI.Tests/ScrollViewerLayoutTests.cssrc/XenoAtom.Terminal.UI.Tests/ScrollViewerTextAreaInteractionTests.cssrc/XenoAtom.Terminal.UI.Tests/ListControlHorizontalScrollViewerTests.csSupport explicit bar visibility modes (always/auto/hidden) and overlay scroll bars.
Add optional “scroll by N lines” configuration for wheel scrolling.
Add keyboard shortcuts for horizontal page scrolling (when the terminal key encoding supports it).
Look for rendering/input tests in src/XenoAtom.Terminal.UI.Tests.
See the ControlsDemo for interactive examples.