Save, open and ship the cbz icon set as TOML documents

38cca4c · gmorell · 2026-09-22 03:48

22 files +1078 -9
Message
{commit_body(@commit)}

Files changed

modified Cargo.lock
+46 −0
@@ -1546,7 +1546,9 @@ dependencies = [
1546 1546 "egui_material_icons",
1547 1547 "image",
1548 1548 "rfd",
1549 + "serde",
1549 1550 "tiny-skia",
1551 + "toml",
1550 1552 ]
1551 1553
1552 1554 [[package]]
@@ -2918,6 +2920,15 @@ dependencies = [
2918 2920 "syn 2.0.119",
2919 2921 ]
2920 2922
2923 +[[package]]
2924 +name = "serde_spanned"
2925 +version = "0.6.9"
2926 +source = "registry+https://github.com/rust-lang/crates.io-index"
2927 +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
2928 +dependencies = [
2929 + "serde",
2930 +]
2931 +
2921 2932 [[package]]
2922 2933 name = "sha1"
2923 2934 version = "0.10.7"
@@ -3266,11 +3277,26 @@ dependencies = [
3266 3277 "zerovec",
3267 3278 ]
3268 3279
3280 +[[package]]
3281 +name = "toml"
3282 +version = "0.8.23"
3283 +source = "registry+https://github.com/rust-lang/crates.io-index"
3284 +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
3285 +dependencies = [
3286 + "serde",
3287 + "serde_spanned",
3288 + "toml_datetime 0.6.11",
3289 + "toml_edit 0.22.27",
3290 +]
3291 +
3269 3292 [[package]]
3270 3293 name = "toml_datetime"
3271 3294 version = "0.6.11"
3272 3295 source = "registry+https://github.com/rust-lang/crates.io-index"
3273 3296 checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
3297 +dependencies = [
3298 + "serde",
3299 +]
3274 3300
3275 3301 [[package]]
3276 3302 name = "toml_datetime"
@@ -3292,6 +3318,20 @@ dependencies = [
3292 3318 "winnow 0.5.40",
3293 3319 ]
3294 3320
3321 +[[package]]
3322 +name = "toml_edit"
3323 +version = "0.22.27"
3324 +source = "registry+https://github.com/rust-lang/crates.io-index"
3325 +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
3326 +dependencies = [
3327 + "indexmap",
3328 + "serde",
3329 + "serde_spanned",
3330 + "toml_datetime 0.6.11",
3331 + "toml_write",
3332 + "winnow 0.7.15",
3333 +]
3334 +
3295 3335 [[package]]
3296 3336 name = "toml_edit"
3297 3337 version = "0.25.15+spec-1.1.0"
@@ -3313,6 +3353,12 @@ dependencies = [
3313 3353 "winnow 1.0.4",
3314 3354 ]
3315 3355
3356 +[[package]]
3357 +name = "toml_write"
3358 +version = "0.1.2"
3359 +source = "registry+https://github.com/rust-lang/crates.io-index"
3360 +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
3361 +
3316 3362 [[package]]
3317 3363 name = "tracing"
3318 3364 version = "0.1.44"
modified Cargo.toml
+2 −0
@@ -19,6 +19,8 @@ egui_material_icons = "0.4"
19 19 tiny-skia = "0.11"
20 20 ab_glyph = "0.2"
21 21 image = { version = "0.25", default-features = false, features = ["png"] }
22 +serde = { version = "1", features = ["derive"] }
23 +toml = "0.8"
22 24 rfd = { version = "0.12", default-features = false, features = ["xdg-portal"] }
23 25
24 26 [profile.release]
modified README.md
+47 −0
@@ -39,6 +39,52 @@ fails rather than quietly drifting.
39 39 The invented parts are reasonable, not recovered. Treat them as a starting
40 40 point rather than as the tool you remember.
41 41
42 +## Documents
43 +
44 +Configurations save as TOML, so they are readable and hand-editable:
45 +
46 +```toml
47 +version = 1
48 +palette = "Casabeza"
49 +
50 +[foreground]
51 +kind = "icon"
52 +codepoint = "e899"
53 +name = "lock" # informational; the codepoint wins
54 +
55 +[colors.background_a]
56 +role = "primary"
57 +rgb = [0, 201, 212]
58 +
59 +[background]
60 +style = "diagonal-split"
61 +angle = 45.0
62 +```
63 +
64 +Colours carry **both** a palette role and literal RGB. The role wins where the
65 +named palette still defines it, so retuning a theme in dms updates every
66 +document that references it; the RGB is the fallback that keeps old documents
67 +openable when a role is gone. A document from a newer `version` is refused
68 +rather than half-read, and an unknown `background.style` is an error rather
69 +than a silent default — but an unknown *grid* preference is dropped, since
70 +that should not block opening a file.
71 +
72 +New / Open / Save / Save as are on the left panel, with `Ctrl+N`, `Ctrl+O`,
73 +`Ctrl+S` and `Ctrl+Shift+S`. The title bar shows the filename and a bullet for
74 +unsaved changes, and New/Open prompt before discarding them.
75 +
76 +### The cbz icon set
77 +
78 +`presets/` holds a document per shipped app — `cbz-auth`, `cbz-lists`,
79 +`cbz-numerics`, `cbz-scanner`, `cbz-inventory` — so each icon opens as an
80 +editable configuration instead of having to be rebuilt by hand.
81 +
82 +These are reconstructions. The palette, the diagonal split and its orientation
83 +are measured from the shipped PNGs, and the scales are taken from each one's
84 +ink extents; the *glyph identifications* are mine by eye, so `cbz-scanner`
85 +being `satellite_alt` is a judgement, not a recovered fact. A test loads all
86 +five and asserts they still reproduce the shipped background orientation.
87 +
42 88 ## Palettes
43 89
44 90 Nothing is retyped by hand. `tools/regen_palettes.py` writes
@@ -77,6 +123,7 @@ cargo run # the studio
77 123 cargo test # including the comparison against shipped icons
78 124 cargo run --example reproduce_cbz # re-render the five cbz icons
79 125 cargo run --example showcase # every background generator
126 +cargo run --example make_presets # regenerate presets/
80 127 ```
81 128
82 129 Export writes `ic_icon`, `ic_icon_background` and `ic_icon_foreground` across
added examples/make_presets.rs
+57 −0
@@ -0,0 +1,57 @@
1 +//! Writes the cbz icon set out as Icon Studio documents.
2 +//!
3 +//! These are reconstructions: the background, palette and text mark are taken
4 +//! from the shipped icons, and the glyph names are identified by eye from the
5 +//! same images. Opening one gives you the icon back as an editable document.
6 +
7 +use icon_studio::doc::{self, Session};
8 +use icon_studio::palette::NamedColor;
9 +use icon_studio::spec::{Background, Foreground, IconSpec};
10 +
11 +fn main() {
12 + let teal = NamedColor { role: "primary", rgb: [0x00, 0xC9, 0xD4] };
13 + let green = NamedColor { role: "accent", rgb: [0xAA, 0xD4, 0x00] };
14 + let ink = NamedColor { role: "base-200", rgb: [0x0D, 0x2A, 0x2C] };
15 +
16 + // (file stem, foreground, glyph name for the file comment, scale measured
17 + // from the shipped foreground's ink extents)
18 + let apps: &[(&str, Foreground, f32)] = &[
19 + ("cbz-auth", Foreground::Icon('\u{e899}'), 0.54), // lock
20 + ("cbz-lists", Foreground::Icon('\u{e85d}'), 0.60), // assignment
21 + ("cbz-numerics", Foreground::Text("1234".into()), 0.62),
22 + ("cbz-scanner", Foreground::Icon('\u{eb3a}'), 0.62), // satellite_alt
23 + ("cbz-inventory", Foreground::Icon('\u{e1a1}'), 0.70), // inventory_2
24 + ];
25 +
26 + for (stem, fg, scale) in apps {
27 + let session = Session {
28 + spec: IconSpec {
29 + foreground: fg.clone(),
30 + sub_banner: String::new(),
31 + fg_color: ink,
32 + bg_a: teal,
33 + bg_b: green,
34 + background: Background::DiagonalSplit,
35 + angle: 45.0,
36 + scale: *scale,
37 + show_guides: false,
38 + filled: false,
39 + },
40 + palette: "Casabeza".into(),
41 + export_name: "ic_icon".into(),
42 + export_dir: String::new(),
43 + grid_masked: false,
44 + grid_colors: vec!["primary".into(), "accent".into(), "base-200".into()],
45 + grid_backgrounds: vec![Background::Solid, Background::DiagonalSplit],
46 + };
47 +
48 + let names = icon_studio::icons::all();
49 + let document = session.to_document(|ch| {
50 + names.iter().filter(|e| e.ch == ch).min_by_key(|e| (e.name.len(), e.name)).map(|e| e.name.to_string()).unwrap_or_default()
51 + });
52 +
53 + let path = std::path::Path::new("presets").join(format!("{stem}.toml"));
54 + doc::save(&path, &document).unwrap();
55 + println!("{}", path.display());
56 + }
57 +}
added presets/cbz-auth.toml
+44 −0
@@ -0,0 +1,44 @@
1 +# Icon Studio document. Hand-editing is fine.
2 +#
3 +# Colours carry both a palette role and literal RGB: the role wins if the
4 +# named palette still defines it, so retuning a theme updates documents
5 +# that reference it; the RGB is the fallback when it does not.
6 +
7 +version = 1
8 +palette = "Casabeza"
9 +sub_banner = ""
10 +show_guides = false
11 +
12 +[foreground]
13 +kind = "icon"
14 +codepoint = "e899"
15 +name = "lock"
16 +
17 +[colors.foreground]
18 +role = "base-200"
19 +rgb = [13, 42, 44]
20 +
21 +[colors.background_a]
22 +role = "primary"
23 +rgb = [0, 201, 212]
24 +
25 +[colors.background_b]
26 +role = "accent"
27 +rgb = [170, 212, 0]
28 +
29 +[background]
30 +style = "diagonal-split"
31 +angle = 45.0
32 +
33 +[mark]
34 +scale = 0.54
35 +filled = false
36 +
37 +[export]
38 +name = "ic_icon"
39 +directory = ""
40 +
41 +[grid]
42 +masked = false
43 +colors = ["primary", "accent", "base-200"]
44 +backgrounds = ["solid", "diagonal-split"]
added presets/cbz-inventory.toml
+44 −0

Click to load diff…

added presets/cbz-lists.toml
+44 −0

Click to load diff…

added presets/cbz-numerics.toml
+43 −0

Click to load diff…

added presets/cbz-scanner.toml
+44 −0

Click to load diff…

modified src/app.rs
+288 −0

Click to load diff…

added src/doc.rs
+273 −0

Click to load diff…

modified src/lib.rs
+1 −0

Click to load diff…

modified src/palette/mod.rs
+1 −1

Click to load diff…

modified src/render.rs
+3 −5

Click to load diff…

modified src/spec.rs
+23 −2

Click to load diff…

added tests/documents.rs
+117 −0

Click to load diff…

modified tests/reproduce.rs
+1 −1

Click to load diff…

Parents: e745ef6