gpui_kit_material_color/dynamic/
seed.rs1use super::blend::harmonize;
10use super::color::{ContrastCurve, DynamicRole, foreground_tone, with_tone_memo};
11use super::contrast::ratio_of_tones;
12use super::hct::Hct;
13use super::palette::TonalPalette;
14use super::scheme::{DynamicScheme, SpecVersion, Variant};
15use crate::scheme::Scheme;
16use crate::themes::{Contrast, REFERENCE_ROLES, ThemeId, ThemeMode};
17use crate::{Color, ColorRole};
18
19#[derive(Clone, Copy, Debug, PartialEq)]
30pub struct SeedScheme {
31 pub seed: u32,
33 pub variant: Variant,
35 pub dark: bool,
37 pub contrast: f64,
39 pub spec: SpecVersion,
41}
42
43impl SeedScheme {
44 pub const fn new(seed: u32) -> Self {
46 Self {
47 seed: seed & 0xff_ffff,
48 variant: Variant::TonalSpot,
49 dark: false,
50 contrast: 0.,
51 spec: SpecVersion::Spec2021,
52 }
53 }
54
55 pub const fn variant(mut self, variant: Variant) -> Self {
57 self.variant = variant;
58 self
59 }
60
61 pub const fn dark(mut self, dark: bool) -> Self {
63 self.dark = dark;
64 self
65 }
66
67 pub const fn contrast(mut self, contrast: f64) -> Self {
69 self.contrast = contrast;
70 self
71 }
72
73 pub const fn spec(mut self, spec: SpecVersion) -> Self {
75 self.spec = spec;
76 self
77 }
78
79 pub fn parse_hex(text: &str) -> Option<u32> {
81 let digits = text.trim();
82 let digits = digits.strip_prefix('#').unwrap_or(digits);
83 if !digits.chars().all(|digit| digit.is_ascii_hexdigit()) {
84 return None;
85 }
86 let value = u32::from_str_radix(digits, 16).ok()?;
87 match digits.len() {
88 6 => Some(value),
89 3 => {
90 let double = |nibble: u32| (nibble & 0xf) * 0x11;
91 Some(double(value >> 8) << 16 | double(value >> 4) << 8 | double(value))
92 }
93 _ => None,
94 }
95 }
96
97 pub fn dynamic(&self) -> DynamicScheme {
99 DynamicScheme::new(
100 Hct::from_argb(0xff00_0000 | self.seed),
101 self.variant,
102 self.dark,
103 self.contrast,
104 self.spec,
105 )
106 }
107
108 pub fn scheme(&self) -> Scheme {
110 let dynamic = self.dynamic();
111 let colors = with_tone_memo(|| {
112 REFERENCE_ROLES.map(|role| {
113 let argb = match upstream_role(role) {
114 Some(upstream) => dynamic.argb(upstream),
115 None => self.status(&dynamic, role),
116 };
117 argb & 0xff_ffff
118 })
119 });
120 Scheme::from_reference(&colors, self.dark)
121 }
122
123 fn status(&self, dynamic: &DynamicScheme, role: ColorRole) -> u32 {
124 let (source_role, foreground) = match role {
125 ColorRole::SUCCESS => (ColorRole::SUCCESS, false),
126 ColorRole::ON_SUCCESS => (ColorRole::SUCCESS, true),
127 ColorRole::WARNING => (ColorRole::WARNING, false),
128 ColorRole::ON_WARNING => (ColorRole::WARNING, true),
129 ColorRole::ON_INFO => (ColorRole::INFO, true),
130 _ => (ColorRole::INFO, false),
131 };
132 let baseline = ThemeId::Baseline.scheme(ThemeMode::Light, Contrast::Standard);
133 let palette = TonalPalette::from_argb(harmonize(
134 argb(baseline.get(source_role)),
135 0xff00_0000 | self.seed,
136 ));
137 let surface = if self.dark {
139 DynamicRole::SurfaceBright
140 } else {
141 DynamicRole::SurfaceDim
142 };
143 let mut tone = if self.dark { 80. } else { 40. };
144 let needed = ContrastCurve::new(3., 4.5, 7., 7.).get(dynamic.contrast_level);
145 let surface_tone = dynamic.tone(surface);
146 if ratio_of_tones(surface_tone, tone) < needed {
147 tone = foreground_tone(surface_tone, needed);
148 }
149 if !foreground {
150 return palette.tone(tone);
151 }
152 let mut on_tone = if self.dark { 20. } else { 100. };
153 let needed = ContrastCurve::new(4.5, 7., 11., 21.).get(dynamic.contrast_level);
154 if ratio_of_tones(tone, on_tone) < needed {
155 on_tone = foreground_tone(tone, needed);
156 }
157 palette.tone(on_tone)
158 }
159}
160
161fn argb(color: Color) -> u32 {
162 let channel = |value: f32| (value.clamp(0., 1.) * 255.).round() as u32;
163 0xff00_0000 | channel(color.r) << 16 | channel(color.g) << 8 | channel(color.b)
164}
165
166fn upstream_role(role: ColorRole) -> Option<DynamicRole> {
168 use DynamicRole as D;
169 Some(match role {
170 ColorRole::ERROR => D::Error,
171 ColorRole::ERROR_CONTAINER => D::ErrorContainer,
172 ColorRole::INVERSE_ON_SURFACE => D::InverseOnSurface,
173 ColorRole::INVERSE_PRIMARY => D::InversePrimary,
174 ColorRole::INVERSE_SURFACE => D::InverseSurface,
175 ColorRole::ON_ERROR => D::OnError,
176 ColorRole::ON_ERROR_CONTAINER => D::OnErrorContainer,
177 ColorRole::ON_PRIMARY => D::OnPrimary,
178 ColorRole::ON_PRIMARY_CONTAINER => D::OnPrimaryContainer,
179 ColorRole::ON_PRIMARY_FIXED => D::OnPrimaryFixed,
180 ColorRole::ON_PRIMARY_FIXED_VARIANT => D::OnPrimaryFixedVariant,
181 ColorRole::ON_SECONDARY => D::OnSecondary,
182 ColorRole::ON_SECONDARY_CONTAINER => D::OnSecondaryContainer,
183 ColorRole::ON_SECONDARY_FIXED => D::OnSecondaryFixed,
184 ColorRole::ON_SECONDARY_FIXED_VARIANT => D::OnSecondaryFixedVariant,
185 ColorRole::ON_SURFACE => D::OnSurface,
186 ColorRole::ON_SURFACE_VARIANT => D::OnSurfaceVariant,
187 ColorRole::ON_TERTIARY => D::OnTertiary,
188 ColorRole::ON_TERTIARY_CONTAINER => D::OnTertiaryContainer,
189 ColorRole::ON_TERTIARY_FIXED => D::OnTertiaryFixed,
190 ColorRole::ON_TERTIARY_FIXED_VARIANT => D::OnTertiaryFixedVariant,
191 ColorRole::OUTLINE => D::Outline,
192 ColorRole::OUTLINE_VARIANT => D::OutlineVariant,
193 ColorRole::PRIMARY => D::Primary,
194 ColorRole::PRIMARY_CONTAINER => D::PrimaryContainer,
195 ColorRole::PRIMARY_FIXED => D::PrimaryFixed,
196 ColorRole::PRIMARY_FIXED_DIM => D::PrimaryFixedDim,
197 ColorRole::SCRIM => D::Scrim,
198 ColorRole::SECONDARY => D::Secondary,
199 ColorRole::SECONDARY_CONTAINER => D::SecondaryContainer,
200 ColorRole::SECONDARY_FIXED => D::SecondaryFixed,
201 ColorRole::SECONDARY_FIXED_DIM => D::SecondaryFixedDim,
202 ColorRole::SHADOW => D::Shadow,
203 ColorRole::SURFACE => D::Surface,
204 ColorRole::SURFACE_BRIGHT => D::SurfaceBright,
205 ColorRole::SURFACE_CONTAINER => D::SurfaceContainer,
206 ColorRole::SURFACE_CONTAINER_HIGH => D::SurfaceContainerHigh,
207 ColorRole::SURFACE_CONTAINER_HIGHEST => D::SurfaceContainerHighest,
208 ColorRole::SURFACE_CONTAINER_LOW => D::SurfaceContainerLow,
209 ColorRole::SURFACE_CONTAINER_LOWEST => D::SurfaceContainerLowest,
210 ColorRole::SURFACE_DIM => D::SurfaceDim,
211 ColorRole::SURFACE_VARIANT => D::SurfaceVariant,
212 ColorRole::TERTIARY => D::Tertiary,
213 ColorRole::TERTIARY_CONTAINER => D::TertiaryContainer,
214 ColorRole::TERTIARY_FIXED => D::TertiaryFixed,
215 ColorRole::TERTIARY_FIXED_DIM => D::TertiaryFixedDim,
216 _ => return None,
217 })
218}