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>
61 lines
2.0 KiB
Python
61 lines
2.0 KiB
Python
"""
|
|
Screen-stack navigation for the console UI.
|
|
|
|
Navigation maintains a stack of ``(screen_name, context, label)`` entries.
|
|
Screens push onto the stack when the user drills into a sub-view and pop
|
|
when they press Escape / Backspace to go back.
|
|
"""
|
|
|
|
|
|
class Navigation:
|
|
"""A simple stack-based navigator.
|
|
|
|
Each entry is a tuple ``(screen_name, context, label)`` where
|
|
*screen_name* identifies which screen to display, *context* carries
|
|
any data the screen needs, and *label* is the human-readable text
|
|
shown in the breadcrumb trail.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._stack: list[tuple[str, object, str]] = []
|
|
|
|
def push(self, screen_name: str, context=None, label: str | None = None) -> None:
|
|
"""Push a new screen onto the stack.
|
|
|
|
If *label* is ``None`` the *screen_name* is used as fallback in
|
|
the breadcrumb.
|
|
"""
|
|
self._stack.append((screen_name, context, label if label is not None else screen_name))
|
|
|
|
def pop(self) -> tuple[str, object] | None:
|
|
"""Remove and return the top entry as ``(screen_name, context)``.
|
|
|
|
Returns ``None`` if the stack is empty.
|
|
"""
|
|
if not self._stack:
|
|
return None
|
|
screen_name, context, _label = self._stack.pop()
|
|
return (screen_name, context)
|
|
|
|
def current(self) -> tuple[str, object] | None:
|
|
"""Return the top entry as ``(screen_name, context)`` without removing it.
|
|
|
|
Returns ``None`` if the stack is empty.
|
|
"""
|
|
if not self._stack:
|
|
return None
|
|
screen_name, context, _label = self._stack[-1]
|
|
return (screen_name, context)
|
|
|
|
def breadcrumb(self) -> list[str]:
|
|
"""Return the list of labels from bottom to top of the stack."""
|
|
return [label for _name, _ctx, label in self._stack]
|
|
|
|
def clear(self) -> None:
|
|
"""Remove all entries from the stack."""
|
|
self._stack.clear()
|
|
|
|
def depth(self) -> int:
|
|
"""Return the number of entries on the stack."""
|
|
return len(self._stack)
|