@akiojin/gwt 6.30.3 → 9.0.1
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/.cargo/config.toml +2 -0
- package/.claude-plugin/marketplace.json +18 -0
- package/.coderabbit.yaml +8 -0
- package/.codex/skills/gwt-fix-issue/scripts/inspect_issue.py +833 -0
- package/.dockerignore +63 -0
- package/.gitattributes +27 -0
- package/.husky/commit-msg +2 -0
- package/.husky/pre-commit +9 -0
- package/.husky/pre-push +12 -0
- package/.markdownlint.json +18 -0
- package/.markdownlintignore +2 -0
- package/Dockerfile +58 -0
- package/README.ja.md +161 -484
- package/README.md +164 -444
- package/cliff.toml +56 -0
- package/clippy.toml +2 -0
- package/cmake/ci-disable-native.cmake +16 -0
- package/codecov.yml +16 -0
- package/commitlint.config.cjs +107 -0
- package/deny.toml +35 -0
- package/docker-compose.yml +59 -0
- package/messages/errors.toml +52 -0
- package/package.json +12 -22
- package/rustfmt.toml +8 -0
- package/scripts/check-e2e-coverage-threshold.mjs +238 -0
- package/scripts/entrypoint.sh +36 -25
- package/scripts/install-linux-deps.sh +46 -0
- package/scripts/postinstall.js +79 -227
- package/scripts/release_issue_refs.py +317 -0
- package/scripts/run-local-backend-tests-on-commit.sh +15 -0
- package/scripts/run-local-e2e-coverage-on-commit.sh +69 -0
- package/scripts/run-local-e2e-on-commit.sh +60 -0
- package/scripts/test-all.sh +13 -0
- package/scripts/test_release_issue_refs.py +257 -0
- package/scripts/validate-skill-frontmatter.sh +108 -0
- package/scripts/verify-ci-node-toolchain.sh +76 -0
- package/scripts/verify-husky-hooks.sh +6 -0
- package/scripts/voice-eval.sh +48 -0
- package/tests/voice_eval/README.md +53 -0
- package/tests/voice_eval/manifest.template.json +55 -0
- package/tests/voice_eval/samples/.gitkeep +1 -0
- package/tests/voice_eval/script-ja.txt +10 -0
- package/vendor/ratatui-core/src/backend/test.rs +1077 -0
- package/vendor/ratatui-core/src/backend.rs +405 -0
- package/vendor/ratatui-core/src/buffer/assert.rs +71 -0
- package/vendor/ratatui-core/src/buffer/buffer.rs +1388 -0
- package/vendor/ratatui-core/src/buffer/cell.rs +377 -0
- package/vendor/ratatui-core/src/buffer.rs +9 -0
- package/vendor/ratatui-core/src/layout/alignment.rs +89 -0
- package/vendor/ratatui-core/src/layout/constraint.rs +526 -0
- package/vendor/ratatui-core/src/layout/direction.rs +63 -0
- package/vendor/ratatui-core/src/layout/flex.rs +212 -0
- package/vendor/ratatui-core/src/layout/layout.rs +2838 -0
- package/vendor/ratatui-core/src/layout/margin.rs +79 -0
- package/vendor/ratatui-core/src/layout/offset.rs +66 -0
- package/vendor/ratatui-core/src/layout/position.rs +253 -0
- package/vendor/ratatui-core/src/layout/rect/iter.rs +356 -0
- package/vendor/ratatui-core/src/layout/rect/ops.rs +136 -0
- package/vendor/ratatui-core/src/layout/rect.rs +1114 -0
- package/vendor/ratatui-core/src/layout/size.rs +147 -0
- package/vendor/ratatui-core/src/layout.rs +333 -0
- package/vendor/ratatui-core/src/lib.rs +82 -0
- package/vendor/ratatui-core/src/style/anstyle.rs +348 -0
- package/vendor/ratatui-core/src/style/color.rs +788 -0
- package/vendor/ratatui-core/src/style/palette/material.rs +608 -0
- package/vendor/ratatui-core/src/style/palette/tailwind.rs +653 -0
- package/vendor/ratatui-core/src/style/palette.rs +6 -0
- package/vendor/ratatui-core/src/style/palette_conversion.rs +82 -0
- package/vendor/ratatui-core/src/style/stylize.rs +668 -0
- package/vendor/ratatui-core/src/style.rs +1069 -0
- package/vendor/ratatui-core/src/symbols/bar.rs +51 -0
- package/vendor/ratatui-core/src/symbols/block.rs +51 -0
- package/vendor/ratatui-core/src/symbols/border.rs +709 -0
- package/vendor/ratatui-core/src/symbols/braille.rs +21 -0
- package/vendor/ratatui-core/src/symbols/half_block.rs +3 -0
- package/vendor/ratatui-core/src/symbols/line.rs +259 -0
- package/vendor/ratatui-core/src/symbols/marker.rs +82 -0
- package/vendor/ratatui-core/src/symbols/merge.rs +748 -0
- package/vendor/ratatui-core/src/symbols/pixel.rs +30 -0
- package/vendor/ratatui-core/src/symbols/scrollbar.rs +46 -0
- package/vendor/ratatui-core/src/symbols/shade.rs +5 -0
- package/vendor/ratatui-core/src/symbols.rs +15 -0
- package/vendor/ratatui-core/src/terminal/frame.rs +192 -0
- package/vendor/ratatui-core/src/terminal/terminal.rs +926 -0
- package/vendor/ratatui-core/src/terminal/viewport.rs +58 -0
- package/vendor/ratatui-core/src/terminal.rs +40 -0
- package/vendor/ratatui-core/src/text/grapheme.rs +84 -0
- package/vendor/ratatui-core/src/text/line.rs +1678 -0
- package/vendor/ratatui-core/src/text/masked.rs +149 -0
- package/vendor/ratatui-core/src/text/span.rs +904 -0
- package/vendor/ratatui-core/src/text/text.rs +1434 -0
- package/vendor/ratatui-core/src/text.rs +64 -0
- package/vendor/ratatui-core/src/widgets/stateful_widget.rs +193 -0
- package/vendor/ratatui-core/src/widgets/widget.rs +174 -0
- package/vendor/ratatui-core/src/widgets.rs +9 -0
- package/bin/gwt.js +0 -131
- package/scripts/postinstall.test.js +0 -71
- package/scripts/release-download.js +0 -66
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
#![allow(clippy::unreadable_literal)]
|
|
2
|
+
|
|
3
|
+
use core::fmt;
|
|
4
|
+
use core::str::FromStr;
|
|
5
|
+
|
|
6
|
+
use crate::style::stylize::{ColorDebug, ColorDebugKind};
|
|
7
|
+
|
|
8
|
+
/// ANSI Color
|
|
9
|
+
///
|
|
10
|
+
/// All colors from the [ANSI color table] are supported (though some names are not exactly the
|
|
11
|
+
/// same).
|
|
12
|
+
///
|
|
13
|
+
/// | Color Name | Color | Foreground | Background |
|
|
14
|
+
/// |----------------|-------------------------|------------|------------|
|
|
15
|
+
/// | `black` | [`Color::Black`] | 30 | 40 |
|
|
16
|
+
/// | `red` | [`Color::Red`] | 31 | 41 |
|
|
17
|
+
/// | `green` | [`Color::Green`] | 32 | 42 |
|
|
18
|
+
/// | `yellow` | [`Color::Yellow`] | 33 | 43 |
|
|
19
|
+
/// | `blue` | [`Color::Blue`] | 34 | 44 |
|
|
20
|
+
/// | `magenta` | [`Color::Magenta`] | 35 | 45 |
|
|
21
|
+
/// | `cyan` | [`Color::Cyan`] | 36 | 46 |
|
|
22
|
+
/// | `gray`* | [`Color::Gray`] | 37 | 47 |
|
|
23
|
+
/// | `darkgray`* | [`Color::DarkGray`] | 90 | 100 |
|
|
24
|
+
/// | `lightred` | [`Color::LightRed`] | 91 | 101 |
|
|
25
|
+
/// | `lightgreen` | [`Color::LightGreen`] | 92 | 102 |
|
|
26
|
+
/// | `lightyellow` | [`Color::LightYellow`] | 93 | 103 |
|
|
27
|
+
/// | `lightblue` | [`Color::LightBlue`] | 94 | 104 |
|
|
28
|
+
/// | `lightmagenta` | [`Color::LightMagenta`] | 95 | 105 |
|
|
29
|
+
/// | `lightcyan` | [`Color::LightCyan`] | 96 | 106 |
|
|
30
|
+
/// | `white`* | [`Color::White`] | 97 | 107 |
|
|
31
|
+
///
|
|
32
|
+
/// - `gray` is sometimes called `white` - this is not supported as we use `white` for bright white
|
|
33
|
+
/// - `gray` is sometimes called `silver` - this is supported
|
|
34
|
+
/// - `darkgray` is sometimes called `light black` or `bright black` (both are supported)
|
|
35
|
+
/// - `white` is sometimes called `light white` or `bright white` (both are supported)
|
|
36
|
+
/// - we support `bright` and `light` prefixes for all colors
|
|
37
|
+
/// - we support `-` and `_` and ` ` as separators for all colors
|
|
38
|
+
/// - we support both `gray` and `grey` spellings
|
|
39
|
+
///
|
|
40
|
+
/// `From<Color> for Style` is implemented by creating a style with the foreground color set to the
|
|
41
|
+
/// given color. This allows you to use colors anywhere that accepts `Into<Style>`.
|
|
42
|
+
///
|
|
43
|
+
/// # Example
|
|
44
|
+
///
|
|
45
|
+
/// ```
|
|
46
|
+
/// use std::str::FromStr;
|
|
47
|
+
///
|
|
48
|
+
/// use ratatui_core::style::Color;
|
|
49
|
+
///
|
|
50
|
+
/// assert_eq!(Color::from_str("red"), Ok(Color::Red));
|
|
51
|
+
/// assert_eq!("red".parse(), Ok(Color::Red));
|
|
52
|
+
/// assert_eq!("lightred".parse(), Ok(Color::LightRed));
|
|
53
|
+
/// assert_eq!("light red".parse(), Ok(Color::LightRed));
|
|
54
|
+
/// assert_eq!("light-red".parse(), Ok(Color::LightRed));
|
|
55
|
+
/// assert_eq!("light_red".parse(), Ok(Color::LightRed));
|
|
56
|
+
/// assert_eq!("lightRed".parse(), Ok(Color::LightRed));
|
|
57
|
+
/// assert_eq!("bright red".parse(), Ok(Color::LightRed));
|
|
58
|
+
/// assert_eq!("bright-red".parse(), Ok(Color::LightRed));
|
|
59
|
+
/// assert_eq!("silver".parse(), Ok(Color::Gray));
|
|
60
|
+
/// assert_eq!("dark-grey".parse(), Ok(Color::DarkGray));
|
|
61
|
+
/// assert_eq!("dark gray".parse(), Ok(Color::DarkGray));
|
|
62
|
+
/// assert_eq!("light-black".parse(), Ok(Color::DarkGray));
|
|
63
|
+
/// assert_eq!("white".parse(), Ok(Color::White));
|
|
64
|
+
/// assert_eq!("bright white".parse(), Ok(Color::White));
|
|
65
|
+
/// ```
|
|
66
|
+
///
|
|
67
|
+
/// [ANSI color table]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
|
68
|
+
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
|
|
69
|
+
pub enum Color {
|
|
70
|
+
/// Resets the foreground or background color
|
|
71
|
+
#[default]
|
|
72
|
+
Reset,
|
|
73
|
+
/// ANSI Color: Black. Foreground: 30, Background: 40
|
|
74
|
+
Black,
|
|
75
|
+
/// ANSI Color: Red. Foreground: 31, Background: 41
|
|
76
|
+
Red,
|
|
77
|
+
/// ANSI Color: Green. Foreground: 32, Background: 42
|
|
78
|
+
Green,
|
|
79
|
+
/// ANSI Color: Yellow. Foreground: 33, Background: 43
|
|
80
|
+
Yellow,
|
|
81
|
+
/// ANSI Color: Blue. Foreground: 34, Background: 44
|
|
82
|
+
Blue,
|
|
83
|
+
/// ANSI Color: Magenta. Foreground: 35, Background: 45
|
|
84
|
+
Magenta,
|
|
85
|
+
/// ANSI Color: Cyan. Foreground: 36, Background: 46
|
|
86
|
+
Cyan,
|
|
87
|
+
/// ANSI Color: White. Foreground: 37, Background: 47
|
|
88
|
+
///
|
|
89
|
+
/// Note that this is sometimes called `silver` or `white` but we use `white` for bright white
|
|
90
|
+
Gray,
|
|
91
|
+
/// ANSI Color: Bright Black. Foreground: 90, Background: 100
|
|
92
|
+
///
|
|
93
|
+
/// Note that this is sometimes called `light black` or `bright black` but we use `dark gray`
|
|
94
|
+
DarkGray,
|
|
95
|
+
/// ANSI Color: Bright Red. Foreground: 91, Background: 101
|
|
96
|
+
LightRed,
|
|
97
|
+
/// ANSI Color: Bright Green. Foreground: 92, Background: 102
|
|
98
|
+
LightGreen,
|
|
99
|
+
/// ANSI Color: Bright Yellow. Foreground: 93, Background: 103
|
|
100
|
+
LightYellow,
|
|
101
|
+
/// ANSI Color: Bright Blue. Foreground: 94, Background: 104
|
|
102
|
+
LightBlue,
|
|
103
|
+
/// ANSI Color: Bright Magenta. Foreground: 95, Background: 105
|
|
104
|
+
LightMagenta,
|
|
105
|
+
/// ANSI Color: Bright Cyan. Foreground: 96, Background: 106
|
|
106
|
+
LightCyan,
|
|
107
|
+
/// ANSI Color: Bright White. Foreground: 97, Background: 107
|
|
108
|
+
/// Sometimes called `bright white` or `light white` in some terminals
|
|
109
|
+
White,
|
|
110
|
+
/// An RGB color.
|
|
111
|
+
///
|
|
112
|
+
/// Note that only terminals that support 24-bit true color will display this correctly.
|
|
113
|
+
/// Notably versions of Windows Terminal prior to Windows 10 and macOS Terminal.app do not
|
|
114
|
+
/// support this.
|
|
115
|
+
///
|
|
116
|
+
/// If the terminal does not support true color, code using the `TermwizBackend` will
|
|
117
|
+
/// fallback to the default text color. Crossterm and Termion do not have this capability and
|
|
118
|
+
/// the display will be unpredictable (e.g. Terminal.app may display glitched blinking text).
|
|
119
|
+
/// See <https://github.com/ratatui/ratatui/issues/475> for an example of this problem.
|
|
120
|
+
///
|
|
121
|
+
/// See also: <https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit>
|
|
122
|
+
Rgb(u8, u8, u8),
|
|
123
|
+
/// An 8-bit 256 color.
|
|
124
|
+
///
|
|
125
|
+
/// See also <https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit>
|
|
126
|
+
Indexed(u8),
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
impl Color {
|
|
130
|
+
/// Convert a u32 to a Color
|
|
131
|
+
///
|
|
132
|
+
/// The u32 should be in the format 0x00RRGGBB.
|
|
133
|
+
pub const fn from_u32(u: u32) -> Self {
|
|
134
|
+
let r = (u >> 16) as u8;
|
|
135
|
+
let g = (u >> 8) as u8;
|
|
136
|
+
let b = u as u8;
|
|
137
|
+
Self::Rgb(r, g, b)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#[cfg(feature = "serde")]
|
|
142
|
+
impl serde::Serialize for Color {
|
|
143
|
+
/// This utilises the [`fmt::Display`] implementation for serialization.
|
|
144
|
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
145
|
+
where
|
|
146
|
+
S: serde::Serializer,
|
|
147
|
+
{
|
|
148
|
+
use alloc::string::ToString;
|
|
149
|
+
|
|
150
|
+
serializer.serialize_str(&self.to_string())
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[cfg(feature = "serde")]
|
|
155
|
+
impl<'de> serde::Deserialize<'de> for Color {
|
|
156
|
+
/// This is used to deserialize a value into Color via serde.
|
|
157
|
+
///
|
|
158
|
+
/// This implementation uses the `FromStr` trait to deserialize strings, so named colours, RGB,
|
|
159
|
+
/// and indexed values are able to be deserialized. In addition, values that were produced by
|
|
160
|
+
/// the the older serialization implementation of Color are also able to be deserialized.
|
|
161
|
+
///
|
|
162
|
+
/// Prior to v0.26.0, Ratatui would be serialized using a map for indexed and RGB values, for
|
|
163
|
+
/// examples in json `{"Indexed": 10}` and `{"Rgb": [255, 0, 255]}` respectively. Now they are
|
|
164
|
+
/// serialized using the string representation of the index and the RGB hex value, for example
|
|
165
|
+
/// in json it would now be `"10"` and `"#FF00FF"` respectively.
|
|
166
|
+
///
|
|
167
|
+
/// See the [`Color`] documentation for more information on color names.
|
|
168
|
+
///
|
|
169
|
+
/// # Examples
|
|
170
|
+
///
|
|
171
|
+
/// ```
|
|
172
|
+
/// use std::str::FromStr;
|
|
173
|
+
///
|
|
174
|
+
/// use ratatui_core::style::Color;
|
|
175
|
+
///
|
|
176
|
+
/// #[derive(Debug, serde::Deserialize)]
|
|
177
|
+
/// struct Theme {
|
|
178
|
+
/// color: Color,
|
|
179
|
+
/// }
|
|
180
|
+
///
|
|
181
|
+
/// # fn get_theme() -> Result<(), serde_json::Error> {
|
|
182
|
+
/// let theme: Theme = serde_json::from_str(r#"{"color": "bright-white"}"#)?;
|
|
183
|
+
/// assert_eq!(theme.color, Color::White);
|
|
184
|
+
///
|
|
185
|
+
/// let theme: Theme = serde_json::from_str(r##"{"color": "#00FF00"}"##)?;
|
|
186
|
+
/// assert_eq!(theme.color, Color::Rgb(0, 255, 0));
|
|
187
|
+
///
|
|
188
|
+
/// let theme: Theme = serde_json::from_str(r#"{"color": "42"}"#)?;
|
|
189
|
+
/// assert_eq!(theme.color, Color::Indexed(42));
|
|
190
|
+
///
|
|
191
|
+
/// let err = serde_json::from_str::<Theme>(r#"{"color": "invalid"}"#).unwrap_err();
|
|
192
|
+
/// assert!(err.is_data());
|
|
193
|
+
/// assert_eq!(
|
|
194
|
+
/// err.to_string(),
|
|
195
|
+
/// "Failed to parse Colors at line 1 column 20"
|
|
196
|
+
/// );
|
|
197
|
+
///
|
|
198
|
+
/// // Deserializing from the previous serialization implementation
|
|
199
|
+
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Rgb":[255,0,255]}}"#)?;
|
|
200
|
+
/// assert_eq!(theme.color, Color::Rgb(255, 0, 255));
|
|
201
|
+
///
|
|
202
|
+
/// let theme: Theme = serde_json::from_str(r#"{"color": {"Indexed":10}}"#)?;
|
|
203
|
+
/// assert_eq!(theme.color, Color::Indexed(10));
|
|
204
|
+
/// # Ok(())
|
|
205
|
+
/// # }
|
|
206
|
+
/// ```
|
|
207
|
+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
208
|
+
where
|
|
209
|
+
D: serde::Deserializer<'de>,
|
|
210
|
+
{
|
|
211
|
+
use alloc::format;
|
|
212
|
+
use alloc::string::String;
|
|
213
|
+
|
|
214
|
+
/// Colors are currently serialized with the `Display` implementation, so
|
|
215
|
+
/// RGB values are serialized via hex, for example "#FFFFFF".
|
|
216
|
+
///
|
|
217
|
+
/// Previously they were serialized using serde derive, which encoded
|
|
218
|
+
/// RGB values as a map, for example { "rgb": [255, 255, 255] }.
|
|
219
|
+
///
|
|
220
|
+
/// The deserialization implementation utilises a `Helper` struct
|
|
221
|
+
/// to be able to support both formats for backwards compatibility.
|
|
222
|
+
#[derive(serde::Deserialize)]
|
|
223
|
+
enum ColorWrapper {
|
|
224
|
+
Rgb(u8, u8, u8),
|
|
225
|
+
Indexed(u8),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[derive(serde::Deserialize)]
|
|
229
|
+
#[serde(untagged)]
|
|
230
|
+
enum ColorFormat {
|
|
231
|
+
V2(String),
|
|
232
|
+
V1(ColorWrapper),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let multi_type = ColorFormat::deserialize(deserializer)
|
|
236
|
+
.map_err(|err| serde::de::Error::custom(format!("Failed to parse Colors: {err}")))?;
|
|
237
|
+
match multi_type {
|
|
238
|
+
ColorFormat::V2(s) => FromStr::from_str(&s).map_err(serde::de::Error::custom),
|
|
239
|
+
ColorFormat::V1(color_wrapper) => match color_wrapper {
|
|
240
|
+
ColorWrapper::Rgb(red, green, blue) => Ok(Self::Rgb(red, green, blue)),
|
|
241
|
+
ColorWrapper::Indexed(index) => Ok(Self::Indexed(index)),
|
|
242
|
+
},
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Error type indicating a failure to parse a color string.
|
|
248
|
+
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
|
249
|
+
pub struct ParseColorError;
|
|
250
|
+
|
|
251
|
+
impl fmt::Display for ParseColorError {
|
|
252
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
253
|
+
write!(f, "Failed to parse Colors")
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
impl core::error::Error for ParseColorError {}
|
|
258
|
+
|
|
259
|
+
/// Converts a string representation to a `Color` instance.
|
|
260
|
+
///
|
|
261
|
+
/// The `from_str` function attempts to parse the given string and convert it to the corresponding
|
|
262
|
+
/// `Color` variant. It supports named colors, RGB values, and indexed colors. If the string cannot
|
|
263
|
+
/// be parsed, a `ParseColorError` is returned.
|
|
264
|
+
///
|
|
265
|
+
/// See the [`Color`] documentation for more information on the supported color names.
|
|
266
|
+
///
|
|
267
|
+
/// # Examples
|
|
268
|
+
///
|
|
269
|
+
/// ```
|
|
270
|
+
/// use std::str::FromStr;
|
|
271
|
+
///
|
|
272
|
+
/// use ratatui_core::style::Color;
|
|
273
|
+
///
|
|
274
|
+
/// let color: Color = Color::from_str("blue").unwrap();
|
|
275
|
+
/// assert_eq!(color, Color::Blue);
|
|
276
|
+
///
|
|
277
|
+
/// let color: Color = Color::from_str("#FF0000").unwrap();
|
|
278
|
+
/// assert_eq!(color, Color::Rgb(255, 0, 0));
|
|
279
|
+
///
|
|
280
|
+
/// let color: Color = Color::from_str("10").unwrap();
|
|
281
|
+
/// assert_eq!(color, Color::Indexed(10));
|
|
282
|
+
///
|
|
283
|
+
/// let color: Result<Color, _> = Color::from_str("invalid_color");
|
|
284
|
+
/// assert!(color.is_err());
|
|
285
|
+
/// ```
|
|
286
|
+
impl FromStr for Color {
|
|
287
|
+
type Err = ParseColorError;
|
|
288
|
+
|
|
289
|
+
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
290
|
+
Ok(
|
|
291
|
+
// There is a mix of different color names and formats in the wild.
|
|
292
|
+
// This is an attempt to support as many as possible.
|
|
293
|
+
match s
|
|
294
|
+
.to_lowercase()
|
|
295
|
+
.replace([' ', '-', '_'], "")
|
|
296
|
+
.replace("bright", "light")
|
|
297
|
+
.replace("grey", "gray")
|
|
298
|
+
.replace("silver", "gray")
|
|
299
|
+
.replace("lightblack", "darkgray")
|
|
300
|
+
.replace("lightwhite", "white")
|
|
301
|
+
.replace("lightgray", "white")
|
|
302
|
+
.as_ref()
|
|
303
|
+
{
|
|
304
|
+
"reset" => Self::Reset,
|
|
305
|
+
"black" => Self::Black,
|
|
306
|
+
"red" => Self::Red,
|
|
307
|
+
"green" => Self::Green,
|
|
308
|
+
"yellow" => Self::Yellow,
|
|
309
|
+
"blue" => Self::Blue,
|
|
310
|
+
"magenta" => Self::Magenta,
|
|
311
|
+
"cyan" => Self::Cyan,
|
|
312
|
+
"gray" => Self::Gray,
|
|
313
|
+
"darkgray" => Self::DarkGray,
|
|
314
|
+
"lightred" => Self::LightRed,
|
|
315
|
+
"lightgreen" => Self::LightGreen,
|
|
316
|
+
"lightyellow" => Self::LightYellow,
|
|
317
|
+
"lightblue" => Self::LightBlue,
|
|
318
|
+
"lightmagenta" => Self::LightMagenta,
|
|
319
|
+
"lightcyan" => Self::LightCyan,
|
|
320
|
+
"white" => Self::White,
|
|
321
|
+
_ => {
|
|
322
|
+
if let Ok(index) = s.parse::<u8>() {
|
|
323
|
+
Self::Indexed(index)
|
|
324
|
+
} else if let Some((r, g, b)) = parse_hex_color(s) {
|
|
325
|
+
Self::Rgb(r, g, b)
|
|
326
|
+
} else {
|
|
327
|
+
return Err(ParseColorError);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
fn parse_hex_color(input: &str) -> Option<(u8, u8, u8)> {
|
|
336
|
+
if !input.starts_with('#') || input.len() != 7 {
|
|
337
|
+
return None;
|
|
338
|
+
}
|
|
339
|
+
let r = u8::from_str_radix(input.get(1..3)?, 16).ok()?;
|
|
340
|
+
let g = u8::from_str_radix(input.get(3..5)?, 16).ok()?;
|
|
341
|
+
let b = u8::from_str_radix(input.get(5..7)?, 16).ok()?;
|
|
342
|
+
Some((r, g, b))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
impl fmt::Display for Color {
|
|
346
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
347
|
+
match self {
|
|
348
|
+
Self::Reset => write!(f, "Reset"),
|
|
349
|
+
Self::Black => write!(f, "Black"),
|
|
350
|
+
Self::Red => write!(f, "Red"),
|
|
351
|
+
Self::Green => write!(f, "Green"),
|
|
352
|
+
Self::Yellow => write!(f, "Yellow"),
|
|
353
|
+
Self::Blue => write!(f, "Blue"),
|
|
354
|
+
Self::Magenta => write!(f, "Magenta"),
|
|
355
|
+
Self::Cyan => write!(f, "Cyan"),
|
|
356
|
+
Self::Gray => write!(f, "Gray"),
|
|
357
|
+
Self::DarkGray => write!(f, "DarkGray"),
|
|
358
|
+
Self::LightRed => write!(f, "LightRed"),
|
|
359
|
+
Self::LightGreen => write!(f, "LightGreen"),
|
|
360
|
+
Self::LightYellow => write!(f, "LightYellow"),
|
|
361
|
+
Self::LightBlue => write!(f, "LightBlue"),
|
|
362
|
+
Self::LightMagenta => write!(f, "LightMagenta"),
|
|
363
|
+
Self::LightCyan => write!(f, "LightCyan"),
|
|
364
|
+
Self::White => write!(f, "White"),
|
|
365
|
+
Self::Rgb(r, g, b) => write!(f, "#{r:02X}{g:02X}{b:02X}"),
|
|
366
|
+
Self::Indexed(i) => write!(f, "{i}"),
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
impl Color {
|
|
372
|
+
pub(crate) const fn stylize_debug(self, kind: ColorDebugKind) -> ColorDebug {
|
|
373
|
+
ColorDebug { kind, color: self }
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// Converts a HSL representation to a `Color::Rgb` instance.
|
|
377
|
+
///
|
|
378
|
+
/// The `from_hsl` function converts the Hue, Saturation and Lightness values to a corresponding
|
|
379
|
+
/// `Color` RGB equivalent.
|
|
380
|
+
///
|
|
381
|
+
/// Hue values should be in the range [-180..180]. Values outside this range are normalized by
|
|
382
|
+
/// wrapping.
|
|
383
|
+
///
|
|
384
|
+
/// Saturation and L values should be in the range [0.0..1.0]. Values outside this range are
|
|
385
|
+
/// clamped.
|
|
386
|
+
///
|
|
387
|
+
/// Clamping to valid ranges happens before conversion to RGB.
|
|
388
|
+
///
|
|
389
|
+
/// # Examples
|
|
390
|
+
///
|
|
391
|
+
/// ```
|
|
392
|
+
/// use palette::Hsl;
|
|
393
|
+
/// use ratatui_core::style::Color;
|
|
394
|
+
///
|
|
395
|
+
/// // Minimum Lightness is black
|
|
396
|
+
/// let color: Color = Color::from_hsl(Hsl::new(0.0, 0.0, 0.0));
|
|
397
|
+
/// assert_eq!(color, Color::Rgb(0, 0, 0));
|
|
398
|
+
///
|
|
399
|
+
/// // Maximum Lightness is white
|
|
400
|
+
/// let color: Color = Color::from_hsl(Hsl::new(0.0, 0.0, 1.0));
|
|
401
|
+
/// assert_eq!(color, Color::Rgb(255, 255, 255));
|
|
402
|
+
///
|
|
403
|
+
/// // Minimum Saturation is fully desaturated red = gray
|
|
404
|
+
/// let color: Color = Color::from_hsl(Hsl::new(0.0, 0.0, 0.5));
|
|
405
|
+
/// assert_eq!(color, Color::Rgb(128, 128, 128));
|
|
406
|
+
///
|
|
407
|
+
/// // Bright red
|
|
408
|
+
/// let color: Color = Color::from_hsl(Hsl::new(0.0, 1.0, 0.5));
|
|
409
|
+
/// assert_eq!(color, Color::Rgb(255, 0, 0));
|
|
410
|
+
///
|
|
411
|
+
/// // Bright blue
|
|
412
|
+
/// let color: Color = Color::from_hsl(Hsl::new(-120.0, 1.0, 0.5));
|
|
413
|
+
/// assert_eq!(color, Color::Rgb(0, 0, 255));
|
|
414
|
+
/// ```
|
|
415
|
+
#[cfg(feature = "palette")]
|
|
416
|
+
pub fn from_hsl(hsl: palette::Hsl) -> Self {
|
|
417
|
+
use palette::{Clamp, FromColor, Srgb};
|
|
418
|
+
let hsl = hsl.clamp();
|
|
419
|
+
let Srgb {
|
|
420
|
+
red,
|
|
421
|
+
green,
|
|
422
|
+
blue,
|
|
423
|
+
standard: _,
|
|
424
|
+
}: Srgb<u8> = Srgb::from_color(hsl).into();
|
|
425
|
+
|
|
426
|
+
Self::Rgb(red, green, blue)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/// Converts a `HSLuv` representation to a `Color::Rgb` instance.
|
|
430
|
+
///
|
|
431
|
+
/// The `from_hsluv` function converts the Hue, Saturation and Lightness values to a
|
|
432
|
+
/// corresponding `Color` RGB equivalent.
|
|
433
|
+
///
|
|
434
|
+
/// Hue values should be in the range [-180.0..180.0]. Values outside this range are normalized
|
|
435
|
+
/// by wrapping.
|
|
436
|
+
///
|
|
437
|
+
/// Saturation and L values should be in the range [0.0..100.0]. Values outside this range are
|
|
438
|
+
/// clamped.
|
|
439
|
+
///
|
|
440
|
+
/// Clamping to valid ranges happens before conversion to RGB.
|
|
441
|
+
///
|
|
442
|
+
/// # Examples
|
|
443
|
+
///
|
|
444
|
+
/// ```
|
|
445
|
+
/// use palette::Hsluv;
|
|
446
|
+
/// use ratatui_core::style::Color;
|
|
447
|
+
///
|
|
448
|
+
/// // Minimum Lightness is black
|
|
449
|
+
/// let color: Color = Color::from_hsluv(Hsluv::new(0.0, 100.0, 0.0));
|
|
450
|
+
/// assert_eq!(color, Color::Rgb(0, 0, 0));
|
|
451
|
+
///
|
|
452
|
+
/// // Maximum Lightness is white
|
|
453
|
+
/// let color: Color = Color::from_hsluv(Hsluv::new(0.0, 0.0, 100.0));
|
|
454
|
+
/// assert_eq!(color, Color::Rgb(255, 255, 255));
|
|
455
|
+
///
|
|
456
|
+
/// // Minimum Saturation is fully desaturated red = gray
|
|
457
|
+
/// let color = Color::from_hsluv(Hsluv::new(0.0, 0.0, 50.0));
|
|
458
|
+
/// assert_eq!(color, Color::Rgb(119, 119, 119));
|
|
459
|
+
///
|
|
460
|
+
/// // Bright Red
|
|
461
|
+
/// let color = Color::from_hsluv(Hsluv::new(12.18, 100.0, 53.2));
|
|
462
|
+
/// assert_eq!(color, Color::Rgb(255, 0, 0));
|
|
463
|
+
///
|
|
464
|
+
/// // Bright Blue
|
|
465
|
+
/// let color = Color::from_hsluv(Hsluv::new(-94.13, 100.0, 32.3));
|
|
466
|
+
/// assert_eq!(color, Color::Rgb(0, 0, 255));
|
|
467
|
+
/// ```
|
|
468
|
+
#[cfg(feature = "palette")]
|
|
469
|
+
pub fn from_hsluv(hsluv: palette::Hsluv) -> Self {
|
|
470
|
+
use palette::{Clamp, FromColor, Srgb};
|
|
471
|
+
let hsluv = hsluv.clamp();
|
|
472
|
+
let Srgb {
|
|
473
|
+
red,
|
|
474
|
+
green,
|
|
475
|
+
blue,
|
|
476
|
+
standard: _,
|
|
477
|
+
}: Srgb<u8> = Srgb::from_color(hsluv).into();
|
|
478
|
+
|
|
479
|
+
Self::Rgb(red, green, blue)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
impl From<[u8; 3]> for Color {
|
|
484
|
+
/// Converts an array of 3 u8 values to a `Color::Rgb` instance.
|
|
485
|
+
fn from([r, g, b]: [u8; 3]) -> Self {
|
|
486
|
+
Self::Rgb(r, g, b)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
impl From<(u8, u8, u8)> for Color {
|
|
491
|
+
/// Converts a tuple of 3 u8 values to a `Color::Rgb` instance.
|
|
492
|
+
fn from((r, g, b): (u8, u8, u8)) -> Self {
|
|
493
|
+
Self::Rgb(r, g, b)
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
impl From<[u8; 4]> for Color {
|
|
498
|
+
/// Converts an array of 4 u8 values to a `Color::Rgb` instance (ignoring the alpha value).
|
|
499
|
+
fn from([r, g, b, _]: [u8; 4]) -> Self {
|
|
500
|
+
Self::Rgb(r, g, b)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
impl From<(u8, u8, u8, u8)> for Color {
|
|
505
|
+
/// Converts a tuple of 4 u8 values to a `Color::Rgb` instance (ignoring the alpha value).
|
|
506
|
+
fn from((r, g, b, _): (u8, u8, u8, u8)) -> Self {
|
|
507
|
+
Self::Rgb(r, g, b)
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
#[cfg(test)]
|
|
512
|
+
mod tests {
|
|
513
|
+
use alloc::boxed::Box;
|
|
514
|
+
use alloc::format;
|
|
515
|
+
use core::error::Error;
|
|
516
|
+
|
|
517
|
+
#[cfg(feature = "palette")]
|
|
518
|
+
use palette::{Hsl, Hsluv};
|
|
519
|
+
#[cfg(feature = "palette")]
|
|
520
|
+
use rstest::rstest;
|
|
521
|
+
#[cfg(feature = "serde")]
|
|
522
|
+
use serde::de::{Deserialize, IntoDeserializer};
|
|
523
|
+
|
|
524
|
+
use super::*;
|
|
525
|
+
|
|
526
|
+
#[cfg(feature = "palette")]
|
|
527
|
+
#[rstest]
|
|
528
|
+
#[case::black(Hsl::new(0.0, 0.0, 0.0), Color::Rgb(0, 0, 0))]
|
|
529
|
+
#[case::white(Hsl::new(0.0, 0.0, 1.0), Color::Rgb(255, 255, 255))]
|
|
530
|
+
#[case::valid(Hsl::new(120.0, 0.5, 0.75), Color::Rgb(159, 223, 159))]
|
|
531
|
+
#[case::min_hue(Hsl::new(-180.0, 0.5, 0.75), Color::Rgb(159, 223, 223))]
|
|
532
|
+
#[case::max_hue(Hsl::new(180.0, 0.5, 0.75), Color::Rgb(159, 223, 223))]
|
|
533
|
+
#[case::min_saturation(Hsl::new(0.0, 0.0, 0.5), Color::Rgb(128, 128, 128))]
|
|
534
|
+
#[case::max_saturation(Hsl::new(0.0, 1.0, 0.5), Color::Rgb(255, 0, 0))]
|
|
535
|
+
#[case::min_lightness(Hsl::new(0.0, 0.5, 0.0), Color::Rgb(0, 0, 0))]
|
|
536
|
+
#[case::max_lightness(Hsl::new(0.0, 0.5, 1.0), Color::Rgb(255, 255, 255))]
|
|
537
|
+
#[case::under_hue_wraps(Hsl::new(-240.0, 0.5, 0.75), Color::Rgb(159, 223, 159))]
|
|
538
|
+
#[case::over_hue_wraps(Hsl::new(480.0, 0.5, 0.75), Color::Rgb(159, 223, 159))]
|
|
539
|
+
#[case::under_saturation_clamps(Hsl::new(0.0, -0.5, 0.75), Color::Rgb(191, 191, 191))]
|
|
540
|
+
#[case::over_saturation_clamps(Hsl::new(0.0, 1.2, 0.75), Color::Rgb(255, 128, 128))]
|
|
541
|
+
#[case::under_lightness_clamps(Hsl::new(0.0, 0.5, -0.20), Color::Rgb(0, 0, 0))]
|
|
542
|
+
#[case::over_lightness_clamps(Hsl::new(0.0, 0.5, 1.5), Color::Rgb(255, 255, 255))]
|
|
543
|
+
#[case::under_saturation_lightness_clamps(Hsl::new(0.0, -0.5, -0.20), Color::Rgb(0, 0, 0))]
|
|
544
|
+
#[case::over_saturation_lightness_clamps(Hsl::new(0.0, 1.2, 1.5), Color::Rgb(255, 255, 255))]
|
|
545
|
+
fn test_hsl_to_rgb(#[case] hsl: palette::Hsl, #[case] expected: Color) {
|
|
546
|
+
assert_eq!(Color::from_hsl(hsl), expected);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
#[cfg(feature = "palette")]
|
|
550
|
+
#[rstest]
|
|
551
|
+
#[case::black(Hsluv::new(0.0, 0.0, 0.0), Color::Rgb(0, 0, 0))]
|
|
552
|
+
#[case::white(Hsluv::new(0.0, 0.0, 100.0), Color::Rgb(255, 255, 255))]
|
|
553
|
+
#[case::valid(Hsluv::new(120.0, 50.0, 75.0), Color::Rgb(147, 198, 129))]
|
|
554
|
+
#[case::min_hue(Hsluv::new(-180.0, 50.0, 75.0), Color::Rgb(135,196, 188))]
|
|
555
|
+
#[case::max_hue(Hsluv::new(180.0, 50.0, 75.0), Color::Rgb(135, 196, 188))]
|
|
556
|
+
#[case::min_saturation(Hsluv::new(0.0, 0.0, 75.0), Color::Rgb(185, 185, 185))]
|
|
557
|
+
#[case::max_saturation(Hsluv::new(0.0, 100.0, 75.0), Color::Rgb(255, 156, 177))]
|
|
558
|
+
#[case::min_lightness(Hsluv::new(0.0, 50.0, 0.0), Color::Rgb(0, 0, 0))]
|
|
559
|
+
#[case::max_lightness(Hsluv::new(0.0, 50.0, 100.0), Color::Rgb(255, 255, 255))]
|
|
560
|
+
#[case::under_hue_wraps(Hsluv::new(-240.0, 50.0, 75.0), Color::Rgb(147, 198, 129))]
|
|
561
|
+
#[case::over_hue_wraps(Hsluv::new(480.0, 50.0, 75.0), Color::Rgb(147, 198, 129))]
|
|
562
|
+
#[case::under_saturation_clamps(Hsluv::new(0.0, -50.0, 75.0), Color::Rgb(185, 185, 185))]
|
|
563
|
+
#[case::over_saturation_clamps(Hsluv::new(0.0, 150.0, 75.0), Color::Rgb(255, 156, 177))]
|
|
564
|
+
#[case::under_lightness_clamps(Hsluv::new(0.0, 50.0, -20.0), Color::Rgb(0, 0, 0))]
|
|
565
|
+
#[case::over_lightness_clamps(Hsluv::new(0.0, 50.0, 150.0), Color::Rgb(255, 255, 255))]
|
|
566
|
+
#[case::under_saturation_lightness_clamps(Hsluv::new(0.0, -50.0, -20.0), Color::Rgb(0, 0, 0))]
|
|
567
|
+
#[case::over_saturation_lightness_clamps(
|
|
568
|
+
Hsluv::new(0.0, 150.0, 150.0),
|
|
569
|
+
Color::Rgb(255, 255, 255)
|
|
570
|
+
)]
|
|
571
|
+
fn test_hsluv_to_rgb(#[case] hsluv: palette::Hsluv, #[case] expected: Color) {
|
|
572
|
+
assert_eq!(Color::from_hsluv(hsluv), expected);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
#[test]
|
|
576
|
+
fn from_u32() {
|
|
577
|
+
assert_eq!(Color::from_u32(0x000000), Color::Rgb(0, 0, 0));
|
|
578
|
+
assert_eq!(Color::from_u32(0xFF0000), Color::Rgb(255, 0, 0));
|
|
579
|
+
assert_eq!(Color::from_u32(0x00FF00), Color::Rgb(0, 255, 0));
|
|
580
|
+
assert_eq!(Color::from_u32(0x0000FF), Color::Rgb(0, 0, 255));
|
|
581
|
+
assert_eq!(Color::from_u32(0xFFFFFF), Color::Rgb(255, 255, 255));
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
#[test]
|
|
585
|
+
fn from_rgb_color() {
|
|
586
|
+
let color: Color = Color::from_str("#FF0000").unwrap();
|
|
587
|
+
assert_eq!(color, Color::Rgb(255, 0, 0));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
#[test]
|
|
591
|
+
fn from_indexed_color() {
|
|
592
|
+
let color: Color = Color::from_str("10").unwrap();
|
|
593
|
+
assert_eq!(color, Color::Indexed(10));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
#[test]
|
|
597
|
+
fn from_ansi_color() -> Result<(), Box<dyn Error>> {
|
|
598
|
+
assert_eq!(Color::from_str("reset")?, Color::Reset);
|
|
599
|
+
assert_eq!(Color::from_str("black")?, Color::Black);
|
|
600
|
+
assert_eq!(Color::from_str("red")?, Color::Red);
|
|
601
|
+
assert_eq!(Color::from_str("green")?, Color::Green);
|
|
602
|
+
assert_eq!(Color::from_str("yellow")?, Color::Yellow);
|
|
603
|
+
assert_eq!(Color::from_str("blue")?, Color::Blue);
|
|
604
|
+
assert_eq!(Color::from_str("magenta")?, Color::Magenta);
|
|
605
|
+
assert_eq!(Color::from_str("cyan")?, Color::Cyan);
|
|
606
|
+
assert_eq!(Color::from_str("gray")?, Color::Gray);
|
|
607
|
+
assert_eq!(Color::from_str("darkgray")?, Color::DarkGray);
|
|
608
|
+
assert_eq!(Color::from_str("lightred")?, Color::LightRed);
|
|
609
|
+
assert_eq!(Color::from_str("lightgreen")?, Color::LightGreen);
|
|
610
|
+
assert_eq!(Color::from_str("lightyellow")?, Color::LightYellow);
|
|
611
|
+
assert_eq!(Color::from_str("lightblue")?, Color::LightBlue);
|
|
612
|
+
assert_eq!(Color::from_str("lightmagenta")?, Color::LightMagenta);
|
|
613
|
+
assert_eq!(Color::from_str("lightcyan")?, Color::LightCyan);
|
|
614
|
+
assert_eq!(Color::from_str("white")?, Color::White);
|
|
615
|
+
|
|
616
|
+
// aliases
|
|
617
|
+
assert_eq!(Color::from_str("lightblack")?, Color::DarkGray);
|
|
618
|
+
assert_eq!(Color::from_str("lightwhite")?, Color::White);
|
|
619
|
+
assert_eq!(Color::from_str("lightgray")?, Color::White);
|
|
620
|
+
|
|
621
|
+
// silver = grey = gray
|
|
622
|
+
assert_eq!(Color::from_str("grey")?, Color::Gray);
|
|
623
|
+
assert_eq!(Color::from_str("silver")?, Color::Gray);
|
|
624
|
+
|
|
625
|
+
// spaces are ignored
|
|
626
|
+
assert_eq!(Color::from_str("light black")?, Color::DarkGray);
|
|
627
|
+
assert_eq!(Color::from_str("light white")?, Color::White);
|
|
628
|
+
assert_eq!(Color::from_str("light gray")?, Color::White);
|
|
629
|
+
|
|
630
|
+
// dashes are ignored
|
|
631
|
+
assert_eq!(Color::from_str("light-black")?, Color::DarkGray);
|
|
632
|
+
assert_eq!(Color::from_str("light-white")?, Color::White);
|
|
633
|
+
assert_eq!(Color::from_str("light-gray")?, Color::White);
|
|
634
|
+
|
|
635
|
+
// underscores are ignored
|
|
636
|
+
assert_eq!(Color::from_str("light_black")?, Color::DarkGray);
|
|
637
|
+
assert_eq!(Color::from_str("light_white")?, Color::White);
|
|
638
|
+
assert_eq!(Color::from_str("light_gray")?, Color::White);
|
|
639
|
+
|
|
640
|
+
// bright = light
|
|
641
|
+
assert_eq!(Color::from_str("bright-black")?, Color::DarkGray);
|
|
642
|
+
assert_eq!(Color::from_str("bright-white")?, Color::White);
|
|
643
|
+
|
|
644
|
+
// bright = light
|
|
645
|
+
assert_eq!(Color::from_str("brightblack")?, Color::DarkGray);
|
|
646
|
+
assert_eq!(Color::from_str("brightwhite")?, Color::White);
|
|
647
|
+
|
|
648
|
+
Ok(())
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#[test]
|
|
652
|
+
fn from_invalid_colors() {
|
|
653
|
+
let bad_colors = [
|
|
654
|
+
"invalid_color", // not a color string
|
|
655
|
+
"abcdef0", // 7 chars is not a color
|
|
656
|
+
" bcdefa", // doesn't start with a '#'
|
|
657
|
+
"#abcdef00", // too many chars
|
|
658
|
+
"#1🦀2", // len 7 but on char boundaries shouldn't panic
|
|
659
|
+
"resets", // typo
|
|
660
|
+
"lightblackk", // typo
|
|
661
|
+
];
|
|
662
|
+
|
|
663
|
+
for bad_color in bad_colors {
|
|
664
|
+
assert!(
|
|
665
|
+
Color::from_str(bad_color).is_err(),
|
|
666
|
+
"bad color: '{bad_color}'"
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
#[test]
|
|
672
|
+
fn display() {
|
|
673
|
+
assert_eq!(format!("{}", Color::Black), "Black");
|
|
674
|
+
assert_eq!(format!("{}", Color::Red), "Red");
|
|
675
|
+
assert_eq!(format!("{}", Color::Green), "Green");
|
|
676
|
+
assert_eq!(format!("{}", Color::Yellow), "Yellow");
|
|
677
|
+
assert_eq!(format!("{}", Color::Blue), "Blue");
|
|
678
|
+
assert_eq!(format!("{}", Color::Magenta), "Magenta");
|
|
679
|
+
assert_eq!(format!("{}", Color::Cyan), "Cyan");
|
|
680
|
+
assert_eq!(format!("{}", Color::Gray), "Gray");
|
|
681
|
+
assert_eq!(format!("{}", Color::DarkGray), "DarkGray");
|
|
682
|
+
assert_eq!(format!("{}", Color::LightRed), "LightRed");
|
|
683
|
+
assert_eq!(format!("{}", Color::LightGreen), "LightGreen");
|
|
684
|
+
assert_eq!(format!("{}", Color::LightYellow), "LightYellow");
|
|
685
|
+
assert_eq!(format!("{}", Color::LightBlue), "LightBlue");
|
|
686
|
+
assert_eq!(format!("{}", Color::LightMagenta), "LightMagenta");
|
|
687
|
+
assert_eq!(format!("{}", Color::LightCyan), "LightCyan");
|
|
688
|
+
assert_eq!(format!("{}", Color::White), "White");
|
|
689
|
+
assert_eq!(format!("{}", Color::Indexed(10)), "10");
|
|
690
|
+
assert_eq!(format!("{}", Color::Rgb(255, 0, 0)), "#FF0000");
|
|
691
|
+
assert_eq!(format!("{}", Color::Reset), "Reset");
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
#[cfg(feature = "serde")]
|
|
695
|
+
#[test]
|
|
696
|
+
fn deserialize() -> Result<(), serde::de::value::Error> {
|
|
697
|
+
assert_eq!(
|
|
698
|
+
Color::Black,
|
|
699
|
+
Color::deserialize("Black".into_deserializer())?
|
|
700
|
+
);
|
|
701
|
+
assert_eq!(
|
|
702
|
+
Color::Magenta,
|
|
703
|
+
Color::deserialize("magenta".into_deserializer())?
|
|
704
|
+
);
|
|
705
|
+
assert_eq!(
|
|
706
|
+
Color::LightGreen,
|
|
707
|
+
Color::deserialize("LightGreen".into_deserializer())?
|
|
708
|
+
);
|
|
709
|
+
assert_eq!(
|
|
710
|
+
Color::White,
|
|
711
|
+
Color::deserialize("bright-white".into_deserializer())?
|
|
712
|
+
);
|
|
713
|
+
assert_eq!(
|
|
714
|
+
Color::Indexed(42),
|
|
715
|
+
Color::deserialize("42".into_deserializer())?
|
|
716
|
+
);
|
|
717
|
+
assert_eq!(
|
|
718
|
+
Color::Rgb(0, 255, 0),
|
|
719
|
+
Color::deserialize("#00ff00".into_deserializer())?
|
|
720
|
+
);
|
|
721
|
+
Ok(())
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
#[cfg(feature = "serde")]
|
|
725
|
+
#[test]
|
|
726
|
+
fn deserialize_error() {
|
|
727
|
+
let color: Result<_, serde::de::value::Error> =
|
|
728
|
+
Color::deserialize("invalid".into_deserializer());
|
|
729
|
+
assert!(color.is_err());
|
|
730
|
+
|
|
731
|
+
let color: Result<_, serde::de::value::Error> =
|
|
732
|
+
Color::deserialize("#00000000".into_deserializer());
|
|
733
|
+
assert!(color.is_err());
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
#[cfg(feature = "serde")]
|
|
737
|
+
#[test]
|
|
738
|
+
fn serialize_then_deserialize() -> Result<(), serde_json::Error> {
|
|
739
|
+
let json_rgb = serde_json::to_string(&Color::Rgb(255, 0, 255))?;
|
|
740
|
+
assert_eq!(json_rgb, r##""#FF00FF""##);
|
|
741
|
+
assert_eq!(
|
|
742
|
+
serde_json::from_str::<Color>(&json_rgb)?,
|
|
743
|
+
Color::Rgb(255, 0, 255)
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
let json_white = serde_json::to_string(&Color::White)?;
|
|
747
|
+
assert_eq!(json_white, r#""White""#);
|
|
748
|
+
|
|
749
|
+
let json_indexed = serde_json::to_string(&Color::Indexed(10))?;
|
|
750
|
+
assert_eq!(json_indexed, r#""10""#);
|
|
751
|
+
assert_eq!(
|
|
752
|
+
serde_json::from_str::<Color>(&json_indexed)?,
|
|
753
|
+
Color::Indexed(10)
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
Ok(())
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
#[cfg(feature = "serde")]
|
|
760
|
+
#[test]
|
|
761
|
+
fn deserialize_with_previous_format() -> Result<(), serde_json::Error> {
|
|
762
|
+
assert_eq!(Color::White, serde_json::from_str::<Color>("\"White\"")?);
|
|
763
|
+
assert_eq!(
|
|
764
|
+
Color::Rgb(255, 0, 255),
|
|
765
|
+
serde_json::from_str::<Color>(r#"{"Rgb":[255,0,255]}"#)?
|
|
766
|
+
);
|
|
767
|
+
assert_eq!(
|
|
768
|
+
Color::Indexed(10),
|
|
769
|
+
serde_json::from_str::<Color>(r#"{"Indexed":10}"#)?
|
|
770
|
+
);
|
|
771
|
+
Ok(())
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
#[test]
|
|
775
|
+
fn test_from_array_and_tuple_conversions() {
|
|
776
|
+
let from_array3 = Color::from([123, 45, 67]);
|
|
777
|
+
assert_eq!(from_array3, Color::Rgb(123, 45, 67));
|
|
778
|
+
|
|
779
|
+
let from_tuple3 = Color::from((89, 76, 54));
|
|
780
|
+
assert_eq!(from_tuple3, Color::Rgb(89, 76, 54));
|
|
781
|
+
|
|
782
|
+
let from_array4 = Color::from([10, 20, 30, 255]);
|
|
783
|
+
assert_eq!(from_array4, Color::Rgb(10, 20, 30));
|
|
784
|
+
|
|
785
|
+
let from_tuple4 = Color::from((200, 150, 100, 0));
|
|
786
|
+
assert_eq!(from_tuple4, Color::Rgb(200, 150, 100));
|
|
787
|
+
}
|
|
788
|
+
}
|