Configuration Guide
SharpConsoleUI provides comprehensive configuration options through ConsoleWindowSystemOptions and the Panel system.
Table of Contents#
- Overview
- ConsoleWindowSystemOptions
- Panel Configuration
- Environment Variables
- ControlDefaults
- Complete Configuration Examples
- RegistryConfiguration
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
falseunless you are certain. It defaults tofalseso 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
trueopts into the WinForms/WPF model whereawaitresumes on the UI thread — but then handlers must useawaitand 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 messagesWarning- Default level - warnings onlyError- Errors onlyCritical- 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.
DefaultScrollWheelLineswas aconstbefore 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#
- Panel System - Full panel and element reference
- Desktop Background - Gradients, patterns, animated backgrounds
- Threading & Async - The UI thread model and
InstallSynchronizationContext - State Services - Runtime state management
- Registry - Persistent key-value storage
- Themes - Theme system and customization
- Debugging - The diagnostics socket, its verbs, and
cxdbg