diff options
author | 2022-05-02 16:05:40 +0100 | |
---|---|---|
committer | 2022-07-27 03:18:34 +0200 | |
commit | a28799b65e4440fae72797006eaf30e4f45f38e5 (patch) | |
tree | d0cba0b051a8bffa79b9c7050e7ca514aa3afa21 /examples | |
parent | 785385d889f02fc3805920b42ed8195a7f6a5672 (diff) | |
download | iced-a28799b65e4440fae72797006eaf30e4f45f38e5.tar.gz iced-a28799b65e4440fae72797006eaf30e4f45f38e5.tar.bz2 iced-a28799b65e4440fae72797006eaf30e4f45f38e5.zip |
add pure version of color_palette example
Diffstat (limited to 'examples')
-rw-r--r-- | examples/pure/color_palette/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/pure/color_palette/README.md | 15 | ||||
-rw-r--r-- | examples/pure/color_palette/screenshot.png | bin | 0 -> 44798 bytes | |||
-rw-r--r-- | examples/pure/color_palette/src/main.rs | 465 |
4 files changed, 490 insertions, 0 deletions
diff --git a/examples/pure/color_palette/Cargo.toml b/examples/pure/color_palette/Cargo.toml new file mode 100644 index 00000000..d08309d5 --- /dev/null +++ b/examples/pure/color_palette/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "pure_color_palette" +version = "0.1.0" +authors = ["Clark Moody <clark@clarkmoody.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../../..", features = ["pure", "canvas", "palette"] } +palette = "0.6.0" diff --git a/examples/pure/color_palette/README.md b/examples/pure/color_palette/README.md new file mode 100644 index 00000000..f90020b1 --- /dev/null +++ b/examples/pure/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="screenshot.png"> + </a> +</div> + +You can run it with `cargo run`: + +``` +cargo run --package pure_color_palette +``` diff --git a/examples/pure/color_palette/screenshot.png b/examples/pure/color_palette/screenshot.png Binary files differnew file mode 100644 index 00000000..e8da35c4 --- /dev/null +++ b/examples/pure/color_palette/screenshot.png diff --git a/examples/pure/color_palette/src/main.rs b/examples/pure/color_palette/src/main.rs new file mode 100644 index 00000000..118c80cc --- /dev/null +++ b/examples/pure/color_palette/src/main.rs @@ -0,0 +1,465 @@ +use iced::pure::{ + column, row, text, + widget::canvas::{self, Canvas, Cursor, Frame, Geometry, Path}, + widget::Slider, + Element, Sandbox, +}; +use iced::{ + alignment, Alignment, Color, Length, Point, Rectangle, Settings, Size, + Vector, +}; +use palette::{self, convert::FromColor, Hsl, Srgb}; +use std::marker::PhantomData; +use std::ops::RangeInclusive; + +pub fn main() -> iced::Result { + 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>, +} + +#[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_color(hsl), + Message::HsvColorChanged(hsv) => palette::Srgb::from_color(hsv), + Message::HwbColorChanged(hwb) => palette::Srgb::from_color(hwb), + Message::LabColorChanged(lab) => palette::Srgb::from_color(lab), + Message::LchColorChanged(lch) => palette::Srgb::from_color(lch), + }; + + self.theme = Theme::new(srgb); + } + + fn view(&self) -> Element<Message> { + let base = self.theme.base; + + let srgb = palette::Srgb::from(base); + let hsl = palette::Hsl::from_color(srgb); + let hsv = palette::Hsv::from_color(srgb); + let hwb = palette::Hwb::from_color(srgb); + let lab = palette::Lab::from_color(srgb); + let lch = palette::Lch::from_color(srgb); + + column() + .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(self.theme.view()) + .into() + } +} + +#[derive(Debug)] +pub struct Theme { + lower: Vec<Color>, + base: Color, + higher: Vec<Color>, + canvas_cache: canvas::Cache, +} + +impl Theme { + pub fn new(base: impl Into<Color>) -> Theme { + use palette::{Hue, Shade}; + + let base = base.into(); + + // Convert to HSL color for manipulation + let hsl = Hsl::from_color(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(color).into()) + .collect(), + base, + higher: higher + .iter() + .map(|&color| Srgb::from_color(color).into()) + .collect(), + canvas_cache: canvas::Cache::default(), + } + } + + 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()) + } + + pub fn view(&self) -> Element<Message> { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } + + fn draw(&self, frame: &mut Frame) { + 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: alignment::Horizontal::Center, + vertical_alignment: alignment::Vertical::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, + }; + frame.fill_rectangle(anchor, box_size, 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 = alignment::Vertical::Bottom; + + let hsl = Hsl::from_color(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_color(graded).into(); + + let anchor = Point { + x: (i as f32) * box_size.width, + y: box_size.height + 2.0 * pad, + }; + + frame.fill_rectangle(anchor, box_size, 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<Message> canvas::Program<Message> for Theme { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &iced::Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec<Geometry> { + let theme = self.canvas_cache.draw(bounds.size(), |frame| { + self.draw(frame); + }); + + vec![theme] + } +} + +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> { + color_space: PhantomData<C>, +} + +trait ColorSpace: Sized { + const LABEL: &'static str; + const COMPONENT_RANGES: [RangeInclusive<f64>; 3]; + + fn new(a: f32, b: f32, c: f32) -> Self; + + fn components(&self) -> [f32; 3]; + + fn to_string(&self) -> String; +} + +impl<C: ColorSpace + Copy> ColorPicker<C> { + fn view(&self, color: C) -> Element<C> { + let [c1, c2, c3] = color.components(); + let [cr1, cr2, cr3] = C::COMPONENT_RANGES; + + fn slider<'a, C: Clone>( + range: RangeInclusive<f64>, + component: f32, + update: impl Fn(f32) -> C + 'a, + ) -> Slider<'a, f64, C, iced::Renderer> { + Slider::new(range, f64::from(component), move |v| update(v as f32)) + .step(0.01) + } + + row() + .spacing(10) + .align_items(Alignment::Center) + .push(text(C::LABEL).width(Length::Units(50))) + .push(slider(cr1, c1, move |v| C::new(v, c2, c3))) + .push(slider(cr2, c2, move |v| C::new(c1, v, c3))) + .push(slider(cr3, c3, move |v| C::new(c1, c2, v))) + .push(text(color.to_string()).width(Length::Units(185)).size(14)) + .into() + } +} + +impl ColorSpace for Color { + const LABEL: &'static str = "RGB"; + const COMPONENT_RANGES: [RangeInclusive<f64>; 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<f64>; 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<f64>; 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<f64>; 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<f64>; 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<f64>; 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() + ) + } +} |