mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 09:59:47 -06:00
The Measure tool and the Measure & Inspect tool snapped the end point of a drag without Shift, but with Shift held they only turned the pointer onto its 45° step and kept its distance: with Snap to Grid on the measurement ended off the grid, with Smart Guides on it did not land in line with an anchor, and with no snapping a drag of (37.3, 12.1) measured 39.2 pt at 0°. Both now project the pointer onto the 45° step nearest its direction and snap it there as the Line Segment tool snaps a Shift-drawn end, so it stays on the step: (37.3, 12.1) measures 36 pt at 0° on a 9 pt grid, and 37.3 pt with no snapping. With Snap to Point alone, the anchor the pointer snaps to is projected onto the step instead, so the end lands on an anchor on the step and in line with one beside it.
90 lines
3.3 KiB
Rust
90 lines
3.3 KiB
Rust
//! Transform and utility tools (rotate/reflect/scale/shear, free transform, eyedropper, gradient,
|
|
//! artboard, magic wand, lasso, measure).
|
|
//!
|
|
//! Like every tool they only emit actions: transforms preview `object.transform` / `object.distort`
|
|
//! on the interaction snapshot, the utility tools execute selection / paint / artboard commands.
|
|
|
|
mod artboard;
|
|
pub(crate) mod eyedropper;
|
|
mod free;
|
|
mod freeform;
|
|
mod gradient;
|
|
mod measure;
|
|
mod transform;
|
|
mod wand;
|
|
|
|
use vectorcraft_doc::{Document, NodeId, NodeKind};
|
|
use vectorcraft_geom::{BezPath, Point, Rect, Shape};
|
|
|
|
use crate::{Overlay, Tool};
|
|
|
|
pub use artboard::ArtboardTool;
|
|
pub use eyedropper::EyedropperTool;
|
|
pub use free::{DistortMode, FreeTransformTool};
|
|
pub use gradient::GradientTool;
|
|
pub(crate) use measure::end_point as measure_end_point;
|
|
pub use measure::{MeasureTool, measure};
|
|
pub use transform::{TransformKind, TransformTool};
|
|
pub use wand::{LassoTool, MagicWandTool};
|
|
|
|
/// Create a transform/utility tool by id (None = not one of ours).
|
|
pub fn create(id: &str) -> Option<Box<dyn Tool>> {
|
|
Some(match id {
|
|
"rotate" => Box::new(TransformTool::new(TransformKind::Rotate)),
|
|
"reflect" => Box::new(TransformTool::new(TransformKind::Reflect)),
|
|
"scale" => Box::new(TransformTool::new(TransformKind::Scale)),
|
|
"shear" => Box::new(TransformTool::new(TransformKind::Shear)),
|
|
"freeTransform" => Box::new(FreeTransformTool::default()),
|
|
"eyedropper" => Box::new(EyedropperTool::default()),
|
|
"gradient" => Box::new(GradientTool::default()),
|
|
"artboard" => Box::new(ArtboardTool::default()),
|
|
"magicWand" => Box::new(MagicWandTool),
|
|
"lasso" => Box::new(LassoTool::default()),
|
|
"measure" => Box::new(MeasureTool::default()),
|
|
_ => return None,
|
|
})
|
|
}
|
|
|
|
/// Cyan used for the reference point target and tool annotations.
|
|
pub const CYAN: [u8; 3] = [0x00, 0xa8, 0xff];
|
|
/// Illustrator's selection blue for bounding boxes.
|
|
pub const BLUE: [u8; 3] = [0x4a, 0x7c, 0xff];
|
|
|
|
/// The reference-point target (circle + crosshair) drawn at `p`, `r` in document units.
|
|
pub(crate) fn target_overlays(p: Point, r: f64) -> Vec<Overlay> {
|
|
let circle = vectorcraft_geom::kurbo::Circle::new(p, r * 0.6).to_path(0.05);
|
|
vec![
|
|
Overlay::Path { path: circle, color: CYAN, width: 1.0, dashed: false },
|
|
Overlay::Line { a: Point::new(p.x - r, p.y), b: Point::new(p.x + r, p.y), color: CYAN, dashed: false },
|
|
Overlay::Line { a: Point::new(p.x, p.y - r), b: Point::new(p.x, p.y + r), color: CYAN, dashed: false },
|
|
]
|
|
}
|
|
|
|
/// Closed polygon path through `pts`.
|
|
pub(crate) fn polygon(pts: &[Point], closed: bool) -> BezPath {
|
|
let mut bp = BezPath::new();
|
|
for (i, p) in pts.iter().enumerate() {
|
|
if i == 0 {
|
|
bp.move_to(*p);
|
|
} else {
|
|
bp.line_to(*p);
|
|
}
|
|
}
|
|
if closed && pts.len() > 2 {
|
|
bp.close_path();
|
|
}
|
|
bp
|
|
}
|
|
|
|
pub(crate) fn rect_corners(r: Rect) -> [Point; 4] {
|
|
[Point::new(r.x0, r.y0), Point::new(r.x1, r.y0), Point::new(r.x1, r.y1), Point::new(r.x0, r.y1)]
|
|
}
|
|
|
|
/// The node that owns the appearance for a hit leaf (a compound path's child → the compound).
|
|
pub fn paint_owner(doc: &Document, leaf: NodeId) -> NodeId {
|
|
match doc.parent_of(leaf) {
|
|
Some(p) if doc.node(p).is_some_and(|n| matches!(n.kind, NodeKind::Compound { .. })) => p,
|
|
_ => leaf,
|
|
}
|
|
}
|