chore: audit all components (#2)
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use gpui::Hsla;
|
||||
use gpui::{Hsla, Pixels, px};
|
||||
|
||||
use crate::system::{PartialPalette, SourceKind, SystemTheme};
|
||||
|
||||
@@ -14,6 +14,9 @@ pub(crate) struct KdeScheme {
|
||||
pub(crate) accent: Option<Hsla>,
|
||||
pub(crate) background: Option<Hsla>,
|
||||
pub(crate) palette: PartialPalette,
|
||||
/// The root font size parsed from `[General] font=` (family, pointSize,
|
||||
/// pixelSize, ...).
|
||||
pub(crate) root_font_size: Option<Pixels>,
|
||||
}
|
||||
|
||||
impl KdeScheme {
|
||||
@@ -54,6 +57,7 @@ impl KdeScheme {
|
||||
accent,
|
||||
background,
|
||||
palette,
|
||||
root_font_size: root_font_size(groups.get("General")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +81,17 @@ impl KdeScheme {
|
||||
.unwrap_or(crate::ThemeVariant::Light),
|
||||
accent: self.accent.or(portal_accent),
|
||||
palette: self.palette,
|
||||
control_radius: None,
|
||||
input_height: None,
|
||||
titlebar_height: None,
|
||||
root_font_size: None,
|
||||
control_radius: Some(px(5.)),
|
||||
overlay_radius: Some(px(5.)),
|
||||
sm_radius: None,
|
||||
control_height: Some(px(33.)),
|
||||
input_height: Some(px(29.)),
|
||||
menu_item_height: Some(px(29.)),
|
||||
switch_height: Some(px(18.)),
|
||||
switch_width: Some(px(36.)),
|
||||
switch_knob: Some(px(18.)),
|
||||
titlebar_height: Some(px(44.)),
|
||||
root_font_size: Some(self.root_font_size.unwrap_or(px(13.33))),
|
||||
accessibility,
|
||||
source: SourceKind::Kde,
|
||||
}
|
||||
@@ -95,6 +106,22 @@ fn variant_by_lightness(background: Hsla) -> crate::ThemeVariant {
|
||||
}
|
||||
}
|
||||
|
||||
/// The root font size of a `[General]` group's `font=` QFont string.
|
||||
fn root_font_size(group: Option<&HashMap<&str, &str>>) -> Option<Pixels> {
|
||||
let fields: Vec<&str> = group?.get("font")?.split(',').map(str::trim).collect();
|
||||
let points = fields.get(1)?.parse::<f32>().ok()?;
|
||||
let pixels = fields
|
||||
.get(2)
|
||||
.and_then(|size| size.parse::<f32>().ok())
|
||||
.unwrap_or(-1.0);
|
||||
let size = if pixels > 0.0 {
|
||||
pixels
|
||||
} else {
|
||||
points * 96.0 / 72.0
|
||||
};
|
||||
(size > 0.0).then(|| px(size))
|
||||
}
|
||||
|
||||
fn parse_groups(text: &str) -> HashMap<&str, HashMap<&str, &str>> {
|
||||
let mut groups: HashMap<&str, HashMap<&str, &str>> = HashMap::new();
|
||||
let mut section: Option<&str> = None;
|
||||
@@ -305,4 +332,31 @@ activeBackground=39,44,49
|
||||
let system = scheme.into_system(Default::default(), None, Some(crate::ThemeVariant::Light));
|
||||
assert_eq!(system.variant, crate::ThemeVariant::Light);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_scheme_states_breeze_metrics_and_the_default_font() {
|
||||
let system = KdeScheme::parse(BREEZE_DARK).into_system(Default::default(), None, None);
|
||||
|
||||
assert_eq!(system.control_height, Some(px(33.)));
|
||||
assert_eq!(system.input_height, Some(px(29.)));
|
||||
assert_eq!(system.menu_item_height, Some(px(29.)));
|
||||
assert_eq!(system.control_radius, Some(px(5.)));
|
||||
assert_eq!(system.overlay_radius, Some(px(5.)));
|
||||
assert_eq!(system.switch_height, Some(px(18.)));
|
||||
assert_eq!(system.switch_width, Some(px(36.)));
|
||||
assert_eq!(system.switch_knob, Some(px(18.)));
|
||||
assert_eq!(system.titlebar_height, Some(px(44.)));
|
||||
assert!((system.root_font_size.unwrap() - px(13.33)).abs() < px(0.01));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_stated_font_beats_the_default() {
|
||||
let text = "[General]\nfont=Noto Sans,10,-1,5,50,0,0,0,0,0\n";
|
||||
let system = KdeScheme::parse(text).into_system(Default::default(), None, None);
|
||||
assert!((system.root_font_size.unwrap() - px(10.0 * 96.0 / 72.0)).abs() < px(0.01));
|
||||
|
||||
let text = "[General]\nfont=Adwaita Sans,-1,12,5,50,0,0,0,0,0\n";
|
||||
let system = KdeScheme::parse(text).into_system(Default::default(), None, None);
|
||||
assert_eq!(system.root_font_size, Some(px(12.)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use gpui::Hsla;
|
||||
use gpui::{Hsla, Pixels};
|
||||
use notify::{RecursiveMode, Watcher};
|
||||
|
||||
use super::ThemeSource;
|
||||
@@ -55,11 +55,12 @@ fn detect(home: &Path, portal_reachable: bool, gsettings_reachable: bool) -> Bac
|
||||
}
|
||||
}
|
||||
|
||||
/// What `gsettings` states for GNOME sessions without a portal.
|
||||
/// What `gsettings` states for GNOME sessions.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
struct GSettingsSnapshot {
|
||||
variant: Option<ThemeVariant>,
|
||||
accent: Option<Hsla>,
|
||||
root_font_size: Option<Pixels>,
|
||||
}
|
||||
|
||||
fn gsettings_value(args: &[&str]) -> Option<String> {
|
||||
@@ -97,23 +98,31 @@ fn read_gsettings() -> Option<GSettingsSnapshot> {
|
||||
"prefer-light" => Some(ThemeVariant::Light),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let accent = gsettings_value(&["get", "org.gnome.desktop.interface", "accent-color"])
|
||||
.and_then(|value| accent_rgb(value.trim().trim_matches('\'')));
|
||||
|
||||
if variant.is_none() && accent.is_none() {
|
||||
let root_font_size = gsettings_value(&["get", "org.gnome.desktop.interface", "font-name"])
|
||||
.and_then(|value| font_root_px(value.trim().trim_matches('\'')));
|
||||
|
||||
if variant.is_none() && accent.is_none() && root_font_size.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(GSettingsSnapshot { variant, accent })
|
||||
Some(GSettingsSnapshot {
|
||||
variant,
|
||||
accent,
|
||||
root_font_size,
|
||||
})
|
||||
}
|
||||
|
||||
fn font_root_px(name: &str) -> Option<Pixels> {
|
||||
let points = name.split_whitespace().last()?.parse::<f32>().ok()?;
|
||||
(points > 0.0).then(|| gpui::px(points * 96.0 / 72.0))
|
||||
}
|
||||
|
||||
pub(crate) fn platform_source() -> Box<dyn ThemeSource> {
|
||||
let portal = portal::io::PortalClient::connect();
|
||||
|
||||
let gsettings = if portal.is_none() {
|
||||
read_gsettings()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let gsettings = read_gsettings();
|
||||
|
||||
let backend = home_dir()
|
||||
.map(|home| detect(&home, portal.is_some(), gsettings.is_some()))
|
||||
@@ -129,6 +138,36 @@ pub(crate) fn platform_source() -> Box<dyn ThemeSource> {
|
||||
}
|
||||
|
||||
impl LinuxSource {
|
||||
fn gnome_snapshot(
|
||||
&self,
|
||||
variant: ThemeVariant,
|
||||
accent: Option<Hsla>,
|
||||
accessibility: crate::system::AccessibilityPreferences,
|
||||
) -> SystemTheme {
|
||||
SystemTheme {
|
||||
variant,
|
||||
accent,
|
||||
palette: PartialPalette::default(),
|
||||
control_radius: Some(gpui::px(9.)),
|
||||
overlay_radius: Some(gpui::px(15.)),
|
||||
sm_radius: Some(gpui::px(6.)),
|
||||
control_height: Some(gpui::px(34.)),
|
||||
input_height: Some(gpui::px(34.)),
|
||||
menu_item_height: Some(gpui::px(32.)),
|
||||
switch_height: Some(gpui::px(26.)),
|
||||
switch_width: Some(gpui::px(46.)),
|
||||
switch_knob: Some(gpui::px(20.)),
|
||||
titlebar_height: Some(gpui::px(47.)),
|
||||
root_font_size: Some(
|
||||
self.gsettings
|
||||
.and_then(|gsettings| gsettings.root_font_size)
|
||||
.unwrap_or(gpui::px(14.67)),
|
||||
),
|
||||
accessibility,
|
||||
source: SourceKind::Gnome,
|
||||
}
|
||||
}
|
||||
|
||||
fn portal_snapshot(
|
||||
&self,
|
||||
variant: ThemeVariant,
|
||||
@@ -136,31 +175,21 @@ impl LinuxSource {
|
||||
accessibility: crate::system::AccessibilityPreferences,
|
||||
) -> SystemTheme {
|
||||
if let Some(appearance) = portal_appearance {
|
||||
return SystemTheme {
|
||||
variant: appearance.variant(variant),
|
||||
accent: appearance.accent,
|
||||
palette: PartialPalette::default(),
|
||||
control_radius: None,
|
||||
input_height: None,
|
||||
titlebar_height: None,
|
||||
root_font_size: None,
|
||||
return self.gnome_snapshot(
|
||||
appearance.variant(variant),
|
||||
appearance.accent,
|
||||
accessibility,
|
||||
source: SourceKind::Gnome,
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(gsettings) = self.gsettings {
|
||||
return SystemTheme {
|
||||
variant: gsettings.variant.unwrap_or(variant),
|
||||
accent: gsettings.accent,
|
||||
palette: PartialPalette::default(),
|
||||
control_radius: None,
|
||||
input_height: None,
|
||||
titlebar_height: None,
|
||||
root_font_size: None,
|
||||
return self.gnome_snapshot(
|
||||
gsettings.variant.unwrap_or(variant),
|
||||
gsettings.accent,
|
||||
accessibility,
|
||||
source: SourceKind::Gnome,
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
SystemTheme::fallback(variant)
|
||||
}
|
||||
}
|
||||
@@ -289,4 +318,52 @@ mod tests {
|
||||
assert_eq!(detect(&home, true, false), Backend::Kde);
|
||||
std::fs::remove_dir_all(&home).expect("cleanup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_gsettings_font_name_states_the_root_font_size() {
|
||||
assert_eq!(
|
||||
font_root_px("Adwaita Sans 11"),
|
||||
Some(gpui::px(11.0 * 96.0 / 72.0))
|
||||
);
|
||||
assert_eq!(
|
||||
font_root_px("Noto Sans 10"),
|
||||
Some(gpui::px(10.0 * 96.0 / 72.0))
|
||||
);
|
||||
assert_eq!(
|
||||
font_root_px("Cantarell Bold 12.5"),
|
||||
Some(gpui::px(12.5 * 96.0 / 72.0))
|
||||
);
|
||||
assert_eq!(font_root_px("Adwaita Sans"), None);
|
||||
assert_eq!(font_root_px("Sans 0"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_gnome_snapshot_states_the_libadwaita_metrics() {
|
||||
let source = LinuxSource {
|
||||
backend: Backend::Portal,
|
||||
portal: None,
|
||||
gsettings: None,
|
||||
file_watcher: None,
|
||||
_signal: None,
|
||||
};
|
||||
let system = source.gnome_snapshot(
|
||||
ThemeVariant::Light,
|
||||
None,
|
||||
crate::system::AccessibilityPreferences::default(),
|
||||
);
|
||||
|
||||
assert_eq!(system.source, SourceKind::Gnome);
|
||||
assert_eq!(system.control_height, Some(gpui::px(34.)));
|
||||
assert_eq!(system.input_height, Some(gpui::px(34.)));
|
||||
assert_eq!(system.menu_item_height, Some(gpui::px(32.)));
|
||||
assert_eq!(system.control_radius, Some(gpui::px(9.)));
|
||||
assert_eq!(system.overlay_radius, Some(gpui::px(15.)));
|
||||
assert_eq!(system.sm_radius, Some(gpui::px(6.)));
|
||||
assert_eq!(system.switch_height, Some(gpui::px(26.)));
|
||||
assert_eq!(system.switch_width, Some(gpui::px(46.)));
|
||||
assert_eq!(system.switch_knob, Some(gpui::px(20.)));
|
||||
assert_eq!(system.titlebar_height, Some(gpui::px(47.)));
|
||||
// The default 11pt font when gsettings states none.
|
||||
assert_eq!(system.root_font_size, Some(gpui::px(14.67)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,16 @@ use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, System
|
||||
const CONTROL_RADIUS_PTS: f32 = 6.0;
|
||||
/// The rounded-bezel text field's intrinsic height at regular size.
|
||||
const INPUT_HEIGHT_PTS: f32 = 24.0;
|
||||
/// AppKit's regular push button, measured via `NSButton::fittingSize`.
|
||||
const CONTROL_HEIGHT_PTS: f32 = 24.0;
|
||||
/// The standard `NSMenu` row height.
|
||||
const MENU_ITEM_HEIGHT_PTS: f32 = 20.0;
|
||||
/// `NSSwitch`, measured via `fittingSize`.
|
||||
const SWITCH_TRACK_HEIGHT_PTS: f32 = 24.0;
|
||||
const SWITCH_TRACK_WIDTH_PTS: f32 = 54.0;
|
||||
const SWITCH_KNOB_PTS: f32 = 20.0;
|
||||
/// The standard titled window's titlebar, measured via `contentLayoutRect`.
|
||||
const TITLEBAR_HEIGHT_PTS: f32 = 32.0;
|
||||
/// The HIG's default body text on macOS is 13 pt (system font size 13 at 1x).
|
||||
const ROOT_FONT_SIZE_PTS: f32 = 13.0;
|
||||
|
||||
@@ -112,8 +122,15 @@ impl ThemeSource for MacosSource {
|
||||
accent: Self::color_to_hsla(&NSColor::controlAccentColor()),
|
||||
palette,
|
||||
control_radius: Some(gpui::px(CONTROL_RADIUS_PTS)),
|
||||
overlay_radius: None,
|
||||
sm_radius: None,
|
||||
control_height: Some(gpui::px(CONTROL_HEIGHT_PTS)),
|
||||
input_height: Some(gpui::px(INPUT_HEIGHT_PTS)),
|
||||
titlebar_height: None,
|
||||
menu_item_height: Some(gpui::px(MENU_ITEM_HEIGHT_PTS)),
|
||||
switch_height: Some(gpui::px(SWITCH_TRACK_HEIGHT_PTS)),
|
||||
switch_width: Some(gpui::px(SWITCH_TRACK_WIDTH_PTS)),
|
||||
switch_knob: Some(gpui::px(SWITCH_KNOB_PTS)),
|
||||
titlebar_height: Some(gpui::px(TITLEBAR_HEIGHT_PTS)),
|
||||
root_font_size: Some(gpui::px(ROOT_FONT_SIZE_PTS)),
|
||||
accessibility,
|
||||
source: SourceKind::Macos,
|
||||
|
||||
@@ -60,8 +60,15 @@ impl OmarchyTheme {
|
||||
variant: self.variant,
|
||||
accent: self.accent,
|
||||
palette: self.palette,
|
||||
control_radius: None,
|
||||
input_height: None,
|
||||
control_radius: Some(gpui::px(9.)),
|
||||
overlay_radius: Some(gpui::px(15.)),
|
||||
sm_radius: Some(gpui::px(6.)),
|
||||
control_height: Some(gpui::px(34.)),
|
||||
input_height: Some(gpui::px(34.)),
|
||||
menu_item_height: Some(gpui::px(32.)),
|
||||
switch_height: Some(gpui::px(26.)),
|
||||
switch_width: Some(gpui::px(46.)),
|
||||
switch_knob: Some(gpui::px(20.)),
|
||||
titlebar_height: None,
|
||||
root_font_size: None,
|
||||
accessibility,
|
||||
@@ -216,7 +223,19 @@ color15 = \"#635d6f\"
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_toml_text_is_rejected() {
|
||||
assert!(OmarchyTheme::parse("not a theme").is_none());
|
||||
fn omarchy_shares_the_gnome_control_metrics() {
|
||||
let theme = OmarchyTheme::parse(TOKYO_NIGHT).expect("parses");
|
||||
let system = theme.into_system(Default::default());
|
||||
|
||||
assert_eq!(system.control_height, Some(gpui::px(34.)));
|
||||
assert_eq!(system.input_height, Some(gpui::px(34.)));
|
||||
assert_eq!(system.menu_item_height, Some(gpui::px(32.)));
|
||||
assert_eq!(system.control_radius, Some(gpui::px(9.)));
|
||||
assert_eq!(system.overlay_radius, Some(gpui::px(15.)));
|
||||
assert_eq!(system.switch_height, Some(gpui::px(26.)));
|
||||
assert_eq!(system.switch_width, Some(gpui::px(46.)));
|
||||
assert_eq!(system.switch_knob, Some(gpui::px(20.)));
|
||||
assert_eq!(system.titlebar_height, None);
|
||||
assert_eq!(system.root_font_size, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,16 +206,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_unset_scheme_yields_the_callers_variant() {
|
||||
let appearance = AppearanceValues::default().decode();
|
||||
assert_eq!(
|
||||
appearance.variant(crate::ThemeVariant::Dark),
|
||||
crate::ThemeVariant::Dark
|
||||
);
|
||||
assert_eq!(appearance, Appearance::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accessibility_flags_forward() {
|
||||
let accessibility = AppearanceValues {
|
||||
|
||||
@@ -14,8 +14,18 @@ use crate::system::{AccessibilityPreferences, PartialPalette, SourceKind, System
|
||||
|
||||
/// WinUI 11's `ControlCornerRadius`, in pixels at 100% scaling.
|
||||
const CONTROL_RADIUS_PX: f32 = 4.0;
|
||||
/// WinUI 11's `OverlayCornerRadius`, in pixels at 100% scaling.
|
||||
const OVERLAY_RADIUS_PX: f32 = 8.0;
|
||||
/// WinUI 11's default text-box height, in pixels at 100% scaling.
|
||||
const INPUT_HEIGHT_PX: f32 = 32.0;
|
||||
/// The default button height, in pixels at 100% scaling.
|
||||
const CONTROL_HEIGHT_PX: f32 = 32.0;
|
||||
/// The default menu row: item padding 4/5 + a 20px Body line + 2px item margins.
|
||||
const MENU_ITEM_HEIGHT_PX: f32 = 33.0;
|
||||
/// ToggleSwitch: a 40x20 track with a 12px resting knob.
|
||||
const SWITCH_TRACK_HEIGHT_PX: f32 = 20.0;
|
||||
const SWITCH_TRACK_WIDTH_PX: f32 = 40.0;
|
||||
const SWITCH_KNOB_PX: f32 = 12.0;
|
||||
/// The window caption's height, in pixels at 100% scaling, as Win32 reports it.
|
||||
const TITLEBAR_HEIGHT_PX: f32 = 32.0;
|
||||
/// WinUI 11's default body text size, in pixels at 100% scaling.
|
||||
@@ -159,7 +169,14 @@ impl ThemeSource for WindowsSource {
|
||||
..PartialPalette::default()
|
||||
},
|
||||
control_radius: Some(gpui::px(CONTROL_RADIUS_PX)),
|
||||
overlay_radius: Some(gpui::px(OVERLAY_RADIUS_PX)),
|
||||
sm_radius: None,
|
||||
control_height: Some(gpui::px(CONTROL_HEIGHT_PX)),
|
||||
input_height: Some(gpui::px(INPUT_HEIGHT_PX)),
|
||||
menu_item_height: Some(gpui::px(MENU_ITEM_HEIGHT_PX)),
|
||||
switch_height: Some(gpui::px(SWITCH_TRACK_HEIGHT_PX)),
|
||||
switch_width: Some(gpui::px(SWITCH_TRACK_WIDTH_PX)),
|
||||
switch_knob: Some(gpui::px(SWITCH_KNOB_PX)),
|
||||
titlebar_height: Some(gpui::px(TITLEBAR_HEIGHT_PX)),
|
||||
root_font_size: Some(gpui::px(ROOT_FONT_SIZE_PX * text_scale as f32)),
|
||||
accessibility: self.accessibility(),
|
||||
|
||||
Reference in New Issue
Block a user