From c819f94e745b2bdf13f768680aaa6b2990300b4c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 4 Aug 2023 11:56:31 -0400 Subject: [PATCH] feat: apply tints --- cosmic-theme/src/model/theme.rs | 106 +++++++++++++++----------------- cosmic-theme/src/steps.rs | 80 +++++++++++++++++++++--- 2 files changed, 123 insertions(+), 63 deletions(-) diff --git a/cosmic-theme/src/model/theme.rs b/cosmic-theme/src/model/theme.rs index 4e761768..b85e2fa7 100644 --- a/cosmic-theme/src/model/theme.rs +++ b/cosmic-theme/src/model/theme.rs @@ -1,16 +1,17 @@ use crate::{ - steps::steps, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, + steps::*, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing, DARK_PALETTE, LIGHT_PALETTE, NAME, THEME_DIR, }; use anyhow::Context; use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry}; use directories::{BaseDirsExt, ProjectDirsExt}; -use palette::{FromColor, Oklcha, Srgb, Srgba}; +use palette::{Srgb, Srgba}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::{ fmt, fs::File, io::Write, + num::NonZeroUsize, path::{Path, PathBuf}, }; @@ -580,14 +581,34 @@ impl ThemeBuilder { accent, } = self; + let is_dark = palette.is_dark(); + let is_high_contrast = palette.is_high_contrast(); + if let Some(accent) = accent { palette.as_mut().accent = accent.into(); } - // TODO apply the tint customizations + let text_steps_array = text_tint.map(|c| steps(c, NonZeroUsize::new(100).unwrap())); - let is_dark = palette.is_dark(); - let is_high_contrast = palette.is_high_contrast(); + if let Some(neutral_tint) = neutral_tint { + let mut neutral_steps_arr = steps(neutral_tint, NonZeroUsize::new(11).unwrap()); + if !is_dark { + neutral_steps_arr.reverse(); + } + + let p = palette.as_mut(); + p.neutral_0 = neutral_steps_arr[0]; + p.neutral_1 = neutral_steps_arr[1]; + p.neutral_2 = neutral_steps_arr[2]; + p.neutral_3 = neutral_steps_arr[3]; + p.neutral_4 = neutral_steps_arr[4]; + p.neutral_5 = neutral_steps_arr[5]; + p.neutral_6 = neutral_steps_arr[6]; + p.neutral_7 = neutral_steps_arr[7]; + p.neutral_8 = neutral_steps_arr[8]; + p.neutral_9 = neutral_steps_arr[9]; + p.neutral_10 = neutral_steps_arr[10]; + } if let Some(accent) = accent { palette.as_mut().accent = accent.into(); @@ -599,8 +620,7 @@ impl ThemeBuilder { } else { p_ref.gray_1.clone() }; - let ok_bg = Oklcha::from_color(bg); - let step_array = steps(ok_bg); + let step_array = steps(bg, NonZeroUsize::new(100).unwrap()); let bg_index = color_index(bg, step_array.len()); let primary_container_bg = if let Some(primary_container_bg_color) = primary_container_bg { @@ -621,6 +641,7 @@ impl ThemeBuilder { &step_array, is_dark, &p_ref.neutral_8, + text_steps_array.as_ref(), ); let bg_component = Component::component( bg_component, @@ -637,6 +658,7 @@ impl ThemeBuilder { &step_array, is_dark, &p_ref.neutral_8, + text_steps_array.as_ref(), ); let primary_component = Component::component( primary_component, @@ -654,6 +676,7 @@ impl ThemeBuilder { &step_array, is_dark, &p_ref.neutral_10, + text_steps_array.as_ref(), ); let secondary_component = Component::component( secondary_component, @@ -668,17 +691,35 @@ impl ThemeBuilder { background: Container::new( bg_component, bg, - get_text(bg_index, &step_array, is_dark, &p_ref.neutral_8), + get_text( + bg_index, + &step_array, + is_dark, + &p_ref.neutral_8, + text_steps_array.as_ref(), + ), ), primary: Container::new( primary_component, primary_container_bg, - get_text(primary_index, &step_array, is_dark, &p_ref.neutral_8), + get_text( + primary_index, + &step_array, + is_dark, + &p_ref.neutral_8, + text_steps_array.as_ref(), + ), ), secondary: Container::new( secondary_component, secondary_container_bg, - get_text(secondary_index, &step_array, is_dark, &p_ref.neutral_8), + get_text( + secondary_index, + &step_array, + is_dark, + &p_ref.neutral_8, + text_steps_array.as_ref(), + ), ), accent: Component::colored_component( p_ref.accent.to_owned(), @@ -711,48 +752,3 @@ impl ThemeBuilder { theme } } - -fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool) -> Option { - if is_dark { - base_index.checked_add(steps) - } else { - base_index.checked_sub(steps) - } - .filter(|i| *i < step_len) -} - -fn get_color( - base_index: usize, - steps: usize, - step_array: &[Srgba; 100], - is_dark: bool, - fallback: &Srgba, -) -> Srgba { - get_index(base_index, steps, step_array.len(), is_dark) - .and_then(|i| step_array.get(i).cloned()) - .unwrap_or_else(|| fallback.to_owned()) -} - -fn get_text( - base_index: usize, - step_array: &[Srgba; 100], - is_dark: bool, - fallback: &Srgba, -) -> Srgba { - let Some(index) = get_index(base_index, 70, step_array.len(), is_dark).or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) else { - return fallback.to_owned(); - }; - - step_array - .get(index) - .cloned() - .unwrap_or_else(|| fallback.to_owned()) -} - -fn color_index(c: C, array_len: usize) -> usize -where - Oklcha: FromColor, -{ - let c = Oklcha::from_color(c); - ((c.l * array_len as f32).round() as usize).clamp(0, array_len - 1) -} diff --git a/cosmic-theme/src/steps.rs b/cosmic-theme/src/steps.rs index 5407a584..58117e5d 100644 --- a/cosmic-theme/src/steps.rs +++ b/cosmic-theme/src/steps.rs @@ -1,21 +1,85 @@ +use std::num::NonZeroUsize; + use almost::equal; -use palette::{convert::FromColorUnclamped, ClampAssign, Oklcha, Srgb, Srgba}; +use palette::{convert::FromColorUnclamped, ClampAssign, FromColor, Oklcha, Srgb, Srgba}; /// Get an array of 100 colors with a specific hue and chroma /// over the full range of lightness. /// Colors which are not valid Srgba will fallback to a color with the nearest valid chroma. -pub fn steps(mut c: Oklcha) -> [Srgba; 100] { - let mut steps = [Srgba::new(0.0, 0.0, 0.0, 1.0); 100]; +pub fn steps(c: C, len: NonZeroUsize) -> Vec +where + Oklcha: FromColor, +{ + let mut c = Oklcha::from_color(c); + let mut steps = Vec::with_capacity(len.get()); - for i in 0..steps.len() { - let lightness = i as f32 / 100.0; + for i in 0..len.get() { + let lightness = i as f32 / (len.get() - 1) as f32; c.l = lightness; - steps[i] = oklch_to_srgba_nearest_chroma(c) + steps.push(oklch_to_srgba_nearest_chroma(c)) } - steps } +/// get the index for a new color some steps away from a base color +pub fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool) -> Option { + if is_dark { + base_index.checked_add(steps) + } else { + base_index.checked_sub(steps) + } + .filter(|i| *i < step_len) +} + +/// get color given a base and some steps +pub fn get_color( + base_index: usize, + steps: usize, + step_array: &Vec, + is_dark: bool, + fallback: &Srgba, +) -> Srgba { + assert!(step_array.len() == 100); + get_index(base_index, steps, step_array.len(), is_dark) + .and_then(|i| step_array.get(i).cloned()) + .unwrap_or_else(|| fallback.to_owned()) +} + +/// get text color given a base background color +pub fn get_text( + base_index: usize, + step_array: &Vec, + is_dark: bool, + fallback: &Srgba, + tint_array: Option<&Vec>, +) -> Srgba { + assert!(step_array.len() == 100); + let step_array = if let Some(tint_array) = tint_array { + assert!(tint_array.len() == 100); + tint_array + } else { + step_array + }; + let Some(index) = get_index(base_index, 70, step_array.len(), is_dark).or_else(|| get_index(base_index, 50, step_array.len(), is_dark)) else { + return fallback.to_owned(); + }; + + step_array + .get(index) + .cloned() + .unwrap_or_else(|| fallback.to_owned()) +} + +/// get the index into the steps array for a given color +/// the index is the lightness value of the color converted to Oklcha, scaled to the range [0, 100] +pub fn color_index(c: C, array_len: usize) -> usize +where + Oklcha: FromColor, +{ + let c = Oklcha::from_color(c); + ((c.l * array_len as f32).round() as usize).clamp(0, array_len - 1) +} + /// find the nearest chroma which makes our color a valid color in Srgba pub fn oklch_to_srgba_nearest_chroma(mut c: Oklcha) -> Srgba { let mut r_chroma = c.chroma; @@ -39,7 +103,7 @@ pub fn oklch_to_srgba_nearest_chroma(mut c: Oklcha) -> Srgba { c.chroma = (c.chroma + l_chroma) / 2.0; } } - Srgba::from_color_unclamped(c) + Srgba::from_color(c) } /// checks that the color is valid srgb