Another project
1
fork

Configure Feed

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

feat(ui): design tokens, why not :P

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

+2072
+49
Cargo.lock
··· 286 286 ] 287 287 288 288 [[package]] 289 + name = "bone-ui" 290 + version = "0.0.0" 291 + dependencies = [ 292 + "insta", 293 + "palette", 294 + "ron", 295 + "serde", 296 + "thiserror 2.0.18", 297 + "uom", 298 + ] 299 + 300 + [[package]] 289 301 name = "bumpalo" 290 302 version = "3.20.2" 291 303 source = "registry+https://github.com/rust-lang/crates.io-index" 292 304 checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" 293 305 294 306 [[package]] 307 + name = "by_address" 308 + version = "1.2.1" 309 + source = "registry+https://github.com/rust-lang/crates.io-index" 310 + checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" 311 + 312 + [[package]] 295 313 name = "bytemuck" 296 314 version = "1.25.0" 297 315 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 681 699 ] 682 700 683 701 [[package]] 702 + name = "fast-srgb8" 703 + version = "1.0.0" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" 706 + 707 + [[package]] 684 708 name = "fastrand" 685 709 version = "2.4.1" 686 710 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1075 1099 dependencies = [ 1076 1100 "console", 1077 1101 "once_cell", 1102 + "ron", 1103 + "serde", 1078 1104 "similar", 1079 1105 "tempfile", 1080 1106 ] ··· 1859 1885 checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" 1860 1886 dependencies = [ 1861 1887 "ttf-parser", 1888 + ] 1889 + 1890 + [[package]] 1891 + name = "palette" 1892 + version = "0.7.6" 1893 + source = "registry+https://github.com/rust-lang/crates.io-index" 1894 + checksum = "4cbf71184cc5ecc2e4e1baccdb21026c20e5fc3dcf63028a086131b3ab00b6e6" 1895 + dependencies = [ 1896 + "approx", 1897 + "fast-srgb8", 1898 + "palette_derive", 1899 + ] 1900 + 1901 + [[package]] 1902 + name = "palette_derive" 1903 + version = "0.7.6" 1904 + source = "registry+https://github.com/rust-lang/crates.io-index" 1905 + checksum = "f5030daf005bface118c096f510ffb781fc28f9ab6a32ab224d8631be6851d30" 1906 + dependencies = [ 1907 + "by_address", 1908 + "proc-macro2", 1909 + "quote", 1910 + "syn 2.0.117", 1862 1911 ] 1863 1912 1864 1913 [[package]]
+3
Cargo.toml
··· 6 6 "crates/bone-solver", 7 7 "crates/bone-document", 8 8 "crates/bone-render", 9 + "crates/bone-ui", 9 10 "crates/bone-app", 10 11 ] 11 12 ··· 32 33 bone-solver = { path = "crates/bone-solver" } 33 34 bone-document = { path = "crates/bone-document" } 34 35 bone-render = { path = "crates/bone-render" } 36 + bone-ui = { path = "crates/bone-ui" } 35 37 36 38 blake3 = { version = "1", default-features = false, features = ["std"] } 37 39 bytemuck = { version = "1", default-features = false, features = ["derive"] } ··· 39 41 insta = "1" 40 42 lyon_tessellation = "1" 41 43 nalgebra = { version = "0.33", default-features = false, features = ["std"] } 44 + palette = { version = "0.7", default-features = false, features = ["std"] } 42 45 png = { version = "0.17", default-features = false } 43 46 pollster = "0.4" 44 47 proptest = { version = "1", default-features = false, features = ["std"] }
+19
crates/bone-ui/Cargo.toml
··· 1 + [package] 2 + name = "bone-ui" 3 + version.workspace = true 4 + edition.workspace = true 5 + license.workspace = true 6 + rust-version.workspace = true 7 + 8 + [dependencies] 9 + palette = { workspace = true } 10 + serde = { workspace = true } 11 + thiserror = { workspace = true } 12 + uom = { workspace = true } 13 + 14 + [dev-dependencies] 15 + insta = { workspace = true, features = ["ron"] } 16 + ron = { workspace = true } 17 + 18 + [lints] 19 + workspace = true
+8
crates/bone-ui/src/lib.rs
··· 1 + pub mod theme; 2 + 3 + pub use theme::{ 4 + BlurRadius, Border, CadColors, Color, ColorError, Colors, Easing, ElevationLevel, 5 + ElevationScale, FontFace, FontSize, FontWeight, LetterSpacing, LineHeight, Motion, MotionToken, 6 + Radius, RadiusScale, Scale12, Shadow, ShadowOffset, Spacing, SpacingScale, Step12, 7 + StrokeWidth, SurfaceLevel, Theme, ThemeMode, Typography, TypographyRole, 8 + };
+683
crates/bone-ui/src/theme/color.rs
··· 1 + use palette::convert::FromColorUnclamped; 2 + use palette::{LinSrgb, Oklch, Srgb}; 3 + use serde::{Deserialize, Serialize}; 4 + 5 + #[derive(Debug, thiserror::Error, PartialEq, Eq)] 6 + pub enum ColorError { 7 + #[error("color channel is not finite")] 8 + ChannelNotFinite, 9 + #[error("color channel outside [0, 1]")] 10 + ChannelOutOfRange, 11 + #[error("color violates premultiplied invariant: rgb must be <= a")] 12 + NotPremultiplied, 13 + } 14 + 15 + #[derive(Copy, Clone, PartialEq, Serialize, Deserialize)] 16 + #[serde(try_from = "ColorWire", into = "ColorWire")] 17 + pub struct Color([f32; 4]); 18 + 19 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 20 + #[serde(rename = "Color", deny_unknown_fields)] 21 + struct ColorWire { 22 + r: f32, 23 + g: f32, 24 + b: f32, 25 + a: f32, 26 + } 27 + 28 + impl From<Color> for ColorWire { 29 + fn from(c: Color) -> Self { 30 + Self { 31 + r: c.0[0], 32 + g: c.0[1], 33 + b: c.0[2], 34 + a: c.0[3], 35 + } 36 + } 37 + } 38 + 39 + const PREMUL_TOLERANCE: f32 = 1.0e-5; 40 + 41 + fn validate_premul(rgba: [f32; 4]) -> Result<(), ColorError> { 42 + if !rgba.iter().all(|c| c.is_finite()) { 43 + return Err(ColorError::ChannelNotFinite); 44 + } 45 + if !rgba.iter().all(|c| (0.0..=1.0).contains(c)) { 46 + return Err(ColorError::ChannelOutOfRange); 47 + } 48 + let alpha = rgba[3]; 49 + if rgba[..3].iter().any(|c| *c > alpha + PREMUL_TOLERANCE) { 50 + return Err(ColorError::NotPremultiplied); 51 + } 52 + Ok(()) 53 + } 54 + 55 + impl TryFrom<ColorWire> for Color { 56 + type Error = ColorError; 57 + 58 + fn try_from(w: ColorWire) -> Result<Self, ColorError> { 59 + let rgba = [w.r, w.g, w.b, w.a]; 60 + validate_premul(rgba)?; 61 + Ok(Self(rgba)) 62 + } 63 + } 64 + 65 + impl Color { 66 + pub const TRANSPARENT: Self = Self([0.0, 0.0, 0.0, 0.0]); 67 + 68 + #[must_use] 69 + pub(in crate::theme) const fn from_linear_rgba_premul( 70 + r: f32, 71 + g: f32, 72 + b: f32, 73 + a: f32, 74 + ) -> Self { 75 + Self([r, g, b, a]) 76 + } 77 + 78 + #[must_use] 79 + pub(in crate::theme) fn from_linear_rgba(r: f32, g: f32, b: f32, a: f32) -> Self { 80 + Self([r * a, g * a, b * a, a]) 81 + } 82 + 83 + #[must_use] 84 + pub(in crate::theme) fn from_srgb_u8(r: u8, g: u8, b: u8) -> Self { 85 + let lin: LinSrgb<f32> = Srgb::new( 86 + f32::from(r) / 255.0, 87 + f32::from(g) / 255.0, 88 + f32::from(b) / 255.0, 89 + ) 90 + .into_linear(); 91 + Self::from_linear_rgba(lin.red, lin.green, lin.blue, 1.0) 92 + } 93 + 94 + #[must_use] 95 + pub(in crate::theme) fn from_oklch(l: f32, c: f32, h_degrees: f32) -> Self { 96 + let rgb = oklch_to_in_gamut_linsrgb(l, c, h_degrees); 97 + Self::from_linear_rgba(rgb[0], rgb[1], rgb[2], 1.0) 98 + } 99 + 100 + #[must_use] 101 + pub fn linear_rgba_premul(self) -> [f32; 4] { 102 + self.0 103 + } 104 + 105 + #[must_use] 106 + pub fn alpha(self) -> f32 { 107 + self.0[3] 108 + } 109 + 110 + #[must_use] 111 + pub fn with_alpha(self, alpha: f32) -> Self { 112 + let alpha = alpha.clamp(0.0, 1.0); 113 + let current = self.0[3]; 114 + if current <= 0.0 { 115 + return Self([0.0, 0.0, 0.0, alpha]); 116 + } 117 + let scale = alpha / current; 118 + Self([ 119 + (self.0[0] * scale).min(alpha), 120 + (self.0[1] * scale).min(alpha), 121 + (self.0[2] * scale).min(alpha), 122 + alpha, 123 + ]) 124 + } 125 + 126 + #[must_use] 127 + pub fn blend(self, other: Self, t: f32) -> Self { 128 + let t = t.clamp(0.0, 1.0); 129 + let s = 1.0 - t; 130 + Self([ 131 + self.0[0] * s + other.0[0] * t, 132 + self.0[1] * s + other.0[1] * t, 133 + self.0[2] * s + other.0[2] * t, 134 + self.0[3] * s + other.0[3] * t, 135 + ]) 136 + } 137 + 138 + #[must_use] 139 + pub fn relative_luminance(self) -> f32 { 140 + let alpha = self.0[3]; 141 + if alpha <= 0.0 { 142 + return 0.0; 143 + } 144 + let inv = 1.0 / alpha; 145 + let r = self.0[0] * inv; 146 + let g = self.0[1] * inv; 147 + let b = self.0[2] * inv; 148 + 0.2126 * r + 0.7152 * g + 0.0722 * b 149 + } 150 + 151 + #[must_use] 152 + pub fn on_surface(self) -> Self { 153 + if self.relative_luminance() > 0.179 { 154 + Self::from_linear_rgba_premul(0.04, 0.04, 0.04, 1.0) 155 + } else { 156 + Self::from_linear_rgba_premul(1.0, 1.0, 1.0, 1.0) 157 + } 158 + } 159 + } 160 + 161 + impl core::fmt::Debug for Color { 162 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 163 + write!( 164 + f, 165 + "Color(r={}, g={}, b={}, a={})", 166 + self.0[0], self.0[1], self.0[2], self.0[3] 167 + ) 168 + } 169 + } 170 + 171 + impl core::fmt::Display for Color { 172 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 173 + write!( 174 + f, 175 + "rgba_lin_premul({}, {}, {}, {})", 176 + self.0[0], self.0[1], self.0[2], self.0[3] 177 + ) 178 + } 179 + } 180 + 181 + const SRGB_GAMUT_EPS: f32 = 1.0e-4; 182 + const GAMUT_BISECT_STEPS: u32 = 24; 183 + 184 + fn oklch_to_linsrgb_raw(l: f32, c: f32, h_degrees: f32) -> [f32; 3] { 185 + let lin = LinSrgb::<f32>::from_color_unclamped(Oklch::<f32>::new(l, c, h_degrees)); 186 + [lin.red, lin.green, lin.blue] 187 + } 188 + 189 + fn channel_in_gamut(channel: f32) -> bool { 190 + (-SRGB_GAMUT_EPS..=1.0 + SRGB_GAMUT_EPS).contains(&channel) 191 + } 192 + 193 + fn in_gamut(rgb: [f32; 3]) -> bool { 194 + rgb.iter().copied().all(channel_in_gamut) 195 + } 196 + 197 + fn oklch_to_in_gamut_linsrgb(l: f32, c: f32, h_degrees: f32) -> [f32; 3] { 198 + let direct = oklch_to_linsrgb_raw(l, c, h_degrees); 199 + if in_gamut(direct) { 200 + return clamp_unit_rgb(direct); 201 + } 202 + let (chroma_in, _) = (0..GAMUT_BISECT_STEPS).fold((0.0_f32, c), |(lo, hi), _| { 203 + let mid = 0.5 * (lo + hi); 204 + if in_gamut(oklch_to_linsrgb_raw(l, mid, h_degrees)) { 205 + (mid, hi) 206 + } else { 207 + (lo, mid) 208 + } 209 + }); 210 + clamp_unit_rgb(oklch_to_linsrgb_raw(l, chroma_in, h_degrees)) 211 + } 212 + 213 + fn clamp_unit_rgb(rgb: [f32; 3]) -> [f32; 3] { 214 + rgb.map(|c| c.clamp(0.0, 1.0)) 215 + } 216 + 217 + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 218 + pub struct Step12(u8); 219 + 220 + impl Step12 { 221 + pub const APP_BG: Self = Self(1); 222 + pub const SUBTLE_BG: Self = Self(2); 223 + pub const ELEMENT_BG: Self = Self(3); 224 + pub const HOVER_BG: Self = Self(4); 225 + pub const SELECTED_BG: Self = Self(5); 226 + pub const SUBTLE_BORDER: Self = Self(6); 227 + pub const BORDER: Self = Self(7); 228 + pub const HOVER_BORDER: Self = Self(8); 229 + pub const SOLID: Self = Self(9); 230 + pub const HOVER_SOLID: Self = Self(10); 231 + pub const TEXT_MUTED: Self = Self(11); 232 + pub const TEXT_HIGH: Self = Self(12); 233 + 234 + #[must_use] 235 + pub const fn try_new(step: u8) -> Option<Self> { 236 + if matches!(step, 1..=12) { 237 + Some(Self(step)) 238 + } else { 239 + None 240 + } 241 + } 242 + 243 + const fn index(self) -> usize { 244 + (self.0 - 1) as usize 245 + } 246 + } 247 + 248 + impl core::fmt::Display for Step12 { 249 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 250 + write!(f, "step{}", self.0) 251 + } 252 + } 253 + 254 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 255 + #[serde(transparent)] 256 + pub struct Scale12([Color; 12]); 257 + 258 + impl Scale12 { 259 + #[must_use] 260 + pub fn step(self, step: Step12) -> Color { 261 + self.0[step.index()] 262 + } 263 + 264 + #[must_use] 265 + pub fn steps(self) -> [Color; 12] { 266 + self.0 267 + } 268 + 269 + pub(in crate::theme) fn from_oklch_curve( 270 + hue: f32, 271 + chromas: &[f32; 12], 272 + lightness: &[f32; 12], 273 + ) -> Self { 274 + let steps = core::array::from_fn::<Color, 12, _>(|i| { 275 + Color::from_oklch(lightness[i], chromas[i], hue) 276 + }); 277 + Self(steps) 278 + } 279 + } 280 + 281 + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 282 + pub enum SurfaceLevel { 283 + L0, 284 + L1, 285 + L2, 286 + L3, 287 + } 288 + 289 + impl SurfaceLevel { 290 + #[must_use] 291 + pub const fn step12(self) -> Step12 { 292 + match self { 293 + Self::L0 => Step12::APP_BG, 294 + Self::L1 => Step12::SUBTLE_BG, 295 + Self::L2 => Step12::ELEMENT_BG, 296 + Self::L3 => Step12::HOVER_BG, 297 + } 298 + } 299 + } 300 + 301 + impl core::fmt::Display for SurfaceLevel { 302 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 303 + match self { 304 + Self::L0 => f.write_str("L0"), 305 + Self::L1 => f.write_str("L1"), 306 + Self::L2 => f.write_str("L2"), 307 + Self::L3 => f.write_str("L3"), 308 + } 309 + } 310 + } 311 + 312 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 313 + pub struct Colors { 314 + pub neutral: Scale12, 315 + pub accent: Scale12, 316 + pub info: Scale12, 317 + pub success: Scale12, 318 + pub warning: Scale12, 319 + pub danger: Scale12, 320 + pub focus_ring: Color, 321 + } 322 + 323 + impl Colors { 324 + #[must_use] 325 + pub fn surface(&self, level: SurfaceLevel) -> Color { 326 + self.neutral.step(level.step12()) 327 + } 328 + 329 + #[must_use] 330 + pub fn text_primary(&self) -> Color { 331 + self.neutral.step(Step12::TEXT_HIGH) 332 + } 333 + 334 + #[must_use] 335 + pub fn text_secondary(&self) -> Color { 336 + self.neutral.step(Step12::TEXT_MUTED) 337 + } 338 + 339 + #[must_use] 340 + pub fn text_disabled(&self) -> Color { 341 + self.neutral.step(Step12::HOVER_BORDER) 342 + } 343 + 344 + #[must_use] 345 + pub fn accent_solid(&self) -> Color { 346 + self.accent.step(Step12::SOLID) 347 + } 348 + 349 + #[must_use] 350 + pub fn info_solid(&self) -> Color { 351 + self.info.step(Step12::SOLID) 352 + } 353 + 354 + #[must_use] 355 + pub fn success_solid(&self) -> Color { 356 + self.success.step(Step12::SOLID) 357 + } 358 + 359 + #[must_use] 360 + pub fn warning_solid(&self) -> Color { 361 + self.warning.step(Step12::SOLID) 362 + } 363 + 364 + #[must_use] 365 + pub fn danger_solid(&self) -> Color { 366 + self.danger.step(Step12::SOLID) 367 + } 368 + } 369 + 370 + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 371 + pub struct CadColors { 372 + pub sketch_under_defined: Color, 373 + pub sketch_fully_defined: Color, 374 + pub sketch_over_defined: Color, 375 + pub sketch_invalid: Color, 376 + pub sketch_dangling: Color, 377 + pub sketch_driven: Color, 378 + pub selection_primary: Color, 379 + pub selection_hover: Color, 380 + pub property_pane_active_box: Color, 381 + pub relation_glyph: Color, 382 + pub reference_geometry: Color, 383 + } 384 + 385 + impl CadColors { 386 + pub(in crate::theme) fn solidworks() -> Self { 387 + Self { 388 + sketch_under_defined: Color::from_srgb_u8(0x1F, 0x4F, 0xCC), 389 + sketch_fully_defined: Color::from_srgb_u8(0x10, 0x10, 0x10), 390 + sketch_over_defined: Color::from_srgb_u8(0xC8, 0x00, 0x1E), 391 + sketch_invalid: Color::from_srgb_u8(0x7A, 0x00, 0x14), 392 + sketch_dangling: Color::from_srgb_u8(0x80, 0x80, 0x00), 393 + sketch_driven: Color::from_srgb_u8(0x7E, 0x7E, 0x7E), 394 + selection_primary: Color::from_srgb_u8(0x2C, 0xCD, 0x33), 395 + selection_hover: Color::from_srgb_u8(0xFF, 0x8C, 0x00), 396 + property_pane_active_box: Color::from_srgb_u8(0xFF, 0xE4, 0xD6), 397 + relation_glyph: Color::from_srgb_u8(0x1B, 0x7B, 0x5E), 398 + reference_geometry: Color::from_srgb_u8(0x9B, 0x9B, 0x9B), 399 + } 400 + } 401 + } 402 + 403 + const NEUTRAL_HUE: f32 = 250.0; 404 + const ACCENT_HUE: f32 = 230.0; 405 + const INFO_HUE: f32 = 215.0; 406 + const SUCCESS_HUE: f32 = 145.0; 407 + const WARNING_HUE: f32 = 70.0; 408 + const DANGER_HUE: f32 = 25.0; 409 + 410 + const NEUTRAL_CHROMAS: [f32; 12] = [ 411 + 0.005, 0.006, 0.008, 0.010, 0.012, 0.014, 0.016, 0.018, 0.020, 0.020, 0.018, 0.012, 412 + ]; 413 + const ACCENT_CHROMAS: [f32; 12] = [ 414 + 0.012, 0.020, 0.035, 0.055, 0.075, 0.095, 0.115, 0.135, 0.170, 0.170, 0.140, 0.080, 415 + ]; 416 + const SEMANTIC_CHROMAS: [f32; 12] = [ 417 + 0.012, 0.022, 0.040, 0.060, 0.080, 0.100, 0.120, 0.140, 0.180, 0.180, 0.150, 0.090, 418 + ]; 419 + 420 + const RADIX_LIGHT_L: [f32; 12] = [ 421 + 0.99, 0.98, 0.96, 0.94, 0.91, 0.88, 0.83, 0.76, 0.65, 0.60, 0.50, 0.30, 422 + ]; 423 + const RADIX_DARK_L: [f32; 12] = [ 424 + 0.18, 0.21, 0.25, 0.29, 0.33, 0.39, 0.46, 0.55, 0.65, 0.70, 0.78, 0.95, 425 + ]; 426 + 427 + pub(in crate::theme) fn light_colors() -> Colors { 428 + build_colors(&RADIX_LIGHT_L) 429 + } 430 + 431 + pub(in crate::theme) fn dark_colors() -> Colors { 432 + build_colors(&RADIX_DARK_L) 433 + } 434 + 435 + fn build_colors(lightness: &[f32; 12]) -> Colors { 436 + let accent = Scale12::from_oklch_curve(ACCENT_HUE, &ACCENT_CHROMAS, lightness); 437 + Colors { 438 + neutral: Scale12::from_oklch_curve(NEUTRAL_HUE, &NEUTRAL_CHROMAS, lightness), 439 + accent, 440 + info: Scale12::from_oklch_curve(INFO_HUE, &SEMANTIC_CHROMAS, lightness), 441 + success: Scale12::from_oklch_curve(SUCCESS_HUE, &SEMANTIC_CHROMAS, lightness), 442 + warning: Scale12::from_oklch_curve(WARNING_HUE, &SEMANTIC_CHROMAS, lightness), 443 + danger: Scale12::from_oklch_curve(DANGER_HUE, &SEMANTIC_CHROMAS, lightness), 444 + focus_ring: accent.step(Step12::SOLID), 445 + } 446 + } 447 + 448 + #[cfg(test)] 449 + mod tests { 450 + use super::{ 451 + CadColors, Color, ColorWire, PREMUL_TOLERANCE, Scale12, Step12, SurfaceLevel, dark_colors, 452 + in_gamut, light_colors, 453 + }; 454 + 455 + #[test] 456 + fn blend_midpoint_is_average() { 457 + let a = Color::from_linear_rgba(0.0, 0.0, 0.0, 1.0); 458 + let b = Color::from_linear_rgba(1.0, 1.0, 1.0, 1.0); 459 + let mid = a.blend(b, 0.5); 460 + assert!((mid.linear_rgba_premul()[0] - 0.5).abs() < 1e-6); 461 + assert!((mid.linear_rgba_premul()[1] - 0.5).abs() < 1e-6); 462 + assert!((mid.linear_rgba_premul()[2] - 0.5).abs() < 1e-6); 463 + } 464 + 465 + #[test] 466 + fn on_surface_picks_dark_text_over_light_bg() { 467 + let bg = Color::from_srgb_u8(0xFF, 0xFF, 0xFF); 468 + let text = bg.on_surface(); 469 + assert!(text.relative_luminance() < 0.1); 470 + } 471 + 472 + #[test] 473 + fn on_surface_picks_light_text_over_dark_bg() { 474 + let bg = Color::from_srgb_u8(0x10, 0x10, 0x10); 475 + let text = bg.on_surface(); 476 + assert!(text.relative_luminance() > 0.9); 477 + } 478 + 479 + #[test] 480 + fn with_alpha_preserves_chromaticity() { 481 + let opaque = Color::from_srgb_u8(0x80, 0x40, 0x20); 482 + let half = opaque.with_alpha(0.5); 483 + assert!((half.alpha() - 0.5).abs() < 1e-6); 484 + let opaque_lin = opaque.linear_rgba_premul(); 485 + let half_lin = half.linear_rgba_premul(); 486 + assert!((half_lin[0] / 0.5 - opaque_lin[0]).abs() < 1e-6); 487 + assert!((half_lin[1] / 0.5 - opaque_lin[1]).abs() < 1e-6); 488 + assert!((half_lin[2] / 0.5 - opaque_lin[2]).abs() < 1e-6); 489 + } 490 + 491 + #[test] 492 + fn step12_try_new_rejects_zero_and_above_twelve() { 493 + assert!(Step12::try_new(0).is_none()); 494 + assert!(Step12::try_new(13).is_none()); 495 + assert!(Step12::try_new(255).is_none()); 496 + assert_eq!(Step12::try_new(1), Some(Step12::APP_BG)); 497 + assert_eq!(Step12::try_new(12), Some(Step12::TEXT_HIGH)); 498 + } 499 + 500 + #[test] 501 + fn scale12_step_indexes_one_based() { 502 + let scale = light_colors().neutral; 503 + let app_bg = scale.step(Step12::APP_BG); 504 + let text_high = scale.step(Step12::TEXT_HIGH); 505 + assert!(app_bg.relative_luminance() > text_high.relative_luminance()); 506 + } 507 + 508 + #[test] 509 + fn surface_levels_map_to_app_subtle_element_hover() { 510 + let colors = light_colors(); 511 + let pairs = [ 512 + (SurfaceLevel::L0, Step12::APP_BG), 513 + (SurfaceLevel::L1, Step12::SUBTLE_BG), 514 + (SurfaceLevel::L2, Step12::ELEMENT_BG), 515 + (SurfaceLevel::L3, Step12::HOVER_BG), 516 + ]; 517 + pairs.iter().fold((), |(), &(level, step)| { 518 + assert_eq!(colors.surface(level), colors.neutral.step(step)); 519 + }); 520 + } 521 + 522 + #[test] 523 + fn dark_inverts_neutral_lightness_across_curve() { 524 + let light = light_colors().neutral.steps(); 525 + let dark = dark_colors().neutral.steps(); 526 + let light_lum: [f32; 12] = light.map(Color::relative_luminance); 527 + let dark_lum: [f32; 12] = dark.map(Color::relative_luminance); 528 + 529 + light_lum.windows(2).fold((), |(), w| { 530 + assert!( 531 + w[0] >= w[1] - 1e-4, 532 + "light neutral luminance must be non-increasing at every step" 533 + ); 534 + }); 535 + dark_lum.windows(2).fold((), |(), w| { 536 + assert!( 537 + w[0] <= w[1] + 1e-4, 538 + "dark neutral luminance must be non-decreasing at every step" 539 + ); 540 + }); 541 + 542 + assert!(light_lum[0] > dark_lum[0]); 543 + assert!(dark_lum[11] > light_lum[11]); 544 + } 545 + 546 + #[test] 547 + fn focus_ring_is_in_gamut() { 548 + let ring = light_colors().focus_ring; 549 + let [r, g, b, a] = ring.linear_rgba_premul(); 550 + assert!((0.0..=1.0).contains(&r)); 551 + assert!((0.0..=1.0).contains(&g)); 552 + assert!((0.0..=1.0).contains(&b)); 553 + assert!((a - 1.0).abs() < 1e-6); 554 + } 555 + 556 + #[test] 557 + fn every_ramp_step_is_in_gamut() { 558 + let check = |scale: Scale12| { 559 + scale.steps().iter().fold((), |(), color| { 560 + let rgba = color.linear_rgba_premul(); 561 + assert!( 562 + in_gamut([rgba[0], rgba[1], rgba[2]]), 563 + "ramp step out of sRGB gamut: {color:?}" 564 + ); 565 + }); 566 + }; 567 + let palettes = [light_colors(), dark_colors()]; 568 + palettes.iter().fold((), |(), colors| { 569 + check(colors.neutral); 570 + check(colors.accent); 571 + check(colors.info); 572 + check(colors.success); 573 + check(colors.warning); 574 + check(colors.danger); 575 + }); 576 + } 577 + 578 + #[test] 579 + fn cad_colors_are_all_opaque_non_transparent() { 580 + let cad = CadColors::solidworks(); 581 + let fields = [ 582 + cad.sketch_under_defined, 583 + cad.sketch_fully_defined, 584 + cad.sketch_over_defined, 585 + cad.sketch_invalid, 586 + cad.sketch_dangling, 587 + cad.sketch_driven, 588 + cad.selection_primary, 589 + cad.selection_hover, 590 + cad.property_pane_active_box, 591 + cad.relation_glyph, 592 + cad.reference_geometry, 593 + ]; 594 + fields.iter().fold((), |(), color| { 595 + assert!( 596 + (color.alpha() - 1.0).abs() < 1e-6, 597 + "CadColors literal must be fully opaque" 598 + ); 599 + assert!( 600 + *color != Color::TRANSPARENT, 601 + "CadColors literal must not be TRANSPARENT, the hex parse silently failed" 602 + ); 603 + }); 604 + } 605 + 606 + #[test] 607 + fn scale12_curve_produces_distinct_steps() { 608 + let scale = Scale12::from_oklch_curve( 609 + 230.0, 610 + &[0.05; 12], 611 + &[0.10, 0.18, 0.26, 0.34, 0.42, 0.50, 0.58, 0.66, 0.74, 0.82, 0.90, 0.98], 612 + ); 613 + let steps = scale.steps(); 614 + let lums: Vec<f32> = steps.iter().map(|c| c.relative_luminance()).collect(); 615 + let monotone = lums.windows(2).all(|w| w[0] <= w[1]); 616 + assert!(monotone, "lightness curve should produce non-decreasing luminance"); 617 + } 618 + 619 + #[test] 620 + fn deserialize_rejects_non_finite_channel() { 621 + let bad = "Color(r:NaN, g:0, b:0, a:1)"; 622 + let result: Result<Color, _> = ron::de::from_str(bad); 623 + assert!(result.is_err(), "NaN must be rejected on deserialize"); 624 + } 625 + 626 + #[test] 627 + fn deserialize_rejects_non_premultiplied() { 628 + let bad = "Color(r:1.0, g:0.0, b:0.0, a:0.5)"; 629 + let result: Result<Color, _> = ron::de::from_str(bad); 630 + assert!( 631 + result.is_err(), 632 + "non-premultiplied (rgb > a) must be rejected on deserialize" 633 + ); 634 + } 635 + 636 + #[test] 637 + fn deserialize_rejects_out_of_range() { 638 + let bad = "Color(r:0.0, g:0.0, b:0.0, a:1.5)"; 639 + let result: Result<Color, _> = ron::de::from_str(bad); 640 + assert!(result.is_err(), "alpha > 1 must be rejected on deserialize"); 641 + } 642 + 643 + #[test] 644 + fn deserialize_accepts_premul_at_boundary() { 645 + let good_inside = ColorWire { 646 + r: 0.5, 647 + g: 0.25, 648 + b: 0.0, 649 + a: 0.5, 650 + }; 651 + let result: Result<Color, _> = good_inside.try_into(); 652 + assert!(result.is_ok()); 653 + 654 + let good_at_boundary = ColorWire { 655 + r: 0.5 + PREMUL_TOLERANCE * 0.5, 656 + g: 0.0, 657 + b: 0.0, 658 + a: 0.5, 659 + }; 660 + let result: Result<Color, _> = good_at_boundary.try_into(); 661 + assert!(result.is_ok()); 662 + } 663 + 664 + #[test] 665 + fn with_alpha_amplification_clamps_to_premul() { 666 + let wire = ColorWire { 667 + r: 0.001 + PREMUL_TOLERANCE * 0.5, 668 + g: 0.0, 669 + b: 0.0, 670 + a: 0.001, 671 + }; 672 + let Ok(edge): Result<Color, _> = wire.try_into() else { 673 + panic!("tolerance-edge color must deserialize"); 674 + }; 675 + let amped = edge.with_alpha(1.0); 676 + let [r, g, b, a] = amped.linear_rgba_premul(); 677 + assert!((a - 1.0).abs() < 1e-6); 678 + assert!(r <= a, "red {r} must stay <= alpha {a} after amplification"); 679 + assert!(g <= a); 680 + assert!(b <= a); 681 + assert!(r <= 1.0); 682 + } 683 + }
+80
crates/bone-ui/src/theme/motion.rs
··· 1 + use core::time::Duration; 2 + 3 + use serde::{Deserialize, Serialize}; 4 + 5 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 6 + pub enum Easing { 7 + Standard, 8 + Accelerate, 9 + Decelerate, 10 + Linear, 11 + } 12 + 13 + impl Easing { 14 + #[must_use] 15 + pub const fn cubic_bezier(self) -> [f32; 4] { 16 + match self { 17 + Self::Standard => [0.2, 0.0, 0.0, 1.0], 18 + Self::Accelerate => [0.3, 0.0, 1.0, 1.0], 19 + Self::Decelerate => [0.0, 0.0, 0.0, 1.0], 20 + Self::Linear => [0.0, 0.0, 1.0, 1.0], 21 + } 22 + } 23 + } 24 + 25 + impl core::fmt::Display for Easing { 26 + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 27 + let name = match self { 28 + Self::Standard => "standard", 29 + Self::Accelerate => "accelerate", 30 + Self::Decelerate => "decelerate", 31 + Self::Linear => "linear", 32 + }; 33 + f.write_str(name) 34 + } 35 + } 36 + 37 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 38 + pub struct MotionToken { 39 + pub duration: Duration, 40 + pub easing: Easing, 41 + } 42 + 43 + #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] 44 + pub struct Motion { 45 + pub fast: MotionToken, 46 + pub quick: MotionToken, 47 + pub normal: MotionToken, 48 + pub relaxed: MotionToken, 49 + pub slow: MotionToken, 50 + pub glacial: MotionToken, 51 + } 52 + 53 + impl Motion { 54 + pub const STANDARD: Self = Self { 55 + fast: MotionToken { 56 + duration: Duration::from_millis(80), 57 + easing: Easing::Standard, 58 + }, 59 + quick: MotionToken { 60 + duration: Duration::from_millis(120), 61 + easing: Easing::Standard, 62 + }, 63 + normal: MotionToken { 64 + duration: Duration::from_millis(180), 65 + easing: Easing::Standard, 66 + }, 67 + relaxed: MotionToken { 68 + duration: Duration::from_millis(260), 69 + easing: Easing::Decelerate, 70 + }, 71 + slow: MotionToken { 72 + duration: Duration::from_millis(400), 73 + easing: Easing::Decelerate, 74 + }, 75 + glacial: MotionToken { 76 + duration: Duration::from_millis(700), 77 + easing: Easing::Decelerate, 78 + }, 79 + }; 80 + }
+598
crates/bone-ui/tests/snapshots/theme_snapshot__theme_dark.snap
··· 1 + --- 2 + source: crates/bone-ui/tests/theme_snapshot.rs 3 + expression: "Theme::dark()" 4 + --- 5 + Theme( 6 + mode: Dark, 7 + colors: Colors( 8 + neutral: (Color( 9 + r: 0.005157554, 10 + g: 0.0059472434, 11 + b: 0.0068684025, 12 + a: 1.0, 13 + ), Color( 14 + r: 0.008159909, 15 + g: 0.009448808, 16 + b: 0.010955492, 17 + a: 1.0, 18 + ), Color( 19 + r: 0.013548436, 20 + g: 0.015976492, 21 + b: 0.018840894, 22 + a: 1.0, 23 + ), Color( 24 + r: 0.020901216, 25 + g: 0.024976067, 26 + b: 0.029815085, 27 + a: 1.0, 28 + ), Color( 29 + r: 0.030523386, 30 + g: 0.036844313, 31 + b: 0.044388447, 32 + a: 1.0, 33 + ), Color( 34 + r: 0.050495297, 35 + g: 0.060799375, 36 + b: 0.073082335, 37 + a: 1.0, 38 + ), Color( 39 + r: 0.083297804, 40 + g: 0.09969727, 41 + b: 0.11918802, 42 + a: 1.0, 43 + ), Color( 44 + r: 0.14377098, 45 + g: 0.17019472, 46 + b: 0.20142835, 47 + a: 1.0, 48 + ), Color( 49 + r: 0.23950589, 50 + g: 0.28058594, 51 + b: 0.32888895, 52 + a: 1.0, 53 + ), Color( 54 + r: 0.30221865, 55 + g: 0.34995604, 56 + b: 0.40575886, 57 + a: 1.0, 58 + ), Color( 59 + r: 0.4288367, 60 + g: 0.48244387, 61 + b: 0.5441973, 62 + a: 1.0, 63 + ), Color( 64 + r: 0.8118944, 65 + g: 0.86540264, 66 + b: 0.9253415, 67 + a: 1.0, 68 + )), 69 + accent: (Color( 70 + r: 0.003738049, 71 + g: 0.006347955, 72 + b: 0.007971437, 73 + a: 1.0, 74 + ), Color( 75 + r: 0.004580794, 76 + g: 0.010387734, 77 + b: 0.014263034, 78 + a: 1.0, 79 + ), Color( 80 + r: 0.004288765, 81 + g: 0.018247556, 82 + b: 0.028622631, 83 + a: 1.0, 84 + ), Color( 85 + r: 0.0010541743, 86 + g: 0.029524766, 87 + b: 0.053303294, 88 + a: 1.0, 89 + ), Color( 90 + r: 0.0, 91 + g: 0.043780655, 92 + b: 0.08130965, 93 + a: 1.0, 94 + ), Color( 95 + r: 0.0, 96 + g: 0.07225539, 97 + b: 0.13410223, 98 + a: 1.0, 99 + ), Color( 100 + r: 0.0, 101 + g: 0.11855267, 102 + b: 0.21993835, 103 + a: 1.0, 104 + ), Color( 105 + r: 0.0, 106 + g: 0.20262873, 107 + b: 0.3758166, 108 + a: 1.0, 109 + ), Color( 110 + r: 0.0, 111 + g: 0.334456, 112 + b: 0.62022746, 113 + a: 1.0, 114 + ), Color( 115 + r: 0.0, 116 + g: 0.41772342, 117 + b: 0.7746074, 118 + a: 1.0, 119 + ), Color( 120 + r: 0.04335399, 121 + g: 0.5705209, 122 + b: 1.0, 123 + a: 1.0, 124 + ), Color( 125 + r: 0.709679, 126 + g: 0.8947468, 127 + b: 1.0, 128 + a: 1.0, 129 + )), 130 + info: (Color( 131 + r: 0.0035153294, 132 + g: 0.0065116268, 133 + b: 0.007443318, 134 + a: 1.0, 135 + ), Color( 136 + r: 0.003580797, 137 + g: 0.0109075215, 138 + b: 0.0134118665, 139 + a: 1.0, 140 + ), Color( 141 + r: 0.0013614884, 142 + g: 0.019682374, 143 + b: 0.026818614, 144 + a: 1.0, 145 + ), Color( 146 + r: 0.0, 147 + g: 0.031311035, 148 + b: 0.04404354, 149 + a: 1.0, 150 + ), Color( 151 + r: 0.0, 152 + g: 0.04612413, 153 + b: 0.06485036, 154 + a: 1.0, 155 + ), Color( 156 + r: 0.0, 157 + g: 0.07611721, 158 + b: 0.10697932, 159 + a: 1.0, 160 + ), Color( 161 + r: 0.0, 162 + g: 0.12488317, 163 + b: 0.1754773, 164 + a: 1.0, 165 + ), Color( 166 + r: 0.0, 167 + g: 0.21344236, 168 + b: 0.29986978, 169 + a: 1.0, 170 + ), Color( 171 + r: 0.0, 172 + g: 0.35229903, 173 + b: 0.49491164, 174 + a: 1.0, 175 + ), Color( 176 + r: 0.0, 177 + g: 0.44000635, 178 + b: 0.61810803, 179 + a: 1.0, 180 + ), Color( 181 + r: 0.0, 182 + g: 0.60875386, 183 + b: 0.8551334, 184 + a: 1.0, 185 + ), Color( 186 + r: 0.6452157, 187 + g: 0.9200909, 188 + b: 1.0, 189 + a: 1.0, 190 + )), 191 + success: (Color( 192 + r: 0.0045246435, 193 + g: 0.0065315547, 194 + b: 0.0045207883, 195 + a: 1.0, 196 + ), Color( 197 + r: 0.0060267393, 198 + g: 0.010985546, 199 + b: 0.006069955, 200 + a: 1.0, 201 + ), Color( 202 + r: 0.0073946593, 203 + g: 0.019991364, 204 + b: 0.007698171, 205 + a: 1.0, 206 + ), Color( 207 + r: 0.007951981, 208 + g: 0.033072148, 209 + b: 0.008884111, 210 + a: 1.0, 211 + ), Color( 212 + r: 0.0077847196, 213 + g: 0.050761472, 214 + b: 0.00980155, 215 + a: 1.0, 216 + ), Color( 217 + r: 0.0103234425, 218 + g: 0.08508686, 219 + b: 0.014120366, 220 + a: 1.0, 221 + ), Color( 222 + r: 0.015623577, 223 + g: 0.14029329, 224 + b: 0.022108175, 225 + a: 1.0, 226 + ), Color( 227 + r: 0.02989692, 228 + g: 0.23816387, 229 + b: 0.040366776, 230 + a: 1.0, 231 + ), Color( 232 + r: 0.030775376, 233 + g: 0.40263656, 234 + b: 0.05176066, 235 + a: 1.0, 236 + ), Color( 237 + r: 0.058930736, 238 + g: 0.49238855, 239 + b: 0.08103162, 240 + a: 1.0, 241 + ), Color( 242 + r: 0.17629735, 243 + g: 0.63231826, 244 + b: 0.19137546, 245 + a: 1.0, 246 + ), Color( 247 + r: 0.58998734, 248 + g: 1.0, 249 + b: 0.5922507, 250 + a: 1.0, 251 + )), 252 + warning: (Color( 253 + r: 0.0075376965, 254 + g: 0.0054847444, 255 + b: 0.003630369, 256 + a: 1.0, 257 + ), Color( 258 + r: 0.013603543, 259 + g: 0.008326292, 260 + b: 0.00403861, 261 + a: 1.0, 262 + ), Color( 263 + r: 0.027133264, 264 + g: 0.01296954, 265 + b: 0.0031400286, 266 + a: 1.0, 267 + ), Color( 268 + r: 0.048156064, 269 + g: 0.018620748, 270 + b: 0.0007755123, 271 + a: 1.0, 272 + ), Color( 273 + r: 0.07293864, 274 + g: 0.026862089, 275 + b: 0.0, 276 + a: 1.0, 277 + ), Color( 278 + r: 0.12028917, 279 + g: 0.044370636, 280 + b: 0.0, 281 + a: 1.0, 282 + ), Color( 283 + r: 0.19727728, 284 + g: 0.07283778, 285 + b: 0.0, 286 + a: 1.0, 287 + ), Color( 288 + r: 0.33708745, 289 + g: 0.124534406, 290 + b: 0.0, 291 + a: 1.0, 292 + ), Color( 293 + r: 0.5563038, 294 + g: 0.20559213, 295 + b: 0.0, 296 + a: 1.0, 297 + ), Color( 298 + r: 0.69476986, 299 + g: 0.25679126, 300 + b: 0.0, 301 + a: 1.0, 302 + ), Color( 303 + r: 0.9013745, 304 + g: 0.37250182, 305 + b: 0.03870493, 306 + a: 1.0, 307 + ), Color( 308 + r: 1.0, 309 + g: 0.82962537, 310 + b: 0.66330075, 311 + a: 1.0, 312 + )), 313 + danger: (Color( 314 + r: 0.008346126, 315 + g: 0.0050143786, 316 + b: 0.0047323555, 317 + a: 1.0, 318 + ), Color( 319 + r: 0.015632957, 320 + g: 0.0071814507, 321 + b: 0.006587466, 322 + a: 1.0, 323 + ), Color( 324 + r: 0.032415546, 325 + g: 0.0101192, 326 + b: 0.008993813, 327 + a: 1.0, 328 + ), Color( 329 + r: 0.05892322, 330 + g: 0.013022868, 331 + b: 0.011436487, 332 + a: 1.0, 333 + ), Color( 334 + r: 0.09641168, 335 + g: 0.015981052, 336 + b: 0.014128435, 337 + a: 1.0, 338 + ), Color( 339 + r: 0.16548789, 340 + g: 0.024249882, 341 + b: 0.021620583, 342 + a: 1.0, 343 + ), Color( 344 + r: 0.2748911, 345 + g: 0.03866884, 346 + b: 0.034600683, 347 + a: 1.0, 348 + ), Color( 349 + r: 0.46176806, 350 + g: 0.06881519, 351 + b: 0.061269954, 352 + a: 1.0, 353 + ), Color( 354 + r: 0.8097997, 355 + g: 0.09760057, 356 + b: 0.08887091, 357 + a: 1.0, 358 + ), Color( 359 + r: 0.95883304, 360 + g: 0.1395711, 361 + b: 0.124507725, 362 + a: 1.0, 363 + ), Color( 364 + r: 1.0, 365 + g: 0.30215427, 366 + b: 0.26803857, 367 + a: 1.0, 368 + ), Color( 369 + r: 1.0, 370 + g: 0.81114423, 371 + b: 0.792139, 372 + a: 1.0, 373 + )), 374 + focus_ring: Color( 375 + r: 0.0, 376 + g: 0.334456, 377 + b: 0.62022746, 378 + a: 1.0, 379 + ), 380 + ), 381 + cad: CadColors( 382 + sketch_under_defined: Color( 383 + r: 0.013702081, 384 + g: 0.07818741, 385 + b: 0.6038273, 386 + a: 1.0, 387 + ), 388 + sketch_fully_defined: Color( 389 + r: 0.005181515, 390 + g: 0.005181515, 391 + b: 0.005181515, 392 + a: 1.0, 393 + ), 394 + sketch_over_defined: Color( 395 + r: 0.57758045, 396 + g: 0.0, 397 + b: 0.012983031, 398 + a: 1.0, 399 + ), 400 + sketch_invalid: Color( 401 + r: 0.19461781, 402 + g: 0.0, 403 + b: 0.0069954083, 404 + a: 1.0, 405 + ), 406 + sketch_dangling: Color( 407 + r: 0.21586053, 408 + g: 0.21586053, 409 + b: 0.0, 410 + a: 1.0, 411 + ), 412 + sketch_driven: Color( 413 + r: 0.20863685, 414 + g: 0.20863685, 415 + b: 0.20863685, 416 + a: 1.0, 417 + ), 418 + selection_primary: Color( 419 + r: 0.025186857, 420 + g: 0.61049557, 421 + b: 0.033104762, 422 + a: 1.0, 423 + ), 424 + selection_hover: Color( 425 + r: 1.0, 426 + g: 0.26225066, 427 + b: 0.0, 428 + a: 1.0, 429 + ), 430 + property_pane_active_box: Color( 431 + r: 1.0, 432 + g: 0.7758222, 433 + b: 0.6724431, 434 + a: 1.0, 435 + ), 436 + relation_glyph: Color( 437 + r: 0.010960091, 438 + g: 0.1980693, 439 + b: 0.11193242, 440 + a: 1.0, 441 + ), 442 + reference_geometry: Color( 443 + r: 0.32777807, 444 + g: 0.32777807, 445 + b: 0.32777807, 446 + a: 1.0, 447 + ), 448 + ), 449 + spacing: SpacingScale( 450 + xs: 4.0, 451 + sm: 8.0, 452 + md: 12.0, 453 + lg: 16.0, 454 + xl: 24.0, 455 + xxl: 32.0, 456 + ), 457 + typography: Typography( 458 + caption: TypographyRole( 459 + face: Sans, 460 + size: 0.002910416666666667, 461 + line_height: 0.003704166666666667, 462 + weight: Regular, 463 + letter_spacing: 0.0, 464 + ), 465 + body: TypographyRole( 466 + face: Sans, 467 + size: 0.003439583333333333, 468 + line_height: 0.0047625, 469 + weight: Regular, 470 + letter_spacing: 0.0, 471 + ), 472 + label: TypographyRole( 473 + face: Sans, 474 + size: 0.003175, 475 + line_height: 0.004233333333333334, 476 + weight: Medium, 477 + letter_spacing: 0.0, 478 + ), 479 + title: TypographyRole( 480 + face: Sans, 481 + size: 0.003704166666666667, 482 + line_height: 0.005291666666666667, 483 + weight: Semibold, 484 + letter_spacing: 0.0, 485 + ), 486 + heading: TypographyRole( 487 + face: Sans, 488 + size: 0.0047625, 489 + line_height: 0.00635, 490 + weight: Semibold, 491 + letter_spacing: 0.0, 492 + ), 493 + mono: TypographyRole( 494 + face: Mono, 495 + size: 0.003175, 496 + line_height: 0.004233333333333334, 497 + weight: Regular, 498 + letter_spacing: 0.0, 499 + ), 500 + ), 501 + radius: RadiusScale( 502 + none: 0.0, 503 + sm: 2.0, 504 + md: 4.0, 505 + lg: 8.0, 506 + pill: 9999.0, 507 + ), 508 + elevation: ElevationScale( 509 + level0: ElevationLevel( 510 + surface: L0, 511 + border: None, 512 + shadow: None, 513 + ), 514 + level1: ElevationLevel( 515 + surface: L1, 516 + border: Some(Border( 517 + width: 1.0, 518 + color: Color( 519 + r: 0.050495297, 520 + g: 0.060799375, 521 + b: 0.073082335, 522 + a: 1.0, 523 + ), 524 + )), 525 + shadow: None, 526 + ), 527 + level2: ElevationLevel( 528 + surface: L2, 529 + border: Some(Border( 530 + width: 1.0, 531 + color: Color( 532 + r: 0.083297804, 533 + g: 0.09969727, 534 + b: 0.11918802, 535 + a: 1.0, 536 + ), 537 + )), 538 + shadow: None, 539 + ), 540 + level3: ElevationLevel( 541 + surface: L3, 542 + border: Some(Border( 543 + width: 1.0, 544 + color: Color( 545 + r: 0.14377098, 546 + g: 0.17019472, 547 + b: 0.20142835, 548 + a: 1.0, 549 + ), 550 + )), 551 + shadow: None, 552 + ), 553 + ), 554 + motion: Motion( 555 + fast: MotionToken( 556 + duration: Duration( 557 + secs: 0, 558 + nanos: 80000000, 559 + ), 560 + easing: Standard, 561 + ), 562 + quick: MotionToken( 563 + duration: Duration( 564 + secs: 0, 565 + nanos: 120000000, 566 + ), 567 + easing: Standard, 568 + ), 569 + normal: MotionToken( 570 + duration: Duration( 571 + secs: 0, 572 + nanos: 180000000, 573 + ), 574 + easing: Standard, 575 + ), 576 + relaxed: MotionToken( 577 + duration: Duration( 578 + secs: 0, 579 + nanos: 260000000, 580 + ), 581 + easing: Decelerate, 582 + ), 583 + slow: MotionToken( 584 + duration: Duration( 585 + secs: 0, 586 + nanos: 400000000, 587 + ), 588 + easing: Decelerate, 589 + ), 590 + glacial: MotionToken( 591 + duration: Duration( 592 + secs: 0, 593 + nanos: 700000000, 594 + ), 595 + easing: Decelerate, 596 + ), 597 + ), 598 + )
+598
crates/bone-ui/tests/snapshots/theme_snapshot__theme_light.snap
··· 1 + --- 2 + source: crates/bone-ui/tests/theme_snapshot.rs 3 + expression: "Theme::light()" 4 + --- 5 + Theme( 6 + mode: Light, 7 + colors: Colors( 8 + neutral: (Color( 9 + r: 0.95007277, 10 + g: 0.97392607, 11 + b: 1.0, 12 + a: 1.0, 13 + ), Color( 14 + r: 0.91690433, 15 + g: 0.9455365, 16 + b: 0.97705656, 17 + a: 1.0, 18 + ), Color( 19 + r: 0.8536979, 20 + g: 0.8902633, 21 + b: 0.93075293, 22 + a: 1.0, 23 + ), Color( 24 + r: 0.79343575, 25 + g: 0.8371681, 26 + b: 0.88589585, 27 + a: 1.0, 28 + ), Color( 29 + r: 0.71185327, 30 + g: 0.76092607, 31 + b: 0.81597877, 32 + a: 1.0, 33 + ), Color( 34 + r: 0.63602823, 35 + g: 0.68943834, 36 + b: 0.74979544, 37 + a: 1.0, 38 + ), Color( 39 + r: 0.5256735, 40 + g: 0.5798132, 41 + b: 0.6415478, 42 + a: 1.0, 43 + ), Color( 44 + r: 0.39559013, 45 + g: 0.44645584, 46 + b: 0.5051464, 47 + a: 1.0, 48 + ), Color( 49 + r: 0.23950589, 50 + g: 0.28058594, 51 + b: 0.32888895, 52 + a: 1.0, 53 + ), Color( 54 + r: 0.18612036, 55 + g: 0.22104254, 56 + b: 0.26238674, 57 + a: 1.0, 58 + ), Color( 59 + r: 0.106354274, 60 + g: 0.1281275, 61 + b: 0.15408953, 62 + a: 1.0, 63 + ), Color( 64 + r: 0.022535374, 65 + g: 0.02774201, 66 + b: 0.034016714, 67 + a: 1.0, 68 + )), 69 + accent: (Color( 70 + r: 0.93819, 71 + g: 0.97857, 72 + b: 1.0, 73 + a: 1.0, 74 + ), Color( 75 + r: 0.87833285, 76 + g: 0.95731646, 77 + b: 1.0, 78 + a: 1.0, 79 + ), Color( 80 + r: 0.7640939, 81 + g: 0.9154076, 82 + b: 1.0, 83 + a: 1.0, 84 + ), Color( 85 + r: 0.6570486, 86 + g: 0.8742764, 87 + b: 1.0, 88 + a: 1.0, 89 + ), Color( 90 + r: 0.50968593, 91 + g: 0.8139833, 92 + b: 1.0, 93 + a: 1.0, 94 + ), Color( 95 + r: 0.37777075, 96 + g: 0.7553034, 97 + b: 1.0, 98 + a: 1.0, 99 + ), Color( 100 + r: 0.19094193, 101 + g: 0.66090363, 102 + b: 1.0, 103 + a: 1.0, 104 + ), Color( 105 + r: 0.043007225, 106 + g: 0.52723646, 107 + b: 0.9204999, 108 + a: 1.0, 109 + ), Color( 110 + r: 0.0, 111 + g: 0.334456, 112 + b: 0.62022746, 113 + a: 1.0, 114 + ), Color( 115 + r: 0.0, 116 + g: 0.2630623, 117 + b: 0.48786175, 118 + a: 1.0, 119 + ), Color( 120 + r: 0.0, 121 + g: 0.15224198, 122 + b: 0.2823989, 123 + a: 1.0, 124 + ), Color( 125 + r: 0.0, 126 + g: 0.032897137, 127 + b: 0.06113141, 128 + a: 1.0, 129 + )), 130 + info: (Color( 131 + r: 0.9240352, 132 + g: 0.98410416, 133 + b: 1.0, 134 + a: 1.0, 135 + ), Color( 136 + r: 0.8506899, 137 + g: 0.9681381, 138 + b: 1.0, 139 + a: 1.0, 140 + ), Color( 141 + r: 0.7113016, 142 + g: 0.93613243, 143 + b: 1.0, 144 + a: 1.0, 145 + ), Color( 146 + r: 0.5815037, 147 + g: 0.904022, 148 + b: 1.0, 149 + a: 1.0, 150 + ), Color( 151 + r: 0.40438432, 152 + g: 0.8556482, 153 + b: 1.0, 154 + a: 1.0, 155 + ), Color( 156 + r: 0.24778245, 157 + g: 0.80701613, 158 + b: 1.0, 159 + a: 1.0, 160 + ), Color( 161 + r: 0.096699774, 162 + g: 0.70766765, 163 + b: 0.93728685, 164 + a: 1.0, 165 + ), Color( 166 + r: 0.0, 167 + g: 0.56311876, 168 + b: 0.7910345, 169 + a: 1.0, 170 + ), Color( 171 + r: 0.0, 172 + g: 0.35229903, 173 + b: 0.49491164, 174 + a: 1.0, 175 + ), Color( 176 + r: 0.0, 177 + g: 0.27709833, 178 + b: 0.38928258, 179 + a: 1.0, 180 + ), Color( 181 + r: 0.0, 182 + g: 0.16036898, 183 + b: 0.2253215, 184 + a: 1.0, 185 + ), Color( 186 + r: 0.0, 187 + g: 0.034660272, 188 + b: 0.04874796, 189 + a: 1.0, 190 + )), 191 + success: (Color( 192 + r: 0.9302576, 193 + g: 0.9918337, 194 + b: 0.92919993, 195 + a: 1.0, 196 + ), Color( 197 + r: 0.8694272, 198 + g: 0.9797509, 199 + b: 0.86785114, 200 + a: 1.0, 201 + ), Color( 202 + r: 0.7600726, 203 + g: 0.9515969, 204 + b: 0.75836635, 205 + a: 1.0, 206 + ), Color( 207 + r: 0.65220046, 208 + g: 0.9260606, 209 + b: 0.651457, 210 + a: 1.0, 211 + ), Color( 212 + r: 0.5318816, 213 + g: 0.87196237, 214 + b: 0.5332457, 215 + a: 1.0, 216 + ), Color( 217 + r: 0.42383683, 218 + g: 0.81873417, 219 + b: 0.42825574, 220 + a: 1.0, 221 + ), Color( 222 + r: 0.2986804, 223 + g: 0.71687996, 224 + b: 0.30696386, 225 + a: 1.0, 226 + ), Color( 227 + r: 0.17421415, 228 + g: 0.57912856, 229 + b: 0.18669257, 230 + a: 1.0, 231 + ), Color( 232 + r: 0.030775376, 233 + g: 0.40263656, 234 + b: 0.05176066, 235 + a: 1.0, 236 + ), Color( 237 + r: 0.0092974305, 238 + g: 0.32428905, 239 + b: 0.029069915, 240 + a: 1.0, 241 + ), Color( 242 + r: 0.005380241, 243 + g: 0.1876674, 244 + b: 0.016822822, 245 + a: 1.0, 246 + ), Color( 247 + r: 0.0011621788, 248 + g: 0.04053613, 249 + b: 0.0036337394, 250 + a: 1.0, 251 + )), 252 + warning: (Color( 253 + r: 1.0, 254 + g: 0.96482563, 255 + b: 0.92736495, 256 + a: 1.0, 257 + ), Color( 258 + r: 1.0, 259 + g: 0.93021876, 260 + b: 0.8574817, 261 + a: 1.0, 262 + ), Color( 263 + r: 1.0, 264 + g: 0.86262476, 265 + b: 0.725467, 266 + a: 1.0, 267 + ), Color( 268 + r: 1.0, 269 + g: 0.79714835, 270 + b: 0.6036718, 271 + a: 1.0, 272 + ), Color( 273 + r: 1.0, 274 + g: 0.7027916, 275 + b: 0.4398166, 276 + a: 1.0, 277 + ), Color( 278 + r: 1.0, 279 + g: 0.612916, 280 + b: 0.29808357, 281 + a: 1.0, 282 + ), Color( 283 + r: 0.9494071, 284 + g: 0.48622718, 285 + b: 0.1501373, 286 + a: 1.0, 287 + ), Color( 288 + r: 0.81568706, 289 + g: 0.3496775, 290 + b: 0.048410922, 291 + a: 1.0, 292 + ), Color( 293 + r: 0.5563038, 294 + g: 0.20559213, 295 + b: 0.0, 296 + a: 1.0, 297 + ), Color( 298 + r: 0.43758255, 299 + g: 0.16169375, 300 + b: 0.0, 301 + a: 1.0, 302 + ), Color( 303 + r: 0.25329924, 304 + g: 0.09355271, 305 + b: 0.0, 306 + a: 1.0, 307 + ), Color( 308 + r: 0.0548405, 309 + g: 0.020170014, 310 + b: 0.0, 311 + a: 1.0, 312 + )), 313 + danger: (Color( 314 + r: 1.0, 315 + g: 0.96066725, 316 + b: 0.9563592, 317 + a: 1.0, 318 + ), Color( 319 + r: 1.0, 320 + g: 0.922143, 321 + b: 0.9137879, 322 + a: 1.0, 323 + ), Color( 324 + r: 1.0, 325 + g: 0.8473892, 326 + b: 0.8316829, 327 + a: 1.0, 328 + ), Color( 329 + r: 1.0, 330 + g: 0.7756427, 331 + b: 0.75359243, 332 + a: 1.0, 333 + ), Color( 334 + r: 1.0, 335 + g: 0.67351925, 336 + b: 0.6438781, 337 + a: 1.0, 338 + ), Color( 339 + r: 1.0, 340 + g: 0.5777968, 341 + b: 0.54293776, 342 + a: 1.0, 343 + ), Color( 344 + r: 1.0, 345 + g: 0.43189234, 346 + b: 0.39381278, 347 + a: 1.0, 348 + ), Color( 349 + r: 0.98739165, 350 + g: 0.25879404, 351 + b: 0.22807789, 352 + a: 1.0, 353 + ), Color( 354 + r: 0.8097997, 355 + g: 0.09760057, 356 + b: 0.08887091, 357 + a: 1.0, 358 + ), Color( 359 + r: 0.67617863, 360 + g: 0.06355104, 361 + b: 0.0603216, 362 + a: 1.0, 363 + ), Color( 364 + r: 0.39130688, 365 + g: 0.03677729, 366 + b: 0.034908257, 367 + a: 1.0, 368 + ), Color( 369 + r: 0.08452233, 370 + g: 0.00794388, 371 + b: 0.0075402, 372 + a: 1.0, 373 + )), 374 + focus_ring: Color( 375 + r: 0.0, 376 + g: 0.334456, 377 + b: 0.62022746, 378 + a: 1.0, 379 + ), 380 + ), 381 + cad: CadColors( 382 + sketch_under_defined: Color( 383 + r: 0.013702081, 384 + g: 0.07818741, 385 + b: 0.6038273, 386 + a: 1.0, 387 + ), 388 + sketch_fully_defined: Color( 389 + r: 0.005181515, 390 + g: 0.005181515, 391 + b: 0.005181515, 392 + a: 1.0, 393 + ), 394 + sketch_over_defined: Color( 395 + r: 0.57758045, 396 + g: 0.0, 397 + b: 0.012983031, 398 + a: 1.0, 399 + ), 400 + sketch_invalid: Color( 401 + r: 0.19461781, 402 + g: 0.0, 403 + b: 0.0069954083, 404 + a: 1.0, 405 + ), 406 + sketch_dangling: Color( 407 + r: 0.21586053, 408 + g: 0.21586053, 409 + b: 0.0, 410 + a: 1.0, 411 + ), 412 + sketch_driven: Color( 413 + r: 0.20863685, 414 + g: 0.20863685, 415 + b: 0.20863685, 416 + a: 1.0, 417 + ), 418 + selection_primary: Color( 419 + r: 0.025186857, 420 + g: 0.61049557, 421 + b: 0.033104762, 422 + a: 1.0, 423 + ), 424 + selection_hover: Color( 425 + r: 1.0, 426 + g: 0.26225066, 427 + b: 0.0, 428 + a: 1.0, 429 + ), 430 + property_pane_active_box: Color( 431 + r: 1.0, 432 + g: 0.7758222, 433 + b: 0.6724431, 434 + a: 1.0, 435 + ), 436 + relation_glyph: Color( 437 + r: 0.010960091, 438 + g: 0.1980693, 439 + b: 0.11193242, 440 + a: 1.0, 441 + ), 442 + reference_geometry: Color( 443 + r: 0.32777807, 444 + g: 0.32777807, 445 + b: 0.32777807, 446 + a: 1.0, 447 + ), 448 + ), 449 + spacing: SpacingScale( 450 + xs: 4.0, 451 + sm: 8.0, 452 + md: 12.0, 453 + lg: 16.0, 454 + xl: 24.0, 455 + xxl: 32.0, 456 + ), 457 + typography: Typography( 458 + caption: TypographyRole( 459 + face: Sans, 460 + size: 0.002910416666666667, 461 + line_height: 0.003704166666666667, 462 + weight: Regular, 463 + letter_spacing: 0.0, 464 + ), 465 + body: TypographyRole( 466 + face: Sans, 467 + size: 0.003439583333333333, 468 + line_height: 0.0047625, 469 + weight: Regular, 470 + letter_spacing: 0.0, 471 + ), 472 + label: TypographyRole( 473 + face: Sans, 474 + size: 0.003175, 475 + line_height: 0.004233333333333334, 476 + weight: Medium, 477 + letter_spacing: 0.0, 478 + ), 479 + title: TypographyRole( 480 + face: Sans, 481 + size: 0.003704166666666667, 482 + line_height: 0.005291666666666667, 483 + weight: Semibold, 484 + letter_spacing: 0.0, 485 + ), 486 + heading: TypographyRole( 487 + face: Sans, 488 + size: 0.0047625, 489 + line_height: 0.00635, 490 + weight: Semibold, 491 + letter_spacing: 0.0, 492 + ), 493 + mono: TypographyRole( 494 + face: Mono, 495 + size: 0.003175, 496 + line_height: 0.004233333333333334, 497 + weight: Regular, 498 + letter_spacing: 0.0, 499 + ), 500 + ), 501 + radius: RadiusScale( 502 + none: 0.0, 503 + sm: 2.0, 504 + md: 4.0, 505 + lg: 8.0, 506 + pill: 9999.0, 507 + ), 508 + elevation: ElevationScale( 509 + level0: ElevationLevel( 510 + surface: L0, 511 + border: None, 512 + shadow: None, 513 + ), 514 + level1: ElevationLevel( 515 + surface: L1, 516 + border: Some(Border( 517 + width: 1.0, 518 + color: Color( 519 + r: 0.63602823, 520 + g: 0.68943834, 521 + b: 0.74979544, 522 + a: 1.0, 523 + ), 524 + )), 525 + shadow: None, 526 + ), 527 + level2: ElevationLevel( 528 + surface: L2, 529 + border: Some(Border( 530 + width: 1.0, 531 + color: Color( 532 + r: 0.5256735, 533 + g: 0.5798132, 534 + b: 0.6415478, 535 + a: 1.0, 536 + ), 537 + )), 538 + shadow: None, 539 + ), 540 + level3: ElevationLevel( 541 + surface: L3, 542 + border: Some(Border( 543 + width: 1.0, 544 + color: Color( 545 + r: 0.39559013, 546 + g: 0.44645584, 547 + b: 0.5051464, 548 + a: 1.0, 549 + ), 550 + )), 551 + shadow: None, 552 + ), 553 + ), 554 + motion: Motion( 555 + fast: MotionToken( 556 + duration: Duration( 557 + secs: 0, 558 + nanos: 80000000, 559 + ), 560 + easing: Standard, 561 + ), 562 + quick: MotionToken( 563 + duration: Duration( 564 + secs: 0, 565 + nanos: 120000000, 566 + ), 567 + easing: Standard, 568 + ), 569 + normal: MotionToken( 570 + duration: Duration( 571 + secs: 0, 572 + nanos: 180000000, 573 + ), 574 + easing: Standard, 575 + ), 576 + relaxed: MotionToken( 577 + duration: Duration( 578 + secs: 0, 579 + nanos: 260000000, 580 + ), 581 + easing: Decelerate, 582 + ), 583 + slow: MotionToken( 584 + duration: Duration( 585 + secs: 0, 586 + nanos: 400000000, 587 + ), 588 + easing: Decelerate, 589 + ), 590 + glacial: MotionToken( 591 + duration: Duration( 592 + secs: 0, 593 + nanos: 700000000, 594 + ), 595 + easing: Decelerate, 596 + ), 597 + ), 598 + )
+34
crates/bone-ui/tests/theme_snapshot.rs
··· 1 + use bone_ui::Theme; 2 + 3 + #[test] 4 + fn light_theme_default_snapshot() { 5 + insta::assert_ron_snapshot!("theme_light", Theme::light()); 6 + } 7 + 8 + #[test] 9 + fn dark_theme_default_snapshot() { 10 + insta::assert_ron_snapshot!("theme_dark", Theme::dark()); 11 + } 12 + 13 + #[test] 14 + fn round_trip_light_through_ron_is_stable() { 15 + assert_round_trip_stable(&Theme::light()); 16 + } 17 + 18 + #[test] 19 + fn round_trip_dark_through_ron_is_stable() { 20 + assert_round_trip_stable(&Theme::dark()); 21 + } 22 + 23 + fn assert_round_trip_stable(theme: &Theme) { 24 + let Ok(first) = ron::ser::to_string(theme) else { 25 + panic!("first serialise failed"); 26 + }; 27 + let Ok(parsed) = ron::de::from_str::<Theme>(&first) else { 28 + panic!("deserialise failed"); 29 + }; 30 + let Ok(second) = ron::ser::to_string(&parsed) else { 31 + panic!("second serialise failed"); 32 + }; 33 + assert_eq!(first, second); 34 + }