aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/tests/test_hub.py9
-rw-r--r--app/tests/test_tui.py26
-rw-r--r--app/ui/hub.py7
-rw-r--r--app/ui/tui.py12
4 files changed, 52 insertions, 2 deletions
diff --git a/app/tests/test_hub.py b/app/tests/test_hub.py
index a822f1a..ef246ad 100644
--- a/app/tests/test_hub.py
+++ b/app/tests/test_hub.py
@@ -967,8 +967,12 @@ class ConvertFlowTests(unittest.TestCase):
# The model menu was fed from the live query (label, id); ids are
# padded so the capability columns line up across entries. A mixed
# tts+clone family reads as the "tts" and "clone" columns.
- self.assertEqual(self._field("model_id")["choices"],
+ model_field = self._field("model_id")
+ self.assertEqual(model_field["choices"],
[("higgs tts clone", "higgs")])
+ # The padded table lives in the pick menu only: the form row
+ # collapses its column padding back to the gutter.
+ self.assertTrue(model_field["compact_label"])
def test_model_menu_lines_the_type_column_up(self):
# Ids are padded to the widest id, and every capability word sits
@@ -1816,6 +1820,9 @@ class ConvertFlowTests(unittest.TestCase):
("VoiceDesign".ljust(11) + " (design)", "design")])
self.assertEqual({label.index("(") for label, _ in
mode_field["choices"]}, {13})
+ # The padded table lives in the pick menu only: the form row
+ # collapses its column padding back to the gutter.
+ self.assertTrue(mode_field["compact_label"])
speaker_field = self._field("speaker")
clone_dir_field = self._field("clone_dir")
# The .wav directory browser alerts on the .wavs it lists.
diff --git a/app/tests/test_tui.py b/app/tests/test_tui.py
index 085a1f1..e3383cb 100644
--- a/app/tests/test_tui.py
+++ b/app/tests/test_tui.py
@@ -631,6 +631,32 @@ class FormTests(TuiTestCase):
self.assertNotIn(" qwen", painted)
self.assertNotIn(" clone", painted)
+ def test_compact_label_collapses_padded_choice_on_the_row(self):
+ # A choice field may opt into a compact row display: the pick
+ # menu keeps the padded table label (aligned capability columns),
+ # while the painted row collapses its runs of column padding back
+ # to the two-space gutter. Fields without the flag paint the
+ # label verbatim.
+ padded = "supertonic".ljust(22) + " tts"
+ fields = [
+ {"key": "model", "label": "Model", "kind": "choice",
+ "value": "supertonic",
+ "choices": [(padded, "supertonic"),
+ ("a-much-longer-model-id".ljust(22) + " tts",
+ "a-much-longer-model-id")],
+ "compact_label": True},
+ {"key": "plain", "label": "Plain", "kind": "choice",
+ "value": "a", "choices": [("a b", "a")]},
+ ]
+ screen = FakeScreen(keys=[27])
+ marker = object()
+ self.assertIs(tui.form(screen, "Form", fields, back_value=marker),
+ marker)
+ painted = [text for _, _, text, _ in screen.strings]
+ self.assertIn(" supertonic tts", painted)
+ self.assertNotIn(padded, painted)
+ self.assertIn(" a b", painted)
+
def test_text_field_edits_then_saves(self):
# Down to the text row, Enter opens the editor, type 'x', Enter,
# then Tab -> Save, Enter.
diff --git a/app/ui/hub.py b/app/ui/hub.py
index abb375a..de5701f 100644
--- a/app/ui/hub.py
+++ b/app/ui/hub.py
@@ -1362,6 +1362,9 @@ def _audiocpp_fields(stdscr, api_url: Optional[str] = None,
{"key": prefix + "model_id", "label": "Model", "kind": "choice",
"value": default_model,
"choices": [(_label(m), m.get("id")) for m in models],
+ # The pick menu shows the padded capability table; the form row
+ # collapses its column padding back to the two-space gutter.
+ "compact_label": True,
"on_change": reset_voice},
# The label tracks the entry's capability: a built-in speaker on
# CustomVoice, otherwise the name of a server-side voice to clone.
@@ -1513,7 +1516,9 @@ def _qwen_fields(remote_modes: Optional[list] = None,
initial_clone = str(initial_wavs[0]) if initial_wavs else ""
fields = [
{"key": prefix + "mode", "label": "Model", "kind": "choice",
- "value": default_mode, "choices": model_choices},
+ "value": default_mode, "choices": model_choices,
+ # Same as the audio.cpp picker: padded menu table, compact row.
+ "compact_label": True},
{"key": prefix + "speaker", "label": "Speaker", "kind": "choice",
"value": default_speaker, "choices": speakers,
"visible": lambda fs: _field_value(fs, prefix + "mode") == "custom"},
diff --git a/app/ui/tui.py b/app/ui/tui.py
index a4854b0..1e95353 100644
--- a/app/ui/tui.py
+++ b/app/ui/tui.py
@@ -1035,6 +1035,11 @@ def form(scr, title: str, fields: Sequence[dict],
keep their value across hide/show. A field may set ``on_change`` to
a callable of the field list, invoked whenever its value changes so
dependent fields (choices, visibility, defaults) can be recomputed.
+ A ``choice`` field may set ``compact_label`` to show its picked
+ label on the form row with runs of spaces collapsed back to the
+ two-space gutter — the pick menu always shows the full label, so a
+ padded table (aligned capability columns) reads aligned there and
+ compact on the row.
A choice field whose resolved list is empty cannot be opened: Enter
is a no-op, or flashes the field's optional ``on_empty_choices``
message (string or callable of the field list) — an explanation the
@@ -1130,6 +1135,13 @@ def form(scr, title: str, fields: Sequence[dict],
and len(choices[0]) == 2:
for label, value in choices:
if value == field["value"]:
+ if field.get("compact_label"):
+ # The pick menu keeps the full padded label
+ # (e.g. the model picker's aligned capability
+ # table); the form row collapses its runs of
+ # column padding back to the two-space gutter
+ # so a short pick reads without a far gap.
+ return re.sub(" {2,}", " ", label)
return label
return str(field["value"])