feat: apply tints
This commit is contained in:
parent
4c6912d351
commit
c819f94e74
2 changed files with 123 additions and 63 deletions
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
steps::steps, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing,
|
steps::*, Component, Container, CornerRadii, CosmicPalette, CosmicPaletteInner, Spacing,
|
||||||
DARK_PALETTE, LIGHT_PALETTE, NAME, THEME_DIR,
|
DARK_PALETTE, LIGHT_PALETTE, NAME, THEME_DIR,
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
use cosmic_config::{Config, ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||||
use directories::{BaseDirsExt, ProjectDirsExt};
|
use directories::{BaseDirsExt, ProjectDirsExt};
|
||||||
use palette::{FromColor, Oklcha, Srgb, Srgba};
|
use palette::{Srgb, Srgba};
|
||||||
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
fmt,
|
fmt,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Write,
|
io::Write,
|
||||||
|
num::NonZeroUsize,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -580,14 +581,34 @@ impl ThemeBuilder {
|
||||||
accent,
|
accent,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
let is_dark = palette.is_dark();
|
||||||
|
let is_high_contrast = palette.is_high_contrast();
|
||||||
|
|
||||||
if let Some(accent) = accent {
|
if let Some(accent) = accent {
|
||||||
palette.as_mut().accent = accent.into();
|
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();
|
if let Some(neutral_tint) = neutral_tint {
|
||||||
let is_high_contrast = palette.is_high_contrast();
|
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 {
|
if let Some(accent) = accent {
|
||||||
palette.as_mut().accent = accent.into();
|
palette.as_mut().accent = accent.into();
|
||||||
|
|
@ -599,8 +620,7 @@ impl ThemeBuilder {
|
||||||
} else {
|
} else {
|
||||||
p_ref.gray_1.clone()
|
p_ref.gray_1.clone()
|
||||||
};
|
};
|
||||||
let ok_bg = Oklcha::from_color(bg);
|
let step_array = steps(bg, NonZeroUsize::new(100).unwrap());
|
||||||
let step_array = steps(ok_bg);
|
|
||||||
|
|
||||||
let bg_index = color_index(bg, step_array.len());
|
let bg_index = color_index(bg, step_array.len());
|
||||||
let primary_container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
|
let primary_container_bg = if let Some(primary_container_bg_color) = primary_container_bg {
|
||||||
|
|
@ -621,6 +641,7 @@ impl ThemeBuilder {
|
||||||
&step_array,
|
&step_array,
|
||||||
is_dark,
|
is_dark,
|
||||||
&p_ref.neutral_8,
|
&p_ref.neutral_8,
|
||||||
|
text_steps_array.as_ref(),
|
||||||
);
|
);
|
||||||
let bg_component = Component::component(
|
let bg_component = Component::component(
|
||||||
bg_component,
|
bg_component,
|
||||||
|
|
@ -637,6 +658,7 @@ impl ThemeBuilder {
|
||||||
&step_array,
|
&step_array,
|
||||||
is_dark,
|
is_dark,
|
||||||
&p_ref.neutral_8,
|
&p_ref.neutral_8,
|
||||||
|
text_steps_array.as_ref(),
|
||||||
);
|
);
|
||||||
let primary_component = Component::component(
|
let primary_component = Component::component(
|
||||||
primary_component,
|
primary_component,
|
||||||
|
|
@ -654,6 +676,7 @@ impl ThemeBuilder {
|
||||||
&step_array,
|
&step_array,
|
||||||
is_dark,
|
is_dark,
|
||||||
&p_ref.neutral_10,
|
&p_ref.neutral_10,
|
||||||
|
text_steps_array.as_ref(),
|
||||||
);
|
);
|
||||||
let secondary_component = Component::component(
|
let secondary_component = Component::component(
|
||||||
secondary_component,
|
secondary_component,
|
||||||
|
|
@ -668,17 +691,35 @@ impl ThemeBuilder {
|
||||||
background: Container::new(
|
background: Container::new(
|
||||||
bg_component,
|
bg_component,
|
||||||
bg,
|
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: Container::new(
|
||||||
primary_component,
|
primary_component,
|
||||||
primary_container_bg,
|
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: Container::new(
|
||||||
secondary_component,
|
secondary_component,
|
||||||
secondary_container_bg,
|
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(
|
accent: Component::colored_component(
|
||||||
p_ref.accent.to_owned(),
|
p_ref.accent.to_owned(),
|
||||||
|
|
@ -711,48 +752,3 @@ impl ThemeBuilder {
|
||||||
theme
|
theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_index(base_index: usize, steps: usize, step_len: usize, is_dark: bool) -> Option<usize> {
|
|
||||||
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: C, array_len: usize) -> usize
|
|
||||||
where
|
|
||||||
Oklcha: FromColor<C>,
|
|
||||||
{
|
|
||||||
let c = Oklcha::from_color(c);
|
|
||||||
((c.l * array_len as f32).round() as usize).clamp(0, array_len - 1)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,85 @@
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use almost::equal;
|
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
|
/// Get an array of 100 colors with a specific hue and chroma
|
||||||
/// over the full range of lightness.
|
/// over the full range of lightness.
|
||||||
/// Colors which are not valid Srgba will fallback to a color with the nearest valid chroma.
|
/// Colors which are not valid Srgba will fallback to a color with the nearest valid chroma.
|
||||||
pub fn steps(mut c: Oklcha) -> [Srgba; 100] {
|
pub fn steps<C>(c: C, len: NonZeroUsize) -> Vec<Srgba>
|
||||||
let mut steps = [Srgba::new(0.0, 0.0, 0.0, 1.0); 100];
|
where
|
||||||
|
Oklcha: FromColor<C>,
|
||||||
|
{
|
||||||
|
let mut c = Oklcha::from_color(c);
|
||||||
|
let mut steps = Vec::with_capacity(len.get());
|
||||||
|
|
||||||
for i in 0..steps.len() {
|
for i in 0..len.get() {
|
||||||
let lightness = i as f32 / 100.0;
|
let lightness = i as f32 / (len.get() - 1) as f32;
|
||||||
c.l = lightness;
|
c.l = lightness;
|
||||||
steps[i] = oklch_to_srgba_nearest_chroma(c)
|
steps.push(oklch_to_srgba_nearest_chroma(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
steps
|
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<usize> {
|
||||||
|
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<Srgba>,
|
||||||
|
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<Srgba>,
|
||||||
|
is_dark: bool,
|
||||||
|
fallback: &Srgba,
|
||||||
|
tint_array: Option<&Vec<Srgba>>,
|
||||||
|
) -> 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: C, array_len: usize) -> usize
|
||||||
|
where
|
||||||
|
Oklcha: FromColor<C>,
|
||||||
|
{
|
||||||
|
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
|
/// find the nearest chroma which makes our color a valid color in Srgba
|
||||||
pub fn oklch_to_srgba_nearest_chroma(mut c: Oklcha) -> Srgba {
|
pub fn oklch_to_srgba_nearest_chroma(mut c: Oklcha) -> Srgba {
|
||||||
let mut r_chroma = c.chroma;
|
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;
|
c.chroma = (c.chroma + l_chroma) / 2.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Srgba::from_color_unclamped(c)
|
Srgba::from_color(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// checks that the color is valid srgb
|
/// checks that the color is valid srgb
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue