Files
vectorcraft/crates/tools/src/addons.rs
T
KindmarkandClaude Sonnet 5.5 db1ef1bfed Contour: make it a built-in add-on and translate its labels
Review of #5694 asked for two changes.

Add-on, not a core tool, as Enclosing Lasso is:
- The command is addon.contour.set, in an add-on module of its own
  (crates/engine/src/cmd/addons/contour.rs) with its tests beside it.
- The tool is listed under Object > Plug-ins > Contour and under its own
  "Contour" heading in All Tools. It has no Basic or Advanced toolbar
  slot and no default shortcut. The Contour bar is unchanged.
- The add-on drawer test lists Contour with the other add-on tools.
- Docs (mcp.md, the parity documents, ROADMAP) say add-on and use the
  new command name; mcp.md gains a Contour add-on section.

Translations: rows for "Contour", "Contour Tool", "Sharp", "Bake" and
"Turn the Contour into plain paths" in all 26 catalogs ("Round" and
"Square" already had them). Drafted from each catalog's own vocabulary
(for Bake, the verb it uses for Expand Appearance; a word other than the
catalog's Stroke or Path word for the tool's name where they collide);
they are not native-speaker reviewed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 12:57:51 +07:00

88 lines
4.5 KiB
Rust

//! The tools of VectorCraft's built-in add-ons. They aren't in the toolbars as they ship: the All
//! Tools drawer lists them (under their add-on's heading) so they can be added, and each add-on's
//! submenu selects them. Tool gestures emit engine commands.
pub mod anchor_eraser;
pub mod contour;
pub mod corner_tool;
pub mod enclosing_lasso;
pub mod extend_path;
pub mod measure_inspect;
pub mod parametric_shape;
pub mod shared_edge;
pub mod slide_anchor;
pub mod smart_pen;
use crate::{Tool, ToolInfo};
/// Path Precision Tools' tools, one flyout group.
pub const PATH_TOOLS: &[ToolInfo] = &[
ToolInfo { id: anchor_eraser::ID, label: "Anchor Eraser Brush", shortcut: None, icon: "dc-remove-brush" },
ToolInfo { id: extend_path::ID, label: "Extend Path Tool", shortcut: None, icon: "dc-arrow-extend" },
ToolInfo { id: measure_inspect::ID, label: "Measure & Inspect Tool", shortcut: None, icon: "ruler" },
ToolInfo { id: parametric_shape::ID, label: "Parametric Shape Tool", shortcut: None, icon: "hexagon" },
ToolInfo { id: corner_tool::ID, label: "Corner Tool", shortcut: None, icon: "dc-rounded-rect" },
ToolInfo { id: slide_anchor::ID, label: "Slide Anchor Along Path", shortcut: None, icon: "dc-anchor" },
ToolInfo { id: shared_edge::ID, label: "Shared Edge Merge Tool", shortcut: None, icon: "dc-join" },
];
/// Smart Pen's tool, a flyout group of its own.
pub const SMART_PEN: &[ToolInfo] = &[ToolInfo { id: smart_pen::ID, label: "Smart Pen Tool", shortcut: None, icon: "spline-pointer" }];
pub const CONTOUR: &[ToolInfo] = &[ToolInfo { id: contour::ID, label: "Contour Tool", shortcut: None, icon: "tool-contour" }];
pub const ENCLOSING_LASSO: &[ToolInfo] = &[ToolInfo { id: enclosing_lasso::ID, label: "Enclosing Lasso Tool", shortcut: None, icon: "tool-lasso" }];
/// The add-ons' tool groups, after the toolbar's own ([`crate::TOOL_GROUPS`]), each with the
/// heading the All Tools drawer lists it under (its add-on's name).
const HEADED: &[(&[ToolInfo], &str)] =
&[(PATH_TOOLS, "Path Precision Tools"), (SMART_PEN, "Smart Pen"), (ENCLOSING_LASSO, "Enclosing Lasso"), (CONTOUR, "Contour")];
/// The add-ons' tool groups, after the toolbar's own ([`crate::TOOL_GROUPS`]).
pub const GROUPS: &[&[ToolInfo]] = &[PATH_TOOLS, SMART_PEN, ENCLOSING_LASSO, CONTOUR];
/// The heading the All Tools drawer lists tool `id` under: its add-on's name.
pub fn heading(id: &str) -> Option<&'static str> {
HEADED.iter().find(|(g, _)| g.iter().any(|t| t.id == id)).map(|(_, h)| *h)
}
/// Create an add-on tool by id (None = not one of them).
pub fn create(id: &str) -> Option<Box<dyn Tool>> {
Some(match id {
anchor_eraser::ID => Box::new(anchor_eraser::AnchorEraserTool::default()),
corner_tool::ID => Box::new(corner_tool::CornerTool::default()),
extend_path::ID => Box::new(extend_path::ExtendPathTool::default()),
measure_inspect::ID => Box::new(measure_inspect::MeasureInspectTool::default()),
parametric_shape::ID => Box::new(parametric_shape::ParametricShapeTool::default()),
shared_edge::ID => Box::new(shared_edge::SharedEdgeTool::default()),
slide_anchor::ID => Box::new(slide_anchor::SlideAnchorTool::default()),
smart_pen::ID => Box::new(smart_pen::SmartPenTool::default()),
enclosing_lasso::ID => Box::new(enclosing_lasso::EnclosingLassoTool::default()),
contour::ID => Box::new(contour::ContourTool::default()),
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_addon_tool_is_made_and_catalogued_but_not_in_the_toolbar() {
for t in GROUPS.iter().flat_map(|g| g.iter()) {
assert_eq!(create(t.id).map(|tool| tool.id()), Some(t.id));
assert_eq!(crate::tool_info(t.id).map(|i| i.label), Some(t.label));
assert!(crate::create(t.id).id() == t.id);
assert!(t.shortcut.is_none(), "no default shortcut");
assert!(!crate::TOOL_GROUPS.iter().any(|g| g.iter().any(|x| x.id == t.id)), "{} is not in the toolbar's groups", t.id);
}
assert!(PATH_TOOLS.iter().all(|t| heading(t.id) == Some("Path Precision Tools")));
assert_eq!(heading(smart_pen::ID), Some("Smart Pen"));
assert_eq!(heading(enclosing_lasso::ID), Some("Enclosing Lasso"));
assert_eq!(heading(contour::ID), Some("Contour"));
assert_eq!(GROUPS.len(), HEADED.len());
assert!(crate::catalog::all_tools().any(|t| t.id == extend_path::ID));
assert!(create("pen").is_none() && heading("pen").is_none());
}
}