mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 21:17:48 +00:00
Dimension tool: a point on a Smart Guide stays on the guide
A Linear pick that the Smart Guides left where the pointer already was, for example straight below the first point on the vertical construction guide through it, or exactly in line with an object's center, was taken as not snapped. The pick then moved to the closest point of a segment within 8 pixels while the guide still showed it on the guide. The same happened to a point already on a grid crossing with Snap to Grid on, or on a whole pixel with Snap to Pixel on. The pick now keeps the snapped point whenever the snap shows guides or grid or pixel snapping is on. With Snap to Grid or Snap to Pixel on, a pick off an anchor is no longer moved from the grid or the whole pixel onto a segment. A point that no anchor, guide, grid or pixel snapping places still lands on the closest point of a segment. An anchor within 8 pixels, a segment within the Smart Guides snapping tolerance and a nearby guide crossing still take the point as before. Docs: docs/dimensions.md, docs/target-app-parity.md.
This commit is contained in:
@@ -242,6 +242,8 @@ impl DimensionTool {
|
||||
/// a link to its path); else where Smart Guides put it (another object's anchor or centre, in
|
||||
/// line with one, or for the second point on a construction guide through the first), its
|
||||
/// guides showing; else on the closest point of the line under the pointer; else `p` itself.
|
||||
/// A point the guides leave where the pointer is (right on a guide line) stays there, and with
|
||||
/// Snap to Pixel or Snap to Grid on a point stays where they place it.
|
||||
fn snap_linear(&mut self, cx: &ToolContext, p: Point) -> (Point, Option<PointRef>) {
|
||||
let leave = self.points.last().map(|(first, _)| crate::guides::Leave::segment(cx, *first, false));
|
||||
let guided = self.snap.hover(cx, p, &[], leave.as_ref());
|
||||
@@ -249,7 +251,7 @@ impl DimensionTool {
|
||||
self.snap.clear();
|
||||
return (point, Some(reference));
|
||||
}
|
||||
if guided.distance(p) > 1e-9 {
|
||||
if guided.distance(p) > 1e-9 || !self.snap.guides().is_empty() || cx.snap_to_pixel || cx.snap_to_grid {
|
||||
return (guided, None);
|
||||
}
|
||||
// Not on an anchor or a guide: snap to the closest point of the line (#2432).
|
||||
@@ -844,6 +846,54 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A point that Smart Guides leave where the pointer already is (the pointer right on a
|
||||
/// construction guide or in line with a target) stays on the guide the overlays show. It
|
||||
/// does not move to the nearest point of a segment within the pick's reach. Off the guides,
|
||||
/// a point near a segment still lands on it.
|
||||
#[test]
|
||||
fn a_point_on_a_guide_stays_on_the_guide() {
|
||||
let (d, _) = doc_with_rect();
|
||||
let (s, p) = (Selection::default(), paint());
|
||||
let c = cx(&d, &s, &p);
|
||||
let click = |t: &mut DimensionTool, x: f64, y: f64| {
|
||||
t.pointer(&c, &PointerEvent::new(PointerKind::Move, x, y));
|
||||
t.pointer(&c, &PointerEvent::new(PointerKind::Down, x, y));
|
||||
t.pointer(&c, &PointerEvent::new(PointerKind::Up, x, y));
|
||||
};
|
||||
let mut t = DimensionTool::default();
|
||||
// In line with the rectangle's center, 6 units right of its right edge.
|
||||
assert_eq!(t.snap_linear(&c, Point::new(206.0, 150.0)), (Point::new(206.0, 150.0), None));
|
||||
assert!(!t.snap.guides().is_empty());
|
||||
// Off the guides, beside the edge: the closest point of the edge.
|
||||
let (on_edge, reference) = t.snap_linear(&c, Point::new(206.0, 170.5));
|
||||
assert!(on_edge.distance(Point::new(200.0, 170.5)) < 1e-4 && reference.is_none(), "{on_edge:?}");
|
||||
// The first point, above the rectangle, then straight down along the vertical
|
||||
// construction guide through it, past the right edge.
|
||||
click(&mut t, 206.0, 50.0);
|
||||
assert_eq!(t.points, vec![(Point::new(206.0, 50.0), None)]);
|
||||
for y in [150.0, 170.5, 190.0] {
|
||||
assert_eq!(t.snap_linear(&c, Point::new(206.0, y)), (Point::new(206.0, y), None), "on the construction guide at y = {y}");
|
||||
assert!(!t.snap.guides().is_empty());
|
||||
}
|
||||
click(&mut t, 206.0, 170.5);
|
||||
let dim = t.pending.as_ref().expect("both points were picked");
|
||||
assert_eq!((dim.a, dim.b), (Point::new(206.0, 50.0), Point::new(206.0, 170.5)));
|
||||
// The second point, off the edge, is held against the rectangle's anchors.
|
||||
let Some(DimensionSource::Points { free_b: Some(free), .. }) = &dim.source else { panic!("{:?}", dim.source) };
|
||||
assert!(matches!(free, FreePoint::Anchors { .. }), "{free:?}");
|
||||
// A pointer already on a gridline crossing or on a whole pixel stays there too, and one
|
||||
// off the grid lands on the grid, not on the edge.
|
||||
let grid = ToolContext { snap_to_grid: true, ..cx(&d, &s, &p) };
|
||||
let step = grid.grid_step();
|
||||
let on_grid = Point::new((200.0 / step).ceil() * step, (170.0 / step).round() * step);
|
||||
assert!(on_grid.x > 200.0 + 1e-6 && on_grid.x - 200.0 < 8.0, "{on_grid:?} is off the edge, within the pick's reach");
|
||||
assert_eq!(DimensionTool::default().snap_linear(&grid, on_grid), (on_grid, None));
|
||||
let off_grid = on_grid + Vec2::new(-step * 0.3, 0.2);
|
||||
assert_eq!(DimensionTool::default().snap_linear(&grid, off_grid), (on_grid, None));
|
||||
let pixel = ToolContext { snap_to_pixel: true, ..cx(&d, &s, &p) };
|
||||
assert_eq!(DimensionTool::default().snap_linear(&pixel, Point::new(206.0, 170.0)), (Point::new(206.0, 170.0), None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn placement_press_previews_without_a_prior_move() {
|
||||
let (d, _) = doc_with_rect();
|
||||
|
||||
+6
-1
@@ -16,7 +16,12 @@ The **Dimension Tool** is in the Modify section of the toolbar. Its strip offers
|
||||
before the release; a longer drag drags out the dimension of the segment under the pointer. A
|
||||
curved segment is measured from end to end. With Smart Guides on, both points also snap into line with other objects'
|
||||
anchors and centres, and the second onto the construction guides through the first; the guides
|
||||
show while hovering, before each click.
|
||||
show while hovering, before each click. VectorCraft's own rules decide where a point lands: an
|
||||
anchor within 8 pixels takes it first; a point on a guide stays on the guide where the pointer
|
||||
is, except that it moves onto a segment within the Smart Guides snapping tolerance, or onto the
|
||||
place where a construction guide crosses another object's alignment line within twice that
|
||||
tolerance; a point that no anchor, guide, grid or pixel snapping places lands on the closest
|
||||
point of a segment within 8 pixels.
|
||||
A linear dimension is aligned, horizontal or vertical, as the
|
||||
[linear dimensions help](https://helpx.adobe.com/illustrator/desktop/measure-and-align/plot-and-measure/plot-linear-dimensions-of-objects.html)
|
||||
describes: after the second click, clicking again places an aligned dimension, and moving the
|
||||
|
||||
@@ -583,6 +583,7 @@ since 2026-10-01.
|
||||
**Drawing tools, Star inner radius held with Ctrl/Cmd:** While a star is dragged out, Ctrl/Cmd keeps the inner radius the star showed when the key went down, also when the key is pressed with the pointer still. `a_star_drag_holds_its_inner_radius_with_ctrl_and_straightens_with_alt` and `a_star_drag_holds_the_inner_radius_it_shows_when_ctrl_goes_down` cover it, with Alt's straight shoulders and a drag canceled with Escape.
|
||||
**Drawing tools, Dimension tool horizontal and vertical linear dimensions:** A linear dimension between two clicked points or dragged out from a segment is aligned, horizontal or vertical, chosen by where the pointer moves while it is placed, and a click again by the second point gives aligned; `dimension.create` and `dimension.update` take `orientation`, and the dimension stays linked to the anchors it measures, keeps its orientation when it or the art turns, with a line that lay outside the points kept outside them; an unlinked horizontal or vertical dimension that is reflected has its line on the reflected side. `the_pointer_chooses_aligned_horizontal_or_vertical`, `two_points_measure_horizontally_vertically_or_aligned`, `clicking_again_by_the_second_point_places_an_aligned_dimension`, `a_segment_drag_measures_horizontally_vertically_or_aligned`, `points_on_a_vertical_line_measure_aligned`, `horizontal_and_vertical_dimensions_draw_lines_along_their_axis`, `turned_horizontal_dimensions_stay_horizontal`, `a_linked_horizontal_dimension_keeps_its_gap_from_turned_art`, `a_linked_horizontal_dimension_below_or_between_its_points_follows_turned_art`, `a_reflected_horizontal_dimension_keeps_its_line_on_the_reflected_side`, `a_dimension_without_an_orientation_reads_as_aligned`, `dimension_orientation_is_checked` and `a_turned_dimension_keeps_its_line_on_its_side` cover it.
|
||||
**Drawing tools, Dimension tool points that are not anchors follow the art:** A clicked point on a segment stays at its place along the segment, also when the segment curves, and a point off the art is linked to anchors (or, when the anchors lie on one line, anchors and handles) of the path, so both ends of the dimension follow when the art is moved, scaled, turned or reflected, also when the point was placed by a Smart Guide and when the dimension is saved and opened again; a linked linear dimension of reflected art (other than art on one line) has its line on the reflected side; deleting the art, changing its anchor count or whether it is closed, turning an angle's arm into a curve, or making a measured circle or arc no longer circular leaves the dimension where it was, no longer linked, instead of removing it. `a_dimension_to_a_point_on_a_segment_follows_the_art_at_both_ends`, `a_dimension_to_a_point_off_the_art_moves_with_the_art`, `a_dimension_whose_art_changes_its_anchors_stays_where_it_was`, `a_dimension_whose_art_is_opened_stays_where_it_was`, `angle_and_arc_dimensions_whose_art_no_longer_fits_stay_where_they_were`, `a_dimension_that_cannot_be_drawn_again_keeps_its_art`, `a_linked_dimension_of_reflected_art_has_its_line_on_the_reflected_side`, `a_dimension_to_a_point_off_a_line_follows_the_line`, `a_point_off_an_arch_is_reflected_with_the_arch`, `a_point_beside_an_almost_straight_curve_follows_it`, `a_point_off_a_loop_with_its_anchors_in_one_place_follows_it`, `a_point_beside_a_reflected_line_moves_as_a_half_turn_would`, `a_point_on_a_segment_stays_on_it_when_the_segment_curves`, `a_linked_dimension_of_a_reflected_curve_has_its_line_on_the_reflected_side`, `a_dimension_of_art_reflected_across_its_points_changes_sides`, `a_dimension_between_paths_reflected_each_in_its_own_way_keeps_its_offset`, `free_points_survive_a_native_file`, `dimension_free_points_are_checked`, `points_off_anchors_are_held_against_the_path` and `a_point_smart_guides_place_is_held_against_the_path` cover it.
|
||||
**Drawing tools, Dimension tool points on a Smart Guide stay on the guide:** By VectorCraft's own rule, a clicked point that lies on a Smart Guide already, such as a point straight below the first one on the vertical construction guide through it, or one in line with an object's center, stays where the pointer is when no anchor is within 8 pixels and no segment is within the Smart Guides snapping tolerance, instead of moving onto a segment farther than that tolerance and within 8 pixels; with Snap to Grid or Snap to Pixel on, a point stays on the grid or the whole pixel those options give. `a_point_on_a_guide_stays_on_the_guide` covers it.
|
||||
**Drawing tools, Dimension tool dimensions selected with their art keep their lines and label:** Object › Expand, Object › Expand Appearance and Object › Path › Outline Stroke leave a dimension selected with art it measures, directly or in selected groups without fills, strokes or effects of their own, as it is and change only the other art; when the new art no longer fits its link, the dimension stays where it was, no longer linked, and undo links it again. `expand_leaves_a_dimension_selected_with_its_art_as_it_is`, `expand_appearance_leaves_a_dimension_selected_with_its_art_as_it_is`, `outline_stroke_leaves_a_dimension_selected_with_its_art_as_it_is`, `a_dimension_selected_with_art_that_has_nothing_to_expand_changes_nothing`, `a_group_effect_expanded_reaches_the_dimension_it_holds`, `expand_keeps_a_dimension_in_its_asset_and_its_variable_binding`, `a_dimension_keeps_no_link_to_the_art_expand_replaced`, `a_dimension_outlasts_the_art_outline_stroke_or_expand_appearance_replaced`, `a_dimension_outlasts_the_stroke_expand_outlined` and `expand_appearance_in_safe_mode_leaves_a_dimension_selected_with_its_art` cover it.
|
||||
**Live trace result bounds and selection highlights exclude the retained bitmap except in source-image views:** `live_trace_result_bounds_exclude_the_retained_bitmap_at_any_angle` checks every TraceView, geometric and visual bounds and rotated result bounds. `live_trace_selection_highlight_excludes_the_retained_source_rectangle` checks result highlights and the existing ordinary-group policy. Shared editing-child traversal also preserves Shaper handling.
|
||||
**Artboard Options shares existing presets and orientation controls and can fit artwork without moving it; artwork fitting includes all drawn document art across artboards and off-board; artwork bounds are cached by active document and revision, and selection uses the shared bounds cache across artboards:** Shared existing preset definitions are reused; no external preset assets are added. Presets preserve each selected artboard's orientation, and orientation choices apply even when the active board already matches. Fit operations draft bounds until OK, affect only the active artboard and leave artwork in place. Regression coverage checks draft/Cancel, selected-set application, active-only fitting and Undo.
|
||||
|
||||
Reference in New Issue
Block a user