use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::Frame;
use ratatui_sci_fi::themes::{Palette, Theme};
use ratatui_sci_fi::widgets::{Panel, PanelShape};
use std::sync::OnceLock;
use crate::style;
pub const PANEL_SHAPE: PanelShape = PanelShape::Rounded;
const PALETTE: Palette = Theme::Cyberpunk.palette();
fn cached(slot: &'static OnceLock<Style>, classes: &'static [&'static str]) -> Style {
*slot.get_or_init(|| style::compute(classes))
}
pub fn bg() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["bg"])
}
pub fn panel() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["panel"])
}
pub fn fg() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["fg"])
}
pub fn border() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["border"])
}
pub fn title_style() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["title"])
}
pub fn selected() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["selected"])
}
pub fn muted() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["muted"])
}
pub fn error() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["err"])
}
pub fn accent() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["accent"])
}
pub fn accent2() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["accent2"])
}
pub mod colors {
use super::*;
pub fn accent() -> Color {
PALETTE.accent.color()
}
pub fn accent2() -> Color {
PALETTE.accent2.color()
}
pub fn ok() -> Color {
PALETTE.ok.color()
}
pub fn fg() -> Color {
PALETTE.fg.color()
}
pub fn muted() -> Color {
PALETTE.muted.color()
}
pub fn alert() -> Color {
PALETTE.alert.color()
}
pub fn panel() -> Color {
PALETTE.panel.color()
}
}
pub fn selected_bar() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["selected_bar"])
}
pub fn bar() -> Style {
static C: OnceLock<Style> = OnceLock::new();
cached(&C, &["bar"])
}
pub fn block(t: &str) -> ratatui::widgets::Block<'_> {
use ratatui::widgets::{Block, BorderType, Borders};
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(border())
.title(t)
.title_style(title_style())
.style(Style::default().fg(PALETTE.fg.color()))
}
pub fn panel_frame(frame: &mut Frame, area: Rect, title: Option<&str>) -> Rect {
let mut panel = Panel::new().theme(Theme::Cyberpunk).shape(PANEL_SHAPE);
if let Some(t) = title {
panel = panel.title(format!(" {t} "));
}
let inner = panel.inner(area);
frame.render_widget(panel, area);
inner
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::{Color, Modifier};
#[test]
fn theme_styles_match_cyberpunk_palette() {
let title = title_style();
assert_eq!(title.fg, Some(Color::Rgb(0xff, 0x00, 0x7f)));
assert!(title.add_modifier.contains(Modifier::BOLD));
assert_eq!(fg().fg, Some(Color::Rgb(0xf0, 0xe6, 0xff)));
assert_eq!(border().fg, Some(Color::Rgb(0x00, 0xf0, 0xff))); assert_eq!(error().fg, Some(Color::Rgb(0xff, 0x20, 0x60))); assert_eq!(accent().fg, Some(Color::Rgb(0x39, 0xff, 0x14))); assert_eq!(bg().bg, Some(Color::Rgb(0x08, 0x04, 0x14)));
assert_eq!(panel().bg, Some(Color::Rgb(0x14, 0x0a, 0x22)));
let sb = selected_bar();
assert_eq!(sb.bg, Some(Color::Rgb(0x14, 0x0a, 0x22)));
assert_eq!(sb.fg, Some(Color::Rgb(0xff, 0x00, 0x7f)));
assert!(sb.add_modifier.contains(Modifier::BOLD));
}
#[test]
fn theme_caching_is_stable() {
let a = title_style();
let b = title_style();
assert_eq!(a.fg, b.fg);
assert_eq!(a.add_modifier, b.add_modifier);
}
}