Files
Terminal.Gui/docfx/docs/layout.md
Copilot b43390a070 Fixes #4677 - Refactors DimAuto for less coupling and improves performance (#4678)
* Phase 5: Add IsFixed and RequiresTargetLayout categorization properties

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Further simplify DimAuto.Calculate using IsFixed property

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Refactor for-loops to foreach and clarify DimFill logic

Refactored multiple for-loops iterating over view lists to use foreach loops for improved readability and reduced boilerplate. Removed unused variables such as viewsNeedingLayout and index counters. Clarified DimFill handling by continuing early if the DimFill is not valid or lacks a To property, reducing nesting and improving intent. Made minor formatting and code style improvements for consistency.

* Refactor subview filtering and sizing logic

Refactored repeated LINQ queries for subview filtering into reusable helper methods (`GetViewsThatMatch`, `GetViewsThatHavePos<TPos>`, `GetViewsThatHaveDim<TDim>`), reducing duplication and improving readability. Moved max content size calculations for various subview types into new helper methods (`GetMaxSizePos<TPos>`, `GetMaxSizeDim<TDim>`). Updated main logic to use these helpers. Adornment thickness calculation now uses a switch expression. These changes improve modularity and maintainability.

* Refactor subview categorization for layout calculation

Refactored layout calculation to use a single-pass CategorizeSubViews method, grouping subviews by relevant Pos/Dim types into a new CategorizedViews struct. This replaces multiple helper methods and reduces redundant iterations. Updated main logic to use these categorized lists, and unified size calculation helpers to further reduce code duplication. Improves performance and maintainability by consolidating subview processing and removing obsolete methods.

* Revert perf POC commits and add missing overrides to Combine types

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add helper methods and simplify DimAuto.Calculate with foreach loops

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Refactor layout calculation in DimAuto.cs

Removed commented-out code and unnecessary list declarations to clean up the layout calculation logic.

* removed old plan file

* Code cleanup

* Add performance analysis and improvement plan for DimAuto.Calculate

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add DimAuto benchmarks and benchmark documentation

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Implement Phase 1 & 2 performance optimizations for DimAuto.Calculate

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Code cleanup

* Delete plans/dimauto-perf-plan.md

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
2026-02-06 12:33:47 -07:00

11 KiB

Layout

Terminal.Gui provides a rich system for how View objects are laid out relative to each other. The layout system also defines how coordinates are specified.

See View Deep Dive, Arrangement Deep Dive, Scrolling Deep Dive, and Drawing Deep Dive for more.

Table of Contents


Lexicon & Taxonomy

[!INCLUDE Layout Lexicon]

Arrangement Modes

See Arrangement Deep Dive for more.

Composition

[!INCLUDE View Composition]

The Content Area

Content Area refers to the rectangle with a location of 0,0 with the size returned by @Terminal.Gui.View.GetContentSize*.

The content area is the area where the view's content is drawn. Content can be any combination of the @Terminal.Gui.View.Text property, SubViews, and other content drawn by the View. The @Terminal.Gui.View.GetContentSize* method gets the size of the content area of the view.

The Content Area size tracks the size of the @Terminal.Gui.View.Viewport by default. If the content size is set via @Terminal.Gui.View.SetContentSize*, the content area is the provided size. If the content size is larger than the @Terminal.Gui.View.Viewport, scrolling is enabled.

The Viewport

The @Terminal.Gui.View.Viewport is a rectangle describing the portion of the Content Area that is visible to the user. It is a "portal" into the content. The Viewport.Location is relative to the top-left corner of the inner rectangle of View.Padding. If Viewport.Size is the same as View.GetContentSize(), Viewport.Location will be 0,0.

To enable scrolling call View.SetContentSize() and then set Viewport.Location to positive values. Making Viewport.Location positive moves the Viewport down and to the right in the content.

See the Scrolling Deep Dive for details on how to enable scrolling.

Viewport Settings

The @Terminal.Gui.View.ViewportSettings property controls how the Viewport is constrained using @Terminal.Gui.ViewBase.ViewportSettingsFlags. By default, ViewportSettings is None, which provides sensible constraints for typical scrolling scenarios.

Default Behavior (No Flags Set)

