Skip to main content

max / audiofiles

Take the theme set from makeover instead of 27 local copies The browser embedded its own themes/ directory: 27 files byte-identical to makeover's, carrying the attribution errors corrected upstream and a palenight already withdrawn elsewhere. It now reads the shared embedded set, so a theme added, corrected, or withdrawn in makeover reaches this app without a second copy drifting behind. neobrute was the one theme only this app had. It is not interchangeable with the similarly-descended goingson theme, so it moved into makeover 0.10.0 rather than being dropped; nothing is lost from the picker. Add a test pinning the parts that would be a regression if the shared set changed underneath: this app's own skin, neobrute, and that palenight stays gone.
Author: Max Johnson <me@maxj.phd> · 2026-07-19 20:20 UTC
Signed with PGP, not checked
Commit: dd5152dcef0a47c2ff6bdbea2e8bf527609ad1d9
Parent: b8d40cb
33 files changed, +60 insertions, -1044 deletions
M Cargo.lock +22 -2
@@ -2425,6 +2425,25 @@
2425 2425 "tiff",
2426 2426 ]
2427 2427
2428 + [[package]]
2429 + name = "include_dir"
2430 + version = "0.7.4"
2431 + source = "registry+https://github.com/rust-lang/crates.io-index"
2432 + checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
2433 + dependencies = [
2434 + "include_dir_macros",
2435 + ]
2436 +
2437 + [[package]]
2438 + name = "include_dir_macros"
2439 + version = "0.7.4"
2440 + source = "registry+https://github.com/rust-lang/crates.io-index"
2441 + checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
2442 + dependencies = [
2443 + "proc-macro2",
2444 + "quote",
2445 + ]
2446 +
2428 2447 [[package]]
2429 2448 name = "indexmap"
2430 2449 version = "2.14.0"
@@ -2815,10 +2834,11 @@
2815 2834
2816 2835 [[package]]
2817 2836 name = "makeover"
2818 - version = "0.8.0"
2837 + version = "0.10.0"
2819 2838 source = "registry+https://github.com/rust-lang/crates.io-index"
2820 - checksum = "c43e27ea24999c6692f6e72ee286fafc561da0cc6f81bc39fc867621718063f7"
2839 + checksum = "c62c909826835a6a4212081a4c95ef2db0d07c0cb1a550849c2e779b962f093e"
2821 2840 dependencies = [
2841 + "include_dir",
2822 2842 "serde",
2823 2843 "toml",
2824 2844 ]
M Cargo.toml +1 -1
@@ -48,4 +48,4 @@
48 48 libc = "0.2"
49 49 midir = "0.11"
50 50 tagtree = { path = "../../MNW/shared/tagtree" }
51 - makeover = "0.8.0"
51 + makeover = "0.10.0"
M README.md +1 -1
@@ -80,7 +80,7 @@
80 80 ### Infrastructure
81 81 - **Cloud sync**: cross-device sync of metadata, tags, and VFS via SyncKit (E2E encrypted, ChaCha20-Poly1305 + Argon2)
82 82 - **OTA updates**: background update checker with consent dialog
83 - - **Bundled themes**: dark, light, and high-contrast variants in TOML format (see `crates/audiofiles-browser/themes/`)
83 + - **Bundled themes**: dark, light, and high-contrast variants in TOML format, from the shared [makeover](https://crates.io/crates/makeover) crate
84 84 - **Audio formats**: WAV, FLAC, MP3, OGG, AIFF (via Symphonia, pure Rust)
85 85 - **Platforms**: macOS, Windows, Linux (standalone, no backend required)
86 86
@@ -108,7 +108,7 @@
108 108
109 109 ## Theme System
110 110
111 - The browser supports multiple color themes via a 14-slot palette (4 background, 3 foreground, 6 accent, 1 border). Themes are defined as TOML files with `[meta]`, `[background]`, `[foreground]`, `[accent]`, and `[border]` sections. Bundled themes live in `crates/audiofiles-browser/themes/` and are compiled in at build time; users can add custom themes to `<config>/audiofiles/themes/` which override bundled themes by ID.
111 + The browser supports multiple color themes. Themes are TOML files declaring colors by intent, with `[meta]`, `[surface]`, `[content]`, `[action]`, `[status]`, `[line]`, and `[category]` sections; interactive states are derived perceptually rather than authored. Bundled themes come from the shared `makeover` crate, embedded at compile time; users can add custom themes to `<config>/audiofiles/themes/` which override bundled themes by ID.
112 112
113 113 The active theme is stored in a global `RwLock<ThemeColors>`. Public accessor functions (e.g., `bg_primary()`, `text_secondary()`, `accent_blue()`) read through the lock. Derived colors (row stripes, selection highlights, hover states) are computed by linear interpolation between base palette slots.
114 114
@@ -9,7 +9,7 @@
9 9 ## Design
10 10
11 11 - **Brand:** "audiofiles/" in Recursive Mono Bold. Short form: "af/"
12 - - **Default theme:** Bold black and white (pure #000000 background, #ffffff text, sharp system accent colors). Bundled themes (dark, light, and high-contrast variants) live in `crates/audiofiles-browser/themes/`.
12 + - **Default theme:** Bold black and white (pure #000000 background, #ffffff text, sharp system accent colors). Bundled themes (dark, light, and high-contrast variants) come from the [makeover](https://crates.io/crates/makeover) crate, shared with the other make-family apps.
13 13 - **Typography:** Recursive Mono Bold for the logo; egui system font for all UI text.
14 14
15 15 ## Features
@@ -178,7 +178,7 @@
178 178 - Escape: close dialog / clear search
179 179
180 180 ### Themes
181 - - Bundled themes (audiofiles default + community themes; see `crates/audiofiles-browser/themes/`)
181 + - Bundled themes (audiofiles default + community themes), shared via the `makeover` crate
182 182 - Dark: audiofiles, Tokyo Night, Catppuccin Mocha, Dracula, Nord, Gruvbox Dark, Rose Pine, Everforest, Solarized Dark, Kanagawa
183 183 - Light: Catppuccin Latte, Ayu Light, Flatwhite, Neobrute, Gruvbox Light, Rose Pine Dawn
184 184 - High Contrast: High Contrast
@@ -55,37 +55,14 @@
55 55 pub const FOCUS: f32 = 1.5;
56 56 }
57 57
58 - // --- Bundled themes (embedded at compile time) ---
58 + // --- Bundled themes ---
59 59
60 - static BUNDLED_THEMES: &[(&str, &str)] = &[
61 - ("audiofiles", include_str!("../../themes/audiofiles.toml")),
62 - ("tokyonight", include_str!("../../themes/tokyonight.toml")),
63 - ("catppuccin-mocha", include_str!("../../themes/catppuccin-mocha.toml")),
64 - ("catppuccin-macchiato", include_str!("../../themes/catppuccin-macchiato.toml")),
65 - ("catppuccin-frappe", include_str!("../../themes/catppuccin-frappe.toml")),
66 - ("catppuccin-latte", include_str!("../../themes/catppuccin-latte.toml")),
67 - ("one-dark", include_str!("../../themes/one-dark.toml")),
68 - ("dracula", include_str!("../../themes/dracula.toml")),
69 - ("nightfox", include_str!("../../themes/nightfox.toml")),
70 - ("carbonfox", include_str!("../../themes/carbonfox.toml")),
71 - ("oxocarbon-dark", include_str!("../../themes/oxocarbon-dark.toml")),
72 - ("poimandres", include_str!("../../themes/poimandres.toml")),
73 - ("ayu-mirage", include_str!("../../themes/ayu-mirage.toml")),
74 - ("nord", include_str!("../../themes/nord.toml")),
75 - ("ayu-light", include_str!("../../themes/ayu-light.toml")),
76 - ("flatwhite", include_str!("../../themes/flatwhite.toml")),
77 - ("dawnfox", include_str!("../../themes/dawnfox.toml")),
78 - ("oxocarbon-light", include_str!("../../themes/oxocarbon-light.toml")),
79 - ("neobrute", include_str!("../../themes/neobrute.toml")),
80 - ("high-contrast", include_str!("../../themes/high-contrast.toml")),
81 - ("gruvbox-dark", include_str!("../../themes/gruvbox-dark.toml")),
82 - ("gruvbox-light", include_str!("../../themes/gruvbox-light.toml")),
83 - ("rosepine", include_str!("../../themes/rosepine.toml")),
84 - ("rosepine-dawn", include_str!("../../themes/rosepine-dawn.toml")),
85 - ("everforest", include_str!("../../themes/everforest.toml")),
86 - ("solarized-dark", include_str!("../../themes/solarized-dark.toml")),
87 - ("kanagawa", include_str!("../../themes/kanagawa.toml")),
88 - ];
60 + /// The shared theme set, from `makeover`. These were once 27 `include_str!`
61 + /// copies of files under `themes/`; they are now read from the one crate every
62 + /// make-family app shares, so a theme added, corrected, or withdrawn there
63 + /// reaches this app without a second copy drifting behind.
64 + static BUNDLED_THEMES: LazyLock<Vec<(&'static str, &'static str)>> =
65 + LazyLock::new(|| makeover::embedded_themes().collect());
89 66
90 67 /// The resolved theme colors (intent vocabulary) for egui.
91 68 #[derive(Debug, Clone)]
@@ -309,7 +286,7 @@
309 286
310 287 /// Load a theme's full color set by id (bundled or custom on disk).
311 288 fn theme_colors_for(id: &str) -> Option<ThemeColors> {
312 - for (bundled_id, content) in BUNDLED_THEMES {
289 + for (bundled_id, content) in BUNDLED_THEMES.iter() {
313 290 if *bundled_id == id {
314 291 return parse_theme(content).ok();
315 292 }
@@ -342,7 +319,7 @@
342 319 let mut seen = std::collections::HashSet::new();
343 320
344 321 // Bundled themes first
345 - for (id, content) in BUNDLED_THEMES {
322 + for (id, content) in BUNDLED_THEMES.iter() {
346 323 let table: toml::Table = match content.parse() {
347 324 Ok(t) => t,
348 325 Err(_) => continue,
@@ -480,7 +457,7 @@
480 457 /// Switch the active theme. Checks bundled themes first, then custom directory.
481 458 pub fn set_theme(id: &str) {
482 459 // Try bundled themes first
483 - for (bundled_id, content) in BUNDLED_THEMES {
460 + for (bundled_id, content) in BUNDLED_THEMES.iter() {
484 461 if *bundled_id == id {
485 462 match parse_theme(content) {
486 463 Ok(colors) => {
@@ -521,7 +498,7 @@
521 498 let content = {
522 499 // Check bundled first
523 500 let mut found = None;
524 - for (bundled_id, c) in BUNDLED_THEMES {
501 + for (bundled_id, c) in BUNDLED_THEMES.iter() {
525 502 if *bundled_id == id {
526 503 found = Some(c.to_string());
527 504 break;
@@ -555,7 +532,7 @@
555 532 }
556 533
557 534 // Check bundled themes
558 - for (bundled_id, content) in BUNDLED_THEMES {
535 + for (bundled_id, content) in BUNDLED_THEMES.iter() {
559 536 if *bundled_id == id {
560 537 return Some(content.to_string());
561 538 }
@@ -1071,9 +1048,27 @@
1071 1048 // Bundled theme parsing (round-trip all embedded themes)
1072 1049 // ---------------------------------------------------------------
1073 1050
1051 + #[test]
1052 + fn bundled_set_comes_from_makeover_and_keeps_this_app_themed() {
1053 + // The theme list is user-visible surface now sourced from another
1054 + // crate, so pin the parts that would be a regression if makeover
1055 + // changed underneath: this app's own skin, and neobrute, which was
1056 + // local here before the consolidation and is not interchangeable with
1057 + // the similarly-descended `goingson` theme.
1058 + let ids: Vec<&str> = BUNDLED_THEMES.iter().map(|(id, _)| *id).collect();
1059 + for required in ["audiofiles", "neobrute", "high-contrast"] {
1060 + assert!(ids.contains(&required), "bundled set lost `{required}`: {ids:?}");
1061 + }
1062 + assert!(
1063 + !ids.contains(&"palenight"),
1064 + "palenight was withdrawn over unresolvable provenance and must not return"
1065 + );
1066 + assert!(ids.len() >= 30, "bundled set looks truncated: {} themes", ids.len());
1067 + }
1068 +
1074 1069 #[test]
1075 1070 fn all_bundled_themes_parse_successfully() {
1076 - for (id, content) in BUNDLED_THEMES {
1071 + for (id, content) in BUNDLED_THEMES.iter() {
1077 1072 let result = parse_theme(content);
1078 1073 assert!(result.is_ok(), "Bundled theme '{id}' failed to parse: {:?}", result.err());
1079 1074 }
@@ -1081,7 +1076,7 @@
1081 1076
1082 1077 #[test]
1083 1078 fn all_bundled_themes_have_valid_meta() {
1084 - for (id, content) in BUNDLED_THEMES {
1079 + for (id, content) in BUNDLED_THEMES.iter() {
1085 1080 let table: toml::Table = content.parse()
1086 1081 .unwrap_or_else(|e| panic!("Bundled theme '{id}' is invalid TOML: {e}"));
1087 1082 let meta = makeover::parse_meta(id, &table, false);
@@ -1093,7 +1088,7 @@
1093 1088 #[test]
1094 1089 fn bundled_theme_colors_are_not_all_black() {
1095 1090 // Sanity check: a properly parsed theme shouldn't have all-black fields
1096 - for (id, content) in BUNDLED_THEMES {
1091 + for (id, content) in BUNDLED_THEMES.iter() {
1097 1092 let theme = parse_theme(content).unwrap();
1098 1093 let all_black = theme.surface_page == Color32::BLACK
1099 1094 && theme.content == Color32::BLACK
@@ -1,39 +1,0 @@
1 - # audiofiles — the app's titular default skin. Cross-available as a universal
2 - # theme. Built from a 5-tone earthy palette: Ash Grey #CCDAD1 / #9CAEA9,
3 - # Grey Olive #788585, Dim Grey #6F6866, Deep Mocha #38302E. Accents are
4 - # desaturated naturals tuned to harmonize with the sage-to-mocha range.
5 -
6 - [meta]
7 - name = "audiofiles"
8 - variant = "light"
9 -
10 - [surface]
11 - page = "#CCDAD1"
12 - raised = "#DAE3DC"
13 - sunken = "#9CAEA9"
14 - overlay = "#B4C5BB"
15 -
16 - [content]
17 - primary = "#38302E"
18 - secondary = "#6F6866"
19 - muted = "#788585"
20 -
21 - [action]
22 - primary = "#5E7C8E"
23 -
24 - [status]
25 - danger = "#B05F4E"
26 - success = "#6F8A5C"
27 - warning = "#C19A53"
28 - info = "#5F8C82"
29 -
30 - [line]
31 - border = "#9CAEA9"
32 -
33 - [category]
34 - one = "#B05F4E"
35 - two = "#6F8A5C"
36 - three = "#5E7C8E"
37 - four = "#C19A53"
38 - five = "#836A80"
39 - six = "#5F8C82"
@@ -1,37 +1,0 @@
1 - # Based on Ayu by Konstantin Pschera — MIT License
2 - # https://github.com/ayu-theme
3 -
4 - [meta]
5 - name = "Ayu Light"
6 - variant = "light"
7 -
8 - [surface]
9 - page = "#e7eaed"
10 - raised = "#f2f4f6"
11 - sunken = "#d0d4d8"
12 - overlay = "#dde1e5"
13 -
14 - [content]
15 - primary = "#5c6166"
16 - secondary = "#6b7580"
17 - muted = "#8b9199"
18 -
19 - [action]
20 - primary = "#399ee6"
21 -
22 - [status]
23 - danger = "#f07171"
24 - success = "#86b300"
25 - warning = "#ffaa33"
26 - info = "#55b4d4"
27 -
28 - [line]
29 - border = "#828c9a"
30 -
31 - [category]
32 - one = "#f07171"
33 - two = "#86b300"
34 - three = "#399ee6"
35 - four = "#ffaa33"
36 - five = "#a37acc"
37 - six = "#55b4d4"
@@ -1,37 +1,0 @@
1 - # Based on Ayu by dempfi — MIT License
2 - # https://github.com/ayu-theme/ayu-colors
3 -
4 - [meta]
5 - name = "Ayu Mirage"
6 - variant = "dark"
7 -
8 - [surface]
9 - page = "#1f2430"
10 - raised = "#242936"
11 - sunken = "#272d38"
12 - overlay = "#191e29"
13 -
14 - [content]
15 - primary = "#cccac2"
16 - secondary = "#b8b4aa"
17 - muted = "#707a8c"
18 -
19 - [action]
20 - primary = "#5ccfe6"
21 -
22 - [status]
23 - danger = "#f28779"
24 - success = "#bae67e"
25 - warning = "#ffd580"
26 - info = "#95e6cb"
27 -
28 - [line]
29 - border = "#33415e"
30 -
31 - [category]
32 - one = "#f28779"
33 - two = "#bae67e"
34 - three = "#5ccfe6"
35 - four = "#ffd580"
36 - five = "#d4bfff"
37 - six = "#95e6cb"
@@ -1,37 +1,0 @@
1 - # Based on Carbonfox by EdenEast — MIT License
2 - # https://github.com/EdenEast/nightfox.nvim
3 -
4 - [meta]
5 - name = "Carbonfox"
6 - variant = "dark"
7 -
8 - [surface]
9 - page = "#161616"
10 - raised = "#252525"
11 - sunken = "#1e1e1e"
12 - overlay = "#101010"
13 -
14 - [content]
15 - primary = "#f2f4f8"
16 - secondary = "#dde1e6"
17 - muted = "#878d96"
18 -
19 - [action]
20 - primary = "#78a9ff"
21 -
22 - [status]
23 - danger = "#ee5396"
24 - success = "#25be6a"
25 - warning = "#08bdba"
26 - info = "#33b1ff"
27 -
28 - [line]
29 - border = "#393939"
30 -
31 - [category]
32 - one = "#ee5396"
33 - two = "#25be6a"
34 - three = "#78a9ff"
35 - four = "#08bdba"
36 - five = "#be95ff"
37 - six = "#33b1ff"
@@ -1,37 +1,0 @@
1 - # Based on Catppuccin by Catppuccin Org — MIT License
2 - # https://github.com/catppuccin/catppuccin
3 -
4 - [meta]
5 - name = "Catppuccin Frappé"
6 - variant = "dark"
7 -
8 - [surface]
9 - page = "#303446"
10 - raised = "#232634"
11 - sunken = "#414559"
12 - overlay = "#292c3c"
13 -
14 - [content]
15 - primary = "#c6d0f5"
16 - secondary = "#b5bfe2"
17 - muted = "#a5adce"
18 -
19 - [action]
20 - primary = "#8caaee"
21 -
22 - [status]
23 - danger = "#e78284"
24 - success = "#a6d189"
25 - warning = "#e5c890"
26 - info = "#81c8be"
27 -
28 - [line]
29 - border = "#626880"
30 -
31 - [category]
32 - one = "#e78284"
33 - two = "#a6d189"
34 - three = "#8caaee"
35 - four = "#e5c890"
36 - five = "#ca9ee6"
37 - six = "#81c8be"
@@ -1,37 +1,0 @@
1 - # Based on Catppuccin by Catppuccin Org — MIT License
2 - # https://github.com/catppuccin/catppuccin
3 -
4 - [meta]
5 - name = "Catppuccin Latte"
6 - variant = "light"
7 -
8 - [surface]
9 - page = "#e6e9ef"
10 - raised = "#eff1f5"
11 - sunken = "#ccd0da"
12 - overlay = "#dce0e8"
13 -
14 - [content]
15 - primary = "#4c4f69"
16 - secondary = "#5c5f77"
17 - muted = "#6c6f82"
18 -
19 - [action]
20 - primary = "#1e66f5"
21 -
22 - [status]
23 - danger = "#d20f39"
24 - success = "#40a02b"
25 - warning = "#df8e1d"
26 - info = "#04a5e5"
27 -
28 - [line]
29 - border = "#bcc0cc"
30 -
31 - [category]
32 - one = "#d20f39"
33 - two = "#40a02b"
34 - three = "#1e66f5"
35 - four = "#df8e1d"
36 - five = "#8839ef"
37 - six = "#04a5e5"
@@ -1,37 +1,0 @@
1 - # Based on Catppuccin by Catppuccin Org — MIT License
2 - # https://github.com/catppuccin/catppuccin
3 -
4 - [meta]
5 - name = "Catppuccin Macchiato"
6 - variant = "dark"
7 -
8 - [surface]
9 - page = "#24273a"
10 - raised = "#181926"
11 - sunken = "#363a4f"
12 - overlay = "#1e2030"
13 -
14 - [content]
15 - primary = "#cad3f5"
16 - secondary = "#b8c0e0"
17 - muted = "#a5adcb"
18 -
19 - [action]
20 - primary = "#8aadf4"
21 -
22 - [status]
23 - danger = "#ed8796"
24 - success = "#a6da95"
25 - warning = "#eed49f"
26 - info = "#8bd5ca"
27 -
28 - [line]
29 - border = "#5b6078"
30 -
31 - [category]
32 - one = "#ed8796"
33 - two = "#a6da95"
34 - three = "#8aadf4"
35 - four = "#eed49f"
36 - five = "#c6a0f6"
37 - six = "#8bd5ca"
@@ -1,37 +1,0 @@
1 - # Based on Catppuccin by Catppuccin Org — MIT License
2 - # https://github.com/catppuccin/catppuccin
3 -
4 - [meta]
5 - name = "Catppuccin Mocha"
6 - variant = "dark"
7 -
8 - [surface]
9 - page = "#181825"
10 - raised = "#1e1e2e"
11 - sunken = "#313244"
12 - overlay = "#11111b"
13 -
14 - [content]
15 - primary = "#cdd6f4"
16 - secondary = "#bac2de"
17 - muted = "#9399b2"
18 -
19 - [action]
20 - primary = "#89b4fa"
21 -
22 - [status]
23 - danger = "#f38ba8"
24 - success = "#a6e3a1"
25 - warning = "#f9e2af"
26 - info = "#89dceb"
27 -
28 - [line]
29 - border = "#45475a"
30 -
31 - [category]
32 - one = "#f38ba8"
33 - two = "#a6e3a1"
34 - three = "#89b4fa"
35 - four = "#f9e2af"
36 - five = "#cba6f7"
37 - six = "#89dceb"