Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): add support for standard ANSI colors theming and update default theme #221

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ input_bar_position = "top"
# A list of builtin themes can be found in the `themes` directory of the television
# repository. You may also create your own theme by creating a new file in a `themes`
# directory in your configuration directory (see the `config.toml` location above).
theme = "catppuccin"
theme = "default"

# Previewers settings
# ----------------------------------------------------------------------------
Expand All @@ -65,7 +65,7 @@ theme = "catppuccin"
# Bulitin syntax highlighting uses the same syntax highlighting engine as bat.
# To get a list of your currently available themes, run `bat --list-themes`
# Note that setting the BAT_THEME environment variable will override this setting.
theme = "Coldark-Dark"
theme = "TwoDark"

# Keybindings
# ----------------------------------------------------------------------------
Expand Down
208 changes: 202 additions & 6 deletions crates/television/config/themes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,68 @@ use super::get_config_dir;

pub mod builtin;

#[derive(Clone, Debug, Default)]
pub struct Color {
#[derive(Clone, Debug, PartialEq)]
pub enum Color {
Ansi(ANSIColor),
Rgb(RGBColor),
}

impl Color {
pub fn from_str(s: &str) -> Option<Self> {
if s.starts_with('#') {
RGBColor::from_str(s).map(Self::Rgb)
} else {
match s.to_lowercase().as_str() {
"black" => Some(Self::Ansi(ANSIColor::Black)),
"red" => Some(Self::Ansi(ANSIColor::Red)),
"green" => Some(Self::Ansi(ANSIColor::Green)),
"yellow" => Some(Self::Ansi(ANSIColor::Yellow)),
"blue" => Some(Self::Ansi(ANSIColor::Blue)),
"magenta" => Some(Self::Ansi(ANSIColor::Magenta)),
"cyan" => Some(Self::Ansi(ANSIColor::Cyan)),
"white" => Some(Self::Ansi(ANSIColor::White)),
"bright-black" => Some(Self::Ansi(ANSIColor::BrightBlack)),
"bright-red" => Some(Self::Ansi(ANSIColor::BrightRed)),
"bright-green" => Some(Self::Ansi(ANSIColor::BrightGreen)),
"bright-yellow" => Some(Self::Ansi(ANSIColor::BrightYellow)),
"bright-blue" => Some(Self::Ansi(ANSIColor::BrightBlue)),
"bright-magenta" => Some(Self::Ansi(ANSIColor::BrightMagenta)),
"bright-cyan" => Some(Self::Ansi(ANSIColor::BrightCyan)),
"bright-white" => Some(Self::Ansi(ANSIColor::BrightWhite)),
_ => None,
}
}
}
}

#[derive(Clone, Debug, PartialEq)]
pub enum ANSIColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct RGBColor {
pub r: u8,
pub g: u8,
pub b: u8,
}

impl Color {
impl RGBColor {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
Expand Down Expand Up @@ -90,11 +144,11 @@ impl Theme {
}
}

pub const DEFAULT_THEME: &str = "gruvbox-dark";
pub const DEFAULT_THEME: &str = "default";

impl Default for Theme {
fn default() -> Self {
let theme_content = include_str!("../../../themes/gruvbox-dark.toml");
let theme_content = include_str!("../../../themes/default.toml");
toml::from_str(theme_content).unwrap()
}
}
Expand Down Expand Up @@ -179,12 +233,46 @@ impl<'de> Deserialize<'de> for Theme {
}

#[allow(clippy::from_over_into)]
impl Into<RatatuiColor> for &Color {
impl Into<RatatuiColor> for &RGBColor {
fn into(self) -> RatatuiColor {
RatatuiColor::Rgb(self.r, self.g, self.b)
}
}

#[allow(clippy::from_over_into)]
impl Into<RatatuiColor> for &ANSIColor {
fn into(self) -> RatatuiColor {
match self {
ANSIColor::Black => RatatuiColor::Black,
ANSIColor::Red => RatatuiColor::Red,
ANSIColor::Green => RatatuiColor::Green,
ANSIColor::Yellow => RatatuiColor::Yellow,
ANSIColor::Blue => RatatuiColor::Blue,
ANSIColor::Magenta => RatatuiColor::Magenta,
ANSIColor::Cyan => RatatuiColor::Cyan,
ANSIColor::White => RatatuiColor::Gray,
ANSIColor::BrightBlack => RatatuiColor::DarkGray,
ANSIColor::BrightRed => RatatuiColor::LightRed,
ANSIColor::BrightGreen => RatatuiColor::LightGreen,
ANSIColor::BrightYellow => RatatuiColor::LightYellow,
ANSIColor::BrightBlue => RatatuiColor::LightBlue,
ANSIColor::BrightMagenta => RatatuiColor::LightMagenta,
ANSIColor::BrightCyan => RatatuiColor::LightCyan,
ANSIColor::BrightWhite => RatatuiColor::White,
}
}
}

#[allow(clippy::from_over_into)]
impl Into<RatatuiColor> for &Color {
fn into(self) -> RatatuiColor {
match self {
Color::Ansi(ansi) => ansi.into(),
Color::Rgb(rgb) => rgb.into(),
}
}
}

#[allow(clippy::from_over_into)]
impl Into<Colorscheme> for &Theme {
fn into(self) -> Colorscheme {
Expand Down Expand Up @@ -265,3 +353,111 @@ impl Into<ModeColorscheme> for &Theme {
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_theme_deserialization() {
let theme_content = r##"
background = "#000000"
border_fg = "black"
text_fg = "white"
dimmed_text_fg = "bright-black"
input_text_fg = "bright-white"
result_count_fg = "bright-white"
result_name_fg = "bright-white"
result_line_number_fg = "bright-white"
result_value_fg = "bright-white"
selection_bg = "bright-white"
match_fg = "bright-white"
preview_title_fg = "bright-white"
channel_mode_fg = "bright-white"
remote_control_mode_fg = "bright-white"
send_to_channel_mode_fg = "bright-white"
"##;
let theme: Theme = toml::from_str(theme_content).unwrap();
assert_eq!(
theme.background,
Some(Color::Rgb(RGBColor::from_str("000000").unwrap()))
);
assert_eq!(theme.border_fg, Color::Ansi(ANSIColor::Black));
assert_eq!(theme.text_fg, Color::Ansi(ANSIColor::White));
assert_eq!(theme.dimmed_text_fg, Color::Ansi(ANSIColor::BrightBlack));
assert_eq!(theme.input_text_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.result_count_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.result_name_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.result_line_number_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
assert_eq!(theme.result_value_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.selection_bg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.match_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.preview_title_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
assert_eq!(theme.channel_mode_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.remote_control_mode_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
assert_eq!(
theme.send_to_channel_mode_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
}

#[test]
fn test_theme_deserialization_no_background() {
let theme_content = r##"
border_fg = "black"
text_fg = "white"
dimmed_text_fg = "bright-black"
input_text_fg = "bright-white"
result_count_fg = "#ffffff"
result_name_fg = "bright-white"
result_line_number_fg = "#ffffff"
result_value_fg = "bright-white"
selection_bg = "bright-white"
match_fg = "bright-white"
preview_title_fg = "bright-white"
channel_mode_fg = "bright-white"
remote_control_mode_fg = "bright-white"
send_to_channel_mode_fg = "bright-white"
"##;
let theme: Theme = toml::from_str(theme_content).unwrap();
assert_eq!(theme.background, None);
assert_eq!(theme.border_fg, Color::Ansi(ANSIColor::Black));
assert_eq!(theme.text_fg, Color::Ansi(ANSIColor::White));
assert_eq!(theme.dimmed_text_fg, Color::Ansi(ANSIColor::BrightBlack));
assert_eq!(theme.input_text_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.result_count_fg,
Color::Rgb(RGBColor::from_str("ffffff").unwrap())
);
assert_eq!(theme.result_name_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.result_line_number_fg,
Color::Rgb(RGBColor::from_str("ffffff").unwrap())
);
assert_eq!(theme.result_value_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.selection_bg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(theme.match_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.preview_title_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
assert_eq!(theme.channel_mode_fg, Color::Ansi(ANSIColor::BrightWhite));
assert_eq!(
theme.remote_control_mode_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
assert_eq!(
theme.send_to_channel_mode_fg,
Color::Ansi(ANSIColor::BrightWhite)
);
}
}
1 change: 1 addition & 0 deletions crates/television/config/themes/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use lazy_static::lazy_static;
lazy_static! {
pub static ref BUILTIN_THEMES: HashMap<&'static str, &'static str> = {
let mut m = HashMap::new();
m.insert("default", include_str!("../../../../themes/default.toml"));
m.insert(
"television",
include_str!("../../../../themes/television.toml"),
Expand Down
20 changes: 20 additions & 0 deletions themes/default.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# general
# background = 'black'
border_fg = 'bright-black'
text_fg = 'bright-blue'
dimmed_text_fg = 'white'
# input
input_text_fg = 'bright-red'
result_count_fg = 'bright-red'
# results
result_name_fg = 'bright-blue'
result_line_number_fg = 'bright-yellow'
result_value_fg = 'white'
selection_bg = 'bright-black'
match_fg = 'bright-red'
# preview
preview_title_fg = 'bright-magenta'
# modes
channel_mode_fg = 'green'
remote_control_mode_fg = 'yellow'
send_to_channel_mode_fg = 'cyan'
Loading