use egui::{Margin, Vec2, vec2};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Metrics {
pub item_spacing: [f32; 2],
pub button_padding: [f32; 2],
pub window_margin: f32,
pub menu_margin: f32,
pub interact_size: [f32; 2],
pub indent: f32,
pub slider_width: f32,
pub icon_width: f32,
pub corner_radius: u8,
pub window_corner_radius: u8,
pub menu_corner_radius: u8,
pub stroke_width: f32,
}
impl Default for Metrics {
fn default() -> Self {
Self::windows()
}
}
impl Metrics {
pub fn windows() -> Self {
Self {
item_spacing: [8.0, 4.0],
button_padding: [12.0, 6.0],
window_margin: 8.0,
menu_margin: 8.0,
interact_size: [40.0, 24.0],
indent: 16.0,
slider_width: 100.0,
icon_width: 16.0,
corner_radius: 6,
window_corner_radius: 8,
menu_corner_radius: 6,
stroke_width: 1.0,
}
}
pub fn macos() -> Self {
Self {
item_spacing: [8.0, 4.0],
button_padding: [12.0, 8.0],
window_margin: 12.0,
menu_margin: 8.0,
interact_size: [44.0, 28.0],
indent: 16.0,
slider_width: 120.0,
icon_width: 16.0,
corner_radius: 8,
window_corner_radius: 12,
menu_corner_radius: 8,
stroke_width: 1.0,
}
}
pub fn device() -> Self {
Self {
item_spacing: [8.0, 4.0],
button_padding: [12.0, 8.0],
window_margin: 8.0,
menu_margin: 8.0,
interact_size: [44.0, 28.0], indent: 16.0,
slider_width: 120.0,
icon_width: 20.0,
corner_radius: 2,
window_corner_radius: 2,
menu_corner_radius: 2,
stroke_width: 1.5, }
}
pub fn item_spacing_vec(&self) -> Vec2 {
vec2(self.item_spacing[0], self.item_spacing[1])
}
pub fn button_padding_vec(&self) -> Vec2 {
vec2(self.button_padding[0], self.button_padding[1])
}
pub fn interact_size_vec(&self) -> Vec2 {
vec2(self.interact_size[0], self.interact_size[1])
}
pub fn window_margin_m(&self) -> Margin {
Margin::same(self.window_margin as i8)
}
pub fn menu_margin_m(&self) -> Margin {
Margin::same(self.menu_margin as i8)
}
pub fn on_4pt_grid(&self) -> bool {
let on4 = |x: f32| (x % 4.0).abs() < 1e-3;
on4(self.item_spacing[0])
&& on4(self.item_spacing[1])
&& on4(self.window_margin)
&& on4(self.menu_margin)
&& on4(self.indent)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_presets_are_on_the_4pt_grid() {
assert!(Metrics::windows().on_4pt_grid(), "windows metrics off-grid");
assert!(Metrics::macos().on_4pt_grid(), "macos metrics off-grid");
assert!(Metrics::device().on_4pt_grid(), "device metrics off-grid");
}
#[test]
fn interact_height_is_at_least_24() {
for m in [Metrics::windows(), Metrics::macos(), Metrics::device()] {
assert!(m.interact_size[1] >= 24.0, "interact height must be >= 24, got {}", m.interact_size[1]);
}
}
#[test]
fn device_radii_are_tight() {
assert!(Metrics::device().corner_radius <= 2, "Device must be crisp/tight");
assert!(Metrics::macos().corner_radius >= Metrics::windows().corner_radius, "macOS softer than Windows");
}
}