/// Border radius scale. /// /// Use named tokens, not raw pixel values. Swap out the RadiusScale /// in the theme to change the visual "softness" of the entire UI at once. /// Named border radius tokens. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Radius { /// 0 — sharp corners None, /// 4dp — subtle rounding (list items, small chips) Sm, /// 8dp — default card / button rounding Md, /// 12dp — more prominent rounding (modals, drawers) Lg, /// 16dp — very rounded (bottom sheets, large cards) Xl, /// 9999dp — fully pill-shaped Full, /// Explicit dp value (escape hatch) Custom(u32), } impl Radius { /// Resolve to dp. pub fn dp(&self) -> u32 { match self { Radius::None => 0, Radius::Sm => 4, Radius::Md => 8, Radius::Lg => 12, Radius::Xl => 16, Radius::Full => 9999, Radius::Custom(v) => *v, } } /// CSS border-radius string. pub fn to_css(&self) -> String { match self { Radius::Full => "9999px".to_string(), other => format!("{}px", other.dp()), } } } /// Theme-level radius scale. #[derive(Debug, Clone)] pub struct RadiusScale { pub sm: u32, pub md: u32, pub lg: u32, pub xl: u32, } impl RadiusScale { pub fn default() -> Self { Self { sm: 4, md: 8, lg: 12, xl: 16, } } /// Resolve a Radius token to dp using this scale. pub fn resolve(&self, radius: &Radius) -> u32 { match radius { Radius::None => 0, Radius::Sm => self.sm, Radius::Md => self.md, Radius::Lg => self.lg, Radius::Xl => self.xl, Radius::Full => 9999, Radius::Custom(v) => *v, } } } #[cfg(test)] mod tests { use super::*; #[test] fn radius_none_is_zero() { assert_eq!(Radius::None.dp(), 0); } #[test] fn radius_full_is_large() { assert_eq!(Radius::Full.dp(), 9999); } #[test] fn radius_css_full() { assert_eq!(Radius::Full.to_css(), "9999px"); } #[test] fn radius_css_md() { assert_eq!(Radius::Md.to_css(), "8px"); } #[test] fn radius_custom() { assert_eq!(Radius::Custom(20).dp(), 20); } #[test] fn radius_scale_default() { let scale = RadiusScale::default(); assert_eq!(scale.resolve(&Radius::Md), 8); assert_eq!(scale.resolve(&Radius::Lg), 12); } }