View Arrangement Deep Dive
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.
Table of Contents
- Overview
- Arrangement Modes
- Arrange Mode (Interactive)
- Tiled vs Overlapped Layouts
- Movable Views
- Resizable Views
- Creating Resizable Splitters
- Modal Views
- Runnable Views
- Examples
Overview
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.
Arrangement Lexicon
| Term | Meaning |
|---|---|
| Arrange Mode | Interactive mode activated via Ctrl+F5 (configurable via Application.ArrangeKey) that displays indicators on arrangeable views and allows keyboard-based arrangement. |
| Arrangement | The feature of Layout which controls how the user can use the mouse and keyboard to arrange views and enables either Tiled or Overlapped layouts. |
| Modal | A view run as an "application" via Application.Run where Modal == true. Has constrained z-order with modal view at z-order 1. |
| Movable | A View that can be moved by the user using keyboard or mouse. Enabled by setting ViewArrangement.Movable flag. |
| Overlapped | Layout where SubViews have overlapping Frames with Z-order determining visual stacking. Enabled by ViewArrangement.Overlapped flag. |
| Resizable | A View that can be resized by the user using keyboard or mouse. Enabled by setting ViewArrangement.Resizable flag. |
| Runnable | A view where Application.Run(Toplevel) is called. Each non-modal Runnable view has its own RunState and operates as a self-contained application. |
| Tiled | Layout where SubViews typically do not overlap, with no z-order. Default layout mode set to ViewArrangement.Fixed. |
ViewArrangement Flags
The ViewArrangement enum supports these flags (can be combined):
- Fixed (0) - View cannot be moved or resized (default)
- Movable (1) - View can be moved by the user
- LeftResizable (2) - Left edge can be resized
- RightResizable (4) - Right edge can be resized
- TopResizable (8) - Top edge can be resized
- BottomResizable (16) - Bottom edge can be resized
- Resizable (30) - All edges can be resized (combines all resize flags)
- Overlapped (32) - View overlaps other views (enables Z-order)
Arrangement Modes
Fixed (Default)
Views with ViewArrangement.Fixed cannot be moved or resized by the user:
var view = new View
{
Arrangement = ViewArrangement.Fixed // Default
};
Movable
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:
- Mouse: Drag the top Border
- Keyboard: Press
Ctrl+F5to enter Arrange Mode, use arrow keys to move
Resizable
Views with ViewArrangement.Resizable can be resized by the user:
var window = new Window
{
Title = "Resizable Window",
Arrangement = ViewArrangement.Resizable
};
User Interaction:
- Mouse: Drag any border edge
- Keyboard: Press
Ctrl+F5to enter Arrange Mode, pressTabto cycle resize handles
Movable and Resizable
Combine 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).
Individual Edge Resizing
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 (Interactive)
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).
Entering Arrange Mode
When the user presses Ctrl+F5:
- Visual indicators appear on arrangeable views
- If ViewArrangement.Movable, a move indicator (
◊) appears in top-left corner - If ViewArrangement.Resizable, pressing
Tabcycles to resize indicators - Arrow keys move or resize the view
- Press
Esc,Ctrl+F5, or click outside to exit
Arrange Mode Indicators
The 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 |
Keyboard Controls in Arrange Mode
- Arrow Keys - Move or resize based on active mode
- Tab - Cycle between move and resize modes (if both available)
- Shift+Tab - Cycle backwards
- Esc - Exit Arrange Mode
- Ctrl+F5 - Exit Arrange Mode
Requirements for Arrangement
For a View to be arrangeable:
- Must be part of a SuperView
- Position and dimensions must be independent of other SubViews
- Must have View.Arrangement flags set
- Typically needs a Border for mouse interaction
Tiled vs Overlapped Layouts
Tiled Layout
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:
- Default mode for most TUI applications
- Views use Pos and Dim for relative positioning
- No Z-order management needed
- More predictable layout behavior
Overlapped Layout
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:
- Z-order determined by SubViews collection order
- Later views appear above earlier views
- Tab navigation constrained to current overlapped view
- Use
Ctrl+Tab/Ctrl+Shift+Tabto switch between overlapped views
Movable Views
Views with ViewArrangement.Movable can be repositioned by the user.
Enabling Movable
var window = new Window
{
Title = "Drag Me!",
X = 10,
Y = 5,
Width = 40,
Height = 15,
Arrangement = ViewArrangement.Movable,
BorderStyle = LineStyle.Single
};
Moving with Mouse
- Click and drag the top Border to move the view
- The view's Frame updates as it moves
- Release the mouse to complete the move
Moving with Keyboard
- Press
Ctrl+F5to enter Arrange Mode - A move indicator (
◊) appears in the top-left corner - Use arrow keys to move the view
- Press
EscorCtrl+F5to exit Arrange Mode
Resizable Views
Views with resizable flags can be resized by the user on specific edges.
All Edges Resizable
var window = new Window
{
Title = "Resize Me!",
Arrangement = ViewArrangement.Resizable,
BorderStyle = LineStyle.Single
};
Specific Edge Resizable
// Only right and bottom edges resizable
var view = new View
{
Arrangement = ViewArrangement.RightResizable | ViewArrangement.BottomResizable,
BorderStyle = LineStyle.Single
};
Resizing with Mouse
- Click and drag any enabled border edge
- Resize indicators appear on hover
- The view's Width and Height update
Resizing with Keyboard
- Press
Ctrl+F5to enter Arrange Mode - Press
Tabto cycle to resize mode - Resize indicator (
⇲) appears - Use arrow keys to resize
- Press
EscorCtrl+F5to exit
Creating Resizable Splitters
A common pattern in tiled layouts is creating a resizable splitter between two panes.
Horizontal Splitter (Left/Right 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:
rightPanehas ViewArrangement.LeftResizable - its left border is draggableleftPaneuses Dim.Fill with a function to fill remaining spaceSuperViewRendersLineCanvas = trueensures proper line rendering- Only the left border is visible, acting as the splitter
Vertical Splitter (Top/Bottom Panes)
View 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
Modal views run as exclusive applications that capture all user input until closed.
See the Multitasking Deep Dive for complete details on modal execution.
What Makes a View Modal
A view is modal when:
- Run via Application.Run
- Toplevel.Modal =
true
Modal Characteristics
- Exclusive Input - All keyboard and mouse input goes to the modal view
- Constrained Z-Order - Modal view has Z-order of 1, everything else at 0
- Blocks Execution -
Application.Runblocks until Application.RequestStop is called - Own RunState - Each modal view has its own RunState
Modal View Types
- Dialog - Centered modal window with button support
- MessageBox - Simple message dialogs
- Wizard - Multi-step modal dialogs
Modal Example
var 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
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.
Non-Modal Runnable Views
var toplevel = new Toplevel
{
Modal = false // Non-modal
};
// Runs as independent application
Application.Run(toplevel);
Characteristics:
- Has its own
RunState - Events dispatched independently
- Can run on separate threads
- See
BackgroundWorkerCollectionfor multi-threaded examples
Modal vs Non-Modal Runnable
| 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 |
Tiled vs Overlapped Layouts
Tiled Layout (Default)
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:
- Simpler layout logic
- No Z-order management
- More predictable behavior
- Standard for most TUI applications
Overlapped Layout
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:
- Order in View.SubViews determines Z-order
- Later views appear above earlier views
- Use View.BringSubviewToFront to change 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 view
Examples
Example 1: Movable and Resizable Window
using 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();
Example 2: Horizontal Resizable Splitter
Application.Init();
var top = new Toplevel();
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();
Example 3: Overlapped Windows
Application.Init();
var desktop = new Toplevel
{
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();
Example 4: Custom Arrange Key
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);
Advanced Topics
Constraints and Limitations
Arrangement only works when:
- View has a SuperView - Root views cannot be arranged
- Independent Position/Size - Views with Pos.Align or complex Dim constraints may not resize properly
- Border Required - Mouse-based arrangement requires a visible Border
SuperViewRendersLineCanvas
When creating splitters, set View.SuperViewRendersLineCanvas = true:
rightPane.SuperViewRendersLineCanvas = true;
This ensures LineCanvas properly handles line intersections at borders.
Z-Order Management
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);
Arrangement Events
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
};
See Also
- Layout Deep Dive - Overall layout system
- View Deep Dive - View base class
- Multitasking Deep Dive - Modal and runnable views
- Drawing Deep Dive - LineCanvas and borders
- Configuration Deep Dive - Configuring Application.ArrangeKey
API Reference
UICatalog Examples
The UICatalog application demonstrates arrangement:
- Arrangement Editor - Interactive arrangement demonstration
- Overlapped scenario - Shows overlapped window management
- Splitter examples - Various splitter configurations