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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 literal 0 HcmV?d00001 diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index b3ad98d0..0092c6ad 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -289,7 +289,7 @@ impl 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