use ratatui::style::Color;
pub fn string_to_color(color_str: &str) -> Color {
match color_str {
"Blue" => Color::Blue,
"Cyan" => Color::Cyan,
"Green" => Color::Green,
"Yellow" => Color::Yellow,
"Magenta" => Color::Magenta,
"Red" => Color::Red,
"LightBlue" => Color::LightBlue,
"LightCyan" => Color::LightCyan,
"LightGreen" => Color::LightGreen,
"LightYellow" => Color::LightYellow,
"LightMagenta" => Color::LightMagenta,
"LightRed" => Color::LightRed,
"DarkGray" => Color::DarkGray,
"Gray" => Color::Gray,
"White" => Color::White,
"Black" => Color::Black,
_ => Color::White,
}
}
pub fn calculate_minutes_per_row(total_minutes: i64, available_rows: usize) -> i64 {
let allowed_resolutions = [1, 2, 5, 10, 15, 30, 60];
if available_rows == 0 {
return 10; }
let ideal = (total_minutes as f64 / available_rows as f64).ceil() as i64;
for &resolution in &allowed_resolutions {
if resolution >= ideal {
return resolution;
}
}
60
}
pub fn breathe_t() -> f64 {
const PERIOD_MS: f64 = 2600.0; let ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as f64;
let p = (ms % PERIOD_MS) / PERIOD_MS; let tri = if p < 0.5 { p * 2.0 } else { 2.0 - p * 2.0 }; tri * tri * (3.0 - 2.0 * tri) }
pub fn breathe_color(base: (u8, u8, u8), t: f64) -> (u8, u8, u8) {
let (h, s, l) = rgb_to_hsl(base);
let l_dim = (l - 0.08).max(0.10);
let l_bright = (l + 0.12).min(0.85);
let s_dim = (s - 0.12).max(0.0);
let s_bright = (s + 0.18).min(1.0);
let cur_l = l_dim + (l_bright - l_dim) * t;
let cur_s = s_dim + (s_bright - s_dim) * t;
hsl_to_rgb(h, cur_s, cur_l)
}
fn color_index(c: Color) -> Option<u8> {
Some(match c {
Color::Black => 0,
Color::Red => 1,
Color::Green => 2,
Color::Yellow => 3,
Color::Blue => 4,
Color::Magenta => 5,
Color::Cyan => 6,
Color::Gray => 7,
Color::DarkGray => 8,
Color::LightRed => 9,
Color::LightGreen => 10,
Color::LightYellow => 11,
Color::LightBlue => 12,
Color::LightMagenta => 13,
Color::LightCyan => 14,
Color::White => 15,
Color::Indexed(i) => i,
_ => return None,
})
}
pub fn themed_rgb(c: Color, palette: &std::collections::HashMap<u8, (u8, u8, u8)>) -> (u8, u8, u8) {
if let Color::Rgb(r, g, b) = c {
return (r, g, b);
}
if let Some(rgb) = color_index(c).and_then(|i| palette.get(&i)) {
return *rgb;
}
color_to_rgb(c)
}
pub fn dim_toward_bg(
c: Color,
palette: &std::collections::HashMap<u8, (u8, u8, u8)>,
t: f64,
) -> Color {
let (r, g, b) = themed_rgb(c, palette);
let bg = palette.get(&0).copied().unwrap_or((0, 0, 0));
let mix = |fg: u8, bg: u8| (fg as f64 * (1.0 - t) + bg as f64 * t).round() as u8;
Color::Rgb(mix(r, bg.0), mix(g, bg.1), mix(b, bg.2))
}
pub fn color_to_rgb(c: Color) -> (u8, u8, u8) {
match c {
Color::Rgb(r, g, b) => (r, g, b),
Color::Black => (0, 0, 0),
Color::Red => (205, 49, 49),
Color::Green => (13, 188, 121),
Color::Yellow => (229, 229, 16),
Color::Blue => (36, 114, 200),
Color::Magenta => (188, 63, 188),
Color::Cyan => (17, 168, 205),
Color::Gray => (118, 118, 118),
Color::DarkGray => (102, 102, 102),
Color::LightRed => (241, 76, 76),
Color::LightGreen => (35, 209, 139),
Color::LightYellow => (245, 245, 67),
Color::LightBlue => (59, 142, 234),
Color::LightMagenta => (214, 112, 214),
Color::LightCyan => (41, 184, 219),
Color::White => (229, 229, 229),
_ => (200, 200, 200),
}
}
fn rgb_to_hsl((r, g, b): (u8, u8, u8)) -> (f64, f64, f64) {
let (r, g, b) = (r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0);
let max = r.max(g).max(b);
let min = r.min(g).min(b);
let l = (max + min) / 2.0;
let d = max - min;
if d == 0.0 {
return (0.0, 0.0, l);
}
let s = d / (1.0 - (2.0 * l - 1.0).abs());
let h = if max == r {
60.0 * (((g - b) / d).rem_euclid(6.0))
} else if max == g {
60.0 * ((b - r) / d + 2.0)
} else {
60.0 * ((r - g) / d + 4.0)
};
(h, s, l)
}
fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (u8, u8, u8) {
let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
let hp = h / 60.0;
let x = c * (1.0 - ((hp.rem_euclid(2.0)) - 1.0).abs());
let (r1, g1, b1) = match hp as i32 {
0 => (c, x, 0.0),
1 => (x, c, 0.0),
2 => (0.0, c, x),
3 => (0.0, x, c),
4 => (x, 0.0, c),
_ => (c, 0.0, x),
};
let m = l - c / 2.0;
let f = |v: f64| ((v + m) * 255.0).round().clamp(0.0, 255.0) as u8;
(f(r1), f(g1), f(b1))
}
pub fn format_duration_seconds(seconds: i64) -> String {
let hours = seconds / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
if hours > 0 {
format!("{}h {:02}m {:02}s", hours, minutes, secs)
} else if minutes > 0 {
format!("{}m {:02}s", minutes, secs)
} else {
format!("{}s", secs)
}
}