From 62fddce2e676123b1325a16d144a1d0674dcd1ff Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 21 Feb 2020 15:22:54 -0600 Subject: [PATCH 01/37] Add check_rgba fn to clamp float values to [0,1] --- core/src/color.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index db509b88..799d85c9 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -2,12 +2,17 @@ #[derive(Debug, Clone, Copy, PartialEq)] #[allow(missing_docs)] 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, } + impl Color { /// The black color. pub const BLACK: Color = Color { @@ -33,11 +38,26 @@ impl Color { a: 0.0, }; + /// Calmps a float value to the range [0.0, 1.0] + pub fn clamp(v: f32) -> f32 { + v.max(0.0f32).min(1.0f32) + } + + /// Ensures RGBA values on the range [0.0, 1.0] + pub fn check_rgba(r: f32, g: f32, b: f32, a:f32) -> Color { + Color { + r: Color::clamp(r), + g: Color::clamp(g), + b: Color::clamp(b), + a: Color::clamp(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 } + pub fn from_rgb(r: f32, g: f32, b: f32) -> Color { + Color::check_rgba(r, g, b, 1.0) } /// Creates a [`Color`] from its RGB8 components. @@ -55,7 +75,7 @@ impl Color { r: f32::from(r) / 255.0, g: f32::from(g) / 255.0, b: f32::from(b) / 255.0, - a, + a: Color::clamp(a), } } From 27a4cbccea91c508b914f2211a07aec2a4bed96e Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 21 Feb 2020 15:40:37 -0600 Subject: [PATCH 02/37] Add inversion functions, rename check_rgba -> new --- core/src/color.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 799d85c9..fbc160e3 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,6 +1,5 @@ /// A color in the sRGB color space. #[derive(Debug, Clone, Copy, PartialEq)] -#[allow(missing_docs)] pub struct Color { /// Red component, 0.0 - 1.0 pub r: f32, @@ -12,7 +11,6 @@ pub struct Color { pub a: f32, } - impl Color { /// The black color. pub const BLACK: Color = Color { @@ -44,7 +42,7 @@ impl Color { } /// Ensures RGBA values on the range [0.0, 1.0] - pub fn check_rgba(r: f32, g: f32, b: f32, a:f32) -> Color { + pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { Color { r: Color::clamp(r), g: Color::clamp(g), @@ -57,7 +55,7 @@ impl Color { /// /// [`Color`]: struct.Color.html pub fn from_rgb(r: f32, g: f32, b: f32) -> Color { - Color::check_rgba(r, g, b, 1.0) + Color::new(r, g, b, 1.0) } /// Creates a [`Color`] from its RGB8 components. @@ -100,6 +98,18 @@ impl Color { self.a, ] } + + /// Invert the Color in-place + pub fn invert(&mut self) { + self.r = Color::clamp(1.0f32 - self.r); + self.b = Color::clamp(1.0f32 - self.g); + self.g = Color::clamp(1.0f32 - self.b); + } + + /// Return an inverted Color + 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 { From 0ff3cbf543e84b1e4d1965efe3d6cce3b1fb5900 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 21 Feb 2020 16:42:12 -0600 Subject: [PATCH 03/37] HSLColor struct, with conversions to/from RGB --- core/src/color.rs | 122 ++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index fbc160e3..a46f44ee 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -36,18 +36,13 @@ impl Color { a: 0.0, }; - /// Calmps a float value to the range [0.0, 1.0] - pub fn clamp(v: f32) -> f32 { - v.max(0.0f32).min(1.0f32) - } - - /// Ensures RGBA values on the range [0.0, 1.0] + /// New Color with range checks pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { Color { - r: Color::clamp(r), - g: Color::clamp(g), - b: Color::clamp(b), - a: Color::clamp(a), + r: clamp(r), + g: clamp(g), + b: clamp(b), + a: clamp(a), } } @@ -73,7 +68,7 @@ impl Color { r: f32::from(r) / 255.0, g: f32::from(g) / 255.0, b: f32::from(b) / 255.0, - a: Color::clamp(a), + a: clamp(a), } } @@ -101,9 +96,9 @@ impl Color { /// Invert the Color in-place pub fn invert(&mut self) { - self.r = Color::clamp(1.0f32 - self.r); - self.b = Color::clamp(1.0f32 - self.g); - self.g = Color::clamp(1.0f32 - self.b); + self.r = clamp(1.0f32 - self.r); + self.b = clamp(1.0f32 - self.g); + self.g = clamp(1.0f32 - self.b); } /// Return an inverted Color @@ -114,12 +109,107 @@ impl Color { 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) } } + +impl From for Color { + fn from(hsl: HSLColor) -> Self { + // Compute Chroma + let ch = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s; + + // Quantized Hue: H' + let hp: u8 = (hsl.h / 60.0).ceil() as u8; + let x: f32 = ch * f32::from(1 - ((hp % 2) - 1)); + + // Intermediate RGB values + let (r1, g1, b1): (f32, f32, f32) = match hp { + 1 => (ch, x, 0.0), + 2 => (x, ch, 0.0), + 3 => (0.0, ch, x), + 4 => (0.0, x, ch), + 5 => (x, 0.0, ch), + 6 => (ch, 0.0, x), + _ => (0.0, 0.0, 0.0), + }; + + // Match lightness + let m = hsl.l - ch / 2.0; + + Color::new(r1 + m, g1 + m, b1 + m, hsl.a) + } +} + +/// A color in the HSL color space. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct HSLColor { + /// Hue, 0.0 - 360.0 + pub h: f32, + /// Saturation, 0.0 - 1.0 + pub s: f32, + /// Lightness, 0.0 - 1.0 + pub l: f32, + /// Transparency, 0.0 - 1.0 + pub a: f32, +} + +impl HSLColor { + /// New HSLColor with range checks + pub fn new(h: f32, s: f32, l: f32, a: f32) -> HSLColor { + HSLColor { + h: clamp_hue(h), + s: clamp(s), + l: clamp(l), + a: clamp(a), + } + } +} + +impl From for HSLColor { + fn from(c: Color) -> Self { + // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB + + // Maximum of the RGB: color Value (for HSV) + let v: f32 = c.r.max(c.g).max(c.b); + // Minimum of the RGB values + let m: f32 = c.r.min(c.g).min(c.b); + // Chroma + let ch: f32 = v - m; + // Lightness + let l: f32 = (v + m) / 2.0; + + // Determine Hue + let mut h = 0.0f32; + if c.r >= c.g && c.r >= c.b { + h = 60.0 * (c.g - c.b) / ch; + } else if c.g >= c.r && c.g >= c.b { + h = 60.0 * (2.0 + (c.b - c.r) / ch); + } else if c.b >= c.r && c.b >= c.g { + h = 60.0 * (4.0 + (c.r - c.g) / ch); + } + + // Determine saturation + let mut s = 0.0f32; + if l > 0.0 && l < 1.0 { + s = (v - l) / l.min(1.0 - l); + } + + HSLColor::new(h, s, l, c.a) + } +} + +/// Calmps a float value to the range [0.0, 1.0] +pub fn clamp(v: f32) -> f32 { + v.max(0.0f32).min(1.0f32) +} + +/// Calmps a float value to the range [0.0, 360.0] +pub fn clamp_hue(v: f32) -> f32 { + v.max(0.0f32).min(360.0f32) +} From 63933e26d2ffd530fc1d8c9a7d7b94927c0e8cc8 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 12:11:37 -0500 Subject: [PATCH 04/37] Add `palette` dependency behind "colors" feature flag --- Cargo.toml | 3 +++ core/Cargo.toml | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9c52ea8f..7f6e1f0f 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` +colors = ["iced_core/colors"] [badges] maintenance = { status = "actively-developed" } @@ -57,6 +59,7 @@ members = [ ] [dependencies] +iced_core = { version = "0.1", 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..e05f824c 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,4 +7,11 @@ description = "The essential concepts of Iced" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +colors = ["palette"] + [dependencies] + +[dependencies.palette] +version = "0.5.0" +optional = true From 831a07f720d522954a75b159ccc00824f3affee6 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 15:20:47 -0500 Subject: [PATCH 05/37] Conversion to palette's Srgba type --- core/src/color.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/core/src/color.rs b/core/src/color.rs index a46f44ee..ce0ea5ed 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "colors")] +use palette::rgb::Srgba; + /// A color in the sRGB color space. #[derive(Debug, Clone, Copy, PartialEq)] pub struct Color { @@ -94,6 +97,24 @@ impl Color { ] } + #[cfg(feature = "colors")] + /// Convert from palette's [`Srgba`] type to a [`Color`] + /// + /// [`Srgba`]: ../palette/rgb/type.Srgba.html + /// [`Color`]: struct.Color.html + pub fn from_srgba(srgba: Srgba) -> Color { + Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha) + } + + #[cfg(feature = "colors")] + /// Convert from [`Color`] to palette's [`Srgba`] type + /// + /// [`Color`]: struct.Color.html + /// [`Srgba`]: ../palette/rgb/type.Srgba.html + pub fn into_srgba(self) -> Srgba { + Srgba::new(self.r, self.g, self.b, self.a) + } + /// Invert the Color in-place pub fn invert(&mut self) { self.r = clamp(1.0f32 - self.r); @@ -119,6 +140,28 @@ impl From<[f32; 4]> for Color { } } +#[cfg(feature = "colors")] +/// Convert from palette's [`Srgba`] type to a [`Color`] +/// +/// [`Srgba`]: ../palette/rgb/type.Srgba.html +/// [`Color`]: struct.Color.html +impl From for Color { + fn from(srgba: Srgba) -> Self { + Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha) + } +} + +#[cfg(feature = "colors")] +/// Convert from [`Color`] to palette's [`Srgba`] type +/// +/// [`Color`]: struct.Color.html +/// [`Srgba`]: ../palette/rgb/type.Srgba.html +impl From for Srgba { + fn from(c: Color) -> Self { + Srgba::new(c.r, c.g, c.b, c.a) + } +} + impl From for Color { fn from(hsl: HSLColor) -> Self { // Compute Chroma From 4009f0cf73cdf261a3218a44706fdd1434653c8f Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 15:31:44 -0500 Subject: [PATCH 06/37] Remove HSLColor --- core/src/color.rs | 90 ----------------------------------------------- 1 file changed, 90 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index ce0ea5ed..33eeedaf 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -162,97 +162,7 @@ impl From for Srgba { } } -impl From for Color { - fn from(hsl: HSLColor) -> Self { - // Compute Chroma - let ch = (1.0 - (2.0 * hsl.l - 1.0).abs()) * hsl.s; - - // Quantized Hue: H' - let hp: u8 = (hsl.h / 60.0).ceil() as u8; - let x: f32 = ch * f32::from(1 - ((hp % 2) - 1)); - - // Intermediate RGB values - let (r1, g1, b1): (f32, f32, f32) = match hp { - 1 => (ch, x, 0.0), - 2 => (x, ch, 0.0), - 3 => (0.0, ch, x), - 4 => (0.0, x, ch), - 5 => (x, 0.0, ch), - 6 => (ch, 0.0, x), - _ => (0.0, 0.0, 0.0), - }; - - // Match lightness - let m = hsl.l - ch / 2.0; - - Color::new(r1 + m, g1 + m, b1 + m, hsl.a) - } -} - -/// A color in the HSL color space. -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct HSLColor { - /// Hue, 0.0 - 360.0 - pub h: f32, - /// Saturation, 0.0 - 1.0 - pub s: f32, - /// Lightness, 0.0 - 1.0 - pub l: f32, - /// Transparency, 0.0 - 1.0 - pub a: f32, -} - -impl HSLColor { - /// New HSLColor with range checks - pub fn new(h: f32, s: f32, l: f32, a: f32) -> HSLColor { - HSLColor { - h: clamp_hue(h), - s: clamp(s), - l: clamp(l), - a: clamp(a), - } - } -} - -impl From for HSLColor { - fn from(c: Color) -> Self { - // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB - - // Maximum of the RGB: color Value (for HSV) - let v: f32 = c.r.max(c.g).max(c.b); - // Minimum of the RGB values - let m: f32 = c.r.min(c.g).min(c.b); - // Chroma - let ch: f32 = v - m; - // Lightness - let l: f32 = (v + m) / 2.0; - - // Determine Hue - let mut h = 0.0f32; - if c.r >= c.g && c.r >= c.b { - h = 60.0 * (c.g - c.b) / ch; - } else if c.g >= c.r && c.g >= c.b { - h = 60.0 * (2.0 + (c.b - c.r) / ch); - } else if c.b >= c.r && c.b >= c.g { - h = 60.0 * (4.0 + (c.r - c.g) / ch); - } - - // Determine saturation - let mut s = 0.0f32; - if l > 0.0 && l < 1.0 { - s = (v - l) / l.min(1.0 - l); - } - - HSLColor::new(h, s, l, c.a) - } -} - /// Calmps a float value to the range [0.0, 1.0] pub fn clamp(v: f32) -> f32 { v.max(0.0f32).min(1.0f32) } - -/// Calmps a float value to the range [0.0, 360.0] -pub fn clamp_hue(v: f32) -> f32 { - v.max(0.0f32).min(360.0f32) -} From bb443988197ecfbb1effe9ae73ec5533db7e3339 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 16:09:06 -0500 Subject: [PATCH 07/37] Revert from_rgb to const --- core/src/color.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 33eeedaf..4ac2f8a7 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -52,8 +52,8 @@ impl Color { /// Creates a [`Color`] from its RGB components. /// /// [`Color`]: struct.Color.html - pub fn from_rgb(r: f32, g: f32, b: f32) -> Color { - Color::new(r, g, b, 1.0) + pub const fn from_rgb(r: f32, g: f32, b: f32) -> Color { + Color { r, g, b, a: 1.0 } } /// Creates a [`Color`] from its RGB8 components. From fd484c76381cd77fdd485939f6435df115f1ca65 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 16:12:18 -0500 Subject: [PATCH 08/37] Fix docstring typo --- core/src/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/color.rs b/core/src/color.rs index 4ac2f8a7..8a0a26ba 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -162,7 +162,7 @@ impl From for Srgba { } } -/// Calmps a float value to the range [0.0, 1.0] +/// Clamps a float value to the range [0.0, 1.0] pub fn clamp(v: f32) -> f32 { v.max(0.0f32).min(1.0f32) } From 9a4ad3d6a7de5305cc992b140ca208a4277f75ea Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 16:40:44 -0500 Subject: [PATCH 09/37] Use debug assertions instead of clamp --- core/src/color.rs | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 8a0a26ba..b7445a8c 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -41,12 +41,24 @@ impl Color { /// New Color with range checks pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { - Color { - r: clamp(r), - g: clamp(g), - b: clamp(b), - a: clamp(a), - } + debug_assert!( + (0.0f32..=1.0f32).contains(&r), + "Red component must be on [0, 1]" + ); + debug_assert!( + (0.0f32..=1.0f32).contains(&g), + "Green component must be on [0, 1]" + ); + debug_assert!( + (0.0f32..=1.0f32).contains(&b), + "Blue component must be on [0, 1]" + ); + debug_assert!( + (0.0f32..=1.0f32).contains(&a), + "Alpha component must be on [0, 1]" + ); + + Color { r, g, b, a } } /// Creates a [`Color`] from its RGB components. @@ -71,7 +83,7 @@ impl Color { r: f32::from(r) / 255.0, g: f32::from(g) / 255.0, b: f32::from(b) / 255.0, - a: clamp(a), + a, } } @@ -117,9 +129,9 @@ impl Color { /// Invert the Color in-place pub fn invert(&mut self) { - self.r = clamp(1.0f32 - self.r); - self.b = clamp(1.0f32 - self.g); - self.g = clamp(1.0f32 - self.b); + self.r = 1.0f32 - self.r; + self.b = 1.0f32 - self.g; + self.g = 1.0f32 - self.b; } /// Return an inverted Color @@ -161,8 +173,3 @@ impl From for Srgba { Srgba::new(c.r, c.g, c.b, c.a) } } - -/// Clamps a float value to the range [0.0, 1.0] -pub fn clamp(v: f32) -> f32 { - v.max(0.0f32).min(1.0f32) -} From e926e4374242cc590ac507b059e3ce0cfa97d52f Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 16:41:07 -0500 Subject: [PATCH 10/37] Add const from_rgba, for RGBA initialization --- core/src/color.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/color.rs b/core/src/color.rs index b7445a8c..4f0d974b 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -65,7 +65,14 @@ impl Color { /// /// [`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. From 7b15e4b0e29c64d53a00fbe6709ff2069630fec5 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 16:44:02 -0500 Subject: [PATCH 11/37] Feature name colors -> palette --- Cargo.toml | 2 +- core/Cargo.toml | 3 --- core/src/color.rs | 10 +++++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7f6e1f0f..9b88f9ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ 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` -colors = ["iced_core/colors"] +palette = ["iced_core/palette"] [badges] maintenance = { status = "actively-developed" } diff --git a/core/Cargo.toml b/core/Cargo.toml index e05f824c..b52bf315 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -7,9 +7,6 @@ description = "The essential concepts of Iced" license = "MIT" repository = "https://github.com/hecrj/iced" -[features] -colors = ["palette"] - [dependencies] [dependencies.palette] diff --git a/core/src/color.rs b/core/src/color.rs index 4f0d974b..be1a2870 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "colors")] +#[cfg(feature = "palette")] use palette::rgb::Srgba; /// A color in the sRGB color space. @@ -116,7 +116,7 @@ impl Color { ] } - #[cfg(feature = "colors")] + #[cfg(feature = "palette")] /// Convert from palette's [`Srgba`] type to a [`Color`] /// /// [`Srgba`]: ../palette/rgb/type.Srgba.html @@ -125,7 +125,7 @@ impl Color { Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha) } - #[cfg(feature = "colors")] + #[cfg(feature = "palette")] /// Convert from [`Color`] to palette's [`Srgba`] type /// /// [`Color`]: struct.Color.html @@ -159,7 +159,7 @@ impl From<[f32; 4]> for Color { } } -#[cfg(feature = "colors")] +#[cfg(feature = "palette")] /// Convert from palette's [`Srgba`] type to a [`Color`] /// /// [`Srgba`]: ../palette/rgb/type.Srgba.html @@ -170,7 +170,7 @@ impl From for Color { } } -#[cfg(feature = "colors")] +#[cfg(feature = "palette")] /// Convert from [`Color`] to palette's [`Srgba`] type /// /// [`Color`]: struct.Color.html From 408e9e566f740a4a9eb564492e96714dd5db4cc3 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Tue, 31 Mar 2020 17:36:09 -0500 Subject: [PATCH 12/37] Add palette test for Color <-> Srgba & manipulation --- core/src/color.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/src/color.rs b/core/src/color.rs index be1a2870..67433ded 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -180,3 +180,44 @@ impl From for Srgba { Srgba::new(c.r, c.g, c.b, c.a) } } + +#[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 = c1.into_srgba().into_linear(); + let l2 = c2.into_srgba().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 + } + ); + } +} From a95d494f707b9492d180b41bd93565b21c729dd8 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Wed, 1 Apr 2020 16:15:29 -0500 Subject: [PATCH 13/37] Remove redundant from_srgba and into_srgba methods --- core/src/color.rs | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 67433ded..57765df0 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -116,24 +116,6 @@ impl Color { ] } - #[cfg(feature = "palette")] - /// Convert from palette's [`Srgba`] type to a [`Color`] - /// - /// [`Srgba`]: ../palette/rgb/type.Srgba.html - /// [`Color`]: struct.Color.html - pub fn from_srgba(srgba: Srgba) -> Color { - Color::new(srgba.red, srgba.green, srgba.blue, srgba.alpha) - } - - #[cfg(feature = "palette")] - /// Convert from [`Color`] to palette's [`Srgba`] type - /// - /// [`Color`]: struct.Color.html - /// [`Srgba`]: ../palette/rgb/type.Srgba.html - pub fn into_srgba(self) -> Srgba { - Srgba::new(self.r, self.g, self.b, self.a) - } - /// Invert the Color in-place pub fn invert(&mut self) { self.r = 1.0f32 - self.r; @@ -202,8 +184,8 @@ mod tests { let c2 = Color::from_rgb(0.2, 0.5, 0.3); // Convert to linear color for manipulation - let l1 = c1.into_srgba().into_linear(); - let l2 = c2.into_srgba().into_linear(); + 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); From 56ce01e262832b78530b0721f735a95919651d91 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Wed, 1 Apr 2020 16:17:46 -0500 Subject: [PATCH 14/37] Simplify range declaration --- core/src/color.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index 57765df0..eff14948 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -42,19 +42,19 @@ impl Color { /// New Color with range checks pub fn new(r: f32, g: f32, b: f32, a: f32) -> Color { debug_assert!( - (0.0f32..=1.0f32).contains(&r), + (0.0..=1.0).contains(&r), "Red component must be on [0, 1]" ); debug_assert!( - (0.0f32..=1.0f32).contains(&g), + (0.0..=1.0).contains(&g), "Green component must be on [0, 1]" ); debug_assert!( - (0.0f32..=1.0f32).contains(&b), + (0.0..=1.0).contains(&b), "Blue component must be on [0, 1]" ); debug_assert!( - (0.0f32..=1.0f32).contains(&a), + (0.0..=1.0).contains(&a), "Alpha component must be on [0, 1]" ); From ea3b7b528275c7ae8a336004ad77f85341599335 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 2 Apr 2020 15:24:40 -0500 Subject: [PATCH 15/37] Derive Default for Color --- core/src/color.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/color.rs b/core/src/color.rs index eff14948..56d5455f 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -2,7 +2,7 @@ use palette::rgb::Srgba; /// A color in the sRGB color space. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct Color { /// Red component, 0.0 - 1.0 pub r: f32, From 04be010fbdf84300531b806fa8855f57bbf727b7 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 2 Apr 2020 17:29:26 -0500 Subject: [PATCH 16/37] Conversion traits for palette::Srgb --- core/src/color.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/color.rs b/core/src/color.rs index 56d5455f..c061add6 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -1,5 +1,5 @@ #[cfg(feature = "palette")] -use palette::rgb::Srgba; +use palette::rgb::{Srgb, Srgba}; /// A color in the sRGB color space. #[derive(Debug, Clone, Copy, PartialEq, Default)] @@ -163,6 +163,28 @@ impl From for Srgba { } } +#[cfg(feature = "palette")] +/// Convert from palette's [`Srgb`] type to a [`Color`] +/// +/// [`Srgb`]: ../palette/rgb/type.Srgb.html +/// [`Color`]: struct.Color.html +impl From for Color { + fn from(srgb: Srgb) -> Self { + Color::new(srgb.red, srgb.green, srgb.blue, 1.0) + } +} + +#[cfg(feature = "palette")] +/// Convert from [`Color`] to palette's [`Srgb`] type +/// +/// [`Color`]: struct.Color.html +/// [`Srgb`]: ../palette/rgb/type.Srgb.html +impl From for Srgb { + fn from(c: Color) -> Self { + Srgb::new(c.r, c.g, c.b) + } +} + #[cfg(feature = "palette")] #[cfg(test)] mod tests { From 71657b50dd69d860663051c588ff643242e971a7 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 2 Apr 2020 17:49:23 -0500 Subject: [PATCH 17/37] Conditional re-export of palette from iced_core --- core/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/lib.rs b/core/src/lib.rs index c2887a0b..ca6013da 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -35,3 +35,6 @@ pub use point::Point; pub use rectangle::Rectangle; pub use size::Size; pub use vector::Vector; + +#[cfg(feature = "palette")] +pub use palette; From 664a63a4b8c1b0b945ca45b1181ead040a12fa73 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 2 Apr 2020 17:52:21 -0500 Subject: [PATCH 18/37] Add example program: color palette Sliders for many color spaces update as any other sliders are moved around. Color is space is clamped to sRGB, so Lab and Lch color spaces cannot be fully expressed. TODO: - Real-time manipulation of base color to create a color scheme. - Show slider value under each slider - Show output values in text boxes for each color space --- Cargo.toml | 1 + examples/color_palette/Cargo.toml | 14 ++ examples/color_palette/README.md | 9 ++ examples/color_palette/src/main.rs | 250 +++++++++++++++++++++++++++++ 4 files changed, 274 insertions(+) create mode 100644 examples/color_palette/Cargo.toml create mode 100644 examples/color_palette/README.md create mode 100644 examples/color_palette/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9b88f9ec..8f0a95c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ members = [ "winit", "examples/bezier_tool", "examples/clock", + "examples/color_palette", "examples/counter", "examples/custom_widget", "examples/download_progress", diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml new file mode 100644 index 00000000..0ad6708c --- /dev/null +++ b/examples/color_palette/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "color_palette" +version = "0.1.0" +authors = ["Clark Moody "] +edition = "2018" +publish = false + +[features] +palette = [] + +[dependencies] +iced = { path = "../..", features = ["palette"] } +iced_core = { path = "../../core" } +iced_native = { path = "../../native" } diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md new file mode 100644 index 00000000..b646f3b3 --- /dev/null +++ b/examples/color_palette/README.md @@ -0,0 +1,9 @@ +## Color Palette + +A color palette generator, based on a user-defined root color. + +You can run it with `cargo run`: + +``` +cargo run --package color_palette +``` diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs new file mode 100644 index 00000000..1c9fffbc --- /dev/null +++ b/examples/color_palette/src/main.rs @@ -0,0 +1,250 @@ +use iced::{ + slider, Color, Column, Element, Row, Sandbox, Settings, Slider, Text, +}; +use iced_core::palette::{self, Limited}; + +pub fn main() { + ColorPalette::run(Settings::default()) +} + +#[derive(Default)] +pub struct ColorPalette { + base_color: Color, + 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], +} + +#[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 { + let mut s = Self::default(); + s.base_color = Color::from_rgb8(27, 135, 199); + s + } + + fn title(&self) -> String { + String::from("Color Palette") + } + + fn update(&mut self, message: Message) { + let mut 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), + }; + srgb.clamp_self(); + self.base_color = Color::from(srgb); + } + + 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.base_color; + let srgb = palette::Srgb::from(self.base_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); + + Column::new() + .padding(20) + .spacing(20) + .push( + Row::new() + .spacing(10) + .push(Text::new("RGB")) + .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( + Row::new() + .spacing(10) + .push(Text::new("HSL")) + .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( + Row::new() + .spacing(10) + .push(Text::new("HSV")) + .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( + Row::new() + .spacing(10) + .push(Text::new("HWB")) + .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( + Row::new() + .spacing(10) + .push(Text::new("Lab")) + .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( + Row::new() + .spacing(10) + .push(Text::new("Lch")) + .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 + }) + }, + )), + ) + .into() + } +} From 6b18e78e535d50f648bd5ba739eb29b3c76a7965 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Wed, 8 Apr 2020 17:45:54 -0500 Subject: [PATCH 19/37] Use canvas to draw color palette for example --- Cargo.toml | 2 +- examples/color_palette/Cargo.toml | 2 +- examples/color_palette/src/main.rs | 106 ++++++++++++++++++++++++++--- 3 files changed, 99 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8f0a95c3..206409bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ members = [ ] [dependencies] -iced_core = { version = "0.1", path = "core" } +iced_core = { version = "0.2", path = "core" } iced_futures = { version = "0.1", path = "futures" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml index 0ad6708c..ad7a0114 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -9,6 +9,6 @@ publish = false palette = [] [dependencies] -iced = { path = "../..", features = ["palette"] } +iced = { path = "../..", features = ["canvas", "palette"] } iced_core = { path = "../../core" } iced_native = { path = "../../native" } diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 1c9fffbc..267cc58c 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,5 +1,6 @@ use iced::{ - slider, Color, Column, Element, Row, Sandbox, Settings, Slider, Text, + canvas, slider, Canvas, Color, Column, Element, Length, Point, Row, + Sandbox, Settings, Slider, Text, }; use iced_core::palette::{self, Limited}; @@ -7,15 +8,39 @@ pub fn main() { ColorPalette::run(Settings::default()) } -#[derive(Default)] +#[derive(Debug, Default)] +pub struct State { + color: Color, + theme: Vec, +} + +fn generate_theme(base_color: &Color) -> Vec { + use palette::{Hsl, Hue, Shade, Srgb}; + let mut theme = Vec::::new(); + // Convert to linear color for manipulation + let srgb = Srgb::from(*base_color); + + let hsl = Hsl::from(srgb); + + theme.push(Srgb::from(hsl.shift_hue(-120.0)).clamp().into()); + theme.push(Srgb::from(hsl.shift_hue(-115.0).darken(0.075)).clamp().into()); + theme.push(Srgb::from(hsl.darken(0.075)).clamp().into()); + theme.push(*base_color); + theme.push(Srgb::from(hsl.lighten(0.075)).clamp().into()); + theme.push(Srgb::from(hsl.shift_hue(115.0).darken(0.075)).clamp().into()); + theme.push(Srgb::from(hsl.shift_hue(120.0)).clamp().into()); + theme +} + pub struct ColorPalette { - base_color: Color, + 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], + canvas_layer: canvas::layer::Cache, } #[derive(Debug, Clone, Copy)] @@ -32,9 +57,24 @@ impl Sandbox for ColorPalette { type Message = Message; fn new() -> Self { - let mut s = Self::default(); - s.base_color = Color::from_rgb8(27, 135, 199); - s + fn triple_slider() -> [slider::State; 3] { + [ + slider::State::new(), + slider::State::new(), + slider::State::new(), + ] + } + + ColorPalette { + state: State::new(), + rgb_sliders: triple_slider(), + hsl_sliders: triple_slider(), + hsv_sliders: triple_slider(), + hwb_sliders: triple_slider(), + lab_sliders: triple_slider(), + lch_sliders: triple_slider(), + canvas_layer: canvas::layer::Cache::new(), + } } fn title(&self) -> String { @@ -51,7 +91,11 @@ impl Sandbox for ColorPalette { Message::LchColorChanged(lch) => palette::Srgb::from(lch), }; srgb.clamp_self(); - self.base_color = Color::from(srgb); + self.canvas_layer.clear(); + self.state.color = Color::from(srgb); + + // Set theme colors + self.state.theme = generate_theme(&self.state.color); } fn view(&mut self) -> Element { @@ -62,8 +106,8 @@ impl Sandbox for ColorPalette { let [lab1, lab2, lab3] = &mut self.lab_sliders; let [lch1, lch2, lch3] = &mut self.lch_sliders; - let color = self.base_color; - let srgb = palette::Srgb::from(self.base_color); + let color = self.state.color; + let srgb = palette::Srgb::from(self.state.color); let hsl = palette::Hsl::from(srgb); let hsv = palette::Hsv::from(srgb); let hwb = palette::Hwb::from(srgb); @@ -245,6 +289,50 @@ impl Sandbox for ColorPalette { }, )), ) + .push( + Canvas::new() + .width(Length::Fill) + .height(Length::Units(150)) + .push(self.canvas_layer.with(&self.state)), + ) .into() } } + +impl State { + pub fn new() -> State { + let base = Color::from_rgb8(27, 135, 199); + State { + color: base, + theme: generate_theme(&base), + } + } +} + +impl canvas::Drawable for State { + fn draw(&self, frame: &mut canvas::Frame) { + use canvas::{Fill, Path}; + if self.theme.len() == 0 { + println!("Zero len"); + return; + } + + let box_width = frame.width() / self.theme.len() as f32; + for i in 0..self.theme.len() { + let anchor = Point { + x: (i as f32) * box_width, + y: 0.0, + }; + let rect = Path::new(|path| { + path.move_to(anchor); + path.line_to(Point { x: anchor.x + box_width, y: anchor.y }); + path.line_to(Point { + x: anchor.x + box_width, + y: anchor.y + frame.height(), + }); + path.line_to(Point { x: anchor.x, y: anchor.y + frame.height() }); + }); + frame.fill(&rect, Fill::Color(self.theme[i])); + } + } +} From b1328f193cceb803e81e59230ff4ca89072ef5a5 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 9 Apr 2020 13:11:39 -0500 Subject: [PATCH 20/37] More theme colors and gradient of lightness --- examples/color_palette/src/main.rs | 76 ++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 9 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 267cc58c..f7918df4 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -17,18 +17,34 @@ pub struct State { fn generate_theme(base_color: &Color) -> Vec { use palette::{Hsl, Hue, Shade, Srgb}; let mut theme = Vec::::new(); - // Convert to linear color for manipulation - let srgb = Srgb::from(*base_color); - - let hsl = Hsl::from(srgb); + // Convert to HSL color for manipulation + let hsl = Hsl::from(Srgb::from(*base_color)); + theme.push( + Srgb::from(hsl.shift_hue(-135.0).lighten(0.075)) + .clamp() + .into(), + ); theme.push(Srgb::from(hsl.shift_hue(-120.0)).clamp().into()); - theme.push(Srgb::from(hsl.shift_hue(-115.0).darken(0.075)).clamp().into()); + theme.push( + Srgb::from(hsl.shift_hue(-105.0).darken(0.075)) + .clamp() + .into(), + ); theme.push(Srgb::from(hsl.darken(0.075)).clamp().into()); theme.push(*base_color); theme.push(Srgb::from(hsl.lighten(0.075)).clamp().into()); - theme.push(Srgb::from(hsl.shift_hue(115.0).darken(0.075)).clamp().into()); + theme.push( + Srgb::from(hsl.shift_hue(105.0).darken(0.075)) + .clamp() + .into(), + ); theme.push(Srgb::from(hsl.shift_hue(120.0)).clamp().into()); + theme.push( + Srgb::from(hsl.shift_hue(135.0).lighten(0.075)) + .clamp() + .into(), + ); theme } @@ -312,12 +328,17 @@ impl State { impl canvas::Drawable for State { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Fill, Path}; + use palette::{Hsl, Srgb}; + if self.theme.len() == 0 { println!("Zero len"); return; } + let pad = 5.0; + let box_width = frame.width() / self.theme.len() as f32; + let box_height = frame.height() / 2.0 - pad; for i in 0..self.theme.len() { let anchor = Point { x: (i as f32) * box_width, @@ -325,14 +346,51 @@ impl canvas::Drawable for State { }; let rect = Path::new(|path| { path.move_to(anchor); - path.line_to(Point { x: anchor.x + box_width, y: anchor.y }); path.line_to(Point { x: anchor.x + box_width, - y: anchor.y + frame.height(), + y: anchor.y, + }); + path.line_to(Point { + x: anchor.x + box_width, + y: anchor.y + box_height, + }); + path.line_to(Point { + x: anchor.x, + y: anchor.y + box_height, }); - path.line_to(Point { x: anchor.x, y: anchor.y + frame.height() }); }); frame.fill(&rect, Fill::Color(self.theme[i])); } + + let hsl = Hsl::from(Srgb::from(self.color)); + for i in 0..self.theme.len() { + let pct = (i as f32 + 1.0) / (self.theme.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_width, + y: box_height + 2.0 * pad, + }; + let rect = Path::new(|path| { + path.move_to(anchor); + path.line_to(Point { + x: anchor.x + box_width, + y: anchor.y, + }); + path.line_to(Point { + x: anchor.x + box_width, + y: anchor.y + box_height, + }); + path.line_to(Point { + x: anchor.x, + y: anchor.y + box_height, + }); + }); + frame.fill(&rect, Fill::Color(color)); + } } } From 39fd8ad9e973b8f6ec9e4e4d08f4e8aca72b069e Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Thu, 9 Apr 2020 17:49:29 -0500 Subject: [PATCH 21/37] TextInput fields with color encodings. Draw shades. --- examples/color_palette/src/main.rs | 287 ++++++++++++++++++++++++++--- 1 file changed, 266 insertions(+), 21 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index f7918df4..fc733787 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,11 +1,14 @@ use iced::{ - canvas, slider, Canvas, Color, Column, Element, Length, Point, Row, - Sandbox, Settings, Slider, Text, + canvas, slider, text_input, Canvas, Color, Column, Element, Length, Point, + Row, Sandbox, Settings, Slider, Text, TextInput, }; use iced_core::palette::{self, Limited}; pub fn main() { - ColorPalette::run(Settings::default()) + ColorPalette::run(Settings { + antialiasing: true, + ..Settings::default() + }) } #[derive(Debug, Default)] @@ -56,6 +59,18 @@ pub struct ColorPalette { hwb_sliders: [slider::State; 3], lab_sliders: [slider::State; 3], lch_sliders: [slider::State; 3], + rgb_text_state: text_input::State, + hsl_text_state: text_input::State, + hsv_text_state: text_input::State, + hwb_text_state: text_input::State, + lab_text_state: text_input::State, + lch_text_state: text_input::State, + rgb_text_value: String, + hsl_text_value: String, + hsv_text_value: String, + hwb_text_value: String, + lab_text_value: String, + lch_text_value: String, canvas_layer: canvas::layer::Cache, } @@ -67,6 +82,7 @@ pub enum Message { HwbColorChanged(palette::Hwb), LabColorChanged(palette::Lab), LchColorChanged(palette::Lch), + TextInput, } impl Sandbox for ColorPalette { @@ -81,14 +97,34 @@ 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: State::new(), + 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_state: text_input::State::new(), + hsl_text_state: text_input::State::new(), + hsv_text_state: text_input::State::new(), + hwb_text_state: text_input::State::new(), + lab_text_state: text_input::State::new(), + lch_text_state: text_input::State::new(), + rgb_text_value, + hsl_text_value, + hsv_text_value, + hwb_text_value, + lab_text_value, + lch_text_value, canvas_layer: canvas::layer::Cache::new(), } } @@ -98,6 +134,11 @@ impl Sandbox for ColorPalette { } fn update(&mut self, message: Message) { + match message { + Message::TextInput => return, + _ => {} + } + let mut srgb = match message { Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb), Message::HslColorChanged(hsl) => palette::Srgb::from(hsl), @@ -105,6 +146,7 @@ impl Sandbox for ColorPalette { Message::HwbColorChanged(hwb) => palette::Srgb::from(hwb), Message::LabColorChanged(lab) => palette::Srgb::from(lab), Message::LchColorChanged(lch) => palette::Srgb::from(lch), + _ => return, }; srgb.clamp_self(); self.canvas_layer.clear(); @@ -112,6 +154,14 @@ 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 { @@ -131,12 +181,12 @@ impl Sandbox for ColorPalette { let lch = palette::Lch::from(srgb); Column::new() - .padding(20) - .spacing(20) + .padding(10) + .spacing(10) .push( Row::new() .spacing(10) - .push(Text::new("RGB")) + .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 }) })) @@ -145,12 +195,23 @@ impl Sandbox for ColorPalette { })) .push(Slider::new(rgb3, 0.0..=1.0, color.b, move |b| { Message::RgbColorChanged(Color { b, ..color }) - })), + })) + .push( + TextInput::new( + &mut self.rgb_text_state, + "", + &mut self.rgb_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Row::new() .spacing(10) - .push(Text::new("HSL")) + .push(Text::new("HSL").width(Length::Units(50))) .push(Slider::new( hsl1, 0.0..=360.0, @@ -183,12 +244,23 @@ impl Sandbox for ColorPalette { ..hsl }) }, - )), + )) + .push( + TextInput::new( + &mut self.hsl_text_state, + "", + &mut self.hsl_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Row::new() .spacing(10) - .push(Text::new("HSV")) + .push(Text::new("HSV").width(Length::Units(50))) .push(Slider::new( hsv1, 0.0..=360.0, @@ -221,12 +293,23 @@ impl Sandbox for ColorPalette { ..hsv }) }, - )), + )) + .push( + TextInput::new( + &mut self.hsv_text_state, + "", + &mut self.hsv_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Row::new() .spacing(10) - .push(Text::new("HWB")) + .push(Text::new("HWB").width(Length::Units(50))) .push(Slider::new( hwb1, 0.0..=360.0, @@ -259,12 +342,23 @@ impl Sandbox for ColorPalette { ..hwb }) }, - )), + )) + .push( + TextInput::new( + &mut self.hwb_text_state, + "", + &mut self.hwb_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Row::new() .spacing(10) - .push(Text::new("Lab")) + .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 }) })) @@ -273,12 +367,23 @@ impl Sandbox for ColorPalette { })) .push(Slider::new(lab3, -128.0..=127.0, lab.b, move |b| { Message::LabColorChanged(palette::Lab { b, ..lab }) - })), + })) + .push( + TextInput::new( + &mut self.lab_text_state, + "", + &mut self.lab_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Row::new() .spacing(10) - .push(Text::new("Lch")) + .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 }) })) @@ -303,12 +408,24 @@ impl Sandbox for ColorPalette { ..lch }) }, - )), + )) + .push( + TextInput::new( + &mut self.lch_text_state, + "", + &mut self.lch_text_value, + |_s| Message::TextInput, + ) + .width(Length::Units(150)) + .size(14) + .padding(2), + ), ) .push( Canvas::new() .width(Length::Fill) - .height(Length::Units(150)) + // .height(Length::Units(250)) + .height(Length::Fill) .push(self.canvas_layer.with(&self.state)), ) .into() @@ -317,7 +434,7 @@ impl Sandbox for ColorPalette { impl State { pub fn new() -> State { - let base = Color::from_rgb8(27, 135, 199); + let base = Color::from_rgb8(75, 128, 190); State { color: base, theme: generate_theme(&base), @@ -328,6 +445,7 @@ impl State { impl canvas::Drawable for State { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Fill, Path}; + use iced::{HorizontalAlignment, VerticalAlignment}; use palette::{Hsl, Srgb}; if self.theme.len() == 0 { @@ -335,10 +453,16 @@ impl canvas::Drawable for State { return; } - let pad = 5.0; + let pad = 20.0; let box_width = frame.width() / self.theme.len() as f32; let box_height = frame.height() / 2.0 - pad; + + let mut text = canvas::Text::default(); + text.horizontal_alignment = HorizontalAlignment::Left; + text.vertical_alignment = VerticalAlignment::Top; + text.size = 15.0; + for i in 0..self.theme.len() { let anchor = Point { x: (i as f32) * box_width, @@ -360,6 +484,57 @@ impl canvas::Drawable for State { }); }); frame.fill(&rect, Fill::Color(self.theme[i])); + + if self.theme[i] == self.color { + let cx = anchor.x + box_width / 2.0; + let tri_w = 10.0; + + let tri = Path::new(|path| { + path.move_to(Point { + x: cx - tri_w, + y: 0.0, + }); + path.line_to(Point { + x: cx + tri_w, + y: 0.0, + }); + path.line_to(Point { x: cx, y: tri_w }); + path.line_to(Point { + x: cx - tri_w, + y: 0.0, + }); + }); + frame.fill(&tri, Fill::Color(Color::WHITE)); + + let tri = Path::new(|path| { + path.move_to(Point { + x: cx - tri_w, + y: box_height, + }); + path.line_to(Point { + x: cx + tri_w, + y: box_height, + }); + path.line_to(Point { + x: cx, + y: box_height - tri_w, + }); + path.line_to(Point { + x: cx - tri_w, + y: box_height, + }); + }); + frame.fill(&tri, Fill::Color(Color::WHITE)); + } + + frame.fill_text(canvas::Text { + content: color_str(&self.theme[i], ColorFormat::Hex), + position: Point { + x: anchor.x, + y: box_height, + }, + ..text + }); } let hsl = Hsl::from(Srgb::from(self.color)); @@ -391,6 +566,76 @@ impl canvas::Drawable for State { }); }); frame.fill(&rect, Fill::Color(color)); + + frame.fill_text(canvas::Text { + content: color_str(&color, ColorFormat::Hex), + position: Point { + x: anchor.x, + y: box_height + 2.0 * pad - 15.0, + }, + ..text + }); } } } + +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() + ), + } +} From 4b90241ea1d2139464587ce8475aeebbf283abc7 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 10 Apr 2020 14:59:57 -0500 Subject: [PATCH 22/37] Hex label text alignment --- examples/color_palette/src/main.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index fc733787..b80db299 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -459,7 +459,7 @@ impl canvas::Drawable for State { let box_height = frame.height() / 2.0 - pad; let mut text = canvas::Text::default(); - text.horizontal_alignment = HorizontalAlignment::Left; + text.horizontal_alignment = HorizontalAlignment::Center; text.vertical_alignment = VerticalAlignment::Top; text.size = 15.0; @@ -530,13 +530,15 @@ impl canvas::Drawable for State { frame.fill_text(canvas::Text { content: color_str(&self.theme[i], ColorFormat::Hex), position: Point { - x: anchor.x, + x: anchor.x + box_width / 2.0, y: box_height, }, ..text }); } + text.vertical_alignment = VerticalAlignment::Bottom; + let hsl = Hsl::from(Srgb::from(self.color)); for i in 0..self.theme.len() { let pct = (i as f32 + 1.0) / (self.theme.len() as f32 + 1.0); @@ -570,8 +572,8 @@ impl canvas::Drawable for State { frame.fill_text(canvas::Text { content: color_str(&color, ColorFormat::Hex), position: Point { - x: anchor.x, - y: box_height + 2.0 * pad - 15.0, + x: anchor.x + box_width / 2.0, + y: box_height + 2.0 * pad, }, ..text }); From 27fadad3246d555f52b991230a0352353d6700b4 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 24 Apr 2020 15:20:00 -0500 Subject: [PATCH 23/37] Do not re-export Palette from iced_core --- core/src/lib.rs | 3 --- examples/color_palette/Cargo.toml | 4 +--- examples/color_palette/src/main.rs | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index ca6013da..c2887a0b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -35,6 +35,3 @@ pub use point::Point; pub use rectangle::Rectangle; pub use size::Size; pub use vector::Vector; - -#[cfg(feature = "palette")] -pub use palette; diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml index ad7a0114..61c9f6b2 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -5,10 +5,8 @@ authors = ["Clark Moody "] edition = "2018" publish = false -[features] -palette = [] - [dependencies] iced = { path = "../..", features = ["canvas", "palette"] } iced_core = { path = "../../core" } iced_native = { path = "../../native" } +palette = "0.5.0" diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index b80db299..576a0e64 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -2,7 +2,7 @@ use iced::{ canvas, slider, text_input, Canvas, Color, Column, Element, Length, Point, Row, Sandbox, Settings, Slider, Text, TextInput, }; -use iced_core::palette::{self, Limited}; +use palette::{self, Limited}; pub fn main() { ColorPalette::run(Settings { From 758a444d7f11809959aa73d7da32f06e98ecc89b Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 24 Apr 2020 15:31:12 -0500 Subject: [PATCH 24/37] Replace text input fields for simple text --- examples/color_palette/src/main.rs | 95 +++++++----------------------- 1 file changed, 20 insertions(+), 75 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 576a0e64..464dc828 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - canvas, slider, text_input, Canvas, Color, Column, Element, Length, Point, - Row, Sandbox, Settings, Slider, Text, TextInput, + canvas, slider, Canvas, Color, Column, Element, Length, Point, Row, + Sandbox, Settings, Slider, Text, }; use palette::{self, Limited}; @@ -59,12 +59,6 @@ pub struct ColorPalette { hwb_sliders: [slider::State; 3], lab_sliders: [slider::State; 3], lch_sliders: [slider::State; 3], - rgb_text_state: text_input::State, - hsl_text_state: text_input::State, - hsv_text_state: text_input::State, - hwb_text_state: text_input::State, - lab_text_state: text_input::State, - lch_text_state: text_input::State, rgb_text_value: String, hsl_text_value: String, hsv_text_value: String, @@ -82,7 +76,6 @@ pub enum Message { HwbColorChanged(palette::Hwb), LabColorChanged(palette::Lab), LchColorChanged(palette::Lch), - TextInput, } impl Sandbox for ColorPalette { @@ -113,12 +106,6 @@ impl Sandbox for ColorPalette { hwb_sliders: triple_slider(), lab_sliders: triple_slider(), lch_sliders: triple_slider(), - rgb_text_state: text_input::State::new(), - hsl_text_state: text_input::State::new(), - hsv_text_state: text_input::State::new(), - hwb_text_state: text_input::State::new(), - lab_text_state: text_input::State::new(), - lch_text_state: text_input::State::new(), rgb_text_value, hsl_text_value, hsv_text_value, @@ -134,11 +121,6 @@ impl Sandbox for ColorPalette { } fn update(&mut self, message: Message) { - match message { - Message::TextInput => return, - _ => {} - } - let mut srgb = match message { Message::RgbColorChanged(rgb) => palette::Srgb::from(rgb), Message::HslColorChanged(hsl) => palette::Srgb::from(hsl), @@ -146,7 +128,6 @@ impl Sandbox for ColorPalette { Message::HwbColorChanged(hwb) => palette::Srgb::from(hwb), Message::LabColorChanged(lab) => palette::Srgb::from(lab), Message::LchColorChanged(lch) => palette::Srgb::from(lch), - _ => return, }; srgb.clamp_self(); self.canvas_layer.clear(); @@ -197,15 +178,9 @@ impl Sandbox for ColorPalette { Message::RgbColorChanged(Color { b, ..color }) })) .push( - TextInput::new( - &mut self.rgb_text_state, - "", - &mut self.rgb_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.rgb_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( @@ -246,15 +221,9 @@ impl Sandbox for ColorPalette { }, )) .push( - TextInput::new( - &mut self.hsl_text_state, - "", - &mut self.hsl_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.hsl_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( @@ -295,15 +264,9 @@ impl Sandbox for ColorPalette { }, )) .push( - TextInput::new( - &mut self.hsv_text_state, - "", - &mut self.hsv_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.hsv_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( @@ -344,15 +307,9 @@ impl Sandbox for ColorPalette { }, )) .push( - TextInput::new( - &mut self.hwb_text_state, - "", - &mut self.hwb_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.hwb_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( @@ -369,15 +326,9 @@ impl Sandbox for ColorPalette { Message::LabColorChanged(palette::Lab { b, ..lab }) })) .push( - TextInput::new( - &mut self.lab_text_state, - "", - &mut self.lab_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.lab_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( @@ -410,15 +361,9 @@ impl Sandbox for ColorPalette { }, )) .push( - TextInput::new( - &mut self.lch_text_state, - "", - &mut self.lch_text_value, - |_s| Message::TextInput, - ) - .width(Length::Units(150)) - .size(14) - .padding(2), + Text::new(&self.lch_text_value) + .width(Length::Units(185)) + .size(16), ), ) .push( From 3e71eaee37bc3aea85feb0f643dcbd4ecc11d0c4 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Fri, 24 Apr 2020 15:40:28 -0500 Subject: [PATCH 25/37] Use Path::rectangle and Size for drawing swatches --- examples/color_palette/src/main.rs | 63 ++++++++++-------------------- 1 file changed, 21 insertions(+), 42 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 464dc828..76a6bf17 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - canvas, slider, Canvas, Color, Column, Element, Length, Point, Row, - Sandbox, Settings, Slider, Text, + canvas, slider, Canvas, Color, Column, Element, Length, Row, Sandbox, + Settings, Slider, Text, }; use palette::{self, Limited}; @@ -391,6 +391,7 @@ impl canvas::Drawable for State { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Fill, Path}; use iced::{HorizontalAlignment, VerticalAlignment}; + use iced_native::{Point, Size}; use palette::{Hsl, Srgb}; if self.theme.len() == 0 { @@ -400,8 +401,10 @@ impl canvas::Drawable for State { let pad = 20.0; - let box_width = frame.width() / self.theme.len() as f32; - let box_height = frame.height() / 2.0 - pad; + let box_size = Size { + width: frame.width() / self.theme.len() as f32, + height: frame.height() / 2.0 - pad, + }; let mut text = canvas::Text::default(); text.horizontal_alignment = HorizontalAlignment::Center; @@ -410,28 +413,16 @@ impl canvas::Drawable for State { for i in 0..self.theme.len() { let anchor = Point { - x: (i as f32) * box_width, + x: (i as f32) * box_size.width, y: 0.0, }; let rect = Path::new(|path| { - path.move_to(anchor); - path.line_to(Point { - x: anchor.x + box_width, - y: anchor.y, - }); - path.line_to(Point { - x: anchor.x + box_width, - y: anchor.y + box_height, - }); - path.line_to(Point { - x: anchor.x, - y: anchor.y + box_height, - }); + path.rectangle(anchor, box_size); }); frame.fill(&rect, Fill::Color(self.theme[i])); if self.theme[i] == self.color { - let cx = anchor.x + box_width / 2.0; + let cx = anchor.x + box_size.width / 2.0; let tri_w = 10.0; let tri = Path::new(|path| { @@ -454,19 +445,19 @@ impl canvas::Drawable for State { let tri = Path::new(|path| { path.move_to(Point { x: cx - tri_w, - y: box_height, + y: box_size.height, }); path.line_to(Point { x: cx + tri_w, - y: box_height, + y: box_size.height, }); path.line_to(Point { x: cx, - y: box_height - tri_w, + y: box_size.height - tri_w, }); path.line_to(Point { x: cx - tri_w, - y: box_height, + y: box_size.height, }); }); frame.fill(&tri, Fill::Color(Color::WHITE)); @@ -475,8 +466,8 @@ impl canvas::Drawable for State { frame.fill_text(canvas::Text { content: color_str(&self.theme[i], ColorFormat::Hex), position: Point { - x: anchor.x + box_width / 2.0, - y: box_height, + x: anchor.x + box_size.width / 2.0, + y: box_size.height, }, ..text }); @@ -494,31 +485,19 @@ impl canvas::Drawable for State { let color: Color = Srgb::from(graded.clamp()).into(); let anchor = Point { - x: (i as f32) * box_width, - y: box_height + 2.0 * pad, + x: (i as f32) * box_size.width, + y: box_size.height + 2.0 * pad, }; let rect = Path::new(|path| { - path.move_to(anchor); - path.line_to(Point { - x: anchor.x + box_width, - y: anchor.y, - }); - path.line_to(Point { - x: anchor.x + box_width, - y: anchor.y + box_height, - }); - path.line_to(Point { - x: anchor.x, - y: anchor.y + box_height, - }); + path.rectangle(anchor, box_size); }); frame.fill(&rect, Fill::Color(color)); frame.fill_text(canvas::Text { content: color_str(&color, ColorFormat::Hex), position: Point { - x: anchor.x + box_width / 2.0, - y: box_height + 2.0 * pad, + x: anchor.x + box_size.width / 2.0, + y: box_size.height + 2.0 * pad, }, ..text }); From 430f78a693a87e9ba3ac4638cac96aab57dd3042 Mon Sep 17 00:00:00 2001 From: Clark Moody Date: Mon, 27 Apr 2020 16:25:13 -0500 Subject: [PATCH 26/37] Abstract into ColorPicker and ColorSpace trait Each color type implements ColorSpace to define its own representation and update methods. View sliders are implemented on the ColorPicker struct. --- examples/color_palette/src/main.rs | 635 +++++++++++++++-------------- 1 file changed, 323 insertions(+), 312 deletions(-) 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 + ) } From 11e4039b5644606e40d603397f1039686ecd6fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 21:43:11 +0200 Subject: [PATCH 27/37] Remove `update_component` in `color_palette` We can use `ColorSpace::new` instead --- examples/color_palette/src/main.rs | 96 ++---------------------------- 1 file changed, 4 insertions(+), 92 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 993b7fb0..46a4d085 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -66,8 +66,6 @@ trait ColorSpace: Sized { fn components(&self) -> [f32; 3]; - fn update_component(c: Self, i: usize, val: f32) -> Self; - fn to_string(&self) -> String; } @@ -76,18 +74,13 @@ impl ColorPicker { 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(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)) @@ -110,15 +103,6 @@ impl ColorSpace for Color { [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})", @@ -150,24 +134,6 @@ impl ColorSpace for palette::Hsl { ] } - 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}%)", @@ -191,21 +157,6 @@ impl ColorSpace for palette::Hsv { [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}%)", @@ -237,24 +188,6 @@ impl ColorSpace for palette::Hwb { ] } - 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}%)", @@ -278,15 +211,6 @@ impl ColorSpace for palette::Lab { [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) } @@ -305,18 +229,6 @@ impl ColorSpace for palette::Lch { [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})", From 0a011f90313dfbd77da5fdaa58bd93924ba7625c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 21:51:08 +0200 Subject: [PATCH 28/37] Improve `generate_theme` in `color_palette` --- examples/color_palette/src/main.rs | 42 +++++++++++------------------- 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 46a4d085..12c24a64 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -21,36 +21,24 @@ pub struct State { fn generate_theme(base_color: &Color) -> Vec { use palette::{Hsl, Hue, Shade, Srgb}; - let mut theme = Vec::::new(); + // Convert to HSL color for manipulation let hsl = Hsl::from(Srgb::from(*base_color)); - theme.push( - Srgb::from(hsl.shift_hue(-135.0).lighten(0.075)) - .clamp() - .into(), - ); - theme.push(Srgb::from(hsl.shift_hue(-120.0)).clamp().into()); - theme.push( - Srgb::from(hsl.shift_hue(-105.0).darken(0.075)) - .clamp() - .into(), - ); - theme.push(Srgb::from(hsl.darken(0.075)).clamp().into()); - theme.push(*base_color); - theme.push(Srgb::from(hsl.lighten(0.075)).clamp().into()); - theme.push( - Srgb::from(hsl.shift_hue(105.0).darken(0.075)) - .clamp() - .into(), - ); - theme.push(Srgb::from(hsl.shift_hue(120.0)).clamp().into()); - theme.push( - Srgb::from(hsl.shift_hue(135.0).lighten(0.075)) - .clamp() - .into(), - ); - theme + [ + 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), + hsl, + 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), + ] + .iter() + .map(|&color| Srgb::from(color).clamp().into()) + .collect() } struct ColorPicker { From 4d724a88e6b8b4f707501c2a45710354f8612b47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 22:24:34 +0200 Subject: [PATCH 29/37] Introduce `Theme` type in `color_palette` example --- examples/color_palette/src/main.rs | 191 +++++++++++++---------------- 1 file changed, 88 insertions(+), 103 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 12c24a64..97363b75 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::{ - canvas, slider, Canvas, Color, Column, Element, Length, Row, Sandbox, - Settings, Slider, Text, + canvas, slider, Align, Canvas, Color, Column, Element, Length, Row, + Sandbox, Settings, Slider, Text, }; use palette::{self, Limited}; use std::marker::PhantomData; @@ -13,34 +13,68 @@ pub fn main() { }) } -#[derive(Debug, Default)] -pub struct State { - color: Color, - theme: Vec, +#[derive(Debug)] +pub struct Theme { + lower: Vec, + base: Color, + higher: Vec, } -fn generate_theme(base_color: &Color) -> Vec { - use palette::{Hsl, Hue, Shade, Srgb}; - - // Convert to HSL color for manipulation - let hsl = Hsl::from(Srgb::from(*base_color)); - - [ - 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), - hsl, - 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), - ] - .iter() - .map(|&color| Srgb::from(color).clamp().into()) - .collect() +impl Default for Theme { + fn default() -> Self { + Theme::new(Color::from_rgb8(75, 128, 190)) + } } +impl Theme { + pub fn new(base: impl Into) -> 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 { + self.lower + .iter() + .chain(std::iter::once(&self.base)) + .chain(self.higher.iter()) + } +} + +#[derive(Default)] struct ColorPicker { sliders: [slider::State; 3], color_space: PhantomData, @@ -65,6 +99,7 @@ impl ColorPicker { 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))) @@ -227,15 +262,16 @@ impl ColorSpace for palette::Lch { } } +#[derive(Default)] pub struct ColorPalette { - state: State, + theme: Theme, rgb: ColorPicker, hsl: ColorPicker, hsv: ColorPicker, hwb: ColorPicker, lab: ColorPicker, lch: ColorPicker, - canvas_layer: canvas::layer::Cache, + canvas_layer: canvas::layer::Cache, } #[derive(Debug, Clone, Copy)] @@ -252,43 +288,7 @@ impl Sandbox for ColorPalette { type Message = Message; fn new() -> Self { - fn triple_slider() -> [slider::State; 3] { - [ - slider::State::new(), - slider::State::new(), - slider::State::new(), - ] - } - - ColorPalette { - 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(), - } + Self::default() } fn title(&self) -> String { @@ -296,7 +296,7 @@ impl Sandbox for ColorPalette { } fn update(&mut self, message: Message) { - let mut srgb = match 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), @@ -304,17 +304,15 @@ impl Sandbox for ColorPalette { Message::LabColorChanged(lab) => palette::Srgb::from(lab), Message::LchColorChanged(lch) => palette::Srgb::from(lch), }; - srgb.clamp_self(); - self.canvas_layer.clear(); - self.state.color = Color::from(srgb); - // Set theme colors - self.state.theme = generate_theme(&self.state.color); + self.theme = Theme::new(srgb.clamp()); + self.canvas_layer.clear(); } fn view(&mut self) -> Element { - let color = self.state.color; - let srgb = palette::Srgb::from(self.state.color); + 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); @@ -324,7 +322,7 @@ impl Sandbox for ColorPalette { Column::new() .padding(10) .spacing(10) - .push(self.rgb.view(color).map(Message::RgbColorChanged)) + .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)) @@ -333,48 +331,35 @@ impl Sandbox for ColorPalette { .push( Canvas::new() .width(Length::Fill) - // .height(Length::Units(250)) .height(Length::Fill) - .push(self.canvas_layer.with(&self.state)), + .push(self.canvas_layer.with(&self.theme)), ) .into() } } -impl State { - pub fn new() -> State { - let base = Color::from_rgb8(75, 128, 190); - State { - color: base, - theme: generate_theme(&base), - } - } -} - -impl canvas::Drawable for State { +impl canvas::Drawable for Theme { fn draw(&self, frame: &mut canvas::Frame) { use canvas::{Fill, Path}; use iced::{HorizontalAlignment, VerticalAlignment}; use iced_native::{Point, Size}; use palette::{Hsl, Srgb}; - if self.theme.len() == 0 { - return; - } - let pad = 20.0; let box_size = Size { - width: frame.width() / self.theme.len() as f32, + width: frame.width() / self.len() as f32, height: frame.height() / 2.0 - pad, }; - let mut text = canvas::Text::default(); - text.horizontal_alignment = HorizontalAlignment::Center; - text.vertical_alignment = VerticalAlignment::Top; - text.size = 15.0; + let mut text = canvas::Text { + horizontal_alignment: HorizontalAlignment::Center, + vertical_alignment: VerticalAlignment::Top, + size: 15.0, + ..canvas::Text::default() + }; - for i in 0..self.theme.len() { + for (i, &color) in self.colors().enumerate() { let anchor = Point { x: (i as f32) * box_size.width, y: 0.0, @@ -382,9 +367,9 @@ impl canvas::Drawable for State { let rect = Path::new(|path| { path.rectangle(anchor, box_size); }); - frame.fill(&rect, Fill::Color(self.theme[i])); + frame.fill(&rect, Fill::Color(color)); - if self.theme[i] == self.color { + if self.base == color { let cx = anchor.x + box_size.width / 2.0; let tri_w = 10.0; @@ -427,7 +412,7 @@ impl canvas::Drawable for State { } frame.fill_text(canvas::Text { - content: color_hex_str(&self.theme[i]), + content: color_hex_str(&color), position: Point { x: anchor.x + box_size.width / 2.0, y: box_size.height, @@ -438,9 +423,9 @@ impl canvas::Drawable for State { text.vertical_alignment = VerticalAlignment::Bottom; - let hsl = Hsl::from(Srgb::from(self.color)); - for i in 0..self.theme.len() { - let pct = (i as f32 + 1.0) / (self.theme.len() as f32 + 1.0); + 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 From 555371f77e02c962c2312dab7f1f2510b03e352a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 22:27:14 +0200 Subject: [PATCH 30/37] Move application implementation in `color_palette` --- examples/color_palette/src/main.rs | 412 ++++++++++++++--------------- 1 file changed, 206 insertions(+), 206 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 97363b75..243fae1d 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -13,6 +13,82 @@ pub fn main() { }) } +#[derive(Default)] +pub struct ColorPalette { + theme: Theme, + rgb: ColorPicker, + hsl: ColorPicker, + hsv: ColorPicker, + hwb: ColorPicker, + lab: ColorPicker, + lch: ColorPicker, + canvas_layer: canvas::layer::Cache, +} + +#[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 { + 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, @@ -20,12 +96,6 @@ pub struct Theme { higher: Vec, } -impl Default for Theme { - fn default() -> Self { - Theme::new(Color::from_rgb8(75, 128, 190)) - } -} - impl Theme { pub fn new(base: impl Into) -> Theme { use palette::{Hsl, Hue, Shade, Srgb}; @@ -74,6 +144,136 @@ impl Theme { } } +impl canvas::Drawable for Theme { + fn draw(&self, frame: &mut canvas::Frame) { + use canvas::{Fill, Path}; + use iced::{HorizontalAlignment, VerticalAlignment}; + use iced_native::{Point, Size}; + 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 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::new(|path| { + path.rectangle(anchor, box_size); + }); + frame.fill(&rect, Fill::Color(color)); + + if self.base == color { + let cx = anchor.x + box_size.width / 2.0; + let tri_w = 10.0; + + let tri = Path::new(|path| { + path.move_to(Point { + x: cx - tri_w, + y: 0.0, + }); + path.line_to(Point { + x: cx + tri_w, + y: 0.0, + }); + path.line_to(Point { x: cx, y: tri_w }); + path.line_to(Point { + x: cx - tri_w, + y: 0.0, + }); + }); + frame.fill(&tri, Fill::Color(Color::WHITE)); + + let tri = Path::new(|path| { + path.move_to(Point { + x: cx - tri_w, + y: box_size.height, + }); + path.line_to(Point { + x: cx + tri_w, + y: box_size.height, + }); + path.line_to(Point { + x: cx, + y: box_size.height - tri_w, + }); + path.line_to(Point { + x: cx - tri_w, + y: box_size.height, + }); + }); + frame.fill(&tri, Fill::Color(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::new(|path| { + path.rectangle(anchor, box_size); + }); + frame.fill(&rect, Fill::Color(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 { sliders: [slider::State; 3], @@ -261,203 +461,3 @@ impl ColorSpace for palette::Lch { ) } } - -#[derive(Default)] -pub struct ColorPalette { - theme: Theme, - rgb: ColorPicker, - hsl: ColorPicker, - hsv: ColorPicker, - hwb: ColorPicker, - lab: ColorPicker, - lch: ColorPicker, - canvas_layer: canvas::layer::Cache, -} - -#[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") - } - - 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 { - 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() - } -} - -impl canvas::Drawable for Theme { - fn draw(&self, frame: &mut canvas::Frame) { - use canvas::{Fill, Path}; - use iced::{HorizontalAlignment, VerticalAlignment}; - use iced_native::{Point, Size}; - 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 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::new(|path| { - path.rectangle(anchor, box_size); - }); - frame.fill(&rect, Fill::Color(color)); - - if self.base == color { - let cx = anchor.x + box_size.width / 2.0; - let tri_w = 10.0; - - let tri = Path::new(|path| { - path.move_to(Point { - x: cx - tri_w, - y: 0.0, - }); - path.line_to(Point { - x: cx + tri_w, - y: 0.0, - }); - path.line_to(Point { x: cx, y: tri_w }); - path.line_to(Point { - x: cx - tri_w, - y: 0.0, - }); - }); - frame.fill(&tri, Fill::Color(Color::WHITE)); - - let tri = Path::new(|path| { - path.move_to(Point { - x: cx - tri_w, - y: box_size.height, - }); - path.line_to(Point { - x: cx + tri_w, - y: box_size.height, - }); - path.line_to(Point { - x: cx, - y: box_size.height - tri_w, - }); - path.line_to(Point { - x: cx - tri_w, - y: box_size.height, - }); - }); - frame.fill(&tri, Fill::Color(Color::WHITE)); - } - - frame.fill_text(canvas::Text { - content: color_hex_str(&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::new(|path| { - path.rectangle(anchor, box_size); - }); - frame.fill(&rect, Fill::Color(color)); - - frame.fill_text(canvas::Text { - content: color_hex_str(&color), - position: Point { - x: anchor.x + box_size.width / 2.0, - y: box_size.height + 2.0 * pad, - }, - ..text - }); - } - } -} - -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 - ) -} From 573929d5ec99981ae3a4a0d675f1248932d56e61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 22:32:40 +0200 Subject: [PATCH 31/37] Use `Path::rectangle` directly in `color_palette` --- examples/color_palette/src/main.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 243fae1d..ff399e76 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -170,9 +170,7 @@ impl canvas::Drawable for Theme { x: (i as f32) * box_size.width, y: 0.0, }; - let rect = Path::new(|path| { - path.rectangle(anchor, box_size); - }); + let rect = Path::rectangle(anchor, box_size); frame.fill(&rect, Fill::Color(color)); if self.base == color { @@ -242,9 +240,8 @@ impl canvas::Drawable for Theme { x: (i as f32) * box_size.width, y: box_size.height + 2.0 * pad, }; - let rect = Path::new(|path| { - path.rectangle(anchor, box_size); - }); + + let rect = Path::rectangle(anchor, box_size); frame.fill(&rect, Fill::Color(color)); frame.fill_text(canvas::Text { From 03ca7eea6c05b32c6273284c35883506e4cf6eb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 1 May 2020 22:45:47 +0200 Subject: [PATCH 32/37] Reuse triangle path with transforms in `color_palette` --- examples/color_palette/src/main.rs | 65 ++++++++++++------------------ 1 file changed, 25 insertions(+), 40 deletions(-) diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index ff399e76..b3ad98d0 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,6 @@ use iced::{ canvas, slider, Align, Canvas, Color, Column, Element, Length, Row, - Sandbox, Settings, Slider, Text, + Sandbox, Settings, Slider, Text, Vector, }; use palette::{self, Limited}; use std::marker::PhantomData; @@ -146,7 +146,7 @@ impl Theme { impl canvas::Drawable for Theme { fn draw(&self, frame: &mut canvas::Frame) { - use canvas::{Fill, Path}; + use canvas::Path; use iced::{HorizontalAlignment, VerticalAlignment}; use iced_native::{Point, Size}; use palette::{Hsl, Srgb}; @@ -158,6 +158,13 @@ impl canvas::Drawable for Theme { 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, @@ -171,48 +178,26 @@ impl canvas::Drawable for Theme { y: 0.0, }; let rect = Path::rectangle(anchor, box_size); - frame.fill(&rect, Fill::Color(color)); + frame.fill(&rect, color); - if self.base == color { - let cx = anchor.x + box_size.width / 2.0; - let tri_w = 10.0; + // We show a little indicator for the base color + if color == self.base { + let triangle_x = anchor.x + box_size.width / 2.0; - let tri = Path::new(|path| { - path.move_to(Point { - x: cx - tri_w, - y: 0.0, - }); - path.line_to(Point { - x: cx + tri_w, - y: 0.0, - }); - path.line_to(Point { x: cx, y: tri_w }); - path.line_to(Point { - x: cx - tri_w, - y: 0.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.fill(&tri, Fill::Color(Color::WHITE)); - let tri = Path::new(|path| { - path.move_to(Point { - x: cx - tri_w, - y: box_size.height, - }); - path.line_to(Point { - x: cx + tri_w, - y: box_size.height, - }); - path.line_to(Point { - x: cx, - y: box_size.height - tri_w, - }); - path.line_to(Point { - x: cx - tri_w, - y: box_size.height, - }); + frame.with_save(|frame| { + frame.translate(Vector::new(triangle_x, box_size.height)); + frame.scale(10.0); + + frame.fill(&triangle, Color::WHITE); }); - frame.fill(&tri, Fill::Color(Color::WHITE)); } frame.fill_text(canvas::Text { @@ -242,7 +227,7 @@ impl canvas::Drawable for Theme { }; let rect = Path::rectangle(anchor, box_size); - frame.fill(&rect, Fill::Color(color)); + frame.fill(&rect, color); frame.fill_text(canvas::Text { content: color_hex_string(&color), From 24574b355d8c5c5e624524c6974df822da98befb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 22:50:25 +0200 Subject: [PATCH 33/37] Mention `color_palette` in examples `README` --- examples/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/README.md b/examples/README.md index 5aea51eb..f67a0dd2 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 [`lyon`]. - [`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. From 1a8d253611d3796b0a32b2f096bb54565a5292e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 22:51:20 +0200 Subject: [PATCH 34/37] Add screenshot of `color_palette` example --- examples/color_palette/screenshot.png | Bin 0 -> 105201 bytes examples/color_palette/src/main.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 examples/color_palette/screenshot.png diff --git a/examples/color_palette/screenshot.png b/examples/color_palette/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..aa4772e03ee86d5fdd4c455075305befa2b49833 GIT binary patch literal 105201 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 mDgMd ColorPicker { .push( Text::new(color.to_string()) .width(Length::Units(185)) - .size(16), + .size(14), ) .into() } From 3d3e51a742ec940e19271897a8266172bffd6587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 22:53:07 +0200 Subject: [PATCH 35/37] Add screenshot to `README` of `color_palette` --- examples/color_palette/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/color_palette/README.md b/examples/color_palette/README.md index b646f3b3..e70188f8 100644 --- a/examples/color_palette/README.md +++ b/examples/color_palette/README.md @@ -1,7 +1,13 @@ -## Color Palette +## Color palette A color palette generator, based on a user-defined root color. + + You can run it with `cargo run`: ``` From e3555174d7c12599d454cbe890248449dc3ea958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 22:55:10 +0200 Subject: [PATCH 36/37] Use only `iced` dependency for `color_palette` `Point` and `Size` are now properly re-exported. --- examples/color_palette/Cargo.toml | 2 -- examples/color_palette/src/main.rs | 7 +++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/color_palette/Cargo.toml b/examples/color_palette/Cargo.toml index 61c9f6b2..00f33e20 100644 --- a/examples/color_palette/Cargo.toml +++ b/examples/color_palette/Cargo.toml @@ -7,6 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "palette"] } -iced_core = { path = "../../core" } -iced_native = { path = "../../native" } palette = "0.5.0" diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index 0092c6ad..073a6734 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -1,6 +1,7 @@ use iced::{ - canvas, slider, Align, Canvas, Color, Column, Element, Length, Row, - Sandbox, Settings, Slider, Text, Vector, + 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; @@ -147,8 +148,6 @@ impl Theme { impl canvas::Drawable for Theme { fn draw(&self, frame: &mut canvas::Frame) { use canvas::Path; - use iced::{HorizontalAlignment, VerticalAlignment}; - use iced_native::{Point, Size}; use palette::{Hsl, Srgb}; let pad = 20.0; From c0fd5de8a0dbb1b99de8c83e4f84c98a6219778b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Mon, 4 May 2020 23:04:02 +0200 Subject: [PATCH 37/37] Improve minor documentation details in `Color` --- core/src/color.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/core/src/color.rs b/core/src/color.rs index c061add6..a4c3d87c 100644 --- a/core/src/color.rs +++ b/core/src/color.rs @@ -39,7 +39,12 @@ impl Color { a: 0.0, }; - /// New Color with range checks + /// 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), @@ -116,14 +121,18 @@ impl Color { ] } - /// Invert the Color in-place + /// 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; } - /// Return an inverted Color + /// 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) } @@ -142,9 +151,8 @@ impl From<[f32; 4]> for Color { } #[cfg(feature = "palette")] -/// Convert from palette's [`Srgba`] type to a [`Color`] +/// Converts from palette's `Srgba` type to a [`Color`]. /// -/// [`Srgba`]: ../palette/rgb/type.Srgba.html /// [`Color`]: struct.Color.html impl From for Color { fn from(srgba: Srgba) -> Self { @@ -153,10 +161,9 @@ impl From for Color { } #[cfg(feature = "palette")] -/// Convert from [`Color`] to palette's [`Srgba`] type +/// Converts from [`Color`] to palette's `Srgba` type. /// /// [`Color`]: struct.Color.html -/// [`Srgba`]: ../palette/rgb/type.Srgba.html impl From for Srgba { fn from(c: Color) -> Self { Srgba::new(c.r, c.g, c.b, c.a) @@ -164,9 +171,8 @@ impl From for Srgba { } #[cfg(feature = "palette")] -/// Convert from palette's [`Srgb`] type to a [`Color`] +/// Converts from palette's `Srgb` type to a [`Color`]. /// -/// [`Srgb`]: ../palette/rgb/type.Srgb.html /// [`Color`]: struct.Color.html impl From for Color { fn from(srgb: Srgb) -> Self { @@ -175,7 +181,7 @@ impl From for Color { } #[cfg(feature = "palette")] -/// Convert from [`Color`] to palette's [`Srgb`] type +/// Converts from [`Color`] to palette's `Srgb` type. /// /// [`Color`]: struct.Color.html /// [`Srgb`]: ../palette/rgb/type.Srgb.html