summaryrefslogtreecommitdiffstats
path: root/src/canvas/styling/colour_utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/canvas/styling/colour_utils.rs')
-rw-r--r--src/canvas/styling/colour_utils.rs268
1 files changed, 268 insertions, 0 deletions
diff --git a/src/canvas/styling/colour_utils.rs b/src/canvas/styling/colour_utils.rs
new file mode 100644
index 00000000..5c39be74
--- /dev/null
+++ b/src/canvas/styling/colour_utils.rs
@@ -0,0 +1,268 @@
+use concat_string::concat_string;
+use itertools::Itertools;
+use tui::style::{Color, Style};
+use unicode_segmentation::UnicodeSegmentation;
+
+use crate::utils::error;
+
+pub const FIRST_COLOUR: Color = Color::LightMagenta;
+pub const SECOND_COLOUR: Color = Color::LightYellow;
+pub const THIRD_COLOUR: Color = Color::LightCyan;
+pub const FOURTH_COLOUR: Color = Color::LightGreen;
+#[cfg(not(target_os = "windows"))]
+pub const FIFTH_COLOUR: Color = Color::LightRed;
+pub const HIGHLIGHT_COLOUR: Color = Color::LightBlue;
+pub const AVG_COLOUR: Color = Color::Red;
+pub const ALL_COLOUR: Color = Color::Green;
+
+/// Convert a hex string to a colour.
+fn convert_hex_to_color(hex: &str) -> error::Result<Color> {
+ fn hex_component_to_int(hex: &str, first: &str, second: &str) -> error::Result<u8> {
+ u8::from_str_radix(&concat_string!(first, second), 16).map_err(|_| {
+ error::BottomError::ConfigError(format!(
+ "\"{hex}\" is an invalid hex color, could not decode."
+ ))
+ })
+ }
+
+ fn invalid_hex_format(hex: &str) -> error::BottomError {
+ error::BottomError::ConfigError(format!(
+ "\"{hex}\" is an invalid hex color. It must be either a 7 character hex string of the form \"#12ab3c\" or a 3 character hex string of the form \"#1a2\".",
+ ))
+ }
+
+ if !hex.starts_with('#') {
+ return Err(invalid_hex_format(hex));
+ }
+
+ let components = hex.graphemes(true).collect_vec();
+ if components.len() == 7 {
+ // A 6-long hex.
+ let r = hex_component_to_int(hex, components[1], components[2])?;
+ let g = hex_component_to_int(hex, components[3], components[4])?;
+ let b = hex_component_to_int(hex, components[5], components[6])?;
+
+ Ok(Color::Rgb(r, g, b))
+ } else if components.len() == 4 {
+ // A 3-long hex.
+ let r = hex_component_to_int(hex, components[1], components[1])?;
+ let g = hex_component_to_int(hex, components[2], components[2])?;
+ let b = hex_component_to_int(hex, components[3], components[3])?;
+
+ Ok(Color::Rgb(r, g, b))
+ } else {
+ Err(invalid_hex_format(hex))
+ }
+}
+
+pub fn str_to_fg(input_val: &str) -> error::Result<Style> {
+ Ok(Style::default().fg(str_to_colour(input_val)?))
+}
+
+pub fn str_to_colour(input_val: &str) -> error::Result<Color> {
+ if input_val.len() > 1 {
+ if input_val.starts_with('#') {
+ convert_hex_to_color(input_val)
+ } else if input_val.contains(',') {
+ convert_rgb_to_color(input_val)
+ } else {
+ convert_name_to_colour(input_val)
+ }
+ } else {
+ Err(error::BottomError::ConfigError(format!(
+ "value \"{input_val}\" is not valid.",
+ )))
+ }
+}
+
+fn convert_rgb_to_color(rgb_str: &str) -> error::Result<Color> {
+ let rgb_list = rgb_str.split(',').collect::<Vec<&str>>();
+ if rgb_list.len() != 3 {
+ return Err(error::BottomError::ConfigError(format!(
+ "value \"{rgb_str}\" is an invalid RGB colour. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
+ )));
+ }
+
+ let rgb = rgb_list
+ .iter()
+ .filter_map(|val| {
+ if let Ok(res) = (*(*val)).to_string().trim().parse::<u8>() {
+ Some(res)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ if rgb.len() == 3 {
+ Ok(Color::Rgb(rgb[0], rgb[1], rgb[2]))
+ } else {
+ Err(error::BottomError::ConfigError(format!(
+ "value \"{rgb_str}\" contained invalid RGB values. It must be a comma separated value with 3 integers from 0 to 255 (ie: \"255, 0, 155\").",
+ )))
+ }
+}
+
+fn convert_name_to_colour(color_name: &str) -> error::Result<Color> {
+ match color_name.to_lowercase().trim() {
+ "reset" => Ok(Color::Reset),
+ "black" => Ok(Color::Black),
+ "red" => Ok(Color::Red),
+ "green" => Ok(Color::Green),
+ "yellow" => Ok(Color::Yellow),
+ "blue" => Ok(Color::Blue),
+ "magenta" => Ok(Color::Magenta),
+ "cyan" => Ok(Color::Cyan),
+ "gray" | "grey" => Ok(Color::Gray),
+ "darkgray" | "darkgrey" | "dark gray" | "dark grey" => Ok(Color::DarkGray),
+ "lightred" | "light red" => Ok(Color::LightRed),
+ "lightgreen" | "light green" => Ok(Color::LightGreen),
+ "lightyellow" | "light yellow" => Ok(Color::LightYellow),
+ "lightblue" | "light blue" => Ok(Color::LightBlue),
+ "lightmagenta" | "light magenta" => Ok(Color::LightMagenta),
+ "lightcyan" | "light cyan" => Ok(Color::LightCyan),
+ "white" => Ok(Color::White),
+ _ => Err(error::BottomError::ConfigError(format!(
+ "\"{color_name}\" is an invalid named color.
+
+The following are supported strings:
++--------+-------------+---------------------+
+| Reset | Magenta | Light Yellow |
++--------+-------------+---------------------+
+| Black | Cyan | Light Blue |
++--------+-------------+---------------------+
+| Red | Gray/Grey | Light Magenta |
++--------+-------------+---------------------+
+| Green | Light Cyan | Dark Gray/Dark Grey |
++--------+-------------+---------------------+
+| Yellow | Light Red | White |
++--------+-------------+---------------------+
+| Blue | Light Green | |
++--------+-------------+---------------------+
+ ",
+ ))),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn invalid_colour_names() {
+ // Test invalid spacing in single word.
+ assert!(convert_name_to_colour("bl ack").is_err());
+
+ // Test invalid spacing in dual word.
+ assert!(convert_name_to_colour("darkg ray").is_err());
+
+ // Test completely invalid colour.
+ assert!(convert_name_to_colour("darkreset").is_err());
+ }
+
+ #[test]
+ fn valid_colour_names() {
+ // Standard color should work
+ assert_eq!(convert_name_to_colour("red"), Ok(Color::Red));
+
+ // Capitalizing should be fine.
+ assert_eq!(convert_name_to_colour("RED"), Ok(Color::Red));
+
+ // Spacing shouldn't be an issue now.
+ assert_eq!(convert_name_to_colour(" red "), Ok(Color::Red));
+
+ // The following are all equivalent.
+ assert_eq!(convert_name_to_colour("darkgray"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("darkgrey"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("dark grey"), Ok(Color::DarkGray));
+ assert_eq!(convert_name_to_colour("dark gray"), Ok(Color::DarkGray));
+
+ assert_eq!(convert_name_to_colour("grey"), Ok(Color::Gray));
+ assert_eq!(convert_name_to_colour("gray"), Ok(Color::Gray));
+
+ // One more test with spacing.
+ assert_eq!(
+ convert_name_to_colour(" lightmagenta "),
+ Ok(Color::LightMagenta)
+ );
+ assert_eq!(
+ convert_name_to_colour("light magenta"),
+ Ok(Color::LightMagenta)
+ );
+ assert_eq!(
+ convert_name_to_colour(" light magenta "),
+ Ok(Color::LightMagenta)
+ );
+ }
+
+ #[test]
+ fn valid_hex_colours() {
+ // Check hex with 6 characters.
+ assert_eq!(
+ convert_hex_to_color("#ffffff").unwrap(),
+ Color::Rgb(255, 255, 255)
+ );
+ assert_eq!(
+ convert_hex_to_color("#000000").unwrap(),
+ Color::Rgb(0, 0, 0)
+ );
+ convert_hex_to_color("#111111").unwrap();
+ convert_hex_to_color("#11ff11").unwrap();
+ convert_hex_to_color("#1f1f1f").unwrap();
+ assert_eq!(
+ convert_hex_to_color("#123abc").unwrap(),
+ Color::Rgb(18, 58, 188)
+ );
+
+ // Check hex with 3 characters.
+ assert_eq!(
+ convert_hex_to_color("#fff").unwrap(),
+ Color::Rgb(255, 255, 255)
+ );
+ assert_eq!(convert_hex_to_color("#000").unwrap(), Color::Rgb(0, 0, 0));
+ convert_hex_to_color("#111").unwrap();
+ convert_hex_to_color("#1f1").unwrap();
+ convert_hex_to_color("#f1f").unwrap();
+ convert_hex_to_color("#ff1").unwrap();
+ convert_hex_to_color("#1ab").unwrap();
+ assert_eq!(
+ convert_hex_to_color("#1ab").unwrap(),
+ Color::Rgb(17, 170, 187)
+ );
+ }
+
+ #[test]
+ fn invalid_hex_colours() {
+ assert!(convert_hex_to_color("ffffff").is_err());
+ assert!(convert_hex_to_color("111111").is_err());
+
+ assert!(convert_hex_to_color("fff").is_err());
+ assert!(convert_hex_to_color("111").is_err());
+ assert!(convert_hex_to_color("fffffff").is_err());
+ assert!(convert_hex_to_color("1234567").is_err());
+
+ assert!(convert_hex_to_color("#fffffff").is_err());
+ assert!(convert_hex_to_color("#1234567").is_err());
+ assert!(convert_hex_to_color("#ff").is_err());
+ assert!(convert_hex_to_color("#12").is_err());
+ assert!(convert_hex_to_color("").is_err());
+
+ assert!(convert_hex_to_color("#pppppp").is_err());
+ assert!(convert_hex_to_color("#00000p").is_err());
+ assert!(convert_hex_to_color("#ppp").is_err());
+
+ assert!(convert_hex_to_color("#一").is_err());
+ assert!(convert_hex_to_color("#一二").is_err());
+ assert!(convert_hex_to_color("#一二三").is_err());
+ assert!(convert_hex_to_color("#一二三四").is_err());
+
+ assert!(convert_hex_to_color("#f一f").is_err());
+ assert!(convert_hex_to_color("#ff一11").is_err());
+
+ assert!(convert_hex_to_color("#🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦").is_err());
+ assert!(convert_hex_to_color("#🇨🇦🇨🇦🇨🇦🇨🇦").is_err());
+
+ assert!(convert_hex_to_color("#हिन्दी").is_err());
+ }
+}