Skip to content

Commit

Permalink
feat(ui): add support for standard ANSI colors theming and update def…
Browse files Browse the repository at this point in the history
…ault theme (#221)

Fixes #211 

<img width="2554" alt="Screenshot 2025-01-06 at 13 01 57"
src="https://github.com/user-attachments/assets/3706b93a-4be1-4a88-8c3e-bfb71a36db50"
/>
  • Loading branch information
alexpasmantier authored Jan 6, 2025
1 parent 53bd4a3 commit d7e6c35
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 8 deletions.
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'

0 comments on commit d7e6c35

Please sign in to comment.