summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-05-04 23:35:09 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2020-05-04 23:35:09 +0200
commit67b2ccb4d5ae19d7c325f973b51fb02db03c853c (patch)
treede0c3fc4ad546403cf68b1dfb0a50db94e9f3197
parent2f41ccee1c7b52f872d68d2e5ebd68ea49a1559b (diff)
parent27aad74a32fd8ac2b12f9d32df8a3b61a3175457 (diff)
downloadiced-67b2ccb4d5ae19d7c325f973b51fb02db03c853c.tar.gz
iced-67b2ccb4d5ae19d7c325f973b51fb02db03c853c.tar.bz2
iced-67b2ccb4d5ae19d7c325f973b51fb02db03c853c.zip
Merge branch 'master' into feature/canvas-interaction
-rw-r--r--Cargo.toml4
-rw-r--r--core/Cargo.toml4
-rw-r--r--core/src/color.rs148
-rw-r--r--examples/README.md1
-rw-r--r--examples/color_palette/Cargo.toml10
-rw-r--r--examples/color_palette/README.md15
-rw-r--r--examples/color_palette/screenshot.pngbin0 -> 105201 bytes
-rw-r--r--examples/color_palette/src/main.rs444
8 files changed, 621 insertions, 5 deletions
diff --git a/Cargo.toml b/Cargo.toml
index d394d516..6dd30b42 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,8 @@ debug = ["iced_winit/debug"]
tokio = ["iced_futures/tokio"]
# Enables `async-std` as the `executor::Default` on native platforms
async-std = ["iced_futures/async-std"]
+# Enables advanced color conversion via `palette`
+palette = ["iced_core/palette"]
[badges]
maintenance = { status = "actively-developed" }
@@ -39,6 +41,7 @@ members = [
"winit",
"examples/bezier_tool",
"examples/clock",
+ "examples/color_palette",
"examples/counter",
"examples/custom_widget",
"examples/download_progress",
@@ -58,6 +61,7 @@ members = [
]
[dependencies]
+iced_core = { version = "0.2", path = "core" }
iced_futures = { version = "0.1", path = "futures" }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 837f6aae..b52bf315 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -8,3 +8,7 @@ license = "MIT"
repository = "https://github.com/hecrj/iced"
[dependencies]
+
+[dependencies.palette]
+version = "0.5.0"
+optional = true
diff --git a/core/src/color.rs b/core/src/color.rs
index db509b88..a4c3d87c 100644
--- a/core/src/color.rs
+++ b/core/src/color.rs
@@ -1,10 +1,16 @@
+#[cfg(feature = "palette")]
+use palette::rgb::{Srgb, Srgba};
+
/// A color in the sRGB color space.
-#[derive(Debug, Clone, Copy, PartialEq)]
-#[allow(missing_docs)]
+#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Color {
+ /// Red component, 0.0 - 1.0
pub r: f32,
+ /// Green component, 0.0 - 1.0
pub g: f32,
+ /// Blue component, 0.0 - 1.0
pub b: f32,
+ /// Transparency, 0.0 - 1.0
pub a: f32,
}
@@ -33,11 +39,45 @@ impl Color {
a: 0.0,
};
+ /// Creates a new [`Color`].
+ ///
+ /// In debug mode, it will panic if the values are not in the correct
+ /// range: 0.0 - 1.0
+ ///
+ /// [`Color`]: struct.Color.html
+ pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color {
+ debug_assert!(
+ (0.0..=1.0).contains(&r),
+ "Red component must be on [0, 1]"
+ );
+ debug_assert!(
+ (0.0..=1.0).contains(&g),
+ "Green component must be on [0, 1]"
+ );
+ debug_assert!(
+ (0.0..=1.0).contains(&b),
+ "Blue component must be on [0, 1]"
+ );
+ debug_assert!(
+ (0.0..=1.0).contains(&a),
+ "Alpha component must be on [0, 1]"
+ );
+
+ Color { r, g, b, a }
+ }
+
/// Creates a [`Color`] from its RGB components.
///
/// [`Color`]: struct.Color.html
pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color {
- Color { r, g, b, a: 1.0 }
+ Color::from_rgba(r, g, b, 1.0f32)
+ }
+
+ /// Creates a [`Color`] from its RGBA components.
+ ///
+ /// [`Color`]: struct.Color.html
+ pub const fn from_rgba(r: f32, g: f32, b: f32, a: f32) -> Color {
+ Color { r, g, b, a }
}
/// Creates a [`Color`] from its RGB8 components.
@@ -80,16 +120,114 @@ impl Color {
self.a,
]
}
+
+ /// Inverts the [`Color`] in-place.
+ ///
+ /// [`Color`]: struct.Color.html
+ pub fn invert(&mut self) {
+ self.r = 1.0f32 - self.r;
+ self.b = 1.0f32 - self.g;
+ self.g = 1.0f32 - self.b;
+ }
+
+ /// Returns the inverted [`Color`].
+ ///
+ /// [`Color`]: struct.Color.html
+ pub fn inverse(self) -> Color {
+ Color::new(1.0f32 - self.r, 1.0f32 - self.g, 1.0f32 - self.b, self.a)
+ }
}
impl From<[f32; 3]> for Color {
fn from([r, g, b]: [f32; 3]) -> Self {
- Color { r, g, b, a: 1.0 }
+ Color::new(r, g, b, 1.0)
}
}
impl From<[f32; 4]> for Color {
fn from([r, g, b, a]: [f32; 4]) -> Self {
- Color { r, g, b, a }
+ Color::new(r, g, b, a)
+ }
+}
+
+#[cfg(feature = "palette")]
+/// Converts from palette's `Srgba` type to a [`Color`].
+///
+/// [`Color`]: struct.Color.html
+impl From<Srgba> for Color {
+ fn from(srgba: Srgba) -> Self {
+ Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha)
+ }
+}
+
+#[cfg(feature = "palette")]
+/// Converts from [`Color`] to palette's `Srgba` type.
+///
+/// [`Color`]: struct.Color.html
+impl From<Color> for Srgba {
+ fn from(c: Color) -> Self {
+ Srgba::new(c.r, c.g, c.b, c.a)
+ }
+}
+
+#[cfg(feature = "palette")]
+/// Converts from palette's `Srgb` type to a [`Color`].
+///
+/// [`Color`]: struct.Color.html
+impl From<Srgb> for Color {
+ fn from(srgb: Srgb) -> Self {
+ Color::new(srgb.red, srgb.green, srgb.blue, 1.0)
+ }
+}
+
+#[cfg(feature = "palette")]
+/// Converts from [`Color`] to palette's `Srgb` type.
+///
+/// [`Color`]: struct.Color.html
+/// [`Srgb`]: ../palette/rgb/type.Srgb.html
+impl From<Color> for Srgb {
+ fn from(c: Color) -> Self {
+ Srgb::new(c.r, c.g, c.b)
+ }
+}
+
+#[cfg(feature = "palette")]
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use palette::Blend;
+
+ #[test]
+ fn srgba_traits() {
+ let c = Color::from_rgb(0.5, 0.4, 0.3);
+ // Round-trip conversion to the palette:Srgba type
+ let s: Srgba = c.into();
+ let r: Color = s.into();
+ assert_eq!(c, r);
+ }
+
+ #[test]
+ fn color_manipulation() {
+ let c1 = Color::from_rgb(0.5, 0.4, 0.3);
+ let c2 = Color::from_rgb(0.2, 0.5, 0.3);
+
+ // Convert to linear color for manipulation
+ let l1 = Srgba::from(c1).into_linear();
+ let l2 = Srgba::from(c2).into_linear();
+
+ // Take the lighter of each of the RGB components
+ let lighter = l1.lighten(l2);
+
+ // Convert back to our Color
+ let r: Color = Srgba::from_linear(lighter).into();
+ assert_eq!(
+ r,
+ Color {
+ r: 0.5,
+ g: 0.5,
+ b: 0.3,
+ a: 1.0
+ }
+ );
}
}
diff --git a/examples/README.md b/examples/README.md
index 5d880d71..7e7bda9d 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -71,6 +71,7 @@ A bunch of simpler examples exist:
- [`bezier_tool`](bezier_tool), a Paint-like tool for drawing Bézier curves using the `Canvas` widget.
- [`clock`](clock), an application that uses the `Canvas` widget to draw a clock and its hands to display the current time.
+- [`color_palette`](color_palette), a color palette generator based on a user-defined root color.
- [`counter`](counter), the classic counter example explained in the [`README`](../README.md).
- [`custom_widget`](custom_widget), a demonstration of how to build a custom widget that draws a circle.
- [`download_progress`](download_progress), a basic application that asynchronously downloads a dummy file of 100 MB and tracks the download progress.
diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml
new file mode 100644
index 00000000..00f33e20
--- /dev/null
+++ b/examples/color_palette/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "color_palette"
+version = "0.1.0"
+authors = ["Clark Moody <clark@clarkmoody.com>"]
+edition = "2018"
+publish = false
+
+[dependencies]
+iced = { path = "../..", features = ["canvas", "palette"] }
+palette = "0.5.0"
diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md
new file mode 100644
index 00000000..e70188f8
--- /dev/null
+++ b/examples/color_palette/README.md
@@ -0,0 +1,15 @@
+## Color palette
+
+A color palette generator, based on a user-defined root color.
+
+<div align="center">
+ <a href="https://gfycat.com/dirtylonebighornsheep">
+ <img src="https://github.com/hecrj/iced/raw/1a8d253611d3796b0a32b2f096bb54565a5292e0/examples/color_palette/screenshot.png">
+ </a>
+</div>
+
+You can run it with `cargo run`:
+
+```
+cargo run --package color_palette
+```
diff --git a/examples/color_palette/screenshot.png b/examples/color_palette/screenshot.png
new file mode 100644
index 00000000..aa4772e0
--- /dev/null
+++ b/examples/color_palette/screenshot.png
Binary files differ
diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs
new file mode 100644
index 00000000..073a6734
--- /dev/null
+++ b/examples/color_palette/src/main.rs
@@ -0,0 +1,444 @@
+use iced::{
+ canvas, slider, Align, Canvas, Color, Column, Element, HorizontalAlignment,
+ Length, Point, Row, Sandbox, Settings, Size, Slider, Text, Vector,
+ VerticalAlignment,
+};
+use palette::{self, Limited};
+use std::marker::PhantomData;
+use std::ops::RangeInclusive;
+
+pub fn main() {
+ ColorPalette::run(Settings {
+ antialiasing: true,
+ ..Settings::default()
+ })
+}
+
+#[derive(Default)]
+pub struct ColorPalette {
+ theme: Theme,
+ rgb: ColorPicker<Color>,
+ hsl: ColorPicker<palette::Hsl>,
+ hsv: ColorPicker<palette::Hsv>,
+ hwb: ColorPicker<palette::Hwb>,
+ lab: ColorPicker<palette::Lab>,
+ lch: ColorPicker<palette::Lch>,
+ canvas_layer: canvas::layer::Cache<Theme>,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum Message {
+ RgbColorChanged(Color),
+ HslColorChanged(palette::Hsl),
+ HsvColorChanged(palette::Hsv),
+ HwbColorChanged(palette::Hwb),
+ LabColorChanged(palette::Lab),
+ LchColorChanged(palette::Lch),
+}
+
+impl Sandbox for ColorPalette {
+ type Message = Message;
+
+ fn new() -> Self {
+ Self::default()
+ }
+
+ fn title(&self) -> String {
+ String::from("Color palette - Iced")
+ }
+
+ fn update(&mut self, message: Message) {
+ let srgb = match message {
+ Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb),
+ Message::HslColorChanged(hsl) => palette::Srgb::from(hsl),
+ Message::HsvColorChanged(hsv) => palette::Srgb::from(hsv),
+ Message::HwbColorChanged(hwb) => palette::Srgb::from(hwb),
+ Message::LabColorChanged(lab) => palette::Srgb::from(lab),
+ Message::LchColorChanged(lch) => palette::Srgb::from(lch),
+ };
+
+ self.theme = Theme::new(srgb.clamp());
+ self.canvas_layer.clear();
+ }
+
+ fn view(&mut self) -> Element<Message> {
+ let base = self.theme.base;
+
+ let srgb = palette::Srgb::from(base);
+ let hsl = palette::Hsl::from(srgb);
+ let hsv = palette::Hsv::from(srgb);
+ let hwb = palette::Hwb::from(srgb);
+ let lab = palette::Lab::from(srgb);
+ let lch = palette::Lch::from(srgb);
+
+ Column::new()
+ .padding(10)
+ .spacing(10)
+ .push(self.rgb.view(base).map(Message::RgbColorChanged))
+ .push(self.hsl.view(hsl).map(Message::HslColorChanged))
+ .push(self.hsv.view(hsv).map(Message::HsvColorChanged))
+ .push(self.hwb.view(hwb).map(Message::HwbColorChanged))
+ .push(self.lab.view(lab).map(Message::LabColorChanged))
+ .push(self.lch.view(lch).map(Message::LchColorChanged))
+ .push(
+ Canvas::new()
+ .width(Length::Fill)
+ .height(Length::Fill)
+ .push(self.canvas_layer.with(&self.theme)),
+ )
+ .into()
+ }
+}
+
+#[derive(Debug)]
+pub struct Theme {
+ lower: Vec<Color>,
+ base: Color,
+ higher: Vec<Color>,
+}
+
+impl Theme {
+ pub fn new(base: impl Into<Color>) -> Theme {
+ use palette::{Hsl, Hue, Shade, Srgb};
+
+ let base = base.into();
+
+ // Convert to HSL color for manipulation
+ let hsl = Hsl::from(Srgb::from(base));
+
+ let lower = [
+ hsl.shift_hue(-135.0).lighten(0.075),
+ hsl.shift_hue(-120.0),
+ hsl.shift_hue(-105.0).darken(0.075),
+ hsl.darken(0.075),
+ ];
+
+ let higher = [
+ hsl.lighten(0.075),
+ hsl.shift_hue(105.0).darken(0.075),
+ hsl.shift_hue(120.0),
+ hsl.shift_hue(135.0).lighten(0.075),
+ ];
+
+ Theme {
+ lower: lower
+ .iter()
+ .map(|&color| Srgb::from(color).clamp().into())
+ .collect(),
+ base,
+ higher: higher
+ .iter()
+ .map(|&color| Srgb::from(color).clamp().into())
+ .collect(),
+ }
+ }
+
+ pub fn len(&self) -> usize {
+ self.lower.len() + self.higher.len() + 1
+ }
+
+ pub fn colors(&self) -> impl Iterator<Item = &Color> {
+ self.lower
+ .iter()
+ .chain(std::iter::once(&self.base))
+ .chain(self.higher.iter())
+ }
+}
+
+impl canvas::Drawable for Theme {
+ fn draw(&self, frame: &mut canvas::Frame) {
+ use canvas::Path;
+ use palette::{Hsl, Srgb};
+
+ let pad = 20.0;
+
+ let box_size = Size {
+ width: frame.width() / self.len() as f32,
+ height: frame.height() / 2.0 - pad,
+ };
+
+ let triangle = Path::new(|path| {
+ path.move_to(Point { x: 0.0, y: -0.5 });
+ path.line_to(Point { x: -0.5, y: 0.0 });
+ path.line_to(Point { x: 0.5, y: 0.0 });
+ path.close();
+ });
+
+ let mut text = canvas::Text {
+ horizontal_alignment: HorizontalAlignment::Center,
+ vertical_alignment: VerticalAlignment::Top,
+ size: 15.0,
+ ..canvas::Text::default()
+ };
+
+ for (i, &color) in self.colors().enumerate() {
+ let anchor = Point {
+ x: (i as f32) * box_size.width,
+ y: 0.0,
+ };
+ let rect = Path::rectangle(anchor, box_size);
+ frame.fill(&rect, color);
+
+ // We show a little indicator for the base color
+ if color == self.base {
+ let triangle_x = anchor.x + box_size.width / 2.0;
+
+ frame.with_save(|frame| {
+ frame.translate(Vector::new(triangle_x, 0.0));
+ frame.scale(10.0);
+ frame.rotate(std::f32::consts::PI);
+
+ frame.fill(&triangle, Color::WHITE);
+ });
+
+ frame.with_save(|frame| {
+ frame.translate(Vector::new(triangle_x, box_size.height));
+ frame.scale(10.0);
+
+ frame.fill(&triangle, Color::WHITE);
+ });
+ }
+
+ frame.fill_text(canvas::Text {
+ content: color_hex_string(&color),
+ position: Point {
+ x: anchor.x + box_size.width / 2.0,
+ y: box_size.height,
+ },
+ ..text
+ });
+ }
+
+ text.vertical_alignment = VerticalAlignment::Bottom;
+
+ let hsl = Hsl::from(Srgb::from(self.base));
+ for i in 0..self.len() {
+ let pct = (i as f32 + 1.0) / (self.len() as f32 + 1.0);
+ let graded = Hsl {
+ lightness: 1.0 - pct,
+ ..hsl
+ };
+ let color: Color = Srgb::from(graded.clamp()).into();
+
+ let anchor = Point {
+ x: (i as f32) * box_size.width,
+ y: box_size.height + 2.0 * pad,
+ };
+
+ let rect = Path::rectangle(anchor, box_size);
+ frame.fill(&rect, color);
+
+ frame.fill_text(canvas::Text {
+ content: color_hex_string(&color),
+ position: Point {
+ x: anchor.x + box_size.width / 2.0,
+ y: box_size.height + 2.0 * pad,
+ },
+ ..text
+ });
+ }
+ }
+}
+
+impl Default for Theme {
+ fn default() -> Self {
+ Theme::new(Color::from_rgb8(75, 128, 190))
+ }
+}
+
+fn color_hex_string(color: &Color) -> String {
+ format!(
+ "#{:x}{:x}{:x}",
+ (255.0 * color.r).round() as u8,
+ (255.0 * color.g).round() as u8,
+ (255.0 * color.b).round() as u8
+ )
+}
+
+#[derive(Default)]
+struct ColorPicker<C: ColorSpace> {
+ sliders: [slider::State; 3],
+ color_space: PhantomData<C>,
+}
+
+trait ColorSpace: Sized {
+ const LABEL: &'static str;
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3];
+
+ fn new(a: f32, b: f32, c: f32) -> Self;
+
+ fn components(&self) -> [f32; 3];
+
+ fn to_string(&self) -> String;
+}
+
+impl<C: 'static + ColorSpace + Copy> ColorPicker<C> {
+ fn view(&mut self, color: C) -> Element<C> {
+ let [c1, c2, c3] = color.components();
+ let [s1, s2, s3] = &mut self.sliders;
+ let [cr1, cr2, cr3] = C::COMPONENT_RANGES;
+
+ Row::new()
+ .spacing(10)
+ .align_items(Align::Center)
+ .push(Text::new(C::LABEL).width(Length::Units(50)))
+ .push(Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3)))
+ .push(Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3)))
+ .push(Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v)))
+ .push(
+ Text::new(color.to_string())
+ .width(Length::Units(185))
+ .size(14),
+ )
+ .into()
+ }
+}
+
+impl ColorSpace for Color {
+ const LABEL: &'static str = "RGB";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=1.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(r: f32, g: f32, b: f32) -> Self {
+ Color::from_rgb(r, g, b)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.r, self.g, self.b]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "rgb({:.0}, {:.0}, {:.0})",
+ 255.0 * self.r,
+ 255.0 * self.g,
+ 255.0 * self.b
+ )
+ }
+}
+
+impl ColorSpace for palette::Hsl {
+ const LABEL: &'static str = "HSL";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, saturation: f32, lightness: f32) -> Self {
+ palette::Hsl::new(
+ palette::RgbHue::from_degrees(hue),
+ saturation,
+ lightness,
+ )
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [
+ self.hue.to_positive_degrees(),
+ self.saturation,
+ self.lightness,
+ ]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hsl({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.saturation,
+ 100.0 * self.lightness
+ )
+ }
+}
+
+impl ColorSpace for palette::Hsv {
+ const LABEL: &'static str = "HSV";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, saturation: f32, value: f32) -> Self {
+ palette::Hsv::new(palette::RgbHue::from_degrees(hue), saturation, value)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.hue.to_positive_degrees(), self.saturation, self.value]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hsv({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.saturation,
+ 100.0 * self.value
+ )
+ }
+}
+
+impl ColorSpace for palette::Hwb {
+ const LABEL: &'static str = "HWB";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=360.0, 0.0..=1.0, 0.0..=1.0];
+
+ fn new(hue: f32, whiteness: f32, blackness: f32) -> Self {
+ palette::Hwb::new(
+ palette::RgbHue::from_degrees(hue),
+ whiteness,
+ blackness,
+ )
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [
+ self.hue.to_positive_degrees(),
+ self.whiteness,
+ self.blackness,
+ ]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "hwb({:.1}, {:.1}%, {:.1}%)",
+ self.hue.to_positive_degrees(),
+ 100.0 * self.whiteness,
+ 100.0 * self.blackness
+ )
+ }
+}
+
+impl ColorSpace for palette::Lab {
+ const LABEL: &'static str = "Lab";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=100.0, -128.0..=127.0, -128.0..=127.0];
+
+ fn new(l: f32, a: f32, b: f32) -> Self {
+ palette::Lab::new(l, a, b)
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.l, self.a, self.b]
+ }
+
+ fn to_string(&self) -> String {
+ format!("Lab({:.1}, {:.1}, {:.1})", self.l, self.a, self.b)
+ }
+}
+
+impl ColorSpace for palette::Lch {
+ const LABEL: &'static str = "Lch";
+ const COMPONENT_RANGES: [RangeInclusive<f32>; 3] =
+ [0.0..=100.0, 0.0..=128.0, 0.0..=360.0];
+
+ fn new(l: f32, chroma: f32, hue: f32) -> Self {
+ palette::Lch::new(l, chroma, palette::LabHue::from_degrees(hue))
+ }
+
+ fn components(&self) -> [f32; 3] {
+ [self.l, self.chroma, self.hue.to_positive_degrees()]
+ }
+
+ fn to_string(&self) -> String {
+ format!(
+ "Lch({:.1}, {:.1}, {:.1})",
+ self.l,
+ self.chroma,
+ self.hue.to_positive_degrees()
+ )
+ }
+}