XenoAtom.Terminal.UI uses a single scrolling model across controls: IScrollable + ScrollModel.
This keeps scrollbars, mouse wheel behavior, and nested scrolling consistent.
ScrollViewer provides clipped scrolling for any content visual:
new ScrollViewer(new VStack("Line 1", "Line 2"))
IScrollableIf ScrollViewer.Content implements IScrollable, the scroll viewer uses the content’s scroll model:
ScrollModelThis is how TextArea integrates with ScrollViewer.
IScrollableIf the content is not scrollable, the scroll viewer owns its scroll offsets and scrolls by translating the content viewport.
IScrollable is a simple contract:
public interface IScrollable
{
ScrollModel Scroll { get; }
}
ScrollModel represents the state of a scrollable surface:
ViewportWidth / ViewportHeight: visible region size (in cells/rows)ExtentWidth / ExtentHeight: total scrollable size (in cells/rows)OffsetX / OffsetY: current scroll positionVersion: a bindable “snapshot” of the current scroll stateScrollModel during Arrange:
Scroll.SetViewport(viewportWidth, viewportHeight)Scroll.SetExtent(extentWidth, extentHeight)ScrollViewer reads the model to decide:
ScrollModel.Version is a bindable property updated whenever viewport/extent/offset changes.
Controls often mirror it into an internal bindable property in PrepareChildren to avoid “read then write” tracking loops.
See Binding for the pattern.
At a high level:
ScrollModel field: private readonly ScrollModel _scroll;IScrollable.Scroll => _scroll;Arrange, update viewport/extent and render content offset by Scroll.OffsetX/OffsetY.Prefer Scroll.ScrollToMakeVisible(...) when you need “ensure selection visible” behavior.
ScrollBar is an abstract base for scroll bars. Use VScrollBar (vertical) or HScrollBar (horizontal) directly, or let ScrollViewer create and manage them for you.
See also: