Configuration Guide

SharpConsoleUI provides comprehensive configuration options through ConsoleWindowSystemOptions and the Panel system.

Table of Contents#

Overview#

Configuration is done at system initialization via the options parameter:

using SharpConsoleUI.Panel;

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: new ConsoleWindowSystemOptions(
        EnablePerformanceMetrics: false,
        EnableFrameRateLimiting: true,
        TargetFPS: 60,
        TopPanelConfig: panel => panel
            .Left(Elements.StatusText("[bold]My App[/]")),
        BottomPanelConfig: panel => panel
            .Left(Elements.StartMenu())
            .Center(Elements.TaskBar())
            .Right(Elements.Clock())
    ));

ConsoleWindowSystemOptions#

Main configuration for the console window system.

ConsoleWindowSystemOptions is a positional record with init-only properties, so all settings are passed as named constructor arguments and modified with with expressions rather than assignment.

Rendering and performance

Property Type Default Description
EnablePerformanceMetrics bool false Enable performance metric tracking
EnableFrameRateLimiting bool true Limit rendering to target FPS (prevents excessive CPU usage)
TargetFPS int 60 Target frames per second (0 = unlimited)
ClampToWindowWidth bool false Clamp rendered output to the window width
DirtyTrackingMode DirtyTrackingMode Smart Dirty-region granularity: Smart (adaptive), Cell (minimal output), Line (fewer cursor moves)
SmartModeCoverageThreshold float 0.6f Smart mode only — above this fraction of dirty cells, switch to line mode
SmartModeFragmentationThreshold int 5 Smart mode only — above this many separate regions, switch to line mode
ClearDestinationOnWindowMove bool true Clear the vacated area when a window moves
EnableAnimations bool true Enable the animation system

Panels and desktop

Property Type Default Description
TopPanelConfig Func<PanelBuilder, PanelBuilder>? null Top panel element configuration
BottomPanelConfig Func<PanelBuilder, PanelBuilder>? null Bottom panel element configuration
ShowTopPanel bool true Show/hide top panel
ShowBottomPanel bool true Show/hide bottom panel
DesktopBackground DesktopBackgroundConfig? null Desktop background (gradient, pattern, animated). See Desktop Background
TerminalTransparencyMode TerminalTransparencyMode PreserveWindowColor How semi-transparent windows blend over a transparent terminal

Input and lifecycle

Property Type Default Description
WindowCycleKey ConsoleKey? ConsoleKey.T Ctrl+key that cycles windows. null disables it
ExitKey ConsoleKey? ConsoleKey.Q Ctrl+key that exits. null disables it
AltDigitSelectsWindow bool true Alt+1..9 activates a top-level window by taskbar index. false leaves the chord to your app
Watchdog WatchdogOptions? null Main-loop watchdog settings. null uses new WatchdogOptions()
InstallSynchronizationContext bool false Install a UI SynchronizationContext for the duration of Run(). See the caveat below
PipedInput PipedInputOptions? null Piped-stdin capture settings. null uses new PipedInputOptions()
Time TimeOptions? null Which clock each part of the system runs on. null means the real clock everywhere. See Clocks

Diagnostics and testing

Property Type Default Description
EnableDiagnostics bool false Enable rendering diagnostics (zero overhead when disabled)
DiagnosticsRetainFrames int 1 Number of frames of diagnostic data to retain
DiagnosticsLayers DiagnosticsLayers All Which layers to capture diagnostics for
EnableQualityAnalysis bool false Enable render quality analysis
EnablePerformanceProfiling bool false Enable detailed performance profiling
DiagnosticsSocket string? null Open a diagnostics socket at this path, without a consent prompt. null means none unless SHARPCONSOLEUI_DIAGNOSTICS_SOCKET asks for one. See Diagnostics Socket

InstallSynchronizationContext#

⚠️ Leave this false unless you are certain. It defaults to false so that awaited continuations resume on the thread pool. A handler that blocks on async work (.Result, .Wait(), .GetAwaiter().GetResult()) then freezes-and-recovers instead of deadlocking outright.

Setting it to true opts into the WinForms/WPF model where await resumes on the UI thread — but then handlers must use await and must never block on async work on the UI thread, or the captured continuation deadlocks against the main loop.

See Opting in (InstallSynchronizationContext) in the Threading & Async guide for the full discussion, and Why blocking on async work deadlocks for the underlying mechanism.

Computed Properties#

Property Type Description
MinFrameTime int Minimum milliseconds between frames (1000/TargetFPS)

Static Factory Methods#

Default

Returns default configuration:

var options = ConsoleWindowSystemOptions.Default;
// EnablePerformanceMetrics: false
// EnableFrameRateLimiting: true
// TargetFPS: 60

Create(bool?, bool?, int?)

Creates configuration with optional environment variable override for metrics:

var options = ConsoleWindowSystemOptions.Create(
    enableMetrics: true,
    enableFrameRateLimiting: false,
    targetFPS: 30
);

WithMetrics

Returns configuration with performance metrics enabled:

var options = ConsoleWindowSystemOptions.WithMetrics;
// EnablePerformanceMetrics: true
// Other settings: defaults

WithoutFrameRateLimiting

Returns configuration with unlimited FPS:

var options = ConsoleWindowSystemOptions.WithoutFrameRateLimiting;
// EnableFrameRateLimiting: false
// Other settings: defaults

WithTargetFPS(int)

Returns configuration with custom target FPS:

var options = ConsoleWindowSystemOptions.WithTargetFPS(30);
// TargetFPS: 30
// Other settings: defaults

Frame Rate Limiting#

Purpose: Prevents excessive CPU usage by limiting rendering speed.

// Limit to 30 FPS (good for dashboards with infrequent updates)
var options = new ConsoleWindowSystemOptions(
    EnableFrameRateLimiting: true,
    TargetFPS: 30
);

// Unlimited FPS (render as fast as possible - use for games/animations)
var options = new ConsoleWindowSystemOptions(
    EnableFrameRateLimiting: false
);

// Default 60 FPS (balanced - recommended for most applications)
var options = ConsoleWindowSystemOptions.Default;

Recommendations:

  • 30 FPS: Dashboards, monitoring tools, mostly static UIs
  • 60 FPS: General applications with animations and interactions (default)
  • Unlimited: Real-time games, simulations, high-frequency visualizations

Panel Configuration#

Panels are the top and bottom bars of the screen. They are composed from elements using a fluent builder pattern. See the Panel System guide for the full reference.

Quick Reference#

using SharpConsoleUI.Panel;

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold cyan]App Title[/]"))
        .Left(Elements.Separator())
        .Left(Elements.StatusText("[dim]Ctrl+T: Theme[/]"))
        .Right(Elements.Performance()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StartMenu()
            .WithText("☰ Start")
            .WithOptions(new StartMenuOptions
            {
                AppName = "My App",
                AppVersion = "1.0.0"
            }))
        .Center(Elements.TaskBar())
        .Right(Elements.Clock().WithFormat("HH:mm:ss"))
);

Available Elements#

Factory Method Description
Elements.StatusText(text) Static or markup text
Elements.Separator() Vertical separator
Elements.TaskBar() Window list with Alt+N shortcuts
Elements.Clock() Live clock display
Elements.Performance() FPS and metrics display
Elements.StartMenu() Start button + menu
Elements.Custom(name) Custom render callback

Performance Metrics#

Add a Performance element to display real-time metrics:

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Right(Elements.Performance())
);

The performance element displays:

  • FPS: Current frames per second
  • Dirty: Number of dirty characters per frame

Runtime Status Text#

Set status text at runtime via PanelStateService:

windowSystem.PanelStateService.TopStatus = "[green]Connected to server[/]";
windowSystem.PanelStateService.BottomStatus = "[yellow]Ready[/]";

This sets the Text property of the first StatusTextElement in each panel.

Panel Visibility#

Toggle panel visibility at runtime:

windowSystem.PanelStateService.ShowTopPanel = false;
windowSystem.PanelStateService.ShowBottomPanel = true;

Clocks#

A test that needs to tell one double click from two slow ones otherwise waits out the real multi-click window, once per assertion. TimeOptions lets an application supply the clock each part of the system runs on, so a test can move time instead:

var clock = new FakeTimeProvider();   // Microsoft.Extensions.TimeProvider.Testing

var system = new ConsoleWindowSystem(driver,
    ConsoleWindowSystemOptions.Create() with
    {
        Time = new TimeOptions { All = clock }
    });
Property Controls
All every part that does not name its own
Mouse the multi-click window
ScheduleRepaint controls that repaint on a cadence: a panel clock, an FPS readout, an indeterminate progress bar
Animation the per-frame animation delta - every tween, and animated desktop backgrounds

Each resolves named, then All, then TimeProvider.System. Configuring nothing changes nothing: every part runs on the real clock.

The named ones exist because the parts measure different things, and a test usually wants one of them still: freezing the mouse clock to step a double click apart should not also stop a progress bar.

// The mouse stops; everything else keeps running.
Time = new TimeOptions { Mouse = clock }

Frame pacing is not here, deliberately. Frame-rate limiting is a statement about the terminal - do not redraw more than TargetFPS times a second - so it stays on the machine clock. Pacing on a configurable clock means a frozen one renders nothing at all and an application cannot start.

Environment Variables#

Debug Logging#

Control built-in debug logging via environment variables:

# Enable debug logging to file
export SHARPCONSOLEUI_DEBUG_LOG=/tmp/consoleui.log

# Set minimum log level (Trace, Debug, Information, Warning, Error, Critical)
export SHARPCONSOLEUI_DEBUG_LEVEL=Debug

Log Levels:

  • Trace - Most verbose (mouse events, render cycles, focus changes)
  • Debug - Significant operations (window add/close, drag/resize)
  • Information - General information messages
  • Warning - Default level - warnings only
  • Error - Errors only
  • Critical - Fatal errors only

Performance Metrics Override#

Override EnablePerformanceMetrics via environment variable:

# Enable performance metrics via environment variable
export SHARPCONSOLEUI_PERF_METRICS=true

# Or use 1 for true
export SHARPCONSOLEUI_PERF_METRICS=1

This allows enabling metrics without code changes:

// Will check SHARPCONSOLEUI_PERF_METRICS environment variable
var options = ConsoleWindowSystemOptions.Create();

Diagnostics Socket#

Ask a running application to open its diagnostics socket, so another program can read what it displays and drive it:

# A path of your choosing
export SHARPCONSOLEUI_DIAGNOSTICS_SOCKET=/tmp/myapp.sock

# Or 1, to have one picked for you: $XDG_RUNTIME_DIR with the pid in the name,
# falling back to an owner-only directory inside the temp directory
export SHARPCONSOLEUI_DIAGNOSTICS_SOCKET=1

This route always asks first. The application prints a consent prompt on the plain console, before it paints anything, and a bare Enter means no. That is deliberate: an environment variable is not consent, because it can be inherited from a shell profile, baked into a .desktop file or a systemd unit, or left over in a terminal from hours ago — and the socket lets another program both read the screen and press keys.

Two cases skip the prompt:

Case Why
ConsoleWindowSystemOptions.DiagnosticsSocket set in code A property cannot be inherited from a shell or a unit file, so it can only be there because someone wrote it there. Intended for tests and for an application's own --debug-socket flag.
HeadlessConsoleDriver There is no user to ask and no screen on display. Being headless is itself a compiled-in decision that the program runs unattended.

If the variable is set but stdin is not interactive, the socket does not open — "nobody is watching" has to mean no, since that is exactly the case a stray variable would exploit.

// The no-prompt route, for an app's own debug flag
var options = ConsoleWindowSystemOptions.Create() with
{
    DiagnosticsSocket = "/tmp/myapp.sock"
};

See Debugging for the wire protocol, the twenty-eight verbs, who can reach the socket, and cxdbg — the attach-and-drive debugger (dotnet tool install -g cxdbg).

Complete Configuration Examples#

Minimal Configuration (Defaults)#

// Uses all defaults — default panels with status text elements
var windowSystem = new ConsoleWindowSystem(new NetConsoleDriver(RenderMode.Buffer));

// Set status text on default panels
windowSystem.PanelStateService.TopStatus = "[bold]My App[/]";
windowSystem.PanelStateService.BottomStatus = "Ready";

Result:

  • Default top and bottom panels with status text
  • No Start Menu, no task bar, no clock
  • 60 FPS frame rate limiting

Windows-like Configuration#

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold cyan]My App v1.0[/]"))
        .Right(Elements.Clock()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StartMenu()
            .WithOptions(new StartMenuOptions
            {
                Layout = StartMenuLayout.TwoColumn,
                AppName = "My App",
                AppVersion = "1.0.0"
            }))
        .Center(Elements.TaskBar())
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Result:

  • Start button in bottom-left (like Windows)
  • Ctrl+Space opens Start Menu
  • Window task bar in center
  • Clock in top-right
  • 60 FPS rendering

Development Configuration#

var options = new ConsoleWindowSystemOptions(
    EnablePerformanceMetrics: true,
    EnableFrameRateLimiting: false,
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold]Dev Mode[/]"))
        .Right(Elements.Performance()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StartMenu())
        .Center(Elements.TaskBar())
        .Right(Elements.Clock().WithFormat("HH:mm:ss"))
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Result:

  • Performance metrics in top-right
  • Unlimited FPS for performance testing
  • Full bottom bar with Start Menu, task bar, and clock

Dashboard/Monitor Configuration#

var options = new ConsoleWindowSystemOptions(
    EnableFrameRateLimiting: true,
    TargetFPS: 30,
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[cyan]System Dashboard[/]"))
        .Right(Elements.Performance()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StatusText("Press [yellow]F10[/] to exit"))
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Result:

  • 30 FPS (sufficient for dashboards)
  • No Start Menu or task bar
  • Clean layout with title and exit hint

Full-Screen Application#

var options = new ConsoleWindowSystemOptions(
    ShowBottomPanel: false,
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold]My Application[/]"))
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Result:

  • Only top panel visible
  • No bottom panel — maximum screen space
  • Single status line for branding

Game Configuration#

var options = new ConsoleWindowSystemOptions(
    EnableFrameRateLimiting: false,
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[cyan]Game Title[/]"))
        .Right(Elements.Performance()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StatusText("[yellow]WASD:[/] Move | [yellow]Space:[/] Jump | [yellow]ESC:[/] Quit"))
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Result:

  • Unlimited FPS with visible FPS counter
  • Top bar for title and metrics
  • Bottom bar for control hints

No Panels (Maximum Screen Space)#

var options = new ConsoleWindowSystemOptions(
    ShowTopPanel: false,
    ShowBottomPanel: false
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Using Environment Variable Override#

// Enable metrics via environment variable if set
var options = ConsoleWindowSystemOptions.Create(
    enableMetrics: null,  // null = check environment variable
    enableFrameRateLimiting: true,
    targetFPS: 60
);

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    options: options);

Shell:

# Enable metrics for this run only
SHARPCONSOLEUI_PERF_METRICS=true dotnet run

# Enable debug logging
SHARPCONSOLEUI_DEBUG_LOG=/tmp/app.log SHARPCONSOLEUI_DEBUG_LEVEL=Debug dotnet run

ControlDefaults#

ConsoleWindowSystemOptions configures the system; ControlDefaults holds library-wide defaults that individual controls start from. Most are compile-time constants, but a few are settable at runtime — set them once at startup, before building your windows.

Mouse wheel step#

using SharpConsoleUI.Configuration;

// Every control that has not overridden it, and windows too:
ControlDefaults.DefaultScrollWheelLines = 3;
Member Default Applies to
DefaultScrollWheelLines 1 Every scrolling control that has not set its own MouseWheelScrollSpeed
DefaultWindowScrollWheelLines 3 Scrolling a window itself

Values below 1 are clamped to 1.

DefaultWindowScrollWheelLines follows DefaultScrollWheelLines when your application sets that, so the single assignment above changes the wheel step everywhere. Setting it explicitly pins it and stops it following:

ControlDefaults.DefaultScrollWheelLines = 3;        // controls AND windows now move 3
ControlDefaults.DefaultWindowScrollWheelLines = 5;  // windows now move 5, controls stay at 3

Individual controls override the default:

var log = Controls.ScrollablePanel()
    .WithMouseWheelScrollSpeed(5)   // this panel only
    .Build();

MouseWheelScrollSpeed (and a matching WithMouseWheelScrollSpeed(int) builder method) is available on ScrollablePanelControl, TableControl, TreeControl, MultilineEditControl, ListControl and HtmlControl.

DefaultScrollWheelLines was a const before 2.6.9. It is now a settable static property so a value set here is observed by code compiled against an earlier version.

RegistryConfiguration#

Persistent key-value storage is configured separately via RegistryConfiguration, passed at construction:

var windowSystem = new ConsoleWindowSystem(
    new NetConsoleDriver(RenderMode.Buffer),
    registryConfiguration: new RegistryConfiguration(
        FilePath: "myapp.json",       // where to persist data
        EagerFlush: false,            // write on every Set*?
        FlushInterval: TimeSpan.FromSeconds(30) // background timer
    )
);

See the Registry guide for full documentation.

See Also#