1#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
2pub enum ToolName {
3 NewBlock,
4 NewArea,
5 AddPin,
6 AddPort,
7 NewImage,
11 Icon,
14 AddText,
15 EditTextBox,
16 MovePin,
17 RenamePin,
18 RetypePin,
19 MoveTitle,
20 MoveBlockType,
21 RenameTitle,
22 RenameBlockType,
23 Route,
24 MoveBlock,
25 ResizeBlock,
26 EditRoute,
27 AddRouteLabel,
28 RenameRoute,
29 MoveLabel,
30 Select,
31 SelectPin,
32 MultiSelect,
33 MultiPinSelect,
34 MoveMultiPin,
35}
36
37impl ToolName {
38 pub fn label(self) -> &'static str {
39 match self {
40 ToolName::Select => "Select",
41 ToolName::SelectPin => "Select Pin",
42 ToolName::MultiSelect => "Multi Select",
43 ToolName::MultiPinSelect => "Multi Pin Select",
44 ToolName::MoveMultiPin => "Move Multi Pin",
45 ToolName::NewBlock => "New Block",
46 ToolName::NewArea => "Area",
47 ToolName::AddPin => "Add Pin",
48 ToolName::AddPort => "Add Port",
49 ToolName::NewImage => "Add Image",
50 ToolName::Icon => "Add Icon",
51 ToolName::AddText => "Add Text",
52 ToolName::EditTextBox => "Edit Text",
53 ToolName::MovePin => "Move Pin",
54 ToolName::RenamePin => "Rename Pin",
55 ToolName::RetypePin => "Retype Pin",
56 ToolName::MoveTitle => "Move Title",
57 ToolName::MoveBlockType => "Move Block Type",
58 ToolName::RenameTitle => "Rename Title",
59 ToolName::RenameBlockType => "Rename Block Type",
60 ToolName::Route => "Route",
61 ToolName::MoveBlock => "Move Block",
62 ToolName::ResizeBlock => "Resize Block",
63 ToolName::EditRoute => "Edit Route",
64 ToolName::AddRouteLabel => "Add Route Label",
65 ToolName::RenameRoute => "Rename Route",
66 ToolName::MoveLabel => "Move Label",
67 }
68 }
69
70 pub fn command_name(self) -> Option<&'static str> {
74 Some(match self {
75 ToolName::Select => "select",
76 ToolName::NewBlock => "new-block",
77 ToolName::NewArea => "area",
78 ToolName::AddPin => "add-pin",
79 ToolName::AddPort => "add-port",
80 ToolName::AddText => "add-text",
81 ToolName::Route => "route",
82 _ => return None,
83 })
84 }
85
86 pub fn verb(self) -> &'static str {
94 match self {
95 ToolName::Select
96 | ToolName::SelectPin
97 | ToolName::MultiSelect
98 | ToolName::MultiPinSelect
99 | ToolName::EditTextBox => "Edit",
100 ToolName::NewBlock
101 | ToolName::NewArea
102 | ToolName::AddPin
103 | ToolName::AddPort
104 | ToolName::NewImage
105 | ToolName::Icon
106 | ToolName::AddText
107 | ToolName::AddRouteLabel => "Add",
108 ToolName::MovePin
109 | ToolName::MoveTitle
110 | ToolName::MoveBlockType
111 | ToolName::MoveBlock
112 | ToolName::MoveLabel
113 | ToolName::MoveMultiPin => "Move",
114 ToolName::RenamePin | ToolName::RenameTitle | ToolName::RenameRoute => "Rename",
115 ToolName::RetypePin | ToolName::RenameBlockType => "Retype",
116 ToolName::Route => "Create",
117 ToolName::ResizeBlock => "Resize",
118 ToolName::EditRoute => "Modify",
119 }
120 }
121
122 pub fn arming_writes_the_document(self) -> bool {
135 match self {
136 ToolName::NewBlock
137 | ToolName::NewArea
138 | ToolName::AddPin
139 | ToolName::AddPort
140 | ToolName::NewImage
141 | ToolName::Icon
142 | ToolName::AddText
143 | ToolName::EditTextBox
144 | ToolName::MovePin
145 | ToolName::RenamePin
146 | ToolName::RetypePin
147 | ToolName::MoveTitle
148 | ToolName::MoveBlockType
149 | ToolName::RenameTitle
150 | ToolName::RenameBlockType
151 | ToolName::Route
152 | ToolName::MoveBlock
153 | ToolName::AddRouteLabel
154 | ToolName::RenameRoute
155 | ToolName::MoveLabel
156 | ToolName::MoveMultiPin => true,
157 ToolName::Select
158 | ToolName::SelectPin
159 | ToolName::MultiSelect
160 | ToolName::MultiPinSelect
161 | ToolName::ResizeBlock
162 | ToolName::EditRoute => false,
163 }
164 }
165}
166
167impl std::fmt::Display for ToolName {
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 f.write_str(self.label())
170 }
171}
172
173#[derive(Clone, Copy, PartialEq, Eq, Debug)]
177pub enum ToolGroup {
178 Selection,
180 Block,
182 Sheet,
185}
186
187pub struct BandTool {
191 pub tool: ToolName,
192 pub group: ToolGroup,
193 pub instruction: Option<&'static str>,
196}
197
198pub const BAND_TOOLS: &[BandTool] = &[
202 BandTool {
203 tool: ToolName::Select,
204 group: ToolGroup::Selection,
205 instruction: None,
206 },
207 BandTool {
208 tool: ToolName::NewBlock,
209 group: ToolGroup::Block,
210 instruction: Some("Click one corner of the block, then the other"),
211 },
212 BandTool {
213 tool: ToolName::AddPin,
214 group: ToolGroup::Block,
215 instruction: Some("Click a point beside a block to add a pin there"),
216 },
217 BandTool {
218 tool: ToolName::Route,
219 group: ToolGroup::Block,
220 instruction: Some("Click a pin or port, then the one to route it to"),
221 },
222 BandTool {
223 tool: ToolName::AddPort,
224 group: ToolGroup::Sheet,
225 instruction: Some("Click on the canvas to add a port"),
226 },
227 BandTool {
228 tool: ToolName::AddText,
229 group: ToolGroup::Sheet,
230 instruction: Some("Click where the text should start"),
231 },
232 BandTool {
233 tool: ToolName::NewArea,
234 group: ToolGroup::Sheet,
235 instruction: Some("Drag on the canvas to draw an area"),
236 },
237 BandTool {
240 tool: ToolName::NewImage,
241 group: ToolGroup::Sheet,
242 instruction: None,
243 },
244];
245
246pub fn instruction(tool: ToolName) -> Option<&'static str> {
250 BAND_TOOLS
251 .iter()
252 .find(|band| band.tool == tool)
253 .and_then(|band| band.instruction)
254}
255
256pub fn band_tools() -> impl ExactSizeIterator<Item = ToolName> + Clone {
259 BAND_TOOLS.iter().map(|band| band.tool)
260}
261
262#[cfg(any(test, feature = "test-support"))]
264pub fn on_the_band(tool: ToolName) -> bool {
265 band_tools().any(|band| band == tool)
266}
267
268pub fn displayed_tool(active: ToolName) -> ToolName {
275 match active {
276 ToolName::Select
277 | ToolName::NewBlock
278 | ToolName::NewArea
279 | ToolName::AddPin
280 | ToolName::AddPort
281 | ToolName::AddText
282 | ToolName::Route => active,
283 ToolName::Icon
287 | ToolName::NewImage
288 | ToolName::AddRouteLabel
289 | ToolName::EditRoute
290 | ToolName::RenameRoute
291 | ToolName::EditTextBox
292 | ToolName::MovePin
293 | ToolName::RenamePin
294 | ToolName::RetypePin
295 | ToolName::MoveTitle
296 | ToolName::MoveBlockType
297 | ToolName::RenameTitle
298 | ToolName::RenameBlockType
299 | ToolName::MoveBlock
300 | ToolName::ResizeBlock
301 | ToolName::MoveLabel
302 | ToolName::SelectPin
303 | ToolName::MultiSelect
304 | ToolName::MultiPinSelect
305 | ToolName::MoveMultiPin => ToolName::Select,
306 }
307}
308
309#[cfg(any(test, feature = "test-support"))]
313pub const ALL_TOOLS: [ToolName; 27] = [
314 ToolName::NewBlock,
315 ToolName::NewArea,
316 ToolName::AddPin,
317 ToolName::AddPort,
318 ToolName::NewImage,
319 ToolName::Icon,
320 ToolName::AddText,
321 ToolName::EditTextBox,
322 ToolName::MovePin,
323 ToolName::RenamePin,
324 ToolName::RetypePin,
325 ToolName::MoveTitle,
326 ToolName::MoveBlockType,
327 ToolName::RenameTitle,
328 ToolName::RenameBlockType,
329 ToolName::Route,
330 ToolName::MoveBlock,
331 ToolName::ResizeBlock,
332 ToolName::EditRoute,
333 ToolName::AddRouteLabel,
334 ToolName::RenameRoute,
335 ToolName::MoveLabel,
336 ToolName::Select,
337 ToolName::SelectPin,
338 ToolName::MultiSelect,
339 ToolName::MultiPinSelect,
340 ToolName::MoveMultiPin,
341];
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
351 fn base_band_tools_map_to_themselves() {
352 for tool in band_tools().filter(|tool| *tool != ToolName::NewImage) {
353 assert_eq!(displayed_tool(tool), tool);
354 }
355 }
356
357 #[test]
358 fn selection_microtools_map_to_select() {
359 for tool in [
360 ToolName::SelectPin,
361 ToolName::MultiSelect,
362 ToolName::ResizeBlock,
363 ToolName::RenameTitle,
364 ] {
365 assert_eq!(displayed_tool(tool), ToolName::Select);
366 }
367 }
368
369 #[test]
370 fn route_editing_maps_to_select() {
371 assert_eq!(displayed_tool(ToolName::EditRoute), ToolName::Select);
372 assert_eq!(displayed_tool(ToolName::RenameRoute), ToolName::Select);
373 assert_eq!(displayed_tool(ToolName::Route), ToolName::Route);
374 }
375
376 #[test]
377 fn overlay_armed_tools_map_to_select() {
378 assert_eq!(displayed_tool(ToolName::Icon), ToolName::Select);
384 assert_eq!(displayed_tool(ToolName::NewImage), ToolName::Select);
385 assert_eq!(displayed_tool(ToolName::AddRouteLabel), ToolName::Select);
386 assert!(!on_the_band(ToolName::Icon));
387 assert!(!on_the_band(ToolName::AddRouteLabel));
388 assert!(on_the_band(ToolName::NewImage));
389 }
390
391 #[test]
392 fn displayed_tool_is_always_a_band_tool() {
393 for tool in ALL_TOOLS {
394 assert!(on_the_band(displayed_tool(tool)));
395 }
396 }
397
398 #[test]
404 fn every_tool_on_the_band_has_a_spelling() {
405 for tool in ALL_TOOLS {
406 assert!(
407 tool.command_name().is_some()
408 || tool == ToolName::NewImage
409 || !band_tools().any(|band| band == tool),
410 "{tool:?} is on the band with no spelling to be named by",
411 );
412 }
413 }
414
415 #[test]
418 fn every_verb_is_one_word() {
419 for tool in ALL_TOOLS {
420 let verb = tool.verb();
421 assert_eq!(
422 verb.split_whitespace().count(),
423 1,
424 "{tool:?} opens gestures under {verb:?}",
425 );
426 }
427 }
428}