tinterm/color.rs
1/// Represents an RGB color with red, green, and blue components.
2///
3/// Each component is a value between 0 and 255, where:
4/// - 0 represents no intensity
5/// - 255 represents full intensity
6///
7/// # Examples
8///
9/// ```
10/// use tinterm::Color;
11///
12/// // Create a red color
13/// let red = Color::new(255, 0, 0);
14///
15/// // Use predefined colors
16/// let blue = Color::BLUE;
17///
18/// // Create from hex string
19/// let orange = Color::from_hex("#FF8000").unwrap();
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Color {
23 /// Red component (0-255)
24 pub r: u8,
25 /// Green component (0-255)
26 pub g: u8,
27 /// Blue component (0-255)
28 pub b: u8,
29}
30
31impl Color {
32 /// Creates a new Color with the specified RGB values.
33 ///
34 /// # Arguments
35 ///
36 /// * `r` - Red component (0-255)
37 /// * `g` - Green component (0-255)
38 /// * `b` - Blue component (0-255)
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use tinterm::Color;
44 ///
45 /// let purple = Color::new(128, 0, 128);
46 /// ```
47 pub fn new(r: u8, g: u8, b: u8) -> Self {
48 Color { r, g, b }
49 }
50
51 /// Creates a new Color with the specified RGB values.
52 ///
53 /// This is an alias for [`Color::new`].
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use tinterm::Color;
59 ///
60 /// let cyan = Color::rgb(0, 255, 255);
61 /// ```
62 pub fn rgb(r: u8, g: u8, b: u8) -> Self {
63 Color { r, g, b }
64 }
65
66 pub(crate) fn interpolate(&self, other: &Color, t: f32) -> Color {
67 Color {
68 r: (self.r as f32 + (other.r as f32 - self.r as f32) * t) as u8,
69 g: (self.g as f32 + (other.g as f32 - self.g as f32) * t) as u8,
70 b: (self.b as f32 + (other.b as f32 - self.b as f32) * t) as u8,
71 }
72 }
73
74 /// Creates a new Color from a hexadecimal color string.
75 ///
76 /// Supports both 6-digit (e.g., "#FF0000" or "FF0000") and 3-digit (e.g., "#F00" or "F00") formats.
77 /// The '#' prefix is optional.
78 ///
79 /// # Arguments
80 ///
81 /// * `hex` - A hex color string
82 ///
83 /// # Returns
84 ///
85 /// * `Ok(Color)` - If the hex string is valid
86 /// * `Err(&str)` - If the hex string is invalid
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// use tinterm::Color;
92 ///
93 /// let red = Color::from_hex("#FF0000").unwrap();
94 /// let green = Color::from_hex("00FF00").unwrap();
95 /// let blue = Color::from_hex("#00F").unwrap();
96 /// ```
97 pub fn from_hex(hex: &str) -> Result<Self, &'static str> {
98 // Remove '#' if present
99 let hex = hex.trim_start_matches('#');
100
101 // Validate hex length
102 if hex.len() != 6 && hex.len() != 3 {
103 return Err(
104 "Invalid hex color length. Expected 6 or 3 characters (e.g., FF0000 or F00)",
105 );
106 }
107
108 // Validate hex characters
109 if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
110 return Err("Invalid hex color format. Expected hexadecimal characters (0-9, A-F)");
111 }
112
113 let (r, g, b) = if hex.len() == 3 {
114 // Convert 3-digit format to 6-digit by duplicating each digit
115 let r = u8::from_str_radix(
116 &format!(
117 "{}{}",
118 hex.chars().nth(0).unwrap(),
119 hex.chars().nth(0).unwrap()
120 ),
121 16,
122 )
123 .map_err(|_| "Failed to parse red component")?;
124 let g = u8::from_str_radix(
125 &format!(
126 "{}{}",
127 hex.chars().nth(1).unwrap(),
128 hex.chars().nth(1).unwrap()
129 ),
130 16,
131 )
132 .map_err(|_| "Failed to parse green component")?;
133 let b = u8::from_str_radix(
134 &format!(
135 "{}{}",
136 hex.chars().nth(2).unwrap(),
137 hex.chars().nth(2).unwrap()
138 ),
139 16,
140 )
141 .map_err(|_| "Failed to parse blue component")?;
142 (r, g, b)
143 } else {
144 // Parse 6-digit format
145 let r =
146 u8::from_str_radix(&hex[0..2], 16).map_err(|_| "Failed to parse red component")?;
147 let g = u8::from_str_radix(&hex[2..4], 16)
148 .map_err(|_| "Failed to parse green component")?;
149 let b =
150 u8::from_str_radix(&hex[4..6], 16).map_err(|_| "Failed to parse blue component")?;
151 (r, g, b)
152 };
153
154 Ok(Color { r, g, b })
155 }
156
157 /// Converts the color to a hexadecimal string representation.
158 ///
159 /// Returns a 6-digit uppercase hex string with '#' prefix.
160 ///
161 /// # Examples
162 ///
163 /// ```
164 /// use tinterm::Color;
165 ///
166 /// let red = Color::new(255, 0, 0);
167 /// assert_eq!(red.to_hex(), "#FF0000");
168 /// ```
169 pub fn to_hex(&self) -> String {
170 format!("#{:02X}{:02X}{:02X}", self.r, self.g, self.b)
171 }
172}