dioxus/packages/tui/src/style.rs
2022-03-30 20:45:41 -05:00

451 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::{num::ParseFloatError, str::FromStr};
use tui::style::{Color, Modifier, Style};
use crate::RenderingMode;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RinkColor {
pub color: Color,
pub alpha: u8,
}
impl Default for RinkColor {
fn default() -> Self {
Self {
color: Color::Black,
alpha: 0,
}
}
}
impl RinkColor {
pub fn blend(self, other: Color) -> Color {
if self.color == Color::Reset {
Color::Reset
} else if self.alpha == 0 {
other
} else {
let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
let [or, og, ob] = to_rgb(other).map(|e| e as u16);
let sa = self.alpha as u16;
let rsa = 255 - sa;
Color::Rgb(
((sr * sa + or * rsa) / 255) as u8,
((sg * sa + og * rsa) / 255) as u8,
((sb * sa + ob * rsa) / 255) as u8,
)
}
}
}
fn parse_value(
v: &str,
current_max_output: f32,
required_max_output: f32,
) -> Result<f32, ParseFloatError> {
if let Some(stripped) = v.strip_suffix('%') {
Ok((stripped.trim().parse::<f32>()? / 100.0) * required_max_output)
} else {
Ok((v.trim().parse::<f32>()? / current_max_output) * required_max_output)
}
}
pub struct ParseColorError;
fn parse_hex(color: &str) -> Result<Color, ParseColorError> {
let mut values = [0, 0, 0];
let mut color_ok = true;
for i in 0..values.len() {
if let Ok(v) = u8::from_str_radix(&color[(1 + 2 * i)..(1 + 2 * (i + 1))], 16) {
values[i] = v;
} else {
color_ok = false;
}
}
if color_ok {
Ok(Color::Rgb(values[0], values[1], values[2]))
} else {
Err(ParseColorError)
}
}
fn parse_rgb(color: &str) -> Result<Color, ParseColorError> {
let mut values = [0, 0, 0];
let mut color_ok = true;
for (v, i) in color.split(',').zip(0..values.len()) {
if let Ok(v) = parse_value(v.trim(), 255.0, 255.0) {
values[i] = v as u8;
} else {
color_ok = false;
}
}
if color_ok {
Ok(Color::Rgb(values[0], values[1], values[2]))
} else {
Err(ParseColorError)
}
}
fn parse_hsl(color: &str) -> Result<Color, ParseColorError> {
let mut values = [0.0, 0.0, 0.0];
let mut color_ok = true;
for (v, i) in color.split(',').zip(0..values.len()) {
if let Ok(v) = parse_value(v.trim(), if i == 0 { 360.0 } else { 100.0 }, 1.0) {
values[i] = v;
} else {
color_ok = false;
}
}
if color_ok {
let [h, s, l] = values;
let rgb = if s == 0.0 {
[l as u8; 3]
} else {
fn hue_to_rgb(p: f32, q: f32, mut t: f32) -> f32 {
if t < 0.0 {
t += 1.0;
}
if t > 1.0 {
t -= 1.0;
}
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}
let q = if l < 0.5 {
l * (1.0 + s)
} else {
l + s - l * s
};
let p = 2.0 * l - q;
[
(hue_to_rgb(p, q, h + 1.0 / 3.0) * 255.0) as u8,
(hue_to_rgb(p, q, h) * 255.0) as u8,
(hue_to_rgb(p, q, h - 1.0 / 3.0) * 255.0) as u8,
]
};
Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
} else {
Err(ParseColorError)
}
}
impl FromStr for RinkColor {
type Err = ParseColorError;
fn from_str(color: &str) -> Result<Self, Self::Err> {
match color {
"red" => Ok(RinkColor {
color: Color::Red,
alpha: 255,
}),
"black" => Ok(RinkColor {
color: Color::Black,
alpha: 255,
}),
"green" => Ok(RinkColor {
color: Color::Green,
alpha: 255,
}),
"yellow" => Ok(RinkColor {
color: Color::Yellow,
alpha: 255,
}),
"blue" => Ok(RinkColor {
color: Color::Blue,
alpha: 255,
}),
"magenta" => Ok(RinkColor {
color: Color::Magenta,
alpha: 255,
}),
"cyan" => Ok(RinkColor {
color: Color::Cyan,
alpha: 255,
}),
"gray" => Ok(RinkColor {
color: Color::Gray,
alpha: 255,
}),
"darkgray" => Ok(RinkColor {
color: Color::DarkGray,
alpha: 255,
}),
// light red does not exist
"orangered" => Ok(RinkColor {
color: Color::LightRed,
alpha: 255,
}),
"lightgreen" => Ok(RinkColor {
color: Color::LightGreen,
alpha: 255,
}),
"lightyellow" => Ok(RinkColor {
color: Color::LightYellow,
alpha: 255,
}),
"lightblue" => Ok(RinkColor {
color: Color::LightBlue,
alpha: 255,
}),
// light magenta does not exist
"orchid" => Ok(RinkColor {
color: Color::LightMagenta,
alpha: 255,
}),
"lightcyan" => Ok(RinkColor {
color: Color::LightCyan,
alpha: 255,
}),
"white" => Ok(RinkColor {
color: Color::White,
alpha: 255,
}),
_ => {
if color.len() == 7 && color.starts_with('#') {
parse_hex(color).map(|c| RinkColor {
color: c,
alpha: 255,
})
} else if let Some(stripped) = color.strip_prefix("rgb(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (alpha, rgb_values) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = alpha.parse() {
parse_rgb(rgb_values).map(|c| RinkColor { color: c, alpha: a })
} else {
Err(ParseColorError)
}
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("rgba(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_rgb(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_rgb(color_values).map(|c| RinkColor {
color: c,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("hsl(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 255,
})
}
} else if let Some(stripped) = color.strip_prefix("hsla(") {
let color_values = stripped.trim_end_matches(')');
if color.matches(',').count() == 3 {
let (rgb_values, alpha) =
color_values.rsplit_once(',').ok_or(ParseColorError)?;
if let Ok(a) = parse_value(alpha, 1.0, 1.0) {
parse_hsl(rgb_values).map(|c| RinkColor {
color: c,
alpha: (a * 255.0) as u8,
})
} else {
Err(ParseColorError)
}
} else {
parse_hsl(color_values).map(|c| RinkColor {
color: c,
alpha: 255,
})
}
} else {
Err(ParseColorError)
}
}
}
}
}
fn to_rgb(c: Color) -> [u8; 3] {
match c {
Color::Black => [0, 0, 0],
Color::Red => [255, 0, 0],
Color::Green => [0, 128, 0],
Color::Yellow => [255, 255, 0],
Color::Blue => [0, 0, 255],
Color::Magenta => [255, 0, 255],
Color::Cyan => [0, 255, 255],
Color::Gray => [128, 128, 128],
Color::DarkGray => [169, 169, 169],
Color::LightRed => [255, 69, 0],
Color::LightGreen => [144, 238, 144],
Color::LightYellow => [255, 255, 224],
Color::LightBlue => [173, 216, 230],
Color::LightMagenta => [218, 112, 214],
Color::LightCyan => [224, 255, 255],
Color::White => [255, 255, 255],
Color::Rgb(r, g, b) => [r, g, b],
Color::Indexed(idx) => match idx {
16..=231 => {
let v = idx - 16;
// add 3 to round up
let r = ((v as u16 / 36) * 255 + 3) / 5;
let g = (((v as u16 % 36) / 6) * 255 + 3) / 5;
let b = ((v as u16 % 6) * 255 + 3) / 5;
[r as u8, g as u8, b as u8]
}
232..=255 => {
let l = (idx - 232) / 24;
[l; 3]
}
// rink will never generate these colors, but they might be on the screen from another program
_ => [0, 0, 0],
},
Color::Reset => [0, 0, 0],
}
}
pub fn convert(mode: RenderingMode, c: Color) -> Color {
if let Color::Reset = c {
c
} else {
match mode {
crate::RenderingMode::BaseColors => match c {
Color::Rgb(_, _, _) => panic!("cannot convert rgb color to base color"),
Color::Indexed(_) => panic!("cannot convert Ansi color to base color"),
_ => c,
},
crate::RenderingMode::Rgb => {
let rgb = to_rgb(c);
Color::Rgb(rgb[0], rgb[1], rgb[2])
}
crate::RenderingMode::Ansi => match c {
Color::Indexed(_) => c,
_ => {
let rgb = to_rgb(c);
// 16-231: 6 × 6 × 6 color cube
// 232-255: 23 step grayscale
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
let idx = 232 + (rgb[0] as u16 * 23 / 255) as u8;
Color::Indexed(idx)
} else {
let r = (rgb[0] as u16 * 5) / 255;
let g = (rgb[1] as u16 * 5) / 255;
let b = (rgb[2] as u16 * 5) / 255;
let idx = 16 + r * 36 + g * 6 + b;
Color::Indexed(idx as u8)
}
}
},
}
}
}
#[test]
fn rgb_to_ansi() {
for idx in 17..=231 {
let idxed = Color::Indexed(idx);
let rgb = to_rgb(idxed);
// gray scale colors have two equivelent repersentations
let color = Color::Rgb(rgb[0], rgb[1], rgb[2]);
let converted = convert(RenderingMode::Ansi, color);
if let Color::Indexed(i) = converted {
if rgb[0] != rgb[1] || rgb[1] != rgb[2] {
assert_eq!(idxed, converted);
} else {
assert!(i >= 232);
}
} else {
panic!("color is not indexed")
}
}
for idx in 232..=255 {
let idxed = Color::Indexed(idx);
let rgb = to_rgb(idxed);
assert!(rgb[0] == rgb[1] && rgb[1] == rgb[2]);
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct RinkStyle {
pub fg: Option<RinkColor>,
pub bg: Option<RinkColor>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
impl Default for RinkStyle {
fn default() -> Self {
Self {
fg: Some(RinkColor {
color: Color::White,
alpha: 255,
}),
bg: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
}
impl RinkStyle {
pub fn add_modifier(mut self, m: Modifier) -> Self {
self.sub_modifier.remove(m);
self.add_modifier.insert(m);
self
}
pub fn remove_modifier(mut self, m: Modifier) -> Self {
self.add_modifier.remove(m);
self.sub_modifier.insert(m);
self
}
pub fn merge(mut self, other: RinkStyle) -> Self {
self.fg = self.fg.or(other.fg);
self.add_modifier(other.add_modifier)
.remove_modifier(other.sub_modifier)
}
}
impl From<RinkStyle> for Style {
fn from(val: RinkStyle) -> Self {
Style {
fg: val.fg.map(|c| c.color),
bg: val.bg.map(|c| c.color),
add_modifier: val.add_modifier,
sub_modifier: val.sub_modifier,
}
}
}