๐ฎ 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_PRESSTriggered by any printable character (e.g.,a,b,1,2,!,?). The character itself is stored inevent.key. -
EventType::KEY_ENTERTriggered by theEnterkey. -
EventType::KEY_BACKSPACETriggered by theBackspacekey. -
EventType::KEY_ESCAPETriggered by theEsckey. -
EventType::KEY_UP/KEY_DOWNTriggered by theUpandDownarrow keys, respectively. -
EventType::KEY_LEFT/KEY_RIGHTTriggered by theLeftandRightarrow keys, respectively. -
EventType::MOUSE_PRESSTriggered by a left mouse click. The coordinates are stored inevent.mouse_xandevent.mouse_y. -
EventType::MOUSE_SCROLL_UPTriggered when the mouse wheel is scrolled up. -
EventType::MOUSE_SCROLL_DOWNTriggered 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
EventTypelikeKEY_ENTERinstead of raw numbers like13. - โ
Keep Logic State-Driven: Change state variables (
AppMode,selected_index), then call an update function. - โ
Delegate: Let container components (like
RadioGroupor a smartInputform) handle their own internal events when possible. - โ
Use
contains(x, y)to detect mouse clicks on any component.
TL;DR
- Use
InputEventinsidekontra::run()for all interaction. - Handle specific actions with
EventType::KEY_ENTER,KEY_UP, etc. - Handle printable characters with
EventType::KEY_PRESSandevent.key. - Handle clicks with
event.mouse_x/yand.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 โจ