Files
vectorcraft/crates/ui-egui/src/theme.rs
T
bflatastic a64a5a9911 UI: Classic colour scheme (User Interface › Color Scheme)
A second colour scheme next to Modern: the neutral greys and blue accent of
earlier versions, on the same cards, corners and layout. Picked in
Preferences › User Interface › Color Scheme (pref uiColorScheme:
modern|classic); Accent Color is dimmed while Classic is chosen, which has
its own blue.
2026-10-16 14:29:33 +07:00

639 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Design tokens (four UI brightness levels), fonts and egui style.
use std::sync::Arc;
use egui::{Color32, CornerRadius, FontData, FontDefinitions, FontFamily, FontId, Stroke, TextStyle, Visuals};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Brightness {
Dark,
#[default]
MediumDark,
MediumLight,
Light,
}
impl Brightness {
pub const ALL: [Brightness; 4] = [Brightness::Dark, Brightness::MediumDark, Brightness::MediumLight, Brightness::Light];
pub fn label(self) -> &'static str {
match self {
Brightness::Dark => "Dark",
Brightness::MediumDark => "Medium Dark",
Brightness::MediumLight => "Medium Light",
Brightness::Light => "Light",
}
}
pub fn id(self) -> &'static str {
match self {
Brightness::Dark => "dark",
Brightness::MediumDark => "mediumDark",
Brightness::MediumLight => "mediumLight",
Brightness::Light => "light",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.into_iter().find(|b| b.id().eq_ignore_ascii_case(s) || b.label().eq_ignore_ascii_case(s))
}
}
/// User Interface › Accent Color: the one colour for what is on, chosen or focused. Coral, the
/// default, is the app icon's.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Accent {
#[default]
Coral,
Blue,
Violet,
Green,
Amber,
Pink,
}
impl Accent {
pub const ALL: [Accent; 6] = [Accent::Coral, Accent::Blue, Accent::Violet, Accent::Green, Accent::Amber, Accent::Pink];
pub fn id(self) -> &'static str {
match self {
Accent::Coral => "coral",
Accent::Blue => "blue",
Accent::Violet => "violet",
Accent::Green => "green",
Accent::Amber => "amber",
Accent::Pink => "pink",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.into_iter().find(|a| a.id().eq_ignore_ascii_case(s))
}
/// (accent, accent_strong) on dark panels and on light ones; the strong one fills buttons
/// with white lettering.
fn colors(self, dark: bool) -> (u32, u32) {
match (self, dark) {
(Accent::Coral, true) => (0xf06a52, 0xe8573f),
(Accent::Coral, false) => (0xd9472f, 0xd14a33),
(Accent::Blue, true) => (0x5b96ff, 0x2f74f0),
(Accent::Blue, false) => (0x2563eb, 0x1f5ad6),
(Accent::Violet, true) => (0xa98bff, 0x7c55f0),
(Accent::Violet, false) => (0x7146dc, 0x6a3fd4),
(Accent::Green, true) => (0x3fcf8e, 0x1f9d63),
(Accent::Green, false) => (0x16955a, 0x14874f),
(Accent::Amber, true) => (0xf5b544, 0xc47f0e),
(Accent::Amber, false) => (0xb06d06, 0xb06d06),
(Accent::Pink, true) => (0xf36aa3, 0xdb3f7c),
(Accent::Pink, false) => (0xcc3672, 0xc22e69),
}
}
}
/// User Interface › Color Scheme: Modern (the default: slate greys and the chosen accent) or
/// Classic (the neutral greys and blue accent of earlier versions). Both keep the same layout,
/// cards and corners; only the colours differ.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Scheme {
#[default]
Modern,
Classic,
}
impl Scheme {
pub const ALL: [Scheme; 2] = [Scheme::Modern, Scheme::Classic];
pub fn id(self) -> &'static str {
match self {
Scheme::Modern => "modern",
Scheme::Classic => "classic",
}
}
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.into_iter().find(|c| c.id().eq_ignore_ascii_case(s))
}
}
/// All colours and metrics the UI uses. Never hard-code a colour in a widget.
#[derive(Clone, Copy, Debug)]
pub struct Tokens {
pub dark: bool,
pub app_bar: Color32,
pub panel: Color32,
pub panel_darker: Color32,
pub input: Color32,
pub input_border: Color32,
pub border: Color32,
pub divider: Color32,
pub text: Color32,
pub text_dim: Color32,
pub text_disabled: Color32,
pub icon: Color32,
pub hover: Color32,
pub tool_active: Color32,
pub accent: Color32,
pub accent_strong: Color32,
pub row_selected: Color32,
pub pasteboard: Color32,
pub ruler: Color32,
pub ruler_tick: Color32,
pub smart_guide: Color32,
pub guide: Color32,
pub measure_bg: Color32,
pub button: Color32,
pub radius: u8,
/// Field values, button labels, link labels.
pub text_strong: Color32,
/// Panel tab strips, doc-tab bar, dock header.
pub tab_strip: Color32,
/// Outline of secondary (outlined) buttons.
pub button_border: Color32,
/// Default Layer 1 selection colour and selected-anchor fill.
pub selection: Color32,
pub anchor_selected: Color32,
/// Active item text in tool flyouts.
pub flyout_active: Color32,
/// Hint-bar / tab-bar heights etc. scale with this base font size.
pub font_size: f32,
/// Window Close button (custom title bar) when hovered, and its glyph there.
pub caption_close: Color32,
pub caption_close_text: Color32,
/// What preview images mark (Flattener Preview highlights).
pub highlight: Color32,
/// The bleed outline around artboards (Document Setup → Bleed).
pub bleed: Color32,
/// Error marks (a missing linked file).
pub error: Color32,
/// Warning marks (a modified linked file).
pub warning: Color32,
/// Rotation gizmo axes, also labelled X, Y and Z.
pub axis_x: Color32,
pub axis_y: Color32,
pub axis_z: Color32,
}
impl Tokens {
pub fn for_brightness(b: Brightness) -> Self {
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
// The Craft look: cool slate greys, flat faces with soft corners, and one accent (the app
// icon's coral) for what is on, chosen or focused. The four brightness levels share the
// style and differ only in their greys.
let base = Tokens {
dark: true,
app_bar: hex(0x0b0d12),
panel: hex(0x181b22),
panel_darker: hex(0x13161c),
input: hex(0x0f1116),
input_border: hex(0x2c323e),
border: hex(0x06070a),
divider: hex(0x232832),
text: hex(0xdfe4ec),
text_dim: hex(0x969faf),
text_disabled: hex(0x5a6271),
icon: hex(0xc9d0dc),
hover: hex(0x262b35),
tool_active: hex(0x2f3541),
accent: hex(0xf06a52),
accent_strong: hex(0xe8573f),
row_selected: hex(0x3a2a2c),
pasteboard: hex(0x101217),
ruler: hex(0x13161c),
ruler_tick: hex(0x6f788a),
smart_guide: hex(0xff3dfc),
guide: hex(0x4affff),
measure_bg: Color32::from_rgba_unmultiplied(24, 27, 34, 235),
button: hex(0x282d37),
radius: 4,
text_strong: hex(0xffffff),
tab_strip: hex(0x0f1116),
button_border: hex(0x4a5363),
selection: hex(0x4f80ff),
anchor_selected: hex(0x3d82ff),
flyout_active: hex(0xff8a74),
font_size: 13.0,
caption_close: hex(0xc42b1c),
caption_close_text: Color32::WHITE,
highlight: hex(0xff3030),
bleed: hex(0xf03030),
error: hex(0xe34850),
warning: hex(0xf0a330),
axis_x: hex(0xef6058),
axis_y: hex(0x45af63),
axis_z: hex(0x378ef0),
};
match b {
Brightness::Dark => base,
// Charcoal, the default.
Brightness::MediumDark => Tokens {
app_bar: hex(0x1b1f27),
panel: hex(0x272c36),
panel_darker: hex(0x21262f),
tab_strip: hex(0x1e222a),
input: hex(0x1a1e26),
input_border: hex(0x3a414e),
border: hex(0x11141a),
divider: hex(0x323946),
text: hex(0xe4e9f1),
text_dim: hex(0x99a2b2),
text_disabled: hex(0x626b7a),
icon: hex(0xccd3de),
hover: hex(0x353c49),
tool_active: hex(0x3d4553),
row_selected: hex(0x4a3236),
pasteboard: hex(0x1e222a),
ruler: hex(0x21262f),
ruler_tick: hex(0x7c8597),
measure_bg: Color32::from_rgba_unmultiplied(39, 44, 54, 235),
button: hex(0x39404d),
button_border: hex(0x5a6476),
..base
},
Brightness::MediumLight => Tokens {
dark: false,
app_bar: hex(0xc4cad4),
panel: hex(0xd3d8e0),
panel_darker: hex(0xc9cfd8),
tab_strip: hex(0xc4cad4),
input: hex(0xe9ecf1),
input_border: hex(0x9aa2b0),
border: hex(0xa9b0bc),
divider: hex(0xbcc3ce),
text: hex(0x1b1f27),
text_strong: hex(0x000000),
text_dim: hex(0x4a5160),
text_disabled: hex(0x7d8594),
icon: hex(0x272c36),
hover: hex(0xc5ccd6),
tool_active: hex(0xb6bec9),
accent: hex(0xd9472f),
accent_strong: hex(0xd14a33),
flyout_active: hex(0xc23e28),
row_selected: hex(0xe7c4bd),
pasteboard: hex(0xb9c0cb),
ruler: hex(0xccd2db),
ruler_tick: hex(0x4a5160),
measure_bg: Color32::from_rgba_unmultiplied(211, 216, 224, 235),
button: hex(0xc9d0da),
button_border: hex(0x8a93a2),
..base
},
Brightness::Light => Tokens {
dark: false,
app_bar: hex(0xe6e9ee),
panel: hex(0xf3f5f8),
panel_darker: hex(0xe9ecf1),
tab_strip: hex(0xe6e9ee),
input: hex(0xffffff),
input_border: hex(0xc3c9d3),
border: hex(0xd0d5dd),
divider: hex(0xdde1e8),
text: hex(0x1b1f27),
text_strong: hex(0x000000),
text_dim: hex(0x58606e),
text_disabled: hex(0x9aa1ad),
icon: hex(0x2a303b),
hover: hex(0xe3e7ed),
tool_active: hex(0xd5dbe3),
accent: hex(0xd9472f),
accent_strong: hex(0xd14a33),
flyout_active: hex(0xc23e28),
row_selected: hex(0xf8d9d2),
pasteboard: hex(0xdfe3e9),
ruler: hex(0xeceff3),
ruler_tick: hex(0x58606e),
measure_bg: Color32::from_rgba_unmultiplied(243, 245, 248, 235),
button: hex(0xe3e7ed),
button_border: hex(0xa4abb8),
..base
},
}
}
/// The Classic colour scheme at brightness `b`: neutral greys with a blue accent, a dark
/// pressed tool and blue-grey selected rows, on the Modern metrics (corners, sizes).
pub fn classic(b: Brightness) -> Self {
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
let base = Tokens {
app_bar: hex(0x262626),
panel: hex(0x323232),
panel_darker: hex(0x282828),
input: hex(0x1f1f1f),
input_border: hex(0x464646),
border: hex(0x1b1b1b),
divider: hex(0x3f3f3f),
text: hex(0xd8d8d8),
text_dim: hex(0xa8a8a8),
text_disabled: hex(0x6e6e6e),
icon: hex(0xcfcfcf),
hover: hex(0x444444),
tool_active: hex(0x1d1d1d),
accent: hex(0x378ef0),
accent_strong: hex(0x1473e6),
row_selected: hex(0x484b50),
pasteboard: hex(0x1e1e1e),
ruler: hex(0x2f2f2f),
ruler_tick: hex(0x8a8a8a),
measure_bg: Color32::from_rgba_unmultiplied(92, 92, 92, 235),
button: hex(0x444444),
text_strong: hex(0xf5f5f5),
tab_strip: hex(0x282828),
button_border: hex(0x6a6a6a),
flyout_active: hex(0x54b2f7),
..Tokens::for_brightness(Brightness::Dark)
};
match b {
Brightness::Dark => base,
Brightness::MediumDark => Tokens {
// A shade below the panels, so the cards stand out from the window behind them.
app_bar: hex(0x464646),
panel: hex(0x535353),
panel_darker: hex(0x424242),
tab_strip: hex(0x424242),
input: hex(0x454545),
input_border: hex(0x5f5f5f),
border: hex(0x383838),
divider: hex(0x4b4b4b),
text: hex(0xd1d1d1),
text_strong: hex(0xffffff),
text_dim: hex(0xb0b0b0),
text_disabled: hex(0x7a7a7a),
icon: hex(0xc2c2c2),
hover: hex(0x606060),
tool_active: hex(0x303030),
row_selected: hex(0x52677c),
pasteboard: hex(0x606060),
ruler: hex(0x333333),
button: hex(0x535353),
button_border: hex(0x747474),
..base
},
Brightness::MediumLight => Tokens {
dark: false,
app_bar: hex(0xa9a9a9),
panel: hex(0xb8b8b8),
panel_darker: hex(0xaaaaaa),
tab_strip: hex(0xa6a6a6),
input: hex(0xd6d6d6),
input_border: hex(0x8c8c8c),
border: hex(0x969696),
divider: hex(0x9e9e9e),
text: hex(0x1f1f1f),
text_strong: hex(0x000000),
text_dim: hex(0x3c3c3c),
text_disabled: hex(0x7c7c7c),
icon: hex(0x2a2a2a),
hover: hex(0xc8c8c8),
tool_active: hex(0x969696),
row_selected: hex(0x9fb4d6),
pasteboard: hex(0xa0a0a0),
ruler: hex(0xc2c2c2),
ruler_tick: hex(0x4a4a4a),
measure_bg: Color32::from_rgba_unmultiplied(184, 184, 184, 235),
button: hex(0xcacaca),
button_border: hex(0x7a7a7a),
flyout_active: hex(0x1473e6),
..base
},
Brightness::Light => Tokens {
dark: false,
app_bar: hex(0xe4e4e4),
panel: hex(0xf0f0f0),
panel_darker: hex(0xe2e2e2),
tab_strip: hex(0xdddddd),
input: hex(0xffffff),
input_border: hex(0xb4b4b4),
border: hex(0xcfcfcf),
divider: hex(0xd6d6d6),
text: hex(0x1f1f1f),
text_strong: hex(0x000000),
text_dim: hex(0x4b4b4b),
text_disabled: hex(0x9a9a9a),
icon: hex(0x303030),
hover: hex(0xdddddd),
tool_active: hex(0xcdcdcd),
row_selected: hex(0xc8daf7),
pasteboard: hex(0xdcdcdc),
ruler: hex(0xeaeaea),
ruler_tick: hex(0x5a5a5a),
measure_bg: Color32::from_rgba_unmultiplied(240, 240, 240, 235),
button: hex(0xe0e0e0),
button_border: hex(0x9a9a9a),
flyout_active: hex(0x1473e6),
..base
},
}
}
/// The tokens for `b` in `scheme`; Modern takes `accent`, Classic has its own blue.
pub fn for_scheme(scheme: Scheme, b: Brightness, accent: Accent) -> Self {
match scheme {
Scheme::Modern => Self::for_brightness(b).with_accent(accent),
Scheme::Classic => Self::classic(b),
}
}
/// These tokens with `accent`: the accent, buttons filled with it, the active item in flyouts and
/// selected rows (the panel tinted towards it).
pub fn with_accent(self, accent: Accent) -> Self {
let hex = |s: u32| Color32::from_rgb((s >> 16) as u8, (s >> 8) as u8, s as u8);
let (a, strong) = accent.colors(self.dark);
let (a, strong) = (hex(a), hex(strong));
let tint = |k: f32| {
let mix = |p: u8, q: u8| (f32::from(p) + (f32::from(q) - f32::from(p)) * k).round() as u8;
Color32::from_rgb(mix(self.panel.r(), a.r()), mix(self.panel.g(), a.g()), mix(self.panel.b(), a.b()))
};
Tokens {
accent: a,
accent_strong: strong,
flyout_active: if self.dark { a } else { strong },
row_selected: tint(if self.dark { 0.24 } else { 0.2 }),
tool_active: tint(if self.dark { 0.3 } else { 0.24 }),
..self
}
}
pub fn get(ctx: &egui::Context) -> Tokens {
ctx.data(|d| d.get_temp::<Tokens>(egui::Id::NULL)).unwrap_or_else(|| Tokens::for_brightness(Brightness::default()))
}
pub fn cr(&self) -> CornerRadius {
CornerRadius::same(self.radius)
}
}
/// The toolbar, the panel dock and the icon rail are cards: `body` drawn on the panel colour with
/// soft corners, `margin` of the window's frame colour around it.
pub fn card<R>(ui: &mut egui::Ui, t: &Tokens, margin: egui::Margin, body: impl FnOnce(&mut egui::Ui) -> R) -> R {
egui::Frame::NONE
.fill(t.app_bar)
.inner_margin(margin)
.show(ui, |ui| {
egui::Frame::NONE
.fill(t.panel)
.corner_radius(CARD_RADIUS)
.show(ui, |ui| {
ui.set_min_size(ui.available_size());
body(ui)
})
.inner
})
.inner
}
/// The corner radius of [`card`]s and floating windows.
pub const CARD_RADIUS: u8 = 8;
/// Selected text in the text fields `ui` draws from here on is in the strong text colour. egui
/// paints it in the selection stroke's colour, the accent, which is hard to read on the selection's
/// accent tint (#1465). For fields that draw their own frame: egui's frame takes its focus ring from
/// the same stroke.
pub fn strong_selected_text(ui: &mut egui::Ui) {
ui.visuals_mut().selection.stroke.color = Tokens::get(ui.ctx()).text_strong;
}
pub const FONT_UI: &str = "ui";
pub const FONT_UI_SEMIBOLD: &str = "ui-semibold";
pub const FONT_MONO: &str = "mono";
pub fn semibold(size: f32) -> FontId {
FontId::new(size, FontFamily::Name(FONT_UI_SEMIBOLD.into()))
}
pub fn mono(size: f32) -> FontId {
FontId::new(size, FontFamily::Name(FONT_MONO.into()))
}
pub fn install_fonts(ctx: &egui::Context) {
let mut fonts = FontDefinitions::default();
let add = |f: &mut FontDefinitions, name: &str, bytes: &'static [u8]| {
f.font_data.insert(name.to_owned(), Arc::new(FontData::from_static(bytes)));
};
add(&mut fonts, "SourceSans3", include_bytes!("../../../assets/fonts/SourceSans3-Regular.ttf"));
// Inter is drawn a little smaller than its size: its letters are wider than Source Sans's, and
// the panels are laid out for those.
let inter =
|bytes: &'static [u8]| Arc::new(FontData::from_static(bytes).tweak(egui::epaint::text::FontTweak { scale: 0.92, ..Default::default() }));
fonts.font_data.insert("Inter".to_owned(), inter(include_bytes!("../../../assets/fonts/Inter-Regular.ttf")));
fonts.font_data.insert("Inter-SemiBold".to_owned(), inter(include_bytes!("../../../assets/fonts/Inter-SemiBold.ttf")));
add(&mut fonts, "SourceSans3-Semibold", include_bytes!("../../../assets/fonts/SourceSans3-Semibold.ttf"));
add(&mut fonts, "JetBrainsMono", include_bytes!("../../../assets/fonts/JetBrainsMono-Regular.ttf"));
let fallback: Vec<String> = fonts.families.get(&FontFamily::Proportional).cloned().unwrap_or_default();
let mut prop = vec!["Inter".to_string(), "SourceSans3".to_string()];
prop.extend(fallback.clone());
fonts.families.insert(FontFamily::Proportional, prop);
let mut semi = vec!["Inter-SemiBold".to_string(), "SourceSans3-Semibold".to_string()];
semi.extend(fallback.clone());
fonts.families.insert(FontFamily::Name(FONT_UI_SEMIBOLD.into()), semi);
let mut ui = vec!["Inter".to_string(), "SourceSans3".to_string()];
ui.extend(fallback.clone());
fonts.families.insert(FontFamily::Name(FONT_UI.into()), ui);
let mut mono = vec!["JetBrainsMono".to_string()];
mono.extend(fallback);
fonts.families.insert(FontFamily::Name(FONT_MONO.into()), mono);
add_craft_fonts(&mut fonts);
ctx.set_fonts(fonts);
}
/// The egui name of a craft-fonts face.
pub(crate) fn craft_font_name(f: &vectorcraft_text::CraftFont) -> String {
format!("craft-fonts {} {}", f.family, f.style)
}
/// The Japanese craft-fonts faces (when built with `CRAFT_FONTS_DIR`) as fallbacks at the end of
/// every family, after the app's own fonts and before the installed fonts `ui_fonts` adds: BIZ
/// UDPGothic first (bold first in the semibold family), then the Mincho faces.
fn add_craft_fonts(fonts: &mut FontDefinitions) {
for f in vectorcraft_text::CRAFT_FONTS.iter().filter(|f| f.is_japanese()) {
fonts.font_data.insert(craft_font_name(f), Arc::new(FontData::from_static(f.bytes)));
}
for (family, stack) in fonts.families.iter_mut() {
let bold = *family == FontFamily::Name(FONT_UI_SEMIBOLD.into());
stack.extend(vectorcraft_text::craft_fonts::japanese_ui_fonts(bold).into_iter().map(craft_font_name));
}
}
/// Apply tokens to egui's global style.
pub fn apply(ctx: &egui::Context, b: Brightness) {
apply_with(ctx, b, Accent::default(), Scheme::default());
}
/// [`apply`] with an accent colour and colour scheme.
pub fn apply_with(ctx: &egui::Context, b: Brightness, accent: Accent, scheme: Scheme) {
let t = Tokens::for_scheme(scheme, b, accent);
ctx.data_mut(|d| d.insert_temp(egui::Id::NULL, t));
let mut v = if t.dark { Visuals::dark() } else { Visuals::light() };
v.panel_fill = t.panel;
v.window_fill = t.panel;
v.extreme_bg_color = t.input;
v.faint_bg_color = t.panel_darker;
v.window_stroke = Stroke::new(1.0, t.border);
v.window_corner_radius = CornerRadius::same(10);
v.menu_corner_radius = CornerRadius::same(8);
v.selection.bg_fill = t.accent.gamma_multiply(0.55);
v.selection.stroke = Stroke::new(1.0, t.accent);
v.hyperlink_color = t.accent;
v.override_text_color = Some(t.text);
v.popup_shadow = egui::epaint::Shadow { offset: [0, 4], blur: 14, spread: 0, color: Color32::from_black_alpha(if t.dark { 120 } else { 60 }) };
v.window_shadow = egui::epaint::Shadow { offset: [0, 8], blur: 28, spread: 0, color: Color32::from_black_alpha(if t.dark { 140 } else { 70 }) };
let w = &mut v.widgets;
craft_ui::theme::WidgetPalette { noninteractive: t.panel, inactive: t.button, hovered: t.hover, active: t.tool_active, open: t.hover }.apply_to(
w,
t.cr(),
Stroke::new(1.0, t.text),
);
w.noninteractive.bg_stroke = Stroke::new(1.0, t.divider);
w.inactive.bg_stroke = Stroke::NONE;
w.hovered.bg_stroke = Stroke::new(1.0, t.input_border);
w.active.bg_stroke = Stroke::new(1.0, t.accent);
ctx.set_visuals(v);
ctx.global_style_mut(|s| {
s.spacing.item_spacing = egui::vec2(6.0, 5.0);
s.spacing.button_padding = egui::vec2(6.0, 2.0);
s.spacing.interact_size = egui::vec2(24.0, 24.0);
s.spacing.menu_margin = egui::Margin::same(6);
s.spacing.slider_width = 120.0;
s.text_styles = [
(TextStyle::Small, FontId::proportional(11.0)),
(TextStyle::Body, FontId::proportional(13.0)),
(TextStyle::Button, FontId::proportional(13.0)),
(TextStyle::Heading, FontId::new(15.0, FontFamily::Name(FONT_UI_SEMIBOLD.into()))),
(TextStyle::Monospace, FontId::new(11.5, FontFamily::Name(FONT_MONO.into()))),
]
.into();
s.animation_time = 0.08;
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn brightness_roundtrip() {
for b in Brightness::ALL {
assert_eq!(Brightness::parse(b.id()), Some(b));
assert_eq!(Brightness::parse(b.label()), Some(b));
}
assert!(!Tokens::for_brightness(Brightness::Light).dark);
}
/// #1662: a slider's rail (`button_border`, see `widgets::slider`) stands out from the panel
/// in every brightness.
#[test]
fn slider_rails_show_on_every_panel() {
for b in Brightness::ALL {
for scheme in Scheme::ALL {
let t = Tokens::for_scheme(scheme, b, Accent::default());
let gap = (i32::from(t.button_border.r()) - i32::from(t.panel.r())).abs();
assert!(gap >= 24, "{scheme:?} {b:?}: rail {:?} on panel {:?}", t.button_border, t.panel);
}
}
}
/// Classic keeps the Modern corners and the cards' frame, and has its own blue accent
/// whatever accent is chosen.
#[test]
fn classic_scheme_keeps_the_corners_and_its_blue() {
for b in Brightness::ALL {
let (modern, classic) = (Tokens::for_brightness(b), Tokens::for_scheme(Scheme::Classic, b, Accent::Pink));
assert_eq!(classic.radius, modern.radius);
assert_eq!(classic.dark, modern.dark, "{b:?}");
assert_ne!(classic.app_bar, classic.panel, "{b:?}: the cards need a frame");
assert_eq!(classic.accent, Color32::from_rgb(0x37, 0x8e, 0xf0));
assert_eq!(Scheme::parse(Scheme::Classic.id()), Some(Scheme::Classic));
}
}
}