aboutsummaryrefslogtreecommitdiff
path: root/app/ui/tui.py
diff options
context:
space:
mode:
authorhistoria <historiavg@proton.me>2026-08-26 19:58:45 -0400
committerhistoria <historiavg@proton.me>2026-08-26 19:58:45 -0400
commit975bd960fd07e75799b8e3adc4c0033046b34792 (patch)
tree6d5a13c0ad6eb3a6afeb5659bd9e9e21e8c215bc /app/ui/tui.py
parent5af37bbd575eec89c6ac2fecf2d8c2eda4c1728d (diff)
downloadtts-audiobook-generator-975bd960fd07e75799b8e3adc4c0033046b34792.tar.gz
feat: language and option fields in tui, context-sensitive voice label
Diffstat (limited to 'app/ui/tui.py')
-rw-r--r--app/ui/tui.py24
1 files changed, 18 insertions, 6 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py
index 56aa79e..f1c5419 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -891,7 +891,11 @@ def form(scr, title: str, fields: Sequence[dict],
"kind": "dir", "value": Path("./voices")}
Fields render as a two-column table: each label is padded to the
- widest label so every value starts in the same column. KINDS:
+ widest label so every value starts in the same column. A field's
+ ``label`` may be a callable of the field list (like ``visible`` and
+ ``choices``); it is re-resolved on every redraw, so a label can track
+ other fields' values, and sub-dialogs (choice menu, line editor,
+ directory browser) are titled with the resolved label. KINDS:
``choice`` opens a single choice menu (its ``choices`` may be a
callable of the field list, resolved when the menu opens); ``text``
opens a line editor (reusing its VALIDATE); ``bool`` shows Yes/No and
@@ -942,7 +946,11 @@ def form(scr, title: str, fields: Sequence[dict],
on_buttons = start_on_buttons
btn_index = 0
edit_cancel = object() # sentinel: backed out of a field editor
- label_w = max(len(field["label"]) for field in fields)
+
+ def field_label(field: dict) -> str:
+ """Resolve FIELD's label (a string, or a callable of FIELDS)."""
+ label = field["label"]
+ return str(label(fields)) if callable(label) else str(label)
def shown_fields() -> List[dict]:
result: List[dict] = []
@@ -992,13 +1000,17 @@ def form(scr, title: str, fields: Sequence[dict],
if help_lines:
frame.mark("")
field_rows: List[int] = [] # visible field index -> row index
+ # Labels can be callables, so the pad width is recomputed from the
+ # visible fields on every redraw (a dynamic label's length may vary).
+ label_w = max((len(field_label(field)) for field in shown),
+ default=0)
for field in shown:
if field.get("note"):
frame.mark("")
frame.mark(field["note"], frame.theme["dim"], align="left")
frame.mark("")
field_rows.append(len(frame.rows))
- name = f"{field['label']}:".ljust(label_w + 1)
+ name = f"{field_label(field)}:".ljust(label_w + 1)
frame.mark_segments(
[(name, frame.theme["body"]),
(" " + display_value(field), frame.theme["input"])],
@@ -1089,7 +1101,7 @@ def form(scr, title: str, fields: Sequence[dict],
values = [value for _, value in pairs]
default = values.index(field["value"]) \
if field["value"] in values else 0
- chosen = menu(scr, field["label"], pairs,
+ chosen = menu(scr, field_label(field), pairs,
default_index=default,
back_value=edit_cancel)
if chosen is not edit_cancel:
@@ -1099,7 +1111,7 @@ def form(scr, title: str, fields: Sequence[dict],
start = field["value"]
start = Path(start) if start else Path.cwd()
picked = browse_directory(
- scr, field["label"], start=start,
+ scr, field_label(field), start=start,
validate=field.get("validate"),
back_value=edit_cancel)
if picked is not edit_cancel:
@@ -1111,7 +1123,7 @@ def form(scr, title: str, fields: Sequence[dict],
on_buttons = True
btn_index = 0
else:
- edited = line_edit(scr, field["label"],
+ edited = line_edit(scr, field_label(field),
field["value"],
validate=field.get("validate"),
back_value=edit_cancel)