aboutsummaryrefslogtreecommitdiff
path: root/app/ui/tui.py
diff options
context:
space:
mode:
Diffstat (limited to 'app/ui/tui.py')
-rw-r--r--app/ui/tui.py29
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"):