This document captures design and implementation notes for Button.
For end-user usage and examples, see Button.
Click routed event when activated.Enter, Space)Button is a ContentVisual and renders a single child (Content) centered in the button.Button : ContentVisualButton() sets Focusable = true and defaults Tone = ControlTone.Default.Button(Visual text) assigns Content via the fluent Content(...) helper.Tone : ControlTone
IsPressed : bool
IsPressedInside : bool (internal bindable)
Click (bubble routing)
Measurement is driven by ButtonStyle:
Padding is applied around the content.ShowBorder reserves 1 row at the top and bottom (see rendering details below).The button measures its Content using inner constraints reduced by:
Padding.Horizontal / Padding.VerticalShowBorder == true, an additional 2 cells in both width and height (historically treated like a full border pad).The resulting SizeHints are derived from the content hints plus the added padding/border cells.
When ShowBorder == true, Button enforces a minimum of 3x3 cells so the top/bottom border and a content row can coexist.
Arrangement centers the content within the inner padded rectangle:
finalRect minus paddingShowBorder == true, minus a 1-cell pad on each side (see note above).(x, y) so that it is centered and clipped by the slot.The button does not currently expose a content alignment property; centering is the built-in behavior.
Rendering is split into three passes:
Full-rect background fill
Bounds with spaces using the resolved style for the current state.Optional top/bottom border
ButtonStyle.ShowBorder == true and height allows, draws a horizontal line at:
y = top using ButtonStyle.BorderTopGlyphy = bottom using ButtonStyle.BorderBottomGlyphtheme.BorderStyle(focused: HasFocus).Content region style stamping
Button backgrounds can be RGBA overlays (alpha blending). Writing the same RGBA background twice in the content region
would blend twice and produce visible “bands”. The implementation avoids this by filling the full rect once, then stamping
the content region with style.ClearBackground() so the background remains the one from the initial fill.
Button does not currently expose dedicated Command instances for activation. It directly raises the Click routed event.
Enter or Space triggers Click and marks the event handled.IsPressed = trueIsPressedInside = Bounds.Contains(pointer)IsPressedInside based on hit-testing.IsPressedClick if the pointer is inside on releaseIsHovered to avoid leaving a hover state behind after a drag-release outside.Resolved from the environment via ButtonStyle.Key:
Padding (default: new Thickness(2, 0, 2, 0))ShowBorder (top/bottom only)BorderTopGlyph (default: ' ')BorderBottomGlyph (default: '▁')Normal, Hovered, Pressed, Focused, DisabledWhen state overrides are not provided, ButtonStyle.Resolve(...) derives styles from the Theme:
Tone.Default: uses theme.ControlFill ?? theme.SurfaceAlt ?? theme.Surface ?? theme.BackgroundTone.Primary/Success/Warning/Error: uses tone color as background and the theme background/foreground as foregroundtheme.ControlFillHover ?? theme.SurfaceAlt as backgroundBoldtheme.ControlFillPressed ?? theme.Selection background + Boldtheme.FocusBorder as foreground (when present) + UnderlineDimTests that lock down current behavior:
src/XenoAtom.Terminal.UI.Tests/ButtonMeasureTests.cssrc/XenoAtom.Terminal.UI.Tests/ButtonInteractionTests.cssrc/XenoAtom.Terminal.UI.Tests/PointerHoverTests.csCommand (in addition to the routed event) for discoverability in CommandBar / CommandPalette.ShowBorder horizontal padding semantics (currently rendered as top/bottom lines only).