""" Dynamic device tabs widget. """ from textual.widgets import Static, Button from textual.containers import Horizontal from textual.message import Message from textual.reactive import reactive class DeviceTabs(Horizontal): """Tab bar for device switching with dynamic updates.""" DEFAULT_CSS = """ DeviceTabs { height: 1; width: 100%; background: $surface; padding: 0; } DeviceTabs .tab-label { padding: 0 1; height: 1; min-width: 8; } DeviceTabs .tab-label.active { background: $primary; color: $text; text-style: bold; } DeviceTabs .tab-label:hover { background: $primary-darken-1; } DeviceTabs .header-label { padding: 0 1; height: 1; color: $text-muted; } DeviceTabs .separator { padding: 0; height: 1; color: $text-muted; } DeviceTabs .device-count { dock: right; padding: 0 1; height: 1; color: $text-muted; } """ active_tab: reactive[str] = reactive("global") devices_hidden: reactive[bool] = reactive(False) class TabSelected(Message): """Posted when a tab is selected.""" def __init__(self, tab_id: str, device_id: str | None = None): self.tab_id = tab_id self.device_id = device_id super().__init__() def __init__(self, **kwargs): super().__init__(**kwargs) self._devices: list[str] = [] def compose(self): yield Static("C3PO", classes="header-label", id="c3po-label") yield Static(" \u2500 ", classes="separator") yield Static("[G]lobal", classes="tab-label active", id="tab-global") yield Static(" [H]ide", classes="tab-label", id="tab-hide") yield Static("", classes="device-count", id="device-count") def add_device(self, device_id: str): """Add a device tab.""" if device_id not in self._devices: self._devices.append(device_id) self._rebuild_tabs() def remove_device(self, device_id: str): """Remove a device tab.""" if device_id in self._devices: self._devices.remove(device_id) if self.active_tab == device_id: self.active_tab = "global" self._rebuild_tabs() def _rebuild_tabs(self): """Rebuild all tabs.""" for widget in list(self.children): if hasattr(widget, 'id') and widget.id and widget.id.startswith("tab-device-"): widget.remove() hide_tab = self.query_one("#tab-hide", Static) for i, device_id in enumerate(self._devices): if i < 9: label = f"[{i+1}]{device_id}" tab = Static( label, classes="tab-label" + (" active" if self.active_tab == device_id else ""), id=f"tab-device-{device_id}" ) self.mount(tab, before=hide_tab) count_label = self.query_one("#device-count", Static) count_label.update(f"{len(self._devices)} device{'s' if len(self._devices) != 1 else ''}") def select_tab(self, tab_id: str): """Select a tab by ID.""" if tab_id == "global": self.active_tab = "global" self.post_message(self.TabSelected("global")) elif tab_id in self._devices: self.active_tab = tab_id self.post_message(self.TabSelected(tab_id, tab_id)) self._update_active_styles() def select_by_index(self, index: int): """Select device tab by numeric index (1-9).""" if 0 < index <= len(self._devices): device_id = self._devices[index - 1] self.select_tab(device_id) def toggle_hide(self): """Toggle device panes visibility.""" self.devices_hidden = not self.devices_hidden hide_tab = self.query_one("#tab-hide", Static) hide_tab.update("[H]ide" if not self.devices_hidden else "[H]show") def _update_active_styles(self): """Update tab styles to show active state.""" for tab in self.query(".tab-label"): tab.remove_class("active") if self.active_tab == "global": self.query_one("#tab-global", Static).add_class("active") else: try: self.query_one(f"#tab-device-{self.active_tab}", Static).add_class("active") except Exception: pass def on_click(self, event) -> None: """Handle tab clicks.""" target = event.target if hasattr(target, 'id') and target.id: if target.id == "tab-global": self.select_tab("global") elif target.id == "tab-hide": self.toggle_hide() elif target.id.startswith("tab-device-"): device_id = target.id.replace("tab-device-", "") self.select_tab(device_id)