Another project
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(ui): macro-ify theme dim and length newtypes

Lewis: May this revision serve well! <lu5a@proton.me>

+390
+77
crates/bone-ui/src/theme/dim.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + macro_rules! px_newtype_nonneg { 4 + ($name:ident) => { 5 + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] 6 + #[serde(transparent)] 7 + pub struct $name(f32); 8 + 9 + impl $name { 10 + #[must_use] 11 + pub const fn px(value: f32) -> Self { 12 + assert!( 13 + value.is_finite() && value >= 0.0, 14 + concat!( 15 + stringify!($name), 16 + " must be a non-negative finite value in device-independent pixels" 17 + ) 18 + ); 19 + Self(value) 20 + } 21 + 22 + #[must_use] 23 + pub const fn value_px(self) -> f32 { 24 + self.0 25 + } 26 + } 27 + 28 + impl core::fmt::Display for $name { 29 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 30 + write!(f, "{}px", self.0) 31 + } 32 + } 33 + }; 34 + } 35 + 36 + macro_rules! px_newtype_signed { 37 + ($name:ident) => { 38 + #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)] 39 + #[serde(transparent)] 40 + pub struct $name(f32); 41 + 42 + impl $name { 43 + #[must_use] 44 + pub const fn px(value: f32) -> Self { 45 + assert!( 46 + value.is_finite(), 47 + concat!( 48 + stringify!($name), 49 + " must be a finite value in device-independent pixels" 50 + ) 51 + ); 52 + Self(value) 53 + } 54 + 55 + #[must_use] 56 + pub const fn value_px(self) -> f32 { 57 + self.0 58 + } 59 + } 60 + 61 + impl core::fmt::Display for $name { 62 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 63 + write!(f, "{}px", self.0) 64 + } 65 + } 66 + }; 67 + } 68 + 69 + px_newtype_nonneg!(Spacing); 70 + px_newtype_nonneg!(Radius); 71 + px_newtype_nonneg!(StrokeWidth); 72 + px_newtype_nonneg!(BlurRadius); 73 + px_newtype_signed!(ShadowOffset); 74 + 75 + impl StrokeWidth { 76 + pub const HAIRLINE: Self = Self::px(1.0); 77 + }
+69
crates/bone-ui/src/theme/elevation.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + use super::color::{Color, Scale12, Step12, SurfaceLevel}; 4 + use super::dim::{BlurRadius, ShadowOffset, StrokeWidth}; 5 + 6 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 7 + pub struct Border { 8 + pub width: StrokeWidth, 9 + pub color: Color, 10 + } 11 + 12 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 13 + pub struct Shadow { 14 + pub offset_x: ShadowOffset, 15 + pub offset_y: ShadowOffset, 16 + pub blur: BlurRadius, 17 + pub color: Color, 18 + } 19 + 20 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 21 + pub struct ElevationLevel { 22 + pub surface: SurfaceLevel, 23 + pub border: Option<Border>, 24 + pub shadow: Option<Shadow>, 25 + } 26 + 27 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 28 + pub struct ElevationScale { 29 + pub level0: ElevationLevel, 30 + pub level1: ElevationLevel, 31 + pub level2: ElevationLevel, 32 + pub level3: ElevationLevel, 33 + } 34 + 35 + impl ElevationScale { 36 + pub(in crate::theme) fn tonal(neutral: Scale12) -> Self { 37 + Self { 38 + level0: ElevationLevel { 39 + surface: SurfaceLevel::L0, 40 + border: None, 41 + shadow: None, 42 + }, 43 + level1: ElevationLevel { 44 + surface: SurfaceLevel::L1, 45 + border: Some(Border { 46 + width: StrokeWidth::HAIRLINE, 47 + color: neutral.step(Step12::SUBTLE_BORDER), 48 + }), 49 + shadow: None, 50 + }, 51 + level2: ElevationLevel { 52 + surface: SurfaceLevel::L2, 53 + border: Some(Border { 54 + width: StrokeWidth::HAIRLINE, 55 + color: neutral.step(Step12::BORDER), 56 + }), 57 + shadow: None, 58 + }, 59 + level3: ElevationLevel { 60 + surface: SurfaceLevel::L3, 61 + border: Some(Border { 62 + width: StrokeWidth::HAIRLINE, 63 + color: neutral.step(Step12::HOVER_BORDER), 64 + }), 65 + shadow: None, 66 + }, 67 + } 68 + } 69 + }
+76
crates/bone-ui/src/theme/mod.rs
··· 1 + mod color; 2 + mod dim; 3 + mod elevation; 4 + mod motion; 5 + mod scales; 6 + mod typography; 7 + 8 + pub use color::{CadColors, Color, ColorError, Colors, Scale12, Step12, SurfaceLevel}; 9 + pub use dim::{BlurRadius, Radius, ShadowOffset, Spacing, StrokeWidth}; 10 + pub use elevation::{Border, ElevationLevel, ElevationScale, Shadow}; 11 + pub use motion::{Easing, Motion, MotionToken}; 12 + pub use scales::{RadiusScale, SpacingScale}; 13 + pub use typography::{ 14 + FontFace, FontSize, FontWeight, LetterSpacing, LineHeight, Typography, TypographyRole, 15 + }; 16 + 17 + use serde::{Deserialize, Serialize}; 18 + 19 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 20 + pub enum ThemeMode { 21 + Light, 22 + Dark, 23 + } 24 + 25 + impl core::fmt::Display for ThemeMode { 26 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 27 + match self { 28 + Self::Light => f.write_str("light"), 29 + Self::Dark => f.write_str("dark"), 30 + } 31 + } 32 + } 33 + 34 + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 35 + pub struct Theme { 36 + pub mode: ThemeMode, 37 + pub colors: Colors, 38 + pub cad: CadColors, 39 + pub spacing: SpacingScale, 40 + pub typography: Typography, 41 + pub radius: RadiusScale, 42 + pub elevation: ElevationScale, 43 + pub motion: Motion, 44 + } 45 + 46 + impl Theme { 47 + #[must_use] 48 + pub fn light() -> Self { 49 + let colors = color::light_colors(); 50 + Self { 51 + mode: ThemeMode::Light, 52 + cad: CadColors::solidworks(), 53 + spacing: SpacingScale::STANDARD, 54 + typography: Typography::standard(), 55 + radius: RadiusScale::STANDARD, 56 + elevation: ElevationScale::tonal(colors.neutral), 57 + motion: Motion::STANDARD, 58 + colors, 59 + } 60 + } 61 + 62 + #[must_use] 63 + pub fn dark() -> Self { 64 + let colors = color::dark_colors(); 65 + Self { 66 + mode: ThemeMode::Dark, 67 + cad: CadColors::solidworks(), 68 + spacing: SpacingScale::STANDARD, 69 + typography: Typography::standard(), 70 + radius: RadiusScale::STANDARD, 71 + elevation: ElevationScale::tonal(colors.neutral), 72 + motion: Motion::STANDARD, 73 + colors, 74 + } 75 + } 76 + }
+43
crates/bone-ui/src/theme/scales.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + use super::dim::{Radius, Spacing}; 4 + 5 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 6 + pub struct SpacingScale { 7 + pub xs: Spacing, 8 + pub sm: Spacing, 9 + pub md: Spacing, 10 + pub lg: Spacing, 11 + pub xl: Spacing, 12 + pub xxl: Spacing, 13 + } 14 + 15 + impl SpacingScale { 16 + pub const STANDARD: Self = Self { 17 + xs: Spacing::px(4.0), 18 + sm: Spacing::px(8.0), 19 + md: Spacing::px(12.0), 20 + lg: Spacing::px(16.0), 21 + xl: Spacing::px(24.0), 22 + xxl: Spacing::px(32.0), 23 + }; 24 + } 25 + 26 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 27 + pub struct RadiusScale { 28 + pub none: Radius, 29 + pub sm: Radius, 30 + pub md: Radius, 31 + pub lg: Radius, 32 + pub pill: Radius, 33 + } 34 + 35 + impl RadiusScale { 36 + pub const STANDARD: Self = Self { 37 + none: Radius::px(0.0), 38 + sm: Radius::px(2.0), 39 + md: Radius::px(4.0), 40 + lg: Radius::px(8.0), 41 + pill: Radius::px(9999.0), 42 + }; 43 + }
+125
crates/bone-ui/src/theme/typography.rs
··· 1 + use serde::{Deserialize, Deserializer, Serialize, Serializer}; 2 + use uom::si::f64::Length; 3 + use uom::si::length::{meter, millimeter}; 4 + 5 + const PX_TO_MM: f64 = 25.4 / 96.0; 6 + const MM_TO_PX: f64 = 96.0 / 25.4; 7 + 8 + fn px_to_length(px: f64) -> Length { 9 + Length::new::<millimeter>(px * PX_TO_MM) 10 + } 11 + 12 + fn length_to_px(length: Length) -> f64 { 13 + length.get::<millimeter>() * MM_TO_PX 14 + } 15 + 16 + fn length_to_meters(length: Length) -> f64 { 17 + length.get::<meter>() 18 + } 19 + 20 + fn length_from_meters(meters: f64) -> Length { 21 + Length::new::<meter>(meters) 22 + } 23 + 24 + macro_rules! length_newtype { 25 + ($name:ident) => { 26 + #[derive(Copy, Clone, Debug, PartialEq)] 27 + pub struct $name(Length); 28 + 29 + impl $name { 30 + #[must_use] 31 + pub fn from_px(px: f64) -> Self { 32 + Self(px_to_length(px)) 33 + } 34 + 35 + #[must_use] 36 + pub fn as_px(self) -> f64 { 37 + length_to_px(self.0) 38 + } 39 + 40 + #[must_use] 41 + pub fn length(self) -> Length { 42 + self.0 43 + } 44 + } 45 + 46 + impl core::fmt::Display for $name { 47 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 48 + write!(f, "{}px", self.as_px()) 49 + } 50 + } 51 + 52 + impl Serialize for $name { 53 + fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> { 54 + s.serialize_f64(length_to_meters(self.0)) 55 + } 56 + } 57 + 58 + impl<'de> Deserialize<'de> for $name { 59 + fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> { 60 + f64::deserialize(d).map(|m| Self(length_from_meters(m))) 61 + } 62 + } 63 + }; 64 + } 65 + 66 + length_newtype!(FontSize); 67 + length_newtype!(LineHeight); 68 + length_newtype!(LetterSpacing); 69 + 70 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 71 + pub enum FontWeight { 72 + Regular, 73 + Medium, 74 + Semibold, 75 + Bold, 76 + } 77 + 78 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 79 + pub enum FontFace { 80 + Sans, 81 + Mono, 82 + } 83 + 84 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 85 + pub struct TypographyRole { 86 + pub face: FontFace, 87 + pub size: FontSize, 88 + pub line_height: LineHeight, 89 + pub weight: FontWeight, 90 + pub letter_spacing: LetterSpacing, 91 + } 92 + 93 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 94 + pub struct Typography { 95 + pub caption: TypographyRole, 96 + pub body: TypographyRole, 97 + pub label: TypographyRole, 98 + pub title: TypographyRole, 99 + pub heading: TypographyRole, 100 + pub mono: TypographyRole, 101 + } 102 + 103 + impl Typography { 104 + #[must_use] 105 + pub fn standard() -> Self { 106 + Self { 107 + caption: role(FontFace::Sans, 11.0, 14.0, FontWeight::Regular), 108 + body: role(FontFace::Sans, 13.0, 18.0, FontWeight::Regular), 109 + label: role(FontFace::Sans, 12.0, 16.0, FontWeight::Medium), 110 + title: role(FontFace::Sans, 14.0, 20.0, FontWeight::Semibold), 111 + heading: role(FontFace::Sans, 18.0, 24.0, FontWeight::Semibold), 112 + mono: role(FontFace::Mono, 12.0, 16.0, FontWeight::Regular), 113 + } 114 + } 115 + } 116 + 117 + fn role(face: FontFace, size_px: f64, line_height_px: f64, weight: FontWeight) -> TypographyRole { 118 + TypographyRole { 119 + face, 120 + size: FontSize::from_px(size_px), 121 + line_height: LineHeight::from_px(line_height_px), 122 + weight, 123 + letter_spacing: LetterSpacing::from_px(0.0), 124 + } 125 + }