aboutsummaryrefslogtreecommitdiff
path: root/app/ui/tui.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/tui.py')
-rw-r--r--app/ui/tui.py40
1 files changed, 33 insertions, 7 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py
index 0391998..0d47863 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -141,6 +141,10 @@ def flash(scr, text: str, kind: str = "warn") -> None:
# ordinary character inside text editors).
_CANCEL_KEYS = (27, ord("q"))
+# A menu() option marker: a bare MENU_SEPARATOR in the options list
+# renders a blank, non-selectable divider row between option groups.
+MENU_SEPARATOR = object()
+
# ---------------------------------------------------------------------------
# Theme
@@ -661,6 +665,12 @@ def menu(scr, title: str, options: Sequence[tuple], default_index: int = 0,
notice_lines: Optional[Sequence[Tuple[str, str]]] = None):
"""Show OPTIONS as (label, value) pairs; return the chosen value.
+ Each option is ``(label, value)``, optionally ``(label, value, suffix)``
+ where SUFFIX is ``(text, kind)`` rendered in the theme color KIND after
+ the label (e.g. a yellow ``[recommended]`` tag). A bare ``MENU_SEPARATOR``
+ in the list renders a blank, non-selectable divider row, which the cursor
+ skips over.
+
The cursor starts on DEFAULT_INDEX; Enter returns the highlighted
option's value. Options are left-justified like a DOS list;
HELP_LINES are dim, centered explanatory lines shown above them.
@@ -686,9 +696,12 @@ def menu(scr, title: str, options: Sequence[tuple], default_index: int = 0,
"""
if not options:
raise ValueError("menu() needs at least one option")
+ entries = [opt for opt in options if opt is not MENU_SEPARATOR]
+ if not entries:
+ raise ValueError("menu() needs at least one selectable option")
frame = Frame(scr, title,
"Up/Down = move Enter = select Esc = cancel")
- cursor = max(0, min(default_index, len(options) - 1))
+ cursor = max(0, min(default_index, len(entries) - 1))
while True:
frame.rows = []
for line in help_lines or []:
@@ -715,21 +728,34 @@ def menu(scr, title: str, options: Sequence[tuple], default_index: int = 0,
frame.theme.get(kind, frame.theme["body"]))],
align="left")
frame.mark("")
- base = len(frame.rows)
- for label, _ in options:
- frame.mark(label, selectable=True, align="left")
- frame.cursor = base + cursor
+ cursor_rows = []
+ for opt in options:
+ if opt is MENU_SEPARATOR:
+ frame.mark("", selectable=False, align="left")
+ continue
+ label = opt[0]
+ if len(opt) > 2:
+ suffix_text, suffix_kind = opt[2]
+ frame.mark_segments(
+ [(label, frame.theme["body"]),
+ (" " + suffix_text,
+ frame.theme.get(suffix_kind, frame.theme["body"]))],
+ selectable=True, align="left")
+ else:
+ frame.mark(label, selectable=True, align="left")
+ cursor_rows.append(len(frame.rows) - 1)
+ frame.cursor = cursor_rows[cursor]
frame.draw()
key = frame.get_key(cancel_keys=())
if key in _CANCEL_KEYS and back_value is not None:
return back_value
if key in _CANCEL_KEYS:
raise WizardCancelled()
- moved = frame.motion(key, cursor, len(options), wrap=True)
+ moved = frame.motion(key, cursor, len(entries), wrap=True)
if moved is not None:
cursor = moved
elif key in (10, 13):
- return options[cursor][1]
+ return entries[cursor][1]
# ---------------------------------------------------------------------------