theme.rs (18570B)
1 #[derive(Clone, Copy, Debug, PartialEq)] 2 pub struct AppUiTheme { 3 pub foundation: AppFoundationTokens, 4 pub components: AppComponentTokens, 5 pub shells: AppShellTokens, 6 } 7 8 #[derive(Clone, Copy, Debug, PartialEq)] 9 pub struct AppFoundationTokens { 10 pub surfaces: AppSurfaceTokens, 11 pub text: AppTextTokens, 12 pub typography: AppTypographyTokens, 13 pub spacing: AppSpacingTokens, 14 pub radii: AppRadiusTokens, 15 pub borders: AppBorderTokens, 16 } 17 18 #[derive(Clone, Copy, Debug, PartialEq)] 19 pub struct AppSurfaceTokens { 20 pub window_background: u32, 21 pub chrome_background: u32, 22 pub panel_background: u32, 23 pub card_background: u32, 24 pub divider: u32, 25 } 26 27 #[derive(Clone, Copy, Debug, PartialEq)] 28 pub struct AppTextTokens { 29 pub primary: u32, 30 pub secondary: u32, 31 pub accent: u32, 32 } 33 34 #[derive(Clone, Copy, Debug, PartialEq)] 35 pub struct AppTypographyTokens { 36 pub utility_title_text_px: f32, 37 pub body_text_px: f32, 38 pub brand_text_px: f32, 39 pub startup_title_text_px: f32, 40 pub startup_tagline_text_px: f32, 41 pub settings_row_text_px: f32, 42 pub settings_account_identity_text_px: f32, 43 pub settings_account_detail_text_px: f32, 44 } 45 46 #[derive(Clone, Copy, Debug, PartialEq)] 47 pub struct AppSpacingTokens { 48 pub micro_px: f32, 49 pub tight_px: f32, 50 pub small_px: f32, 51 pub medium_px: f32, 52 pub large_px: f32, 53 pub xlarge_px: f32, 54 } 55 56 #[derive(Clone, Copy, Debug, PartialEq)] 57 pub struct AppRadiusTokens { 58 pub small_px: f32, 59 pub medium_px: f32, 60 pub large_px: f32, 61 } 62 63 #[derive(Clone, Copy, Debug, PartialEq)] 64 pub struct AppBorderTokens { 65 pub divider_thickness_px: f32, 66 } 67 68 #[derive(Clone, Copy, Debug, PartialEq)] 69 pub struct AppComponentTokens { 70 pub app_segment_button_icon: AppSegmentButtonIconTokens, 71 pub app_button: AppButtonTokens, 72 pub app_input_text: AppInputTextTokens, 73 pub app_checkbox_field: AppCheckboxFieldTokens, 74 pub app_status_indicator: AppStatusIndicatorTokens, 75 pub app_account_selector_row: AppAccountSelectorRowTokens, 76 } 77 78 #[derive(Clone, Copy, Debug, PartialEq)] 79 pub struct AppAccountSelectorRowTokens { 80 pub inactive_background: u32, 81 pub active_background: u32, 82 } 83 84 #[derive(Clone, Copy, Debug, PartialEq)] 85 pub struct AppSegmentButtonIconTokens { 86 pub sizing: AppSegmentButtonIconSizing, 87 pub colors: AppSegmentButtonIconColors, 88 } 89 90 #[derive(Clone, Copy, Debug, PartialEq)] 91 pub struct AppSegmentButtonIconSizing { 92 pub height_px: f32, 93 pub corner_radius_px: f32, 94 pub inner_padding_px: f32, 95 pub icon_size_px: f32, 96 pub label_size_px: f32, 97 } 98 99 #[derive(Clone, Copy, Debug, PartialEq)] 100 pub struct AppSegmentButtonIconColors { 101 pub active_background: u32, 102 pub inactive_background: u32, 103 pub active_foreground: u32, 104 pub inactive_foreground: u32, 105 } 106 107 #[derive(Clone, Copy, Debug, PartialEq)] 108 pub struct AppButtonTokens { 109 pub sizing: AppButtonSizing, 110 pub secondary_colors: AppButtonColors, 111 pub primary_colors: AppButtonColors, 112 pub primary_disabled_colors: AppButtonColors, 113 } 114 115 #[derive(Clone, Copy, Debug, PartialEq)] 116 pub struct AppButtonSizing { 117 pub height_px: f32, 118 pub corner_radius_px: f32, 119 pub horizontal_padding_px: f32, 120 pub compact_horizontal_padding_px: f32, 121 pub label_size_px: f32, 122 pub icon_size_px: f32, 123 pub square_width_px: f32, 124 } 125 126 #[derive(Clone, Copy, Debug, PartialEq)] 127 pub struct AppButtonColors { 128 pub background: u32, 129 pub foreground: u32, 130 pub hover_changes_background: bool, 131 pub hover_background: u32, 132 pub active_background: u32, 133 } 134 135 #[derive(Clone, Copy, Debug, PartialEq)] 136 pub struct AppInputTextTokens { 137 pub background: u32, 138 pub disabled_background: u32, 139 pub border: u32, 140 pub corner_radius_px: f32, 141 } 142 143 #[derive(Clone, Copy, Debug, PartialEq)] 144 pub struct AppCheckboxFieldTokens { 145 pub size_px: f32, 146 pub corner_radius_px: f32, 147 pub icon_size_px: f32, 148 pub checked_background: u32, 149 pub unchecked_background: u32, 150 pub unchecked_border: u32, 151 pub check_foreground: u32, 152 } 153 154 #[derive(Clone, Copy, Debug, PartialEq)] 155 pub struct AppStatusIndicatorTokens { 156 pub size_px: f32, 157 pub online: u32, 158 pub offline: u32, 159 pub attention: u32, 160 } 161 162 #[derive(Clone, Copy, Debug, PartialEq)] 163 pub struct AppShellTokens { 164 pub home_min_width_px: f32, 165 pub home_min_height_px: f32, 166 pub settings_width_px: f32, 167 pub settings_height_px: f32, 168 pub home_window_padding_px: f32, 169 pub home_sidebar_width_px: f32, 170 pub home_card_max_width_px: f32, 171 pub focused_task_max_width_px: f32, 172 pub focused_detail_max_width_px: f32, 173 pub settings_panel_content_max_width_px: f32, 174 pub home_card_padding_px: f32, 175 pub home_stack_gap_px: f32, 176 pub startup_stack_gap_px: f32, 177 pub metadata_row_gap_px: f32, 178 pub utility_title_row_height_px: f32, 179 pub settings_chrome_height_px: f32, 180 pub settings_navigation_width_px: f32, 181 pub settings_section_gap_px: f32, 182 pub settings_navigation_row_padding_px: f32, 183 pub settings_navigation_row_gap_px: f32, 184 pub settings_content_padding_px: f32, 185 pub settings_account_sidebar_width_px: f32, 186 pub settings_account_sidebar_padding_px: f32, 187 pub settings_account_sidebar_button_height_px: f32, 188 pub settings_account_sidebar_button_padding_px: f32, 189 pub settings_account_sidebar_button_corner_radius_px: f32, 190 pub settings_account_sidebar_button_gap_px: f32, 191 pub settings_account_sidebar_avatar_size_px: f32, 192 pub settings_account_identity_text_gap_px: f32, 193 pub settings_account_sidebar_footer_padding_top_px: f32, 194 pub settings_account_sidebar_footer_row_gap_px: f32, 195 pub settings_account_sidebar_footer_button_gap_px: f32, 196 pub settings_account_main_padding_px: f32, 197 pub settings_account_content_max_width_px: f32, 198 pub settings_account_main_stack_gap_px: f32, 199 pub settings_account_profile_avatar_size_px: f32, 200 pub settings_account_detail_row_gap_px: f32, 201 pub settings_account_detail_value_gap_px: f32, 202 pub settings_checkbox_label_gap_px: f32, 203 pub settings_account_status_gap_px: f32, 204 pub settings_account_action_row_gap_px: f32, 205 } 206 207 const APP_SURFACE_WINDOW_BACKGROUND: u32 = 0xFFFFFF; 208 const APP_SURFACE_CHROME_BACKGROUND: u32 = 0xF5F5F7; 209 const APP_SURFACE_PANEL_BACKGROUND: u32 = 0xFFFFFF; 210 const APP_SURFACE_CARD_BACKGROUND: u32 = 0xF2F2F7; 211 const APP_SURFACE_DIVIDER: u32 = 0xD2D2D7; 212 const APP_SURFACE_ACCOUNT_SELECTOR_ACTIVE_BACKGROUND: u32 = 0xE5E5EA; 213 const APP_TEXT_PRIMARY: u32 = 0x1D1D1F; 214 const APP_TEXT_SECONDARY: u32 = 0x6E6E73; 215 const APP_TEXT_ACCENT: u32 = 0x0A84FF; 216 const APP_STATUS_ONLINE: u32 = 0x34C759; 217 const APP_STATUS_OFFLINE: u32 = 0xFFD60A; 218 const APP_STATUS_ATTENTION: u32 = 0xFF3B30; 219 const APP_SPACING_MICRO_PX: f32 = 4.0; 220 const APP_SPACING_TIGHT_PX: f32 = 6.0; 221 const APP_SPACING_SMALL_PX: f32 = 8.0; 222 const APP_SPACING_MEDIUM_PX: f32 = 12.0; 223 const APP_SPACING_LARGE_PX: f32 = 16.0; 224 const APP_SPACING_XLARGE_PX: f32 = 24.0; 225 const APP_RADIUS_SMALL_PX: f32 = 5.0; 226 const APP_RADIUS_MEDIUM_PX: f32 = 8.0; 227 const APP_RADIUS_LARGE_PX: f32 = 10.0; 228 229 pub const APP_UI_THEME: AppUiTheme = AppUiTheme { 230 foundation: AppFoundationTokens { 231 surfaces: AppSurfaceTokens { 232 window_background: APP_SURFACE_WINDOW_BACKGROUND, 233 chrome_background: APP_SURFACE_CHROME_BACKGROUND, 234 panel_background: APP_SURFACE_PANEL_BACKGROUND, 235 card_background: APP_SURFACE_CARD_BACKGROUND, 236 divider: APP_SURFACE_DIVIDER, 237 }, 238 text: AppTextTokens { 239 primary: APP_TEXT_PRIMARY, 240 secondary: APP_TEXT_SECONDARY, 241 accent: APP_TEXT_ACCENT, 242 }, 243 typography: AppTypographyTokens { 244 utility_title_text_px: 12.0, 245 body_text_px: 14.0, 246 brand_text_px: 14.0, 247 startup_title_text_px: 18.0, 248 startup_tagline_text_px: 16.0, 249 settings_row_text_px: 13.0, 250 settings_account_identity_text_px: 14.0, 251 settings_account_detail_text_px: 14.0, 252 }, 253 spacing: AppSpacingTokens { 254 micro_px: APP_SPACING_MICRO_PX, 255 tight_px: APP_SPACING_TIGHT_PX, 256 small_px: APP_SPACING_SMALL_PX, 257 medium_px: APP_SPACING_MEDIUM_PX, 258 large_px: APP_SPACING_LARGE_PX, 259 xlarge_px: APP_SPACING_XLARGE_PX, 260 }, 261 radii: AppRadiusTokens { 262 small_px: APP_RADIUS_SMALL_PX, 263 medium_px: APP_RADIUS_MEDIUM_PX, 264 large_px: APP_RADIUS_LARGE_PX, 265 }, 266 borders: AppBorderTokens { 267 divider_thickness_px: 1.0, 268 }, 269 }, 270 components: AppComponentTokens { 271 app_segment_button_icon: AppSegmentButtonIconTokens { 272 sizing: AppSegmentButtonIconSizing { 273 height_px: 44.0, 274 corner_radius_px: APP_RADIUS_MEDIUM_PX, 275 inner_padding_px: 2.0, 276 icon_size_px: 16.0, 277 label_size_px: 12.0, 278 }, 279 colors: AppSegmentButtonIconColors { 280 active_background: APP_SURFACE_WINDOW_BACKGROUND, 281 inactive_background: APP_SURFACE_CHROME_BACKGROUND, 282 active_foreground: APP_TEXT_ACCENT, 283 inactive_foreground: APP_TEXT_PRIMARY, 284 }, 285 }, 286 app_button: AppButtonTokens { 287 sizing: AppButtonSizing { 288 height_px: 24.0, 289 corner_radius_px: APP_RADIUS_MEDIUM_PX, 290 horizontal_padding_px: APP_SPACING_MEDIUM_PX, 291 compact_horizontal_padding_px: APP_SPACING_MICRO_PX, 292 label_size_px: 13.0, 293 icon_size_px: 14.0, 294 square_width_px: 24.0, 295 }, 296 secondary_colors: AppButtonColors { 297 background: 0xE5E5EA, 298 foreground: APP_TEXT_PRIMARY, 299 hover_changes_background: false, 300 hover_background: 0xDCDCE1, 301 active_background: 0xD1D1D6, 302 }, 303 primary_colors: AppButtonColors { 304 background: APP_TEXT_ACCENT, 305 foreground: APP_SURFACE_WINDOW_BACKGROUND, 306 hover_changes_background: true, 307 hover_background: 0x007AFF, 308 active_background: 0x0062CC, 309 }, 310 primary_disabled_colors: AppButtonColors { 311 background: 0xA7C8F8, 312 foreground: APP_SURFACE_WINDOW_BACKGROUND, 313 hover_changes_background: false, 314 hover_background: 0xA7C8F8, 315 active_background: 0xA7C8F8, 316 }, 317 }, 318 app_input_text: AppInputTextTokens { 319 background: APP_SURFACE_WINDOW_BACKGROUND, 320 disabled_background: APP_SURFACE_CARD_BACKGROUND, 321 border: 0xD1D1D6, 322 corner_radius_px: APP_RADIUS_LARGE_PX, 323 }, 324 app_checkbox_field: AppCheckboxFieldTokens { 325 size_px: 16.0, 326 corner_radius_px: APP_RADIUS_SMALL_PX, 327 icon_size_px: 13.0, 328 checked_background: APP_TEXT_ACCENT, 329 unchecked_background: APP_SURFACE_CARD_BACKGROUND, 330 unchecked_border: 0xD1D1D6, 331 check_foreground: APP_SURFACE_WINDOW_BACKGROUND, 332 }, 333 app_status_indicator: AppStatusIndicatorTokens { 334 size_px: 12.0, 335 online: APP_STATUS_ONLINE, 336 offline: APP_STATUS_OFFLINE, 337 attention: APP_STATUS_ATTENTION, 338 }, 339 app_account_selector_row: AppAccountSelectorRowTokens { 340 inactive_background: APP_SURFACE_CARD_BACKGROUND, 341 active_background: APP_SURFACE_ACCOUNT_SELECTOR_ACTIVE_BACKGROUND, 342 }, 343 }, 344 shells: AppShellTokens { 345 home_min_width_px: 1284.0, 346 home_min_height_px: 795.0, 347 settings_width_px: 600.0, 348 settings_height_px: 540.0, 349 home_window_padding_px: APP_SPACING_XLARGE_PX, 350 home_sidebar_width_px: 240.0, 351 home_card_max_width_px: 1080.0, 352 focused_task_max_width_px: 720.0, 353 focused_detail_max_width_px: 840.0, 354 settings_panel_content_max_width_px: 560.0, 355 home_card_padding_px: APP_SPACING_XLARGE_PX, 356 home_stack_gap_px: APP_SPACING_MEDIUM_PX, 357 startup_stack_gap_px: APP_SPACING_TIGHT_PX, 358 metadata_row_gap_px: APP_SPACING_MEDIUM_PX, 359 utility_title_row_height_px: 24.0, 360 settings_chrome_height_px: 88.0, 361 settings_navigation_width_px: 216.0, 362 settings_section_gap_px: APP_SPACING_SMALL_PX, 363 settings_navigation_row_padding_px: APP_SPACING_SMALL_PX, 364 settings_navigation_row_gap_px: APP_SPACING_SMALL_PX, 365 settings_content_padding_px: APP_SPACING_XLARGE_PX, 366 settings_account_sidebar_width_px: 200.0, 367 settings_account_sidebar_padding_px: APP_SPACING_SMALL_PX, 368 settings_account_sidebar_button_height_px: 52.0, 369 settings_account_sidebar_button_padding_px: APP_SPACING_MICRO_PX, 370 settings_account_sidebar_button_corner_radius_px: APP_RADIUS_MEDIUM_PX, 371 settings_account_sidebar_button_gap_px: APP_SPACING_SMALL_PX, 372 settings_account_sidebar_avatar_size_px: 32.0, 373 settings_account_identity_text_gap_px: 0.0, 374 settings_account_sidebar_footer_padding_top_px: APP_SPACING_SMALL_PX, 375 settings_account_sidebar_footer_row_gap_px: APP_SPACING_SMALL_PX, 376 settings_account_sidebar_footer_button_gap_px: APP_SPACING_SMALL_PX, 377 settings_account_main_padding_px: APP_SPACING_XLARGE_PX, 378 settings_account_content_max_width_px: 260.0, 379 settings_account_main_stack_gap_px: APP_SPACING_LARGE_PX, 380 settings_account_profile_avatar_size_px: 64.0, 381 settings_account_detail_row_gap_px: APP_SPACING_LARGE_PX, 382 settings_account_detail_value_gap_px: APP_SPACING_MEDIUM_PX, 383 settings_checkbox_label_gap_px: APP_SPACING_SMALL_PX, 384 settings_account_status_gap_px: APP_SPACING_MICRO_PX, 385 settings_account_action_row_gap_px: APP_SPACING_SMALL_PX, 386 }, 387 }; 388 389 #[cfg(test)] 390 mod tests { 391 use super::APP_UI_THEME; 392 393 #[test] 394 fn paperwhite_shell_layers_are_distinct() { 395 assert_eq!(APP_UI_THEME.foundation.surfaces.window_background, 0xFFFFFF); 396 assert_eq!(APP_UI_THEME.foundation.surfaces.chrome_background, 0xF5F5F7); 397 assert_eq!(APP_UI_THEME.foundation.surfaces.card_background, 0xF2F2F7); 398 assert_eq!(APP_UI_THEME.foundation.surfaces.divider, 0xD2D2D7); 399 assert_ne!( 400 APP_UI_THEME.foundation.surfaces.window_background, 401 APP_UI_THEME.foundation.surfaces.card_background 402 ); 403 } 404 405 #[test] 406 fn foundation_scales_are_explicit() { 407 assert_eq!(APP_UI_THEME.foundation.spacing.micro_px, 4.0); 408 assert_eq!(APP_UI_THEME.foundation.spacing.tight_px, 6.0); 409 assert_eq!(APP_UI_THEME.foundation.spacing.xlarge_px, 24.0); 410 assert_eq!(APP_UI_THEME.foundation.radii.small_px, 5.0); 411 assert_eq!(APP_UI_THEME.foundation.radii.medium_px, 8.0); 412 assert_eq!(APP_UI_THEME.foundation.radii.large_px, 10.0); 413 assert_eq!(APP_UI_THEME.foundation.borders.divider_thickness_px, 1.0); 414 } 415 416 #[test] 417 fn home_window_minimums_match_the_upgraded_shell_budget() { 418 assert_eq!(APP_UI_THEME.shells.home_min_width_px, 1284.0); 419 assert_eq!(APP_UI_THEME.shells.home_min_height_px, 795.0); 420 assert_eq!(APP_UI_THEME.shells.home_sidebar_width_px, 240.0); 421 assert_eq!(APP_UI_THEME.shells.home_window_padding_px, 24.0); 422 assert_eq!(APP_UI_THEME.shells.focused_task_max_width_px, 720.0); 423 assert_eq!(APP_UI_THEME.shells.focused_detail_max_width_px, 840.0); 424 } 425 426 #[test] 427 fn settings_shell_layout_contract_is_explicit() { 428 assert_eq!(APP_UI_THEME.shells.settings_width_px, 600.0); 429 assert_eq!(APP_UI_THEME.shells.settings_height_px, 540.0); 430 assert_eq!(APP_UI_THEME.shells.settings_chrome_height_px, 88.0); 431 assert_eq!(APP_UI_THEME.shells.settings_content_padding_px, 24.0); 432 assert_eq!( 433 APP_UI_THEME.shells.settings_panel_content_max_width_px, 434 560.0 435 ); 436 assert_eq!(APP_UI_THEME.shells.settings_account_sidebar_width_px, 200.0); 437 assert_eq!( 438 APP_UI_THEME 439 .shells 440 .settings_account_sidebar_button_height_px, 441 52.0 442 ); 443 } 444 445 #[test] 446 fn control_tokens_match_the_frozen_budget() { 447 let segmented = APP_UI_THEME.components.app_segment_button_icon.sizing; 448 let action = APP_UI_THEME.components.app_button.sizing; 449 let text_input = APP_UI_THEME.components.app_input_text; 450 let checkbox = APP_UI_THEME.components.app_checkbox_field; 451 let status = APP_UI_THEME.components.app_status_indicator; 452 let account_selector = APP_UI_THEME.components.app_account_selector_row; 453 454 assert_eq!(segmented.height_px, 44.0); 455 assert_eq!(segmented.corner_radius_px, 8.0); 456 assert_eq!(segmented.inner_padding_px, 2.0); 457 assert_eq!(action.height_px, 24.0); 458 assert_eq!(action.corner_radius_px, 8.0); 459 assert_eq!(action.square_width_px, 24.0); 460 assert_eq!(text_input.corner_radius_px, 10.0); 461 assert_eq!(text_input.background, 0xFFFFFF); 462 assert_eq!(text_input.disabled_background, 0xF2F2F7); 463 assert_eq!(checkbox.size_px, 16.0); 464 assert_eq!(checkbox.corner_radius_px, 5.0); 465 assert_eq!(status.size_px, 12.0); 466 assert_eq!(account_selector.inactive_background, 0xF2F2F7); 467 assert_eq!(account_selector.active_background, 0xE5E5EA); 468 } 469 470 #[test] 471 fn accent_and_status_colors_match_the_current_shell_contract() { 472 assert_eq!(APP_UI_THEME.foundation.text.accent, 0x0A84FF); 473 assert_eq!( 474 APP_UI_THEME.components.app_button.primary_colors.background, 475 0x0A84FF 476 ); 477 assert_eq!( 478 APP_UI_THEME.components.app_button.primary_colors.foreground, 479 0xFFFFFF 480 ); 481 assert_eq!( 482 APP_UI_THEME 483 .components 484 .app_checkbox_field 485 .checked_background, 486 0x0A84FF 487 ); 488 assert_eq!( 489 APP_UI_THEME.components.app_status_indicator.online, 490 0x34C759 491 ); 492 assert_eq!( 493 APP_UI_THEME.components.app_status_indicator.offline, 494 0xFFD60A 495 ); 496 assert_eq!( 497 APP_UI_THEME.components.app_status_indicator.attention, 498 0xFF3B30 499 ); 500 } 501 }