diff options
| author | historia <historiavg@proton.me> | 2026-09-01 14:32:05 -0400 |
|---|---|---|
| committer | historia <historiavg@proton.me> | 2026-09-01 14:32:05 -0400 |
| commit | 6cfcd564c0684c52618235e6366f4a81c02b9a5b (patch) | |
| tree | 55321760a8103bc6b5d79489fac4135a60e6e3ba /app/ui/tui.py | |
| parent | dc6e7cd43029da62dabe2513fb5aa8a34df1bd6d (diff) | |
| download | tts-audiobook-generator-6cfcd564c0684c52618235e6366f4a81c02b9a5b.tar.gz | |
slop refactor/dedup
Diffstat (limited to 'app/ui/tui.py')
| -rw-r--r-- | app/ui/tui.py | 29 |
1 files changed, 17 insertions, 12 deletions
diff --git a/app/ui/tui.py b/app/ui/tui.py index 1e95353..60a77dc 100644 --- a/app/ui/tui.py +++ b/app/ui/tui.py @@ -399,11 +399,11 @@ class Frame: # -- drawing --------------------------------------------------------- def _row_width(self, row: dict) -> int: - """Logical width of a row, including its indent.""" + """Display width of a row (wide chars count 2), plus its indent.""" if row["segments"] is not None: - return sum(len(text) for text, _ in row["segments"]) \ + return sum(_disp_width(text) for text, _ in row["segments"]) \ + 2 * row["indent"] - return len(row["text"]) + 2 * row["indent"] + return _disp_width(row["text"]) + 2 * row["indent"] def _status_extra(self) -> int: """Rows the status block needs beyond its single bottom row. @@ -417,19 +417,24 @@ class Frame: return max(0, len(self.status[0].split("\n")) - 1) def _measure(self, width: int) -> int: - """Dialog width: widest row plus frame, capped to the screen.""" - longest = max(len(self.title) + 4, len(self.footer) + 4, 40) + """Dialog width: widest row plus frame, capped to the screen. + + Measured in display columns (wide chars count 2), so CJK text + gets a dialog wide enough to hold it untruncated. + """ + longest = max(_disp_width(self.title) + 4, + _disp_width(self.footer) + 4, 40) for row in self.rows: longest = max(longest, self._row_width(row) + 4) if self.status: # Measure per line: a multi-line status must not widen the # dialog to the combined length of its lines. - longest = max(longest, max(len(line) for line + longest = max(longest, max(_disp_width(line) for line in self.status[0].split("\n")) + 6) if self.buttons: labels, _ = self.buttons longest = max(longest, - sum(len(label) + 6 for label in labels) + 4) + sum(_disp_width(label) + 6 for label in labels) + 4) return min(longest + 4, width - 2) def _flatten(self, usable: int @@ -589,7 +594,7 @@ class Frame: # A wrapped row draws only its piece; an unwrapped one (piece is # None) draws all of row["segments"] (truncated at the border). segments = piece if piece is not None else row["segments"] - total = sum(len(text) for text, _ in segments) + total = sum(_disp_width(text) for text, _ in segments) if row["align"] == "left": x = inner_x + self.LIST_MARGIN + 2 * row["indent"] else: @@ -602,8 +607,8 @@ class Frame: if not text: break _addstr(scr, y, x, text, theme["bar"] if selected else attr) - x += len(text) - room -= len(text) + x += _disp_width(text) + room -= _disp_width(text) def _draw_text_row(self, y: int, row: dict, piece: Optional[str], inner_x: int, inner_w: int, selected: bool) -> None: @@ -890,7 +895,7 @@ def menu(scr, title: str, options: Sequence[tuple], default_index: int = 0, if table_rows: if table_title: frame.mark(table_title, frame.theme["dim"], align="left") - name_w = max(len(row[0]) for row in table_rows) + name_w = max(_disp_width(row[0]) for row in table_rows) for row in table_rows: name, status, kind = row[0], row[1], row[2] name_kind = row[3] if len(row) > 3 else "body" @@ -1156,7 +1161,7 @@ def form(scr, title: str, fields: Sequence[dict], 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), + label_w = max((_disp_width(field_label(field)) for field in shown), default=0) for field in shown: if field.get("note"): |
