diff options
Diffstat (limited to 'app/ui')
| -rw-r--r-- | app/ui/hub.py | 4 | ||||
| -rw-r--r-- | app/ui/tui.py | 48 |
2 files changed, 35 insertions, 17 deletions
diff --git a/app/ui/hub.py b/app/ui/hub.py index 0e227da..c3efce6 100644 --- a/app/ui/hub.py +++ b/app/ui/hub.py @@ -403,7 +403,9 @@ def _settings_menu(stdscr) -> None: {"key": "qwen_custom_port", "label": "qwen-tts CustomVoice port", "kind": "text", "value": str(_port_from_url(config.QWEN_API_URL, 7860)), - "validate": _validate_port}, + "validate": _validate_port, + "note": "Ports apply to servers this tool starts and detecting " + "local servers"}, {"key": "qwen_clone_port", "label": "qwen-tts Base (clone) port", "kind": "text", "value": str(_port_from_url(config.CLONE_API_URL, 7861)), diff --git a/app/ui/tui.py b/app/ui/tui.py index 12c7d4f..7b6c740 100644 --- a/app/ui/tui.py +++ b/app/ui/tui.py @@ -755,16 +755,22 @@ def form(scr, title: str, fields: Sequence[dict], "kind": "text", "value": "250", "validate": lambda s: None if s.isdigit() else "digits only"} - Each field renders as a left-justified ``Label: value`` row. Up/Down - (or k/j) move the cursor; Enter on a ``choice`` row opens a single - choice menu, Enter on a ``text`` row opens a line editor (reusing its - VALIDATE for that one field). Tab, Left/Right or Up/Down move focus to - the Save/Cancel buttons — Up from the first field and Down from the - last field step straight onto them; Enter on Save validates every text - field (the first failure flashes in red and re-focuses that row) and - returns ``{key: value}``, Enter on Cancel returns BACK_VALUE. Esc (or - 'q') returns BACK_VALUE / aborts as in menu(). Values are edited in - place in the FIELDS dicts, so Cancel simply discards them. + Fields render as a two-column table: each label is padded to the + widest label so every value starts in the same column. An optional + ``note`` string on a field renders as a dim, non-selectable line in + a blank-line frame above that field's row — a section divider with a + short explanation. Up/Down (or k/j) move the cursor; Enter on a + ``choice`` row opens a single choice menu, Enter on a ``text`` row + opens a line editor (reusing its VALIDATE for that one field). Tab, + Left/Right, j or k at the ends of the list move focus to the + Save/Cancel buttons — Down from the last field and Up from the first + field both land on Save (the fields wrap onto the buttons); on the + buttons, arrows/j/k/Tab return to the fields. Enter on Save validates + every text field (the first failure flashes in red and re-focuses + that row) and returns ``{key: value}``, Enter on Cancel returns + BACK_VALUE. Esc (or 'q') returns BACK_VALUE / aborts as in menu(). + Values are edited in place in the FIELDS dicts, so Cancel simply + discards them. """ if not fields: raise ValueError("form() needs at least one field") @@ -775,17 +781,26 @@ def form(scr, title: str, fields: Sequence[dict], on_buttons = False btn_index = 0 edit_cancel = object() # sentinel: backed out of a field editor + label_w = max(len(field["label"]) for field in fields) while True: frame.rows = [] for line in help_lines or []: frame.mark(line, frame.theme["dim"]) if help_lines: frame.mark("") - base = len(frame.rows) + field_rows: List[int] = [] # field index -> row index for field in fields: - frame.mark(f"{field['label']}: {field['value']}", - selectable=True, align="left") - frame.cursor = None if on_buttons else base + cursor + 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) + frame.mark_segments( + [(name, frame.theme["body"]), + (" " + field["value"], frame.theme["input"])], + selectable=True, align="left") + frame.cursor = None if on_buttons else field_rows[cursor] frame.buttons = (["Save", "Cancel"], btn_index if on_buttons else None) frame.draw() curses = frame.curses @@ -795,7 +810,8 @@ def form(scr, title: str, fields: Sequence[dict], if key in _CANCEL_KEYS: raise WizardCancelled() if on_buttons: - if key in (9, curses.KEY_BTAB, curses.KEY_UP, curses.KEY_DOWN): + if key in (9, curses.KEY_BTAB, curses.KEY_UP, curses.KEY_DOWN, + ord("j"), ord("k")): on_buttons = False elif key in (curses.KEY_LEFT, curses.KEY_RIGHT, ord("h"), ord("l")): @@ -823,7 +839,7 @@ def form(scr, title: str, fields: Sequence[dict], btn_index = 0 # Save elif key in (curses.KEY_UP, ord("k")) and cursor == 0: on_buttons = True - btn_index = 1 # Cancel + btn_index = 0 # Save (wraps around from the top) else: moved = frame.motion(key, cursor, len(fields), wrap=True) if moved is not None: |
