kk/src/display/theme.rs

217 lines
5.6 KiB
Rust

use serde::{de::Visitor, Deserialize, Serialize};
use std::ops::Deref;
use termion::color;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Theme {
pub windows: Colors,
pub frames: Frames,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct ColorSet {
pub fg: Color,
pub bg: Color,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Frames {
pub outer: Frame,
pub inner: Frame,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct FrameChars {
pub top: char,
pub bottom: char,
pub left: char,
pub right: char,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Frame {
pub width: u16,
pub frame_chars: FrameChars,
pub color: ColorSet,
}
impl Default for ColorSet {
fn default() -> Self {
Self {
fg: "#ffffff".try_into().unwrap(),
bg: "#000000".try_into().unwrap(),
}
}
}
impl ToString for ColorSet {
#[inline(always)]
fn to_string(&self) -> String {
self.bg.bg_string() + &self.fg.fg_string()
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Colors {
pub primary: ColorSet,
pub subwin: ColorSet,
pub highlight: ColorSet,
}
#[derive(Clone, Copy, Debug)]
pub struct Color(color::Rgb);
impl Serialize for Color {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let rgb = self.0;
serializer.serialize_str(&format!(
"#{:02X}{:02X}{:02X}",
rgb.0, rgb.1, rgb.2,
))
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct ColorCodeVisitor;
impl<'de> Visitor<'de> for ColorCodeVisitor {
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str("hex color code")
}
type Value = Color;
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match TryInto::<Color>::try_into(v) {
Ok(c) => Ok(c),
Err(e) => Err(E::custom(e.to_string())),
}
}
fn visit_string<E>(
self,
v: String,
) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match TryInto::<Color>::try_into(v) {
Ok(c) => Ok(c),
Err(e) => Err(E::custom(e.to_string())),
}
}
}
deserializer.deserialize_any(ColorCodeVisitor {})
}
}
impl Deref for Color {
type Target = color::Rgb;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TryFrom<String> for Color {
type Error = anyhow::Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
Ok(value.as_str().try_into()?)
}
}
impl TryFrom<&str> for Color {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
if value.len() < 6 || value.len() > 7 {
panic!("hex code length invalid: {}", value.len());
}
let mut i = 0;
if value.starts_with('#') {
i = 1;
}
Ok(Self(color::Rgb(
u8::from_str_radix(&value[i..i + 2], 16)
.map_err(|_| anyhow::anyhow!("red hex"))?,
u8::from_str_radix(&value[i + 2..i + 4], 16)
.map_err(|_| anyhow::anyhow!("green hex"))?,
u8::from_str_radix(&value[i + 4..i + 6], 16)
.map_err(|_| anyhow::anyhow!("blue hex"))?,
)))
}
}
impl Default for Theme {
fn default() -> Self {
Self {
windows: Colors {
primary: ColorSet {
fg: "#330033".try_into().unwrap(),
bg: "#a006d3".try_into().unwrap(),
},
highlight: ColorSet {
fg: "#ffffff".try_into().unwrap(),
bg: "#0000ff".try_into().unwrap(),
},
subwin: ColorSet {
fg: "#ffffff".try_into().unwrap(),
bg: "#500170".try_into().unwrap(),
},
},
frames: Frames {
outer: Frame {
color: ColorSet {
fg: "#ff36ff".try_into().unwrap(),
bg: "#9005c2".try_into().unwrap(),
},
frame_chars: FrameChars {
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
},
width: 1,
},
inner: Frame {
color: ColorSet {
fg: "#ffe6ff".try_into().unwrap(),
bg: "#7003a0".try_into().unwrap(),
},
frame_chars: FrameChars {
top: ' ',
bottom: ' ',
left: ' ',
right: ' ',
},
width: 1,
},
},
}
}
}
impl Theme {
#[inline(always)]
pub fn primary(&self) -> String {
self.windows.primary.to_string()
}
#[inline(always)]
pub fn frame(&self) -> String {
self.frames.outer.color.to_string()
}
}