use heatsync_core::heat::{Hue, Look, Tier};
use heatsync_core::{Badge, BadgeKind, NoteKind, Platform, Tint, Unread};
use ratatui::style::{Color, Modifier, Style};
pub const DIM: Style = Style::new().fg(Color::White);
pub const TEXT: Style = Style::new().fg(Color::White);
pub const HEAD: Style = Style::new().fg(Color::White).add_modifier(Modifier::BOLD);
pub const SEL: Style = Style::new().add_modifier(Modifier::REVERSED);
pub const TAG: Style = Style::new().add_modifier(Modifier::REVERSED.union(Modifier::BOLD));
pub const WARN: Style = Style::new().fg(Color::Yellow);
pub const WARN_BOLD: Style = Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD);
pub const PING: Style = Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD);
pub const TRACK: Style = Style::new().fg(Color::Blue);
pub const LIVE: Style = Style::new().fg(Color::Green);
pub const READ: Color = Color::Rgb(128, 128, 128);
pub const DIM_TAG: Style = Style::new().fg(READ);
pub const STAMP: Style = Style::new().fg(READ);
pub const GONE: Style = Style::new().fg(READ).add_modifier(Modifier::CROSSED_OUT);
pub const GONE_TAG: Style = Style::new().fg(READ);
pub fn tab(u: Unread) -> Style {
match u {
Unread::None => Style::new().fg(READ),
Unread::Chat => TEXT,
Unread::Event => Style::new().fg(Color::Yellow),
Unread::Ping => Style::new().fg(Color::Red).add_modifier(Modifier::BOLD),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Fill {
Olive,
Purple,
Navy,
Green,
Teal,
Red,
}
pub const FILL_OLIVE: Color = Color::Rgb(128, 128, 0);
pub const FILL_PURPLE: Color = Color::Rgb(128, 0, 128);
pub const FILL_NAVY: Color = Color::Rgb(0, 0, 128);
pub const FILL_GREEN: Color = Color::Rgb(0, 128, 0);
pub const FILL_TEAL: Color = Color::Rgb(0, 128, 128);
pub const FILL_RED: Color = Color::Rgb(95, 0, 0);
impl Fill {
pub fn bg(self) -> Color {
match self {
Fill::Olive => FILL_OLIVE,
Fill::Purple => FILL_PURPLE,
Fill::Navy => FILL_NAVY,
Fill::Green => FILL_GREEN,
Fill::Teal => FILL_TEAL,
Fill::Red => FILL_RED,
}
}
}
pub const TWITCH: Color = Color::Rgb(200, 168, 255);
fn color(h: Hue) -> Color {
match h {
Hue::Black => Color::Black,
Hue::Red => Color::Red,
Hue::Green => Color::Green,
Hue::Yellow => Color::Yellow,
Hue::Blue => Color::Blue,
Hue::Magenta => Color::Magenta,
Hue::Cyan => Color::Cyan,
Hue::White => Color::White,
}
}
fn style(l: Look) -> Style {
let mut s = Style::new().fg(color(l.hue));
if l.bold {
s = s.add_modifier(Modifier::BOLD);
}
if l.reversed {
s = s.add_modifier(Modifier::REVERSED);
}
if l.blink {
s = s.add_modifier(Modifier::SLOW_BLINK);
}
s
}
pub fn heat(t: Tier) -> Style {
style(t.look())
}
pub fn heat_fill(t: Tier) -> Style {
let mut l = t.look();
l.reversed = false;
l.blink = false;
style(l)
}
pub fn platform(p: Platform) -> Style {
Style::new().fg(match p {
Platform::Twitch => TWITCH,
Platform::Kick => Color::Green,
Platform::Youtube => Color::Red,
})
}
pub fn badge(b: Badge) -> Style {
use BadgeKind as K;
let s = Style::new().add_modifier(Modifier::BOLD);
match b.kind {
K::Broadcaster => s.fg(Color::Black).bg(Color::Red),
K::Moderator => s.fg(Color::Black).bg(Color::Green),
K::Vip => s.fg(Color::Black).bg(Color::Magenta),
K::Subscriber | K::Member => Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD),
K::Founder => s.fg(Color::Black).bg(Color::Yellow),
K::Staff | K::GlobalMod => s.fg(Color::Black).bg(Color::White),
K::Verified => s.fg(Color::Black).bg(Color::Cyan),
K::Og => s
.fg(Color::Black)
.bg(Color::Cyan)
.add_modifier(Modifier::UNDERLINED),
K::Premium | K::Predictions | K::Hype => {
Style::new().fg(Color::Blue).add_modifier(Modifier::BOLD)
}
K::Bits | K::Artist => Style::new().fg(Color::Yellow).add_modifier(Modifier::BOLD),
K::Gifter | K::Turbo => Style::new().fg(Color::Magenta).add_modifier(Modifier::BOLD),
K::Other(_) => Style::new().add_modifier(Modifier::BOLD),
}
}
pub fn hs_badge(b: &heatsync_core::hsbadge::HsBadge) -> Style {
Style::new().fg(color(b.hue))
}
pub fn note(k: NoteKind) -> (&'static str, Style) {
use NoteKind as K;
let s = Style::new();
match k {
K::Sub => ("★", s.fg(Color::Yellow)),
K::Gift => ("✦", s.fg(Color::Magenta)),
K::Cheer => ("◆", s.fg(Color::Yellow).add_modifier(Modifier::BOLD)),
K::Raid => ("⚑", s.fg(Color::Magenta).add_modifier(Modifier::BOLD)),
K::Redeem => ("◇", s.fg(Color::Cyan)),
K::Live => ("●", s.fg(Color::Green)),
K::Offline => ("○", s.fg(Color::White)),
K::Category => ("→", s.fg(Color::Cyan)),
K::Notice => ("»", s.fg(Color::Cyan)),
K::Spike => ("▲", s.fg(Color::Yellow).add_modifier(Modifier::BOLD)),
K::Mod => ("×", s.fg(Color::Red)),
K::Announce(t) => (
"!",
match t {
Tint::Plain => s.add_modifier(Modifier::BOLD),
Tint::Blue => s.fg(Color::Blue),
Tint::Green => s.fg(Color::Green),
Tint::Yellow => s.fg(Color::Yellow),
Tint::Magenta => s.fg(Color::Magenta),
},
),
K::Streak => ("≈", s.fg(Color::Yellow)),
K::Milestone => ("♦", s.fg(Color::Magenta)),
K::Charity => ("♥", s.fg(Color::Magenta)),
K::ModAnniv => ("✪", s.fg(Color::White)),
K::Ritual => ("✧", s.fg(Color::Green)),
K::Mode => ("◈", s.fg(Color::Cyan)),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_sanctioned_rgb_set_is_exactly_this() {
let allowed = [
(128, 128, 128), (200, 168, 255), (128, 128, 0), (128, 0, 128), (0, 0, 128), (0, 128, 0), (0, 128, 128), (95, 0, 0), ];
for c in [
READ,
TWITCH,
FILL_OLIVE,
FILL_PURPLE,
FILL_NAVY,
FILL_GREEN,
FILL_TEAL,
FILL_RED,
] {
let Color::Rgb(r, g, b) = c else {
panic!("{c:?}")
};
assert!(allowed.contains(&(r, g, b)), "{c:?}");
}
let src = include_str!("palette.rs");
let code = &src[..src.find("#[cfg(test)]").unwrap()];
assert_eq!(code.matches("Color::Rgb(").count(), allowed.len());
for f in [
Fill::Olive,
Fill::Purple,
Fill::Navy,
Fill::Green,
Fill::Teal,
Fill::Red,
] {
assert!(matches!(f.bg(), Color::Rgb(..)));
}
}
#[test]
fn gone_is_gray_and_crossed_out_but_its_tag_is_not() {
assert_eq!(GONE.fg, Some(READ));
assert!(GONE.add_modifier.contains(Modifier::CROSSED_OUT));
assert!(!GONE_TAG.add_modifier.contains(Modifier::CROSSED_OUT));
}
#[test]
fn tab_states_read_gray_unread_white_ping_bold_red() {
assert_eq!(tab(Unread::None).fg, Some(READ));
assert_eq!(tab(Unread::Chat), TEXT);
assert_eq!(tab(Unread::Event).fg, Some(Color::Yellow));
let p = tab(Unread::Ping);
assert_eq!(p.fg, Some(Color::Red));
assert!(p.add_modifier.contains(Modifier::BOLD));
}
}