Add the three core modules that all screens depend on: - keybindings.py: Key constants (curses codes) and KeyBindings registry - navigation.py: Stack-based screen navigation with breadcrumbs - screens.py: Screen base class with on_enter/on_key/render lifecycle Includes 31 tests covering all public APIs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
2.2 KiB
Python
88 lines
2.2 KiB
Python
"""
|
|
Key constants and key-binding registry for the console UI.
|
|
|
|
Key provides named constants matching curses key codes so that screens
|
|
and renderers never need to import curses directly.
|
|
|
|
KeyBindings maps key codes to callable actions and tracks the footer
|
|
labels displayed at the bottom of the screen.
|
|
"""
|
|
|
|
import curses
|
|
|
|
|
|
class Key:
|
|
"""Key constants matching curses key codes."""
|
|
|
|
ESCAPE = 27
|
|
ENTER = 10
|
|
TAB = 9
|
|
BACKSPACE = 127
|
|
|
|
UP = curses.KEY_UP
|
|
DOWN = curses.KEY_DOWN
|
|
LEFT = curses.KEY_LEFT
|
|
RIGHT = curses.KEY_RIGHT
|
|
|
|
PGUP = curses.KEY_PPAGE
|
|
PGDN = curses.KEY_NPAGE
|
|
HOME = curses.KEY_HOME
|
|
END = curses.KEY_END
|
|
|
|
F1 = curses.KEY_F1
|
|
F2 = curses.KEY_F2
|
|
F3 = curses.KEY_F3
|
|
F4 = curses.KEY_F4
|
|
F5 = curses.KEY_F5
|
|
F6 = curses.KEY_F6
|
|
F7 = curses.KEY_F7
|
|
F8 = curses.KEY_F8
|
|
F9 = curses.KEY_F9
|
|
F10 = curses.KEY_F10
|
|
|
|
|
|
class KeyBindings:
|
|
"""Registry that maps key codes to callable actions.
|
|
|
|
Usage::
|
|
|
|
kb = KeyBindings()
|
|
kb.bind(Key.ENTER, lambda: do_something())
|
|
handled = kb.handle(Key.ENTER) # True, callback was invoked
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._bindings: dict[int, callable] = {}
|
|
self._footer_labels: list[tuple[str, str]] = []
|
|
|
|
def bind(self, key: int, action: callable) -> None:
|
|
"""Register *action* as the callback for *key*.
|
|
|
|
If *key* already has a binding it is replaced.
|
|
"""
|
|
self._bindings[key] = action
|
|
|
|
def handle(self, key: int) -> bool:
|
|
"""Look up *key* and invoke its callback if one exists.
|
|
|
|
Returns ``True`` if a callback was found and executed,
|
|
``False`` otherwise.
|
|
"""
|
|
action = self._bindings.get(key)
|
|
if action is not None:
|
|
action()
|
|
return True
|
|
return False
|
|
|
|
def set_footer(self, labels: list[tuple[str, str]]) -> None:
|
|
"""Set the footer bar labels.
|
|
|
|
*labels* is a list of ``(key_label, description)`` tuples, e.g.
|
|
``[("F1", "Help"), ("F10", "Quit")]``.
|
|
"""
|
|
self._footer_labels = list(labels)
|
|
|
|
def get_footer_labels(self) -> list[tuple[str, str]]:
|
|
"""Return the current footer labels list."""
|
|
return list(self._footer_labels)
|