Files
Autoparts-DB/console/core/keybindings.py
consultoria-as ceacab789b feat(console): add core framework - keybindings, navigation, screen base
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>
2026-02-15 01:38:02 +00:00

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)