feat(color-module): 添加 SchemeSelect 枚举并更新 Baseline 和 QScheme2 结构以支持新颜色选择逻辑

This commit is contained in:
徐涛
2026-01-15 08:31:01 +08:00
parent a25d1b03dd
commit 083c3d87a9
4 changed files with 175 additions and 116 deletions
@@ -5,7 +5,7 @@ use palette::Oklch;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr}; use serde_repr::{Deserialize_repr, Serialize_repr};
use strum::Display; use strum::Display;
use wasm_bindgen::{prelude::wasm_bindgen, JsError, JsValue}; use wasm_bindgen::{JsError, JsValue, prelude::wasm_bindgen};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Serialize, Deserialize)]
#[wasm_bindgen] #[wasm_bindgen]
@@ -103,6 +103,7 @@ pub struct SchemeSetting {
pub dark_convert: ColorShifting, pub dark_convert: ColorShifting,
pub expand_method: ColorExpand, pub expand_method: ColorExpand,
pub wacg_follows: WACGSetting, pub wacg_follows: WACGSetting,
pub scheme_select: SchemeSelect,
} }
#[derive(Debug, Clone, Copy, Display, Sequence, Serialize_repr, Deserialize_repr)] #[derive(Debug, Clone, Copy, Display, Sequence, Serialize_repr, Deserialize_repr)]
@@ -142,6 +143,33 @@ pub enum WACGSetting {
HighContrast, HighContrast,
} }
#[derive(Debug, Clone, Copy, Display, Sequence, Serialize_repr, Deserialize_repr)]
#[wasm_bindgen]
#[repr(u8)]
pub enum SchemeSelect {
Both,
Light,
Dark,
}
impl SchemeSelect {
pub fn label(&self) -> String {
match self {
SchemeSelect::Both => "Both".to_string(),
SchemeSelect::Light => "Light".to_string(),
SchemeSelect::Dark => "Dark".to_string(),
}
}
pub fn includes_light(&self) -> bool {
matches!(self, SchemeSelect::Both | SchemeSelect::Light)
}
pub fn includes_dark(&self) -> bool {
matches!(self, SchemeSelect::Both | SchemeSelect::Dark)
}
}
impl WACGSetting { impl WACGSetting {
pub fn label(&self) -> String { pub fn label(&self) -> String {
match self { match self {
@@ -178,6 +206,7 @@ impl Default for SchemeSetting {
}, },
expand_method: ColorExpand::AnalogousAndComplementary, expand_method: ColorExpand::AnalogousAndComplementary,
wacg_follows: WACGSetting::AutomaticAAA, wacg_follows: WACGSetting::AutomaticAAA,
scheme_select: SchemeSelect::Both,
} }
} }
} }
@@ -193,6 +222,7 @@ impl SchemeSetting {
dark_convert: ColorShifting, dark_convert: ColorShifting,
expand_method: ColorExpand, expand_method: ColorExpand,
wacg_follows: WACGSetting, wacg_follows: WACGSetting,
scheme_select: SchemeSelect,
) -> Self { ) -> Self {
SchemeSetting { SchemeSetting {
hover, hover,
@@ -202,6 +232,7 @@ impl SchemeSetting {
dark_convert, dark_convert,
expand_method, expand_method,
wacg_follows, wacg_follows,
scheme_select,
} }
} }
+87 -40
View File
@@ -104,8 +104,7 @@ pub struct Baseline {
pub accent: Option<ColorUnit>, pub accent: Option<ColorUnit>,
pub neutral: ColorSet, pub neutral: ColorSet,
pub neutral_variant: ColorSet, pub neutral_variant: ColorSet,
pub surface: ColorSet, pub surface: [ColorSet; 6],
pub surface_variant: ColorSet,
#[serde(serialize_with = "crate::schemes::q_style_2::swatch::serialize_neutral_swatch")] #[serde(serialize_with = "crate::schemes::q_style_2::swatch::serialize_neutral_swatch")]
pub neutral_swatch: Arc<NeutralSwatch>, pub neutral_swatch: Arc<NeutralSwatch>,
pub danger: ColorUnit, pub danger: ColorUnit,
@@ -214,8 +213,6 @@ impl Baseline {
let neutral_color = neutral_swatch.get(if is_dark { 0.35 } else { 0.65 }); let neutral_color = neutral_swatch.get(if is_dark { 0.35 } else { 0.65 });
let neutral_variant_color = neutral_swatch.get(if is_dark { 0.45 } else { 0.55 }); let neutral_variant_color = neutral_swatch.get(if is_dark { 0.45 } else { 0.55 });
let surface_color = neutral_swatch.get(if is_dark { 0.10 } else { 0.98 });
let surface_variant_color = neutral_swatch.get(if is_dark { 0.20 } else { 0.85 });
let neutral_set = ColorSet::new( let neutral_set = ColorSet::new(
&neutral_color, &neutral_color,
@@ -229,18 +226,66 @@ impl Baseline {
reference_lightness, reference_lightness,
settings, settings,
); );
let surface_set = ColorSet::new(
&surface_color, // Calculate surface array
&neutral_swatch, // 0-2: neutral_darkest to neutral_color (equally spaced)
reference_lightness, // 3-5: neutral_color to neutral_lightest (equally spaced)
settings, let neutral_darkest_percent = 0.0;
let neutral_color_percent = if is_dark { 0.35 } else { 0.65 };
let neutral_lightest_percent = 1.0;
let surface_0_color = neutral_swatch.get(
neutral_darkest_percent + (neutral_color_percent - neutral_darkest_percent) * 0.33,
); );
let surface_variant_set = ColorSet::new( let surface_1_color = neutral_swatch.get(
&surface_variant_color, neutral_darkest_percent + (neutral_color_percent - neutral_darkest_percent) * 0.67,
&neutral_swatch,
reference_lightness,
settings,
); );
let surface_2_color = neutral_swatch.get(neutral_color_percent);
let surface_3_color = neutral_swatch
.get(neutral_color_percent + (neutral_lightest_percent - neutral_color_percent) * 0.33);
let surface_4_color = neutral_swatch
.get(neutral_color_percent + (neutral_lightest_percent - neutral_color_percent) * 0.67);
let surface_5_color = neutral_swatch.get(neutral_lightest_percent);
let surface_array = [
ColorSet::new(
&surface_0_color,
&neutral_swatch,
reference_lightness,
settings,
),
ColorSet::new(
&surface_1_color,
&neutral_swatch,
reference_lightness,
settings,
),
ColorSet::new(
&surface_2_color,
&neutral_swatch,
reference_lightness,
settings,
),
ColorSet::new(
&surface_3_color,
&neutral_swatch,
reference_lightness,
settings,
),
ColorSet::new(
&surface_4_color,
&neutral_swatch,
reference_lightness,
settings,
),
ColorSet::new(
&surface_5_color,
&neutral_swatch,
reference_lightness,
settings,
),
];
let primary_unit = ColorUnit::new(primary, &neutral_swatch, reference_lightness, settings); let primary_unit = ColorUnit::new(primary, &neutral_swatch, reference_lightness, settings);
let secondary_unit = final_secondary let secondary_unit = final_secondary
@@ -262,8 +307,7 @@ impl Baseline {
accent: accent_unit, accent: accent_unit,
neutral: neutral_set, neutral: neutral_set,
neutral_variant: neutral_variant_set, neutral_variant: neutral_variant_set,
surface: surface_set, surface: surface_array,
surface_variant: surface_variant_set,
neutral_swatch, neutral_swatch,
danger: danger_unit, danger: danger_unit,
success: success_unit, success: success_unit,
@@ -320,11 +364,12 @@ impl Baseline {
self.neutral_variant self.neutral_variant
.to_css_variables(scheme_mode, "neutral-variant"), .to_css_variables(scheme_mode, "neutral-variant"),
); );
css_variables.extend(self.surface.to_css_variables(scheme_mode, "surface"));
css_variables.extend( // Handle surface array [0-5]
self.surface_variant for (index, surface_set) in self.surface.iter().enumerate() {
.to_css_variables(scheme_mode, "surface-variant"), css_variables
); .extend(surface_set.to_css_variables(scheme_mode, &format!("surface-{}", index)));
}
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch); let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
for (n, c) in neutral_swatch { for (n, c) in neutral_swatch {
@@ -378,11 +423,12 @@ impl Baseline {
self.neutral_variant self.neutral_variant
.to_css_auto_scheme_collection("neutral-variant"), .to_css_auto_scheme_collection("neutral-variant"),
); );
css_variables.extend(self.surface.to_css_auto_scheme_collection("surface"));
css_variables.extend( // Handle surface array [0-5]
self.surface_variant for (index, surface_set) in self.surface.iter().enumerate() {
.to_css_auto_scheme_collection("surface-variant"), css_variables
); .extend(surface_set.to_css_auto_scheme_collection(&format!("surface-{}", index)));
}
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch); let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
for (n, c) in neutral_swatch { for (n, c) in neutral_swatch {
@@ -429,11 +475,12 @@ impl Baseline {
self.neutral_variant self.neutral_variant
.to_scss_variables(scheme_mode, "neutral-variant"), .to_scss_variables(scheme_mode, "neutral-variant"),
); );
scss_variables.extend(self.surface.to_scss_variables(scheme_mode, "surface"));
scss_variables.extend( // Handle surface array [0-5]
self.surface_variant for (index, surface_set) in self.surface.iter().enumerate() {
.to_scss_variables(scheme_mode, "surface-variant"), scss_variables
); .extend(surface_set.to_scss_variables(scheme_mode, &format!("surface-{}", index)));
}
let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch); let neutral_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
for (n, c) in neutral_swatch { for (n, c) in neutral_swatch {
@@ -525,15 +572,15 @@ impl Baseline {
{ {
javascript_fields.push(format!("{indent}{line:4}")); javascript_fields.push(format!("{indent}{line:4}"));
} }
for line in self.surface.to_javascript_fields("surface").iter() {
javascript_fields.push(format!("{indent}{line:4}")); // Handle surface array [0-5]
} for (index, surface_set) in self.surface.iter().enumerate() {
for line in self for line in surface_set
.surface_variant .to_javascript_fields(&format!("surface{}", index))
.to_javascript_fields("surface_variant") .iter()
.iter() {
{ javascript_fields.push(format!("{indent}{line:4}"));
javascript_fields.push(format!("{indent}{line:4}")); }
} }
let neurtal_swatch = generate_neutral_swatch_list(&self.neutral_swatch); let neurtal_swatch = generate_neutral_swatch_list(&self.neutral_swatch);
@@ -19,12 +19,6 @@ pub struct ColorSet {
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")] #[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub root: Oklch, pub root: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")] #[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub active: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub focus: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub hover: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub disabled: Oklch, pub disabled: Oklch,
#[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")] #[serde(serialize_with = "crate::foreign_serializer::serialize_oklch_to_hex")]
pub on_root: Oklch, pub on_root: Oklch,
@@ -88,12 +82,9 @@ impl ColorSet {
let settings = Arc::clone(settings); let settings = Arc::clone(settings);
let root = color.clone(); let root = color.clone();
let hover = color * settings.hover;
let active = color * settings.active;
let focus = color * settings.focus;
let disabled = color * settings.disabled; let disabled = color * settings.disabled;
let color_list = &[&root, &hover, &active, &focus]; let color_list = &[&root];
let (on_root, on_disabled) = match settings.wacg_follows { let (on_root, on_disabled) = match settings.wacg_follows {
WACGSetting::Fixed => ( WACGSetting::Fixed => (
@@ -116,9 +107,6 @@ impl ColorSet {
Self { Self {
root, root,
active,
focus,
hover,
disabled, disabled,
on_root, on_root,
on_disabled, on_disabled,
@@ -154,18 +142,6 @@ impl ColorSet {
map_oklch_to_srgb_hex(&self.root) map_oklch_to_srgb_hex(&self.root)
} }
fn hover_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.hover)
}
fn active_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.active)
}
fn focus_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.focus)
}
fn disabled_hex(&self) -> String { fn disabled_hex(&self) -> String {
map_oklch_to_srgb_hex(&self.disabled) map_oklch_to_srgb_hex(&self.disabled)
} }
@@ -182,18 +158,6 @@ impl ColorSet {
let mut variables = Vec::new(); let mut variables = Vec::new();
variables.push(format!("--color-{prefix}-{name}: #{};", self.root_hex())); variables.push(format!("--color-{prefix}-{name}: #{};", self.root_hex()));
variables.push(format!(
"--color-{prefix}-{name}-hover: #{};",
self.hover_hex()
));
variables.push(format!(
"--color-{prefix}-{name}-active: #{};",
self.active_hex()
));
variables.push(format!(
"--color-{prefix}-{name}-focus: #{};",
self.focus_hex()
));
variables.push(format!( variables.push(format!(
"--color-{prefix}-{name}-disabled: ${};", "--color-{prefix}-{name}-disabled: ${};",
self.disabled_hex() self.disabled_hex()
@@ -214,9 +178,6 @@ impl ColorSet {
let mut collection = LinkedHashMap::new(); let mut collection = LinkedHashMap::new();
collection.insert(format!("{name}"), self.root_hex()); collection.insert(format!("{name}"), self.root_hex());
collection.insert(format!("{name}-hover"), self.hover_hex());
collection.insert(format!("{name}-active"), self.active_hex());
collection.insert(format!("{name}-focus"), self.focus_hex());
collection.insert(format!("{name}-disabled"), self.disabled_hex()); collection.insert(format!("{name}-disabled"), self.disabled_hex());
collection.insert(format!("on-{name}"), self.on_root_hex()); collection.insert(format!("on-{name}"), self.on_root_hex());
collection.insert(format!("on-{name}-disabled"), self.on_disabled_hex()); collection.insert(format!("on-{name}-disabled"), self.on_disabled_hex());
@@ -228,18 +189,6 @@ impl ColorSet {
let mut variables = Vec::new(); let mut variables = Vec::new();
variables.push(format!("&color-{prefix}-{name}: #{};", self.root_hex())); variables.push(format!("&color-{prefix}-{name}: #{};", self.root_hex()));
variables.push(format!(
"$color-{prefix}-{name}-hover: #{};",
self.hover_hex()
));
variables.push(format!(
"$color-{prefix}-{name}-active: #{};",
self.active_hex()
));
variables.push(format!(
"$color-{prefix}-{name}-focus: ${};",
self.focus_hex()
));
variables.push(format!( variables.push(format!(
"$color-{prefix}-{name}-disabled: #{};", "$color-{prefix}-{name}-disabled: #{};",
self.disabled_hex() self.disabled_hex()
@@ -267,9 +216,6 @@ impl ColorSet {
+ &name[1..]; + &name[1..];
variables.push(format!("{name}: '#{}',", self.root_hex())); variables.push(format!("{name}: '#{}',", self.root_hex()));
variables.push(format!("{name}Hover: '#{}',", self.hover_hex()));
variables.push(format!("{name}Active: '#{}',", self.active_hex()));
variables.push(format!("{name}Focus: '#{}',", self.focus_hex()));
variables.push(format!("{name}Disabled: '#{}',", self.disabled_hex())); variables.push(format!("{name}Disabled: '#{}',", self.disabled_hex()));
variables.push(format!("on{capitalized_name}: '#{}',", self.on_root_hex())); variables.push(format!("on{capitalized_name}: '#{}',", self.on_root_hex()));
variables.push(format!( variables.push(format!(
+55 -20
View File
@@ -15,8 +15,8 @@ mod swatch;
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct QScheme2 { pub struct QScheme2 {
pub light: Baseline, pub light: Option<Baseline>,
pub dark: Baseline, pub dark: Option<Baseline>,
#[serde(skip)] #[serde(skip)]
_settings: Arc<SchemeSetting>, _settings: Arc<SchemeSetting>,
} }
@@ -80,8 +80,16 @@ impl QScheme2 {
); );
Ok(Self { Ok(Self {
light: light_scheme, light: if settings.scheme_select.includes_light() {
dark: dark_scheme, Some(light_scheme)
} else {
None
},
dark: if settings.scheme_select.includes_dark() {
Some(dark_scheme)
} else {
None
},
_settings: settings, _settings: settings,
}) })
} }
@@ -89,9 +97,12 @@ impl QScheme2 {
pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> { pub fn add_custom_color(&mut self, name: &str, color: &str) -> Result<(), errors::ColorError> {
let custom_color = parse_to_oklch!(color); let custom_color = parse_to_oklch!(color);
self.light.add_custom_color(name, &custom_color)?; if let Some(light) = &mut self.light {
self.dark light.add_custom_color(name, &custom_color)?;
.add_custom_color(name, &(custom_color * self._settings.dark_convert))?; }
if let Some(dark) = &mut self.dark {
dark.add_custom_color(name, &(custom_color * self._settings.dark_convert))?;
}
Ok(()) Ok(())
} }
@@ -101,8 +112,12 @@ impl SchemeExport for QScheme2 {
fn output_css_variables(&self) -> String { fn output_css_variables(&self) -> String {
let mut variables = Vec::new(); let mut variables = Vec::new();
variables.extend(self.light.to_css_variables()); if let Some(light) = &self.light {
variables.extend(self.dark.to_css_variables()); variables.extend(light.to_css_variables());
}
if let Some(dark) = &self.dark {
variables.extend(dark.to_css_variables());
}
variables.join("\n") variables.join("\n")
} }
@@ -110,13 +125,25 @@ impl SchemeExport for QScheme2 {
fn output_css_auto_scheme_variables(&self) -> String { fn output_css_auto_scheme_variables(&self) -> String {
let mut collection = Vec::new(); let mut collection = Vec::new();
let mut keys = LinkedHashSet::new(); let mut keys = LinkedHashSet::new();
let light_collection = self.light.to_css_auto_scheme_collection(); let light_collection = self
let dark_collection = self.dark.to_css_auto_scheme_collection(); .light
.as_ref()
.map(|l| l.to_css_auto_scheme_collection());
let dark_collection = self
.dark
.as_ref()
.map(|d| d.to_css_auto_scheme_collection());
keys.extend(light_collection.keys().cloned()); if let Some(ref light_col) = light_collection {
keys.extend(dark_collection.keys().cloned()); keys.extend(light_col.keys().cloned());
}
if let Some(ref dark_col) = dark_collection {
keys.extend(dark_col.keys().cloned());
}
for key in keys { for key in keys {
match (light_collection.get(&key), dark_collection.get(&key)) { let light_val = light_collection.as_ref().and_then(|l| l.get(&key));
let dark_val = dark_collection.as_ref().and_then(|d| d.get(&key));
match (light_val, dark_val) {
(Some(light), Some(dark)) => { (Some(light), Some(dark)) => {
collection.push(format!("--color-{key}: light-dark(#{light}, #{dark});")); collection.push(format!("--color-{key}: light-dark(#{light}, #{dark});"));
} }
@@ -133,8 +160,12 @@ impl SchemeExport for QScheme2 {
fn output_scss_variables(&self) -> String { fn output_scss_variables(&self) -> String {
let mut variables = Vec::new(); let mut variables = Vec::new();
variables.extend(self.light.to_scss_variables()); if let Some(light) = &self.light {
variables.extend(self.dark.to_scss_variables()); variables.extend(light.to_scss_variables());
}
if let Some(dark) = &self.dark {
variables.extend(dark.to_scss_variables());
}
variables.join("\n") variables.join("\n")
} }
@@ -144,11 +175,15 @@ impl SchemeExport for QScheme2 {
let indent = " ".repeat(4); let indent = " ".repeat(4);
javascript_object.push("{".to_string()); javascript_object.push("{".to_string());
for line in self.light.to_javascript_fields() { if let Some(light) = &self.light {
javascript_object.push(format!("{indent}{line}")); for line in light.to_javascript_fields() {
javascript_object.push(format!("{indent}{line}"));
}
} }
for line in self.dark.to_javascript_fields() { if let Some(dark) = &self.dark {
javascript_object.push(format!("{indent}{line}")); for line in dark.to_javascript_fields() {
javascript_object.push(format!("{indent}{line}"));
}
} }
javascript_object.push("}".to_string()); javascript_object.push("}".to_string());