Separate resolved theme values from configuration resolution
This commit is contained in:
1 parent
4c3e884438
commit
8195b82073
13 files changed
+509
-477
No files matched your search
@@ -6,6 +6,7 @@ license.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
ferese-theme-model = { path = "../ferese-theme-model" }
|
||||
jiff.workspace = true
|
||||
kdl = "6.7.1"
|
||||
serde.workspace = true
|
||||
|
||||
@@ -1,54 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::theme::{Appearance, Tokens};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Palette {
|
||||
pub background: String,
|
||||
pub bar: String,
|
||||
pub surface: String,
|
||||
pub text: String,
|
||||
pub muted: String,
|
||||
pub accent: String,
|
||||
}
|
||||
|
||||
impl From<&Tokens> for Palette {
|
||||
fn from(tokens: &Tokens) -> Self {
|
||||
Self {
|
||||
background: tokens.colors.surface_base.clone(),
|
||||
bar: tokens.surface.bar.background.clone(),
|
||||
surface: tokens.colors.surface_raised.clone(),
|
||||
text: tokens.colors.text_primary.clone(),
|
||||
muted: tokens.colors.text_muted.clone(),
|
||||
accent: tokens.colors.accent.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Family {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub light: Option<Palette>,
|
||||
pub dark: Option<Palette>,
|
||||
}
|
||||
|
||||
impl Family {
|
||||
pub fn palette(&self, appearance: Appearance) -> Option<&Palette> {
|
||||
match appearance {
|
||||
Appearance::Light => self.light.as_ref(),
|
||||
Appearance::Dark => self.dark.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn availability(&self) -> &'static str {
|
||||
match (self.light.is_some(), self.dark.is_some()) {
|
||||
(true, true) => "light and dark",
|
||||
(true, false) => "light only",
|
||||
_ => "dark only",
|
||||
}
|
||||
}
|
||||
}
|
||||
use crate::theme::Appearance;
|
||||
pub use ferese_theme_model::families::{Family, Palette};
|
||||
|
||||
pub const BUILTINS: [(&str, &str, &str, &str); 6] = [
|
||||
("ferese-blue", "Ferese Blue", "ferese-blue-light", "ferese-blue"),
|
||||
|
||||
@@ -8,34 +8,11 @@ use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::Document;
|
||||
pub use ferese_theme_model::*;
|
||||
|
||||
pub const SCHEMA_VERSION: u32 = 2;
|
||||
pub const TRANSITION_MS: u64 = 250;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Appearance {
|
||||
Light,
|
||||
#[default]
|
||||
Dark,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Mode {
|
||||
Light,
|
||||
#[default]
|
||||
Dark,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[serde(default)]
|
||||
pub struct Accessibility {
|
||||
pub increase_contrast: bool,
|
||||
pub reduce_transparency: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(default)]
|
||||
pub struct Selection {
|
||||
@@ -225,105 +202,8 @@ impl Default for Policy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Colors {
|
||||
pub surface_base: String,
|
||||
pub surface_raised: String,
|
||||
pub application_background: String,
|
||||
pub text_primary: String,
|
||||
pub text_muted: String,
|
||||
pub accent: String,
|
||||
pub on_accent: String,
|
||||
pub border: String,
|
||||
pub shadow: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Material {
|
||||
pub style: String,
|
||||
pub opacity: f64,
|
||||
pub blur_radius: f64,
|
||||
pub tint_strength: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Geometry {
|
||||
pub border_width: f64,
|
||||
pub focus_ring_width: f64,
|
||||
pub window_radius: f64,
|
||||
pub shell_radius: f64,
|
||||
pub top_bar_height: f64,
|
||||
pub top_bar_margin_top: i32,
|
||||
pub top_bar_margin_horizontal: i32,
|
||||
pub top_bar_window_gap: i32,
|
||||
pub panel_padding: f64,
|
||||
pub control_gap: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Typography {
|
||||
pub font_family: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Background {
|
||||
pub path: Option<PathBuf>,
|
||||
pub lock_path: Option<PathBuf>,
|
||||
pub mode: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct BarSurface {
|
||||
pub background: String,
|
||||
pub text_primary: String,
|
||||
pub text_muted: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Surfaces {
|
||||
pub bar: BarSurface,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct SoftShadow {
|
||||
pub offset_y: f64,
|
||||
pub blur: f64,
|
||||
pub opacity: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Shadows {
|
||||
pub soft: SoftShadow,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Gradient {
|
||||
pub from: String,
|
||||
pub to: String,
|
||||
pub angle: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Paint {
|
||||
pub gradient: Option<Gradient>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Tokens {
|
||||
pub colors: Colors,
|
||||
pub material: Material,
|
||||
pub geometry: Geometry,
|
||||
pub typography: Typography,
|
||||
pub background: Background,
|
||||
pub surface: Surfaces,
|
||||
pub shadow: Shadows,
|
||||
pub border: Paint,
|
||||
pub focus_ring: Paint,
|
||||
}
|
||||
|
||||
impl Default for Tokens {
|
||||
fn default() -> Self {
|
||||
serde_json::from_value(json!({
|
||||
pub fn default_tokens() -> Tokens {
|
||||
serde_json::from_value(json!({
|
||||
"colors": {"surface_base":"#111821", "surface_raised":"#1E2530", "application_background":"#171E27", "text_primary":"#F4F7FB", "text_muted":"#8793A2",
|
||||
"accent":"#3D7BE6", "on_accent":"#FFFFFF", "border":"#FFFFFF18", "shadow":"#00000055"},
|
||||
"material":{"style":"solid","opacity":0.78,"blur_radius":12.0,"tint_strength":0.5},
|
||||
@@ -337,27 +217,15 @@ impl Default for Tokens {
|
||||
"border":{"gradient":null},"focus_ring":{"gradient":null}
|
||||
}))
|
||||
.expect("valid built-in theme")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct ResolvedTheme {
|
||||
pub appearance: Appearance,
|
||||
pub tokens: Tokens,
|
||||
pub requested_accent: String,
|
||||
pub accessibility: Accessibility,
|
||||
pub reduced_motion: bool,
|
||||
}
|
||||
|
||||
impl Default for ResolvedTheme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
appearance: Appearance::Dark,
|
||||
requested_accent: "#3D7BE6".into(),
|
||||
tokens: Tokens::default(),
|
||||
accessibility: Accessibility::default(),
|
||||
reduced_motion: false,
|
||||
}
|
||||
pub fn default_theme() -> ResolvedTheme {
|
||||
ResolvedTheme {
|
||||
appearance: Appearance::Dark,
|
||||
requested_accent: "#3D7BE6".into(),
|
||||
tokens: default_tokens(),
|
||||
accessibility: Accessibility::default(),
|
||||
reduced_motion: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,8 +250,8 @@ impl Default for Snapshot {
|
||||
version: SCHEMA_VERSION,
|
||||
revision: 0,
|
||||
mode: Mode::Dark,
|
||||
theme: ResolvedTheme::default(),
|
||||
presented: ResolvedTheme::default(),
|
||||
theme: default_theme(),
|
||||
presented: default_theme(),
|
||||
warnings: vec![],
|
||||
error: None,
|
||||
families: crate::families::builtins(),
|
||||
@@ -711,7 +579,7 @@ pub fn preset(name: &str, appearance: Appearance) -> Result<Tokens, String> {
|
||||
if preset.appearance != appearance {
|
||||
return Err(format!("Preset {name} does not support {appearance:?}"));
|
||||
}
|
||||
let mut tokens = Tokens::default();
|
||||
let mut tokens = default_tokens();
|
||||
tokens.colors.surface_base = preset.base.into();
|
||||
tokens.colors.surface_raised = preset.raised.into();
|
||||
tokens.colors.application_background = preset.application_background.into();
|
||||
@@ -737,7 +605,7 @@ pub fn preset(name: &str, appearance: Appearance) -> Result<Tokens, String> {
|
||||
tokens.shadow.soft.opacity = preset.shadow_opacity;
|
||||
return Ok(tokens);
|
||||
}
|
||||
let mut t = Tokens::default();
|
||||
let mut t = default_tokens();
|
||||
let (base, text, muted, accent, border) = match name {
|
||||
"monochrome" => ("#101012", "#EDEDF0", "#97979F", "#E5E5E5", "#FFFFFF18"),
|
||||
"dracula" => ("#282A36", "#F8F8F2", "#A4ADCD", "#BD93F9", "#44475A"),
|
||||
@@ -863,103 +731,6 @@ fn validate(tokens: &Tokens) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rgba(s: &str) -> Result<[f64; 4], String> {
|
||||
let hex = s.strip_prefix('#').ok_or_else(|| format!("Invalid color: {s}"))?;
|
||||
if !hex.is_ascii() || !matches!(hex.len(), 6 | 8) {
|
||||
return Err(format!("Invalid color: {s}"));
|
||||
}
|
||||
let mut out = [1.; 4];
|
||||
for (i, c) in out.iter_mut().enumerate().take(hex.len() / 2) {
|
||||
*c = f64::from(u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).map_err(|_| format!("Invalid color: {s}"))?)
|
||||
/ 255.;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
pub fn hex(c: [f64; 4]) -> String {
|
||||
format!(
|
||||
"#{:02X}{:02X}{:02X}{:02X}",
|
||||
(c[0].clamp(0., 1.) * 255.).round() as u8,
|
||||
(c[1].clamp(0., 1.) * 255.).round() as u8,
|
||||
(c[2].clamp(0., 1.) * 255.).round() as u8,
|
||||
(c[3].clamp(0., 1.) * 255.).round() as u8
|
||||
)
|
||||
}
|
||||
|
||||
pub fn luminance(c: [f64; 4]) -> f64 {
|
||||
let linear = |v: f64| {
|
||||
if v <= 0.04045 {
|
||||
v / 12.92
|
||||
} else {
|
||||
((v + 0.055) / 1.055).powf(2.4)
|
||||
}
|
||||
};
|
||||
0.2126 * linear(c[0]) + 0.7152 * linear(c[1]) + 0.0722 * linear(c[2])
|
||||
}
|
||||
|
||||
pub fn contrast(a: [f64; 4], b: [f64; 4]) -> f64 {
|
||||
let (a, b) = (luminance(a), luminance(b));
|
||||
(a.max(b) + 0.05) / (a.min(b) + 0.05)
|
||||
}
|
||||
|
||||
pub fn composite(fg: [f64; 4], bg: [f64; 4]) -> [f64; 4] {
|
||||
let mut c = [0.; 4];
|
||||
for i in 0..3 {
|
||||
c[i] = fg[i] * fg[3] + bg[i] * (1. - fg[3]);
|
||||
}
|
||||
c[3] = 1.;
|
||||
c
|
||||
}
|
||||
|
||||
fn blend(a: [f64; 4], b: [f64; 4], p: f64) -> [f64; 4] {
|
||||
std::array::from_fn(|i| a[i] + (b[i] - a[i]) * p)
|
||||
}
|
||||
|
||||
pub fn readable(preferred: [f64; 4], background: [f64; 4], minimum: f64) -> [f64; 4] {
|
||||
let mut preferred = preferred;
|
||||
preferred[3] = 1.;
|
||||
if contrast(preferred, background) >= minimum {
|
||||
return preferred;
|
||||
}
|
||||
let black = [0., 0., 0., 1.];
|
||||
let white = [1., 1., 1., 1.];
|
||||
let target = if contrast(black, background) > contrast(white, background) {
|
||||
black
|
||||
} else {
|
||||
white
|
||||
};
|
||||
for step in 1..=100 {
|
||||
let c = blend(preferred, target, f64::from(step) / 100.);
|
||||
if contrast(rgba(&hex(c)).expect("generated color"), background) >= minimum {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
target
|
||||
}
|
||||
|
||||
fn readable_across(mut preferred: [f64; 4], backgrounds: &[[f64; 4]], minimum: f64) -> [f64; 4] {
|
||||
preferred[3] = 1.;
|
||||
let score = |color| {
|
||||
backgrounds
|
||||
.iter()
|
||||
.map(|background| contrast(color, *background))
|
||||
.fold(f64::INFINITY, f64::min)
|
||||
};
|
||||
if score(preferred) >= minimum {
|
||||
return preferred;
|
||||
}
|
||||
let black = [0., 0., 0., 1.];
|
||||
let white = [1.; 4];
|
||||
let target = if score(black) > score(white) { black } else { white };
|
||||
for step in 1..=100 {
|
||||
let color = rgba(&hex(blend(preferred, target, f64::from(step) / 100.))).unwrap();
|
||||
if score(color) >= minimum {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
target
|
||||
}
|
||||
|
||||
fn transform(t: &mut Tokens, accessibility: &Accessibility, warnings: &mut Vec<String>) {
|
||||
let surface = rgba(&t.colors.surface_base).expect("validated surface");
|
||||
let mut requested = rgba(&t.colors.accent).expect("validated accent");
|
||||
@@ -1075,180 +846,6 @@ fn resolve_material_contrast(tokens: &mut Tokens, minimum: f64) {
|
||||
}
|
||||
}
|
||||
|
||||
fn material_bounds(mut surface: [f64; 4], opacity: f64) -> [[f64; 4]; 2] {
|
||||
surface[3] = opacity;
|
||||
[composite(surface, [0., 0., 0., 1.]), composite(surface, [1.; 4])]
|
||||
}
|
||||
|
||||
fn material_foreground(preferred: [f64; 4], backgrounds: [[f64; 4]; 2]) -> [f64; 4] {
|
||||
let black = [0., 0., 0., 1.];
|
||||
let white = [1.; 4];
|
||||
let (target, worst) = if contrast(black, backgrounds[0]) >= contrast(white, backgrounds[1]) {
|
||||
(black, backgrounds[0])
|
||||
} else {
|
||||
(white, backgrounds[1])
|
||||
};
|
||||
let result = readable(preferred, worst, 4.5);
|
||||
let outside = luminance(result) < luminance(backgrounds[0]) || luminance(result) > luminance(backgrounds[1]);
|
||||
if outside
|
||||
&& backgrounds
|
||||
.iter()
|
||||
.all(|background| contrast(rgba(&hex(result)).unwrap(), *background) >= 4.5)
|
||||
{
|
||||
result
|
||||
} else {
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvedTheme {
|
||||
/// Bound contrast across all backdrops instead of interpolating foregrounds.
|
||||
pub fn transition(&self, to: &Self, progress: f64) -> Self {
|
||||
if to.reduced_motion || self.accessibility != to.accessibility || progress >= 1.0 {
|
||||
return to.clone();
|
||||
}
|
||||
if progress <= 0.0 || self == to {
|
||||
return self.clone();
|
||||
}
|
||||
let p = progress.clamp(0., 1.);
|
||||
let p = p * p * (3. - 2. * p);
|
||||
let mut frame = to.clone();
|
||||
let mix = |a: &str, b: &str| hex(blend(rgba(a).unwrap(), rgba(b).unwrap(), p));
|
||||
frame.tokens.colors.surface_base = mix(&self.tokens.colors.surface_base, &to.tokens.colors.surface_base);
|
||||
frame.tokens.colors.surface_raised = mix(&self.tokens.colors.surface_raised, &to.tokens.colors.surface_raised);
|
||||
frame.tokens.colors.application_background = mix(
|
||||
&self.tokens.colors.application_background,
|
||||
&to.tokens.colors.application_background,
|
||||
);
|
||||
frame.tokens.colors.accent = mix(&self.tokens.colors.accent, &to.tokens.colors.accent);
|
||||
frame.tokens.colors.border = mix(&self.tokens.colors.border, &to.tokens.colors.border);
|
||||
frame.tokens.colors.shadow = mix(&self.tokens.colors.shadow, &to.tokens.colors.shadow);
|
||||
frame.tokens.surface.bar.background =
|
||||
mix(&self.tokens.surface.bar.background, &to.tokens.surface.bar.background);
|
||||
frame.tokens.material.opacity =
|
||||
self.tokens.material.opacity + (to.tokens.material.opacity - self.tokens.material.opacity) * p;
|
||||
frame.tokens.material.tint_strength = self.tokens.material.tint_strength
|
||||
+ (to.tokens.material.tint_strength - self.tokens.material.tint_strength) * p;
|
||||
frame.tokens.shadow.soft.opacity =
|
||||
self.tokens.shadow.soft.opacity + (to.tokens.shadow.soft.opacity - self.tokens.shadow.soft.opacity) * p;
|
||||
let preferred = if p < 0.5 { self } else { to };
|
||||
frame.appearance = preferred.appearance;
|
||||
if self.appearance == to.appearance {
|
||||
let body = [
|
||||
rgba(&frame.tokens.colors.surface_base).unwrap(),
|
||||
rgba(&frame.tokens.colors.surface_raised).unwrap(),
|
||||
rgba(&frame.tokens.colors.application_background).unwrap(),
|
||||
];
|
||||
let minimum = if to.accessibility.increase_contrast { 7.0 } else { 4.5 };
|
||||
frame.tokens.colors.text_primary = hex(readable_across(
|
||||
rgba(&preferred.tokens.colors.text_primary).unwrap(),
|
||||
&body,
|
||||
minimum,
|
||||
));
|
||||
frame.tokens.colors.text_muted = hex(readable_across(
|
||||
rgba(&preferred.tokens.colors.text_muted).unwrap(),
|
||||
&body,
|
||||
minimum,
|
||||
));
|
||||
let bar = rgba(&frame.tokens.surface.bar.background).unwrap();
|
||||
frame.tokens.surface.bar.text_primary = hex(readable(
|
||||
rgba(&preferred.tokens.surface.bar.text_primary).unwrap(),
|
||||
bar,
|
||||
minimum,
|
||||
));
|
||||
frame.tokens.surface.bar.text_muted = hex(readable(
|
||||
rgba(&preferred.tokens.surface.bar.text_muted).unwrap(),
|
||||
bar,
|
||||
minimum,
|
||||
));
|
||||
frame.tokens.colors.on_accent = hex(readable(
|
||||
rgba(&preferred.tokens.colors.on_accent).unwrap(),
|
||||
rgba(&frame.tokens.colors.accent).unwrap(),
|
||||
4.5,
|
||||
));
|
||||
return frame;
|
||||
}
|
||||
let base = rgba(&frame.tokens.colors.surface_base).unwrap();
|
||||
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
|
||||
let app = rgba(&frame.tokens.colors.application_background).unwrap();
|
||||
let minimum = [base, raised, app]
|
||||
.into_iter()
|
||||
.map(luminance)
|
||||
.fold(f64::INFINITY, f64::min);
|
||||
let maximum = [base, raised, app].into_iter().map(luminance).fold(0., f64::max);
|
||||
if ((minimum + 0.05) / 0.05).max(1.05 / (maximum + 0.05)) < 4.5 {
|
||||
frame.tokens.colors.surface_raised = frame.tokens.colors.surface_base.clone();
|
||||
frame.tokens.colors.application_background = frame.tokens.colors.surface_base.clone();
|
||||
}
|
||||
let surface = rgba(&frame.tokens.colors.surface_base).unwrap();
|
||||
let bar = rgba(&frame.tokens.surface.bar.background).unwrap();
|
||||
let mut opacity = if frame.tokens.material.style == "solid" {
|
||||
1.
|
||||
} else {
|
||||
frame.tokens.material.opacity * frame.tokens.material.tint_strength
|
||||
};
|
||||
for step in 0..=100 {
|
||||
let candidate = opacity + (1. - opacity) * f64::from(step) / 100.;
|
||||
let backgrounds = material_bounds(surface, candidate);
|
||||
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
|
||||
let app = rgba(&frame.tokens.colors.application_background).unwrap();
|
||||
let all = [backgrounds[0], backgrounds[1], raised, app];
|
||||
let minimum = all.into_iter().map(luminance).fold(f64::INFINITY, f64::min);
|
||||
let maximum = all.into_iter().map(luminance).fold(0., f64::max);
|
||||
let bar = material_bounds(bar, candidate);
|
||||
let readable = ((minimum + 0.05) / 0.05).max(1.05 / (maximum + 0.05)) >= 4.5
|
||||
&& contrast([0., 0., 0., 1.], bar[0]).max(contrast([1.; 4], bar[1])) >= 4.5;
|
||||
if readable {
|
||||
opacity = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if opacity > frame.tokens.material.opacity {
|
||||
frame.tokens.material.opacity = opacity;
|
||||
frame.tokens.material.tint_strength = 1.;
|
||||
} else if frame.tokens.material.opacity > 0. {
|
||||
frame.tokens.material.tint_strength = opacity / frame.tokens.material.opacity;
|
||||
}
|
||||
let surface = material_bounds(surface, opacity);
|
||||
let bar = material_bounds(bar, opacity);
|
||||
let raised = rgba(&frame.tokens.colors.surface_raised).unwrap();
|
||||
let app = rgba(&frame.tokens.colors.application_background).unwrap();
|
||||
let body_backgrounds = [surface[0], surface[1], raised, app];
|
||||
let body_bounds = [
|
||||
*body_backgrounds
|
||||
.iter()
|
||||
.min_by(|a, b| luminance(**a).total_cmp(&luminance(**b)))
|
||||
.unwrap(),
|
||||
*body_backgrounds
|
||||
.iter()
|
||||
.max_by(|a, b| luminance(**a).total_cmp(&luminance(**b)))
|
||||
.unwrap(),
|
||||
];
|
||||
frame.tokens.colors.text_primary = hex(material_foreground(
|
||||
rgba(&preferred.tokens.colors.text_primary).unwrap(),
|
||||
body_bounds,
|
||||
));
|
||||
frame.tokens.colors.text_muted = hex(material_foreground(
|
||||
rgba(&preferred.tokens.colors.text_muted).unwrap(),
|
||||
body_bounds,
|
||||
));
|
||||
frame.tokens.surface.bar.text_primary = hex(material_foreground(
|
||||
rgba(&preferred.tokens.surface.bar.text_primary).unwrap(),
|
||||
bar,
|
||||
));
|
||||
frame.tokens.surface.bar.text_muted = hex(material_foreground(
|
||||
rgba(&preferred.tokens.surface.bar.text_muted).unwrap(),
|
||||
bar,
|
||||
));
|
||||
frame.tokens.colors.on_accent = hex(readable(
|
||||
rgba(&preferred.tokens.colors.on_accent).unwrap(),
|
||||
rgba(&frame.tokens.colors.accent).unwrap(),
|
||||
4.5,
|
||||
));
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
pub type ImportedFamily = (crate::families::Family, (Option<Tokens>, Option<Tokens>), Vec<String>);
|
||||
|
||||
pub fn import_family(id: &str, source: &str) -> Result<ImportedFamily, String> {
|
||||
@@ -1758,6 +1355,6 @@ mod tests {
|
||||
.theme;
|
||||
assert_eq!(light.appearance, Appearance::Light);
|
||||
light.reduced_motion = true;
|
||||
assert_eq!(ResolvedTheme::default().transition(&light, 0.), light);
|
||||
assert_eq!(default_theme().transition(&light, 0.), light);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user