From f7295233440567009a1b53ff358c6e1d566b49c1 Mon Sep 17 00:00:00 2001 From: Joe Ardent Date: Sat, 5 Jul 2025 13:54:12 -0700 Subject: [PATCH] start of TUI --- src/main.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index dea0991..6c275fd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,18 @@ +use std::{io, sync::Arc}; + use joecalsend::{Client, JoecalState, error, models::device::DeviceInfo}; use local_ip_address::local_ip; use network_interface::{Addr, NetworkInterface, NetworkInterfaceConfig, V4IfAddr}; +use ratatui::{ + DefaultTerminal, Frame, + buffer::Buffer, + crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind}, + layout::Rect, + style::Stylize, + symbols::border, + text::{Line, Text}, + widgets::{Block, Paragraph, Widget}, +}; #[tokio::main] async fn main() -> error::Result<()> { @@ -33,7 +45,103 @@ async fn main() -> error::Result<()> { .unwrap(); let (h1, h2, h3) = client.start().await.unwrap(); - let _ = tokio::join!(h1, h2, h3); + let mut app = App::new(Arc::new(client.clone())); + let mut terminal = ratatui::init(); + let result = app.run(&mut terminal); + ratatui::restore(); + //let _ = tokio::join!(h1, h2, h3); - Ok(()) + Ok(result?) +} + +struct App { + client: Arc, + runstate: AppRunState, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum AppRunState { + Running, + Stopping, +} + +impl Default for AppRunState { + fn default() -> Self { + Self::Running + } +} + +impl App { + pub fn new(client: Arc) -> Self { + App { + client, + runstate: AppRunState::Running, + } + } + + pub fn run(&mut self, terminal: &mut DefaultTerminal) -> io::Result<()> { + while AppRunState::Running == self.runstate { + terminal.draw(|frame| self.draw(frame))?; + self.handle_events()?; + } + Ok(()) + } + + fn draw(&self, frame: &mut Frame) { + frame.render_widget(self, frame.area()); + } + + fn handle_events(&mut self) -> io::Result<()> { + match event::read()? { + // it's important to check that the event is a key press event as + // crossterm also emits key release and repeat events on Windows. + Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { + self.handle_key_event(key_event) + } + _ => {} + }; + Ok(()) + } + + fn handle_key_event(&mut self, key_event: KeyEvent) { + match key_event.code { + KeyCode::Char('q') => self.exit(), + KeyCode::Char('s') => {} + KeyCode::Char('r') => {} + KeyCode::Char('d') => {} + _ => {} + } + } + + fn exit(&mut self) { + self.runstate = AppRunState::Stopping + } +} + +impl Widget for &App { + fn render(self, area: Rect, buf: &mut Buffer) { + let title = Line::from(" Counter App Tutorial ".bold()); + let instructions = Line::from(vec![ + " Send ".into(), + "".blue().bold(), + " Receive ".into(), + "".blue().bold(), + " Discover ".into(), + "".blue().bold(), + " Quit ".into(), + " ".blue().bold(), + ]); + let block = Block::bordered() + .title(title.centered()) + .title_bottom(instructions.centered()) + .border_set(border::THICK); + + let rs = format!("{:?}", self.runstate); + let state_text = Text::from(vec![Line::from(vec!["runstate: ".into(), rs.yellow()])]); + + Paragraph::new(state_text) + .centered() + .block(block) + .render(area, buf); + } }