app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

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 }