Skip to content

Commit d2dd2ef

Browse files
committed
Fixing a textual glitch in b2view tests for windows
1 parent 7980c98 commit d2dd2ef

2 files changed

Lines changed: 47 additions & 54 deletions

File tree

src/blosc2/b2view/app.py

Lines changed: 44 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import contextlib
56
import io
67
import os
78
from typing import TYPE_CHECKING, Any, ClassVar
@@ -12,6 +13,7 @@
1213
from textual.app import App, ComposeResult
1314
from textual.binding import Binding
1415
from textual.containers import Horizontal, Vertical, VerticalScroll
16+
from textual.content import Content
1517
from textual.css.query import NoMatches
1618
from textual.screen import ModalScreen
1719
from textual.theme import Theme
@@ -26,6 +28,7 @@
2628
Static,
2729
Tree,
2830
)
31+
from textual.widgets._header import HeaderTitle
2932
from textual.widgets.option_list import Option
3033
from textual.widgets.selection_list import Selection
3134

@@ -1526,78 +1529,68 @@ def on_progress(downloaded: int, content_total: int | None) -> None:
15261529

15271530

15281531
class B2ViewHeader(Header):
1529-
"""App header that also shows the open bundle's filename, docked left.
1530-
1531-
Adds a left-docked filename label next to the stock icon plus a right-docked
1532-
spacer of the *same* width, so the title ("b2view — Python-Blosc2 X") stays
1533-
centered across the full header. The two widths are recomputed on resize so
1534-
the title's full text is reserved first and the filename only takes what is
1535-
left over (truncating, then hiding, as the terminal narrows) — the title
1536-
keeps its room as long as possible. Set the name with :meth:`set_filename`.
1532+
"""App header that also shows the open bundle's filename, left of the title.
1533+
1534+
The filename is rendered *into the stock ``HeaderTitle`` widget* (in the
1535+
space left of the centered "b2view — Python-Blosc2 X" title) rather than as
1536+
extra docked child widgets. Adding docked children to the Header was found
1537+
to break Tab focus cycling between the panels under the Windows test driver,
1538+
so this keeps the Header's widget tree exactly as Textual builds it and only
1539+
overrides what the title renders. The filename takes only the room left over
1540+
once the centered title is reserved, truncating (with an ellipsis) as the
1541+
terminal narrows. Set the name with :meth:`set_filename`.
15371542
"""
15381543

1539-
_ICON_WIDTH = 8 # HeaderIcon dock width
1540-
_CLOCK_WIDTH = 10 # HeaderClockSpace dock width
1541-
15421544
_GAP = 2 # cells kept between the filename and the centered title
15431545

15441546
DEFAULT_CSS = """
1545-
B2ViewHeader #header-filename, B2ViewHeader #header-spacer {
1546-
dock: left;
1547-
text-opacity: 85%;
1548-
text-style: italic;
1549-
text-wrap: nowrap;
1550-
text-overflow: ellipsis;
1551-
content-align: left middle;
1552-
}
1553-
B2ViewHeader #header-spacer {
1554-
dock: right;
1547+
B2ViewHeader HeaderTitle {
1548+
content-align: left middle; /* we place the title ourselves */
15551549
}
15561550
"""
15571551

15581552
def __init__(self, *args, **kwargs) -> None:
15591553
super().__init__(*args, **kwargs)
15601554
self._label = ""
15611555

1562-
def compose(self) -> ComposeResult:
1563-
yield from super().compose()
1564-
yield Static(id="header-filename")
1565-
yield Static(id="header-spacer") # mirrors the filename width to keep the title centered
1566-
15671556
def set_filename(self, label: str) -> None:
15681557
self._label = label
1569-
self._relayout()
1558+
self._refresh_title()
15701559

15711560
def on_resize(self) -> None:
1572-
self._relayout()
1561+
self._refresh_title()
1562+
1563+
def _refresh_title(self) -> None:
1564+
with contextlib.suppress(NoMatches): # HeaderTitle may not be composed yet
1565+
self.query_one(HeaderTitle).update(self.format_title())
15731566

1574-
def _relayout(self) -> None:
1575-
"""Size the filename + mirror spacer, reserving the title's width first.
1567+
def format_title(self) -> Content:
1568+
"""Render the centered title with the filename in the left gutter.
15761569
1577-
No CSS padding (the width math stays in exact cells): a leading space on
1578-
the label provides the gap from the icon.
1570+
With no filename (or no room for one) this is just the stock centered
1571+
title. Otherwise the title is left-padded so it stays centered across
1572+
the ``HeaderTitle`` region, and the filename fills the left gutter.
15791573
"""
1574+
base = super().format_title()
1575+
if not self._label:
1576+
return base
15801577
try:
1581-
fname = self.query_one("#header-filename", Static)
1582-
spacer = self.query_one("#header-spacer", Static)
1578+
width = self.query_one(HeaderTitle).content_size.width
15831579
except NoMatches:
1584-
return # not composed yet
1585-
text = f" {self._label}" if self._label else ""
1586-
fname.update(text)
1587-
title = self.app.title or ""
1588-
sub = self.app.sub_title or ""
1589-
title_len = len(title) + (len(sub) + 3 if sub else 0) # "title — sub"
1590-
# Reserve the icon, clock, the full title and a gap; split the rest
1591-
# symmetrically so the title stays centered and fully visible — the
1592-
# filename takes only the leftover, truncating then hiding as it tightens.
1593-
budget = self.size.width - self._ICON_WIDTH - self._CLOCK_WIDTH - title_len - self._GAP
1594-
each = max(0, budget // 2)
1595-
fname_w = min(len(text), each) if text else 0
1596-
show = fname_w >= 2 # below this there is not even room for " x"
1597-
for widget in (fname, spacer):
1598-
widget.display = show
1599-
if show:
1600-
widget.styles.width = fname_w
1580+
return base
1581+
title_len = base.cell_length
1582+
if width <= 0 or title_len >= width:
1583+
return base # not even room for the title alone
1584+
# Left padding that centers the title across the full HeaderTitle width.
1585+
left_pad = (width - title_len) // 2
1586+
avail = left_pad - self._GAP # cells the filename may use
1587+
if avail < 2: # below this there is not even room for " x"
1588+
return base
1589+
label = f" {self._label}"
1590+
if len(label) > avail:
1591+
label = label[: avail - 1] + "…"
1592+
gutter = " " * (left_pad - len(label))
1593+
return Content.assemble((label, "italic dim"), gutter, base)
16011594

16021595

16031596
class B2ViewApp(App):

tests/b2view/test_basics.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,7 +1140,7 @@ def fake_download(url, dst, on_progress):
11401140
assert not isinstance(app.screen, DownloadScreen)
11411141
assert app.browser is not None
11421142
assert len(app.query_one("#tree", Tree).root.children) > 0
1143-
# The header shows the @public-relative path, docked left of the title.
1144-
from textual.widgets import Static
1143+
# The header shows the @public-relative path to the left of the title.
1144+
from textual.widgets._header import HeaderTitle
11451145

1146-
assert str(app.query_one("#header-filename", Static).render()).strip() == "large/fetched.b2z"
1146+
assert "large/fetched.b2z" in app.query_one(HeaderTitle).render().plain

0 commit comments

Comments
 (0)