chore: audit all components (#2)
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
+167
-42
@@ -1,7 +1,9 @@
|
||||
use crate::definition::ThemeDefinition;
|
||||
use crate::ramps::{AccentShades, Oklch, contrast_foreground, derive_shades, mix};
|
||||
use crate::system::SystemTheme;
|
||||
use crate::{ColorTokens, Elevations, Motion, Radius, Scrollbar, SizeTable, ThemeVariant};
|
||||
use crate::{
|
||||
Borders, ColorTokens, Elevations, Motion, Radius, Scrollbar, SizeTable, Spacing, ThemeVariant,
|
||||
};
|
||||
use gpui::px;
|
||||
|
||||
/// The floor of a custom titlebar's height, in px at 100% scaling: the height
|
||||
@@ -26,6 +28,8 @@ pub struct ResolvedTheme {
|
||||
pub colors: ColorTokens,
|
||||
pub ramps: Ramps,
|
||||
pub radius: Radius,
|
||||
pub borders: Borders,
|
||||
pub spacing: Spacing,
|
||||
pub elevations: Elevations,
|
||||
pub motion: Motion,
|
||||
pub scrollbar: Scrollbar,
|
||||
@@ -198,18 +202,35 @@ pub(crate) fn resolve(
|
||||
//
|
||||
// Fallback: 16 (the web default gpui also uses for rems).
|
||||
let root_font_size = system.root_font_size.unwrap_or(px(16.));
|
||||
|
||||
let mut sizes = SizeTable::with_root_font_size(root_font_size);
|
||||
|
||||
if let Some(control_height) = system.control_height {
|
||||
sizes.set_control_height(control_height);
|
||||
}
|
||||
|
||||
if let Some(input_height) = system.input_height {
|
||||
sizes.set_input_height(input_height);
|
||||
}
|
||||
|
||||
if let Some(switch_height) = system.switch_height {
|
||||
sizes.set_switch_metrics(
|
||||
switch_height,
|
||||
system.switch_width.unwrap_or(switch_height * 2.25),
|
||||
system.switch_knob.unwrap_or(switch_height * 0.85),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(menu_item_height) = system.menu_item_height {
|
||||
sizes.set_menu_item_height(menu_item_height);
|
||||
}
|
||||
|
||||
// A platform that states its titlebar height wins.
|
||||
let titlebar_height = system
|
||||
.titlebar_height
|
||||
.unwrap_or_else(|| sizes.medium.control_height.max(px(TITLEBAR_HEIGHT_MIN_PX)));
|
||||
|
||||
let mut radius = Radius::default();
|
||||
|
||||
if let Some(control) = preset
|
||||
.and_then(|def| def.control_radius)
|
||||
.map(px)
|
||||
@@ -218,6 +239,22 @@ pub(crate) fn resolve(
|
||||
radius.control = control;
|
||||
}
|
||||
|
||||
if let Some(overlay) = preset
|
||||
.and_then(|def| def.overlay_radius)
|
||||
.map(px)
|
||||
.or(system.overlay_radius)
|
||||
{
|
||||
radius.overlay = overlay;
|
||||
}
|
||||
|
||||
if let Some(sm) = preset
|
||||
.and_then(|def| def.sm_radius)
|
||||
.map(px)
|
||||
.or(system.sm_radius)
|
||||
{
|
||||
radius.sm = sm;
|
||||
}
|
||||
|
||||
ResolvedTheme {
|
||||
variant,
|
||||
ramps: Ramps {
|
||||
@@ -238,6 +275,10 @@ pub(crate) fn resolve(
|
||||
},
|
||||
colors,
|
||||
radius,
|
||||
borders: Borders::for_platform(system.source, variant),
|
||||
spacing: Spacing {
|
||||
unit: system.source.spacing_unit(),
|
||||
},
|
||||
elevations: Elevations::for_platform(system.source, variant),
|
||||
motion: Motion::default(),
|
||||
scrollbar: Scrollbar {
|
||||
@@ -283,7 +324,14 @@ mod tests {
|
||||
accent,
|
||||
palette,
|
||||
control_radius: None,
|
||||
overlay_radius: None,
|
||||
sm_radius: None,
|
||||
control_height: None,
|
||||
input_height: None,
|
||||
menu_item_height: None,
|
||||
switch_height: None,
|
||||
switch_width: None,
|
||||
switch_knob: None,
|
||||
titlebar_height: None,
|
||||
root_font_size: None,
|
||||
accessibility: AccessibilityPreferences::default(),
|
||||
@@ -291,24 +339,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metric_priority_is_definition_then_system_then_fallback() {
|
||||
let mut stated = system(ThemeVariant::Dark, PartialPalette::default(), None);
|
||||
stated.control_radius = Some(px(4.));
|
||||
|
||||
let resolved = resolve(&stated, None, None);
|
||||
assert_eq!(resolved.radius.control, px(4.));
|
||||
|
||||
let def = ThemeDefinition::parse_toml("control_radius = 12.0\n").unwrap();
|
||||
let overridden = resolve(&stated, Some(&def), None);
|
||||
assert_eq!(overridden.radius.control, px(12.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_system_resolves_to_the_neutral_fallback() {
|
||||
for variant in [ThemeVariant::Light, ThemeVariant::Dark] {
|
||||
let resolved = resolve(
|
||||
&system(variant, PartialPalette::default(), None),
|
||||
&SystemTheme {
|
||||
source: SourceKind::Fallback,
|
||||
..system(variant, PartialPalette::default(), None)
|
||||
},
|
||||
None,
|
||||
None,
|
||||
);
|
||||
@@ -333,6 +371,21 @@ mod tests {
|
||||
assert_eq!(resolved.sizes.medium.dense_padding_x, px(16. * 0.75));
|
||||
assert_eq!(resolved.sizes.medium.plate_inset, px(4.));
|
||||
assert_eq!(resolved.sizes.medium.strip_height, px(32.));
|
||||
assert_eq!(resolved.sizes.medium.icon_size, px(16.));
|
||||
assert_eq!(resolved.sizes.medium.menu_item_height, px(28. * 0.85));
|
||||
assert_eq!(resolved.sizes.medium.switch_height, px(21.));
|
||||
assert_eq!(resolved.sizes.medium.switch_width, px(21. * 2.25));
|
||||
assert_eq!(resolved.sizes.medium.switch_knob, px(21. * 0.85));
|
||||
assert_eq!(
|
||||
resolved.borders,
|
||||
Borders {
|
||||
buttons: true,
|
||||
fields: true,
|
||||
overlays: true,
|
||||
focus_ring: false,
|
||||
}
|
||||
);
|
||||
assert_eq!(resolved.spacing.unit, px(4.));
|
||||
assert_eq!(resolved.titlebar_height, px(34.));
|
||||
}
|
||||
}
|
||||
@@ -383,17 +436,105 @@ mod tests {
|
||||
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||
stated.input_height = Some(px(24.));
|
||||
let resolved = resolve(&stated, None, None);
|
||||
|
||||
// The medium class takes the stated height verbatim, and the other
|
||||
// classes keep their derived ratio to the medium class.
|
||||
assert_eq!(resolved.sizes.medium.input_height, px(24.));
|
||||
|
||||
// Derive what small would be without the statement, scale it by the
|
||||
// stated-to-derived ratio, and compare.
|
||||
let derived = SizeTable::with_root_font_size(px(16.));
|
||||
let scale = px(24.) / derived.medium.input_height;
|
||||
let expected_small = derived.small.input_height * scale;
|
||||
assert!((resolved.sizes.small.input_height - expected_small).abs() < px(0.01));
|
||||
let expected_small = (derived.small.input_height * scale).floor();
|
||||
assert_eq!(resolved.sizes.small.input_height, expected_small);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stated_control_height_overrides_the_medium_class_and_scales() {
|
||||
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||
stated.control_height = Some(px(32.));
|
||||
let resolved = resolve(&stated, None, None);
|
||||
assert_eq!(resolved.sizes.medium.control_height, px(32.));
|
||||
|
||||
let derived = SizeTable::with_root_font_size(px(16.));
|
||||
let scale = px(32.) / derived.medium.control_height;
|
||||
let expected_small = (derived.small.control_height * scale).floor();
|
||||
let expected_large = (derived.large.control_height * scale).floor();
|
||||
assert_eq!(resolved.sizes.small.control_height, expected_small);
|
||||
assert_eq!(resolved.sizes.large.control_height, expected_large);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stated_switch_and_menu_metrics_override_the_derived_ones() {
|
||||
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||
stated.switch_height = Some(px(20.));
|
||||
stated.switch_width = Some(px(40.));
|
||||
stated.switch_knob = Some(px(12.));
|
||||
stated.menu_item_height = Some(px(33.));
|
||||
let resolved = resolve(&stated, None, None);
|
||||
|
||||
assert_eq!(resolved.sizes.medium.switch_height, px(20.));
|
||||
assert_eq!(resolved.sizes.medium.switch_width, px(40.));
|
||||
assert_eq!(resolved.sizes.medium.switch_knob, px(12.));
|
||||
assert_eq!(resolved.sizes.medium.menu_item_height, px(33.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_radius_ladder_follows_stated_values_with_preset_priority() {
|
||||
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||
stated.control_radius = Some(px(9.));
|
||||
stated.overlay_radius = Some(px(15.));
|
||||
stated.sm_radius = Some(px(6.));
|
||||
let resolved = resolve(&stated, None, None);
|
||||
assert_eq!(resolved.radius.control, px(9.));
|
||||
assert_eq!(resolved.radius.overlay, px(15.));
|
||||
assert_eq!(resolved.radius.sm, px(6.));
|
||||
|
||||
let def = ThemeDefinition::parse_toml("overlay_radius = 12.0\n").unwrap();
|
||||
let overridden = resolve(&stated, Some(&def), None);
|
||||
assert_eq!(overridden.radius.overlay, px(12.));
|
||||
assert_eq!(overridden.radius.sm, px(6.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn borders_and_spacing_follow_the_platform_source() {
|
||||
let chrome = |source| {
|
||||
let mut stated = system(ThemeVariant::Light, PartialPalette::default(), None);
|
||||
stated.source = source;
|
||||
let resolved = resolve(&stated, None, None);
|
||||
(resolved.borders, resolved.spacing.unit)
|
||||
};
|
||||
|
||||
let (borders, unit) = chrome(SourceKind::Windows);
|
||||
assert!(borders.buttons && borders.fields && borders.overlays && !borders.focus_ring);
|
||||
assert_eq!(unit, px(4.));
|
||||
|
||||
let (borders, unit) = chrome(SourceKind::Kde);
|
||||
assert!(borders.buttons && borders.fields && borders.overlays && !borders.focus_ring);
|
||||
assert_eq!(unit, px(4.));
|
||||
|
||||
let (borders, unit) = chrome(SourceKind::Macos);
|
||||
assert!(!borders.buttons && borders.fields && !borders.overlays && borders.focus_ring);
|
||||
assert_eq!(unit, px(4.));
|
||||
|
||||
for source in [SourceKind::Gnome, SourceKind::Omarchy] {
|
||||
let (borders, unit) = chrome(source);
|
||||
assert!(!borders.buttons && !borders.fields && !borders.overlays && borders.focus_ring);
|
||||
assert_eq!(unit, px(6.));
|
||||
}
|
||||
|
||||
let (borders, unit) = chrome(SourceKind::Fallback);
|
||||
assert!(borders.buttons && borders.fields && borders.overlays && !borders.focus_ring);
|
||||
assert_eq!(unit, px(4.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dark_mode_separates_overlays_by_border() {
|
||||
for source in [SourceKind::Macos, SourceKind::Gnome, SourceKind::Omarchy] {
|
||||
let mut stated = system(ThemeVariant::Dark, PartialPalette::default(), None);
|
||||
stated.source = source;
|
||||
let resolved = resolve(&stated, None, None);
|
||||
assert!(
|
||||
resolved.borders.overlays,
|
||||
"{source:?} frames floating layers in dark"
|
||||
);
|
||||
assert!(resolved.elevations.popover.shadows().is_empty());
|
||||
assert!(resolved.elevations.dialog.shadows().is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -426,7 +567,7 @@ mod tests {
|
||||
#[test]
|
||||
fn elevations_and_motion_resolve_with_defaults() {
|
||||
let resolved = resolve(
|
||||
&system(ThemeVariant::Dark, PartialPalette::default(), None),
|
||||
&system(ThemeVariant::Light, PartialPalette::default(), None),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
@@ -438,7 +579,7 @@ mod tests {
|
||||
);
|
||||
assert!(resolved.motion.overlay > std::time::Duration::ZERO);
|
||||
let shadows = resolved.elevations.popover.shadows();
|
||||
assert_eq!(shadows.len(), 1);
|
||||
assert_eq!(shadows.len(), 2);
|
||||
assert!(!shadows[0].inset);
|
||||
}
|
||||
|
||||
@@ -666,20 +807,4 @@ mod tests {
|
||||
let preset_stated = resolve(&system(ThemeVariant::Dark, palette, None), Some(&def), None);
|
||||
assert_eq!(preset_stated.colors.info, hsla(0x0ea5e9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn muted_hover_follows_the_overlay_chain() {
|
||||
// No platform states a hover wash; a preset or user file is the only
|
||||
// override above the neutral base.
|
||||
let palette = PartialPalette {
|
||||
muted_hover: Some(hsla(0x155e75)),
|
||||
..PartialPalette::default()
|
||||
};
|
||||
let stated = resolve(&system(ThemeVariant::Dark, palette, None), None, None);
|
||||
assert_eq!(stated.colors.muted_hover, hsla(0x155e75));
|
||||
|
||||
let def = ThemeDefinition::parse_toml("muted_hover = \"#0ea5e9\"\n").unwrap();
|
||||
let preset_stated = resolve(&system(ThemeVariant::Dark, palette, None), Some(&def), None);
|
||||
assert_eq!(preset_stated.colors.muted_hover, hsla(0x0ea5e9));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user