* 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>
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.XandViewport.Ycannot go below0. 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.WidthandViewport.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- PermitsViewport.X < 0(scroll left of content)AllowNegativeY- PermitsViewport.Y < 0(scroll above content)AllowNegativeLocation- Combines both X and Y
Greater Than Content Flags - Allow scrolling past the last row/column:
AllowXGreaterThanContentWidth- PermitsViewport.X >= ContentSize.WidthAllowYGreaterThanContentHeight- PermitsViewport.Y >= ContentSize.HeightAllowLocationGreaterThanContentSize- Combines both X and Y
Blank Space Flags - Allow blank space to appear when scrolling:
AllowXPlusWidthGreaterThanContentWidth- PermitsViewport.X + Viewport.Width > ContentSize.Width(blank space on right)AllowYPlusHeightGreaterThanContentHeight- PermitsViewport.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 viewAllowNegativeYWhenHeightGreaterThanContentHeight- Useful for centering content smaller than the viewAllowNegativeLocationWhenSizeGreaterThanContentSize- Combines both X and Y
Drawing Flags - Control clipping and clearing behavior:
ClipContentOnly- Clips drawing to the visible content area instead of the entire ViewportClearContentOnly- Clears only the visible content area (requiresClipContentOnly)Transparent- The view does not clear its background when drawingTransparentMouse- 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.Filldoes not contribute to a SuperView's @Terminal.Gui.Dim.Auto sizing unlessminimumContentDimis 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.