Panel System

SharpConsoleUI provides a configurable Panel system for the top and bottom bars of the screen. Panels sit outside the window area and display elements like status text, clocks, task bars, start menus, and performance metrics.

Table of Contents#

Overview#

The panel system replaces the former StatusBarOptions configuration. Instead of toggling predefined status bar features, you now compose panels from discrete elements using a fluent builder.

Each panel has three zones — Left, Center, and Right — and elements are laid out within those zones. Elements can be fixed-width or flexible (flex-grow), and the layout engine distributes space automatically.

┌─────────────────────────────────────────────────────────────┐
│  [Left elements...]        [Center...]      [Right elements] │  ← Top Panel
├─────────────────────────────────────────────────────────────┤
│                                                             │
│                      Window Area                            │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  [Left elements...]        [Center...]      [Right elements] │  ← Bottom Panel
└─────────────────────────────────────────────────────────────┘

Note For the in-window StatusBarControl (three-zone bar with clickable items and shortcut hints), see the StatusBarControl documentation. That is a different control from the system-level panels documented here.

Configuration#

Panels are configured via TopPanelConfig and BottomPanelConfig on ConsoleWindowSystemOptions. Each accepts a function that receives a PanelBuilder and returns it with elements added:

using SharpConsoleUI.Panel;

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold cyan]My App[/]"))
        .Right(Elements.Performance()),
    BottomPanelConfig: panel => panel
        .Left(Elements.StartMenu())
        .Center(Elements.TaskBar())
        .Right(Elements.Clock())
);

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

Panel Visibility#

Show or hide panels at configuration time:

var options = new ConsoleWindowSystemOptions(
    ShowTopPanel: true,      // default: true
    ShowBottomPanel: false   // hide bottom panel entirely
);

Or toggle at runtime:

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

Default Panels#

When no TopPanelConfig or BottomPanelConfig is provided, the system creates default panels with a status text element in each. This lets PanelStateService.TopStatus and BottomStatus work out of the box.

Elements Factory#

The Elements static class provides factory methods for all built-in panel elements:

using SharpConsoleUI.Panel;

Elements.StatusText("text")    // Static or markup text
Elements.Separator()           // Vertical separator character
Elements.TaskBar()             // Window list with Alt+N shortcuts
Elements.Clock()               // Live clock display
Elements.Performance()         // FPS and dirty-char metrics
Elements.StartMenu()           // Start button + menu system
Elements.Custom("name")        // Custom render callback

Each method returns a builder that supports fluent configuration before being passed to a PanelBuilder zone.

Built-in Elements#

StatusText#

Displays static or markup-formatted text. Supports click handlers.

Elements.StatusText("[bold cyan]My Application[/]")
    .WithName("title")
    .WithColor(Color.Cyan1)
    .OnClick(() => ShowAbout())

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name for lookup | | WithColor(Color) | Override text color | | OnClick(Action) | Click handler |

Properties (on StatusTextElement): | Property | Type | Description | |----------|------|-------------| | Text | string | Markup text to display (get/set) | | TextColor | Color? | Optional color override | | ClickHandler | Action? | Optional click callback |

Separator#

Renders a single vertical separator character between elements.

Elements.Separator()
    .WithChar('|')
    .WithColor(Color.Grey50)

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name | | WithChar(char) | Separator character (default: │) | | WithColor(Color) | Separator color |

TaskBar#

Displays a list of open windows with Alt+N keyboard shortcuts. Flexes to fill available space.

Elements.TaskBar()
    .WithActiveColor(Color.Cyan1)
    .WithInactiveColor(Color.Grey50)
    .WithMinimizedDim(true)

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name | | WithActiveColor(Color) | Active window highlight color | | WithInactiveColor(Color) | Inactive window color | | WithMinimizedDim(bool) | Dim minimized windows (default: true) |

Features:

  • Alt+1 through Alt+9 to switch windows (set AltDigitSelectsWindow: false to keep the chord for your own app — see Configuration)
  • Click to activate a window
  • Minimized windows shown dimmed
  • Automatically updates when windows are added/removed

Clock#

Displays the current time with configurable format. Auto-updates.

Elements.Clock()
    .WithFormat("HH:mm:ss")
    .WithColor(Color.Yellow)
    .WithUpdateInterval(1000)

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name | | WithFormat(string) | Time format string (default: "HH:mm") | | WithColor(Color) | Text color | | WithUpdateInterval(int) | Update interval in ms (default: 1000) |

Performance#

Displays real-time FPS and dirty character count. Auto-updates.

Elements.Performance()
    .ShowFPS(true)
    .ShowDirtyChars(true)

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name | | ShowFPS(bool) | Show FPS counter (default: true) | | ShowDirtyChars(bool) | Show dirty char count (default: true) |

StartMenu#

Adds a Start button that opens a Windows-like menu with categorized actions, window list, and system features.

