#![allow(dead_code)]
use ratatui::style::Color;
use std::time::Duration;
#[derive(Debug, Clone, Copy)]
pub struct Palette {
pub brand_green: Color,
pub brand_green_dim: Color,
pub brand_green_glow: Color,
pub bg_0: Color,
pub bg_1: Color,
pub bg_2: Color,
pub bg_3: Color,
pub bg_4: Color,
pub fg_0: Color,
pub fg_1: Color,
pub fg_2: Color,
pub fg_3: Color,
pub fg_4: Color,
pub rule_dim: Color,
pub rule: Color,
pub rule_strong: Color,
pub ember: Color,
pub ember_dim: Color,
pub copper: Color,
pub cyan: Color,
pub cyan_dim: Color,
pub magenta: Color,
pub heartbeat: Color,
pub amber: Color,
pub red: Color,
pub violet: Color,
pub state_pass: Color,
}
impl Palette {
pub const fn truecolor() -> Self {
Self {
brand_green: Color::Rgb(0xa0, 0xce, 0x29),
brand_green_dim: Color::Rgb(0x7e, 0xa3, 0x21),
brand_green_glow: Color::Rgb(0xc4, 0xec, 0x4d),
bg_0: Color::Rgb(0x07, 0x09, 0x0b),
bg_1: Color::Rgb(0x0d, 0x10, 0x14),
bg_2: Color::Rgb(0x14, 0x18, 0x20),
bg_3: Color::Rgb(0x1c, 0x21, 0x2c),
bg_4: Color::Rgb(0x26, 0x2c, 0x39),
fg_0: Color::Rgb(0xf2, 0xf5, 0xf2),
fg_1: Color::Rgb(0xc9, 0xd0, 0xca),
fg_2: Color::Rgb(0x8a, 0x93, 0x88),
fg_3: Color::Rgb(0x5a, 0x63, 0x60),
fg_4: Color::Rgb(0x34, 0x3b, 0x3a),
rule_dim: Color::Rgb(0x22, 0x28, 0x34),
rule: Color::Rgb(0x2e, 0x36, 0x44),
rule_strong: Color::Rgb(0x3b, 0x45, 0x55),
ember: Color::Rgb(0xff, 0x7b, 0x3e),
ember_dim: Color::Rgb(0xc5, 0x59, 0x24),
copper: Color::Rgb(0xd6, 0x8e, 0x5a),
cyan: Color::Rgb(0x4c, 0xc9, 0xf0),
cyan_dim: Color::Rgb(0x2a, 0x8a, 0xaa),
magenta: Color::Rgb(0xd9, 0x46, 0xef),
heartbeat: Color::Rgb(0xff, 0x2e, 0x7e),
amber: Color::Rgb(0xff, 0xb0, 0x00),
red: Color::Rgb(0xef, 0x44, 0x44),
violet: Color::Rgb(0x7c, 0x3a, 0xed),
state_pass: Color::Rgb(0x7e, 0xa3, 0x21), }
}
pub const fn indexed_256() -> Self {
Self {
brand_green: Color::Indexed(149),
brand_green_dim: Color::Indexed(106),
brand_green_glow: Color::Indexed(155),
bg_0: Color::Indexed(232),
bg_1: Color::Indexed(233),
bg_2: Color::Indexed(234),
bg_3: Color::Indexed(235),
bg_4: Color::Indexed(236),
fg_0: Color::Indexed(255),
fg_1: Color::Indexed(252),
fg_2: Color::Indexed(245),
fg_3: Color::Indexed(241),
fg_4: Color::Indexed(238),
rule_dim: Color::Indexed(236),
rule: Color::Indexed(239),
rule_strong: Color::Indexed(243),
ember: Color::Indexed(208),
ember_dim: Color::Indexed(166),
copper: Color::Indexed(173),
cyan: Color::Indexed(45),
cyan_dim: Color::Indexed(31),
magenta: Color::Indexed(200),
heartbeat: Color::Indexed(198),
amber: Color::Indexed(214),
red: Color::Indexed(203),
violet: Color::Indexed(99),
state_pass: Color::Indexed(106), }
}
pub fn detect() -> Self {
match std::env::var("COLORTERM").as_deref() {
Ok("truecolor") | Ok("24bit") => Self::truecolor(),
_ => Self::indexed_256(),
}
}
}
pub struct Motion;
impl Motion {
pub const FAST: Duration = Duration::from_millis(120);
pub const NORMAL: Duration = Duration::from_millis(200);
pub const SLOW: Duration = Duration::from_millis(420);
pub const BEAT: Duration = Duration::from_millis(1200);
}
pub fn color_lerp(a: Color, b: Color, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
match (a, b) {
(Color::Rgb(ar, ag, ab), Color::Rgb(br, bg, bb)) => {
let lerp = |x: u8, y: u8| -> u8 {
let xf = x as f32;
let yf = y as f32;
(xf + (yf - xf) * t).round().clamp(0.0, 255.0) as u8
};
Color::Rgb(lerp(ar, br), lerp(ag, bg), lerp(ab, bb))
}
_ => {
if t < 0.5 {
a
} else {
b
}
}
}
}
pub fn breathing_brightness(elapsed_ms: u64, period_ms: u64) -> f32 {
use std::f32::consts::PI;
let phase = (elapsed_ms as f32 / period_ms as f32) * 2.0 * PI;
0.86 + 0.14 * (1.0 + phase.sin()) / 2.0
}
pub fn fade_factor(age_secs: f32) -> f32 {
let t = (age_secs / 8.0).clamp(0.0, 1.0);
1.0 - 0.4 * t
}
pub struct Glyphs;
impl Glyphs {
pub const SPINNER: &'static [&'static str] =
&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
pub const SPARKLINE: &'static [&'static str] = &["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
pub const HEARTBEAT_DOT: &'static str = "●";
pub const ARROW_RIGHT: &'static str = "─▶";
pub const ARROW_THICK: &'static str = "═▶";
pub const ARROW_BIDI: &'static str = "⇄";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn truecolor_palette_brand_matches_logo() {
let p = Palette::truecolor();
assert_eq!(p.brand_green, Color::Rgb(0xa0, 0xce, 0x29));
}
#[test]
fn indexed_palette_ember_is_208() {
let p = Palette::indexed_256();
assert_eq!(p.ember, Color::Indexed(208));
}
#[test]
fn detect_returns_a_palette() {
let _ = Palette::detect();
}
#[test]
fn motion_durations_match_css() {
assert_eq!(Motion::FAST, Duration::from_millis(120));
assert_eq!(Motion::BEAT, Duration::from_millis(1200));
}
#[test]
fn spinner_has_10_frames() {
assert_eq!(Glyphs::SPINNER.len(), 10);
}
}