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:
1 parent
9d20e95590
commit
71edf6a937
6 files changed
+60
-15
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)))
|
||||
|
||||
Reference in new issue
Block a user