This document captures design and implementation notes for HSplitter and VSplitter.
For end-user usage and examples, see Splitter (HSplitter / VSplitter).
DockLayout)src/XenoAtom.Terminal.UI/Controls/Splitter.cs (base)src/XenoAtom.Terminal.UI/Controls/HSplitter.cssrc/XenoAtom.Terminal.UI/Controls/VSplitter.cssrc/XenoAtom.Terminal.UI/Styling/SplitterStyle.cssrc/XenoAtom.Terminal.UI.Tests/SplitterLayoutTests.cssamples/ControlsDemo/Demos/SplitterDemo.csSplitter : Visual (abstract base)HSplitter : Splitter (sealed)VSplitter : Splitter (sealed)Focusable = trueHorizontalAlignment = Align.StretchVerticalAlignment = Align.StretchRatio = 0.5BarSize = 1First : Visual? (bindable)Second : Visual? (bindable)Ratio : double (bindable)
[0..1].0.5 during arrange.BarSize : int (bindable)
max(1, BarSize).MinFirst : int (bindable)
MinSecond : int (bindable)
The base Splitter also defines internal bindables used for styling state:
IsDragging (while mouse is dragging the bar)IsBarHovered (when the mouse is over the bar and not dragging)The splitter has up to 2 children (First and Second). When only one pane is set, that pane is arranged to fill the entire available rectangle and no bar is used.
Measurement is heuristic:
This keeps measure simple and avoids needing to solve "best split" during measure.
The returned SizeHints uses:
Natural and Max equal to the clamped combined child desired sizes (plus the bar)Min is (0, 0)FlexGrowX/Y follow the control alignments (defaults are stretch, so it grows)FlexShrinkX/Y = 1Arrange computes:
bar = max(1, BarSize) when both panes exist, otherwise bar = 0available = totalAlongAxis - bar, clamped to >= 0firstSize = round(available * ratio)firstSize is clamped so that:
firstSize >= clamp(MinFirst, 0, available)secondSize = available - firstSize >= clamp(MinSecond, 0, available)Then it arranges:
First with firstSize_barRect) with thickness barSecond with secondSizeRounding is done with Math.Round, so the split point is stable but can move by 1 cell as the container size changes.
The splitter renders only the bar (the panes render themselves).
Key details:
_barRect is non-empty (i.e., both panes are present)HSplitter draws a vertical bar glyph (a column separating left/right panes)VSplitter draws a horizontal bar glyph (a row separating top/bottom panes)SplitterLayoutTests validates that:
Splitters do not use commands yet; interactions are implemented directly in pointer/key handlers.
IsBarHovered when the pointer moves over/out of the bar (when not dragging).Ratio based on pointer deltaDrag updates also obey MinFirst/MinSecond clamping, so the split point cannot be moved beyond those constraints.
When enabled and focused:
HSplitter):
Left decreases ratio, Right increases ratioHome sets ratio to 0, End sets ratio to 1VSplitter):
Up decreases ratio, Down increases ratioHome sets ratio to 0, End sets ratio to 1Step size is based on modifiers:
The ratio delta is computed as stepCells / availableCells, where availableCells is the current arranged size along
the split axis (minus the bar).
SplitterStyle controls:
HorizontalGlyph (used when the bar is horizontal)VerticalGlyph (used when the bar is vertical)BarStyleHoverStyleFocusStyleDragStyleDisabledStyleSplitterStyle.Resolve(theme, enabled, focused, hovered, dragging) applies precedence:
Defaults (when not overridden) use theme.BorderStyle(...), with bold emphasis for hover/drag, and dimmed styling when
disabled.
Increase, Decrease, CollapseFirst, CollapseSecond).