Skip to main content

max / audiofiles

Put the logo face on makeover's override layer The family name was a const spelled in theme.rs and asked for again in toolbar.rs, with the face's weight recorded nowhere. It is one FontOverride on the display slot now, and setup_fonts reads the family and the source back off it. egui loads faces itself, so this reads the layer rather than emitting a stylesheet from it -- the first renderer to do so. Needs makeover 2.10.0.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-18 13:38 UTC
Signed with PGP, not checked
Commit: 05e780340dc271acc0de7f015d6ab0b7b90f96b0
Parent: 1be81d7
6 files changed, +91 insertions, -10 deletions
M Cargo.lock +2 -2
@@ -3056,9 +3056,9 @@
3056 3056
3057 3057 [[package]]
3058 3058 name = "makeover"
3059 - version = "2.9.0"
3059 + version = "2.10.0"
3060 3060 source = "registry+https://github.com/rust-lang/crates.io-index"
3061 - checksum = "5d18f543dde04d03af163315b215bc73dca1d87af19d6ab62f46aff1bf14f40a"
3061 + checksum = "f456435cb409328226f55f4209234a8dd37afb9ebbb941a7a654e7df385c020a"
3062 3062 dependencies = [
3063 3063 "include_dir",
3064 3064 "serde",
M Cargo.toml +1 -1
@@ -67,7 +67,7 @@
67 67 libc = "0.2"
68 68 midir = "0.11"
69 69 tagtree = { git = "https://makenot.work/git/max/makenotwork.git", version = "0.4" }
70 - makeover = "2.9.0"
70 + makeover = "2.10.0"
71 71 # The invariant half of the design system: relational spacing. makeover
72 72 # resolves colour, which a theme may override; this carries what no theme may.
73 73 makeover-geometry = "0.7"
@@ -10,7 +10,7 @@
10 10
11 11 - **Brand:** "audiofiles/" in Recursive Mono Bold. Short form: "af/"
12 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 - - **Typography:** Recursive Mono Bold for the logo; egui system font for all UI text.
13 + - **Typography:** Recursive Mono Bold for the logo; egui system font for all UI text. The logo face is declared on makeover's override layer, not by hand. See `docs/design-system.md`.
14 14
15 15 ## Features
16 16
@@ -105,6 +105,21 @@
105 105
106 106 **Spacing is not themeable, and this is the one hard rule.** A theme overrides colour; geometry is invariant, which is exactly why it belongs to a shared crate rather than to this app. The old `[spacing]` TOML section is gone, along with the `section_spacing`, `grid_row_spacing`, `item_spacing_x/y` and `button_padding_x/y` keys. Corner radius survives under a `[geometry]` section because the crate carries spacing only.
107 107
108 + ### Typography
109 +
110 + One face is audiofiles' own: Recursive Mono Linear Bold, the "af/" logo. Everything else is egui's system font, and there is no plan for that to change.
111 +
112 + It is declared on `makeover`'s override layer, the same three-tier model MNW and GoingsOn use: a product override, the house defaults, the system generic. The display slot is the brand tier and has no house answer, so overriding it is the whole of what an app has to say about type.
113 +
114 + Typography::house("fonts").with_override(
115 + FontOverride::new(FontSlot::Display, "\"RecursiveMono\", monospace")
116 + .with_face(FontFace::new("RecursiveMono", ["RecursiveMonoLnrSt-Bold.ttf"]).weight("700")),
117 + )
118 +
119 + **The layer is read here rather than emitted.** MNW and GoingsOn declare an override in a build script and it reaches the page as generated CSS. There is no stylesheet anywhere in this path: egui loads faces itself, so `setup_fonts` reads the declaration back through `Typography::faces` and registers the file under the family name the stack spells. That is what `makeover` 2.10.0 added, and audiofiles is the first renderer on the far side of it.
120 +
121 + The gain is that the family name is stated once. `setup_fonts` registers the face under it, `toolbar.rs` asks egui for it back, and neither spells it — a mismatch used to be two string literals agreeing by hand, and it fails silently by falling back to Hack.
122 +
108 123 ### Stroke / radius
109 124
110 125 | Token | Value (default) | Use |
@@ -29,8 +29,44 @@
29 29 /// Recursive Mono Linear Bold, used for the "af/" logo.
30 30 static LOGO_FONT: &[u8] = include_bytes!("../../fonts/RecursiveMonoLnrSt-Bold.ttf");
31 31
32 - /// The egui font family name for the logo font.
33 - pub const LOGO_FONT_FAMILY: &str = "RecursiveMono";
32 + /// The file [`LOGO_FONT`] is compiled from, named again so the declaration
33 + /// below can state which file backs the face. A test asserts the two agree.
34 + const LOGO_FONT_FILE: &str = "RecursiveMonoLnrSt-Bold.ttf";
35 +
36 + /// audiofiles' typography: the house two, plus the brand face over the display
37 + /// slot.
38 + ///
39 + /// The same layer MNW and goingson declare in their build scripts, read the
40 + /// other way round. Those emit a stylesheet, so an override reaches the page
41 + /// as generated CSS; egui loads faces itself and there is no stylesheet
42 + /// anywhere in this path, so the declaration is read at runtime through
43 + /// `Typography::faces` and handed to `ctx.set_fonts`. The point either way is
44 + /// that the family name is stated once: [`setup_fonts`] registers the file
45 + /// under it and the toolbar asks for it back, and neither spells it.
46 + ///
47 + /// The weight is on the face because it is true of the file, not because
48 + /// anything here reads it. Nothing does: this is a single static face, so
49 + /// egui cannot synthesise the second bold that the same omission cost
50 + /// goingson's Reglo in a browser.
51 + static TYPOGRAPHY: LazyLock<makeover::Typography> = LazyLock::new(|| {
52 + makeover::Typography::house("fonts").with_override(
53 + makeover::FontOverride::new(makeover::FontSlot::Display, "\"RecursiveMono\", monospace")
54 + .with_face(makeover::FontFace::new("RecursiveMono", [LOGO_FONT_FILE]).weight("700")),
55 + )
56 + });
57 +
58 + /// The brand face declared over the display slot.
59 + fn logo_face() -> &'static makeover::FontFace {
60 + TYPOGRAPHY
61 + .faces(makeover::FontSlot::Display)
62 + .first()
63 + .expect("the display slot is overridden with exactly one face")
64 + }
65 +
66 + /// The egui font family name for the logo font, off the override layer.
67 + pub fn logo_font_family() -> &'static str {
68 + logo_face().family()
69 + }
34 70
35 71 // --- Spacing and stroke tokens ---
36 72 //
@@ -848,14 +884,15 @@
848 884 /// (e.g. from the eframe `CreationContext` or nih-plug init callback), because
849 885 /// `ctx.set_fonts()` only takes effect on the next frame.
850 886 pub fn setup_fonts(ctx: &egui::Context) {
887 + let family = logo_font_family();
851 888 let mut fonts = egui::FontDefinitions::default();
852 889 fonts.font_data.insert(
853 - LOGO_FONT_FAMILY.to_owned(),
890 + family.to_owned(),
854 891 egui::FontData::from_static(LOGO_FONT).into(),
855 892 );
856 893 fonts.families.insert(
857 - egui::FontFamily::Name(LOGO_FONT_FAMILY.into()),
858 - vec![LOGO_FONT_FAMILY.to_owned(), "Hack".to_owned()],
894 + egui::FontFamily::Name(family.into()),
895 + vec![family.to_owned(), "Hack".to_owned()],
859 896 );
860 897 ctx.set_fonts(fonts);
861 898 }
@@ -1537,6 +1574,35 @@
1537 1574 assert!(export_theme_content(DEFAULT_THEME_ID).is_some());
1538 1575 }
1539 1576
1577 + #[test]
1578 + fn the_declared_face_names_the_file_that_is_actually_embedded() {
1579 + // The override layer is only worth having if it describes the face
1580 + // that ships. `include_bytes!` needs a literal path, so the filename
1581 + // is written twice by necessity; this is what stops the second copy
1582 + // from drifting into a description of a font nobody loads.
1583 + assert!(
1584 + std::path::Path::new(concat!(
1585 + env!("CARGO_MANIFEST_DIR"),
1586 + "/fonts/RecursiveMonoLnrSt-Bold.ttf"
1587 + ))
1588 + .ends_with(LOGO_FONT_FILE)
1589 + );
1590 + assert_eq!(logo_face().sources(), [LOGO_FONT_FILE]);
1591 + assert!(!LOGO_FONT.is_empty());
1592 + }
1593 +
1594 + #[test]
1595 + fn the_stack_the_display_slot_resolves_to_names_the_face_behind_it() {
1596 + // egui registers a family by name and asks for it back by name, so a
1597 + // stack that named something else would leave the face loaded and
1598 + // unreachable.
1599 + let stack = TYPOGRAPHY
1600 + .resolve(makeover::FontSlot::Display)
1601 + .expect("the display slot is overridden");
1602 + assert!(stack.contains(logo_font_family()));
1603 + assert_eq!(logo_font_family(), "RecursiveMono");
1604 + }
1605 +
1540 1606 #[test]
1541 1607 fn bundled_theme_colors_are_not_all_black() {
1542 1608 // Sanity check: a properly parsed theme shouldn't have all-black fields
@@ -405,7 +405,7 @@
405 405 // Logo
406 406 ui.label(
407 407 egui::RichText::new("af/")
408 - .family(egui::FontFamily::Name(theme::LOGO_FONT_FAMILY.into()))
408 + .family(egui::FontFamily::Name(theme::logo_font_family().into()))
409 409 .size(16.0)
410 410 .color(theme::content()),
411 411 )