With no flags set, the Viewport is constrained as follows:

  • No negative scrolling: Viewport.X and Viewport.Y cannot go below 0. The user cannot scroll above or to the left of the content origin.
  • Content fills the viewport: The Viewport is clamped so that Viewport.X + Viewport.Width <= ContentSize.Width and Viewport.Y + Viewport.Height <= ContentSize.Height. This prevents blank space from appearing when scrolling - the content always fills the visible area.
  • Last row/column always visible: Even if trying to scroll past the end of content, at least the last row and last column remain visible.

Flag Categories

The flags are organized into categories:

Negative Location Flags - Allow scrolling before the content origin (0,0):

  • AllowNegativeX - Permits Viewport.X < 0 (scroll left of content)
  • AllowNegativeY - Permits Viewport.Y < 0 (scroll above content)
  • AllowNegativeLocation - Combines both X and Y

Greater Than Content Flags - Allow scrolling past the last row/column:

  • AllowXGreaterThanContentWidth - Permits Viewport.X >= ContentSize.Width
  • AllowYGreaterThanContentHeight - Permits Viewport.Y >= ContentSize.Height
  • AllowLocationGreaterThanContentSize - Combines both X and Y

Blank Space Flags - Allow blank space to appear when scrolling:

  • AllowXPlusWidthGreaterThanContentWidth - Permits Viewport.X + Viewport.Width > ContentSize.Width (blank space on right)
  • AllowYPlusHeightGreaterThanContentHeight - Permits Viewport.Y + Viewport.Height > ContentSize.Height (blank space on bottom)
  • AllowLocationPlusSizeGreaterThanContentSize - Combines both X and Y

Conditional Negative Flags - Allow negative scrolling only when viewport is larger than content:

  • AllowNegativeXWhenWidthGreaterThanContentWidth - Useful for centering content smaller than the view
  • AllowNegativeYWhenHeightGreaterThanContentHeight - Useful for centering content smaller than the view
  • AllowNegativeLocationWhenSizeGreaterThanContentSize - Combines both X and Y

Drawing Flags - Control clipping and clearing behavior:

  • ClipContentOnly - Clips drawing to the visible content area instead of the entire Viewport
  • ClearContentOnly - Clears only the visible content area (requires ClipContentOnly)
  • Transparent - The view does not clear its background when drawing
  • TransparentMouse - Mouse events pass through areas not occupied by SubViews

Layout Engine

Terminal.Gui provides a rich system for how views are laid out relative to each other. The position of a view is set by setting the X and Y properties, which are of time @Terminal.Gui.Pos. The size is set via Width and Height, which are of type @Terminal.Gui.Dim.

The layout system uses virtual properties for categorization without type checking: ReferencesOtherViews(), DependsOnSuperViewContentSize, CanContributeToAutoSizing, GetMinimumContribution(), IsFixed, and RequiresTargetLayout. This enables extensibility.

var label1 = new Label () { X = 1, Y = 2, Width = 3, Height = 4, Title = "Absolute")

var label2 = new Label () {
    Title = "Computed",
    X = Pos.Right (otherView),
    Y = Pos.Center (),
    Width = Dim.Fill (),
    Height = Dim.Percent (50)
};

@Terminal.Gui.Pos

@Terminal.Gui.Pos is the type of View.X and View.Y and supports the following sub-types:

  • Absolute position, by passing an integer - @Terminal.Gui.Pos.Absolute*.
  • Percentage of the parent's view size - @Terminal.Gui.Pos.Percent(System.Int32)
  • Anchored from the end of the dimension - @Terminal.Gui.Pos.AnchorEnd(System.Int32)
  • Centered, using @Terminal.Gui.Pos.Center*
  • The @Terminal.Gui.Pos.Left*, @Terminal.Gui.Pos.Right*, @Terminal.Gui.Pos.Top*, and @Terminal.Gui.Pos.Bottom* tracks the position of another view.
  • Aligned (left, right, center, etc...) with other views - @Terminal.Gui.Pos.Align*
  • An arbitrary function - @Terminal.Gui.Pos.Func*

All Pos coordinates are relative to the SuperView's content area.

Pos values can be combined using addition or subtraction:

// Set the X coordinate to 10 characters left from the center
view.X = Pos.Center () - 10;
view.Y = Pos.Percent (20);

