use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::{format, vec};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseColorError;
impl core::fmt::Display for ParseColorError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid CSS hex color (expected #RGB, RGB, #RRGGBB, or RRGGBB)")
}
}
impl core::error::Error for ParseColorError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseVariantError;
impl core::fmt::Display for ParseVariantError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid variant (expected \"Dark\" or \"Light\")")
}
}
impl core::error::Error for ParseVariantError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseContrastError;
impl core::fmt::Display for ParseContrastError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("invalid contrast (expected \"High\", \"Normal\", or \"Low\")")
}
}
impl core::error::Error for ParseContrastError {}
const fn hex_digit(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
#[must_use]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
}
impl Color {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}
pub const fn from_hex(hex: u32) -> Self {
Self {
r: ((hex >> 16) & 0xFF) as u8,
g: ((hex >> 8) & 0xFF) as u8,
b: (hex & 0xFF) as u8,
}
}
#[must_use]
pub const fn from_css_hex(s: &str) -> Option<Color> {
let bytes = s.as_bytes();
let (has_hash, hex_len) = if !bytes.is_empty() && bytes[0] == b'#' {
(true, bytes.len() - 1)
} else {
(false, bytes.len())
};
let start = if has_hash { 1 } else { 0 };
match hex_len {
6 => {
let (r_hi, r_lo) = (hex_digit(bytes[start]), hex_digit(bytes[start + 1]));
let (g_hi, g_lo) = (hex_digit(bytes[start + 2]), hex_digit(bytes[start + 3]));
let (b_hi, b_lo) = (hex_digit(bytes[start + 4]), hex_digit(bytes[start + 5]));
match (r_hi, r_lo, g_hi, g_lo, b_hi, b_lo) {
(Some(rh), Some(rl), Some(gh), Some(gl), Some(bh), Some(bl)) => Some(Color {
r: rh << 4 | rl,
g: gh << 4 | gl,
b: bh << 4 | bl,
}),
_ => None,
}
}
3 => {
let (r, g, b) = (
hex_digit(bytes[start]),
hex_digit(bytes[start + 1]),
hex_digit(bytes[start + 2]),
);
match (r, g, b) {
(Some(r), Some(g), Some(b)) => Some(Color {
r: r << 4 | r,
g: g << 4 | g,
b: b << 4 | b,
}),
_ => None,
}
}
_ => None,
}
}
#[must_use]
pub const fn to_hex(self) -> u32 {
((self.r as u32) << 16) | ((self.g as u32) << 8) | (self.b as u32)
}
#[must_use]
pub fn to_css_hex(self) -> String {
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}
#[must_use]
pub const fn to_f32(self) -> (f32, f32, f32) {
(
self.r as f32 / 255.0,
self.g as f32 / 255.0,
self.b as f32 / 255.0,
)
}
pub fn from_f32(r: f32, g: f32, b: f32) -> Self {
Self {
r: (r.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
g: (g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
b: (b.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
}
}
#[must_use]
pub fn luminance(self) -> f64 {
let r = srgb_to_linear(self.r as f64 / 255.0);
let g = srgb_to_linear(self.g as f64 / 255.0);
let b = srgb_to_linear(self.b as f64 / 255.0);
0.2126 * r + 0.7152 * g + 0.0722 * b
}
#[must_use]
pub fn contrast_ratio(self, other: Color) -> f64 {
let l1 = self.luminance();
let l2 = other.luminance();
let (lighter, darker) = if l1 > l2 { (l1, l2) } else { (l2, l1) };
(lighter + 0.05) / (darker + 0.05)
}
pub fn lerp(self, other: Color, t: f32) -> Color {
let t = t.clamp(0.0, 1.0);
Color {
r: (self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
g: (self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
b: (self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
}
}
}
fn srgb_to_linear(c: f64) -> f64 {
if c <= 0.03928 {
c / 12.92
} else {
libm::pow((c + 0.055) / 1.055, 2.4)
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum Variant {
#[default]
Dark,
Light,
}
impl core::fmt::Display for Variant {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Variant::Dark => f.write_str("Dark"),
Variant::Light => f.write_str("Light"),
}
}
}
impl core::str::FromStr for Variant {
type Err = ParseVariantError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("dark") {
Ok(Variant::Dark)
} else if s.eq_ignore_ascii_case("light") {
Ok(Variant::Light)
} else {
Err(ParseVariantError)
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
pub enum Contrast {
High,
#[default]
Normal,
Low,
}
impl core::fmt::Display for Contrast {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Contrast::High => f.write_str("High"),
Contrast::Normal => f.write_str("Normal"),
Contrast::Low => f.write_str("Low"),
}
}
}
impl Contrast {
#[must_use]
pub const fn from_ratio(ratio: f64) -> Self {
if ratio >= 10.0 {
Contrast::High
} else if ratio >= 4.5 {
Contrast::Normal
} else {
Contrast::Low
}
}
}
impl core::str::FromStr for Contrast {
type Err = ParseContrastError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.eq_ignore_ascii_case("high") {
Ok(Contrast::High)
} else if s.eq_ignore_ascii_case("normal") {
Ok(Contrast::Normal)
} else if s.eq_ignore_ascii_case("low") {
Ok(Contrast::Low)
} else {
Err(ParseContrastError)
}
}
}
impl core::fmt::Display for Color {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}
}
impl From<u32> for Color {
fn from(hex: u32) -> Self {
Self::from_hex(hex)
}
}
impl From<(u8, u8, u8)> for Color {
fn from((r, g, b): (u8, u8, u8)) -> Self {
Self::new(r, g, b)
}
}
impl From<Color> for u32 {
fn from(c: Color) -> Self {
c.to_hex()
}
}
impl From<Color> for (u8, u8, u8) {
fn from(c: Color) -> Self {
(c.r, c.g, c.b)
}
}
impl From<[u8; 3]> for Color {
fn from([r, g, b]: [u8; 3]) -> Self {
Self::new(r, g, b)
}
}
impl From<Color> for [u8; 3] {
fn from(c: Color) -> Self {
[c.r, c.g, c.b]
}
}
impl Default for Color {
fn default() -> Self {
Self::new(0, 0, 0)
}
}
impl core::str::FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_css_hex(s).ok_or(ParseColorError)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
#[non_exhaustive]
pub struct Theme {
pub name: Cow<'static, str>,
pub author: Cow<'static, str>,
pub variant: Variant,
pub contrast: Contrast,
pub bg: Color,
pub fg: Color,
pub cursor: Option<Color>,
pub selection: Option<Color>,
pub line_highlight: Option<Color>,
pub gutter: Option<Color>,
pub statusbar_bg: Option<Color>,
pub statusbar_fg: Option<Color>,
pub comment: Option<Color>,
pub keyword: Option<Color>,
pub string: Option<Color>,
pub function: Option<Color>,
pub variable: Option<Color>,
pub r#type: Option<Color>,
pub constant: Option<Color>,
pub operator: Option<Color>,
pub tag: Option<Color>,
pub error: Option<Color>,
pub warning: Option<Color>,
pub info: Option<Color>,
pub success: Option<Color>,
pub red: Option<Color>,
pub orange: Option<Color>,
pub yellow: Option<Color>,
pub green: Option<Color>,
pub cyan: Option<Color>,
pub blue: Option<Color>,
pub purple: Option<Color>,
pub magenta: Option<Color>,
}
impl Theme {
#[must_use]
pub const fn is_dark(&self) -> bool {
matches!(self.variant, Variant::Dark)
}
#[must_use]
pub const fn is_light(&self) -> bool {
matches!(self.variant, Variant::Light)
}
pub const fn accent(&self) -> Color {
if let Some(c) = self.blue {
return c;
}
if let Some(c) = self.purple {
return c;
}
if let Some(c) = self.cyan {
return c;
}
if let Some(c) = self.green {
return c;
}
if let Some(c) = self.orange {
return c;
}
if let Some(c) = self.red {
return c;
}
self.fg
}
pub fn builder(
name: impl Into<Cow<'static, str>>,
author: impl Into<Cow<'static, str>>,
bg: Color,
fg: Color,
) -> ThemeBuilder {
ThemeBuilder::new(name, author, bg, fg)
}
#[must_use]
pub fn colors(&self) -> Vec<(&'static str, Color)> {
let mut out = vec![("bg", self.bg), ("fg", self.fg)];
macro_rules! push_opt {
($field:ident) => {
if let Some(c) = self.$field {
out.push((stringify!($field), c));
}
};
}
push_opt!(cursor);
push_opt!(selection);
push_opt!(line_highlight);
push_opt!(gutter);
push_opt!(statusbar_bg);
push_opt!(statusbar_fg);
push_opt!(comment);
push_opt!(keyword);
push_opt!(string);
push_opt!(function);
push_opt!(variable);
push_opt!(r#type);
push_opt!(constant);
push_opt!(operator);
push_opt!(tag);
push_opt!(error);
push_opt!(warning);
push_opt!(info);
push_opt!(success);
push_opt!(red);
push_opt!(orange);
push_opt!(yellow);
push_opt!(green);
push_opt!(cyan);
push_opt!(blue);
push_opt!(purple);
push_opt!(magenta);
out
}
}
#[must_use = "builders do nothing until .build() is called"]
#[derive(Debug, Clone)]
pub struct ThemeBuilder {
name: Cow<'static, str>,
author: Cow<'static, str>,
bg: Color,
fg: Color,
variant: Option<Variant>,
contrast: Option<Contrast>,
cursor: Option<Color>,
selection: Option<Color>,
line_highlight: Option<Color>,
gutter: Option<Color>,
statusbar_bg: Option<Color>,
statusbar_fg: Option<Color>,
comment: Option<Color>,
keyword: Option<Color>,
string: Option<Color>,
function: Option<Color>,
variable: Option<Color>,
r#type: Option<Color>,
constant: Option<Color>,
operator: Option<Color>,
tag: Option<Color>,
error: Option<Color>,
warning: Option<Color>,
info: Option<Color>,
success: Option<Color>,
red: Option<Color>,
orange: Option<Color>,
yellow: Option<Color>,
green: Option<Color>,
cyan: Option<Color>,
blue: Option<Color>,
purple: Option<Color>,
magenta: Option<Color>,
}
impl ThemeBuilder {
pub fn new(
name: impl Into<Cow<'static, str>>,
author: impl Into<Cow<'static, str>>,
bg: Color,
fg: Color,
) -> Self {
Self {
name: name.into(),
author: author.into(),
bg,
fg,
variant: None,
contrast: None,
cursor: None,
selection: None,
line_highlight: None,
gutter: None,
statusbar_bg: None,
statusbar_fg: None,
comment: None,
keyword: None,
string: None,
function: None,
variable: None,
r#type: None,
constant: None,
operator: None,
tag: None,
error: None,
warning: None,
info: None,
success: None,
red: None,
orange: None,
yellow: None,
green: None,
cyan: None,
blue: None,
purple: None,
magenta: None,
}
}
pub fn variant(mut self, v: Variant) -> Self {
self.variant = Some(v);
self
}
pub fn contrast(mut self, c: Contrast) -> Self {
self.contrast = Some(c);
self
}
pub fn cursor(mut self, c: Color) -> Self {
self.cursor = Some(c);
self
}
pub fn selection(mut self, c: Color) -> Self {
self.selection = Some(c);
self
}
pub fn line_highlight(mut self, c: Color) -> Self {
self.line_highlight = Some(c);
self
}
pub fn gutter(mut self, c: Color) -> Self {
self.gutter = Some(c);
self
}
pub fn statusbar_bg(mut self, c: Color) -> Self {
self.statusbar_bg = Some(c);
self
}
pub fn statusbar_fg(mut self, c: Color) -> Self {
self.statusbar_fg = Some(c);
self
}
pub fn comment(mut self, c: Color) -> Self {
self.comment = Some(c);
self
}
pub fn keyword(mut self, c: Color) -> Self {
self.keyword = Some(c);
self
}
pub fn string(mut self, c: Color) -> Self {
self.string = Some(c);
self
}
pub fn function(mut self, c: Color) -> Self {
self.function = Some(c);
self
}
pub fn variable(mut self, c: Color) -> Self {
self.variable = Some(c);
self
}
pub fn r#type(mut self, c: Color) -> Self {
self.r#type = Some(c);
self
}
pub fn constant(mut self, c: Color) -> Self {
self.constant = Some(c);
self
}
pub fn operator(mut self, c: Color) -> Self {
self.operator = Some(c);
self
}
pub fn tag(mut self, c: Color) -> Self {
self.tag = Some(c);
self
}
pub fn error(mut self, c: Color) -> Self {
self.error = Some(c);
self
}
pub fn warning(mut self, c: Color) -> Self {
self.warning = Some(c);
self
}
pub fn info(mut self, c: Color) -> Self {
self.info = Some(c);
self
}
pub fn success(mut self, c: Color) -> Self {
self.success = Some(c);
self
}
pub fn red(mut self, c: Color) -> Self {
self.red = Some(c);
self
}
pub fn orange(mut self, c: Color) -> Self {
self.orange = Some(c);
self
}
pub fn yellow(mut self, c: Color) -> Self {
self.yellow = Some(c);
self
}
pub fn green(mut self, c: Color) -> Self {
self.green = Some(c);
self
}
pub fn cyan(mut self, c: Color) -> Self {
self.cyan = Some(c);
self
}
pub fn blue(mut self, c: Color) -> Self {
self.blue = Some(c);
self
}
pub fn purple(mut self, c: Color) -> Self {
self.purple = Some(c);
self
}
pub fn magenta(mut self, c: Color) -> Self {
self.magenta = Some(c);
self
}
#[must_use]
pub fn build(self) -> Theme {
let variant = self.variant.unwrap_or_else(|| {
if self.bg.luminance() > 0.5 {
Variant::Light
} else {
Variant::Dark
}
});
let contrast = self
.contrast
.unwrap_or_else(|| Contrast::from_ratio(self.bg.contrast_ratio(self.fg)));
Theme {
name: self.name,
author: self.author,
variant,
contrast,
bg: self.bg,
fg: self.fg,
cursor: self.cursor,
selection: self.selection,
line_highlight: self.line_highlight,
gutter: self.gutter,
statusbar_bg: self.statusbar_bg,
statusbar_fg: self.statusbar_fg,
comment: self.comment,
keyword: self.keyword,
string: self.string,
function: self.function,
variable: self.variable,
r#type: self.r#type,
constant: self.constant,
operator: self.operator,
tag: self.tag,
error: self.error,
warning: self.warning,
info: self.info,
success: self.success,
red: self.red,
orange: self.orange,
yellow: self.yellow,
green: self.green,
cyan: self.cyan,
blue: self.blue,
purple: self.purple,
magenta: self.magenta,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Base16Palette {
pub base00: Color,
pub base01: Color,
pub base02: Color,
pub base03: Color,
pub base04: Color,
pub base05: Color,
pub base06: Color,
pub base07: Color,
pub base08: Color,
pub base09: Color,
pub base0a: Color,
pub base0b: Color,
pub base0c: Color,
pub base0d: Color,
pub base0e: Color,
pub base0f: Color,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(
feature = "serde-support",
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Base24Palette {
pub base: Base16Palette,
pub base10: Color,
pub base11: Color,
pub base12: Color,
pub base13: Color,
pub base14: Color,
pub base15: Color,
pub base16: Color,
pub base17: Color,
}