diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 821450a1583..3a1d0c8308e 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,11 +1,11 @@ use crate::{ composite::over, - steps::{color_index, get_surface_color, get_text, steps}, + steps::{color_index, get_index, get_small_widget_color, get_surface_color, get_text, steps}, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, ThemeMode, DARK_PALETTE, LIGHT_PALETTE, NAME, }; use cosmic_config::{Config, CosmicConfigEntry}; -use palette::{rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba}; +use palette::{color_difference::Wcag21RelativeContrast, rgb::Rgb, IntoColor, Oklcha, Srgb, Srgba}; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; @@ -97,6 +97,9 @@ pub struct Theme { pub is_frosted: bool, /// shade color for dialogs pub shade: Srgba, + /// accent text colors + /// If None, accent base color is the accent text color. + pub accent_text: Option, } impl Default for Theme { @@ -276,7 +279,7 @@ impl Theme { #[allow(clippy::doc_markdown)] /// get @accent_text_color pub fn accent_text_color(&self) -> Srgba { - self.accent.base + self.accent_text.unwrap_or(self.accent.base) } #[must_use] #[allow(clippy::doc_markdown)] @@ -844,9 +847,102 @@ impl ThemeBuilder { color_index(bg_component, step_array.len()), &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), + text_steps_array.as_deref(), ); + let primary = { + let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { + primary_container_bg_color + } else { + get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) + }; + + let base_index: usize = color_index(container_bg, step_array.len()); + let component_base = + get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); + + component_hovered_overlay = if base_index < 91 { + p_ref.neutral_10 + } else { + p_ref.neutral_0 + }; + component_hovered_overlay.alpha = 0.1; + + component_pressed_overlay = component_hovered_overlay; + component_pressed_overlay.alpha = 0.2; + + let container = Container::new( + Component::component( + component_base, + accent, + get_text( + color_index(component_base, step_array.len()), + &step_array, + &p_ref.neutral_8, + text_steps_array.as_deref(), + ), + component_hovered_overlay, + component_pressed_overlay, + is_high_contrast, + p_ref.neutral_8, + ), + container_bg, + get_text( + base_index, + &step_array, + &p_ref.neutral_8, + text_steps_array.as_deref(), + ), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), + ); + + container + }; + + let accent_text = if is_dark { + (primary.base.relative_contrast(accent.color) < 4.).then(|| { + let step_array = steps(accent, NonZeroUsize::new(100).unwrap()); + let primary_color_index = color_index(primary.base, 100); + let steps = if is_high_contrast { 60 } else { 50 }; + let accent_text = get_surface_color( + primary_color_index, + steps, + &step_array, + is_dark, + &Srgba::new(1., 1., 1., 1.), + ); + if primary.base.relative_contrast(accent_text.color) < 4. { + Srgba::new(1., 1., 1., 1.) + } else { + accent_text + } + }) + } else { + let darkest = if bg.relative_luminance().luma < primary.base.relative_luminance().luma { + bg + } else { + primary.base + }; + + (darkest.relative_contrast(accent.color) < 4.).then(|| { + let step_array = steps(accent, NonZeroUsize::new(100).unwrap()); + let primary_color_index = color_index(darkest, 100); + let steps = if is_high_contrast { 60 } else { 50 }; + let accent_text = get_surface_color( + primary_color_index, + steps, + &step_array, + is_dark, + &Srgba::new(1., 1., 1., 1.), + ); + if darkest.relative_contrast(accent_text.color) < 4. { + Srgba::new(0., 0., 0., 1.) + } else { + accent_text + } + }) + }; + let mut theme: Theme = Theme { name: palette.name().to_string(), shade: if palette.is_dark() { @@ -869,70 +965,11 @@ impl ThemeBuilder { bg_index, &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - bg_index, - 5, - &neutral_steps, - bg_index <= 65, - &p_ref.neutral_6, + text_steps_array.as_deref(), ), + get_small_widget_color(bg_index, 5, &neutral_steps, &p_ref.neutral_6), ), - primary: { - let container_bg = if let Some(primary_container_bg_color) = primary_container_bg { - primary_container_bg_color - } else { - get_surface_color(bg_index, 5, &step_array, is_dark, &p_ref.neutral_1) - }; - - let base_index: usize = color_index(container_bg, step_array.len()); - let component_base = - get_surface_color(base_index, 6, &step_array, is_dark, &p_ref.neutral_3); - - component_hovered_overlay = if base_index < 91 { - p_ref.neutral_10 - } else { - p_ref.neutral_0 - }; - component_hovered_overlay.alpha = 0.1; - - component_pressed_overlay = component_hovered_overlay; - component_pressed_overlay.alpha = 0.2; - - let container = Container::new( - Component::component( - component_base, - accent, - get_text( - color_index(component_base, step_array.len()), - &step_array, - &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - component_hovered_overlay, - component_pressed_overlay, - is_high_contrast, - p_ref.neutral_8, - ), - container_bg, - get_text( - base_index, - &step_array, - &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - base_index, - 5, - &neutral_steps, - base_index <= 65, - &p_ref.neutral_6, - ), - ); - - container - }, + primary, secondary: { let container_bg = if let Some(secondary_container_bg) = secondary_container_bg { secondary_container_bg @@ -962,7 +999,7 @@ impl ThemeBuilder { color_index(secondary_component, step_array.len()), &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), + text_steps_array.as_deref(), ), component_hovered_overlay, component_pressed_overlay, @@ -974,15 +1011,9 @@ impl ThemeBuilder { base_index, &step_array, &p_ref.neutral_8, - text_steps_array.as_ref(), - ), - get_surface_color( - base_index, - 5, - &neutral_steps, - base_index <= 65, - &p_ref.neutral_6, + text_steps_array.as_deref(), ), + get_small_widget_color(base_index, 5, &neutral_steps, &p_ref.neutral_6), ) }, accent: Component::colored_component( @@ -1098,6 +1129,7 @@ impl ThemeBuilder { active_hint, window_hint, is_frosted, + accent_text, }; theme.spacing = spacing; theme.corner_radii = corner_radii; diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 2c306e3c347..506b6fa8c40 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,7 +1,7 @@ use std::num::NonZeroUsize; use almost::equal; -use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Oklcha, Srgb, Srgba}; +use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Lch, Oklcha, Srgb, Srgba}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. @@ -35,7 +35,7 @@ pub fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool pub fn get_surface_color( base_index: usize, steps: usize, - step_array: &Vec, + step_array: &[Srgba], mut is_dark: bool, fallback: &Srgba, ) -> Srgba { @@ -48,12 +48,38 @@ pub fn get_surface_color( .unwrap_or(fallback) } +/// get surface color given a base and some steps +#[must_use] +pub fn get_small_widget_color( + base_index: usize, + steps: usize, + step_array: &[Srgba], + fallback: &Srgba, +) -> Srgba { + assert!(step_array.len() == 100); + + let is_dark = base_index <= 40 || (base_index > 51 && base_index < 65); + + let res = *get_index(base_index, steps, step_array.len(), is_dark) + .and_then(|i| step_array.get(i)) + .unwrap_or(fallback); + + let mut lch = Lch::from_color(res); + if lch.chroma / Lch::::max_chroma() > 0.03 { + lch.chroma = 0.03 * Lch::::max_chroma(); + lch.clamp_assign(); + Srgba::from_color(lch) + } else { + res + } +} + /// get text color given a base background color pub fn get_text( base_index: usize, - step_array: &Vec, + step_array: &[Srgba], fallback: &Srgba, - tint_array: Option<&Vec>, + tint_array: Option<&[Srgba]>, ) -> Srgba { assert!(step_array.len() == 100); let step_array = if let Some(tint_array) = tint_array { diff --git a/iced b/iced index 7b5d3057c24..654f3c33b54 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7b5d3057c2499ae691c1ececd6062d97c21d09da +Subproject commit 654f3c33b54679c06c041be6eea2c03f6d7cfdce