use ratatui::style::Color;
mod panel;
#[allow(unused_imports)]
pub use panel::{pane, panel, Bind, PanelOpts};
pub const BG: Color = Color::Rgb(0x0c, 0x14, 0x18);
pub const FG: Color = Color::Rgb(0xc8, 0xd4, 0xd9);
pub const DIM: Color = Color::Rgb(0x6d, 0x81, 0x89);
pub const FAINT: Color = Color::Rgb(0x42, 0x55, 0x5d);
pub const RULE: Color = Color::Rgb(0x1c, 0x28, 0x2e);
pub const GREEN: Color = Color::Rgb(0x5c, 0xd9, 0x89);
pub const CYAN: Color = Color::Rgb(0x5f, 0xdc, 0xff);
pub const AMBER: Color = Color::Rgb(0xf0, 0xc0, 0x60);
pub const RED: Color = Color::Rgb(0xff, 0x78, 0x78);
pub const VIOLET: Color = Color::Rgb(0xb8, 0xa8, 0xe8);
pub const WHITE: Color = Color::Rgb(0xff, 0xff, 0xff);
pub const NEVER_DOT: Color = Color::Rgb(0x33, 0x45, 0x4d);
pub const ROW_SELECTED_BG: Color = Color::Rgb(0x10, 0x1a, 0x1f);
pub const GREEN_MUTED: Color = Color::Rgb(0x3b, 0xb6, 0x73);
pub const RAMP_DIV: [Color; 5] = [
Color::Rgb(0x2c, 0x3a, 0x42),
Color::Rgb(0x4a, 0x5a, 0x60),
Color::Rgb(0x8a, 0x8f, 0x6a),
Color::Rgb(0xf0, 0xc0, 0x60),
Color::Rgb(0xff, 0x78, 0x78),
];
pub const RAMP_OK: [Color; 5] = [
Color::Rgb(0x13, 0x4f, 0x42),
Color::Rgb(0x1f, 0x7d, 0x58),
Color::Rgb(0x3b, 0xb6, 0x73),
Color::Rgb(0x5c, 0xd9, 0x89),
Color::Rgb(0xa6, 0xf2, 0xc0),
];
pub const RAMP_NET: [Color; 5] = [
Color::Rgb(0x10, 0x3f, 0x52),
Color::Rgb(0x1c, 0x6f, 0x8c),
Color::Rgb(0x3a, 0xa9, 0xc9),
Color::Rgb(0x5f, 0xdc, 0xff),
Color::Rgb(0xb6, 0xed, 0xff),
];
fn channels(c: Color) -> (u8, u8, u8) {
match c {
Color::Rgb(r, g, b) => (r, g, b),
_ => (0, 0, 0),
}
}
fn mix(a: Color, b: Color, f: f64) -> Color {
let (ar, ag, ab) = channels(a);
let (br, bg, bb) = channels(b);
let lerp = |x: u8, y: u8| -> u8 { (x as f64 + (y as f64 - x as f64) * f).round() as u8 };
Color::Rgb(lerp(ar, br), lerp(ag, bg), lerp(ab, bb))
}
pub fn ramp_at(ramp: &[Color], f: f64) -> Color {
if ramp.is_empty() {
return FG;
}
let t = f.clamp(0.0, 1.0) * (ramp.len() - 1) as f64;
let i = t.floor() as usize;
if i >= ramp.len() - 1 {
ramp[ramp.len() - 1]
} else {
mix(ramp[i], ramp[i + 1], t - i as f64)
}
}
pub fn magnitude(pct: f64) -> Color {
ramp_at(&RAMP_OK, pct / 100.0)
}
pub fn bounded_bad(pct: f64) -> Color {
match pct {
p if p >= 90.0 => RED,
p if p >= 80.0 => AMBER,
_ => GREEN_MUTED,
}
}
pub fn divergence(severity: f64) -> Color {
ramp_at(&RAMP_DIV, severity)
}
pub fn divergence_count(n: usize) -> Color {
if n == 0 {
FAINT
} else {
ramp_at(&RAMP_DIV, (n as f64 / 3.0).min(1.0))
}
}
pub fn meter(fraction: f64, width: usize) -> (String, String) {
let filled = ((fraction.clamp(0.0, 1.0)) * width as f64).round() as usize;
let filled = filled.min(width);
("█".repeat(filled), "░".repeat(width - filled))
}
const BRAILLE_DOTS: [[u8; 4]; 2] = [[0x01, 0x02, 0x04, 0x40], [0x08, 0x10, 0x20, 0x80]];
pub fn sparkline(values: &[u64], width: usize, ceiling: u64) -> String {
if width == 0 {
return String::new();
}
if values.is_empty() {
return " ".repeat(width);
}
let ceiling = ceiling.max(1) as f64;
let samples = width * 2;
let mut out = String::with_capacity(width * 3);
let mut cell: u8 = 0;
for s in 0..samples {
let idx = if samples == 1 {
values.len() - 1
} else {
(s * (values.len().saturating_sub(1))) / (samples - 1)
};
let v = values[idx.min(values.len() - 1)] as f64;
let level = ((v / ceiling).clamp(0.0, 1.0) * 4.0).round().max(1.0) as usize;
let col = s % 2;
for row in 0..level.min(4) {
cell |= BRAILLE_DOTS[col][3 - row];
}
if col == 1 {
out.push(char::from_u32(0x2800 + cell as u32).unwrap_or(' '));
cell = 0;
}
}
if samples % 2 == 1 {
out.push(char::from_u32(0x2800 + cell as u32).unwrap_or(' '));
}
out
}
pub fn axis_ceiling(values: &[u64]) -> u64 {
let peak = values.iter().copied().max().unwrap_or(1).max(1);
let mut rung = 1u64;
while rung < peak {
rung = (rung as f64 * 1.25).ceil() as u64;
}
rung
}
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Block;
use ratatui::Frame;
pub fn header_cell(label: &str, emphasised: bool) -> Span<'static> {
Span::styled(
label.to_uppercase(),
Style::default().fg(if emphasised { CYAN } else { FAINT }),
)
}
pub fn chip(text: &str, warn: bool) -> Span<'static> {
Span::styled(
format!(" {} ", text),
Style::default().fg(if warn { AMBER } else { DIM }),
)
}
pub fn dot(color: Color) -> Span<'static> {
Span::styled("●", Style::default().fg(color))
}
pub fn footer_line(pairs: &[(&str, &str)]) -> Line<'static> {
let mut spans = vec![Span::raw(" ")];
for (i, (key, label)) in pairs.iter().enumerate() {
if i > 0 {
spans.push(Span::raw(" "));
}
spans.push(Span::styled(
key.to_string(),
Style::default().fg(CYAN).add_modifier(Modifier::BOLD),
));
spans.push(Span::raw(" "));
spans.push(Span::styled(label.to_string(), Style::default().fg(FAINT)));
}
Line::from(spans)
}
#[allow(dead_code)] pub fn headline<'a>(value: &'a str, unit: &'a str, sub: &'a str) -> Vec<Line<'a>> {
vec![
Line::from(vec![
Span::styled(
value,
Style::default().fg(WHITE).add_modifier(Modifier::BOLD),
),
Span::raw(" "),
Span::styled(unit, Style::default().fg(DIM)),
]),
Line::from(Span::styled(sub, Style::default().fg(FAINT))),
]
}
pub fn none_line(text: &str) -> Line<'static> {
Line::from(Span::styled(
text.to_string(),
Style::default().fg(FAINT).add_modifier(Modifier::ITALIC),
))
}
pub fn paint_bg(f: &mut Frame, area: Rect) {
f.render_widget(Block::default().style(Style::default().bg(BG).fg(FG)), area);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn palette_matches_the_handoff_exactly() {
assert_eq!(BG, Color::Rgb(0x0c, 0x14, 0x18));
assert_eq!(FG, Color::Rgb(0xc8, 0xd4, 0xd9));
assert_eq!(DIM, Color::Rgb(0x6d, 0x81, 0x89));
assert_eq!(FAINT, Color::Rgb(0x42, 0x55, 0x5d));
assert_eq!(RULE, Color::Rgb(0x1c, 0x28, 0x2e));
assert_eq!(GREEN, Color::Rgb(0x5c, 0xd9, 0x89));
assert_eq!(CYAN, Color::Rgb(0x5f, 0xdc, 0xff));
assert_eq!(AMBER, Color::Rgb(0xf0, 0xc0, 0x60));
assert_eq!(RED, Color::Rgb(0xff, 0x78, 0x78));
assert_eq!(VIOLET, Color::Rgb(0xb8, 0xa8, 0xe8));
}
#[test]
fn divergence_ramp_runs_faint_to_red() {
assert_eq!(divergence(0.0), RAMP_DIV[0]);
assert_eq!(divergence(1.0), RED);
let mut last_red = -1i32;
for i in 0..=10 {
let (r, _, _) = channels(divergence(i as f64 / 10.0));
assert!(r as i32 >= last_red, "warmth dipped at {}", i);
last_red = r as i32;
}
let (r0, _, b0) = channels(divergence(0.0));
assert!(b0 > r0, "consensus should not read warm");
}
#[test]
fn magnitude_never_reddens() {
for pct in [0.0, 25.0, 50.0, 75.0, 95.0, 100.0] {
let c = magnitude(pct);
assert_ne!(c, RED, "magnitude at {}% went red", pct);
assert_ne!(c, AMBER, "magnitude at {}% went amber", pct);
}
let (_, g_low, _) = channels(magnitude(10.0));
let (_, g_high, _) = channels(magnitude(90.0));
assert!(g_high > g_low);
}
#[test]
fn bounded_bad_is_reserved_for_values_that_really_are_bad() {
assert_eq!(bounded_bad(42.0), GREEN_MUTED);
assert_eq!(bounded_bad(84.0), AMBER);
assert_eq!(bounded_bad(93.0), RED);
}
#[test]
fn a_diverge_count_of_zero_is_faint_not_green() {
assert_eq!(divergence_count(0), FAINT);
assert_eq!(divergence_count(3), RED);
assert_ne!(divergence_count(1), divergence_count(2));
}
#[test]
fn meter_fills_proportionally_and_never_overflows() {
let (f, e) = meter(0.42, 10);
assert_eq!(f.chars().count(), 4);
assert_eq!(f.chars().count() + e.chars().count(), 10);
let (f, e) = meter(1.5, 10);
assert_eq!(f.chars().count(), 10);
assert_eq!(e.chars().count(), 0);
let (f, _) = meter(-1.0, 10);
assert_eq!(f.chars().count(), 0);
}
#[test]
fn a_sparkline_is_exactly_the_width_asked_for() {
for w in [1usize, 7, 26, 74] {
let s = sparkline(&[1, 5, 3, 9, 2], w, 10);
assert_eq!(s.chars().count(), w, "width {}", w);
}
}
#[test]
fn an_empty_series_draws_nothing_rather_than_a_flat_line() {
let s = sparkline(&[], 10, 100);
assert_eq!(s, " ");
assert!(!s.contains('⣿'));
}
#[test]
fn levels_are_absolute_so_a_quiet_series_stays_low() {
let quiet = sparkline(&[1, 2, 1, 2], 8, 100);
let busy = sparkline(&[90, 95, 92, 99], 8, 100);
assert_ne!(quiet, busy);
assert!(
quiet.chars().all(|c| {
let bits = c as u32 - 0x2800;
bits & !(0x40 | 0x80) == 0
}),
"quiet series climbed: {}",
quiet
);
}
#[test]
fn axis_ceiling_climbs_in_rungs_no_more_than_25_percent_apart() {
assert!(axis_ceiling(&[1]) >= 1);
for peak in [3u64, 17, 42, 100, 999] {
let c = axis_ceiling(&[peak]);
assert!(c >= peak, "ceiling {} below peak {}", c, peak);
assert!(
c as f64 <= peak as f64 * 1.25 + 1.0,
"ceiling {} too far above peak {}",
c,
peak
);
}
}
#[test]
fn a_series_at_its_ceiling_reaches_the_top_row() {
let s = sparkline(&[10, 10, 10], 4, 10);
assert!(s.chars().all(|c| c == '⣿'), "got {}", s);
}
}