mtui 0.9.3

An extensive Modbus client for your terminal.
Documentation
use crate::app::App;
use crate::state::State;
use crate::tui::draw_state;
use crate::tui::hints;
use crate::tui::make_bottom_title::make_bottom_title;
use crate::tui::make_top_title::make_top_title;
use crate::tui::theme::status_span;
use chrono::Local;
use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders};
use ratatui::Frame;

pub fn render(app: &mut App, frame: &mut Frame) {
    let device = app.config.display_device();
    let theme = app.config.theme;

    let mode = make_top_title(&app.state);
    let key_hints = make_bottom_title(&theme, app);
    let mut clock_spans = Vec::new();
    if app.config.show_clock {
        let clock = Local::now().format("%H:%M:%S.%3f").to_string();
        clock_spans.push(Span::styled(format!("{clock} "), theme.accent_style()));
    }
    if app.config.show_ram {
        if let Some(bytes) = crate::compat::ram_bytes() {
            clock_spans.push(Span::styled(
                format!("{:.1}MiB ", bytes as f64 / (1024. * 1024.)),
                theme.dim_style(),
            ));
        }
    }
    if app.config.show_frame_time {
        clock_spans.push(Span::styled(
            format!("{:.2?}ms ", app.last_frame.as_micros() as f64 / 1000.),
            theme.dim_style(),
        ));
    }
    let clock_line = Line::from(clock_spans);

    let live = match &app.state {
        State::Read(p) => draw_state::read::live_status(app, p, &theme),
        _ => Vec::new(),
    };

    let mut left_top = vec![status_span(&app.connection, &theme)];
    left_top.extend(live);

    let mut mode_spans = Vec::new();
    if let State::Logs(l) = &app.state {
        mode_spans.push(Span::styled(
            format!(" {} \u{b7}", draw_state::logs::counter(l, app)),
            theme.dim_style(),
        ));
    }
    mode_spans.push(Span::styled(format!(" {mode}"), theme.base()));

    let mut outer = Block::default()
        .title_top(Line::from(left_top))
        .title_top(Line::from(mode_spans).right_aligned())
        .title_bottom(clock_line)
        .title_bottom(key_hints.right_aligned())
        .style(Style::default().fg(theme.border))
        .borders(Borders::TOP | Borders::BOTTOM)
        .border_type(BorderType::Rounded);

    // h_max_offset is written during the table draw below, so this reads the
    // previous frame's value; it settles on the next redraw.
    if let State::Read(p) = &app.state {
        let max = app.h_max_offset.get();
        if let Some(hint) = hints::hscroll(&theme, p.col_offset.min(max), max) {
            outer = outer.title_top(hint.centered());
        }
    }

    let area = frame.area();
    let inner = outer.inner(area);
    frame.render_widget(outer, area);

    match &app.state {
        State::Read(p) => draw_state::read::draw(p, app, frame, inner, &theme, &device),
        State::Settings(s) => draw_state::settings::draw(s, app, frame, inner, &theme),
        State::Logs(l) => draw_state::logs::draw(l, app, frame, inner, &theme),
    }
}