๐ŸŽฎ Event Handling in Kontra TUI

Kontra TUI uses a unified event system to handle keyboard, mouse, and scroll interactions through a single, clean interface.

You handle all events inside the kontra::run(...) loop. This gives you complete control over your application's interactivity.


๐Ÿง  How It Works

Kontra's main event loop provides an InputEvent object every time the user interacts with the terminal.


  kontra::run(screen, [&](const InputEvent& event) {
      // Handle the event here
      switch (event.type) {
          // ... cases for different event types ...
      }
  });

The InputEvent struct abstracts away platform differences:

// core/event.hpp
struct InputEvent {
    EventType type;  // The kind of event that occurred
    char key;        // The character, ONLY valid for EventType::KEY_PRESS
    int mouse_x;     // X coordinate (column) for mouse events
    int mouse_y;     // Y coordinate (row) for mouse events
};

๐ŸŽ›๏ธ Abstract Event Types

Instead of checking for raw ASCII codes, you should handle these abstract event types. This makes your code readable and cross-platform.

Here are the main event types supported by InputEvent. Your application logic should check event.type to decide how to react.

  • EventType::KEY_PRESS Triggered by any printable character (e.g., a, b, 1, 2, !, ?). The character itself is stored in event.key.

  • EventType::KEY_ENTER Triggered by the Enter key.

  • EventType::KEY_BACKSPACE Triggered by the Backspace key.

  • EventType::KEY_ESCAPE Triggered by the Esc key.

  • EventType::KEY_UP / KEY_DOWN Triggered by the Up and Down arrow keys, respectively.

  • EventType::KEY_LEFT / KEY_RIGHT Triggered by the Left and Right arrow keys, respectively.

  • EventType::MOUSE_PRESS Triggered by a left mouse click. The coordinates are stored in event.mouse_x and event.mouse_y.

  • EventType::MOUSE_SCROLL_UP Triggered when the mouse wheel is scrolled up.

  • EventType::MOUSE_SCROLL_DOWN Triggered when the mouse wheel is scrolled down.


Keyboard Input Example

Handle navigation with arrow keys and actions with Enter/Escape.


  kontra::run(screen, [&](const InputEvent& event) {
      switch (event.type) {
          case EventType::KEY_UP:
              selected_index--;
              update_ui();
              break;
          case EventType::KEY_DOWN:
              selected_index++;
              update_ui();
              break;
          case EventType::KEY_ENTER:
              handle_selection();
              break;
          case EventType::KEY_ESCAPE:
              cancel_input();
              break;
          case EventType::KEY_PRESS:
              // Handle printable characters for an input box
              if (event.key == 'i') {
                  current_mode = AppMode::Editing;
                  update_ui();
              }
              break;
      }
  });

Mouse Click Example

Detect if the user clicked on a button or component.


  if (event.type == EventType::MOUSE_PRESS) {
      // Iterate through all clickable components
      for (const auto& btn : buttons) {
          // Use the .contains() method to check for a hit
          if (btn->contains(event.mouse_x, event.mouse_y)) {
              btn->click();
              // Optional: update focus to the clicked button
              active_button = btn;
              break; // Stop checking once a button is found
          }
      }
  }

๐Ÿ“Œ Tip: All Kontra components inherit a .contains(x, y) method to check if a coordinate lies within their last rendered position.


Scroll Example

Scroll vertically with the mouse wheel. This works best with scrollable components like List.


  kontra::run(screen, [&](const InputEvent& event) {
      switch (event.type) {
          case EventType::MOUSE_SCROLL_UP:
              main_list->scroll_up();
              break;
          case EventType::MOUSE_SCROLL_DOWN:
              main_list->scroll_down();
              break;
      }
  });


๐Ÿคน Combining Input Modes

You can manage complex states like editing vs. navigating by checking an AppMode enum first.


  kontra::run(screen, [&](const InputEvent& event) {
      if (current_mode == AppMode::Editing) {
          // Handle events differently in editing mode
          switch (event.type) {
              case EventType::KEY_ENTER:
                  add_task();
                  break;
              case EventType::KEY_ESCAPE:
                  current_mode = AppMode::Navigating;
                  update_ui();
                  break;
              default:
                  // Pass all other events to the input box
                  input_box->handle_event(event);
                  break;
          }
      } else { // Navigation Mode
          switch (event.type) {
              case EventType::KEY_UP:   navigate_up(); break;
              case EventType::KEY_DOWN: navigate_down(); break;
              case EventType::KEY_PRESS:
                  if (event.key == 'i') {
                      current_mode = AppMode::Editing;
                      update_ui();
                  }
                  break;
              case EventType::MOUSE_PRESS:
                  // ... handle mouse clicks for navigation ...
                  break;
          }
      }
  });


๐Ÿ“š Best Practices

  • โœ… Use Abstract Events: Always check for EventType like KEY_ENTER instead of raw numbers like 13.
  • โœ… Keep Logic State-Driven: Change state variables (AppMode, selected_index), then call an update function.
  • โœ… Delegate: Let container components (like RadioGroup or a smart Input form) handle their own internal events when possible.
  • โœ… Use contains(x, y) to detect mouse clicks on any component.

TL;DR

  • Use InputEvent inside kontra::run() for all interaction.
  • Handle specific actions with EventType::KEY_ENTER, KEY_UP, etc.
  • Handle printable characters with EventType::KEY_PRESS and event.key.
  • Handle clicks with event.mouse_x/y and .contains(...).
  • Support scroll with MOUSE_SCROLL_UP / DOWN.
  • Split input logic based on app mode or active components.

Now go build something awesome and interactive โœจ