This document covers keyboard input, mouse input, the focus system, and scrolling.
When a key is pressed:
- Tab / Shift+Tab: Moves focus forward or backward through focusable widgets.
- Ctrl+Key combinations: Ctrl+A, Ctrl+C, Ctrl+V, and Ctrl+Enter are forwarded to the focused widget. Ctrl+PageUp/PageDown scroll horizontally. All other Ctrl+Key combinations fire the
Shortcutevent. - PageUp / PageDown: Scrolls the nearest scrollable parent vertically.
- Escape: Forwarded to the focused widget AND fires the
Shortcutevent. - All other keys: Forwarded to the focused widget.
The Shortcut event on the TermuiX instance fires for Ctrl+Key combinations (except Ctrl+A/C/V/Enter and Ctrl+PageUp/PageDown) and for Escape.
termui.Shortcut += (sender, key) =>
{
if (key.Modifiers.HasFlag(ConsoleModifiers.Control))
{
switch (key.Key)
{
case ConsoleKey.F:
// Ctrl+F: Focus search
break;
case ConsoleKey.N:
// Ctrl+N: New item
break;
case ConsoleKey.Q:
// Ctrl+Q: Quit
break;
}
}
if (key.Key == ConsoleKey.Escape)
{
// Close popups, cancel operations, etc.
}
};Each focusable widget responds to keys when focused:
- Button: Enter and Space trigger Click.
- Input: Arrow keys move cursor, Backspace/Delete edit text, Enter fires EnterPressed (or inserts newline in multiline+CtrlEnter mode), Escape fires EscapePressed.
- Checkbox: Space and Enter toggle checked state.
- RadioButton: Space and Enter select the radio button.
- Slider: Left/Right change value by Step, Shift+Left/Right by Step*10, Home sets Min, End sets Max.
- TreeView: Up/Down navigate, Left/Right expand/collapse or navigate parent/child, Enter/Space toggle and select, Home/End jump to first/last.
By default, Ctrl+C terminates the application. To handle Ctrl+C yourself:
TermuiXLib.AllowCancelKeyExit = false;
// Now Ctrl+C does NOT exit. Use Console.CancelKeyPress if you need to react to it.Mouse input is enabled by default. Disable it with:
termui.MouseEnabled = false;public enum MouseEventType
{
LeftButtonPressed,
LeftButtonReleased,
RightButtonPressed,
RightButtonReleased,
WheelUp,
WheelDown,
Moved
}public readonly struct MouseEventArgs
{
public int X { get; init; } // Absolute screen column (0-based)
public int Y { get; init; } // Absolute screen row (0-based)
public int LocalX { get; init; } // X relative to widget's top-left
public int LocalY { get; init; } // Y relative to widget's top-left
public MouseEventType EventType { get; init; }
public bool Shift { get; init; } // Whether Shift was held
}The MouseClick event on TermuiX fires for ALL mouse events (not just clicks). Filter by EventType:
termui.MouseClick += (sender, args) =>
{
if (args.EventType == MouseEventType.LeftButtonPressed)
{
// Close popups when clicking outside them
ClosePopups();
}
};- Button: Left click fires Click. Right click fires RightClick.
- Checkbox: Left click toggles checked state.
- RadioButton: Left click selects the button.
- Slider: Left click and drag set value proportionally based on position.
- TreeView: Click on the expand indicator toggles expand/collapse. Click elsewhere on a row selects the node.
Left-clicking on a focusable widget gives it focus. Right-clicking also sets focus to the clicked widget.
- Tab: Moves to the next focusable widget (wraps around).
- Shift+Tab: Moves to the previous focusable widget (wraps around).
- Click: Sets focus to the clicked widget.
- SetFocus(widget): Sets focus in code.
Only visible, focusable, non-disabled widgets can receive focus.
public class FocusChangedEventArgs : EventArgs
{
public IWidget Widget { get; } // The widget that received focus
public FocusChangeReason Reason { get; } // Why focus changed
}
public enum FocusChangeReason
{
Keyboard, // Tab / Shift+Tab
Click, // Mouse click
Programmatic // SetFocus() called in code
}termui.FocusChanged += (sender, args) =>
{
if (args.Reason == FocusChangeReason.Click)
{
ClosePopups();
}
};When focus changes, the nearest scrollable parent auto-scrolls to keep the focused widget visible.
termui.SetFocus(someWidget);SetFocus silently does nothing if the widget is not focusable, disabled, or invisible.
| Input | Effect |
|---|---|
| Mouse wheel up/down | Scroll by 3 lines |
| Ctrl + mouse wheel | Horizontal scroll by 3 columns |
| PageUp / PageDown | Scroll by 1 line |
| Ctrl+PageUp / Ctrl+PageDown | Horizontal scroll by 1 column |
| Tab | Auto-scroll to keep focused widget visible |
If a container has only horizontal scrolling (no vertical), mouse wheel automatically routes to horizontal scroll without needing Ctrl.
bool ScrollX { get; } // Whether horizontal scrolling is enabled
bool ScrollY { get; } // Whether vertical scrolling is enabled
long ScrollOffsetX { get; set; } // Current horizontal scroll position
long ScrollOffsetY { get; set; } // Current vertical scroll positionA common pattern for closing popups when the user clicks elsewhere, changes focus, or presses Escape:
void ClosePopups()
{
if (sortDropdown is not null) { sortDropdown.Visible = false; }
if (contextMenu is not null) { contextMenu.Visible = false; }
}
// Close on click outside
termui.MouseClick += (sender, args) =>
{
if (args.EventType == MouseEventType.LeftButtonPressed)
{
ClosePopups();
}
};
// Close on focus change
termui.FocusChanged += (sender, args) =>
{
if (args.Reason == FocusChangeReason.Click)
{
ClosePopups();
}
};
// Close on Escape
termui.Shortcut += (sender, key) =>
{
if (key.Key == ConsoleKey.Escape)
{
ClosePopups();
}
};Open the popup by setting Visible = true and positioning it with PositionX/PositionY.