diff --git a/meshterm/ui/tui/prompt.py b/meshterm/ui/tui/prompt.py index 3bce656..389819f 100644 --- a/meshterm/ui/tui/prompt.py +++ b/meshterm/ui/tui/prompt.py @@ -662,7 +662,7 @@ class CountdownDialog(_KeylessDialog): cell_len(self._reason), len(" Cancel "), ) - return inner + 8 + return inner + 12 # panel padding + border, plus breathing room (see ButtonDialog) @staticmethod def _clock_text(remaining: float) -> str: @@ -671,18 +671,20 @@ class CountdownDialog(_KeylessDialog): return f"Ready in {seconds}s" def render_body(self, width: int) -> list[str]: - """Draw the clock, its reason, and the Cancel chip.""" - lines: list[Text] = [Text(self._clock_text(self._remaining), style="warn")] + """Draw the clock and its reason centered above a centered Cancel chip. + + Centered line by line, the way :class:`ButtonDialog` and :class:`ProgressDialog` + draw theirs: a chip is a chip wherever it appears, and one shoved against the left + border read as an afterthought beside its siblings' centered rows. + """ + parts: list[Text] = [ + _center(Text(self._clock_text(self._remaining), style="warn"), width) + ] if self._reason: - lines.append(Text(self._reason, style="muted")) - lines.append(Text("")) - lines.append(Text(" Cancel ", style="selected")) - rendered: list[str] = [] - for line in lines: - # An empty Text renders to no lines at all on its own, so the spacer above the - # chip has to be asked for explicitly or the chip rides up against the reason. - rendered.extend(render_lines(line, width) or [""]) - return rendered + parts.append(_center(Text(self._reason, style="muted"), width)) + parts.append(Text("")) + parts.append(_center(Text(" Cancel ", style="selected"), width)) + return render_lines(Group(*parts), width) def handle(self, action: str, data: str = "") -> None: """Enter and Esc both abandon: the only chip on the dialog is Cancel.""" diff --git a/tests/test_cooldown.py b/tests/test_cooldown.py index 5402743..867dc45 100644 --- a/tests/test_cooldown.py +++ b/tests/test_cooldown.py @@ -110,6 +110,18 @@ def test_the_countdown_shows_the_clock_its_reason_and_the_way_out() -> None: assert dialog.footer_hint == "Enter/Esc cancel" +def test_the_clock_its_reason_and_the_chip_are_all_centered() -> None: + """Centered like every other dialog's body — a chip against the left border read as odd.""" + dialog = CountdownDialog("Flood advert", 47.0, reason="reaches the whole mesh") + width = dialog.dialog_width - 8 + for line in _lines(dialog): + if not line.strip(): + continue + left = len(line) - len(line.lstrip(" ")) + right = width - len(line.rstrip(" ")) + assert abs(left - right) <= 1, line + + def test_a_part_second_still_reads_as_a_second() -> None: """The clock counts the way a person would say it: 0.2s left is still "1s".""" assert "Ready in 1s" in _lines(CountdownDialog("Advert", 0.2))[0]