diff --git a/meshterm/ui/node_detail_screen.py b/meshterm/ui/node_detail_screen.py index bfba82d..bcd0580 100644 --- a/meshterm/ui/node_detail_screen.py +++ b/meshterm/ui/node_detail_screen.py @@ -23,7 +23,7 @@ screen is the node itself, in full: faint ``…`` marks at the edges, the same as a long path line scrolls on the Routes tab. Then, when the node has advertised a location, a static basemap preview (refer to :class:`~meshterm.ui.minimap.MiniMap`) centred on the node. The preview grows into - the rows of the viewport that remain. + the rows of the viewport that remain. A short frame (the Cardputer) does not show it. The actions of the tab: ``Open full map`` opens the full map, centred here, with its find filter set to this node, so the node is lit among the others. Then @@ -884,7 +884,7 @@ class NodeDetailScreen(Screen): if tab is not None: if tab.kind == "info": budget = viewport - len(lines) - action_lines - 1 # the line of the rule - lines.extend(self._info_stage(width, budget)) + lines.extend(self._info_stage(width, budget, short=short)) else: route_blocks = [ self._route_row_lines(route, i == self._row_index, width) @@ -936,17 +936,18 @@ class NodeDetailScreen(Screen): self._scroll_total = max(1, len(lines)) return lines - def _info_stage(self, width: int, budget: int) -> list[str]: + def _info_stage(self, width: int, budget: int, *, short: bool = False) -> list[str]: """The block of vitals and, with a fix, the location preview grown to fit. ``budget`` is the number of visible lines that remain for the full stage. The rows of vitals are never truncated. The preview changes its size instead: it takes the - lines that the vitals and its caption leave, up to ``_MAP_MAX_ROWS``. When that is - less than ``_MAP_MIN_ROWS``, the preview is not shown at all (a short frame keeps - its header and actions visible instead). Each row, but not the key, hangs under - itself when it wraps. The key keeps its one line and scrolls (refer to - :meth:`_key_line`). Thus the row count that sets the size of the preview cannot - change while the user scrolls through a long key. + lines that the vitals and its caption leave, between ``_MAP_MIN_ROWS`` and + ``_MAP_MAX_ROWS``. When the vitals leave less than the minimum, the preview keeps + its minimum and the page scrolls. On a ``short`` frame, the preview is not shown at + all, because its minimum alone pushes the header or the actions off the screen. + Each row, but not the key, hangs under itself when it wraps. The key keeps its one + line and scrolls (refer to :meth:`_key_line`). Thus the row count that sets the + size of the preview cannot change while the user scrolls through a long key. """ lines: list[str] = [""] * tab_air() # the space of the stage under the strip, if any for label, value in self._info_rows: @@ -961,17 +962,16 @@ class NodeDetailScreen(Screen): indent=_LABEL_LANE, ) ) - if self._minimap is not None: + # A short frame has no rows for the preview. The 14 rows of the Cardputer leave it + # two, and its minimum pushes the identity header and the actions off the screen. + if self._minimap is not None and not short: caption = 1 if self._map_caption is not None else 0 room = budget - len(lines) - 1 - caption # minus the blank line above it - # Below its minimum, the preview is not shown. It does not push the identity - # header and the actions off a short frame (the 14 rows of the Cardputer leave - # it two). - if room >= _MAP_MIN_ROWS: - lines.append("") - lines.extend(self._minimap.render(width, min(_MAP_MAX_ROWS, room))) - if self._map_caption is not None: - lines.extend(render_lines(self._map_caption, width, no_wrap=True)) + rows = max(_MAP_MIN_ROWS, min(_MAP_MAX_ROWS, room)) + lines.append("") + lines.extend(self._minimap.render(width, rows)) + if self._map_caption is not None: + lines.extend(render_lines(self._map_caption, width, no_wrap=True)) return lines def _key_line(self, label: str, value: Text, width: int) -> str: diff --git a/tests/test_node_detail.py b/tests/test_node_detail.py index 21294a0..eba72b1 100644 --- a/tests/test_node_detail.py +++ b/tests/test_node_detail.py @@ -1369,6 +1369,39 @@ def test_node_detail_screen_tabs_switch_the_stage() -> None: assert screen.picocalc_lyra_lane[2].label == "Info" +def test_node_detail_preview_keeps_its_minimum_except_on_a_short_frame() -> None: + """The preview shows at its minimum when the actions leave less. A short frame has none. + + A contact page has many action rows, so on a terminal of 24 rows they leave the preview + less than its minimum. The preview keeps its minimum and the page scrolls. The 11-row + viewport of the Cardputer is a short frame, and there the preview is not shown. + """ + mini = MiniMap( + _FakeSession(), + _OfflineSource(), + 14, + center_lat=45.5, + center_lon=-73.6, + zoom=13, + markers=[MapMarker(label="Hub", lat=45.5, lon=-73.6, key="3d63aa")], + ) + actions = [_Action(f"a{i}", "⚙", "", f"Action {i}") for i in range(8)] + screen = _screen( + minimap=mini, + map_caption=Text("Hub · centred here", style="faint"), + info_actions=actions, + ) + screen.note_viewport(21) + body = _plain(screen.render_body(72)).splitlines() + caption = next(i for i, line in enumerate(body) if "centred here" in line) + vitals = next(i for i, line in enumerate(body) if line.startswith("packets")) + assert caption - vitals - 2 >= 5 # the rows between the blank line and the caption + assert len(body) > 21 # the preview did not shrink under its minimum: the page scrolls + + screen.note_viewport(11) + assert "centred here" not in _plain(screen.render_body(53)) + + def test_node_detail_single_tab_hides_the_switch_hint() -> None: """A page with only one tab does not have the ←→ tab atom in its footer.""" screen = _screen(tabs=[_Tab("Info", "info")]) # our node: no Routes tab