Skip to main content

bern_target_api/
icon_set.rs

1//! The Bern icon set: icon names in SF Symbols naming. The Apple targets
2//! draw every SF Symbol; the other targets map each name to one of their
3//! platform's own icons, and every one of them draws at least the names
4//! listed here. A name a target cannot draw fails its check at the
5//! element, so no target shows a placeholder in its place.
6
7use crate::Diagnostic;
8use bern_ir::{Application, Node, Prop};
9
10/// The names every target draws, sorted.
11pub const NAMES: &[&str] = &[
12    "archivebox",
13    "arrow.down",
14    "arrow.down.circle",
15    "arrow.down.right.and.arrow.up.left",
16    "arrow.left",
17    "arrow.triangle.2.circlepath",
18    "arrow.triangle.branch",
19    "arrow.up.arrow.down",
20    "arrow.up.left.and.arrow.down.right",
21    "arrow.up.right.square",
22    "backward.end.fill",
23    "calendar",
24    "camera",
25    "captions.bubble",
26    "character.cursor.ibeam",
27    "checkmark",
28    "checkmark.circle",
29    "chevron.down",
30    "chevron.left",
31    "chevron.right",
32    "chevron.up",
33    "chevron.up.chevron.down",
34    "circle",
35    "circle.fill",
36    "clock.arrow.circlepath",
37    "doc",
38    "dot.radiowaves.left.and.right",
39    "ellipsis",
40    "ellipsis.circle",
41    "exclamationmark.triangle",
42    "eye.slash",
43    "film",
44    "film.stack",
45    "folder",
46    "forward.end.fill",
47    "gearshape",
48    "gearshape.2",
49    "globe",
50    "gobackward.10",
51    "goforward.10",
52    "hand.tap",
53    "headphones",
54    "heart",
55    "house",
56    "info.circle",
57    "keyboard",
58    "line.3.horizontal.decrease",
59    "link",
60    "list.bullet.rectangle",
61    "magnifyingglass",
62    "minus",
63    "moon",
64    "music.note",
65    "note.text",
66    "paintbrush",
67    "paintpalette",
68    "paperplane",
69    "pause",
70    "pause.fill",
71    "person.crop.circle",
72    "photo",
73    "pip.enter",
74    "play",
75    "play.fill",
76    "play.rectangle",
77    "plus",
78    "power",
79    "puzzlepiece.extension",
80    "scissors",
81    "server.rack",
82    "shield",
83    "sidebar.right",
84    "slider.horizontal.3",
85    "sparkles",
86    "speaker.fill",
87    "speaker.slash.fill",
88    "speaker.wave.2",
89    "speaker.wave.3.fill",
90    "speedometer",
91    "square",
92    "square.and.arrow.up",
93    "square.grid.2x2",
94    "square.grid.3x3",
95    "square.on.square",
96    "star",
97    "star.fill",
98    "star.slash",
99    "sun.max.fill",
100    "sun.min",
101    "switch.2",
102    "text.bubble",
103    "text.rectangle",
104    "textformat",
105    "textformat.size",
106    "textformat.size.larger",
107    "textformat.size.smaller",
108    "trash",
109    "tray",
110    "tv",
111    "viewfinder",
112    "wifi",
113    "xmark",
114    "xmark.circle.fill",
115];
116
117/// An error at each element whose icon `has` rejects; `set` names the
118/// target's icons in the message, e.g. "the Web icon set".
119pub fn check(app: &Application, set: &str, has: impl Fn(&str) -> bool) -> Vec<Diagnostic> {
120    let mut diagnostics = Vec::new();
121    app.walk(&mut |node: &Node, _| {
122        if let Node::Element(element) = node {
123            for (name, prop) in &element.props {
124                if let Prop::Icon { name: icon } = prop
125                    && !has(icon)
126                {
127                    diagnostics.push(
128                        Diagnostic::error(format!(
129                            "{set} has no `{icon}` (property `{name}` of `{}`)",
130                            element.kind
131                        ))
132                        .at(element.source.clone()),
133                    );
134                }
135            }
136        }
137    });
138    diagnostics
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn the_names_are_sorted_and_in_sf_symbols_naming() {
147        assert!(NAMES.windows(2).all(|pair| pair[0] < pair[1]));
148        for name in NAMES {
149            assert!(
150                name.chars()
151                    .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '.'),
152                "{name}"
153            );
154        }
155    }
156}