brep_app/icons.rs
1//! The inline-icon catalog: every `assets/glyphs/*.svg` as SVG source, keyed by
2//! the character it draws.
3//!
4//! This is the lookup half of the SVG-icon feature; [`crate::icon_text`] is the
5//! drawing half. The table itself is generated by `build.rs` at compile time
6//! from the glyph SVGs, so an icon is catalogued by existing, with no list here
7//! to fall out of step. See `build.rs` for the two transforms it applies (the
8//! `#111` ink sentinel, and the viewBox-derived aspect ratio).
9//!
10//! Why both a font and an SVG catalog: the font is what egui lays out inside
11//! ordinary text, and it stays the fallback for every glyph that is drawn as
12//! text (toolbar buttons, `ui.label`s not yet converted). The catalog is what
13//! lets the same character be drawn as a real image — which is what makes
14//! genuine multi-colour icons possible, since a TrueType glyph can only be one
15//! colour. The stacked private-use layers in
16//! [`crate::panels::toolbar_button`] exist only to work around that limit.
17//!
18//! Three icons already take that route — the bug (U+1F41E), the book
19//! (U+1F4DA) and the pencil (U+270D). Each was composed from the very layers
20//! `toolbar_button` stacks by hand, in the same palette, so they are the same
21//! artwork expressed once instead of assembled at paint time. Their glyph SVGs
22//! mark the ink layer `brep:font-outline="1"`, so the TTF is byte-for-byte what
23//! it was and a text use of those characters still renders exactly as before —
24//! the colour is additive, visible only through this catalog.
25
26// The generated `pub static ICONS: &[Icon]`, sorted by codepoint.
27include!(concat!(env!("OUT_DIR"), "/icon_catalog.rs"));
28
29/// One catalogued icon: the SVG source for a character, plus what the renderer
30/// needs to place and colour it.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct Icon {
33 /// The character this icon draws — what a string must contain to select it.
34 pub ch: char,
35 /// PostScript glyph name (e.g. `icon_2699`), for debugging and docs.
36 pub name: &'static str,
37 /// A stable, per-icon `bytes://…svg` URI. egui caches the decoded texture
38 /// under it, so it must be constant across frames — and it must end in
39 /// `.svg`, which is how `egui_extras`' loader recognises the format.
40 pub uri: &'static str,
41 /// The SVG source, with `mono` artwork already rewritten to white.
42 pub svg: &'static str,
43 /// width / height of the viewBox — the proportion the font draws it at.
44 pub aspect: f32,
45 /// SVG cropped to painted bounds, for standalone artwork in icon tiles.
46 pub artwork_svg: &'static str,
47 /// Aspect ratio of the painted artwork, excluding canvas padding.
48 pub artwork_aspect: f32,
49 /// Monochrome artwork that should take the colour of the surrounding text.
50 /// `false` means the SVG carries its own colours and must not be tinted.
51 pub mono: bool,
52}
53
54/// The icon for `ch`, or `None` if the catalog has none — in which case the
55/// character stays ordinary text and the font draws it.
56pub fn lookup(ch: char) -> Option<&'static Icon> {
57 let i = ICONS.binary_search_by_key(&ch, |icon| icon.ch).ok()?;
58 Some(&ICONS[i])
59}
60
61/// Whether `ch` has an icon. Cheaper to read at a call site than `lookup().is_some()`.
62pub fn has(ch: char) -> bool {
63 lookup(ch).is_some()
64}
65
66/// The catalogued artwork for a label that is exactly one character.
67///
68/// This is the test every icon-drawing site makes — toolbar buttons, tree rows,
69/// palette rows — so they all agree on what is an icon. `None` for a
70/// multi-character label (a composite, not an icon) and for an uncatalogued
71/// character; both are drawn as ordinary text.
72///
73/// MONOCHROME artwork is included. It used to be excluded, on the grounds that
74/// the icon font already drew it in the live text colour for free — but that
75/// exclusion was the last thing keeping a font in the binary. Callers tint a
76/// `mono` icon to their text colour (it is white in the catalog, so a multiply
77/// lands it exactly) and leave a colour one alone.
78pub fn artwork(glyph: &str) -> Option<&'static Icon> {
79 let mut chars = glyph.chars();
80 let icon = lookup(chars.next()?)?;
81 chars.next().is_none().then_some(icon)
82}
83
84// BREP private tests: 4d18c45d7a290280