diff --git a/zellij-client/src/lib.rs b/zellij-client/src/lib.rs index bd46c90b7b..ece154ea7c 100644 --- a/zellij-client/src/lib.rs +++ b/zellij-client/src/lib.rs @@ -91,7 +91,14 @@ pub fn start_client( let take_snapshot = "\u{1b}[?1049h"; let bracketed_paste = "\u{1b}[?2004h"; os_input.unset_raw_mode(0); - let palette = os_input.load_palette(); + let config_options = Options::from_cli(&config.options, opts.command.clone()); + let palette = config.themes.clone().map_or_else( + || os_input.load_palette(), + |t| { + t.theme_config(&config_options) + .unwrap_or_else(|| os_input.load_palette()) + }, + ); let _ = os_input .get_stdout_writer() .write(take_snapshot.as_bytes()) @@ -102,8 +109,6 @@ pub fn start_client( .unwrap(); std::env::set_var(&"ZELLIJ", "0"); - let config_options = Options::from_cli(&config.options, opts.command.clone()); - let full_screen_ws = os_input.get_terminal_size_using_fd(0); let client_attributes = ClientAttributes { position_and_size: full_screen_ws, diff --git a/zellij-server/src/lib.rs b/zellij-server/src/lib.rs index 76b4b7b184..1691420e46 100644 --- a/zellij-server/src/lib.rs +++ b/zellij-server/src/lib.rs @@ -15,7 +15,7 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex, RwLock}; use std::thread; use wasmer::Store; -use zellij_tile::data::{Event, PluginCapabilities}; +use zellij_tile::data::{Event, Palette, PluginCapabilities}; use crate::{ os_input_output::ServerOsApi, @@ -84,6 +84,7 @@ impl ErrorInstruction for ServerInstruction { pub(crate) struct SessionMetaData { pub senders: ThreadSenders, pub capabilities: PluginCapabilities, + pub palette: Palette, screen_thread: Option>, pty_thread: Option>, wasm_thread: Option>, @@ -401,6 +402,7 @@ fn init_session( to_server: None, }, capabilities, + palette: client_attributes.palette, screen_thread: Some(screen_thread), pty_thread: Some(pty_thread), wasm_thread: Some(wasm_thread), diff --git a/zellij-server/src/route.rs b/zellij-server/src/route.rs index 673f563a5d..285f333cb6 100644 --- a/zellij-server/src/route.rs +++ b/zellij-server/src/route.rs @@ -18,7 +18,7 @@ use zellij_utils::{ fn route_action( action: Action, session: &SessionMetaData, - os_input: &dyn ServerOsApi, + _os_input: &dyn ServerOsApi, to_server: &SenderWithContext, ) -> bool { let mut should_break = false; @@ -34,7 +34,7 @@ fn route_action( .unwrap(); } Action::SwitchToMode(mode) => { - let palette = os_input.load_palette(); + let palette = session.palette; // TODO: use the palette from the client and remove it from the server os api // this is left here as a stop gap measure until we shift some code around // to allow for this diff --git a/zellij-tile/src/data.rs b/zellij-tile/src/data.rs index 3bd926ca63..2111d9aa5e 100644 --- a/zellij-tile/src/data.rs +++ b/zellij-tile/src/data.rs @@ -72,13 +72,13 @@ impl Default for InputMode { } #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] -pub enum Theme { +pub enum ThemeHue { Light, Dark, } -impl Default for Theme { - fn default() -> Theme { - Theme::Dark +impl Default for ThemeHue { + fn default() -> ThemeHue { + ThemeHue::Dark } } @@ -123,7 +123,7 @@ impl Default for PaletteSource { #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)] pub struct Palette { pub source: PaletteSource, - pub theme: Theme, + pub theme_hue: ThemeHue, pub fg: PaletteColor, pub bg: PaletteColor, pub black: PaletteColor, diff --git a/zellij-utils/src/input/config.rs b/zellij-utils/src/input/config.rs index c4cf0cd72b..56e333f467 100644 --- a/zellij-utils/src/input/config.rs +++ b/zellij-utils/src/input/config.rs @@ -7,6 +7,7 @@ use std::path::{Path, PathBuf}; use super::keybinds::{Keybinds, KeybindsFromYaml}; use super::options::Options; +use super::theme::ThemesFromYaml; use crate::cli::{CliArgs, Command}; use crate::setup; @@ -23,6 +24,7 @@ pub struct ConfigFromYaml { #[serde(flatten)] pub options: Option, pub keybinds: Option, + pub themes: Option, } /// Main configuration. @@ -30,6 +32,7 @@ pub struct ConfigFromYaml { pub struct Config { pub keybinds: Keybinds, pub options: Options, + pub themes: Option, } #[derive(Debug)] @@ -48,7 +51,13 @@ impl Default for Config { fn default() -> Self { let keybinds = Keybinds::default(); let options = Options::default(); - Config { keybinds, options } + let themes = None; + + Config { + keybinds, + options, + themes, + } } } @@ -90,7 +99,13 @@ impl Config { let config_from_yaml: ConfigFromYaml = serde_yaml::from_str(&yaml_config)?; let keybinds = Keybinds::get_default_keybinds_with_config(config_from_yaml.keybinds); let options = Options::from_yaml(config_from_yaml.options); - Ok(Config { keybinds, options }) + let themes = config_from_yaml.themes; + + Ok(Config { + keybinds, + options, + themes, + }) } /// Deserializes from given path. diff --git a/zellij-utils/src/input/mod.rs b/zellij-utils/src/input/mod.rs index 068e22bc48..93a1d2255c 100644 --- a/zellij-utils/src/input/mod.rs +++ b/zellij-utils/src/input/mod.rs @@ -4,6 +4,7 @@ pub mod actions; pub mod config; pub mod keybinds; pub mod options; +pub mod theme; use termion::input::TermRead; use zellij_tile::data::{InputMode, Key, ModeInfo, Palette, PluginCapabilities}; diff --git a/zellij-utils/src/input/options.rs b/zellij-utils/src/input/options.rs index 3476c2fb9c..64256976bf 100644 --- a/zellij-utils/src/input/options.rs +++ b/zellij-utils/src/input/options.rs @@ -12,7 +12,10 @@ pub struct Options { /// that is compatible with more fonts #[structopt(long)] pub simplified_ui: bool, - /// Allows to specify the default mode + /// Set the default theme + #[structopt(long)] + pub theme: Option, + /// Set the default mode #[structopt(long)] pub default_mode: Option, } @@ -41,8 +44,14 @@ impl Options { other => other, }; + let theme = match other.theme { + None => self.theme.clone(), + other => other, + }; + Options { simplified_ui, + theme, default_mode, } } diff --git a/zellij-utils/src/input/theme.rs b/zellij-utils/src/input/theme.rs new file mode 100644 index 0000000000..35bd3d87bf --- /dev/null +++ b/zellij-utils/src/input/theme.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +use super::options::Options; +use zellij_tile::data::{Palette, PaletteColor}; + +/// Intermediate deserialization of themes +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct ThemesFromYaml(HashMap); + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +struct Theme { + #[serde(flatten)] + palette: PaletteFromYaml, +} + +/// Intermediate deserialization struct +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, Default)] +pub struct PaletteFromYaml { + pub fg: PaletteColorFromYaml, + pub bg: PaletteColorFromYaml, + pub black: PaletteColorFromYaml, + pub red: PaletteColorFromYaml, + pub green: PaletteColorFromYaml, + pub yellow: PaletteColorFromYaml, + pub blue: PaletteColorFromYaml, + pub magenta: PaletteColorFromYaml, + pub cyan: PaletteColorFromYaml, + pub white: PaletteColorFromYaml, + pub orange: PaletteColorFromYaml, +} + +/// Intermediate deserialization enum +// This is here in order to make the untagged enum work +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[serde(untagged)] +pub enum PaletteColorFromYaml { + Rgb((u8, u8, u8)), + EightBit(u8), +} + +impl Default for PaletteColorFromYaml { + fn default() -> Self { + PaletteColorFromYaml::EightBit(0) + } +} + +impl ThemesFromYaml { + pub fn theme_config(self, opts: &Options) -> Option { + let mut from_yaml = self; + match &opts.theme { + Some(theme) => from_yaml.from_default_theme(theme.to_owned()), + None => from_yaml.from_default_theme("default".into()), + } + } + + fn get_theme(&mut self, theme: String) -> Option { + self.0.remove(&theme) + } + + fn from_default_theme(&mut self, theme: String) -> Option { + self.clone() + .get_theme(theme) + .map(|t| Palette::from(t.palette)) + } +} + +impl From for Palette { + fn from(yaml: PaletteFromYaml) -> Self { + Palette { + fg: yaml.fg.into(), + bg: yaml.fg.into(), + black: yaml.black.into(), + red: yaml.red.into(), + green: yaml.green.into(), + yellow: yaml.yellow.into(), + blue: yaml.blue.into(), + magenta: yaml.magenta.into(), + cyan: yaml.cyan.into(), + white: yaml.white.into(), + orange: yaml.orange.into(), + ..Palette::default() + } + } +} + +impl From for PaletteColor { + fn from(yaml: PaletteColorFromYaml) -> Self { + match yaml { + PaletteColorFromYaml::Rgb(color) => PaletteColor::Rgb(color), + PaletteColorFromYaml::EightBit(color) => PaletteColor::EightBit(color), + } + } +} diff --git a/zellij-utils/src/shared.rs b/zellij-utils/src/shared.rs index bc8b677115..a741cc10b2 100644 --- a/zellij-utils/src/shared.rs +++ b/zellij-utils/src/shared.rs @@ -8,7 +8,7 @@ use colors_transform::{Color, Rgb}; use std::os::unix::fs::PermissionsExt; use std::path::Path; use std::{fs, io}; -use zellij_tile::data::{Palette, PaletteColor, PaletteSource, Theme}; +use zellij_tile::data::{Palette, PaletteColor, PaletteSource, ThemeHue}; const UNIX_PERMISSIONS: u32 = 0o700; @@ -64,7 +64,7 @@ pub fn _hex_to_rgb(hex: &str) -> (u8, u8, u8) { pub fn default_palette() -> Palette { Palette { source: PaletteSource::Default, - theme: Theme::Dark, + theme_hue: ThemeHue::Dark, fg: PaletteColor::EightBit(colors::BRIGHT_GRAY), bg: PaletteColor::EightBit(colors::GRAY), black: PaletteColor::EightBit(colors::BLACK), @@ -80,7 +80,7 @@ pub fn default_palette() -> Palette { } // Dark magic -pub fn _detect_theme(bg: PaletteColor) -> Theme { +pub fn _detect_theme_hue(bg: PaletteColor) -> ThemeHue { match bg { PaletteColor::Rgb((r, g, b)) => { // HSP, P stands for perceived brightness @@ -89,11 +89,11 @@ pub fn _detect_theme(bg: PaletteColor) -> Theme { + 0.114 * (b as f64 * b as f64)) .sqrt(); match hsp > 127.5 { - true => Theme::Light, - false => Theme::Dark, + true => ThemeHue::Light, + false => ThemeHue::Dark, } } - _ => Theme::Dark, + _ => ThemeHue::Dark, } }