diff options
| author | historia <historiavg@proton.me> | 2026-08-24 21:10:43 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-08-24 21:10:43 -0400 |
| commit | f4b1de303704e13818259d5057d176cd841b6ed8 (patch) | |
| tree | 5c3c4e1495819434deddc223c1b4a670b3b7325e /app/ui | |
| parent | 610d6828fa8300efa3f8df3d3bf8e3c00e24c1dc (diff) | |
| download | tts-audiobook-generator-f4b1de303704e13818259d5057d176cd841b6ed8.tar.gz | |
feat: add confirm/cancel buttons to checkbox tui
Diffstat (limited to 'app/ui')
| -rw-r--r-- | app/ui/tui.py | 151 |
1 files changed, 92 insertions, 59 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py index f1abea1..0391998 100644 --- a/app/ui/tui.py +++ b/app/ui/tui.py @@ -1158,18 +1158,21 @@ def checkbox_tree(scr, title: str, families: List[dict], ], } - Space on a family row checks its recommended option (or clears every - option when one is already checked); Space on an option row toggles - that option. Tab/Right expands or collapses the family under the - cursor. Enter returns the flat list of (family_index, option_key) - pairs for every checked option, in tree order; at least one checked - option is required. Nothing is checked by default, and with - EXPAND_ALL every family starts expanded. CHECKED (a set of - (family_index, option_key) pairs) pre-checks those options instead, - expanding every family that holds a checked option and placing the - cursor on the first such family — the "modify an existing config" - entry point. A "[recommended]" tag is shown only when a family has - more than one option — a single option needs no tag. + Space or Enter on a family row checks its recommended option (or + clears every option when one is already checked); Space or Enter on + an option row toggles that option. Right/l expands or collapses the + family under the cursor, Left/h collapses it. Tab (or Up/Down at the + ends of the list) moves the focus to the Confirm/Back buttons: Enter + on Confirm returns the flat list of (family_index, option_key) pairs + for every checked option, in tree order, and requires at least one + checked option; Enter on Back returns BACK_VALUE. Nothing is checked + by default and every family starts collapsed; with EXPAND_ALL every + family starts expanded. CHECKED (a set of (family_index, option_key) + pairs) pre-checks those options instead, expanding every family that + holds a checked option and placing the cursor on the first such + family — the "modify an existing config" entry point. A + "[recommended]" tag is shown only when a family has more than one + option — a single option needs no tag. Family and option rows are left-justified like a DOS list. Esc (or 'q') aborts the wizard unless BACK_VALUE is given (not None), in which case either key returns it so the caller can fall back a @@ -1177,18 +1180,14 @@ def checkbox_tree(scr, title: str, families: List[dict], """ if not families: raise ValueError("checkbox_tree() needs at least one family") - footer = footer or ("Up/Down = move Tab/Right = expand Space = check " - "Enter = accept Esc = cancel") + footer = footer or ("Up/Down = move Enter/Space = check " + "Left/Right = expand Tab = buttons Esc = cancel") frame = Frame(scr, title, footer) expanded = {index for index in range(len(families))} if expand_all else set() checked = set(checked or ()) # (family_index, option_key) - if checked: - for index, _option_key in checked: - expanded.add(index) - expanded.add(0) - else: - expanded.add(0) + for index, _option_key in checked: + expanded.add(index) def family_checked(index: int) -> bool: return any(pair[0] == index for pair in checked) @@ -1211,6 +1210,8 @@ def checkbox_tree(scr, title: str, families: List[dict], first_checked = min((index for index, _option_key in checked), default=None) cursor = 0 + on_buttons = False + btn_index = 0 while True: nodes = visible_nodes() if first_checked is not None: @@ -1249,9 +1250,14 @@ def checkbox_tree(scr, title: str, families: List[dict], segments.append((" [recommended]", frame.theme["warn"])) frame.mark_segments(segments, indent=2, selectable=True, align="left") - frame.cursor = cursor - node = nodes[cursor] - frame.status = (families[node[1]].get("detail", ""), "info") + frame.cursor = None if on_buttons else cursor + frame.buttons = (["Confirm", "Back"], + btn_index if on_buttons else None) + if on_buttons: + frame.status = None + else: + node = nodes[cursor] + frame.status = (families[node[1]].get("detail", ""), "info") frame.draw() curses = frame.curses key = frame.get_key(cancel_keys=()) @@ -1259,41 +1265,68 @@ def checkbox_tree(scr, title: str, families: List[dict], return back_value if key in _CANCEL_KEYS: raise WizardCancelled() - moved = frame.motion(key, cursor, len(nodes), wrap=True) - if moved is not None: - cursor = moved - elif key in (9, curses.KEY_RIGHT, ord("l")) and node[0] == "family": - index = node[1] - if index in expanded: - expanded.discard(index) + if on_buttons: + if key in (curses.KEY_LEFT, curses.KEY_RIGHT, ord("h"), ord("l")): + btn_index = 1 - btn_index + elif key in (curses.KEY_UP, ord("k"), curses.KEY_BTAB): + on_buttons = False + cursor = len(nodes) - 1 if nodes else 0 + elif key in (curses.KEY_DOWN, ord("j"), 9): + on_buttons = False + cursor = 0 + elif key in (10, 13): + if btn_index == 0: # Confirm + selection = accept() + if selection: + return selection + frame.flash("Check at least one model package", "err") + else: # Back + return back_value + else: + node = nodes[cursor] + if key in (curses.KEY_DOWN, ord("j")) \ + and cursor == len(nodes) - 1: + on_buttons = True + btn_index = 0 + elif key in (curses.KEY_UP, ord("k")) and cursor == 0: + on_buttons = True + btn_index = 0 + elif key in (9, curses.KEY_BTAB): + on_buttons = True + btn_index = 0 else: - expanded.add(index) - elif key == curses.KEY_LEFT and node[0] == "family": - expanded.discard(node[1]) - elif key == ord(" "): - if node[0] == "family": - index = node[1] - options = families[index]["options"] - if family_checked(index): - for option in options: - checked.discard((index, option["key"])) - else: - for option in options: - if option.get("recommended"): - checked.add((index, option["key"])) - break + moved = frame.motion(key, cursor, len(nodes), wrap=True) + if moved is not None: + cursor = moved + elif key in (curses.KEY_RIGHT, ord("l")) \ + and node[0] == "family": + index = node[1] + if index in expanded: + expanded.discard(index) else: - if options: - checked.add((index, options[0]["key"])) - expanded.add(index) - else: - _, index, option_key = node - if (index, option_key) in checked: - checked.discard((index, option_key)) - else: - checked.add((index, option_key)) - elif key in (10, 13): # Enter: accept the checked selection - selection = accept() - if selection: - return selection - frame.flash("Check at least one model package (Space)", "err") + expanded.add(index) + elif key in (curses.KEY_LEFT, ord("h")) \ + and node[0] == "family": + expanded.discard(node[1]) + elif key in (10, 13, ord(" ")): + if node[0] == "family": + index = node[1] + options = families[index]["options"] + if family_checked(index): + for option in options: + checked.discard((index, option["key"])) + else: + for option in options: + if option.get("recommended"): + checked.add((index, option["key"])) + break + else: + if options: + checked.add((index, options[0]["key"])) + expanded.add(index) + else: + _, index, option_key = node + if (index, option_key) in checked: + checked.discard((index, option_key)) + else: + checked.add((index, option_key)) |
