Skip to main content

max / makeover

5.2 KB · 120 lines History Blame Raw
1 # makeover
2
3 Shared theme loading for the make-family apps. Parses theme metadata and color values from `.toml` files on disk, resolves them into intent-based tokens, and derives the rest perceptually (OKLab) with WCAG contrast checks.
4
5 The crate ships the theme set it loads, in `themes/`. Consumers get working themes from a clean checkout without depending on any sibling repo.
6
7 Used by MNW server, GoingsOn, Balanced Breakfast, audiofiles, and Alloy.
8
9 The crate ships 31 themes. `bundled_themes_dir()` hands back the directory; `embedded_themes()` hands back the same set as `(id, toml_source)` pairs with no filesystem path involved, for consumers that embed at compile time or bundle assets at build time.
10
11 ## Usage
12
13 ```rust
14 use makeover::{load_theme, list_themes_from_dirs, bundled_themes_dir};
15 use std::path::PathBuf;
16
17 // Set up theme directories (later entries override earlier ones)
18 let bundled = bundled_themes_dir().expect("makeover ships themes/");
19 let custom = PathBuf::from("/path/to/user/custom-themes");
20 let dirs = vec![(bundled, false), (custom, true)];
21
22 // List available themes (sorted by name)
23 let themes = list_themes_from_dirs(&dirs);
24 for t in &themes {
25 println!("{} ({}, {})", t.name, t.id, t.variant);
26 }
27
28 // Load a specific theme by ID
29 let theme = load_theme(&dirs, "catppuccin-mocha").unwrap();
30 println!("Name: {}", theme.meta.name); // "Catppuccin Mocha"
31 println!("Variant: {}", theme.meta.variant); // "dark"
32 println!("BG: {}", theme.colors["surface.page"]); // "#1e1e2e"
33
34 // Build-from-source fallback: the themes this crate ships
35 if let Some(dir) = bundled_themes_dir() {
36 // dir = <makeover checkout>/themes
37 }
38 ```
39
40 ## Theme File Format
41
42 Colors are declared by **intent** (the role a color plays), not by hue. See `themes/` for the 31 bundled themes.
43
44 ```toml
45 [meta]
46 name = "Nord" # Display name; falls back to the filename
47 variant = "dark" # "dark", "light", or "high-contrast" (default: "dark")
48
49 [surface] # Container backgrounds by elevation
50 page = "#2e3440"
51 raised = "#3b4252"
52 sunken = "#434c5e"
53 overlay = "#3b4252"
54
55 [content] # Text and ink by emphasis
56 primary = "#d8dee9"
57 secondary = "#e5e9f0"
58 muted = "#616e88"
59
60 [action] # Interactive / brand color
61 primary = "#81a1c1"
62
63 [status] # State semantics
64 danger = "#bf616a"
65 success = "#a3be8c"
66 warning = "#ebcb8b"
67 info = "#88c0d0"
68
69 [line]
70 border = "#4c566a"
71
72 [category] # Optional: tag and label colors
73 ```
74
75 ### Derived tokens
76
77 Interactive states are not authored. `resolve()` derives hover, active,
78 selection, row striping, and contrast pairings perceptually in OKLab, so theme
79 files stay small and every consuming app derives them identically rather than
80 each recomputing its own.
81
82 `intent_css_vars()` renders a resolved theme as a `:root { … }` block for web
83 consumers; native consumers read RGB tuples off the same resolved tokens.
84
85 ### Theme ID
86
87 The theme ID is the filename without `.toml` (e.g., `catppuccin-mocha.toml` has ID `catppuccin-mocha`). IDs must contain only alphanumeric characters, hyphens, and underscores. Path traversal characters are rejected.
88
89 ## API
90
91 | Function | Description |
92 |----------|-------------|
93 | `list_themes_from_dirs(dirs)` | Scan directories for `.toml` files, return sorted `Vec<ThemeMeta>` |
94 | `load_theme(dirs, id)` | Load a theme by ID, returning `ThemeColors` (metadata + color map) |
95 | `find_theme_path(dirs, id)` | Find the file path for a theme ID (highest-priority directory wins) |
96 | `parse_meta(id, table, is_custom)` | Parse `[meta]` from a TOML table into `ThemeMeta` |
97 | `extract_colors(table)` | Flatten color sections into a `HashMap<String, String>` |
98 | `validate_theme_id(id)` | Check that an ID contains only safe characters |
99 | `bundled_themes_dir()` | The `themes/` directory this crate ships, for build-from-source fallback |
100 | `embedded_themes()` | The shipped themes as `(id, toml_source)` pairs, embedded at compile time (no path needed) |
101 | `parse_theme_str(id, source, is_custom)` | Parse a theme from a string, for use with `embedded_themes()` |
102 | `resolve(theme)` | Resolve authored intents into the full token set, deriving interactive states |
103 | `intent_css_vars(tokens)` | Render resolved tokens as a `:root { … }` CSS block |
104
105 ## Directory Priority
106
107 `list_themes_from_dirs` and `load_theme` accept a list of `(PathBuf, bool)` pairs. Later directories override earlier ones by theme ID. The `bool` marks whether the directory contains user-custom themes (`is_custom` on `ThemeMeta`).
108
109 Typical setup for a Tauri app:
110 1. Bundled themes, packaged by the app or from `bundled_themes_dir()` (is_custom = false)
111 2. User themes from an app data directory (is_custom = true)
112
113 ## License
114
115 MIT.
116
117 The themes in `themes/` adapt palettes from third-party projects (Catppuccin, Dracula, Nord, gruvbox, Tokyo Night, Rosé Pine, and others). Each upstream, its license, and the exact copyright line that license requires reproducing are recorded in [THIRD-PARTY-NOTICES.md]THIRD-PARTY-NOTICES.md, verified against the upstream LICENSE files themselves. Every adapted theme file also carries that information in a header comment, so credit travels with the file.
118
119 If an attribution is wrong or you would prefer your work not be included, write to info@makenot.work and it will be corrected or removed.
120