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    "heart",
54    "house",
55    "info.circle",
56    "keyboard",
57    "line.3.horizontal.decrease",
58    "link",
59    "list.bullet.rectangle",
60    "magnifyingglass",
61    "minus",
62    "moon",
63    "music.note",
64    "note.text",
65    "paintbrush",
66    "paintpalette",
67    "paperplane",
68    "pause",
69    "pause.fill",
70    "person.crop.circle",
71    "photo",
72    "pip.enter",
73    "play",
74    "play.fill",
75    "play.rectangle",
76    "plus",
77    "power",
78    "puzzlepiece.extension",
79    "scissors",
80    "server.rack",
81    "shield",
82    "sidebar.right",
83    "slider.horizontal.3",
84    "sparkles",
85    "speaker.fill",
86    "speaker.slash.fill",
87    "speaker.wave.2",
88    "speaker.wave.3.fill",
89    "speedometer",
90    "square",
91    "square.and.arrow.up",
92    "square.grid.2x2",
93    "square.grid.3x3",
94    "square.on.square",
95    "star",
96    "star.fill",
97    "star.slash",
98    "sun.max.fill",
99    "sun.min",
100    "switch.2",
101    "text.bubble",
102    "text.rectangle",
103    "textformat",
104    "textformat.size",
105    "textformat.size.larger",
106    "textformat.size.smaller",
107    "trash",
108    "tray",
109    "tv",
110    "viewfinder",
111    "wifi",
112    "xmark",
113    "xmark.circle.fill",
114];
115
116/// An error at each element whose icon `has` rejects; `set` names the
117/// target's icons in the message, e.g. "the Web icon set".
118pub fn check(app: &Application, set: &str, has: impl Fn(&str) -> bool) -> Vec<Diagnostic> {
119    let mut diagnostics = Vec::new();
120    app.walk(&mut |node: &Node, _| {
121        if let Node::Element(element) = node {
122            for (name, prop) in &element.props {
123                if let Prop::Icon { name: icon } = prop
124                    && !has(icon)
125                {
126                    diagnostics.push(
127                        Diagnostic::error(format!(
128                            "{set} has no `{icon}` (property `{name}` of `{}`)",
129                            element.kind
130                        ))
131                        .at(element.source.clone()),
132                    );
133                }
134            }
135        }
136    });
137    diagnostics
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn the_names_are_sorted_and_in_sf_symbols_naming() {
146        assert!(NAMES.windows(2).all(|pair| pair[0] < pair[1]));
147        for name in NAMES {
148            assert!(
149                name.chars()
150                    .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '.'),
151                "{name}"
152            );
153        }
154    }
155}