Input¶
Events¶
from termopy.events import Event, Key, KeyPress, Mouse, MouseKind, Paste
def keys(event: Event) -> bool:
match event:
case KeyPress(key=Key.ENTER):
submit()
case KeyPress(key="j"):
move_down()
case KeyPress(key=str() as ch) if ch.isprintable():
type_character(ch)
case _:
return False
return True
ui.on_event(keys)
Event is KeyPress | Mouse | Paste. A KeyPress carries key (a str for printable
characters, or a Key enum member for named keys) and mods, a frozenset of
Mod.CTRL, Mod.SHIFT, Mod.META.
Returning True consumes the event. Handlers behind it never see it. Returning
False (or None) passes it on.
on_event also works as a decorator:
Focus¶
A widget that owns the keyboard joins the focus ring:
active = ui.focus("search-box") # True if it currently holds the keyboard
if active:
ui.on_event(handle_typing, focused=True)
focused=True handlers run before the app's own, whatever order they were registered
in. A focused editor eats j wherever your global keymap was set up.
Tab cycles the ring; clicking inside a focusable takes it, provided the widget tagged itself:
Focus order is registration order, not layout order
Whichever focusable is constructed first owns the keyboard by default, even when it is drawn at the bottom of the screen. A filter box built before the table it sits above will swallow every keystroke meant for the app, and the symptom is "this key does nothing", which points nowhere near focus.
Say which one should own it:
Other focus controls: ui.focused, ui.set_focus(key), ui.focus_next(step).
Mouse¶
view.on_click(lambda: pick(index))
view.on_click(callback, key=("row", index)) # stable identity
view.on_click_at(lambda ctx: pick_at(ctx.pos))
view.on_right_click(show_menu)
Handlers carry their region, translated as the view is composed, so the click lands on whatever ended up at that position. The innermost handler wins.
The key matters for anything that moves. A row that scrolls between press and release
would otherwise hand its click to whatever is now at those coordinates; with a key, a
click is matched by identity instead.
Bindings¶
There is no BINDINGS list. A key table is a dict:
ACTIONS = {"q": quit, "r": refresh, Key.ESCAPE: back}
def keys(event: Event) -> bool:
if not isinstance(event, KeyPress):
return False
action = ACTIONS.get(event.key)
if action is None:
return False
action()
return True
A focused text input takes every printable key
If your app has a text box that always holds focus (a console, a search field),
d is a character. Give the user a way out that is not a letter: Esc,
or a modifier.