1#[derive(Clone, Copy, PartialEq, Eq, Debug)]
11pub struct Icon(&'static str);
12
13impl Icon {
14 #[must_use]
20 pub fn inked(self) -> String {
21 if self.0.contains("#ffffff") {
22 self.0.replace("#ffffff", "currentColor")
23 } else {
24 self.0.replacen("<svg ", "<svg fill=\"currentColor\" ", 1)
25 }
26 }
27}
28
29macro_rules! faces {
30 ($($name:ident => $file:literal),+ $(,)?) => {
31 $(pub const $name: Icon = Icon(include_str!(concat!("../../icons/", $file)));)+
32 };
33}
34
35faces! {
36 UNDO => "icon-undo.svg",
37 REDO => "icon-redo.svg",
38 FIT => "icon-fit.svg",
39 LEVEL_UP => "icon-level-up.svg",
40 GEAR => "icon-gear.svg",
41 DIAGRAMS => "icon-diagrams.svg",
42 PARTS => "icon-parts.svg",
43 HISTORY => "icon-history.svg",
44 SUN => "icon-sun.svg",
45 MOON => "icon-moon.svg",
46 MONITOR => "icon-monitor.svg",
47 CHECK => "icon-check.svg",
48 CHEVRON_UP => "icon-chevron-up.svg",
49 EXPORT => "icon-export.svg",
50 IMPORT => "icon-import.svg",
51 ADD => "icon-add.svg",
52 FILE => "icon-file.svg",
53 TRASH => "icon-trash.svg",
54 STEP_OLDER => "icon-step-older.svg",
55 STEP_NEWER => "icon-step-newer.svg",
56 SELECT => "icon-select.svg",
57 NEW_BLOCK => "icon-new-block.svg",
58 AREA => "icon-area.svg",
59 ADD_PORT => "icon-add-port.svg",
60 ADD_PIN => "icon-add-pin.svg",
61 IMAGE => "icon-image.svg",
62 ADD_TEXT => "icon-add-text.svg",
63 ROUTE => "icon-route.svg",
64 MORE => "icon-more.svg",
65 COPY => "icon-copy.svg",
66 CUT => "icon-cut.svg",
67 FLIP_LR => "icon-flip-lr.svg",
68 FLIP_UD => "icon-flip-ud.svg",
69 REROUTE => "icon-reroute.svg",
70 ROUTE_LABEL => "icon-route-label.svg",
71 EXPAND => "icon-expand.svg",
72 LOCK => "icon-lock.svg",
73 UNLOCK => "icon-unlock.svg",
74 ADD_ICON => "icon-add-icon.svg",
75 EYE => "icon-eye.svg",
76 EYE_OFF => "icon-eye-off.svg",
77 PAINT => "icon-paint.svg",
78 PIN_INPUT => "icon-pin-input.svg",
79 PIN_OUTPUT => "icon-pin-output.svg",
80 PIN_INOUT => "icon-pin-inout.svg",
81}
82
83#[must_use]
89pub fn overlay(id: blockworx_tools::commands::CommandId) -> Icon {
90 use blockworx_tools::commands::CommandId;
91 match id {
92 CommandId::Copy => COPY,
93 CommandId::Cut => CUT,
94 CommandId::Delete => TRASH,
95 CommandId::FlipLr => FLIP_LR,
96 CommandId::FlipUd => FLIP_UD,
97 CommandId::Reroute | CommandId::RerouteBlock => REROUTE,
98 CommandId::AddRouteLabel => ROUTE_LABEL,
99 CommandId::ExpandBlock => EXPAND,
100 CommandId::Lock => LOCK,
101 CommandId::Unlock => UNLOCK,
102 CommandId::AddIcon => ADD_ICON,
103 CommandId::HideTags => EYE_OFF,
104 CommandId::ShowTags => EYE,
105 CommandId::PinType => PIN_INPUT,
106 CommandId::ExportSelection(_) => EXPORT,
107 _ => PAINT,
108 }
109}
110
111#[must_use]
114pub fn pin_dir(dir: blockworx_doc::values::PinDir) -> Icon {
115 use blockworx_doc::values::PinDir;
116 match dir {
117 PinDir::Input => PIN_INPUT,
118 PinDir::Output => PIN_OUTPUT,
119 PinDir::InOut => PIN_INOUT,
120 }
121}
122
123#[must_use]
126pub fn tool(tool: blockworx_tools::names::ToolName) -> Icon {
127 use blockworx_tools::names::ToolName;
128 match tool {
129 ToolName::NewBlock => NEW_BLOCK,
130 ToolName::NewArea => AREA,
131 ToolName::AddPort => ADD_PORT,
132 ToolName::AddPin => ADD_PIN,
133 ToolName::NewImage => IMAGE,
134 ToolName::AddText => ADD_TEXT,
135 ToolName::Route => ROUTE,
136 _ => SELECT,
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143
144 #[test]
148 fn a_face_is_re_inked_to_follow_its_button() {
149 let drawn = UNDO.inked();
150 assert!(
151 UNDO.0.contains("#ffffff"),
152 "the bundled art no longer states a stroke to swap",
153 );
154 assert!(drawn.contains("currentColor"));
155 assert!(!drawn.contains("#ffffff"));
156 }
157
158 #[test]
162 fn every_overlay_verb_has_a_face_of_its_own() {
163 use blockworx_tools::commands::{CommandId, ExportFormat, in_overlay};
164 use blockworx_tools::names::ToolName;
165 let every = [
166 CommandId::Accent,
167 CommandId::AddIcon,
168 CommandId::AddRouteLabel,
169 CommandId::Copy,
170 CommandId::Cut,
171 CommandId::Delete,
172 CommandId::ExpandBlock,
173 CommandId::ExportSelection(ExportFormat::Svg),
174 CommandId::FlipLr,
175 CommandId::FlipUd,
176 CommandId::HideTags,
177 CommandId::Lock,
178 CommandId::PinType,
179 CommandId::Reroute,
180 CommandId::RerouteBlock,
181 CommandId::ShowTags,
182 CommandId::Unlock,
183 CommandId::Arm(ToolName::Route),
185 CommandId::Undo,
186 ];
187 let on_the_bar: Vec<CommandId> = every.into_iter().filter(|id| in_overlay(*id)).collect();
188 assert_eq!(
189 on_the_bar.len(),
190 17,
191 "the registry's bar changed shape: {on_the_bar:?}",
192 );
193 for id in on_the_bar {
194 if id == CommandId::Accent {
197 continue;
198 }
199 assert!(
200 overlay(id) != PAINT,
201 "{} fell through to the fallback face",
202 id.name(),
203 );
204 }
205 }
206
207 #[test]
208 fn every_band_tool_has_a_face_of_its_own() {
209 let faces: Vec<Icon> = blockworx_editor::names::band_tools().map(tool).collect();
210 assert_eq!(faces.len(), 8);
211 assert!(
212 faces.iter().filter(|face| **face == SELECT).count() == 1,
213 "a tool fell through to the select cursor: {faces:?}",
214 );
215 }
216}