Coverage for src/secchi/ui/widgets/panel.py: 82%
33 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-04 22:15 +0000
1"""Reusable titled-box-with-caption panel — the mock's recurring container look."""
3from __future__ import annotations
5import logging
7from textual.app import ComposeResult
8from textual.containers import Vertical
9from textual.widget import Widget
10from textual.widgets import Static
12logger = logging.getLogger(__name__)
15class Panel(Vertical):
16 """A bordered box with an embedded title and an optional dim caption footer.
18 Children passed positionally are mounted into the body. Subclasses that need
19 to build content lazily can override `compose_body()` instead.
20 """
22 def __init__(
23 self,
24 title: str,
25 *children: Widget,
26 caption: str = "",
27 id: str | None = None,
28 classes: str | None = None,
29 ) -> None:
30 super().__init__(id=id, classes=classes)
31 self._title = title
32 self._body_children = list(children)
33 self._caption = caption
34 self.add_class("panel")
36 def on_mount(self) -> None:
37 self.border_title = self._title
39 def compose(self) -> ComposeResult:
40 body_children = self.compose_body()
41 yield PanelBody(*body_children)
42 if self._caption:
43 yield Static(self._caption, classes="panel-caption")
45 def compose_body(self) -> list[Widget]:
46 """Override to build body widgets lazily; default returns ctor children."""
47 return self._body_children
49 def set_caption(self, caption: str) -> None:
50 self._caption = caption
51 try:
52 self.query_one(".panel-caption", Static).update(caption)
53 except Exception:
54 # Captions may be updated before the lazily-built body is mounted.
55 logger.debug("Unable to update panel caption", exc_info=True)
58class PanelBody(Vertical):
59 """The flexible-height body region of a Panel (above the caption)."""
61 def __init__(self, *children: Widget) -> None:
62 super().__init__(*children)
63 self.add_class("panel-body")