mod chrome;
mod devices;
mod dialogs;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::Line;
use ratatui::widgets::{Paragraph, Wrap};
use ratatui::Frame;
use super::app::{App, Overlay};
pub(crate) const MIN_WIDTH: u16 = 80;
pub(crate) const MIN_HEIGHT: u16 = 24;
pub(crate) const FULL_WIDTH: u16 = 120;
pub(crate) const FULL_HEIGHT: u16 = 40;
pub(crate) fn draw(frame: &mut Frame, app: &App) {
let area = frame.area();
let theme = Theme { color: app.color };
if area.width < MIN_WIDTH || area.height < MIN_HEIGHT {
too_small(frame, area, &theme);
return;
}
let banner = app.duplicates.banner();
let banner_height: usize = banner
.iter()
.map(|line| {
line.chars()
.count()
.div_ceil(usize::from(area.width))
.max(1)
})
.sum();
let log_height = match (app.show_log, area.height >= FULL_HEIGHT) {
(false, _) => 0,
(true, true) => 12,
(true, false) => 7,
};
let [status, banner_area, body, log, footer] = Layout::vertical([
Constraint::Length(1),
Constraint::Length(banner_height as u16),
Constraint::Min(5),
Constraint::Length(log_height),
Constraint::Length(1),
])
.areas(area);
chrome::status_bar(frame, status, app, &theme);
devices::banner(frame, banner_area, banner, &theme);
devices::table(frame, body, app, &theme);
if app.show_log {
chrome::log_pane(frame, log, app, &theme);
}
chrome::footer(frame, footer, app, &theme);
match &app.overlay {
Overlay::None => {}
Overlay::Help => chrome::help(frame, area, &theme),
Overlay::WhoIs(form) => dialogs::who_is(frame, area, app, form, &theme),
Overlay::Picker(picker) => dialogs::picker(frame, area, picker, &theme),
}
}
fn too_small(frame: &mut Frame, area: Rect, theme: &Theme) {
let text = vec![
Line::styled(
format!("Terminal too small: {}x{}.", area.width, area.height),
theme.warn(),
),
Line::raw(format!(
"bacnet tui needs at least {MIN_WIDTH}x{MIN_HEIGHT} \
({FULL_WIDTH}x{FULL_HEIGHT} for the full layout)."
)),
Line::raw("Resize the window, or press Ctrl-C twice to quit."),
];
frame.render_widget(Paragraph::new(text).wrap(Wrap { trim: true }), area);
}
pub(crate) struct Theme {
color: bool,
}
impl Theme {
fn pick(&self, colored: Style, plain: Style) -> Style {
if self.color {
colored
} else {
plain
}
}
fn bar(&self) -> Style {
self.pick(
Style::new().fg(Color::Black).bg(Color::Cyan),
Style::new().add_modifier(Modifier::REVERSED),
)
}
fn safe(&self) -> Style {
self.pick(
Style::new()
.fg(Color::Black)
.bg(Color::Green)
.add_modifier(Modifier::BOLD),
Style::new().add_modifier(Modifier::BOLD),
)
}
fn accent(&self) -> Style {
self.pick(
Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD),
Style::new().add_modifier(Modifier::BOLD),
)
}
fn warn(&self) -> Style {
self.pick(
Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD),
Style::new().add_modifier(Modifier::BOLD),
)
}
fn danger(&self) -> Style {
self.pick(
Style::new()
.fg(Color::White)
.bg(Color::Red)
.add_modifier(Modifier::BOLD),
Style::new().add_modifier(Modifier::BOLD | Modifier::REVERSED),
)
}
fn dim(&self) -> Style {
self.pick(
Style::new().fg(Color::DarkGray),
Style::new().add_modifier(Modifier::DIM),
)
}
fn selected(&self) -> Style {
Style::new().add_modifier(Modifier::REVERSED)
}
}
fn centered(area: Rect, width: u16, height: u16) -> Rect {
area.centered(
Constraint::Length(width.min(area.width)),
Constraint::Length(height.min(area.height)),
)
}