pub mod api;
pub mod canvas;
pub mod render;
pub mod widgets;
#[cfg(target_arch = "wasm32")]
pub mod wasm_entry;
use std::sync::Arc;
pub use api::{dashboard, gauge, meter, plot, table};
pub use canvas::{Canvas, CellBuffer, Color, Point, Rect, TextStyle};
pub use render::{DiffRenderer, RenderBackend};
pub use widgets::{BrailleGraph, Brick, Gauge, Meter, ProgressBar, Table};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventResult {
Handled,
Ignored,
}
#[derive(Debug, Clone)]
pub enum Event {
Key(KeyEvent),
Mouse(MouseEvent),
Resize { width: u16, height: u16 },
Focus(bool),
}
#[derive(Debug, Clone)]
pub struct KeyEvent {
pub code: KeyCode,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyCode {
Char(char),
Enter,
Backspace,
Delete,
Left,
Right,
Up,
Down,
Home,
End,
PageUp,
PageDown,
Tab,
Escape,
F(u8),
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Modifiers {
pub shift: bool,
pub ctrl: bool,
pub alt: bool,
}
#[derive(Debug, Clone)]
pub struct MouseEvent {
pub kind: MouseEventKind,
pub column: u16,
pub row: u16,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseEventKind {
Down(MouseButton),
Up(MouseButton),
Drag(MouseButton),
Moved,
ScrollDown,
ScrollUp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseButton {
Left,
Right,
Middle,
}
#[derive(Debug, Clone)]
pub struct WidgetSpec {
pub kind: WidgetKind,
pub bounds: Option<Rect>,
pub style: WidgetStyle,
}
#[derive(Debug, Clone)]
pub enum WidgetKind {
BrailleGraph {
data: Vec<f64>,
mode: GraphMode,
},
Meter {
value: f64,
max: f64,
label: String,
},
Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
},
Gauge {
value: f64,
label: String,
},
Progress {
current: u64,
total: u64,
show_eta: bool,
},
Dashboard {
children: Vec<(String, WidgetSpec)>,
},
Text {
content: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GraphMode {
#[default]
Braille,
Block,
Tty,
}
#[derive(Debug, Clone, Default)]
pub struct WidgetStyle {
pub color: Option<Color>,
pub background: Option<Color>,
pub title: Option<String>,
pub border: BorderStyle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BorderStyle {
#[default]
None,
Sharp,
Rounded,
Double,
Heavy,
}
#[derive(Debug, Clone)]
pub struct Gradient {
stops: Vec<(f64, Color)>,
cache: Option<Arc<[Color; 101]>>,
}
impl Gradient {
pub fn new(stops: Vec<(f64, Color)>) -> Self {
Self { stops, cache: None }
}
pub fn two(start: Color, end: Color) -> Self {
Self::new(vec![(0.0, start), (1.0, end)])
}
pub fn three(start: Color, mid: Color, end: Color) -> Self {
Self::new(vec![(0.0, start), (0.5, mid), (1.0, end)])
}
pub fn precompute(&mut self) {
let mut colors = [Color::default(); 101];
for (i, color) in colors.iter_mut().enumerate() {
*color = self.sample(i as f64 / 100.0);
}
self.cache = Some(Arc::new(colors));
}
#[inline]
pub fn sample(&self, t: f64) -> Color {
let t = t.clamp(0.0, 1.0);
if self.stops.is_empty() {
return Color::default();
}
if self.stops.len() == 1 {
return self.stops[0].1;
}
let mut prev = &self.stops[0];
for stop in &self.stops {
if stop.0 >= t {
let range = stop.0 - prev.0;
if range <= 0.0 {
return stop.1;
}
let local_t = (t - prev.0) / range;
return Color::lerp(prev.1, stop.1, local_t as f32);
}
prev = stop;
}
self.stops.last().map(|s| s.1).unwrap_or_default()
}
#[inline]
pub fn at_percent(&self, pct: u8) -> Color {
if let Some(ref cache) = self.cache {
cache[pct.min(100) as usize]
} else {
self.sample(f64::from(pct) / 100.0)
}
}
}
pub mod palettes {
use super::Color;
pub const CPU: [(f64, Color); 3] = [
(0.0, Color::new(0.3, 0.9, 0.5, 1.0)), (0.7, Color::new(1.0, 0.9, 0.3, 1.0)), (1.0, Color::new(1.0, 0.3, 0.3, 1.0)), ];
pub const TEMP: [(f64, Color); 3] = [
(0.0, Color::new(0.3, 0.5, 1.0, 1.0)), (0.5, Color::new(1.0, 1.0, 1.0, 1.0)), (1.0, Color::new(1.0, 0.3, 0.3, 1.0)), ];
pub const MEMORY: [(f64, Color); 2] = [
(0.0, Color::new(0.6, 0.3, 0.9, 1.0)), (1.0, Color::new(1.0, 0.9, 0.3, 1.0)), ];
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gradient_two_color() {
let g = Gradient::two(
Color::new(0.0, 0.0, 0.0, 1.0),
Color::new(1.0, 1.0, 1.0, 1.0),
);
let mid = g.sample(0.5);
assert!((mid.r - 0.5).abs() < 0.01);
assert!((mid.g - 0.5).abs() < 0.01);
assert!((mid.b - 0.5).abs() < 0.01);
}
#[test]
fn test_gradient_precompute() {
let mut g = Gradient::two(
Color::new(0.0, 0.0, 0.0, 1.0),
Color::new(1.0, 1.0, 1.0, 1.0),
);
g.precompute();
let color = g.at_percent(50);
assert!((color.r - 0.5).abs() < 0.01);
}
#[test]
fn test_event_result() {
assert_ne!(EventResult::Handled, EventResult::Ignored);
}
#[test]
fn test_graph_mode_default() {
assert_eq!(GraphMode::default(), GraphMode::Braille);
}
#[test]
fn test_border_style_default() {
assert_eq!(BorderStyle::default(), BorderStyle::None);
}
}