1#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
2pub enum ToolName {
3 NewBlock,
4 NewArea,
5 AddPort,
6 NewImage,
7 Icon,
8 AddText,
9 EditTextBox,
10 MovePin,
11 RenamePin,
12 RetypePin,
13 MoveTitle,
14 MoveBlockType,
15 RenameTitle,
16 RenameBlockType,
17 Route,
18 MoveBlock,
19 ResizeBlock,
20 EditRoute,
21 AddRouteLabel,
22 RenameRoute,
23 MoveLabel,
24 Select,
25 SelectPin,
26 MultiSelect,
27 MultiPinSelect,
28 MoveMultiPin,
29}
30
31impl ToolName {
32 pub fn label(self) -> &'static str {
33 match self {
34 ToolName::Select => "Select",
35 ToolName::SelectPin => "Select Pin",
36 ToolName::MultiSelect => "Multi Select",
37 ToolName::MultiPinSelect => "Multi Pin Select",
38 ToolName::MoveMultiPin => "Move Multi Pin",
39 ToolName::NewBlock => "New Block",
40 ToolName::NewArea => "Area",
41 ToolName::AddPort => "Add Port",
42 ToolName::NewImage => "Add Image",
43 ToolName::Icon => "Add Icon",
44 ToolName::AddText => "Add Text",
45 ToolName::EditTextBox => "Edit Text",
46 ToolName::MovePin => "Move Pin",
47 ToolName::RenamePin => "Rename Pin",
48 ToolName::RetypePin => "Retype Pin",
49 ToolName::MoveTitle => "Move Title",
50 ToolName::MoveBlockType => "Move Block Type",
51 ToolName::RenameTitle => "Rename Title",
52 ToolName::RenameBlockType => "Rename Block Type",
53 ToolName::Route => "Route",
54 ToolName::MoveBlock => "Move Block",
55 ToolName::ResizeBlock => "Resize Block",
56 ToolName::EditRoute => "Edit Route",
57 ToolName::AddRouteLabel => "Add Route Label",
58 ToolName::RenameRoute => "Rename Route",
59 ToolName::MoveLabel => "Move Label",
60 }
61 }
62
63 pub fn command_name(self) -> Option<&'static str> {
67 Some(match self {
68 ToolName::Select => "select",
69 ToolName::NewBlock => "new-block",
70 ToolName::NewArea => "area",
71 ToolName::AddPort => "add-port",
72 ToolName::NewImage => "add-image",
73 ToolName::AddText => "add-text",
74 ToolName::Route => "route",
75 _ => return None,
76 })
77 }
78
79 pub fn verb(self) -> &'static str {
87 match self {
88 ToolName::Select
89 | ToolName::SelectPin
90 | ToolName::MultiSelect
91 | ToolName::MultiPinSelect
92 | ToolName::EditTextBox => "Edit",
93 ToolName::NewBlock
94 | ToolName::NewArea
95 | ToolName::AddPort
96 | ToolName::NewImage
97 | ToolName::Icon
98 | ToolName::AddText
99 | ToolName::AddRouteLabel => "Add",
100 ToolName::MovePin
101 | ToolName::MoveTitle
102 | ToolName::MoveBlockType
103 | ToolName::MoveBlock
104 | ToolName::MoveLabel
105 | ToolName::MoveMultiPin => "Move",
106 ToolName::RenamePin | ToolName::RenameTitle | ToolName::RenameRoute => "Rename",
107 ToolName::RetypePin | ToolName::RenameBlockType => "Retype",
108 ToolName::Route => "Create",
109 ToolName::ResizeBlock => "Resize",
110 ToolName::EditRoute => "Modify",
111 }
112 }
113
114 pub fn arming_writes_the_document(self) -> bool {
127 match self {
128 ToolName::NewBlock
129 | ToolName::NewArea
130 | ToolName::AddPort
131 | ToolName::NewImage
132 | ToolName::Icon
133 | ToolName::AddText
134 | ToolName::EditTextBox
135 | ToolName::MovePin
136 | ToolName::RenamePin
137 | ToolName::RetypePin
138 | ToolName::MoveTitle
139 | ToolName::MoveBlockType
140 | ToolName::RenameTitle
141 | ToolName::RenameBlockType
142 | ToolName::Route
143 | ToolName::MoveBlock
144 | ToolName::AddRouteLabel
145 | ToolName::RenameRoute
146 | ToolName::MoveLabel
147 | ToolName::MoveMultiPin => true,
148 ToolName::Select
149 | ToolName::SelectPin
150 | ToolName::MultiSelect
151 | ToolName::MultiPinSelect
152 | ToolName::ResizeBlock
153 | ToolName::EditRoute => false,
154 }
155 }
156}
157
158impl std::fmt::Display for ToolName {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_str(self.label())
161 }
162}
163
164pub struct BandTool {
168 pub tool: ToolName,
169 pub instruction: Option<&'static str>,
172}
173
174pub const BAND_TOOLS: &[BandTool] = &[
178 BandTool {
179 tool: ToolName::Select,
180 instruction: None,
181 },
182 BandTool {
183 tool: ToolName::NewBlock,
184 instruction: Some("Click one corner of the block, then the other"),
185 },
186 BandTool {
187 tool: ToolName::NewArea,
188 instruction: Some("Drag on the canvas to draw an area"),
189 },
190 BandTool {
191 tool: ToolName::AddPort,
192 instruction: Some("Drag along the level's boundary to add a port"),
193 },
194 BandTool {
195 tool: ToolName::NewImage,
196 instruction: Some("Drag on the canvas to choose an image and place it"),
197 },
198 BandTool {
199 tool: ToolName::AddText,
200 instruction: Some("Drag to the corner the text should start at"),
201 },
202 BandTool {
203 tool: ToolName::Route,
204 instruction: Some("Click a pin or port, then the one to route it to"),
205 },
206];
207
208pub fn instruction(tool: ToolName) -> Option<&'static str> {
212 BAND_TOOLS
213 .iter()
214 .find(|band| band.tool == tool)
215 .and_then(|band| band.instruction)
216}
217
218pub fn band_tools() -> impl ExactSizeIterator<Item = ToolName> + Clone {
221 BAND_TOOLS.iter().map(|band| band.tool)
222}
223
224#[cfg(test)]
226pub(crate) fn on_the_band(tool: ToolName) -> bool {
227 band_tools().any(|band| band == tool)
228}
229
230pub fn displayed_tool(active: ToolName) -> ToolName {
237 match active {
238 ToolName::Select
239 | ToolName::NewBlock
240 | ToolName::NewArea
241 | ToolName::AddPort
242 | ToolName::NewImage
243 | ToolName::AddText
244 | ToolName::Route => active,
245 ToolName::Icon
248 | ToolName::AddRouteLabel
249 | ToolName::EditRoute
250 | ToolName::RenameRoute
251 | ToolName::EditTextBox
252 | ToolName::MovePin
253 | ToolName::RenamePin
254 | ToolName::RetypePin
255 | ToolName::MoveTitle
256 | ToolName::MoveBlockType
257 | ToolName::RenameTitle
258 | ToolName::RenameBlockType
259 | ToolName::MoveBlock
260 | ToolName::ResizeBlock
261 | ToolName::MoveLabel
262 | ToolName::SelectPin
263 | ToolName::MultiSelect
264 | ToolName::MultiPinSelect
265 | ToolName::MoveMultiPin => ToolName::Select,
266 }
267}
268
269#[cfg(test)]
273pub(crate) const ALL_TOOLS: [ToolName; 26] = [
274 ToolName::NewBlock,
275 ToolName::NewArea,
276 ToolName::AddPort,
277 ToolName::NewImage,
278 ToolName::Icon,
279 ToolName::AddText,
280 ToolName::EditTextBox,
281 ToolName::MovePin,
282 ToolName::RenamePin,
283 ToolName::RetypePin,
284 ToolName::MoveTitle,
285 ToolName::MoveBlockType,
286 ToolName::RenameTitle,
287 ToolName::RenameBlockType,
288 ToolName::Route,
289 ToolName::MoveBlock,
290 ToolName::ResizeBlock,
291 ToolName::EditRoute,
292 ToolName::AddRouteLabel,
293 ToolName::RenameRoute,
294 ToolName::MoveLabel,
295 ToolName::Select,
296 ToolName::SelectPin,
297 ToolName::MultiSelect,
298 ToolName::MultiPinSelect,
299 ToolName::MoveMultiPin,
300];
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[test]
307 fn base_band_tools_map_to_themselves() {
308 for tool in band_tools() {
309 assert_eq!(displayed_tool(tool), tool);
310 }
311 }
312
313 #[test]
314 fn selection_microtools_map_to_select() {
315 for tool in [
316 ToolName::SelectPin,
317 ToolName::MultiSelect,
318 ToolName::ResizeBlock,
319 ToolName::RenameTitle,
320 ] {
321 assert_eq!(displayed_tool(tool), ToolName::Select);
322 }
323 }
324
325 #[test]
326 fn route_editing_maps_to_select() {
327 assert_eq!(displayed_tool(ToolName::EditRoute), ToolName::Select);
328 assert_eq!(displayed_tool(ToolName::RenameRoute), ToolName::Select);
329 assert_eq!(displayed_tool(ToolName::Route), ToolName::Route);
330 }
331
332 #[test]
333 fn overlay_armed_tools_map_to_select() {
334 assert_eq!(displayed_tool(ToolName::Icon), ToolName::Select);
337 assert_eq!(displayed_tool(ToolName::AddRouteLabel), ToolName::Select);
338 assert!(!on_the_band(ToolName::Icon));
339 assert!(!on_the_band(ToolName::AddRouteLabel));
340 }
341
342 #[test]
343 fn displayed_tool_is_always_a_band_tool() {
344 for tool in ALL_TOOLS {
345 assert!(on_the_band(displayed_tool(tool)));
346 }
347 }
348
349 #[test]
353 fn every_tool_on_the_band_has_a_spelling() {
354 for tool in ALL_TOOLS {
355 assert!(
356 tool.command_name().is_some() || !band_tools().any(|band| band == tool),
357 "{tool:?} is on the band with no spelling to be named by",
358 );
359 }
360 }
361
362 #[test]
365 fn every_verb_is_one_word() {
366 for tool in ALL_TOOLS {
367 let verb = tool.verb();
368 assert_eq!(
369 verb.split_whitespace().count(),
370 1,
371 "{tool:?} opens gestures under {verb:?}",
372 );
373 }
374 }
375}