diff --git a/src/tuimble/app.py b/src/tuimble/app.py index b185d1e..e8de001 100644 --- a/src/tuimble/app.py +++ b/src/tuimble/app.py @@ -25,6 +25,7 @@ VOLUME_STEPS = (0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0) RECONNECT_INITIAL = 2 # seconds before first retry RECONNECT_MAX = 30 # maximum backoff delay RECONNECT_RETRIES = 10 # attempts before giving up +TREE_DEBOUNCE = 0.1 # seconds to coalesce state changes def _next_volume(current: float) -> float: @@ -186,8 +187,7 @@ class ChannelTree(Static): for child in children: self._collect_order(child.channel_id, order) - @property - def _available_width(self) -> int: + def _get_width(self) -> int: """Usable character width (content area, excludes padding/border).""" w = self.content_size.width if w <= 0: @@ -218,9 +218,10 @@ class ChannelTree(Static): if not self._channels: return " Channels\n [dim]\u2514\u2500 (not connected)[/]" + w = self._get_width() lines = [" [bold]Channels[/]"] root_id = self._find_root() - self._render_tree(root_id, lines, indent=1, is_last=True) + self._render_tree(root_id, lines, indent=1, is_last=True, w=w) return "\n".join(lines) def _find_root(self) -> int: @@ -235,12 +236,11 @@ class ChannelTree(Static): lines: list[str], indent: int, is_last: bool, + w: int, ) -> None: ch = self._channels.get(channel_id) if ch is None: return - - w = self._available_width prefix = " " * indent branch = "\u2514\u2500" if is_last else "\u251c\u2500" @@ -294,6 +294,7 @@ class ChannelTree(Static): lines, indent + 2, is_last=i == len(children) - 1, + w=w, ) def on_key(self, event: events.Key) -> None: @@ -412,6 +413,7 @@ class TuimbleApp(App): self._audio.input_gain = acfg.input_gain self._audio.output_gain = acfg.output_gain self._pending_reload: Config | None = None + self._tree_refresh_timer = None self._reconnecting: bool = False self._reconnect_attempt: int = 0 self._intentional_disconnect: bool = False @@ -620,7 +622,11 @@ class TuimbleApp(App): chatlog.write(f"[#7aa2f7]{msg.sender}[/] {clean}") def on_server_state_changed(self, _msg: ServerStateChanged) -> None: - self._refresh_channel_tree() + if self._tree_refresh_timer is not None: + self._tree_refresh_timer.stop() + self._tree_refresh_timer = self.set_timer( + TREE_DEBOUNCE, self._refresh_channel_tree, + ) def on_channel_selected(self, msg: ChannelSelected) -> None: if not self._client.connected: