import vban_cmd from textual.app import App, ComposeResult from textual.containers import Grid from textual.widgets import RichLog from .history import CommandHistory from .hybrid import LabelInput from .settings import Settings class VbanTui(App): """A Textual App to display VBAN data.""" def _select_css(self): if hasattr(self, '_settings') and not self._settings.rich_log: return 'tui_no_log.tcss' return 'tui.tcss' def __init__(self): self._settings = Settings() self.CSS_PATH = self._select_css() super().__init__() self._command_history = CommandHistory() self._history_index = None def compose(self) -> ComposeResult: """Create child widgets for the app.""" yield Grid( LabelInput( 'VBAN Host:', 'localhost', id='host-labelinput', label_id='host-label', input_id='host-input', ), LabelInput( 'VBAN Port:', '6980', id='port-labelinput', label_id='port-label', input_id='port-input', ), LabelInput( 'VBAN Stream Name:', 'Command1', id='streamname-labelinput', label_id='streamname-label', input_id='streamname-input', ), LabelInput( 'VBAN Command:', 'Enter request', id='request-labelinput', label_id='request-label', input_id='request-input', ), RichLog(id='response-log'), id='main-grid', ) def on_mount(self): """Focus the request input on mount.""" self.query_one('#host-input').value = self._settings.host self.query_one('#port-input').value = str(self._settings.port) self.query_one('#streamname-input').value = self._settings.streamname self.query_one('#request-input').focus() if not self._settings.rich_log: self.query_one('#response-log').remove() self.query_one('#main-grid').styles.height = '20' self.query_one('#main-grid').refresh(layout=True) def on_key(self, event): """Handle key events.""" request_input = self.query_one('#request-input') match event.key: case 'q': self.exit() case 'enter' if request_input.has_focus: request_input.add_class('request-sent') self.send_request() self.set_timer(0.5, lambda: request_input.remove_class('request-sent')) value = request_input.value.strip() if value: self._command_history.add(value) self._history_index = None self.query_one('#request-input').clear() case 'up' if request_input.has_focus: if self._command_history: if self._history_index is None: self._history_index = len(self._command_history) - 1 elif self._history_index > 0: self._history_index -= 1 request_input.value = self._command_history.get_previous( self._history_index ) case 'down' if request_input.has_focus: if self._command_history and self._history_index is not None: if self._history_index < len(self._command_history) - 1: self._history_index += 1 request_input.value = self._command_history.get_next( self._history_index ) else: self._history_index = None request_input.value = '' def send_request(self): request = self.query_one('#request-input').value.strip() if request == 'clear': self.query_one('#response-log').clear() return with vban_cmd.api( 'potato', host=self.query_one('#host-input').value, port=int(self.query_one('#port-input').value), streamname=self.query_one('#streamname-input').value, disable_rt_listeners=True, ) as vban: if response := vban.sendtext(request): self.query_one('#response-log').write(response) def main(): """Run the VBAN TUI application.""" app = VbanTui() app.run()