diff options
| author | historia <historiavg@proton.me> | 2026-08-24 23:49:34 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-24 23:49:34 -0400 |
| commit | fe4b2b9eb7fb8aac81f65630720c9079d0a3121a (patch) | |
| tree | 9c5e5f56d0b931d25e6580f10d09453505eddc2f /app/ui/tui.py | |
| parent | f4b1de303704e13818259d5057d176cd841b6ed8 (diff) | |
| download | tts-audiobook-generator-fe4b2b9eb7fb8aac81f65630720c9079d0a3121a.tar.gz | |
feat: user-friendly menu gating, clearer install/configure path for backends
Diffstat (limited to 'app/ui/tui.py')
| -rw-r--r-- | app/ui/tui.py | 40 |
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] # --------------------------------------------------------------------------- |