anotherView.X = AnchorEnd (10);
anotherView.Width = 9;

myView.X = Pos.X (view);
myView.Y = Pos.Bottom (anotherView) + 5;

@Terminal.Gui.Dim

@Terminal.Gui.Dim is the type of View.Width and View.Height and supports the following sub-types:

  • Automatic size based on the View's content (either SubViews or Text) - @Terminal.Gui.Dim.Auto* - See Dim.Auto Deep Dive.
  • Absolute size, by passing an integer - @Terminal.Gui.Dim.Absolute(System.Int32).
  • Percentage of the SuperView's Content Area - @Terminal.Gui.Dim.Percent(System.Int32).
  • Fill to the end of the SuperView's Content Area - @Terminal.Gui.Dim.Fill*. Note: Dim.Fill does not contribute to a SuperView's @Terminal.Gui.Dim.Auto sizing unless minimumContentDim is specified. See Dim.Auto Deep Dive for details.
  • Reference the Width or Height of another view - @Terminal.Gui.Dim.Width(Terminal.Gui.View), @Terminal.Gui.Dim.Height(Terminal.Gui.View).
  • An arbitrary function - @Terminal.Gui.Dim.Func(System.Func{System.Int32}).

All Dim dimensions are relative to the SuperView's content area.

Like, Pos, objects of type Dim can be combined using addition or subtraction, like this:

// Set the Width to be 10 characters less than filling 
// the remaining portion of the screen
view.Width = Dim.Fill () - 10;

view.Height = Dim.Percent(20) - 1;

anotherView.Height = Dim.Height (view) + 1;
classDiagram
    class View ["View — location and size relative to SuperView"]
    class Frame ["Frame — Rectangle"]
    class Viewport ["Viewport — visible portion of Content Area"]
    class Margin ["Margin — where Shadows live"]
    class Border ["Border — Title and Arrangement controls"]
    class Padding ["Padding — where ScrollBars live"]
    class Adornment
    class Thickness ["Thickness — each side has a width"]

    View --> Frame
    View --> Viewport
    View --> Margin : has
    View --> Border : has
    View --> Padding : has
    Margin --|> Adornment
    Border --|> Adornment
    Padding --|> Adornment
    Adornment --> Thickness : has

How To

This section provides solutions to common layout scenarios.

Stretch a View Between Fixed Elements

Scenario: A label on the left, a text field that stretches to fill available space, and a button anchored to the right:

[label][    stretching text field    ][button]
Label label = new () { Text = "_Name:" };
Button btn = new () { Text = "_OK", X = Pos.AnchorEnd () };
TextField textField = new ()
{
    X = Pos.Right (label) + 1,
    Width = Dim.Func (() => btn.Frame.X - label.Frame.Width - 1)
};
superView.Add (label, textField, btn);

Align Multiple Views (Like Dialog Buttons)

Scenario: Align buttons horizontally using @Terminal.Gui.Pos.Align, as Dialog does:

Button cancelBtn = new ()
{
    Text = "_Cancel",
    X = Pos.Align (Alignment.End)
};
Button okBtn = new ()
{
    Text = "_OK",
    X = Pos.Align (Alignment.End)
};
superView.Add (cancelBtn, okBtn);

The Pos.Align method supports different alignments (Start, Center, End, Fill) and can add spacing between items via AlignmentModes.

Center with Auto-Sizing and Constraints (Like Dialog)

Scenario: A centered view that auto-sizes to its content, with minimum and maximum constraints that account for adornments (Border, Margin, Padding). This is how Dialog positions and sizes itself:

Window popup = new ()
{
    X = Pos.Center (),
    Y = Pos.Center (),
    Width = Dim.Auto (
        minimumContentDim: 20,  // Minimum width
        maximumContentDim: Dim.Percent (100) - Dim.Func (_ => popup.GetAdornmentsThickness ().Horizontal)),
    Height = Dim.Auto (
        minimumContentDim: 5,   // Minimum height
        maximumContentDim: Dim.Percent (100) - Dim.Func (_ => popup.GetAdornmentsThickness ().Vertical))
};

The key insight is maximumContentDim subtracts the adornments thickness from 100% to ensure the view (including its Border, Margin, and Padding) never exceeds the SuperView's bounds.