topdio 0.2.1

Like top, but with audio. Listen to your computer's resource usage statistics.
Documentation
use anyhow::Result;
use crossterm::{
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::io::{stdout, Stdout};
use tui::{
    backend::CrosstermBackend,
    layout::{Constraint, Direction, Layout},
    style::{Modifier, Style},
    text::Text,
    widgets::{Block, Borders, Row, Table},
    Terminal,
};

use crate::topdio::{ProcessInfo, TopdioMessage, TopdioSubscriber};

pub struct UI {
    terminal: Terminal<CrosstermBackend<Stdout>>,
}

impl UI {
    pub fn new() -> Result<UI> {
        enable_raw_mode()?;
        let mut stdout = stdout();
        execute!(stdout, EnterAlternateScreen)?;
        let backend = CrosstermBackend::new(stdout);
        let terminal = Terminal::new(backend)?;

        Ok(UI { terminal })
    }

    pub fn teardown(&mut self) -> Result<()> {
        disable_raw_mode()?;
        execute!(self.terminal.backend_mut(), LeaveAlternateScreen)?;
        self.terminal.show_cursor()?;
        Ok(())
    }

    pub fn render_frame(&mut self, processes: &[ProcessInfo]) -> Result<()> {
        self.terminal.draw(|frame| {
            let chunks = Layout::default()
                .direction(Direction::Horizontal)
                .constraints([Constraint::Length(70), Constraint::Length(0)].as_ref())
                .split(frame.size());
            let rows: Vec<_> = processes
                .iter()
                .map(|p| {
                    Row::new([
                        p.pid.to_string(),
                        p.name.to_str().unwrap_or("(none)").to_string(),
                        format!("{:.2}%", p.cpu_usage),
                    ])
                })
                .collect();
            let table = Table::new(rows)
                .block(Block::default().title("TOPDIO").borders(Borders::ALL))
                .header(Row::new(["PID", "COMMAND", "CPU USAGE"].iter().map(|s| {
                    Text::styled(*s, Style::default().add_modifier(Modifier::BOLD))
                })))
                .widths(&[
                    Constraint::Length(10),
                    Constraint::Length(45),
                    Constraint::Length(15),
                ])
                .column_spacing(1);
            frame.render_widget(table, chunks[0])
        })?;
        Ok(())
    }
}

impl TopdioSubscriber for UI {
    fn handle(&mut self, message: &TopdioMessage) -> Result<()> {
        match message {
            TopdioMessage::Stats { processes } => self.render_frame(processes),
            TopdioMessage::Stop => self.teardown(),
        }
    }
}