Merge pull request #1354 from dotCooCoo/feat/direct-selection-pattern-tiles

Part of #394: art moved whole with direct-selected anchors follows Transform Pattern Tiles
This commit is contained in:
BFlat
2026-10-12 15:10:16 +07:00
committed by GitHub
5 changed files with 65 additions and 7 deletions
+2 -2
View File
@@ -86,7 +86,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Nudge",
[],
None,
"{dx: -1|0|1, dy: -1|0|1, big?: bool (×10), copy?: bool} arrow-key nudge by the keyboard increment (the selected anchors, else objects, else ruler guides)",
"{dx: -1|0|1, dy: -1|0|1, big?: bool (×10), copy?: bool, patterns?: bool (Transform Patterns: pattern fills and strokes transform with the objects that move whole; default: prefs transformPatternTiles)} arrow-key nudge by the keyboard increment (the selected anchors, else objects, else ruler guides)",
has_selection_or_guides,
nudge
),
@@ -337,7 +337,7 @@ fn nudge(s: &mut Session, p: &Value) -> Result<Value> {
if dx == 0.0 && dy == 0.0 {
return ok();
}
return super::path::move_anchors(s, &json!({ "dx": dx, "dy": dy }));
return super::path::move_anchors(s, &json!({ "dx": dx, "dy": dy, "patterns": p.get("patterns") }));
}
if s.doc()?.selection.is_empty() {
let copy = bool_or(p, "copy", false);
+7 -3
View File
@@ -3,7 +3,7 @@
use std::collections::BTreeSet;
use serde_json::{Value, json};
use vectorcraft_doc::{NodeId, NodeKind};
use vectorcraft_doc::{NodeId, NodeKind, Scaling};
use vectorcraft_geom::{AnchorKind, PathData, Point, SubPath, Vec2};
use super::create::anchor_from_json;
@@ -27,7 +27,7 @@ pub fn specs() -> Vec<CommandSpec> {
"Move Anchors",
[],
None,
"{dx, dy} move direct-selected anchors (whole paths if fully selected)",
"{dx, dy, patterns?: bool (Transform Patterns: pattern fills and strokes transform with the objects that move whole; default: prefs transformPatternTiles)} move direct-selected anchors (whole paths if fully selected)",
has_selection,
move_anchors
),
@@ -172,6 +172,10 @@ pub(crate) fn move_anchors(s: &mut Session, p: &Value) -> Result<Value> {
let sel = st.selection.clone();
let scale_strokes = s.prefs.scale_strokes;
let _ = scale_strokes;
// Objects selected whole (with no entry in `sel.anchors`) move as objects, and their pattern
// tiles move with them when `patterns` (default: General › Transform Pattern Tiles) is on.
let whole: Vec<NodeId> = sel.objects.iter().copied().filter(|id| !sel.anchors.contains_key(id)).collect();
let sc = Scaling { patterns: super::object::transform_patterns(s, p, &whole)?, ..Scaling::default() };
s.edit("Move", |doc, _| {
for id in &sel.objects {
match sel.anchors.get(id) {
@@ -185,7 +189,7 @@ pub(crate) fn move_anchors(s: &mut Session, p: &Value) -> Result<Value> {
}
None => {
if let Some(n) = doc.node_mut(*id) {
n.transform(vectorcraft_geom::Affine::translate(d), false);
n.transform(vectorcraft_geom::Affine::translate(d), sc);
}
}
}
+53
View File
@@ -699,6 +699,59 @@ fn art_moved_with_its_artboard_follows_transform_pattern_tiles() {
}
}
/// General › Transform Pattern Tiles (#394) applies to the objects selected whole that move with
/// direct-selected anchors (an arrow key, `path.moveAnchors` or a Direct Selection drag of an
/// anchor): off, the tiles of a pattern-filled square stay in place; on, they move with it, and
/// `patterns: false` keeps them in place for one call.
#[test]
fn whole_objects_moved_with_selected_anchors_follow_transform_pattern_tiles() {
use vectorcraft_tools::PointerKind::{Down, Drag, Up};
let mut s = new_doc();
let filled = patterned_square(&mut s);
let other = square(&mut s, 300.0, 100.0);
let v = ViewInfo { smart_guides: false, snap_to_point: false, corner_widgets: false, ..ViewInfo::default() };
s.select_tool("directSelection", v).unwrap();
// The square is selected whole with the top-left anchor of `other`, as a click on the square's
// fill and a Shift-click on that anchor select them. Each move takes both 10 pt right: Shift
// with an arrow key, `path.moveAnchors`, and a drag of the anchor (no params).
for (cmd, p) in [
("object.nudge", Some(json!({"dx": 1, "dy": 0, "big": true}))),
("path.moveAnchors", Some(json!({"dx": 10, "dy": 0}))),
("path.moveAnchors", None),
] {
let run = |s: &mut Session, p: Option<&Value>| {
s.execute("select.set", &json!({"ids": [filled.0]})).unwrap();
s.execute("select.anchors", &json!({"id": other.0, "anchors": [[0, 0]], "mode": "add"})).unwrap();
match p {
Some(p) => {
s.execute(cmd, p).unwrap();
}
None => gesture(s, v, &[(Down, 300.0, 100.0), (Drag, 305.0, 100.0), (Drag, 310.0, 100.0), (Up, 310.0, 100.0)]),
}
assert_eq!(top_left(s, filled), (110.0, 100.0), "{cmd}: the square moves whole");
let anchors = &s.doc().unwrap().doc.node(other).unwrap().path_data().unwrap().subpaths[0].anchors;
assert_eq!((anchors[0].p.x, anchors[1].p.x), (310.0, 350.0), "{cmd}: the selected anchor moves alone");
assert_eq!(s.journal.last().unwrap().0, cmd);
};
run(&mut s, p.as_ref());
assert_eq!(pattern_tiles(&s, filled), [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], "{cmd}: off, the tiles stay");
assert_eq!(s.journal.last().unwrap().1["patterns"], json!(false), "{cmd}: the journal keeps the choice");
s.execute("edit.undo", &json!({})).unwrap();
set_pref(&mut s, "transformPatternTiles", json!(true));
run(&mut s, p.as_ref());
assert_eq!(pattern_tiles(&s, filled), [1.0, 0.0, 0.0, 1.0, 10.0, 0.0], "{cmd}: on, they move with the square");
assert_eq!(s.journal.last().unwrap().1["patterns"], json!(true), "{cmd}: the journal keeps the choice");
s.execute("edit.undo", &json!({})).unwrap();
if let Some(mut p) = p {
p["patterns"] = json!(false);
run(&mut s, Some(&p));
assert_eq!(pattern_tiles(&s, filled), [1.0, 0.0, 0.0, 1.0, 0.0, 0.0], "{cmd}: the param wins");
s.execute("edit.undo", &json!({})).unwrap();
}
set_pref(&mut s, "transformPatternTiles", json!(false));
}
}
/// General › Select Same Tint % (#394): off, Select › Same › Fill Color takes every tint of the
/// swatch; on, only the same tint.
#[test]
+2 -1
View File
@@ -1592,7 +1592,8 @@ The Selection & Anchor Display and General preferences apply to `pointer_gesture
`object.align`, `object.distribute`, `object.distributeSpacing`, the Selection and transform tools, the dialogs'
Transform Patterns) and of the same param on the commands that move or duplicate art with an artboard (`artboard.move`
and the Artboard tool, `artboard.duplicate`, `artboard.rearrange`, `artboard.setProps`): pattern fills and strokes
move with the art.
move with the art. `object.nudge` (the arrow keys) and `path.moveAnchors` (a Direct Selection drag of selected
anchors) take the same param, with the same default, for the objects they move whole.
- `selectSameTintPercent` (off by default): `select.same.fillColor`, `strokeColor` and `fillAndStroke` take every tint
of a global or spot swatch; on, only the same tint.
File diff suppressed because one or more lines are too long