@delorenj/claude-notifications 1.2.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DO.md +5 -0
- package/FIXES-APPLIED.md +195 -0
- package/INTEGRATION.md +445 -0
- package/LAYOUT-INTEGRATION.md +191 -0
- package/QUICK-REFERENCE.md +195 -0
- package/README.md +145 -14
- package/TASK.md +15 -0
- package/ZELLIJ-NOTIFY.md +523 -0
- package/_bmad-output/implementation-artifacts/spec-install-multi-cli-hooks.md +241 -0
- package/bin/claude-notifications.js +424 -305
- package/bin/claude-notify.js +47 -1
- package/bin/zellij-notify.js +346 -0
- package/bun.lock +35 -0
- package/diagnose-zellij.sh +105 -0
- package/examples/settings-with-zellij.json +18 -0
- package/examples/settings-zellij-only.json +18 -0
- package/examples/zellij-notify-examples.sh +143 -0
- package/lib/adapters/_stub.js +35 -0
- package/lib/adapters/auggie.js +10 -0
- package/lib/adapters/claude-code.js +181 -0
- package/lib/adapters/codex.js +10 -0
- package/lib/adapters/copilot.js +10 -0
- package/lib/adapters/gemini.js +10 -0
- package/lib/adapters/index.js +240 -0
- package/lib/adapters/kimi.js +10 -0
- package/lib/adapters/opencode.js +14 -0
- package/lib/adapters/vibe.js +10 -0
- package/lib/config.js +121 -5
- package/lib/tui.js +115 -0
- package/lib/zellij.js +248 -0
- package/package.json +6 -4
- package/postinstall.js +28 -25
- package/preuninstall.js +18 -9
- package/test/adapters/claude-code.test.js +144 -0
- package/test/adapters/patches.test.js +81 -0
- package/test/adapters/registry.test.js +89 -0
- package/test/adapters/stubs.test.js +46 -0
- package/test/cli-json.test.js +79 -0
- package/test/helpers/fake-fs.js +59 -0
- package/test-integration.sh +113 -0
- package/test-notification-plugin.kdl +34 -0
- package/test-updated-layout.sh +75 -0
- package/test-zellij-cli.sh +72 -0
- package/test.sh +1 -1
- package/zellij-plugin/.cargo/config.toml +5 -0
- package/zellij-plugin/.github/workflows/ci.yml +97 -0
- package/zellij-plugin/Cargo.lock +3558 -0
- package/zellij-plugin/Cargo.toml +40 -0
- package/zellij-plugin/README.md +290 -0
- package/zellij-plugin/build.sh +179 -0
- package/zellij-plugin/configs/examples/accessibility.kdl +31 -0
- package/zellij-plugin/configs/examples/catppuccin.kdl +32 -0
- package/zellij-plugin/configs/examples/default.kdl +34 -0
- package/zellij-plugin/configs/examples/minimal.kdl +22 -0
- package/zellij-plugin/docs/CONFIGURATION.md +191 -0
- package/zellij-plugin/docs/INTEGRATION.md +333 -0
- package/zellij-plugin/src/animation.rs +451 -0
- package/zellij-plugin/src/colors.rs +407 -0
- package/zellij-plugin/src/config.rs +664 -0
- package/zellij-plugin/src/event_bridge.rs +339 -0
- package/zellij-plugin/src/main.rs +420 -0
- package/zellij-plugin/src/notification.rs +466 -0
- package/zellij-plugin/src/queue.rs +399 -0
- package/zellij-plugin/src/renderer.rs +477 -0
- package/zellij-plugin/src/state.rs +338 -0
- package/zellij-plugin/src/tests.rs +413 -0
- package/ruv-swarm-mcp.db +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
//! Color management module for Zellij Visual Notifications
|
|
2
|
+
//!
|
|
3
|
+
//! Handles terminal color capabilities, theme colors, and color interpolation for animations.
|
|
4
|
+
|
|
5
|
+
use crate::config::ThemeConfig;
|
|
6
|
+
use crate::notification::NotificationType;
|
|
7
|
+
|
|
8
|
+
/// Color manager for handling terminal colors
|
|
9
|
+
#[derive(Debug, Clone)]
|
|
10
|
+
pub struct ColorManager {
|
|
11
|
+
/// Current theme configuration
|
|
12
|
+
theme: ThemeConfig,
|
|
13
|
+
/// Detected color capability
|
|
14
|
+
color_capability: ColorCapability,
|
|
15
|
+
/// High contrast mode enabled
|
|
16
|
+
high_contrast: bool,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Default for ColorManager {
|
|
20
|
+
fn default() -> Self {
|
|
21
|
+
Self {
|
|
22
|
+
theme: ThemeConfig::default(),
|
|
23
|
+
color_capability: ColorCapability::TrueColor,
|
|
24
|
+
high_contrast: false,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
impl ColorManager {
|
|
30
|
+
/// Create a new color manager with the given theme
|
|
31
|
+
pub fn new(theme: &ThemeConfig) -> Self {
|
|
32
|
+
Self {
|
|
33
|
+
theme: theme.clone(),
|
|
34
|
+
color_capability: Self::detect_capability(),
|
|
35
|
+
high_contrast: false,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Detect terminal color capability
|
|
40
|
+
fn detect_capability() -> ColorCapability {
|
|
41
|
+
// In WASM environment, we can't directly check environment variables
|
|
42
|
+
// Default to TrueColor as Zellij supports it
|
|
43
|
+
ColorCapability::TrueColor
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Set high contrast mode
|
|
47
|
+
pub fn set_high_contrast(&mut self, enabled: bool) {
|
|
48
|
+
self.high_contrast = enabled;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// Get the notification color based on type
|
|
52
|
+
pub fn get_notification_color(&self, notification_type: &NotificationType) -> Option<String> {
|
|
53
|
+
let base_color = match notification_type {
|
|
54
|
+
NotificationType::Success => &self.theme.success_color,
|
|
55
|
+
NotificationType::Error => &self.theme.error_color,
|
|
56
|
+
NotificationType::Warning => &self.theme.warning_color,
|
|
57
|
+
NotificationType::Info => &self.theme.info_color,
|
|
58
|
+
NotificationType::Progress => &self.theme.highlight_color,
|
|
59
|
+
NotificationType::Attention => &self.theme.warning_color,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
Some(self.adjust_for_capability(base_color))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Get the background color
|
|
66
|
+
pub fn get_background_color(&self) -> String {
|
|
67
|
+
self.adjust_for_capability(&self.theme.background_color)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Get the foreground color
|
|
71
|
+
pub fn get_foreground_color(&self) -> String {
|
|
72
|
+
self.adjust_for_capability(&self.theme.foreground_color)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Get the dimmed color
|
|
76
|
+
pub fn get_dimmed_color(&self) -> String {
|
|
77
|
+
self.adjust_for_capability(&self.theme.dimmed_color)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Adjust color based on terminal capability and high contrast mode
|
|
81
|
+
fn adjust_for_capability(&self, hex_color: &str) -> String {
|
|
82
|
+
let color = Color::from_hex(hex_color);
|
|
83
|
+
|
|
84
|
+
if self.high_contrast {
|
|
85
|
+
// Increase contrast
|
|
86
|
+
let adjusted = color.increase_contrast();
|
|
87
|
+
return match self.color_capability {
|
|
88
|
+
ColorCapability::TrueColor => adjusted.to_hex(),
|
|
89
|
+
ColorCapability::Color256 => adjusted.to_ansi256().to_string(),
|
|
90
|
+
ColorCapability::Color16 => adjusted.to_ansi16().to_string(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
match self.color_capability {
|
|
95
|
+
ColorCapability::TrueColor => hex_color.to_string(),
|
|
96
|
+
ColorCapability::Color256 => color.to_ansi256().to_string(),
|
|
97
|
+
ColorCapability::Color16 => color.to_ansi16().to_string(),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// Interpolate between two colors based on a factor (0.0 - 1.0)
|
|
102
|
+
pub fn interpolate(&self, color1: &str, color2: &str, factor: f32) -> String {
|
|
103
|
+
let c1 = Color::from_hex(color1);
|
|
104
|
+
let c2 = Color::from_hex(color2);
|
|
105
|
+
let result = c1.interpolate(&c2, factor);
|
|
106
|
+
result.to_hex()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Apply brightness to a color
|
|
110
|
+
pub fn apply_brightness(&self, hex_color: &str, brightness: f32) -> String {
|
|
111
|
+
let color = Color::from_hex(hex_color);
|
|
112
|
+
let adjusted = color.apply_brightness(brightness);
|
|
113
|
+
adjusted.to_hex()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Get ANSI escape sequence for setting foreground color
|
|
117
|
+
pub fn fg_escape(&self, hex_color: &str) -> String {
|
|
118
|
+
let color = Color::from_hex(hex_color);
|
|
119
|
+
match self.color_capability {
|
|
120
|
+
ColorCapability::TrueColor => {
|
|
121
|
+
format!("\x1b[38;2;{};{};{}m", color.r, color.g, color.b)
|
|
122
|
+
}
|
|
123
|
+
ColorCapability::Color256 => {
|
|
124
|
+
format!("\x1b[38;5;{}m", color.to_ansi256())
|
|
125
|
+
}
|
|
126
|
+
ColorCapability::Color16 => {
|
|
127
|
+
format!("\x1b[{}m", color.to_ansi16())
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Get ANSI escape sequence for setting background color
|
|
133
|
+
pub fn bg_escape(&self, hex_color: &str) -> String {
|
|
134
|
+
let color = Color::from_hex(hex_color);
|
|
135
|
+
match self.color_capability {
|
|
136
|
+
ColorCapability::TrueColor => {
|
|
137
|
+
format!("\x1b[48;2;{};{};{}m", color.r, color.g, color.b)
|
|
138
|
+
}
|
|
139
|
+
ColorCapability::Color256 => {
|
|
140
|
+
format!("\x1b[48;5;{}m", color.to_ansi256())
|
|
141
|
+
}
|
|
142
|
+
ColorCapability::Color16 => {
|
|
143
|
+
format!("\x1b[{}m", color.to_ansi16() + 10)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Get ANSI reset escape sequence
|
|
149
|
+
pub fn reset_escape(&self) -> &'static str {
|
|
150
|
+
"\x1b[0m"
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Terminal color capability levels
|
|
155
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
156
|
+
pub enum ColorCapability {
|
|
157
|
+
/// True color (24-bit RGB)
|
|
158
|
+
TrueColor,
|
|
159
|
+
/// 256 color mode
|
|
160
|
+
Color256,
|
|
161
|
+
/// 16 color mode (basic ANSI)
|
|
162
|
+
Color16,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// RGB Color representation
|
|
166
|
+
#[derive(Debug, Clone, Copy, Default)]
|
|
167
|
+
pub struct Color {
|
|
168
|
+
pub r: u8,
|
|
169
|
+
pub g: u8,
|
|
170
|
+
pub b: u8,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
impl Color {
|
|
174
|
+
/// Create a new color from RGB values
|
|
175
|
+
pub fn new(r: u8, g: u8, b: u8) -> Self {
|
|
176
|
+
Self { r, g, b }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// Parse color from hex string (supports #RRGGBB and RRGGBB)
|
|
180
|
+
pub fn from_hex(hex: &str) -> Self {
|
|
181
|
+
let hex = hex.trim_start_matches('#');
|
|
182
|
+
if hex.len() != 6 {
|
|
183
|
+
return Self::default();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
|
187
|
+
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
|
|
188
|
+
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
|
|
189
|
+
|
|
190
|
+
Self { r, g, b }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/// Convert to hex string
|
|
194
|
+
pub fn to_hex(&self) -> String {
|
|
195
|
+
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// Convert to ANSI 256 color code
|
|
199
|
+
pub fn to_ansi256(&self) -> u8 {
|
|
200
|
+
// If it's a grayscale color
|
|
201
|
+
if self.r == self.g && self.g == self.b {
|
|
202
|
+
if self.r < 8 {
|
|
203
|
+
return 16;
|
|
204
|
+
}
|
|
205
|
+
if self.r > 248 {
|
|
206
|
+
return 231;
|
|
207
|
+
}
|
|
208
|
+
return ((self.r as f32 - 8.0) / 247.0 * 24.0) as u8 + 232;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Convert to 6x6x6 color cube
|
|
212
|
+
let r = (self.r as f32 / 255.0 * 5.0).round() as u8;
|
|
213
|
+
let g = (self.g as f32 / 255.0 * 5.0).round() as u8;
|
|
214
|
+
let b = (self.b as f32 / 255.0 * 5.0).round() as u8;
|
|
215
|
+
|
|
216
|
+
16 + 36 * r + 6 * g + b
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Convert to ANSI 16 color code
|
|
220
|
+
pub fn to_ansi16(&self) -> u8 {
|
|
221
|
+
let value = self.r.max(self.g).max(self.b);
|
|
222
|
+
|
|
223
|
+
// If very dark, use black
|
|
224
|
+
if value < 64 {
|
|
225
|
+
return 30;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let mut code = 30;
|
|
229
|
+
if self.r > 127 {
|
|
230
|
+
code += 1;
|
|
231
|
+
}
|
|
232
|
+
if self.g > 127 {
|
|
233
|
+
code += 2;
|
|
234
|
+
}
|
|
235
|
+
if self.b > 127 {
|
|
236
|
+
code += 4;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Use bright variants for light colors
|
|
240
|
+
if value > 192 {
|
|
241
|
+
code += 60;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
code
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Interpolate between two colors
|
|
248
|
+
pub fn interpolate(&self, other: &Color, factor: f32) -> Color {
|
|
249
|
+
let factor = factor.clamp(0.0, 1.0);
|
|
250
|
+
Color {
|
|
251
|
+
r: (self.r as f32 + (other.r as f32 - self.r as f32) * factor) as u8,
|
|
252
|
+
g: (self.g as f32 + (other.g as f32 - self.g as f32) * factor) as u8,
|
|
253
|
+
b: (self.b as f32 + (other.b as f32 - self.b as f32) * factor) as u8,
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Apply brightness multiplier (0.0 = black, 1.0 = original, >1.0 = brighter)
|
|
258
|
+
pub fn apply_brightness(&self, brightness: f32) -> Color {
|
|
259
|
+
Color {
|
|
260
|
+
r: (self.r as f32 * brightness).min(255.0) as u8,
|
|
261
|
+
g: (self.g as f32 * brightness).min(255.0) as u8,
|
|
262
|
+
b: (self.b as f32 * brightness).min(255.0) as u8,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Increase contrast (move towards white or black)
|
|
267
|
+
pub fn increase_contrast(&self) -> Color {
|
|
268
|
+
let luminance = 0.299 * self.r as f32 + 0.587 * self.g as f32 + 0.114 * self.b as f32;
|
|
269
|
+
|
|
270
|
+
if luminance > 127.0 {
|
|
271
|
+
// Make lighter
|
|
272
|
+
Color {
|
|
273
|
+
r: (self.r as f32 * 1.2).min(255.0) as u8,
|
|
274
|
+
g: (self.g as f32 * 1.2).min(255.0) as u8,
|
|
275
|
+
b: (self.b as f32 * 1.2).min(255.0) as u8,
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
// Make darker or more saturated
|
|
279
|
+
Color {
|
|
280
|
+
r: (self.r as f32 * 0.9) as u8,
|
|
281
|
+
g: (self.g as f32 * 0.9) as u8,
|
|
282
|
+
b: (self.b as f32 * 0.9) as u8,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/// Calculate luminance (0.0 - 1.0)
|
|
288
|
+
pub fn luminance(&self) -> f32 {
|
|
289
|
+
(0.299 * self.r as f32 + 0.587 * self.g as f32 + 0.114 * self.b as f32) / 255.0
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Check if color is considered "light"
|
|
293
|
+
pub fn is_light(&self) -> bool {
|
|
294
|
+
self.luminance() > 0.5
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/// Predefined colors for quick access
|
|
299
|
+
pub mod colors {
|
|
300
|
+
use super::Color;
|
|
301
|
+
|
|
302
|
+
pub const BLACK: Color = Color { r: 0, g: 0, b: 0 };
|
|
303
|
+
pub const WHITE: Color = Color { r: 255, g: 255, b: 255 };
|
|
304
|
+
pub const RED: Color = Color { r: 255, g: 0, b: 0 };
|
|
305
|
+
pub const GREEN: Color = Color { r: 0, g: 255, b: 0 };
|
|
306
|
+
pub const BLUE: Color = Color { r: 0, g: 0, b: 255 };
|
|
307
|
+
pub const YELLOW: Color = Color { r: 255, g: 255, b: 0 };
|
|
308
|
+
pub const CYAN: Color = Color { r: 0, g: 255, b: 255 };
|
|
309
|
+
pub const MAGENTA: Color = Color { r: 255, g: 0, b: 255 };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/// Generate a color gradient for animations
|
|
313
|
+
pub fn generate_gradient(start: &Color, end: &Color, steps: usize) -> Vec<Color> {
|
|
314
|
+
(0..steps)
|
|
315
|
+
.map(|i| {
|
|
316
|
+
let factor = i as f32 / (steps - 1) as f32;
|
|
317
|
+
start.interpolate(end, factor)
|
|
318
|
+
})
|
|
319
|
+
.collect()
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/// Generate a pulse gradient (start -> end -> start)
|
|
323
|
+
pub fn generate_pulse_gradient(base: &Color, bright: &Color, steps: usize) -> Vec<Color> {
|
|
324
|
+
let half_steps = steps / 2;
|
|
325
|
+
let mut gradient = Vec::with_capacity(steps);
|
|
326
|
+
|
|
327
|
+
// First half: base -> bright
|
|
328
|
+
for i in 0..half_steps {
|
|
329
|
+
let factor = i as f32 / half_steps as f32;
|
|
330
|
+
gradient.push(base.interpolate(bright, factor));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Second half: bright -> base
|
|
334
|
+
for i in 0..(steps - half_steps) {
|
|
335
|
+
let factor = i as f32 / (steps - half_steps) as f32;
|
|
336
|
+
gradient.push(bright.interpolate(base, factor));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
gradient
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
#[cfg(test)]
|
|
343
|
+
mod tests {
|
|
344
|
+
use super::*;
|
|
345
|
+
|
|
346
|
+
#[test]
|
|
347
|
+
fn test_color_from_hex() {
|
|
348
|
+
let color = Color::from_hex("#ff5500");
|
|
349
|
+
assert_eq!(color.r, 255);
|
|
350
|
+
assert_eq!(color.g, 85);
|
|
351
|
+
assert_eq!(color.b, 0);
|
|
352
|
+
|
|
353
|
+
let color2 = Color::from_hex("00ff00");
|
|
354
|
+
assert_eq!(color2.r, 0);
|
|
355
|
+
assert_eq!(color2.g, 255);
|
|
356
|
+
assert_eq!(color2.b, 0);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
#[test]
|
|
360
|
+
fn test_color_to_hex() {
|
|
361
|
+
let color = Color::new(255, 128, 64);
|
|
362
|
+
assert_eq!(color.to_hex(), "#ff8040");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn test_color_interpolation() {
|
|
367
|
+
let black = Color::new(0, 0, 0);
|
|
368
|
+
let white = Color::new(255, 255, 255);
|
|
369
|
+
|
|
370
|
+
let mid = black.interpolate(&white, 0.5);
|
|
371
|
+
assert!(mid.r > 120 && mid.r < 135);
|
|
372
|
+
assert!(mid.g > 120 && mid.g < 135);
|
|
373
|
+
assert!(mid.b > 120 && mid.b < 135);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn test_color_brightness() {
|
|
378
|
+
let color = Color::new(100, 100, 100);
|
|
379
|
+
let brighter = color.apply_brightness(1.5);
|
|
380
|
+
assert_eq!(brighter.r, 150);
|
|
381
|
+
|
|
382
|
+
let darker = color.apply_brightness(0.5);
|
|
383
|
+
assert_eq!(darker.r, 50);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
#[test]
|
|
387
|
+
fn test_ansi256_conversion() {
|
|
388
|
+
let red = Color::new(255, 0, 0);
|
|
389
|
+
let ansi = red.to_ansi256();
|
|
390
|
+
assert!(ansi >= 16 && ansi <= 231);
|
|
391
|
+
|
|
392
|
+
let gray = Color::new(128, 128, 128);
|
|
393
|
+
let ansi_gray = gray.to_ansi256();
|
|
394
|
+
assert!(ansi_gray >= 232 || (ansi_gray >= 16 && ansi_gray <= 231));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
#[test]
|
|
398
|
+
fn test_gradient_generation() {
|
|
399
|
+
let start = Color::new(0, 0, 0);
|
|
400
|
+
let end = Color::new(255, 255, 255);
|
|
401
|
+
let gradient = generate_gradient(&start, &end, 5);
|
|
402
|
+
|
|
403
|
+
assert_eq!(gradient.len(), 5);
|
|
404
|
+
assert_eq!(gradient[0].r, 0);
|
|
405
|
+
assert_eq!(gradient[4].r, 255);
|
|
406
|
+
}
|
|
407
|
+
}
|