app: add volume key bindings and status display
This commit is contained in:
@@ -20,6 +20,16 @@ from tuimble.ptt import KittyPtt, TogglePtt, detect_backend
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
VOLUME_STEPS = (0.0, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0)
|
||||||
|
|
||||||
|
|
||||||
|
def _next_volume(current: float) -> float:
|
||||||
|
"""Cycle through VOLUME_STEPS, wrapping to 0.0 after max."""
|
||||||
|
for step in VOLUME_STEPS:
|
||||||
|
if step > current + 0.01:
|
||||||
|
return step
|
||||||
|
return VOLUME_STEPS[0]
|
||||||
|
|
||||||
|
|
||||||
# -- custom messages (pymumble thread -> Textual) ----------------------------
|
# -- custom messages (pymumble thread -> Textual) ----------------------------
|
||||||
|
|
||||||
@@ -63,6 +73,14 @@ class StatusBar(Static):
|
|||||||
connected = reactive(False)
|
connected = reactive(False)
|
||||||
self_deaf = reactive(False)
|
self_deaf = reactive(False)
|
||||||
server_info = reactive("")
|
server_info = reactive("")
|
||||||
|
output_vol = reactive(100)
|
||||||
|
input_vol = reactive(100)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _vol_bar(pct: int) -> str:
|
||||||
|
"""Compact 4-char volume indicator using block chars."""
|
||||||
|
filled = round(pct / 25)
|
||||||
|
return "\u2588" * filled + "\u2591" * (4 - filled)
|
||||||
|
|
||||||
def render(self) -> str:
|
def render(self) -> str:
|
||||||
w = self.content_size.width if self.content_size.width > 0 else 80
|
w = self.content_size.width if self.content_size.width > 0 else 80
|
||||||
@@ -88,8 +106,14 @@ class StatusBar(Static):
|
|||||||
return f" {conn_sym} {deaf_sym}{ptt_sym}"
|
return f" {conn_sym} {deaf_sym}{ptt_sym}"
|
||||||
if w < 60:
|
if w < 60:
|
||||||
return f" {conn_full} {deaf_full}{' ' if deaf_full else ''}{ptt_full}"
|
return f" {conn_full} {deaf_full}{' ' if deaf_full else ''}{ptt_full}"
|
||||||
|
|
||||||
|
vol = (
|
||||||
|
f" [dim]out[/]{self._vol_bar(self.output_vol)}"
|
||||||
|
f" [dim]in[/]{self._vol_bar(self.input_vol)}"
|
||||||
|
)
|
||||||
info = f" [dim]{self.server_info}[/]" if self.server_info else ""
|
info = f" [dim]{self.server_info}[/]" if self.server_info else ""
|
||||||
return f" {conn_full} {deaf_full}{' ' if deaf_full else ''}{ptt_full}{info}"
|
deaf = f"{deaf_full} " if deaf_full else ""
|
||||||
|
return f" {conn_full} {deaf}{ptt_full}{vol}{info}"
|
||||||
|
|
||||||
|
|
||||||
class ChannelTree(Static):
|
class ChannelTree(Static):
|
||||||
@@ -345,6 +369,8 @@ class TuimbleApp(App):
|
|||||||
|
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("f1", "toggle_deaf", "Deafen"),
|
("f1", "toggle_deaf", "Deafen"),
|
||||||
|
("f2", "cycle_output_volume", "Vol Out"),
|
||||||
|
("f3", "cycle_input_volume", "Vol In"),
|
||||||
("q", "quit", "Quit"),
|
("q", "quit", "Quit"),
|
||||||
("ctrl+c", "quit", "Quit"),
|
("ctrl+c", "quit", "Quit"),
|
||||||
]
|
]
|
||||||
@@ -388,6 +414,10 @@ class TuimbleApp(App):
|
|||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
|
status = self.query_one("#status", StatusBar)
|
||||||
|
status.output_vol = int(self._audio.output_gain * 100)
|
||||||
|
status.input_vol = int(self._audio.input_gain * 100)
|
||||||
|
|
||||||
chatlog = self.query_one("#chatlog", ChatLog)
|
chatlog = self.query_one("#chatlog", ChatLog)
|
||||||
chatlog.write("[dim]tuimble v0.1.0[/dim]")
|
chatlog.write("[dim]tuimble v0.1.0[/dim]")
|
||||||
srv = self._config.server
|
srv = self._config.server
|
||||||
@@ -550,6 +580,28 @@ class TuimbleApp(App):
|
|||||||
else:
|
else:
|
||||||
chatlog.write("[#9ece6a]\u2713 undeafened[/]")
|
chatlog.write("[#9ece6a]\u2713 undeafened[/]")
|
||||||
|
|
||||||
|
# -- volume ---------------------------------------------------------------
|
||||||
|
|
||||||
|
def action_cycle_output_volume(self) -> None:
|
||||||
|
"""Cycle output volume through preset steps."""
|
||||||
|
vol = _next_volume(self._audio.output_gain)
|
||||||
|
self._audio.output_gain = vol
|
||||||
|
pct = int(vol * 100)
|
||||||
|
status = self.query_one("#status", StatusBar)
|
||||||
|
status.output_vol = pct
|
||||||
|
chatlog = self.query_one("#chatlog", ChatLog)
|
||||||
|
chatlog.write(f"[dim]output volume {pct}%[/dim]")
|
||||||
|
|
||||||
|
def action_cycle_input_volume(self) -> None:
|
||||||
|
"""Cycle input volume through preset steps."""
|
||||||
|
vol = _next_volume(self._audio.input_gain)
|
||||||
|
self._audio.input_gain = vol
|
||||||
|
pct = int(vol * 100)
|
||||||
|
status = self.query_one("#status", StatusBar)
|
||||||
|
status.input_vol = pct
|
||||||
|
chatlog = self.query_one("#chatlog", ChatLog)
|
||||||
|
chatlog.write(f"[dim]input volume {pct}%[/dim]")
|
||||||
|
|
||||||
# -- PTT -----------------------------------------------------------------
|
# -- PTT -----------------------------------------------------------------
|
||||||
|
|
||||||
def on_key(self, event: events.Key) -> None:
|
def on_key(self, event: events.Key) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user