Table of Contents

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

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+F5 to 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+F5 to enter Arrange Mode, press Tab to 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:

  1. Visual indicators appear on arrangeable views
  2. If ViewArrangement.Movable, a move indicator () appears in top-left corner
  3. If ViewArrangement.Resizable, pressing Tab cycles to resize indicators
  4. Arrow keys move or resize the view
  5. 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:

  1. Must be part of a SuperView
  2. Position and dimensions must be independent of other SubViews
  3. Must have View.Arrangement flags set
  4. 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+Tab to 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

  1. Press Ctrl+F5 to enter Arrange Mode
  2. A move indicator () appears in the top-left corner
  3. Use arrow keys to move the view
  4. Press Esc or Ctrl+F5 to 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

  1. Press Ctrl+F5 to enter Arrange Mode
  2. Press Tab to cycle to resize mode
  3. Resize indicator () appears
  4. Use arrow keys to resize
  5. Press Esc or Ctrl+F5 to 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:

  • rightPane has ViewArrangement.LeftResizable - its left border is draggable
  • leftPane uses Dim.Fill with a function to fill remaining space
  • SuperViewRendersLineCanvas = true ensures 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 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:

  • 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.Run blocks until Application.RequestStop is called
  • Own RunState - Each modal view has its own RunState
  • Dialog - Centered modal window with button support
  • MessageBox - Simple message dialogs
  • Wizard - Multi-step modal dialogs
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 BackgroundWorkerCollection 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

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:

Navigation:

  • Tab / Shift+Tab - Navigate within current overlapped view
  • Ctrl+Tab (Ctrl+PageDown) - Switch to next overlapped view
  • Ctrl+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:

  1. View has a SuperView - Root views cannot be arranged
  2. Independent Position/Size - Views with Pos.Align or complex Dim constraints may not resize properly
  3. 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

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