Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Input Handling

The input handling system allows TUI applications to respond to keyboard, mouse, and other terminal events. Input handlers wrap widgets and intercept events before they propagate further.

The Event Type

All terminal events are represented by the Event type:

type Event = [
  `FocusGained,
  `FocusLost,
  `Key(KeyEvent),
  `Mouse(MouseEvent),
  `Paste(string),
  `Resize(i64, i64)
];
  • FocusGained / FocusLost: Terminal window focus changes
  • Key: Keyboard input
  • Mouse: Mouse input (requires tui::mouse <- true)
  • Paste: Bracketed paste content
  • Resize: Terminal was resized to (columns, rows)

Keyboard Events

Keyboard events contain detailed information about key presses:

type KeyEvent = {
  code: KeyCode,
  kind: KeyEventKind,
  modifiers: Array<KeyModifier>,
  state: Array<KeyEventState>
};

Key Codes

type KeyCode = [
  `Backspace,
  `Enter,
  `Left,
  `Right,
  `Up,
  `Down,
  `Home,
  `End,
  `PageUp,
  `PageDown,
  `Tab,
  `BackTab,
  `Delete,
  `Insert,
  `F(i64),
  `Char(string),
  `Null,
  `Esc,
  `CapsLock,
  `ScrollLock,
  `NumLock,
  `PrintScreen,
  `Pause,
  `Menu,
  `KeypadBegin,
  `Media(MediaKeyCode),
  `Modifier(ModifierKeyCode)
];

Character keys use Char(string), e.g., Char("a") or Char("A"). Function keys use F(i64), e.g., F(1) for F1.

Key Event Kind

type KeyEventKind = [
  `Press,
  `Repeat,
  `Release
];

Most applications only care about Press events.

Key Modifiers

type KeyModifier = [
  `Shift,
  `Control,
  `Alt,
  `Super,
  `Hyper,
  `Meta
];

The modifiers array contains all modifiers held during the key event.

Mouse Events

Mouse events require enabling mouse support first:

tui::mouse <- true

Mouse events contain position and action information:

type MouseEvent = {
  column: i64,
  kind: MouseEventKind,
  modifiers: Array<KeyModifier>,
  row: i64
};

type MouseEventKind = [
  `Down(MouseButton),
  `Up(MouseButton),
  `Drag(MouseButton),
  `Moved,
  `ScrollDown,
  `ScrollUp,
  `ScrollLeft,
  `ScrollRight
];

type MouseButton = [
  `Left,
  `Right,
  `Middle
];

Creating Input Handlers

Use tui::input_handler::input_handler to wrap a widget with event handling:

val input_handler: fn(
  ?#enabled: &[bool, null],
  #handle: &fn(Event) -> [`Stop, `Continue] throws 'e,
  &Tui
) -> Tui throws 'e;

The handler function receives events and returns:

  • Stop: The event was handled; don’t propagate to other handlers
  • Continue: Pass the event to the next handler

Basic Example

let count = 0;

tui::input_handler::input_handler(
  #handle: &|event| select event {
    `Key({ code: `Char("q"), .. }) => {
      // Exit on 'q'
      `Stop
    },
    e@ `Key({ code: `Up, .. }) => {
      count <- e ~ count + 1;
      `Stop
    },
    e@ `Key({ code: `Down, .. }) => {
      count <- e ~ count - 1;
      `Stop
    },
    _ => `Continue
  },
  &tui::paragraph::paragraph("[count]")
)

Conditional Handling

The #enabled parameter allows dynamically enabling/disabling a handler:

let editing = false;

tui::input_handler::input_handler(
  #enabled: &editing,
  #handle: &|event| select event {
    `Key({ code: `Esc, .. }) => {
      editing <- false;
      `Stop
    },
    `Key({ code: `Char(c), .. }) => {
      // Handle character input when editing
      `Stop
    },
    _ => `Continue
  },
  &tui::paragraph::paragraph("Edit mode")
)

Event Propagation

Input handlers form a stack. Events flow from the outermost handler inward:

tui::input_handler::input_handler(
  #handle: &|e| /* handler A */ `Continue,
  &tui::input_handler::input_handler(
    #handle: &|e| /* handler B */ `Continue,
    &widget
  )
)

If handler A returns Continue, the event reaches handler B. If either returns Stop, propagation stops.

The Raw Event Stream

For advanced use cases, you can access the raw event stream directly via tui::event:

select tui::event {
  `Key(k) => dbg(k),
  _ => ()
}

However, using input_handler is preferred as it integrates properly with the widget tree and supports the enable/disable pattern.

Common Patterns

Vim-style Mode Switching

type Mode = [`Normal, `Insert];
let mode: Mode = `Normal;

tui::input_handler::input_handler(
  #handle: &|event| select (mode, event) {
    (`Normal, `Key({ code: `Char("i"), .. })) => {
      mode <- `Insert;
      `Stop
    },
    (`Insert, `Key({ code: `Esc, .. })) => {
      mode <- `Normal;
      `Stop
    },
    (`Insert, `Key({ code: `Char(c), .. })) => {
      // Insert character
      `Stop
    },
    _ => `Continue
  },
  &content
)

Focus Management

let focused = 0;
let num_widgets = 3;

tui::input_handler::input_handler(
  #handle: &|event| select event {
    `Key({ code: `Tab, .. }) => {
      focused <- (focused + 1) % num_widgets;
      `Stop
    },
    `Key({ code: `BackTab, .. }) => {
      focused <- (focused - 1 + num_widgets) % num_widgets;
      `Stop
    },
    _ => `Continue
  },
  &widgets
)

See Also

  • style - Styling text and widgets
  • layout - Arranging widgets with focus support