added color

This commit is contained in:
emilis 2023-01-17 11:26:45 +00:00
parent 63ff28dd5d
commit 7890424ea9
6 changed files with 279 additions and 6 deletions

56
Cargo.lock generated
View File

@ -19,6 +19,7 @@ name = "kkdisp"
version = "0.1.0"
dependencies = [
"anyhow",
"serde",
"termion",
]
@ -42,6 +43,24 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@ -60,6 +79,37 @@ dependencies = [
"redox_syscall",
]
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termion"
version = "2.0.1"
@ -71,3 +121,9 @@ dependencies = [
"redox_syscall",
"redox_termios",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"

View File

@ -8,3 +8,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0.68"
termion = "2.0.1"
[dependencies.serde]
version = "1"
features = ["std", "derive"]

View File

@ -1,11 +1,15 @@
#[derive(PartialEq, Eq, Debug)]
use crate::theme::{Color, ColorSet};
#[derive(PartialEq, Debug, Clone)]
pub enum Component {
X(usize),
String(String),
Clear(Clear),
Fg(Color),
Bg(Color),
}
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum Clear {
Line,
Screen,

View File

@ -3,6 +3,7 @@ use termion::raw::{IntoRawMode, RawTerminal};
extern crate termion;
mod component;
mod theme;
mod token;
pub struct Display {

152
kkdisp/src/theme.rs Normal file
View File

@ -0,0 +1,152 @@
use serde::{de::Visitor, Deserialize, Serialize};
use std::ops::Deref;
use termion::color;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct Theme {
pub primary: ColorSet,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)]
pub struct ColorSet {
pub fg: Color,
pub bg: Color,
}
impl PartialEq for Color {
fn eq(&self, other: &Self) -> bool {
self.0 .0 == other.0 .0
&& self.0 .1 == other.0 .1
&& self.0 .2 == other.0 .2
}
}
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)]
pub struct Color(color::Rgb);
impl Color {
pub const BLACK: Color = Color(color::Rgb(0, 0, 0));
pub const RED: Color = Color(color::Rgb(255, 0, 0));
pub const GREEN: Color = Color(color::Rgb(0, 255, 0));
pub const BLUE: Color = Color(color::Rgb(0, 0, 255));
}
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 {
primary: ColorSet {
fg: "#330033".try_into().unwrap(),
bg: "#a006d3".try_into().unwrap(),
},
}
}
}

View File

@ -1,4 +1,4 @@
use crate::component::Component;
use crate::{component::Component, theme::Color};
// (line (centered (limited (padded "hello world"))))
#[derive(Debug, Clone)]
@ -6,6 +6,8 @@ pub enum Token {
Centered(Box<Token>),
Limited(Box<Token>, usize),
CharPad(Box<Token>, char, usize),
Fg(Box<Token>, Color),
Bg(Box<Token>, Color),
String(String),
}
@ -36,9 +38,19 @@ impl Token {
Self::CharPad(Box::new(self), c, pad_to)
}
pub fn fg(self, c: Color) -> Self {
Self::Fg(Box::new(self), c)
}
pub fn bg(self, c: Color) -> Self {
Self::Bg(Box::new(self), c)
}
fn walk_to_end(&self) -> &String {
match self {
Token::String(s) => s,
Token::Fg(t, _) => t.walk_to_end(),
Token::Bg(t, _) => t.walk_to_end(),
Token::Centered(t) => t.walk_to_end(),
Token::Limited(t, _) => t.walk_to_end(),
Token::CharPad(t, _, _) => t.walk_to_end(),
@ -96,6 +108,14 @@ impl Token {
}
})
.collect(),
Token::Fg(t, c) => vec![Component::Fg(c)]
.into_iter()
.chain(t.with_width(width))
.collect(),
Token::Bg(t, c) => vec![Component::Bg(c)]
.into_iter()
.chain(t.with_width(width))
.collect(),
}
}
}
@ -146,10 +166,45 @@ mod tests {
.pad_char('_', 15)
.centered(),
vec![
Component::X(WIDTH - 3),
Component::X((WIDTH - 15) / 2),
Component::String("It was...______".into()),
],
),
(
"prefixes color",
Token::String("this is red".into()).fg(Color::RED),
vec![
Component::Fg(Color::RED),
Component::String("this is red".into()),
],
),
(
"color at beginning of line",
Token::String("colored".into())
.centered()
.bg(Color::RED),
vec![
Component::Bg(Color::RED),
Component::X((WIDTH - 7) / 2),
Component::String("colored".into()),
],
),
(
"color isnt part of limit",
Token::String("ten chars!".into())
.bg(Color::RED)
.fg(Color::GREEN)
.bg(Color::BLUE)
.fg(Color::BLACK)
.limited(10),
vec![
Component::Fg(Color::BLACK),
Component::Bg(Color::BLUE),
Component::Fg(Color::GREEN),
Component::Bg(Color::RED),
Component::String("ten chars!".into()),
],
),
]
.into_iter()
.for_each(|test| {
@ -159,11 +214,12 @@ mod tests {
let actual_fmt = format!("{:#?}", &actual);
assert!(
expected
.clone()
.iter()
.zip(actual.iter())
.filter(|&(l, r)| l != r)
.filter(|&(l, r)| l == r)
.count()
== 0,
!= 0,
"{} expected: {:#?} actual: {}",
name,
expected,