1use std::hash::{DefaultHasher, Hash, Hasher};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct RGB {
8 pub r: u8,
10 pub g: u8,
12 pub b: u8,
14}
15
16impl RGB {
17 pub fn new(r: u8, g: u8, b: u8) -> Self {
19 Self { r, g, b }
20 }
21
22 pub fn write_fg<W: core::fmt::Write>(&self, f: &mut W) -> core::fmt::Result {
24 write!(f, "\x1b[38;2;{};{};{}m", self.r, self.g, self.b)
25 }
26
27 pub fn write_bg<W: core::fmt::Write>(&self, f: &mut W) -> core::fmt::Result {
29 write!(f, "\x1b[48;2;{};{};{}m", self.r, self.g, self.b)
30 }
31}
32
33pub struct ColorGenerator {
35 base_hue: f32,
36 saturation: f32,
37 lightness: f32,
38}
39
40impl Default for ColorGenerator {
41 fn default() -> Self {
42 Self {
43 base_hue: 210.0,
44 saturation: 0.7,
45 lightness: 0.6,
46 }
47 }
48}
49
50impl ColorGenerator {
51 pub fn new() -> Self {
53 Self::default()
54 }
55
56 pub fn with_base_hue(mut self, hue: f32) -> Self {
58 self.base_hue = hue;
59 self
60 }
61
62 pub fn with_saturation(mut self, saturation: f32) -> Self {
64 self.saturation = saturation.clamp(0.0, 1.0);
65 self
66 }
67
68 pub fn with_lightness(mut self, lightness: f32) -> Self {
70 self.lightness = lightness.clamp(0.0, 1.0);
71 self
72 }
73
74 pub fn generate_color(&self, hash: u64) -> RGB {
76 let hue_offset = (hash % 360) as f32;
78 let hue = (self.base_hue + hue_offset) % 360.0;
79
80 self.hsl_to_rgb(hue, self.saturation, self.lightness)
82 }
83
84 pub fn generate_color_for<T: Hash>(&self, value: &T) -> RGB {
86 let mut hasher = DefaultHasher::new();
87 value.hash(&mut hasher);
88 let hash = hasher.finish();
89 self.generate_color(hash)
90 }
91
92 fn hsl_to_rgb(&self, h: f32, s: f32, l: f32) -> RGB {
94 let c = (1.0 - (2.0 * l - 1.0).abs()) * s;
95 let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
96 let m = l - c / 2.0;
97
98 let (r, g, b) = match h as u32 {
99 0..=59 => (c, x, 0.0),
100 60..=119 => (x, c, 0.0),
101 120..=179 => (0.0, c, x),
102 180..=239 => (0.0, x, c),
103 240..=299 => (x, 0.0, c),
104 _ => (c, 0.0, x),
105 };
106
107 RGB::new(
108 ((r + m) * 255.0) as u8,
109 ((g + m) * 255.0) as u8,
110 ((b + m) * 255.0) as u8,
111 )
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_color_generator_default() {
121 let generator = ColorGenerator::default();
122 assert_eq!(generator.base_hue, 210.0);
123 assert_eq!(generator.saturation, 0.7);
124 assert_eq!(generator.lightness, 0.6);
125 }
126
127 #[test]
128 fn test_color_generator_with_methods() {
129 let generator = ColorGenerator::new()
130 .with_base_hue(180.0)
131 .with_saturation(0.5)
132 .with_lightness(0.7);
133
134 assert_eq!(generator.base_hue, 180.0);
135 assert_eq!(generator.saturation, 0.5);
136 assert_eq!(generator.lightness, 0.7);
137 }
138
139 #[test]
140 fn test_saturation_clamping() {
141 let generator = ColorGenerator::new().with_saturation(1.5);
142 assert_eq!(generator.saturation, 1.0);
143
144 let generator = ColorGenerator::new().with_saturation(-0.5);
145 assert_eq!(generator.saturation, 0.0);
146 }
147
148 #[test]
149 fn test_lightness_clamping() {
150 let generator = ColorGenerator::new().with_lightness(1.5);
151 assert_eq!(generator.lightness, 1.0);
152
153 let generator = ColorGenerator::new().with_lightness(-0.5);
154 assert_eq!(generator.lightness, 0.0);
155 }
156
157 #[test]
158 fn test_generate_color() {
159 let generator = ColorGenerator::default();
160
161 let color1 = generator.generate_color(42);
163 let color2 = generator.generate_color(42);
164 assert_eq!(color1, color2);
165
166 let color3 = generator.generate_color(100);
168 assert_ne!(color1, color3);
169 }
170
171 #[test]
172 fn test_generate_color_for() {
173 let generator = ColorGenerator::default();
174
175 let color1 = generator.generate_color_for(&"test");
177 let color2 = generator.generate_color_for(&"test");
178 assert_eq!(color1, color2);
179
180 let color3 = generator.generate_color_for(&"other");
182 assert_ne!(color1, color3);
183 }
184}