This document captures design and implementation notes for TabControl.
For end-user usage and examples, see TabControl.
src/XenoAtom.Terminal.UI/Controls/TabControl.cssrc/XenoAtom.Terminal.UI/Controls/TabPage.cssrc/XenoAtom.Terminal.UI/Styling/TabControlStyle.cssrc/XenoAtom.Terminal.UI.Tests/TabControlInteractionTests.cssrc/XenoAtom.Terminal.UI.Tests/TabControlRenderingTests.cssrc/XenoAtom.Terminal.UI.Tests/TabControlFeatureTests.cssamples/ControlsDemo/Demos/TabControlDemo.csTabControl : Visual (sealed)TabPage : record class, IVisualElementTabCloseReasonTabPageClosingEventArgsTabPageClosedEventArgsFocusable = trueHorizontalAlignment = Align.StretchVerticalAlignment = Align.StretchSelectedIndex : int (bindable)
FirstVisibleIndex : int (bindable)
Tabs : IReadOnlyList<TabPage>
AddTab(Visual header, Visual content)AddTab(TabPage page)TryCloseTab(int index)TryCloseTab(TabPage page)TabPage is a bindable state container:
Header : VisualContent : VisualIsEnabled : boolShowCloseButton : boolData : object?RequestClosing eventClosed eventBecause TabPage implements IVisualElement, page property changes participate in dependency tracking once the page is attached to a TabControl.
TabControl attaches:
Only the selected content is hosted at any given time:
ContentVisual host (TabContentHost) contains the selected TabPage.ContentTabControlStyle.TabContentTemplateFactory)When a bound TabPage.Header changes while attached:
When a bound TabPage.Content changes while that page is selected:
PrepareChildren:
TabControlStyle and ensures a content template existsSelectedIndexMeasurement considers:
Close button layout reserve:
GetRuneWidth(CloseButtonRune) + CloseButtonSpacingTabPage.ShowCloseButton is trueArrange computes:
_headerHeightOverflow behavior:
FirstVisibleIndex determines where the visible window startsFirstVisibleIndex to keep the selected tab visibleFirstVisibleIndex directly and may hide the selected tab header while keeping the selected content visibleNon-visible headers are arranged to a zero rectangle so stale bounds do not render.
Render draws:
Header/background text is still rendered by child visuals.
State inputs:
TabPage.IsEnabled && TabPage.Header.IsEnabled && TabPage.Content.IsEnabledTabControl.HasFocusindex == SelectedIndexWhen focused:
Left: select previous enabled tabRight: select next enabled tabThere is no wrap-around.
Mouse interaction targets the header strip only:
TabPage.RequestClosingFirstVisibleIndex backward/forward by one tabClose requests:
TabPage.RequestClosing is raised first and may set Cancel = trueTabPage.Closed is raisedTabControl.TryCloseTab(...) uses the same lifecycleExisting properties remain:
TabPaddingStripStyleTabStyle, TabHoveredStyle, TabPressedStyle, TabSelectedStyle, TabDisabledStyleTabContentTemplateFactoryNew styling surface:
CloseButtonRuneCloseButtonSpacingCloseButtonStyle, CloseButtonHoveredStyle, CloseButtonPressedStyle, CloseButtonDisabledStyleOverflowPreviousRune, OverflowNextRuneOverflowButtonStyle, OverflowButtonHoveredStyle, OverflowButtonPressedStyle, OverflowButtonDisabledStyleDefault close button behavior:
Default overflow button behavior:
TabControlInteractionTests covers:
TabControlRenderingTests covers:
TabControlFeatureTests covers:
TabPage remains a record class for compatibility, but now behaves as an attached, bindable model object.Tabs stays read-only at the public API boundary; list mutation still flows through AddTab(...) / TryCloseTab(...).