Elements.StartMenu()
    .WithText("☰ Start")
    .WithColors(Color.White, Color.DarkBlue)
    .WithShortcutKey(ConsoleKey.Spacebar, ConsoleModifiers.Control)
    .WithOptions(new StartMenuOptions
    {
        Layout = StartMenuLayout.TwoColumn,
        AppName = "My App",
        AppVersion = "1.0.0",
        ShowSystemCategory = true,
        ShowWindowList = true
    })

Builder Methods: | Method | Description | |--------|-------------| | WithName(string) | Set element name | | WithText(string) | Button text (default: "☰ Start") | | WithColors(Color, Color) | Button foreground and background | | WithShortcutKey(ConsoleKey, ConsoleModifiers) | Keyboard shortcut (default: Ctrl+Space) | | WithOptions(StartMenuOptions) | Menu configuration |

StartMenuOptions: | Property | Type | Default | Description | |----------|------|---------|-------------| | Layout | StartMenuLayout | TwoColumn | SingleColumn or TwoColumn | | AppName | string? | null | App name in header | | AppVersion | string? | null | App version in header | | ShowIcons | bool | true | Show icons in header/exit | | HeaderIcon | string | "☰" | Header icon | | ShowSystemCategory | bool | true | Show System category | | ShowWindowList | bool | true | Show Windows list | | SidebarStyle | StartMenuSidebarStyle | IconLabel | Sidebar display style | | BackgroundGradient | GradientBackground? | null | Menu gradient background |

Registering Actions:

Actions are registered directly on the StartMenuElement:

var startMenu = windowSystem.BottomPanel!.FindElement<StartMenuElement>("startmenu")!;

startMenu.RegisterAction("New Document", () => CreateDoc(), category: "File", order: 10);
startMenu.RegisterAction("Open File", () => OpenFile(), category: "File", order: 20);
startMenu.RegisterAction("Calculator", () => OpenCalc(), category: "Tools", order: 10);

StartMenuElement Methods: | Method | Description | |--------|-------------| | RegisterAction(name, callback, category?, order?) | Register a menu action | | UnregisterAction(name) | Remove an action | | GetActions() | Get all registered actions | | Show() | Programmatically open the menu |

Custom#

Create elements with custom render callbacks for full control.

Elements.Custom("myElement")
    .WithFixedWidth(20)
    .WithRenderCallback((buffer, x, y, width, fg, bg) =>
    {
        buffer.WriteString(x, y, "Custom content", fg, bg);
    })
    .WithClickHandler(() => HandleClick())

Builder Methods: | Method | Description | |--------|-------------| | WithFixedWidth(int) | Set fixed width | | WithFlexGrow(int) | Set flex grow factor | | WithRenderCallback(Action<...>) | Set render function | | WithClickHandler(Action) | Set click handler |

PanelBuilder#

The PanelBuilder provides a fluent API for composing panel layouts:

var panel = Panel.Builder()
    .Left(Elements.StatusText("Left"))
    .Left(Elements.Separator())
    .Center(Elements.TaskBar())
    .Right(Elements.Clock())
    .WithBackgroundColor(Color.DarkBlue)
    .WithForegroundColor(Color.White)
    .Visible(true)
    .Build();

Methods: | Method | Returns | Description | |--------|---------|-------------| | Left(IPanelElement) | PanelBuilder | Add element to left zone | | Left(IPanelElementBuilder) | PanelBuilder | Add builder's element to left zone | | Left(params object[]) | PanelBuilder | Add mixed elements/builders to left | | Center(...) | PanelBuilder | Same variants for center zone | | Right(...) | PanelBuilder | Same variants for right zone | | WithBackgroundColor(Color) | PanelBuilder | Set background color | | WithForegroundColor(Color) | PanelBuilder | Set foreground color | | Visible(bool) | PanelBuilder | Set initial visibility | | Build() | Panel | Build the panel |

Runtime Access#

Via PanelStateService#

Access panels and convenience properties through windowSystem.PanelStateService:

// Convenience status text shortcuts
windowSystem.PanelStateService.TopStatus = "[bold cyan]Connected[/]";
windowSystem.PanelStateService.BottomStatus = "Ready";

// Toggle visibility
windowSystem.PanelStateService.ShowTopPanel = false;
windowSystem.PanelStateService.ShowBottomPanel = true;

// Check dirty state
if (windowSystem.PanelStateService.IsDirty) { /* ... */ }

Via Panel References#

Access panels directly for element manipulation:

// Direct panel access
var topPanel = windowSystem.PanelStateService.TopPanel;
var bottomPanel = windowSystem.PanelStateService.BottomPanel;

// Or via shorthand properties
var bottomPanel = windowSystem.BottomPanel;

// Find elements by name
var clock = bottomPanel!.FindElement<ClockElement>("clock");

// Find elements by type
var startMenu = bottomPanel!.FindElement<StartMenuElement>("startmenu");

// Check for element existence
if (bottomPanel!.HasElement<TaskBarElement>()) { /* ... */ }

// Get all elements of a type
var allText = topPanel!.FindAllElements<StatusTextElement>();

