pub mod burst;
pub mod cpu;
pub mod font;
pub mod gauge;
pub mod header;
pub mod memory;
pub mod network;
pub mod ports;
pub mod power;
pub mod processes;
pub mod screen;
pub mod scroll;
pub mod sessions;
pub mod spark;
pub mod temp;
pub mod text;
pub mod theme;
use ratatui::prelude::*;
use ratatui::widgets::{Block, Paragraph};
use crate::app::{App, Focus, MAX_VISIBLE_PROCS};
use screen::{Body, Gauge, Screen, Tier};
pub fn draw(f: &mut Frame, app: &App) {
let area = f.area();
f.render_widget(Block::default().style(Style::default().bg(theme::BASE)), area);
let screen = Screen::resolve(area);
let split = Layout::vertical([Constraint::Min(0), Constraint::Length(1)]).split(area);
render_footer(f, split[1], app);
let chunks = Layout::vertical([
Constraint::Length(screen.tier.header_rows()),
Constraint::Length(screen.tier.gauge_rows()),
Constraint::Min(6),
])
.split(split[0]);
header::render(f, chunks[0], app);
render_gauges(f, chunks[1], app, &screen);
render_body(f, chunks[2], app, &screen);
}
fn render_gauges(f: &mut Frame, area: Rect, app: &App, screen: &Screen) {
let half = |r: Rect| {
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).split(r).to_vec()
};
let cells: Vec<Rect> = match screen.tier {
Tier::Grid => {
let bands =
Layout::vertical([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).split(area);
[half(bands[0]), half(bands[1])].concat()
}
_ => {
let n = screen.gauges.len() as u32;
Layout::horizontal(vec![Constraint::Ratio(1, n); n as usize]).split(area).to_vec()
}
};
for (gauge, cell) in screen.gauges.iter().zip(cells) {
match gauge {
Gauge::Cpu => cpu::render(f, cell, app),
Gauge::Temp => temp::render(f, cell, app),
Gauge::Power => power::render(f, cell, app),
Gauge::Memory => memory::render(f, cell, app),
}
}
}
fn render_body(f: &mut Frame, area: Rect, app: &App, screen: &Screen) {
match screen.body {
Body::ThreeCards => {
let rows = Layout::vertical([Constraint::Min(3), Constraint::Max(10)]).split(area);
let procs = beside_network(f, rows[0], app, screen);
processes::render(f, procs, app);
let cards = Layout::horizontal([Constraint::Ratio(1, 3); 3]).split(rows[1]);
ports::render(f, cards[0], app, ports::Card::Localhost);
sessions::render(f, cards[1], app);
ports::render(f, cards[2], app, ports::Card::Others);
}
Body::ProcessesOverPorts => {
let left = beside_network(f, area, app, screen);
let rows = Layout::vertical([
Constraint::Max(MAX_VISIBLE_PROCS as u16 + 3),
Constraint::Min(4),
])
.split(left);
processes::render(f, rows[0], app);
ports::render(f, rows[1], app, ports::Card::Combined);
}
Body::ProcessesOnly => {
let procs = beside_network(f, area, app, screen);
processes::render(f, procs, app);
}
}
}
fn beside_network(f: &mut Frame, area: Rect, app: &App, screen: &Screen) -> Rect {
if !screen.network {
return area;
}
let cols =
Layout::horizontal([Constraint::Ratio(3, 5), Constraint::Ratio(2, 5)]).split(area);
network::render(f, cols[1], app);
cols[0]
}
fn render_footer(f: &mut Frame, area: Rect, app: &App) {
let show_sort = matches!(app.focus, Focus::Processes);
let show_kill = matches!(app.focus, Focus::Processes | Focus::Localhost);
let items: [Option<&str>; 5] = [
Some("↑↓ select"),
show_sort.then_some("c/m sort"),
show_kill.then_some("k kill"),
Some("tab focus"),
Some("q quit"),
];
let text = items.into_iter().flatten().collect::<Vec<_>>().join(" · ");
f.render_widget(Paragraph::new(text).style(Style::default().fg(theme::DIM)), area);
}