The map runs right up to the panel's side borders

The bordered frame pads its body one column in from each side border,
which is air for a row of text and two columns of drawing given up on
the map. A screen can now be flush (Screen.flush), and the map is: its
body is cols - 2 wide on the desktop, base_body_size agrees through the
shared panel_inset, and the braille edge scrub drops to the one border
column. The borderless platforms already drew the full width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jean-Pierre MartineauandClaude Opus 5.5 committed 2026-10-01 23:03:01 -04:00
1 parent 9d20e95590
commit 71edf6a937
6 files changed
+60 -15

No files matched your search

+11 -7
View File
@@ -130,6 +130,9 @@ class MapScreen(Screen):
"""A full-screen, keyboard-driven map of the mesh's located nodes over an OSM basemap."""
floating = False
#: The drawing runs right up to the panel's side borders: a picture has no text edge
#: to keep off them, so the padding columns would only be map given up.
flush = True
#: Whether printable keys feed the find-as-you-type node filter. The location
#: picker turns this off: there, typing has no job and a silent filter would
@@ -299,17 +302,18 @@ class MapScreen(Screen):
"""Right-edge columns the session should force-repaint on the next paint (0 = none).
A braille glyph the terminal font lacks is substituted by a *double-width* fallback,
which shoves the row and smears the panel's right padding and border. The map body
itself redraws wholesale as it pans, so those cells self-heal — but the static edge
does not change frame-to-frame, so prompt_toolkit's differential paint never rewrites
it and the smear lingers there. After a move we ask the session to force just those
two columns (right padding + border) to repaint, scrubbing the smear without the
whole-frame flicker a full repaint would cause.
which shoves the row and smears the panel's right border. The map body itself
redraws wholesale as it pans, so its cells self-heal — but the static border does
not change frame-to-frame, so prompt_toolkit's differential paint never rewrites it
and the smear lingers there. After a move we ask the session to force just that
column to repaint, scrubbing the smear without the whole-frame flicker a full
repaint would cause. (The map is :attr:`flush`, so there is no padding column
between the drawing and the border to scrub as well.)
"""
if not self._needs_scrub:
return 0
self._needs_scrub = False
return 2 # the panel's right padding cell and its right border cell
return 1 # the panel's right border cell
def set_markers(self, markers: list[MapMarker]) -> None:
"""Replace the plotted nodes on the open map — nodes that arrived after it opened.
+18 -4
View File
@@ -158,6 +158,7 @@ def _panel_box(
more_below: bool,
border: str,
caption: str = "",
flush: bool = False,
) -> Panel:
"""Wrap already-sliced body lines in a titled, scroll-aware panel.
@@ -179,6 +180,8 @@ def _panel_box(
more_below: Whether content continues below the slice.
border: Border style name (``"accent"`` for the focused screen).
caption: A short muted credit for the bottom rule's right end (empty for none).
flush: Drop the one-column padding inside each side border, for a body that fills
the frame (see :attr:`~meshterm.ui.tui.screen.Screen.flush`).
Returns:
A Rich :class:`Panel` of exactly ``len(visible) + 2`` rows.
@@ -196,10 +199,20 @@ def _panel_box(
subtitle=subtitle,
subtitle_align="right" if caption else "center",
border_style=border,
padding=(0, 1),
padding=(0, 0 if flush else 1),
)
def panel_inset(flush: bool) -> int:
"""Columns the bordered base frame takes from the body's width.
Its two side borders, plus the padding column inside each unless the screen is
:attr:`~meshterm.ui.tui.screen.Screen.flush`. One definition, because the session sizes
a screen against this frame before the frame is drawn (``base_body_size``).
"""
return 2 if flush else 4
#: The last framed base composition: ``(content key, rendered lines)``. Re-parsing the
#: sliced body (``Text.from_ansi``) and re-rendering it through the Panel is the priciest
#: part of a repaint (~8 ms on a full frame), and the idle tick recomposes an unchanged
@@ -358,7 +371,7 @@ def compose_base(
# list inside itself (see :class:`~meshterm.ui.tui.screen.ListWindow`) can size
# its chrome to the frame it is about to be sliced into.
base.note_viewport(viewport)
body_lines = base.render_body(cols - 4)
body_lines = base.render_body(cols - panel_inset(base.flush))
visible, more_above, more_below = _visible_slice(base, body_lines, viewport)
# The panel wrap is a pure function of what's between its borders: memoize it so
# the repaints that change nothing below the header (the 1 Hz tick) skip the
@@ -372,11 +385,12 @@ def compose_base(
caption = base.bottom_caption
# Rich sets the padded title into the rule's inner ``cols - 4`` cells.
title = fitted_title(base, cols - 4 - 2)
key = (cols, rows, title, caption, footer_hint, more_above, more_below, *visible)
flush = base.flush
key = (cols, rows, title, caption, flush, footer_hint, more_above, more_below, *visible)
if footer_lane is None and _BASE_BOX_CACHE is not None and _BASE_BOX_CACHE[0] == key:
body = _BASE_BOX_CACHE[1]
else:
panel = _panel_box(title, visible, more_above, more_below, "accent", caption)
panel = _panel_box(title, visible, more_above, more_below, "accent", caption, flush)
body = render_lines(Group(panel, footer_row()), cols)
if footer_lane is None:
_BASE_BOX_CACHE = (key, body)
+7
View File
@@ -106,12 +106,19 @@ class Screen:
screen may change it as its state changes. Drawn only where the platform's
frame *has* a bottom rule — the borderless one has none, and a screen wanting
a mark there puts it in its own body instead.
flush: Whether, as the base screen in a bordered frame, the body runs right up to
the panel's side borders instead of sitting one blank column in from each. The
padding is air between a border and a row of *text*; a picture that fills the
frame (the map) has no text edge to keep off the border, and those two columns
are drawing it would otherwise give up for nothing (JP, 2026-10-01). Moot where
the platform's frame has no border — the body is the full width there already.
"""
title: str = ""
short_title: str = ""
footer_hint: str = "Esc back"
bottom_caption: str = ""
flush: bool = False
floating: bool = True
modal: bool = False
grow_only: bool = False
+7 -3
View File
@@ -1813,7 +1813,9 @@ class TuiSession:
without waiting a repaint to learn its height. *Both* of that function's branches:
a borderless platform swaps the panel's two border rows and four padding columns for
a single title-bar row, so a screen sizing itself against the bordered math there
would leave a row of the frame it was handed permanently blank.
would leave a row of the frame it was handed permanently blank. The width is the
current base screen's, which a :attr:`~meshterm.ui.tui.screen.Screen.flush` one
widens by the two padding columns it does without.
Returns:
The inner content width and the body viewport height, both in character cells.
@@ -1823,8 +1825,10 @@ class TuiSession:
cols, rows = self._size()
header_h = len(render_lines(self._header(cols), cols, no_wrap=True))
if get_platform().frame_border:
# minus footer(1) and panel border(2); the border eats 4 columns too
return cols - 4, max(1, rows - header_h - 1 - 2)
base = self._base_screen()
inset = frame.panel_inset(base is not None and base.flush)
# minus footer(1) and panel border(2)
return cols - inset, max(1, rows - header_h - 1 - 2)
return cols, max(1, rows - header_h - 1 - 1) # minus footer(1) and title bar(1)
def _base_index(self) -> int:
+1 -1
View File
@@ -1891,7 +1891,7 @@ def test_map_screen_scrubs_right_edge_after_move() -> None:
braille = MapScreen(_StubSession(80, 24), markers, _StubSource(), 14)
braille.render_body(80)
braille.handle("right") # a move flags the edge for a scrub
assert braille.consume_edge_scrub() == 2 # right padding + border columns
assert braille.consume_edge_scrub() == 1 # the border column: the map is flush
assert braille.consume_edge_scrub() == 0 # consumed — not repeated without another move
+16
View File
@@ -743,6 +743,22 @@ def test_compose_base_fills_exactly_terminal_height() -> None:
assert out.count("\n") + 1 == rows
def test_a_flush_screen_draws_up_to_the_side_borders() -> None:
"""A ``flush`` base gets the two padding columns back; an ordinary one keeps its air."""
class Fill(Screen):
floating = False
def render_body(self, width: int) -> list[str]:
return ["#" * width] * 3
for flush, row in ((True, "│" + "#" * 78 + "│"), (False, "│ " + "#" * 76 + " │")):
screen = Fill()
screen.flush = flush
lines = [_plain(ln) for ln in frame.compose_base(Text("h"), screen, "", 80, 12).split("\n")]
assert row in lines, (flush, lines)
def test_compose_dialog_is_bounded() -> None:
"""A dialog for a huge renderable never exceeds the terminal height."""
tall = Text("\n".join(f"row {i}" for i in range(200)))