From f59eb77252d1730319d532fc0a0c50ce860edd9d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 12 Feb 2025 18:30:32 +0100 Subject: [PATCH 1/8] perf: set static mmap threshold on gnu target env by default --- src/app/mod.rs | 6 +++++- src/app/settings.rs | 4 ++++ src/applet/mod.rs | 8 +++++--- src/lib.rs | 8 ++++++-- src/malloc.rs | 14 ++++++++++++++ 5 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 src/malloc.rs diff --git a/src/app/mod.rs b/src/app/mod.rs index 45b53fc6ea2..58b1c66d1dd 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -138,7 +138,11 @@ pub(crate) fn iced_settings( /// /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { - let default_font = settings.default_font; + #[cfg(target_env = "gnu")] + if let Some(threshold) = settings.default_mmap_threshold { + crate::malloc::limit_mmap_threshold(threshold); + } + let (settings, mut flags, window_settings) = iced_settings::(settings, flags); #[cfg(not(feature = "multi-window"))] { diff --git a/src/app/settings.rs b/src/app/settings.rs index bd8a5f3debb..a5782c9979e 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -38,6 +38,9 @@ pub struct Settings { /// Default size of fonts. pub(crate) default_text_size: f32, + /// Set the default mmap threshold for malloc with mallopt. + pub(crate) default_mmap_threshold: Option, + /// Whether the window should be resizable or not. /// and the size of the window border which can be dragged for a resize pub(crate) resizable: Option, @@ -85,6 +88,7 @@ impl Default for Settings { default_font: font::default(), default_icon_theme: None, default_text_size: 14.0, + default_mmap_threshold: Some(128 * 1024), resizable: Some(8.0), scale_factor: std::env::var("COSMIC_SCALE") .ok() diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 4980c91d42b..0ca8a4f1306 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -153,7 +153,6 @@ impl Context { self.size = Size::Hardcoded((width, height)); } - #[must_use] #[allow(clippy::cast_precision_loss)] pub fn window_settings(&self) -> crate::app::Settings { let (width, height) = self.suggested_size(true); @@ -183,7 +182,6 @@ impl Context { matches!(self.anchor, PanelAnchor::Top | PanelAnchor::Bottom) } - #[must_use] pub fn icon_button_from_handle<'a, Message: 'static>( &self, icon: widget::icon::Handle, @@ -213,7 +211,6 @@ impl Context { .class(Button::AppletIcon) } - #[must_use] pub fn icon_button<'a, Message: 'static>( &self, icon_name: &'a str, @@ -385,6 +382,11 @@ pub fn run(flags: App::Flags) -> iced::Result { let mut settings = helper.window_settings(); settings.resizable = None; + #[cfg(target_env = "gnu")] + if let Some(threshold) = settings.default_mmap_threshold { + crate::malloc::limit_mmap_threshold(threshold); + } + if let Some(icon_theme) = settings.default_icon_theme.clone() { crate::icon_theme::set_default(icon_theme); } diff --git a/src/lib.rs b/src/lib.rs index 3e583d131fa..bd4651ace1b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,9 @@ pub use cosmic_config; #[doc(inline)] pub use cosmic_theme; +#[cfg(feature = "desktop")] +pub mod desktop; + #[cfg(any(feature = "xdg-portal", feature = "rfd"))] pub mod dialog; @@ -73,8 +76,9 @@ pub use iced_wgpu; pub mod icon_theme; pub mod keyboard_nav; -#[cfg(feature = "desktop")] -pub mod desktop; +#[cfg(target_env = "gnu")] +pub(crate) mod malloc; + #[cfg(all(feature = "process", not(windows)))] pub mod process; diff --git a/src/malloc.rs b/src/malloc.rs new file mode 100644 index 00000000000..001f9a166c3 --- /dev/null +++ b/src/malloc.rs @@ -0,0 +1,14 @@ +use std::os::raw::c_int; + +const M_MMAP_THRESHOLD: c_int = -3; + +extern "C" { + fn mallopt(param: c_int, value: c_int) -> c_int; +} + +/// Prevents glibc from hoarding memory via memory fragmentation. +pub fn limit_mmap_threshold(threshold: i32) { + unsafe { + mallopt(M_MMAP_THRESHOLD, threshold as c_int); + } +} From cba28b13720a5e5c9ebfa668a534d062c35cfd78 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Mon, 10 Feb 2025 14:28:54 -0800 Subject: [PATCH 2/8] Update `cctk` and `iced` --- Cargo.toml | 2 +- iced | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8591022a4b..0c75ccc9f53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,7 @@ markdown = ["iced/markdown"] apply = "0.3.0" ashpd = { version = "0.9.1", default-features = false, optional = true } async-fs = { version = "2.1", optional = true } -cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "29ab323", optional = true } +cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit", rev = "178eb0b", optional = true } chrono = "0.4.35" cosmic-config = { path = "cosmic-config" } cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } diff --git a/iced b/iced index 4ea727c08ea..7b5d3057c24 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 4ea727c08ea78053aac78d7b6c5a1faedcd43239 +Subproject commit 7b5d3057c2499ae691c1ececd6062d97c21d09da From 0b7e23444afb3f351cd947c52babb6b87f30381d Mon Sep 17 00:00:00 2001 From: Tony4dev <78384793+Tony4dev@users.noreply.github.com> Date: Thu, 13 Feb 2025 15:22:42 +0000 Subject: [PATCH 3/8] feat(segmented_button): add len method --- src/widget/segmented_button/model/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 449c829fb47..c24fe85f13a 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -310,6 +310,11 @@ where self.items.get(id).map_or(false, |e| e.enabled) } + /// Get number of items in the model. + pub fn len(&self) -> usize { + self.order.len() + } + /// Iterates across items in the model in the order that they are displayed. pub fn iter(&self) -> impl Iterator + '_ { self.order.iter().copied() From ccc1068d9fd894334feef7d50a7a2b6930c7a34f Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 21:44:08 +0100 Subject: [PATCH 4/8] feat(cosmic_config): add ConfigGet::get_{local,system_default} Required by https://github.com/pop-os/cosmic-settings/pull/975 to a modify a config containing a HashMap which is used to partially-override the system default config in the compositor. --- cosmic-config/src/lib.rs | 60 ++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index f915da71d24..0a3e0c9e7cc 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -33,6 +33,7 @@ pub enum Error { Io(std::io::Error), NoConfigDirectory, Notify(notify::Error), + NotFound, Ron(ron::Error), RonSpanned(ron::error::SpannedError), GetKey(String, std::io::Error), @@ -46,6 +47,7 @@ impl fmt::Display for Error { Self::Io(err) => err.fmt(f), Self::NoConfigDirectory => write!(f, "cosmic config directory not found"), Self::Notify(err) => err.fmt(f), + Self::NotFound => write!(f, "cosmic config key not configured"), Self::Ron(err) => err.fmt(f), Self::RonSpanned(err) => err.fmt(f), Self::GetKey(key, err) => write!(f, "failed to get key '{}': {}", key, err), @@ -55,6 +57,15 @@ impl fmt::Display for Error { impl std::error::Error for Error {} +impl Error { + /// Whether the reason for the missing config is caused by an error. + /// + /// Useful for determining if it is appropriate to log as an error. + pub fn is_err(&self) -> bool { + matches!(self, Self::NoConfigDirectory | Self::NotFound) + } +} + impl From> for Error { fn from(f: atomicwrites::Error) -> Self { Self::AtomicWrites(f) @@ -87,7 +98,15 @@ impl From for Error { pub trait ConfigGet { /// Get a configuration value + /// + /// Fallback to the system default if a local user override is not defined. fn get(&self, key: &str) -> Result; + + /// Get a locally-defined configuration value from the user's local config. + fn get_local(&self, key: &str) -> Result; + + /// Get the system-defined default configuration value. + fn get_system_default(&self, key: &str) -> Result; } pub trait ConfigSet { @@ -216,7 +235,7 @@ impl Config { } // Start a transaction (to set multiple configs at the same time) - pub fn transaction<'a>(&'a self) -> ConfigTransaction<'a> { + pub fn transaction(&self) -> ConfigTransaction { ConfigTransaction { config: self, updates: Mutex::new(Vec::new()), @@ -288,6 +307,7 @@ impl Config { Ok(system_path.join(sanitize_name(key)?)) } + /// Get the path of the key in the user's local config directory. fn key_path(&self, key: &str) -> Result { let Some(user_path) = self.user_path.as_ref() else { return Err(Error::NoConfigDirectory); @@ -300,22 +320,34 @@ impl Config { impl ConfigGet for Config { //TODO: check for transaction fn get(&self, key: &str) -> Result { + match self.get_local(key) { + Ok(value) => Ok(value), + Err(Error::NotFound) => self.get_system_default(key), + Err(why) => Err(why), + } + } + + fn get_local(&self, key: &str) -> Result { // If key path exists - let key_path = self.key_path(key); - let data = match key_path { - Ok(key_path) if key_path.is_file() => { + match self.key_path(key)? { + key_path if key_path.is_file() => { // Load user override - fs::read_to_string(key_path).map_err(|err| Error::GetKey(key.to_string(), err))? - } - _ => { - // Load system default - let default_path = self.default_path(key)?; - fs::read_to_string(default_path) - .map_err(|err| Error::GetKey(key.to_string(), err))? + let data = fs::read_to_string(key_path) + .map_err(|err| Error::GetKey(key.to_string(), err))?; + + Ok(ron::from_str(&data)?) } - }; - let t = ron::from_str(&data)?; - Ok(t) + + _ => Err(Error::NotFound), + } + } + + fn get_system_default(&self, key: &str) -> Result { + // Load system default + let default_path = self.default_path(key)?; + let data = + fs::read_to_string(default_path).map_err(|err| Error::GetKey(key.to_string(), err))?; + Ok(ron::from_str(&data)?) } } From cd8f4ee8590170f09ba3c90542be92daaba38e87 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 22:39:09 +0100 Subject: [PATCH 5/8] fix(cosmic_config): treat errors getting key_path in get_local as NotFound --- cosmic-config/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 0a3e0c9e7cc..357625e1633 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -329,8 +329,8 @@ impl ConfigGet for Config { fn get_local(&self, key: &str) -> Result { // If key path exists - match self.key_path(key)? { - key_path if key_path.is_file() => { + match self.key_path(key) { + Ok(key_path) if key_path.is_file() => { // Load user override let data = fs::read_to_string(key_path) .map_err(|err| Error::GetKey(key.to_string(), err))?; From 580db26868a67aba768669fc3762e81a93e0fb85 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Fri, 14 Feb 2025 22:42:50 +0100 Subject: [PATCH 6/8] fix(cosmic_config): is_err method conditions reversed --- cosmic-config/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index 357625e1633..0c0f4db7e6a 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -62,7 +62,7 @@ impl Error { /// /// Useful for determining if it is appropriate to log as an error. pub fn is_err(&self) -> bool { - matches!(self, Self::NoConfigDirectory | Self::NotFound) + !matches!(self, Self::NoConfigDirectory | Self::NotFound) } } From 25bf8f60cc695175dde945e8b09ac629af96c145 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 17 Feb 2025 01:17:52 -0500 Subject: [PATCH 7/8] feat: improve accent_text for low contrast accent colors. --- cosmic-theme/src/model/theme.rs | 164 +++++++++++++++++++++----------- iced | 2 +- 2 files changed, 108 insertions(+), 58 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 821450a1583..0740b4e5259 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_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)] @@ -847,6 +850,105 @@ impl ThemeBuilder { text_steps_array.as_ref(), ); + 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_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 + }; + + 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() { @@ -879,60 +981,7 @@ impl ThemeBuilder { &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 @@ -1098,6 +1147,7 @@ impl ThemeBuilder { active_hint, window_hint, is_frosted, + accent_text, }; theme.spacing = spacing; theme.corner_radii = corner_radii; diff --git a/iced b/iced index 7b5d3057c24..654f3c33b54 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7b5d3057c2499ae691c1ececd6062d97c21d09da +Subproject commit 654f3c33b54679c06c041be6eea2c03f6d7cfdce From 3f25af87a3a41c53bdbd69878f32766bbcda2194 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 17 Feb 2025 02:11:33 -0500 Subject: [PATCH 8/8] refactor: small widget container colors --- cosmic-theme/src/model/theme.rs | 38 +++++++++------------------------ cosmic-theme/src/steps.rs | 34 +++++++++++++++++++++++++---- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 0740b4e5259..3a1d0c8308e 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,6 +1,6 @@ use crate::{ composite::over, - steps::{color_index, get_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, }; @@ -847,7 +847,7 @@ 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 = { @@ -879,7 +879,7 @@ impl ThemeBuilder { color_index(component_base, 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, @@ -891,15 +891,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), ); container @@ -971,15 +965,9 @@ 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, secondary: { @@ -1011,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, @@ -1023,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( 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 {