diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 76a6bf17..993b7fb0 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -3,6 +3,8 @@ use iced::{ Settings, Slider, Text, }; use palette::{self, Limited}; +use std::marker::PhantomData; +use std::ops::RangeInclusive; pub fn main() { ColorPalette::run(Settings { @@ -51,20 +53,288 @@ fn generate_theme(base_color: &Color) -> Vec { theme } +struct ColorPicker { + sliders: [slider::State; 3], + color_space: PhantomData, +} + +trait ColorSpace: Sized { + const LABEL: &'static str; + const COMPONENT_RANGES: [RangeInclusive; 3]; + + fn new(a: f32, b: f32, c: f32) -> Self; + + fn components(&self) -> [f32; 3]; + + fn update_component(c: Self, i: usize, val: f32) -> Self; + + fn to_string(&self) -> String; +} + +impl ColorPicker { + fn view(&mut self, color: C) -> Element { + let [c1, c2, c3] = color.components(); + let [s1, s2, s3] = &mut self.sliders; + let [cr1, cr2, cr3] = C::COMPONENT_RANGES; + Row::new() + .spacing(10) + .push(Text::new(C::LABEL).width(Length::Units(50))) + .push(Slider::new(s1, cr1, c1, move |v| { + C::update_component(color, 0, v) + })) + .push(Slider::new(s2, cr2, c2, move |v| { + C::update_component(color, 1, v) + })) + .push(Slider::new(s3, cr3, c3, move |v| { + C::update_component(color, 2, v) + })) + .push( + Text::new(color.to_string()) + .width(Length::Units(185)) + .size(16), + ) + .into() + } +} + +impl ColorSpace for Color { + const LABEL: &'static str = "RGB"; + const COMPONENT_RANGES: [RangeInclusive; 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 update_component(c: Color, i: usize, val: f32) -> Self { + match i { + 0 => Color { r: val, ..c }, + 1 => Color { g: val, ..c }, + 2 => Color { b: val, ..c }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + 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; 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 update_component(c: palette::Hsl, i: usize, val: f32) -> Self { + match i { + 0 => palette::Hsl { + hue: palette::RgbHue::from_degrees(val), + ..c + }, + 1 => palette::Hsl { + saturation: val, + ..c + }, + 2 => palette::Hsl { + lightness: val, + ..c + }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + 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; 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 update_component(c: palette::Hsv, i: usize, val: f32) -> Self { + match i { + 0 => palette::Hsv { + hue: palette::RgbHue::from_degrees(val), + ..c + }, + 1 => palette::Hsv { + saturation: val, + ..c + }, + 2 => palette::Hsv { value: val, ..c }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + 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; 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 update_component(c: palette::Hwb, i: usize, val: f32) -> Self { + match i { + 0 => palette::Hwb { + hue: palette::RgbHue::from_degrees(val), + ..c + }, + 1 => palette::Hwb { + whiteness: val, + ..c + }, + 2 => palette::Hwb { + blackness: val, + ..c + }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + 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; 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 update_component(c: palette::Lab, i: usize, val: f32) -> Self { + match i { + 0 => palette::Lab { l: val, ..c }, + 1 => palette::Lab { a: val, ..c }, + 2 => palette::Lab { b: val, ..c }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + 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; 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 update_component(c: palette::Lch, i: usize, val: f32) -> Self { + match i { + 0 => palette::Lch { l: val, ..c }, + 1 => palette::Lch { chroma: val, ..c }, + 2 => palette::Lch { + hue: palette::LabHue::from_degrees(val), + ..c + }, + _ => panic!("Invalid component index: {:?}", i), + } + } + + fn to_string(&self) -> String { + format!( + "Lch({:.1}, {:.1}, {:.1})", + self.l, + self.chroma, + self.hue.to_positive_degrees() + ) + } +} + pub struct ColorPalette { state: State, - rgb_sliders: [slider::State; 3], - hsl_sliders: [slider::State; 3], - hsv_sliders: [slider::State; 3], - hwb_sliders: [slider::State; 3], - lab_sliders: [slider::State; 3], - lch_sliders: [slider::State; 3], - rgb_text_value: String, - hsl_text_value: String, - hsv_text_value: String, - hwb_text_value: String, - lab_text_value: String, - lch_text_value: String, + rgb: ColorPicker, + hsl: ColorPicker, + hsv: ColorPicker, + hwb: ColorPicker, + lab: ColorPicker, + lch: ColorPicker, canvas_layer: canvas::layer::Cache, } @@ -90,28 +360,33 @@ impl Sandbox for ColorPalette { ] } - let state = State::new(); - let rgb_text_value = color_str(&state.color, ColorFormat::Rgb); - let hsl_text_value = color_str(&state.color, ColorFormat::Hsl); - let hsv_text_value = color_str(&state.color, ColorFormat::Hsv); - let hwb_text_value = color_str(&state.color, ColorFormat::Hwb); - let lab_text_value = color_str(&state.color, ColorFormat::Lab); - let lch_text_value = color_str(&state.color, ColorFormat::Lch); - ColorPalette { - state, - rgb_sliders: triple_slider(), - hsl_sliders: triple_slider(), - hsv_sliders: triple_slider(), - hwb_sliders: triple_slider(), - lab_sliders: triple_slider(), - lch_sliders: triple_slider(), - rgb_text_value, - hsl_text_value, - hsv_text_value, - hwb_text_value, - lab_text_value, - lch_text_value, + state: State::new(), + rgb: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, + hsl: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, + hsv: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, + + hwb: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, + lab: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, + lch: ColorPicker { + sliders: triple_slider(), + color_space: PhantomData::, + }, canvas_layer: canvas::layer::Cache::new(), } } @@ -135,24 +410,9 @@ impl Sandbox for ColorPalette { // Set theme colors self.state.theme = generate_theme(&self.state.color); - - // Set text - self.rgb_text_value = color_str(&self.state.color, ColorFormat::Rgb); - self.hsl_text_value = color_str(&self.state.color, ColorFormat::Hsl); - self.hsv_text_value = color_str(&self.state.color, ColorFormat::Hsv); - self.hwb_text_value = color_str(&self.state.color, ColorFormat::Hwb); - self.lab_text_value = color_str(&self.state.color, ColorFormat::Lab); - self.lch_text_value = color_str(&self.state.color, ColorFormat::Lch); } fn view(&mut self) -> Element { - let [rgb1, rgb2, rgb3] = &mut self.rgb_sliders; - let [hsl1, hsl2, hsl3] = &mut self.hsl_sliders; - let [hsv1, hsv2, hsv3] = &mut self.hsv_sliders; - let [hwb1, hwb2, hwb3] = &mut self.hwb_sliders; - let [lab1, lab2, lab3] = &mut self.lab_sliders; - let [lch1, lch2, lch3] = &mut self.lch_sliders; - let color = self.state.color; let srgb = palette::Srgb::from(self.state.color); let hsl = palette::Hsl::from(srgb); @@ -164,208 +424,12 @@ impl Sandbox for ColorPalette { Column::new() .padding(10) .spacing(10) - .push( - Row::new() - .spacing(10) - .push(Text::new("RGB").width(Length::Units(50))) - .push(Slider::new(rgb1, 0.0..=1.0, color.r, move |r| { - Message::RgbColorChanged(Color { r, ..color }) - })) - .push(Slider::new(rgb2, 0.0..=1.0, color.g, move |g| { - Message::RgbColorChanged(Color { g, ..color }) - })) - .push(Slider::new(rgb3, 0.0..=1.0, color.b, move |b| { - Message::RgbColorChanged(Color { b, ..color }) - })) - .push( - Text::new(&self.rgb_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) - .push( - Row::new() - .spacing(10) - .push(Text::new("HSL").width(Length::Units(50))) - .push(Slider::new( - hsl1, - 0.0..=360.0, - hsl.hue.to_positive_degrees(), - move |hue| { - Message::HslColorChanged(palette::Hsl { - hue: palette::RgbHue::from_degrees(hue), - ..hsl - }) - }, - )) - .push(Slider::new( - hsl2, - 0.0..=1.0, - hsl.saturation, - move |saturation| { - Message::HslColorChanged(palette::Hsl { - saturation, - ..hsl - }) - }, - )) - .push(Slider::new( - hsl3, - 0.0..=1.0, - hsl.lightness, - move |lightness| { - Message::HslColorChanged(palette::Hsl { - lightness, - ..hsl - }) - }, - )) - .push( - Text::new(&self.hsl_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) - .push( - Row::new() - .spacing(10) - .push(Text::new("HSV").width(Length::Units(50))) - .push(Slider::new( - hsv1, - 0.0..=360.0, - hsv.hue.to_positive_degrees(), - move |hue| { - Message::HsvColorChanged(palette::Hsv { - hue: palette::RgbHue::from_degrees(hue), - ..hsv - }) - }, - )) - .push(Slider::new( - hsv2, - 0.0..=1.0, - hsv.saturation, - move |saturation| { - Message::HsvColorChanged(palette::Hsv { - saturation, - ..hsv - }) - }, - )) - .push(Slider::new( - hsv3, - 0.0..=1.0, - hsv.value, - move |value| { - Message::HsvColorChanged(palette::Hsv { - value, - ..hsv - }) - }, - )) - .push( - Text::new(&self.hsv_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) - .push( - Row::new() - .spacing(10) - .push(Text::new("HWB").width(Length::Units(50))) - .push(Slider::new( - hwb1, - 0.0..=360.0, - hwb.hue.to_positive_degrees(), - move |hue| { - Message::HwbColorChanged(palette::Hwb { - hue: palette::RgbHue::from_degrees(hue), - ..hwb - }) - }, - )) - .push(Slider::new( - hwb2, - 0.0..=1.0, - hwb.whiteness, - move |whiteness| { - Message::HwbColorChanged(palette::Hwb { - whiteness, - ..hwb - }) - }, - )) - .push(Slider::new( - hwb3, - 0.0..=1.0, - hwb.blackness, - move |blackness| { - Message::HwbColorChanged(palette::Hwb { - blackness, - ..hwb - }) - }, - )) - .push( - Text::new(&self.hwb_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) - .push( - Row::new() - .spacing(10) - .push(Text::new("Lab").width(Length::Units(50))) - .push(Slider::new(lab1, 0.0..=100.0, lab.l, move |l| { - Message::LabColorChanged(palette::Lab { l, ..lab }) - })) - .push(Slider::new(lab2, -128.0..=127.0, lab.a, move |a| { - Message::LabColorChanged(palette::Lab { a, ..lab }) - })) - .push(Slider::new(lab3, -128.0..=127.0, lab.b, move |b| { - Message::LabColorChanged(palette::Lab { b, ..lab }) - })) - .push( - Text::new(&self.lab_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) - .push( - Row::new() - .spacing(10) - .push(Text::new("Lch").width(Length::Units(50))) - .push(Slider::new(lch1, 0.0..=100.0, lch.l, move |l| { - Message::LchColorChanged(palette::Lch { l, ..lch }) - })) - .push(Slider::new( - lch2, - 0.0..=128.0, - lch.chroma, - move |chroma| { - Message::LchColorChanged(palette::Lch { - chroma, - ..lch - }) - }, - )) - .push(Slider::new( - lch3, - 0.0..=360.0, - lch.hue.to_positive_degrees(), - move |hue| { - Message::LchColorChanged(palette::Lch { - hue: palette::LabHue::from_degrees(hue), - ..lch - }) - }, - )) - .push( - Text::new(&self.lch_text_value) - .width(Length::Units(185)) - .size(16), - ), - ) + .push(self.rgb.view(color).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) @@ -395,7 +459,6 @@ impl canvas::Drawable for State { use palette::{Hsl, Srgb}; if self.theme.len() == 0 { - println!("Zero len"); return; } @@ -464,7 +527,7 @@ impl canvas::Drawable for State { } frame.fill_text(canvas::Text { - content: color_str(&self.theme[i], ColorFormat::Hex), + content: color_hex_str(&self.theme[i]), position: Point { x: anchor.x + box_size.width / 2.0, y: box_size.height, @@ -494,7 +557,7 @@ impl canvas::Drawable for State { frame.fill(&rect, Fill::Color(color)); frame.fill_text(canvas::Text { - content: color_str(&color, ColorFormat::Hex), + content: color_hex_str(&color), position: Point { x: anchor.x + box_size.width / 2.0, y: box_size.height + 2.0 * pad, @@ -505,63 +568,11 @@ impl canvas::Drawable for State { } } -enum ColorFormat { - Hex, - Rgb, - Hsl, - Hsv, - Hwb, - Lab, - Lch, -} - -fn color_str(color: &Color, color_format: ColorFormat) -> String { - let srgb = palette::Srgb::from(*color); - 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); - - match color_format { - ColorFormat::Hex => 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 - ), - ColorFormat::Rgb => format!( - "rgb({:.0}, {:.0}, {:.0})", - 255.0 * color.r, - 255.0 * color.g, - 255.0 * color.b - ), - ColorFormat::Hsl => format!( - "hsl({:.1}, {:.1}%, {:.1}%)", - hsl.hue.to_positive_degrees(), - 100.0 * hsl.saturation, - 100.0 * hsl.lightness - ), - ColorFormat::Hsv => format!( - "hsv({:.1}, {:.1}%, {:.1}%)", - hsv.hue.to_positive_degrees(), - 100.0 * hsv.saturation, - 100.0 * hsv.value - ), - ColorFormat::Hwb => format!( - "hwb({:.1}, {:.1}%, {:.1}%)", - hwb.hue.to_positive_degrees(), - 100.0 * hwb.whiteness, - 100.0 * hwb.blackness - ), - ColorFormat::Lab => { - format!("Lab({:.1}, {:.1}, {:.1})", lab.l, lab.a, lab.b) - } - ColorFormat::Lch => format!( - "Lch({:.1}, {:.1}, {:.1})", - lch.l, - lch.chroma, - lch.hue.to_positive_degrees() - ), - } +fn color_hex_str(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 + ) }