Files
vectorcraft/crates/tools/src/catalog.rs
T
bflatasticandClaude Opus 5.5 a844503e4c Fix #122: Mirror & Cut, Line Cut and Rectangle Cut tools
The issue asked for one-step symmetric shapes, splitting all selected art
along a line and cropping art to a rectangle as real geometry. Only the
freehand Knife and the Scissors existed, and neither works on compound
paths as one shape.

New commands path.mirrorCut, path.lineCut and path.rectCut cut the
selected paths and compound paths (inside groups too) with pathops
booleans against a half-plane or rectangle, and split open paths with
the Knife/Path Eraser helpers. Mirror & Cut reflects the kept half and
unites it with itself (open halves are joined on the axis). The draw2
piece helpers now rebuild compound paths too. Three tools (Eraser group,
original icons) preview the cut live while dragging and commit it as one
undo step; Mirror & Cut's axis and kept side are tool options shown in
the Control bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 00:42:26 +07:00

183 lines
8.2 KiB
Rust

//! The tool catalogue: ids, labels, shortcuts and toolbar groups (Illustrator's Advanced toolbar order).
use serde::Serialize;
#[derive(Clone, Copy, Debug, Serialize)]
pub struct ToolInfo {
pub id: &'static str,
pub label: &'static str,
/// Single-key shortcut (Shift+key shown as "Shift+X").
pub shortcut: Option<&'static str>,
/// Icon name in the UI icon set.
pub icon: &'static str,
}
const fn t(id: &'static str, label: &'static str, shortcut: Option<&'static str>, icon: &'static str) -> ToolInfo {
ToolInfo { id, label, shortcut, icon }
}
/// Toolbar groups in order; the first tool of each group is the default visible one.
pub const TOOL_GROUPS: &[&[ToolInfo]] = &[
&[t("selection", "Selection Tool", Some("V"), "tool-selection")],
&[
t("directSelection", "Direct Selection Tool", Some("A"), "tool-direct"),
t("groupSelection", "Group Selection Tool", None, "tool-group-select"),
],
&[t("magicWand", "Magic Wand Tool", Some("Y"), "tool-magic-wand")],
&[t("lasso", "Lasso Tool", Some("Q"), "tool-lasso")],
&[
t("pen", "Pen Tool", Some("P"), "tool-pen"),
t("addAnchor", "Add Anchor Point Tool", Some("+"), "tool-pen-add"),
t("deleteAnchor", "Delete Anchor Point Tool", Some("-"), "tool-pen-delete"),
t("anchorPoint", "Anchor Point Tool", Some("Shift+C"), "tool-anchor"),
],
&[t("curvature", "Curvature Tool", Some("Shift+~"), "tool-curvature")],
&[
t("type", "Type Tool", Some("T"), "tool-type"),
t("areaType", "Area Type Tool", None, "tool-type-area"),
t("typeOnPath", "Type on a Path Tool", None, "tool-type-path"),
t("verticalType", "Vertical Type Tool", None, "tool-type-vertical"),
t("verticalAreaType", "Vertical Area Type Tool", None, "tool-type-vertical"),
t("verticalTypeOnPath", "Vertical Type on a Path Tool", None, "tool-type-path"),
t("touchType", "Touch Type Tool", Some("Shift+T"), "tool-touch-type"),
],
&[
t("lineSegment", "Line Segment Tool", Some("\\"), "tool-line"),
t("arc", "Arc Tool", None, "tool-arc"),
t("spiral", "Spiral Tool", None, "tool-spiral"),
t("rectangularGrid", "Rectangular Grid Tool", None, "tool-rect-grid"),
t("polarGrid", "Polar Grid Tool", None, "tool-polar-grid"),
],
&[
t("rectangle", "Rectangle Tool", Some("M"), "tool-rect"),
t("roundedRectangle", "Rounded Rectangle Tool", None, "tool-rounded-rect"),
t("ellipse", "Ellipse Tool", Some("L"), "tool-ellipse"),
t("polygon", "Polygon Tool", None, "tool-polygon"),
t("star", "Star Tool", None, "tool-star"),
t("flare", "Flare Tool", None, "tool-flare"),
],
&[t("paintbrush", "Paintbrush Tool", Some("B"), "tool-brush"), t("blobBrush", "Blob Brush Tool", Some("Shift+B"), "tool-blob-brush")],
&[
t("shaper", "Shaper Tool", Some("Shift+N"), "tool-shaper"),
t("pencil", "Pencil Tool", Some("N"), "tool-pencil"),
t("smooth", "Smooth Tool", None, "tool-smooth"),
t("pathEraser", "Path Eraser Tool", None, "tool-path-eraser"),
t("join", "Join Tool", None, "tool-join"),
],
&[
t("eraser", "Eraser Tool", Some("Shift+E"), "tool-eraser"),
t("scissors", "Scissors Tool", Some("C"), "tool-scissors"),
t("knife", "Knife", None, "tool-knife"),
t("mirrorCut", "Mirror & Cut Tool", None, "tool-mirror-cut"),
t("lineCut", "Line Cut Tool", None, "tool-line-cut"),
t("rectCut", "Rectangle Cut Tool", None, "tool-rect-cut"),
],
&[t("rotate", "Rotate Tool", Some("R"), "tool-rotate"), t("reflect", "Reflect Tool", Some("O"), "tool-reflect")],
&[
t("scale", "Scale Tool", Some("S"), "tool-scale"),
t("shear", "Shear Tool", None, "tool-shear"),
t("reshape", "Reshape Tool", None, "tool-reshape"),
],
&[
t("width", "Width Tool", Some("Shift+W"), "tool-width"),
t("warp", "Warp Tool", Some("Shift+R"), "tool-warp"),
t("twirl", "Twirl Tool", None, "tool-twirl"),
t("pucker", "Pucker Tool", None, "tool-pucker"),
t("bloat", "Bloat Tool", None, "tool-bloat"),
t("scallop", "Scallop Tool", None, "tool-scallop"),
t("crystallize", "Crystallize Tool", None, "tool-crystallize"),
t("wrinkle", "Wrinkle Tool", None, "tool-wrinkle"),
],
&[t("freeTransform", "Free Transform Tool", Some("E"), "tool-free-transform"), t("puppetWarp", "Puppet Warp Tool", None, "tool-puppet")],
&[
t("shapeBuilder", "Shape Builder Tool", Some("Shift+M"), "tool-shape-builder"),
t("livePaintBucket", "Live Paint Bucket", Some("K"), "tool-bucket"),
t("livePaintSelection", "Live Paint Selection Tool", Some("Shift+L"), "tool-live-select"),
],
&[
t("perspectiveGrid", "Perspective Grid Tool", Some("Shift+P"), "tool-perspective"),
t("perspectiveSelection", "Perspective Selection Tool", Some("Shift+V"), "tool-perspective-select"),
],
&[t("mesh", "Mesh Tool", Some("U"), "tool-mesh")],
&[t("gradient", "Gradient Tool", Some("G"), "tool-gradient")],
&[t("eyedropper", "Eyedropper Tool", Some("I"), "tool-eyedropper"), t("measure", "Measure Tool", None, "tool-measure")],
&[t("blend", "Blend Tool", Some("W"), "tool-blend")],
&[
t("symbolSprayer", "Symbol Sprayer Tool", Some("Shift+S"), "tool-symbol"),
t("symbolShifter", "Symbol Shifter Tool", None, "tool-symbol"),
t("symbolScruncher", "Symbol Scruncher Tool", None, "tool-symbol"),
t("symbolSizer", "Symbol Sizer Tool", None, "tool-symbol"),
t("symbolSpinner", "Symbol Spinner Tool", None, "tool-symbol"),
t("symbolStainer", "Symbol Stainer Tool", None, "tool-symbol"),
t("symbolScreener", "Symbol Screener Tool", None, "tool-symbol"),
t("symbolStyler", "Symbol Styler Tool", None, "tool-symbol"),
],
&[
t("columnGraph", "Column Graph Tool", Some("J"), "tool-graph"),
t("stackedColumnGraph", "Stacked Column Graph Tool", None, "tool-graph"),
t("barGraph", "Bar Graph Tool", None, "tool-graph"),
t("stackedBarGraph", "Stacked Bar Graph Tool", None, "tool-graph"),
t("lineGraph", "Line Graph Tool", None, "tool-graph"),
t("areaGraph", "Area Graph Tool", None, "tool-graph"),
t("scatterGraph", "Scatter Graph Tool", None, "tool-graph"),
t("pieGraph", "Pie Graph Tool", None, "tool-graph"),
t("radarGraph", "Radar Graph Tool", None, "tool-graph"),
],
&[
t("artboard", "Artboard Tool", Some("Shift+O"), "tool-artboard"),
t("slice", "Slice Tool", Some("Shift+K"), "tool-slice"),
t("sliceSelection", "Slice Selection Tool", None, "tool-slice"),
],
&[
t("hand", "Hand Tool", Some("H"), "tool-hand"),
t("rotateView", "Rotate View Tool", Some("Shift+H"), "tool-rotate-view"),
t("printTiling", "Print Tiling Tool", None, "tool-print-tiling"),
],
&[t("zoom", "Zoom Tool", Some("Z"), "tool-zoom")],
];
pub fn all_tools() -> impl Iterator<Item = &'static ToolInfo> {
TOOL_GROUPS.iter().flat_map(|g| g.iter())
}
pub fn tool_info(id: &str) -> Option<&'static ToolInfo> {
all_tools().find(|t| t.id == id)
}
/// Tool for a single-key shortcut such as "V" or "Shift+M".
pub fn tool_for_shortcut(s: &str) -> Option<&'static ToolInfo> {
all_tools().find(|t| t.shortcut == Some(s))
}
/// Index of the group containing `id`.
pub fn group_of(id: &str) -> Option<usize> {
TOOL_GROUPS.iter().position(|g| g.iter().any(|t| t.id == id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unique_ids_and_shortcuts() {
let mut ids: Vec<&str> = all_tools().map(|t| t.id).collect();
let n = ids.len();
ids.sort();
ids.dedup();
assert_eq!(ids.len(), n, "duplicate tool ids");
let mut sc: Vec<&str> = all_tools().filter_map(|t| t.shortcut).collect();
let n = sc.len();
sc.sort();
sc.dedup();
assert_eq!(sc.len(), n, "duplicate shortcuts");
assert!(n > 40);
}
#[test]
fn lookups() {
assert_eq!(tool_for_shortcut("V").unwrap().id, "selection");
assert_eq!(tool_for_shortcut("Shift+M").unwrap().id, "shapeBuilder");
assert_eq!(group_of("star"), group_of("rectangle"));
}
}