Terminal.Gui provides a powerful Arrangement system that enables users to interactively move and resize views using the keyboard and mouse. This system supports both Tiled and Overlapped layout modes, allowing for flexible UI organization.
See the Layout Deep Dive for the broader layout system context.
The View.Arrangement property controls how users can arrange views within their SuperView. The ViewArrangement enum provides flags that can be combined to specify arrangement behavior.
[!INCLUDE Arrangement Lexicon]
The ViewArrangement enum supports these flags (can be combined):
Views with ViewArrangement.Fixed cannot be moved or resized by the user:
var view = new View
{
Arrangement = ViewArrangement.Fixed // Default
};
Views with ViewArrangement.Movable can be dragged with the mouse or moved with keyboard:
var window = new Window
{
Title = "Movable Window",
Arrangement = ViewArrangement.Movable
};
User Interaction:
Ctrl+F5 to enter Arrange Mode, use arrow keys to moveViews with ViewArrangement.Resizable can be resized by the user:
var window = new Window
{
Title = "Resizable Window",
Arrangement = ViewArrangement.Resizable
};
User Interaction:
Ctrl+F5 to enter Arrange Mode, press Tab to cycle resize handlesCombine flags for full desktop-like experience:
var window = new Window
{
Title = "Movable and Resizable",
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
};
Note: When both Movable and Resizable are set, the top edge cannot be resized (Movable takes precedence).
For fine-grained control, use individual edge flags:
// Only bottom edge resizable
var view = new View
{
Arrangement = ViewArrangement.BottomResizable
};
// Left and right edges resizable
var view2 = new View
{
Arrangement = ViewArrangement.LeftResizable | ViewArrangement.RightResizable
};
Arrange Mode is an interactive mode for arranging views using the keyboard. It is activated by pressing the Arrange Key (default: Ctrl+F5, configurable via Application.ArrangeKey).
When the user presses Ctrl+F5:
◊) appears in top-left cornerTab cycles to resize indicatorsEsc, Ctrl+F5, or click outside to exitThe Border shows visual indicators based on arrangement options:
| Arrangement Flag | Indicator | Location |
|---|---|---|
| Movable | ◊ (Glyphs.Move) |
Top-left corner |
| Resizable | ⇲ (Glyphs.SizeBottomRight) |
Bottom-right corner |
| LeftResizable | ↔ (Glyphs.SizeHorizontal) |
Left edge, centered |
| RightResizable | ↔ (Glyphs.SizeHorizontal) |
Right edge, centered |
| TopResizable | ↕ (Glyphs.SizeVertical) |
Top edge, centered |
| BottomResizable | ↕ (Glyphs.SizeVertical) |
Bottom edge, centered |
For a View to be arrangeable:
In Tiled layouts, SubViews typically do not overlap. There is no Z-order; all views are at the same layer.
var container = new View { Arrangement = ViewArrangement.Fixed };
var view1 = new View { X = 0, Y = 0, Width = 20, Height = 10 };
var view2 = new View { X = 21, Y = 0, Width = 20, Height = 10 };
container.Add(view1, view2);
// Views are side-by-side, non-overlapping
Characteristics:
In Overlapped layouts, SubViews can overlap with Z-order determining visual stacking.
Enable overlapped mode with ViewArrangement.Overlapped:
var container = new View
{
Arrangement = ViewArrangement.Overlapped
};
var window1 = new Window
{
X = 5, Y = 3, Width = 40, Height = 15,
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
var window2 = new Window
{
X = 15, Y = 8, Width = 40, Height = 15,
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
container.Add(window1, window2);
// window2 will overlap window1
Characteristics:
Ctrl+Tab / Ctrl+Shift+Tab to switch between overlapped viewsViews with ViewArrangement.Movable can be repositioned by the user.
var window = new Window
{
Title = "Drag Me!",
X = 10,
Y = 5,
Width = 40,
Height = 15,
Arrangement = ViewArrangement.Movable,
BorderStyle = LineStyle.Single
};
Ctrl+F5 to enter Arrange Mode◊) appears in the top-left cornerEsc or Ctrl+F5 to exit Arrange ModeViews with resizable flags can be resized by the user on specific edges.
var window = new Window
{
Title = "Resize Me!",
Arrangement = ViewArrangement.Resizable,
BorderStyle = LineStyle.Single
};
// Only right and bottom edges resizable
var view = new View
{
Arrangement = ViewArrangement.RightResizable | ViewArrangement.BottomResizable,
BorderStyle = LineStyle.Single
};
Ctrl+F5 to enter Arrange ModeTab to cycle to resize mode⇲) appearsEsc or Ctrl+F5 to exitA common pattern in tiled layouts is creating a resizable splitter between two panes.
View leftPane = new ()
{
X = 0,
Y = 0,
Width = Dim.Fill(Dim.Func(_ => rightPane.Frame.Width)),
Height = Dim.Fill(),
BorderStyle = LineStyle.Single
};
View rightPane = new ()
{
X = Pos.Right(leftPane) - 1,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill(),
Arrangement = ViewArrangement.LeftResizable,
BorderStyle = LineStyle.Single,
SuperViewRendersLineCanvas = true
};
rightPane.Border.Thickness = new Thickness(1, 0, 0, 0); // Only left border
container.Add(leftPane, rightPane);
How it works:
rightPane has ViewArrangement.LeftResizable - its left border is draggableleftPane uses Dim.Fill with a function to fill remaining spaceSuperViewRendersLineCanvas = true ensures proper line renderingView topPane = new ()
{
X = 0,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill(Dim.Func(_ => bottomPane.Frame.Height)),
BorderStyle = LineStyle.Single
};
View bottomPane = new ()
{
X = 0,
Y = Pos.Bottom(topPane) - 1,
Width = Dim.Fill(),
Height = Dim.Fill(),
Arrangement = ViewArrangement.TopResizable,
BorderStyle = LineStyle.Single,
SuperViewRendersLineCanvas = true
};
bottomPane.Border.Thickness = new Thickness(1, 0, 0, 0); // Only top border
container.Add(topPane, bottomPane);
Modal views run as exclusive applications that capture all user input until closed.
See the Multitasking Deep Dive for complete details on modal execution.
A view is modal when:
trueApplication.Run blocks until Application.RequestStop is calledvar dialog = new Dialog
{
Title = "Confirm",
Width = 40,
Height = 10
};
var label = new Label
{
Text = "Are you sure?",
X = Pos.Center(),
Y = 2
};
dialog.Add(label);
var ok = new Button { Text = "OK" };
ok.Accepting += (s, e) => Application.RequestStop();
dialog.AddButton(ok);
// Run modally - blocks until closed
Application.Run(dialog);
// Dialog has been closed
Runnable views are those run via Application.Run. Each non-modal Runnable view operates as a self-contained "application" with its own RunState.
See the Multitasking Deep Dive for complete details.
var runnable = new Runnable
{
Modal = false // Non-modal
};
// Runs as independent application
Application.Run(runnable);
Characteristics:
RunStateBackgroundWorkerCollection for multi-threaded examples| Aspect | Modal | Non-Modal |
|---|---|---|
| Input | Exclusive | Shared |
| Z-Order | Constrained (1 vs 0) | Full Z-order support |
| Blocks Execution | Yes | No |
| Use Case | Dialogs, confirmations | Multi-window apps |
SubViews do not overlap, positioned side-by-side or top-to-bottom:
var container = new View();
var left = new View
{
X = 0,
Y = 0,
Width = Dim.Percent(50),
Height = Dim.Fill()
};
var right = new View
{
X = Pos.Right(left),
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill()
};
container.Add(left, right);
Benefits:
SubViews can overlap with Z-order determining which is on top:
var container = new View
{
Arrangement = ViewArrangement.Overlapped
};
var window1 = new Window
{
X = 5,
Y = 3,
Width = 40,
Height = 15,
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
var window2 = new Window
{
X = 15,
Y = 8,
Width = 40,
Height = 15,
Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped
};
container.Add(window1, window2);
// window2 appears on top of window1
Z-Order:
Navigation:
Tab / Shift+Tab - Navigate within current overlapped viewCtrl+Tab (Ctrl+PageDown) - Switch to next overlapped viewCtrl+Shift+Tab (Ctrl+PageUp) - Switch to previous overlapped viewusing Terminal.Gui;
Application.Init();
var window = new Window
{
Title = "Drag and Resize Me! (Ctrl+F5 for keyboard mode)",
X = Pos.Center(),
Y = Pos.Center(),
Width = 50,
Height = 15,
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable,
BorderStyle = LineStyle.Double
};
var label = new Label
{
Text = "Try dragging the border with mouse\nor press Ctrl+F5!",
X = Pos.Center(),
Y = Pos.Center()
};
window.Add(label);
Application.Run(window);
Application.Shutdown();
Application.Init();
var top = new Runnable();
var leftPane = new FrameView
{
Title = "Left Pane",
X = 0,
Y = 0,
Width = Dim.Fill(Dim.Func(_ => rightPane.Frame.Width)),
Height = Dim.Fill()
};
var rightPane = new FrameView
{
Title = "Right Pane (drag left edge)",
X = Pos.Right(leftPane) - 1,
Y = 0,
Width = Dim.Fill(),
Height = Dim.Fill(),
Arrangement = ViewArrangement.LeftResizable,
SuperViewRendersLineCanvas = true
};
rightPane.Border.Thickness = new Thickness(1, 0, 0, 0);
top.Add(leftPane, rightPane);
Application.Run(top);
Application.Shutdown();
Application.Init();
var desktop = new Runnable
{
Arrangement = ViewArrangement.Overlapped
};
var window1 = new Window
{
Title = "Window 1",
X = 5,
Y = 3,
Width = 40,
Height = 12,
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable | ViewArrangement.Overlapped
};
var window2 = new Window
{
Title = "Window 2 (overlaps Window 1)",
X = 15,
Y = 8,
Width = 40,
Height = 12,
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable | ViewArrangement.Overlapped
};
desktop.Add(window1, window2);
Application.Run(desktop);
Application.Shutdown();
using Terminal.Gui;
using Terminal.Gui.Configuration;
// Change the arrange key
Application.ArrangeKey = Key.F2;
var window = new Window
{
Title = "Press F2 to enter arrange mode",
Arrangement = ViewArrangement.Movable | ViewArrangement.Resizable
};
Application.Run(window);
Arrangement only works when:
When creating splitters, set View.SuperViewRendersLineCanvas = true:
rightPane.SuperViewRendersLineCanvas = true;
This ensures LineCanvas properly handles line intersections at borders.
For overlapped views, manage Z-order with:
// Bring a view to the front
container.BringSubviewToFront(window1);
// Send a view to the back
container.SendSubviewToBack(window2);
// Check current order
int index = container.SubViews.IndexOf(window1);
Monitor arrangement changes by handling layout events:
view.FrameChanged += (s, e) =>
{
Console.WriteLine($"View moved/resized to {e.NewValue}");
};
view.LayoutComplete += (s, e) =>
{
// Layout has completed after arrangement change
};
The UICatalog application demonstrates arrangement: