Color Schemes (#142)

* WIP: Color Schemes

* Import/export using color_schemes config

* Finish color scheme implementation

* Add color scheme rename
This commit is contained in:
Jeremy Soller 2024-02-22 09:55:46 -07:00 committed by GitHub
parent 01052fae0b
commit 89e1dcb83a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 2630 additions and 861 deletions

View file

@ -5,6 +5,7 @@ use cosmic::{
theme,
};
use cosmic_text::{Metrics, Stretch, Weight};
use hex_color::HexColor;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
@ -31,6 +32,139 @@ impl AppTheme {
}
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct ColorSchemeId(pub u64);
//TODO: there is a lot of extra code to keep the exported color scheme clean,
//consider how to reduce this
fn de_color_opt<'de, D>(deserializer: D) -> Result<Option<HexColor>, D::Error>
where
D: serde::Deserializer<'de>,
{
let hex_color: HexColor = Deserialize::deserialize(deserializer)?;
Ok(Some(hex_color))
}
fn ser_color_opt<S>(hex_color_opt: &Option<HexColor>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::Error as _;
match hex_color_opt {
Some(hex_color) => Serialize::serialize(hex_color, serializer),
None => Err(S::Error::custom("ser_color_opt called with None")),
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ColorSchemeAnsi {
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub black: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub red: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub green: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub yellow: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub blue: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub magenta: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub cyan: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub white: Option<HexColor>,
}
impl ColorSchemeAnsi {
pub fn is_empty(&self) -> bool {
self.black.is_none()
&& self.red.is_none()
&& self.green.is_none()
&& self.yellow.is_none()
&& self.blue.is_none()
&& self.magenta.is_none()
&& self.cyan.is_none()
&& self.white.is_none()
}
}
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct ColorScheme {
pub name: String,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub foreground: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub background: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub cursor: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub bright_foreground: Option<HexColor>,
#[serde(
deserialize_with = "de_color_opt",
serialize_with = "ser_color_opt",
skip_serializing_if = "Option::is_none"
)]
pub dim_foreground: Option<HexColor>,
#[serde(skip_serializing_if = "ColorSchemeAnsi::is_empty")]
pub normal: ColorSchemeAnsi,
#[serde(skip_serializing_if = "ColorSchemeAnsi::is_empty")]
pub bright: ColorSchemeAnsi,
#[serde(skip_serializing_if = "ColorSchemeAnsi::is_empty")]
pub dim: ColorSchemeAnsi,
}
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(transparent)]
pub struct ProfileId(pub u64);
@ -63,6 +197,7 @@ impl Default for Profile {
#[derive(Clone, CosmicConfigEntry, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Config {
pub app_theme: AppTheme,
pub color_schemes: BTreeMap<ColorSchemeId, ColorScheme>,
pub font_name: String,
pub font_size: u16,
pub font_weight: u16,
@ -84,6 +219,7 @@ impl Default for Config {
Self {
app_theme: AppTheme::System,
bold_font_weight: Weight::BOLD.0,
color_schemes: BTreeMap::new(),
dim_font_weight: Weight::NORMAL.0,
focus_follow_mouse: false,
font_name: "Fira Mono".to_string(),
@ -102,6 +238,25 @@ impl Default for Config {
}
impl Config {
// Get a sorted and adjusted for duplicates list of color scheme names and ids
pub fn color_scheme_names(&self) -> Vec<(String, ColorSchemeId)> {
let mut color_scheme_names =
Vec::<(String, ColorSchemeId)>::with_capacity(self.color_schemes.len());
for (color_scheme_id, color_scheme) in self.color_schemes.iter() {
let mut name = color_scheme.name.clone();
let mut copies = 1;
while color_scheme_names.iter().find(|x| x.0 == name).is_some() {
copies += 1;
name = format!("{} ({})", color_scheme.name, copies);
}
color_scheme_names.push((name, *color_scheme_id));
}
color_scheme_names.sort_by(|a, b| lexical_sort::natural_lexical_cmp(&a.0, &b.0));
color_scheme_names
}
fn font_size_adjusted(&self, zoom_adj: i8) -> f32 {
let font_size = f32::from(self.font_size).max(1.0);
let adj = f32::from(zoom_adj);
@ -120,7 +275,7 @@ impl Config {
(self.opacity as f32) / 100.0
}
// Get a sorted and adjusted for duplicates list of profiles names and ids
// Get a sorted and adjusted for duplicates list of profile names and ids
pub fn profile_names(&self) -> Vec<(String, ProfileId)> {
let mut profile_names = Vec::<(String, ProfileId)>::with_capacity(self.profiles.len());
for (profile_id, profile) in self.profiles.iter() {