neiam
/icon-studio
public · Issues · Pulls · Labels · Forks · Compare · Actions failure · Badges · Packages
Log in to mark this repository.
Save, open and ship the cbz icon set as TOML documents
38cca4c · gmorell · 2026-09-22 03:48
Message
{commit_body(@commit)}
Files changed
modified
Cargo.lock
+46
−0
| 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 | 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 | 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 | 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 | 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 | 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 | 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 | 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
| 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
| 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