// Add/remove elements at runtime
topPanel!.AddRight(new StatusTextElement("New item"));
topPanel!.Remove("itemName");

// Clear zones
topPanel!.ClearRight();
bottomPanel!.ClearAll();

Panel Properties#

Property Type Description
Visible bool Panel visibility (triggers screen redraw)
BackgroundColor Color? Background color (falls back to theme)
ForegroundColor Color? Foreground color (falls back to theme)
IsDirty bool Whether panel needs redraw
Height int 1 if visible, 0 if hidden

Panel Methods#

Method Description
AddLeft(params IPanelElement[]) Add elements to left zone
AddCenter(params IPanelElement[]) Add elements to center zone
AddRight(params IPanelElement[]) Add elements to right zone
Remove(string name) Remove element by name
Remove(IPanelElement) Remove specific element
FindElement<T>(string name) Find and cast element by name
HasElement<T>() Check if element type exists
FindAllElements<T>() Get all elements of type
ClearAll() Clear all zones
ClearLeft() / ClearCenter() / ClearRight() Clear specific zone
MarkDirty() Mark for redraw

Complete Examples#

Minimal (Default Panels)#

// Default panels with status text elements
var windowSystem = new ConsoleWindowSystem(new NetConsoleDriver(RenderMode.Buffer));
windowSystem.PanelStateService.TopStatus = "[bold]My App[/]";
windowSystem.PanelStateService.BottomStatus = "Ready";

Windows-like Desktop#

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

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

// Register Start Menu actions
var startMenu = windowSystem.BottomPanel!.FindElement<StartMenuElement>("startmenu")!;
startMenu.RegisterAction("New Document", () => { /* ... */ }, category: "File", order: 10);
startMenu.RegisterAction("Settings", () => { /* ... */ }, category: "Edit", order: 10);

Dashboard (No Start Menu)#

var options = new ConsoleWindowSystemOptions(
    EnableFrameRateLimiting: true,
    TargetFPS: 30,
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold 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);

Full-Screen App (Top Panel Only)#

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

No Panels#

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

Custom Element#

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold]App[/]"))
        .Right(Elements.Custom("indicator")
            .WithFixedWidth(12)
            .WithRenderCallback((buffer, x, y, width, fg, bg) =>
            {
                var status = IsConnected ? "[green]●[/]" : "[red]●[/]";
                var cells = MarkupParser.Parse($" {status} Online", fg, bg);
                buffer.WriteCellsClipped(x, y, cells, new LayoutRect(x, y, width, 1));
            }))
);

Creating Custom Elements#

Extend PanelElement to create reusable custom elements:

public class BatteryElement : PanelElement
{
    public BatteryElement() : base("battery") { }

    public override int? FixedWidth => 10;

    public override void Render(CharacterBuffer buffer, int x, int y,
        int width, Color fg, Color bg)
    {
        var level = GetBatteryLevel();
        var color = level > 20 ? Color.Green : Color.Red;
        buffer.WriteString(x, y, $"⚡ {level}%", color, bg);
    }
}

// Use it:
var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Right(new BatteryElement())
);

Migration from StatusBarOptions#

If you were using StatusBarOptions, here's how to migrate:

Old (StatusBarOptions) New (Panel System)
ShowTopStatus: true ShowTopPanel: true
ShowBottomStatus: true ShowBottomPanel: true
ShowStartButton: true Add Elements.StartMenu() to a panel
ShowTaskBar: true Add Elements.TaskBar() to a panel
StartButtonLocation: Bottom Add StartMenu to BottomPanelConfig
StartButtonPosition: Left Use .Left(Elements.StartMenu())
windowSystem.TopStatus = "..." windowSystem.PanelStateService.TopStatus = "..."
windowSystem.BottomStatus = "..." windowSystem.PanelStateService.BottomStatus = "..."
windowSystem.RegisterStartMenuAction(...) startMenuElement.RegisterAction(...)

Before:

var options = new ConsoleWindowSystemOptions(
    StatusBarOptions: new StatusBarOptions(
        ShowStartButton: true,
        StartButtonLocation: StatusBarLocation.Bottom,
        ShowTaskBar: true,
        StartMenu: new StartMenuOptions { AppName = "My App" }
    )
);
windowSystem.TopStatus = "[bold]Title[/]";
windowSystem.RegisterStartMenuAction("New", CreateNew, "File", 10);

After:

var options = new ConsoleWindowSystemOptions(
    TopPanelConfig: panel => panel
        .Left(Elements.StatusText("[bold]Title[/]")),
    BottomPanelConfig: panel => panel
        .Left(Elements.StartMenu()
            .WithOptions(new StartMenuOptions { AppName = "My App" }))
        .Center(Elements.TaskBar())
);
var startMenu = windowSystem.BottomPanel!.FindElement<StartMenuElement>("startmenu")!;
startMenu.RegisterAction("New", () => CreateNew(), category: "File", order: 10);

See Also#