Merge pull request #5237 from dotCooCoo/feat/global-edit-shift-exclude

Global Edit: a Shift-click on a highlighted match leaves it out of the edit
This commit is contained in:
Brandon Thomas
2026-11-03 00:19:36 -05:00
committed by GitHub
36 changed files with 392 additions and 24 deletions
+55 -5
View File
@@ -59,8 +59,15 @@
//! opacity, blending, fills and effects). When the model group gains or loses objects, or one of
//! them becomes another kind of object, each member takes the model's objects at that level
//! whole, keeping its own ids in order. At most [`MAX_MEMBERS`] objects are taken, so that a huge
//! document can't stall the start or every edit. Rotated copies and excluding a member with a
//! Shift-click are not covered.
//! document can't stall the start or every edit.
//!
//! In the how-to above, a Shift-click on one of the found objects takes it out of the edit. Here a
//! Shift-click (not a Shift-drag) with the Selection tool on a highlighted member, or on an object
//! inside it, leaves that member out (`select.globalEditExclude`): it is no longer highlighted,
//! and later edits leave it as it is.
//! The object the edit started from can't be left out (a Shift-click on it selects it or
//! deselects it as before), Global Edit ends when fewer than two members are left, and leaving
//! one out is not an undo step. Rotated copies are not covered.
use serde::Serialize;
use serde_json::{Value, json};
@@ -68,10 +75,11 @@ use vectorcraft_doc::{Appearance, AppearanceItem, Document, FillLayer, Node, Nod
use vectorcraft_geom::{Affine, BezPath, PathData, Point, Rect, Shape};
use super::wand::{fill_of, stroke_of};
use super::{CommandSpec, Result, Session, bad, bool_or, cmd, has_doc};
use super::{CommandSpec, Result, Session, bad, bool_or, checked_id, cmd, has_doc};
const C: &str = "select.globalEdit";
const OPTIONS: &str = "select.globalEditOptions";
const EXCLUDE: &str = "select.globalEditExclude";
/// The most objects a Global Edit takes, the selected one included.
pub const MAX_MEMBERS: usize = 10_000;
const UNSUPPORTED: &str = "Global Edit works on paths, compound paths, symbol instances and groups of them";
@@ -96,6 +104,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_doc,
set_options
),
cmd!(
"select.globalEditExclude",
"Leave Out of Global Edit",
[],
None,
"{id} while Global Edit is on: leave the object `id` of the Global Edit (not the one it started from) out of it, as a Shift-click on it with the Selection tool does: it is no longer highlighted and later edits leave it as it is; with fewer than two objects left, Global Edit ends; not an undo step → {active, count, ids} (the objects left in it; none once it has ended)",
is_on,
exclude
),
]
}
@@ -385,6 +402,35 @@ fn toggle(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "active": true, "count": ids.len(), "ids": ids }))
}
fn is_on(s: &Session) -> std::result::Result<(), String> {
match s.active() {
None => Err("no document open".into()),
Some(st) if st.global_edit.is_none() => Err("Global Edit is off".into()),
Some(_) => Ok(()),
}
}
/// Leave one object out of the Global Edit (a Shift-click on it): not the object it started from.
fn exclude(s: &mut Session, p: &Value) -> Result<Value> {
let id = checked_id(s, p.get("id").ok_or_else(|| bad(EXCLUDE, "missing id"))?, EXCLUDE)?;
let st = s.doc_mut()?;
let Some(ge) = &mut st.global_edit else { return Err(bad(EXCLUDE, "Global Edit is off")) };
match ge.members.iter().position(|(m, _)| *m == id) {
Some(i) if i > 0 => {
ge.members.remove(i);
}
_ => return Err(bad(EXCLUDE, "only an object of the Global Edit other than the one it started from can be left out")),
}
let ids: Vec<u64> = if ge.members.len() < 2 {
st.global_edit = None;
vec![]
} else {
ge.members.iter().map(|(id, _)| id.0).collect()
};
st.revision += 1;
Ok(json!({ "active": !ids.is_empty(), "count": ids.len(), "ids": ids }))
}
fn set_options(s: &mut Session, p: &Value) -> Result<Value> {
let o = s.global_edit_options.with(p, &s.doc()?.doc, OPTIONS)?;
s.global_edit_options = o.clone();
@@ -674,8 +720,9 @@ pub(crate) fn propagate(before: &Document, d: &mut Document, ge: &GlobalEdit, se
edited
}
/// The outlines of the members of the active document's Global Edit that are not selected, in
/// their layer's colour: the canvas highlights them while Global Edit is on.
/// The outlines of the members of the active document's Global Edit that are not selected, hold
/// no selected object and sit inside no selected object, in their layer's colour: the canvas
/// highlights them while Global Edit is on.
pub fn highlights(s: &Session) -> Outlines {
let Some(st) = s.active() else { return vec![] };
let Some(ge) = &st.global_edit else { return vec![] };
@@ -690,6 +737,9 @@ pub fn highlights(s: &Session) -> Outlines {
.iter()
.filter_map(|(id, _)| {
let n = st.doc.node(*id).filter(|n| !holds(n, &st.selection.objects))?;
if st.doc.ancestry(*id).is_some_and(|a| a.iter().any(|p| st.selection.objects.contains(p))) {
return None;
}
let path = match n.stroke_path() {
Some(p) => p,
None => n.geometric_bounds_with(&symbols)?.to_path(0.1),
+132
View File
@@ -391,6 +391,138 @@ fn undo_and_redo_drop_the_members_the_document_lacks() {
assert_eq!(members(&s), None);
}
#[test]
fn an_object_left_out_keeps_its_own_shape_while_the_others_follow() {
let mut s = session();
let [a, b, c, ..] = squares(&mut s);
assert_eq!(start(&mut s, a, json!({})), [a, b, c]);
let steps = s.doc().unwrap().history.undo.len();
let r = s.execute("select.globalEditExclude", &json!({"id": b.0})).unwrap();
assert_eq!(r, json!({"active": true, "count": 2, "ids": [a.0, c.0]}));
assert_eq!(members(&s), Some(vec![a, c]));
assert_eq!(crate::cmd::globaledit::highlights(&s).len(), 1, "the copy is no longer highlighted");
assert_eq!(s.doc().unwrap().history.undo.len(), steps, "not an undo step");
assert_eq!(s.doc().unwrap().selection.objects, [a], "the selection stays");
let b_was = node(&s, b);
s.execute("object.setBounds", &json!({"height": 50, "reference": 0})).unwrap();
assert!(close(bounds(&s, c), Rect::new(500.0, 0.0, 520.0, 10.0)), "{:?}", bounds(&s, c));
assert_eq!(node(&s, b), b_was, "the copy left out keeps its shape");
}
/// A Shift-drag with the Selection tool from `from` to `to`.
fn shift_drag(s: &mut Session, from: (f64, f64), to: (f64, f64)) {
use vectorcraft_tools::{Mods, PointerEvent, PointerKind};
let v = ViewInfo::default();
s.select_tool("selection", v).unwrap();
let steps = if from == to {
vec![(PointerKind::Down, from), (PointerKind::Up, to)]
} else {
vec![(PointerKind::Down, from), (PointerKind::Drag, to), (PointerKind::Up, to)]
};
for (kind, (x, y)) in steps {
s.pointer(&PointerEvent::new(kind, x, y).with_mods(Mods { shift: true, ..Mods::default() }), v).unwrap();
}
}
/// A Shift-click with the Selection tool at (`x`, `y`).
fn shift_click(s: &mut Session, x: f64, y: f64) {
shift_drag(s, (x, y), (x, y));
}
#[test]
fn a_shift_click_on_a_highlighted_object_leaves_it_out() {
let mut s = session();
let [a, b, c, ..] = squares(&mut s);
start(&mut s, a, json!({}));
// A Shift-drag that starts on the copy selects it and moves it with the selection, and leaves
// the copy in.
shift_drag(&mut s, (350.0, 50.0), (390.0, 50.0));
assert_eq!(members(&s), Some(vec![a, b, c]), "a drag leaves nothing out");
assert_eq!(s.doc().unwrap().selection.objects, [a, b]);
assert!(bounds(&s, a).x0 > 0.0, "the selection moved: {:?}", bounds(&s, a));
assert!(bounds(&s, b).x0 > 300.0, "the copy moved with it: {:?}", bounds(&s, b));
s.execute("edit.undo", &json!({})).unwrap();
sel(&mut s, &[a]);
shift_click(&mut s, 350.0, 50.0);
assert_eq!(members(&s), Some(vec![a, c]), "the copy is left out");
assert_eq!(s.doc().unwrap().selection.objects, [a], "and not selected");
// A Shift-click on the object the edit started from deselects it, as before.
shift_click(&mut s, 50.0, 50.0);
assert_eq!(members(&s), Some(vec![a, c]));
assert!(s.doc().unwrap().selection.objects.is_empty());
shift_click(&mut s, 510.0, 10.0);
assert_eq!(members(&s), None, "one object left: Global Edit ends");
assert!(s.doc().unwrap().selection.objects.is_empty());
// With Global Edit off, a Shift-click selects, as before.
shift_click(&mut s, 350.0, 50.0);
assert_eq!(s.doc().unwrap().selection.objects, [b]);
}
#[test]
fn a_shift_click_inside_a_matched_group_leaves_the_whole_group_out() {
let mut s = session();
let (g, _) = card(&mut s, 0.0, 1.0);
let (copy, _) = card(&mut s, 300.0, 1.0);
let (small, _) = card(&mut s, 500.0, 0.5);
assert_eq!(start(&mut s, g, json!({})), [g, copy, small]);
// On the circle in the copy.
shift_click(&mut s, 370.0, 20.0);
assert_eq!(members(&s), Some(vec![g, small]));
assert_eq!(s.doc().unwrap().selection.objects, [g]);
// An object inside a member that is selected: a Shift-click deselects it, as before.
let inner = kids(&s, small)[0];
sel(&mut s, &[inner]);
shift_click(&mut s, 505.0, 5.0);
assert_eq!(members(&s), Some(vec![g, small]));
}
#[test]
fn a_shift_click_on_a_member_inside_a_selected_group_deselects_the_group() {
let mut s = session();
let [a, b, c, wide, _] = squares(&mut s);
let pair = group_of(&mut s, &[b, wide]);
let all = start(&mut s, a, json!({}));
assert_eq!(all, [a, c, b], "the copy in the group comes after the small square");
sel(&mut s, &[pair]);
assert_eq!(crate::cmd::globaledit::highlights(&s).len(), 2, "the copy inside the selected group is not highlighted");
shift_click(&mut s, 350.0, 50.0);
assert_eq!(members(&s), Some(all), "nothing is left out");
assert!(s.doc().unwrap().selection.objects.is_empty(), "the group is deselected");
}
#[test]
fn leaving_out_all_but_one_ends_global_edit_and_the_first_object_stays() {
let mut s = session();
let [a, b, c, wide, _] = squares(&mut s);
let e = s.execute("select.globalEditExclude", &json!({"id": b.0})).unwrap_err().to_string();
assert!(e.contains("Global Edit is off"), "{e}");
start(&mut s, a, json!({}));
for id in [a, wide] {
let e = s.execute("select.globalEditExclude", &json!({"id": id.0})).unwrap_err().to_string();
assert!(e.contains("other than the one it started from"), "{id}: {e}");
}
assert_eq!(members(&s), Some(vec![a, b, c]), "nothing left out");
s.execute("select.globalEditExclude", &json!({"id": c.0})).unwrap();
let r = s.execute("select.globalEditExclude", &json!({"id": b.0})).unwrap();
assert_eq!(r, json!({"active": false, "count": 0, "ids": []}));
assert_eq!(members(&s), None, "one object left: Global Edit ends");
assert!(crate::cmd::globaledit::highlights(&s).is_empty());
}
#[test]
fn a_group_left_out_keeps_its_objects_while_the_others_follow() {
let mut s = session();
let (g, [_, dot, _]) = card(&mut s, 0.0, 1.0);
let (copy, [_, cdot, _]) = card(&mut s, 300.0, 1.0);
let (small, [_, sdot, _]) = card(&mut s, 500.0, 0.5);
assert_eq!(start(&mut s, g, json!({})), [g, copy, small]);
s.execute("select.globalEditExclude", &json!({"id": copy.0})).unwrap();
let was = fill(&s, cdot);
sel(&mut s, &[dot]);
s.execute("paint.setFill", &json!({"color": "#12ab34"})).unwrap();
assert_eq!((fill(&s, sdot), fill(&s, cdot)), ("#12ab34".to_string(), was));
}
/// Two 100 pt squares and a 20 pt one made after `begin`, all three in a Global Edit; `put_back`
/// takes the document back to before the small one. The small square leaves the Global Edit, and
/// a square made under its freed id is not edited with the others.
+1
View File
@@ -328,6 +328,7 @@ impl Session {
screen: view.screen,
plane_widget: self.prefs.perspective_widget.show.then_some(self.prefs.perspective_widget.position),
precise_cursors: self.precise_cursors(),
global_edit: st.global_edit.as_ref().map_or(&[], |g| g.members.as_slice()),
};
let tool = &mut self.tool;
match crate::guard::catch_panic(|| f(tool.as_mut(), &cx)) {
+3
View File
@@ -256,6 +256,9 @@ pub struct ToolContext<'a> {
/// General › Use Precise Cursors, or Caps Lock while it is on: the drawing tools' pointers are
/// crosshairs ([`crate::Cursor::precise`]), and the Blob Brush and the Eraser show no tip outline.
pub precise_cursors: bool,
/// The objects of the Global Edit in progress (Select › Start Global Edit), the one it started
/// from first, each with its transform; empty while Global Edit is off.
pub global_edit: &'a [(NodeId, vectorcraft_geom::Affine)],
}
impl ToolContext<'_> {
+1
View File
@@ -508,6 +508,7 @@ pub(crate) mod testutil {
screen: None,
plane_widget: Some(Default::default()),
precise_cursors: false,
global_edit: &[],
}
}
}
+72 -17
View File
@@ -32,6 +32,8 @@
//! With Smart Guides and Invoke Distance Guides on, Alt/Option held over another object shows how
//! far the selection is from it, and over an empty part of an artboard how far it is from the
//! artboard's edges ([`crate::guides::distance_guides`]).
//! While Global Edit is on, a Shift-click on a highlighted object of it, or on an object inside
//! one, leaves that object out of the edit (`select.globalEditExclude`).
use std::cell::RefCell;
use std::collections::HashMap;
@@ -73,6 +75,11 @@ enum State {
/// guides: released without a drag, it becomes the key object that Align aligns to if
/// `can_be_key` allows it (the key again: no key).
key: Option<NodeId>,
/// Shift-pressed on this highlighted object of the Global Edit, and the object the press
/// hit: released without a drag, the first leaves the Global Edit
/// (`select.globalEditExclude`); a drag selects the second and moves it with the
/// selection.
exclude: Option<(NodeId, NodeId)>,
},
Scaling {
handle: Handle,
@@ -728,7 +735,7 @@ impl Tool for SelectionTool {
&& cx.select_behind
&& let Some(behind) = object_behind(cx, p)
{
self.state = State::Moving { start: p, press: p, hold: p, began: false, deselect: None, key: None };
self.state = State::Moving { start: p, press: p, hold: p, began: false, deselect: None, key: None, exclude: None };
return vec![Action::Exec("select.set".into(), json!({ "ids": [behind.0] }))];
}
let (hit, scope) = match selected_source_hit(cx, p) {
@@ -739,15 +746,9 @@ impl Tool for SelectionTool {
Some(h) => {
let top = h.top_object(scope);
let mut out = vec![];
let (mut deselect, mut key) = (None, None);
let (mut deselect, mut key, mut exclude) = (None, None, None);
if m.shift {
// A Shift-click takes a selected object out of the selection when it
// is released; a Shift-drag moves the selection, constrained.
if cx.selection.contains(top) {
deselect = Some(top);
} else {
out.push(Action::Exec("select.toggle".into(), json!({ "id": top.0 })));
}
(deselect, exclude) = shift_press(cx, &h, top, &mut out);
} else if !cx.selection.contains(top) {
out.push(Action::Exec("select.set".into(), json!({ "ids": [top.0] })));
} else if (cx.selection.objects.len() > 1 || !cx.selection.guides.is_empty()) && !m.cmd && !m.alt {
@@ -756,7 +757,7 @@ impl Tool for SelectionTool {
// Grabbed by no anchor or centre, a press on a path's stroke or beside its
// edge holds the path there, and that point of it snaps (#3701).
let hold = if grabbed.is_some() { grab } else { hold_point(cx, &h, p) };
self.state = State::Moving { start: grab, press: p, hold, began: false, deselect, key };
self.state = State::Moving { start: grab, press: p, hold, began: false, deselect, key, exclude };
out
}
None => {
@@ -765,8 +766,8 @@ impl Tool for SelectionTool {
}
}
}
(PointerKind::Drag, State::Moving { start, press, hold, began, .. }) => {
self.drag_moving(cx, p, m, ev.backquote, (start, press, hold, began))
(PointerKind::Drag, State::Moving { start, press, hold, began, exclude, .. }) => {
self.drag_moving(cx, p, m, ev.backquote, (start, press, hold, began), exclude)
}
(PointerKind::Drag, State::Scaling { handle, bx, areas }) => {
self.last_backquote = ev.backquote;
@@ -851,7 +852,9 @@ impl Tool for SelectionTool {
self.state = State::Idle;
b.finish()
}
(PointerKind::Up, State::Moving { began, deselect, key, .. }) => self.release_move(cx, ev.backquote, began, deselect, key),
(PointerKind::Up, State::Moving { began, deselect, key, exclude, .. }) => {
self.release_move(cx, ev.backquote, began, deselect, key, exclude)
}
(PointerKind::Up, State::Scaling { .. } | State::Rotating { .. }) => {
let mut out = if ev.backquote || self.last_backquote {
self.last_box_pos.map_or_else(Vec::new, |pos| self.pointer(cx, &PointerEvent { kind: PointerKind::Drag, pos, ..*ev }))
@@ -999,6 +1002,34 @@ fn object_behind(cx: &ToolContext, p: Point) -> Option<NodeId> {
stack.get(next).or(stack.first()).copied()
}
/// A Shift-press on `top` (hit by `h`): a Shift-click takes a selected object out of the selection
/// when it is released, and a Shift-drag moves the selection, constrained. On an object of the
/// Global Edit that is highlighted, the release leaves that object out of the edit instead, and a
/// drag selects `top` when it starts. Returns the object to deselect and the object to leave out
/// on release (with `top`); adds to `out` the action that selects `top` at once.
fn shift_press(cx: &ToolContext, h: &Hit, top: NodeId, out: &mut Vec<Action>) -> (Option<NodeId>, Option<(NodeId, NodeId)>) {
if let Some(member) = global_edit_member(cx, h) {
(None, Some((member, top)))
} else if cx.selection.contains(top) {
(Some(top), None)
} else {
out.push(Action::Exec("select.toggle".into(), json!({ "id": top.0 })));
(None, None)
}
}
/// The object of the Global Edit in progress that the click `h` lands on or inside, when it is
/// highlighted: neither the object the edit started from nor one that is selected, holds a
/// selected object or sits inside a selected object (`select.globalEditExclude` leaves it out of
/// the edit).
fn global_edit_member(cx: &ToolContext, h: &Hit) -> Option<NodeId> {
let others = cx.global_edit.get(1..)?;
let member = h.ancestry.iter().chain([&h.leaf]).find(|id| others.iter().any(|(m, _)| m == *id)).copied()?;
let holds = cx.selection.objects.iter().any(|s| cx.doc.ancestry(*s).is_some_and(|a| a.contains(&member)));
let inside = cx.doc.ancestry(member).is_some_and(|a| a.iter().any(|id| cx.selection.contains(*id)));
(!holds && !inside).then_some(member)
}
/// Can a click make `id` the key object? `select.key` takes only a selected top-level object. In
/// isolation mode a click can pick a sublayer (inside an isolated layer), a compound-path member
/// (inside an isolated compound path) or an object inside another selected object (inside an
@@ -1076,11 +1107,24 @@ pub(crate) fn handle_cursor(h: Handle, angle: f64) -> Cursor {
mod tests;
impl SelectionTool {
fn release_move(&mut self, cx: &ToolContext, backquote: bool, began: bool, deselect: Option<NodeId>, key: Option<NodeId>) -> Vec<Action> {
fn release_move(
&mut self,
cx: &ToolContext,
backquote: bool,
began: bool,
deselect: Option<NodeId>,
key: Option<NodeId>,
exclude: Option<(NodeId, NodeId)>,
) -> Vec<Action> {
self.state = State::Idle;
self.measure = None;
self.guides.clear();
self.moving = None;
// A Shift-click (no drag) on a highlighted object of the Global Edit leaves it out.
if let (false, Some((id, _))) = (began, exclude) {
self.last_move = None;
return vec![Action::Exec("select.globalEditExclude".into(), json!({ "id": id.0 }))];
}
let changed_mode = self.last_move.as_ref().is_some_and(|params| params.get("objects").is_some()) != backquote;
let last = self.last_move.take().map(|mut params| {
if backquote {
@@ -1286,7 +1330,7 @@ impl SelectionTool {
/// grabs the selection by that art (at `grab`), as a press on its visible part does.
fn press_nothing(&mut self, cx: &ToolContext, p: Point, grab: Point, m: Mods) {
self.state = if !m.shift && selected_hidden_at(cx.doc, p, cx.selection_hit_options(), &cx.selection.objects).is_some() {
State::Moving { start: grab, press: p, hold: grab, began: false, deselect: None, key: None }
State::Moving { start: grab, press: p, hold: grab, began: false, deselect: None, key: None, exclude: None }
} else {
State::Marquee { start: p, cur: p, toggle: m.shift, enclosed: false }
};
@@ -1299,14 +1343,25 @@ impl SelectionTool {
m: Mods,
backquote: bool,
(start, press, hold, began): (Point, Point, Point, bool),
exclude: Option<(NodeId, NodeId)>,
) -> Vec<Action> {
let mut out = vec![];
if !began {
if p.distance(press) < Self::drag_threshold(cx) {
return out;
}
// A Shift-drag from a highlighted object of the Global Edit selects it and moves it
// with the selection: its snap targets are gathered for the selection with it.
self.moving = Some(match exclude {
Some((_, top)) => {
out.push(Action::Exec("select.toggle".into(), json!({ "id": top.0 })));
let mut selection = cx.selection.clone();
selection.add(top);
MoveSnap::new(&ToolContext { selection: &selection, ..*cx })
}
None => MoveSnap::new(cx),
});
out.push(Action::Begin(if m.alt { "Copy".into() } else { "Move".into() }));
self.moving = Some(MoveSnap::new(cx));
}
let mut d = move_delta(start, p, m.shift, cx.constrain_angle);
let unsnapped = d;
@@ -1315,7 +1370,7 @@ impl SelectionTool {
}
// Shift has already constrained `d`: any change is a snap.
self.snapped = (d - unsnapped).hypot() > 1e-9;
self.state = State::Moving { start, press, hold, began: true, deselect: None, key: None };
self.state = State::Moving { start, press, hold, began: true, deselect: None, key: None, exclude: None };
self.measure = cx.measurement_labels.then(|| (p, cx.offset_label(d.x, d.y)));
let mut params = json!({ "matrix": matrix_json(Affine::translate(d)), "copy": m.alt });
if backquote {
+47
View File
@@ -1004,6 +1004,53 @@ fn shift_click_toggles_an_object_on_release() {
assert!(t.pointer(&cx2, &shift(PointerKind::Up, 150.0, 150.0)).is_empty());
}
/// A Shift-drag that starts on a highlighted object of the Global Edit selects that object and
/// moves it; only a Shift-click leaves it out of the edit.
#[test]
fn shift_drag_on_a_global_edit_match_selects_and_moves_it() {
let (d, a, b) = two_rects(Rect::new(300.0, 100.0, 400.0, 200.0));
let p = paint();
let s = Selection::default();
let members = [(a, Affine::IDENTITY), (b, Affine::IDENTITY)];
let cx = ToolContext { global_edit: &members, ..cx(&d, &s, &p) };
let shift = |kind, x, y| ev(kind, x, y).with_mods(Mods { shift: true, ..Mods::default() });
let mut t = SelectionTool::default();
assert!(t.pointer(&cx, &shift(PointerKind::Down, 350.0, 150.0)).is_empty());
let mut actions = t.pointer(&cx, &shift(PointerKind::Drag, 390.0, 150.0));
assert_eq!(actions[..2], [Action::Exec("select.toggle".into(), json!({"id": b.0})), Action::Begin("Move".into())]);
actions.extend(t.pointer(&cx, &shift(PointerKind::Drag, 395.0, 150.0)));
actions.extend(t.pointer(&cx, &shift(PointerKind::Up, 395.0, 150.0)));
assert!(
actions.iter().any(|a| matches!(a, Action::Preview(c, v) if c == "object.transform" && v["matrix"][4].as_f64().is_some_and(|x| x > 39.0))),
"moved right: {actions:?}"
);
assert_eq!(actions.last(), Some(&Action::Commit));
assert!(!actions.iter().any(|a| matches!(a, Action::Exec(c, _) if c == "select.globalEditExclude")), "{actions:?}");
// A Shift-click on it leaves it out instead.
let mut t = SelectionTool::default();
assert!(t.pointer(&cx, &shift(PointerKind::Down, 350.0, 150.0)).is_empty());
assert_eq!(t.pointer(&cx, &shift(PointerKind::Up, 350.0, 150.0)), vec![Action::Exec("select.globalEditExclude".into(), json!({"id": b.0}))]);
}
/// The first preview of a Shift-drag from a Global Edit match snaps, the match's bounds taken in
/// with the selection's: with Snap to Grid, a move of 13 right brings the right edge (413) onto
/// the gridline 414.
#[test]
fn shift_drag_on_a_global_edit_match_snaps_from_its_first_event() {
let (d, a, b) = two_rects(Rect::new(300.0, 100.0, 400.0, 200.0));
let p = paint();
let s = Selection::default();
let members = [(a, Affine::IDENTITY), (b, Affine::IDENTITY)];
let cx = ToolContext { global_edit: &members, snap_to_grid: true, ..cx(&d, &s, &p) };
let shift = |kind, x, y| ev(kind, x, y).with_mods(Mods { shift: true, ..Mods::default() });
let mut t = SelectionTool::default();
assert!(t.pointer(&cx, &shift(PointerKind::Down, 350.0, 150.0)).is_empty());
let actions = t.pointer(&cx, &shift(PointerKind::Drag, 363.0, 150.0));
let Some(Action::Preview(_, v)) = actions.get(2) else { panic!("{actions:?}") };
let m = Vec2::new(v["matrix"][4].as_f64().unwrap(), v["matrix"][5].as_f64().unwrap());
assert!(moved_by(m, (14.0, 0.0)), "{m:?}");
}
/// #541: a click on one object of a selection of several makes it the key object; a click on
/// the key again lets it go. A drag moves them all, a lone object has no key unless ruler
/// guides are selected with it.
+1
View File
@@ -5617,6 +5617,7 @@ axis Y Axis Y ос
Create New Set Създаване на нов набор
Stop Global Edit Край на глобалното редактиране
Global Edit Options Опции за глобално редактиране
Leave Out of Global Edit Изключване от глобалното редактиране
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Глобалното редактиране работи с пътища, съставни пътища, екземпляри на символи и групи от тях
Match Съвпадение
On Artboards В работните области
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5640,6 +5640,7 @@ axis Y Axis Y osa
Create New Set Napravi novi skup
Stop Global Edit Zaustavi globalno uređivanje
Global Edit Options Opcije globalnog uređivanja
Leave Out of Global Edit Isključi iz globalnog uređivanja
Match Podudaranje
On Artboards Na radnim pločama
All Artboards Sve radne ploče
@@ -5648,6 +5649,8 @@ axis Y Axis Y osa
@msg select one object odaberite jedan objekat
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Globalno uređivanje radi na putanjama, složenim putanjama, instancama simbola i njihovim grupama
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nisu pronađeni slični objekti (rotirane ili zrcaljene kopije se ne podudaraju); promijenite opcije globalnog uređivanja i pokušajte ponovo
@msg Global Edit is off Globalno uređivanje je isključeno
@msg only an object of the Global Edit other than the one it started from can be left out iz globalnog uređivanja može se isključiti samo objekt koji nije onaj od kojeg je počelo
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all ili raspon poput 1, 3-5, a ne {v}
Add to Symbols Dodaj u simbole
Basic Shapes Osnovni oblici
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -1697,6 +1697,7 @@ digits Malayalam Malaiàlam
Global Adjust Ajust global
Global Brush Dimensions Dimensions globals del pinzell
Global Edit Options Opcions globals d’edició
Leave Out of Global Edit Exclou de l’edició global
Global Process Color Color de procés global
Global Process Color, CMYK Color de procés global, CMYK
Glow Amount Quantitat de resplendor
@@ -5236,6 +5237,8 @@ digits Malayalam Malaiàlam
@msg no selected slice in the image: select slices, or save all of them no hi ha cap tall seleccionat a la imatge: seleccioneu talls o deseu-los tots
@msg no selection bounds no hi ha límits de selecció
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again no s’ha trobat cap objecte similar (les còpies girades o reflexades no coincideixen); canvieu les opcions d’edició global i torneu-ho a provar
@msg Global Edit is off L’edició global no està activa
@msg only an object of the Global Edit other than the one it started from can be left out només es pot excloure un objecte de l’edició global que no sigui aquell amb què va començar
@msg no slice selected no hi ha cap tall seleccionat
@msg no such action no existeix aquesta acció
@msg no such action set no existeix aquest conjunt d’accions
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5732,6 +5732,7 @@ axis Y Axis osa Y
Create New Set Vytvořit novou sadu
Stop Global Edit Ukončit globální úpravy
Global Edit Options Možnosti globálních úprav
Leave Out of Global Edit Vyřadit z globálních úprav
Match Shoda
On Artboards Na pracovních plochách
All Artboards Všechny pracovní plochy
@@ -5740,6 +5741,8 @@ axis Y Axis osa Y
@msg select one object vyberte jeden objekt
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Globální úpravy pracují s cestami, složenými cestami, instancemi symbolů a jejich skupinami
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nebyly nalezeny žádné podobné objekty (otočené a zrcadlené kopie se nevyhledávají); změňte možnosti globálních úprav a zkuste to znovu
@msg Global Edit is off Globální úpravy nejsou zapnuté
@msg only an object of the Global Edit other than the one it started from can be left out z globálních úprav lze vyřadit jen jiný objekt než ten, od kterého začaly
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all nebo rozsah, například 1, 3-5, ne {v}
Add to Symbols Přidat do symbolů
Basic Shapes Základní tvary
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5634,6 +5634,7 @@ axis Y Axis Y-akse
Create New Set Opret nyt sæt
Stop Global Edit Stop global redigering
Global Edit Options Indstillinger for global redigering
Leave Out of Global Edit Udelad fra global redigering
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Global redigering virker på stier, sammensatte stier, symbolforekomster og grupper af dem
Match Match
On Artboards På tegneflader
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5314,6 +5314,7 @@ axis Y Axis Y-Achse
Create New Set Neuen Satz erstellen
Stop Global Edit Globale Bearbeitung beenden
Global Edit Options Optionen für globale Bearbeitung
Leave Out of Global Edit Von globaler Bearbeitung ausschließen
Match Übereinstimmung
On Artboards Auf Zeichenflächen
All Artboards Alle Zeichenflächen
@@ -5322,6 +5323,8 @@ axis Y Axis Y-Achse
@msg select one object wählen Sie ein Objekt aus
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Globale Bearbeitung funktioniert mit Pfaden, zusammengesetzten Pfaden, Symbolinstanzen und Gruppen daraus
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again keine ähnlichen Objekte gefunden (gedrehte oder gespiegelte Kopien werden nicht erkannt); ändern Sie die Optionen für globale Bearbeitung und versuchen Sie es erneut
@msg Global Edit is off Globale Bearbeitung ist nicht aktiv
@msg only an object of the Global Edit other than the one it started from can be left out nur ein Objekt der globalen Bearbeitung außer dem, mit dem sie begonnen hat, kann ausgeschlossen werden
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all oder ein Bereich wie 1, 3-5, nicht {v}
Add to Symbols Zu Symbolen hinzufügen
Basic Shapes Grundformen
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5683,6 +5683,7 @@ axis Y Axis Άξονας Y
Create New Set Δημιουργία νέου συνόλου
Stop Global Edit Τερματισμός καθολικής επεξεργασίας
Global Edit Options Επιλογές καθολικής επεξεργασίας
Leave Out of Global Edit Εξαίρεση από την καθολική επεξεργασία
Match Αντιστοίχιση
On Artboards Σε πίνακες σχεδίασης
All Artboards Όλοι οι πίνακες σχεδίασης
@@ -5691,6 +5692,8 @@ axis Y Axis Άξονας Y
@msg select one object επιλέξτε ένα αντικείμενο
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Η καθολική επεξεργασία λειτουργεί σε διαδρομές, σύνθετες διαδρομές, παρουσίες συμβόλων και ομάδες τους
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again δεν βρέθηκαν παρόμοια αντικείμενα (τα περιστραμμένα ή κατοπτρισμένα αντίγραφα δεν εντοπίζονται)· αλλάξτε τις επιλογές καθολικής επεξεργασίας και δοκιμάστε ξανά
@msg Global Edit is off Η καθολική επεξεργασία δεν είναι ενεργή
@msg only an object of the Global Edit other than the one it started from can be left out μπορεί να εξαιρεθεί μόνο ένα αντικείμενο της καθολικής επεξεργασίας εκτός από εκείνο με το οποίο ξεκίνησε
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all ή ένα εύρος όπως 1, 3-5, όχι {v}
Add to Symbols Προσθήκη στα σύμβολα
Basic Shapes Βασικά σχήματα
Can't render this file because it contains an unexpected character in line 163 and column 5.
+3
View File
@@ -5673,6 +5673,7 @@ axis Y Axis Eje Y
Create New Set Crear nuevo conjunto
Stop Global Edit Detener edición global
Global Edit Options Opciones de edición global
Leave Out of Global Edit Excluir de la edición global
Match Coincidir
On Artboards En mesas de trabajo
All Artboards Todas las mesas de trabajo
@@ -5681,6 +5682,8 @@ axis Y Axis Eje Y
@msg select one object seleccione un objeto
@msg Global Edit works on paths, compound paths, symbol instances and groups of them La edición global funciona con trazados, trazados compuestos, instancias de símbolos y grupos de ellos
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again no se encontraron objetos similares (las copias giradas o reflejadas no coinciden); cambie las opciones de edición global e inténtelo de nuevo
@msg Global Edit is off La edición global no está activa
@msg only an object of the Global Edit other than the one it started from can be left out solo se puede excluir un objeto de la edición global distinto de aquel con el que empezó
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all o un intervalo como 1, 3-5, no {v}
Add to Symbols Agregar a símbolos
Basic Shapes Formas básicas
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5762,6 +5762,7 @@ axis Y Axis Y-akseli
Create New Set Luo uusi sarja
Stop Global Edit Lopeta yleismuokkaus
Global Edit Options Yleismuokkauksen asetukset
Leave Out of Global Edit Jätä pois yleismuokkauksesta
Match Vastaavuus
On Artboards Piirtoalustoilla
All Artboards Kaikki piirtoalustat
@@ -5770,6 +5771,8 @@ axis Y Axis Y-akseli
@msg select one object valitse yksi objekti
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Yleismuokkaus toimii poluille, yhdistelmäpoluille, symbolien esiintymille ja niiden ryhmille
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again samankaltaisia objekteja ei löytynyt (kierrettyjä tai peilattuja kopioita ei tunnisteta); muuta yleismuokkauksen asetuksia ja yritä uudelleen
@msg Global Edit is off Yleismuokkaus ei ole käynnissä
@msg only an object of the Global Edit other than the one it started from can be left out yleismuokkauksesta voi jättää pois vain muun objektin kuin sen, josta se alkoi
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all tai alue, kuten 1, 3-5, ei {v}
Add to Symbols Lisää symboleihin
Basic Shapes Perusmuodot
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5727,6 +5727,7 @@ axis Y Axis Axe Y
Create New Set Créer un jeu
Stop Global Edit Arrêter la modification globale
Global Edit Options Options de modification globale
Leave Out of Global Edit Exclure de la modification globale
Match Correspondance
On Artboards Sur les plans de travail
All Artboards Tous les plans de travail
@@ -5735,6 +5736,8 @@ axis Y Axis Axe Y
@msg select one object sélectionnez un seul objet
@msg Global Edit works on paths, compound paths, symbol instances and groups of them La modification globale s’applique aux tracés, aux tracés transparents, aux occurrences de symboles et aux groupes de ces objets
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again aucun objet similaire trouvé (les copies pivotées ou symétriques ne sont pas reconnues) ; modifiez les options de modification globale et réessayez
@msg Global Edit is off La modification globale n’est pas active
@msg only an object of the Global Edit other than the one it started from can be left out seul un objet de la modification globale autre que celui à partir duquel elle a commencé peut être exclu
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards` : all ou une plage telle que 1, 3-5, et non {v}
Add to Symbols Ajouter aux symboles
Basic Shapes Formes de base
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5715,6 +5715,7 @@ axis Y Axis Y tengely
Create New Set Új készlet létrehozása
Stop Global Edit Globális szerkesztés befejezése
Global Edit Options Globális szerkesztés beállításai
Leave Out of Global Edit Kizárás a globális szerkesztésből
Match Egyezés
On Artboards Rajztáblákon
All Artboards Minden rajztábla
@@ -5723,6 +5724,8 @@ axis Y Axis Y tengely
@msg select one object jelöljön ki egy objektumot
@msg Global Edit works on paths, compound paths, symbol instances and groups of them A globális szerkesztés útvonalakon, összetett útvonalakon, szimbólumpéldányokon és ezek csoportjain működik
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nem található hasonló objektum (az elforgatott vagy tükrözött másolatok nem számítanak egyezésnek); módosítsa a globális szerkesztés beállításait, és próbálja újra
@msg Global Edit is off A globális szerkesztés nincs bekapcsolva
@msg only an object of the Global Edit other than the one it started from can be left out a globális szerkesztésből csak a kiindulási objektumtól eltérő objektum zárható ki
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all vagy egy tartomány, például 1, 3-5, nem {v}
Add to Symbols Hozzáadás a szimbólumokhoz
Basic Shapes Alapalakzatok
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5634,6 +5634,7 @@ axis Y Axis Sumbu Y
Create New Set Buat set baru
Stop Global Edit Hentikan edit global
Global Edit Options Opsi edit global
Leave Out of Global Edit Keluarkan dari edit global
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Edit global berfungsi pada jalur, jalur gabungan, instans simbol, dan grupnya
Match Cocokkan
On Artboards Di artboard
Can't render this file because it contains an unexpected character in line 2210 and column 5.
+3
View File
@@ -5348,6 +5348,7 @@ axis Y Axis Asse Y
Create New Set Crea nuovo set
Stop Global Edit Interrompi modifica globale
Global Edit Options Opzioni modifica globale
Leave Out of Global Edit Escludi dalla modifica globale
Match Corrispondenza
On Artboards Sulle tavole da disegno
All Artboards Tutte le tavole da disegno
@@ -5356,6 +5357,8 @@ axis Y Axis Asse Y
@msg select one object selezionate un oggetto
@msg Global Edit works on paths, compound paths, symbol instances and groups of them La modifica globale funziona su tracciati, tracciati composti, istanze di simboli e gruppi di essi
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nessun oggetto simile trovato (le copie ruotate o riflesse non vengono riconosciute); cambiate le opzioni di modifica globale e riprovate
@msg Global Edit is off La modifica globale non è attiva
@msg only an object of the Global Edit other than the one it started from can be left out si può escludere solo un oggetto della modifica globale diverso da quello da cui è partita
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all o un intervallo come 1, 3-5, non {v}
Add to Symbols Aggiungi ai simboli
Basic Shapes Forme di base
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5413,6 +5413,7 @@ axis Y Axis Y 軸
Create New Set 新規セットを作成
Stop Global Edit グローバル編集を終了
Global Edit Options グローバル編集オプション
Leave Out of Global Edit グローバル編集から除外
Match 一致
On Artboards アートボード上
All Artboards すべてのアートボード
@@ -5421,6 +5422,8 @@ axis Y Axis Y 軸
@msg select one object オブジェクトを 1 つ選択してください
@msg Global Edit works on paths, compound paths, symbol instances and groups of them グローバル編集はパス、複合パス、シンボルインスタンスとそれらのグループに使用できます
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again 類似するオブジェクトが見つかりません(回転または反転したコピーは一致しません)。グローバル編集オプションを変更してもう一度試してください
@msg Global Edit is off グローバル編集はオフです
@msg only an object of the Global Edit other than the one it started from can be left out 除外できるのは、グローバル編集を開始したオブジェクト以外のオブジェクトだけです
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all または 1, 3-5 のような範囲を指定してください ({v} ではなく)
Add to Symbols シンボルに追加
Basic Shapes 基本シェイプ
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5646,6 +5646,7 @@ axis Y Axis Y ღერძი
Create New Set ახალი ნაკრების შექმნა
Stop Global Edit გლობალური რედაქტირების დასრულება
Global Edit Options გლობალური რედაქტირების პარამეტრები
Leave Out of Global Edit გლობალური რედაქტირებიდან გამორიცხვა
Match დამთხვევა
On Artboards სამუშაო დაფებზე
All Artboards ყველა სამუშაო დაფა
@@ -5654,6 +5655,8 @@ axis Y Axis Y ღერძი
@msg select one object მონიშნეთ ერთი ობიექტი
@msg Global Edit works on paths, compound paths, symbol instances and groups of them გლობალური რედაქტირება მუშაობს ტრაექტორიებზე, შედგენილ ტრაექტორიებზე, სიმბოლოს ეგზემპლარებსა და მათ ჯგუფებზე
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again მსგავსი ობიექტები ვერ მოიძებნა (შემობრუნებული ან არეკლილი ასლები არ ემთხვევა); შეცვალეთ გლობალური რედაქტირების პარამეტრები და სცადეთ ხელახლა
@msg Global Edit is off გლობალური რედაქტირება გამორთულია
@msg only an object of the Global Edit other than the one it started from can be left out გლობალური რედაქტირებიდან შეიძლება გამოირიცხოს მხოლოდ ობიექტი, რომლითაც ის არ დაწყებულა
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all ან დიაპაზონი, მაგალითად 1, 3-5, და არა {v}
Add to Symbols სიმბოლოებში დამატება
Basic Shapes ძირითადი ფიგურები
Can't render this file because it is too large.
+3
View File
@@ -5309,6 +5309,7 @@ axis Y Axis Y축
Create New Set 새 세트 만들기
Stop Global Edit 전체 편집 중지
Global Edit Options 전체 편집 옵션
Leave Out of Global Edit 전체 편집에서 제외
Match 일치
On Artboards 아트보드에서
All Artboards 모든 아트보드
@@ -5317,6 +5318,8 @@ axis Y Axis Y축
@msg select one object 개체 하나를 선택하십시오
@msg Global Edit works on paths, compound paths, symbol instances and groups of them 전체 편집은 패스, 컴파운드 패스, 심볼 인스턴스 및 이들의 그룹에서 작동합니다
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again 유사한 개체를 찾을 수 없습니다(회전하거나 반사한 복사본은 일치하지 않습니다). 전체 편집 옵션을 변경한 후 다시 시도하십시오
@msg Global Edit is off 전체 편집이 꺼져 있습니다
@msg only an object of the Global Edit other than the one it started from can be left out 전체 편집을 시작한 개체가 아닌 전체 편집의 개체만 제외할 수 있습니다
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all 또는 1, 3-5 같은 범위여야 합니다({v}이(가) 아님)
Add to Symbols 심볼에 추가
Basic Shapes 기본 모양
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -5678,6 +5678,7 @@ axis Y Axis Y-as
Create New Set Nieuwe set maken
Stop Global Edit Globale bewerking stoppen
Global Edit Options Opties voor globale bewerking
Leave Out of Global Edit Uitsluiten van globale bewerking
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Globale bewerking werkt op paden, samengestelde paden, symboolinstanties en groepen daarvan
Match Overeenkomst
On Artboards Op tekengebieden
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5585,6 +5585,7 @@ axis Y Axis Oś Y
Create New Set Utwórz nowy zestaw
Stop Global Edit Zakończ edycję globalną
Global Edit Options Opcje edycji globalnej
Leave Out of Global Edit Wyklucz z edycji globalnej
Match Dopasowanie
On Artboards Na obszarach kompozycji
All Artboards Wszystkie obszary kompozycji
@@ -5593,6 +5594,8 @@ axis Y Axis Oś Y
@msg select one object zaznacz jeden obiekt
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Edycja globalna działa na ścieżkach, ścieżkach złożonych, wystąpieniach symboli i ich grupach
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nie znaleziono podobnych obiektów (obrócone i odbite kopie nie są dopasowywane); zmień opcje edycji globalnej i spróbuj ponownie
@msg Global Edit is off Edycja globalna jest wyłączona
@msg only an object of the Global Edit other than the one it started from can be left out z edycji globalnej można wykluczyć tylko obiekt inny niż ten, od którego się zaczęła
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all lub zakres, np. 1, 3-5, a nie {v}
Add to Symbols Dodaj do symboli
Basic Shapes Kształty podstawowe
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5647,6 +5647,7 @@ axis Y Axis Eixo Y
Create New Set Criar Novo Conjunto
Stop Global Edit Parar Edição Global
Global Edit Options Opções de Edição Global
Leave Out of Global Edit Deixar Fora da Edição Global
Match Corresponder
On Artboards Nas Mesas de Trabalho
All Artboards Todas as Mesas de Trabalho
@@ -5655,6 +5656,8 @@ axis Y Axis Eixo Y
@msg select one object selecione um objeto
@msg Global Edit works on paths, compound paths, symbol instances and groups of them A edição global funciona com demarcadores, demarcadores compostos, instâncias de símbolos e grupos deles
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again nenhum objeto semelhante encontrado (cópias giradas ou refletidas não são correspondidas); altere as opções de edição global e tente novamente
@msg Global Edit is off A edição global está desativada
@msg only an object of the Global Edit other than the one it started from can be left out só é possível deixar fora da edição global um objeto diferente daquele com que ela começou
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all ou um intervalo como 1, 3-5, não {v}
Add to Symbols Adicionar aos Símbolos
Basic Shapes Formas Básicas
Can't render this file because it contains an unexpected character in line 6 and column 47.
+3
View File
@@ -5343,6 +5343,7 @@ axis Y Axis Ось Y
Create New Set Создать новый набор
Stop Global Edit Завершить глобальное редактирование
Global Edit Options Параметры глобального редактирования
Leave Out of Global Edit Исключить из глобального редактирования
Match Совпадение
On Artboards На монтажных областях
All Artboards Все монтажные области
@@ -5351,6 +5352,8 @@ axis Y Axis Ось Y
@msg select one object выберите один объект
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Глобальное редактирование работает с контурами, составными контурами, экземплярами символов и их группами
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again похожие объекты не найдены (повёрнутые или отражённые копии не учитываются); измените параметры глобального редактирования и повторите попытку
@msg Global Edit is off Глобальное редактирование выключено
@msg only an object of the Global Edit other than the one it started from can be left out исключить из глобального редактирования можно только объект, отличный от того, с которого оно началось
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all или диапазон, например 1, 3-5, а не {v}
Add to Symbols Добавить в символы
Basic Shapes Базовые фигуры
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5755,6 +5755,7 @@ axis Y Axis Y-axel
Create New Set Skapa ny uppsättning
Stop Global Edit Avsluta global redigering
Global Edit Options Alternativ för global redigering
Leave Out of Global Edit Uteslut från global redigering
Match Matcha
On Artboards På ritytor
All Artboards Alla ritytor
@@ -5763,6 +5764,8 @@ axis Y Axis Y-axel
@msg select one object markera ett objekt
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Global redigering fungerar på banor, sammansatta banor, symbolinstanser och grupper av dem
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again inga liknande objekt hittades (roterade eller speglade kopior matchas inte); ändra alternativen för global redigering och försök igen
@msg Global Edit is off Global redigering är inte aktiv
@msg only an object of the Global Edit other than the one it started from can be left out bara ett annat objekt än det som den globala redigeringen började med kan uteslutas
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all eller ett intervall som 1, 3-5, inte {v}
Add to Symbols Lägg till i symboler
Basic Shapes Grundformer
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5689,6 +5689,7 @@ axis Y Axis Y ekseni
Create New Set Yeni Set Oluştur
Stop Global Edit Genel Düzenlemeyi Durdur
Global Edit Options Genel Düzenleme Seçenekleri
Leave Out of Global Edit Genel Düzenlemenin Dışında Bırak
Match Eşleştir
On Artboards Çalışma Yüzeylerinde
All Artboards Tüm Çalışma Yüzeyleri
@@ -5697,6 +5698,8 @@ axis Y Axis Y ekseni
@msg select one object bir nesne seçin
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Genel düzenleme yollar, bileşik yollar, sembol örnekleri ve bunların grupları üzerinde çalışır
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again benzer nesne bulunamadı (döndürülmüş veya yansıtılmış kopyalar eşleşmez); Genel Düzenleme Seçeneklerini değiştirip yeniden deneyin
@msg Global Edit is off Genel düzenleme kapalı
@msg only an object of the Global Edit other than the one it started from can be left out yalnızca genel düzenlemenin başladığı nesne dışındaki bir nesne düzenlemenin dışında bırakılabilir
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all ya da 1, 3-5 gibi bir aralık olmalı, {v} değil
Add to Symbols Sembollere Ekle
Basic Shapes Temel Şekiller
Can't render this file because it contains an unexpected character in line 5 and column 25.
+3
View File
@@ -5328,6 +5328,7 @@ axis Y Axis Вісь Y
Create New Set Створити новий набір
Stop Global Edit Завершити глобальне редагування
Global Edit Options Параметри глобального редагування
Leave Out of Global Edit Виключити з глобального редагування
Match Збіг
On Artboards На монтажних областях
All Artboards Усі монтажні області
@@ -5336,6 +5337,8 @@ axis Y Axis Вісь Y
@msg select one object виберіть один об’єкт
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Глобальне редагування працює з контурами, складеними контурами, екземплярами символів та їхніми групами
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again схожих об’єктів не знайдено (повернуті чи віддзеркалені копії не враховуються); змініть параметри глобального редагування та спробуйте ще раз
@msg Global Edit is off Глобальне редагування вимкнено
@msg only an object of the Global Edit other than the one it started from can be left out виключити з глобального редагування можна лише об’єкт, відмінний від того, з якого воно почалося
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all або діапазон, наприклад 1, 3-5, а не {v}
Add to Symbols Додати до символів
Basic Shapes Базові фігури
Can't render this file because it contains an unexpected character in line 177 and column 5.
+3
View File
@@ -5329,6 +5329,7 @@ axis Y Axis Trục Y
Create New Set Tạo bộ mới
Stop Global Edit Dừng chỉnh sửa toàn cục
Global Edit Options Tùy chọn chỉnh sửa toàn cục
Leave Out of Global Edit Loại khỏi chỉnh sửa toàn cục
Match Khớp
On Artboards Trên các bảng vẽ
All Artboards Tất cả bảng vẽ
@@ -5337,6 +5338,8 @@ axis Y Axis Trục Y
@msg select one object hãy chọn một đối tượng
@msg Global Edit works on paths, compound paths, symbol instances and groups of them Chỉnh sửa toàn cục hoạt động trên đường dẫn, đường dẫn phức hợp, phiên bản biểu tượng và nhóm của chúng
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again không tìm thấy đối tượng tương tự (các bản sao đã xoay hoặc lật không được tính); hãy thay đổi tùy chọn chỉnh sửa toàn cục rồi thử lại
@msg Global Edit is off Chỉnh sửa toàn cục đang tắt
@msg only an object of the Global Edit other than the one it started from can be left out chỉ có thể loại khỏi chỉnh sửa toàn cục một đối tượng khác với đối tượng mà nó bắt đầu
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`: all hoặc một phạm vi như 1, 3-5, không phải {v}
Add to Symbols Thêm vào ký hiệu
Basic Shapes Hình cơ bản
Can't render this file because it contains an unexpected character in line 199 and column 5.
+3
View File
@@ -5570,6 +5570,7 @@ axis Y Axis Y 轴
Create New Set 创建新组
Stop Global Edit 停止全局编辑
Global Edit Options 全局编辑选项
Leave Out of Global Edit 从全局编辑中排除
Match 匹配
On Artboards 在画板上
All Artboards 所有画板
@@ -5578,6 +5579,8 @@ axis Y Axis Y 轴
@msg select one object 请选择一个对象
@msg Global Edit works on paths, compound paths, symbol instances and groups of them 全局编辑适用于路径、复合路径、符号实例及其编组
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again 未找到相似对象(旋转或镜像的副本不会匹配);请更改全局编辑选项后重试
@msg Global Edit is off 全局编辑未开启
@msg only an object of the Global Edit other than the one it started from can be left out 只能从全局编辑中排除起始对象以外的对象
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`:应为 all 或 1, 3-5 这样的范围,而不是 {v}
Add to Symbols 添加到符号
Basic Shapes 基本形状
Can't render this file because it contains an unexpected character in line 2 and column 73.
+3
View File
@@ -5366,6 +5366,7 @@ axis Y Axis Y 軸
Create New Set 建立新組
Stop Global Edit 停止整體編輯
Global Edit Options 整體編輯選項
Leave Out of Global Edit 從整體編輯中排除
Match 符合
On Artboards 在工作區域上
All Artboards 所有工作區域
@@ -5374,6 +5375,8 @@ axis Y Axis Y 軸
@msg select one object 請選取一個物件
@msg Global Edit works on paths, compound paths, symbol instances and groups of them 整體編輯適用於路徑、複合路徑、符號實體及其群組
@msg no similar objects found (rotated or reflected copies are not matched); change the Global Edit Options and try again 找不到相似的物件(旋轉或鏡像的副本不會相符);請變更整體編輯選項後再試一次
@msg Global Edit is off 整體編輯未開啟
@msg only an object of the Global Edit other than the one it started from can be left out 只能從整體編輯中排除起始物件以外的物件
@msg `artboards`: all or a range such as 1, 3-5, not {v} `artboards`:應為 all 或 1, 3-5 這類範圍,而不是 {v}
Add to Symbols 新增至符號
Basic Shapes 基本形狀
Can't render this file because it contains an unexpected character in line 6 and column 47.
+8
View File
@@ -1498,6 +1498,14 @@ Redo, a cancelled drag or scrub), the matches it no longer has leave the Global
are left. An edit that would take any match past the canvas fails with the coordinates-out-of-range error and changes
nothing. Nothing of it is saved.
`select.globalEditExclude {id}` leaves the match `id` out of the Global Edit, as a Shift-click on a highlighted match
(or on an object inside it) with the Selection tool does: it is no longer highlighted, and later edits leave it as it
is. It returns `{active, count, ids}`, the objects left in the Global Edit; with fewer than two left, the Global Edit
ends (`{active: false, count: 0, ids: []}`). The object the Global Edit started from can't be left out, and neither
can an object that is not a match. It is not an undo step. A Shift-drag that starts on a match selects it and moves
it with the selection, and leaves it in the Global Edit. Source: [Update assets quickly with Global
Edit](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html).
## Saved selections
Select → Save Selection… keeps the selected objects under a name, in the document: `select.save {name?}` (default
+2 -1
View File
@@ -121,7 +121,7 @@ dominate; 3D, graphs and automation are specialist). They sum to 105. Feature de
| Area | Weight | Done | Main gaps | One agent (h) | Kind |
|---|---:|---:|---|---:|---|
| Selection, transform & align tools | 6 | 85% | Direct Selection segment click/Delete corrected (#1278); open-path anchor Delete keeps gaps; Select › Start Global Edit finds the paths, compound paths and symbol instances of the selected object's shape (Appearance, Size and artboard options) and gives an edit of one to all of them in proportion, as one undo step (#3521), and on a group the groups like it, an edit inside one reaching the same object in the others (#4939); the Align panel menu's Align to Glyph Bounds › Point Text and Area Text measure type by its glyphs' ink for Align and Distribute (#4231); remaining: Free Transform/Scale/Reflect along a rotated box, Global Edit of rotated copies and excluding a match with Shift-click, transform nuances (no-op anchor Align history regression-tested, #1180 follow-up) | 4–6 | estimated; Global Edit regression-tested (`crates/engine/src/tests/globaledit.rs`; Quick Action, menu label and Escape in `panels::properties::tests::start_global_edit_quick_action_and_escape`) |
| Selection, transform & align tools | 6 | 85% | Direct Selection segment click/Delete corrected (#1278); open-path anchor Delete keeps gaps; Select › Start Global Edit finds the paths, compound paths and symbol instances of the selected object's shape (Appearance, Size and artboard options) and gives an edit of one to all of them in proportion, as one undo step (#3521), and on a group the groups like it, an edit inside one reaching the same object in the others (#4939), and a Shift-click on a highlighted match leaves it out; the Align panel menu's Align to Glyph Bounds › Point Text and Area Text measure type by its glyphs' ink for Align and Distribute (#4231); remaining: Free Transform/Scale/Reflect along a rotated box, Global Edit of rotated copies, transform nuances (no-op anchor Align history regression-tested, #1180 follow-up) | 4–6 | estimated; Global Edit regression-tested (`crates/engine/src/tests/globaledit.rs`; Quick Action, menu label and Escape in `panels::properties::tests::start_global_edit_quick_action_and_escape`) |
| Drawing tools (Pen, Curvature, Pencil, shapes, Shaper) | 7 | 85% | Shaper construction widget, Pencil/Pen nuances, Blob Brush options, Touch Type, Dimension tool; see [ui-parity.md](ui-parity.md) | 8–13 | estimated; Curvature point display regression-tested (#1236, in part); Polygon/Star exact-size count persistence regression-covered; Live Corners' Relative rounding done (#3091), score unchanged |
| Path operations, Pathfinder, Shape Builder, Live Paint | 5 | 80% | Shape Builder on the reporter's file (#937: art past 4,096 segments is cut by lines again; unconfirmed on the file itself); gap detection shares Live Paint’s finder, with a 72 pt custom cap and the existing options; Live Paint Gap Options | 4–8 | estimated; Shape Builder Gap Detection shares Live Paint’s spatial gap finder and temporary merge bridges (#2526); Custom is capped at 72 pt in the existing options dialog (lowered from 86%: open issues); compound attributes regression-tested (#1197); single-result source names survive SVG export/import (#1293), with [SVG ID semantics](https://www.w3.org/TR/SVG2/struct.html#IDAttribute) and original-shape tests; Simplify component selection and preview regression-tested (#1272); Simplify of a Direct Selection part of a path regression-tested (`tests/simplify_part.rs`); Divide open-cutting-path geometry and appearance regression-tested (#1307); the Shape Builder's edge-source grid is bounded in memory and files a segment once per cell (`planar.rs` test `rect_index_holds_a_bounded_number_of_references`); live compound shapes (Make, Alt-click, member Shape Modes, Release, Expand, saved and exported) regression-tested (#4522) |
| Colour, swatches, gradients, patterns, mesh, recolor | 7 | 92% | 30.0's gradient additions are done (dither, perceptual blending, presets); freeform gradients in SVG/PDF, mixed spot/process gradients in SVG, and PDF gradients mixing a spot color with RGB or Lab stops | 4–7 | estimated; Recolor model selection is available through the engine and MCP (#2520), and the dialog's color sliders (HSB, RGB, CMYK, Lab, Tint for a spot color) edit the selected row's new color in its mode, and the Edit tab's Global Adjust sliders (Saturation, Brightness, Temperature, Luminosity, -100% to 100%) change every recolored color at once (lowered from 96%: target moved); unpainted zero-width stroke restoration fixed (#1253), score and hours unchanged; Eyedropper source reads regression-tested (#1251); screen-bounded Recolor Edit chip list (#3416), score and hours unchanged |
@@ -483,6 +483,7 @@ since 2026-10-01.
- **Clipping paths on their outline (G1):** the selection tools (Selection, Direct Selection, Group Selection), Object Highlighting and a right-click take a clipping path on its outline before the art it clips; Select Behind lists the same; the eyedropper, the tools that act on what is drawn and the selection tools' double-clicks, loaded text and guide release take the art. Doc and tool regression tests; score unchanged.
- **Escape in Selection tool drags:** Escape during a Selection tool move or Alt-drag copy, a bounding-box scale or rotation, a marquee, or a corner widget, pie widget or bracket drag cancels it: the art returns to its state at the press, no copy is left, no undo step is added, the release does nothing, and the tool stays chosen. Regression tests: `escape_cancels_*` in `select/tests.rs` and `pie.rs`, and `escape_cancels_a_selection_tool_move_copy_scale_or_rotation` in `tests/selectescape.rs`.
- **Global Edit: Shift-click leaves a match out:** while Global Edit is on, a Shift-click with the Selection tool on a highlighted match, or on an object inside a matched group, leaves that match out (`select.globalEditExclude`): it is no longer highlighted and later edits leave it as it is; the object the edit started from stays, and Global Edit ends when fewer than two matches are left ([Update assets quickly with Global Edit](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)). Regression tests: `a_shift_click_*`, `an_object_left_out_*`, `a_group_left_out_*` and `leaving_out_all_but_one_*` in `crates/engine/src/tests/globaledit.rs`.
- **Pattern-only backquote drags:** With Selection move, bounding-box scale or rotate, or the Rotate, Reflect, Scale or Shear tool, backquote held at mouse release applies the drag matrix to selected pattern paint while object geometry stays in place. The key may be pressed after mouse press; if released before the mouse button, the drag commits as an ordinary transform. Objects without pattern paint stay still; stroke-only patterns and patterned objects in mixed selections take the same matrix. Regression tests: `backquote_move_uses_release_state_and_one_commit` and `backquote_box_scale_and_rotate_use_pattern_matrix_at_release` in `select/tests.rs`, `backquote_drag_previews_and_commits_pattern_only_for_each_transform_tool` in `xform/transform.rs`, `selection_backquote_drag_changes_pattern_fill_and_stroke_without_moving_mixed_art` and `selection_backquote_drag_changes_stroke_only_pattern` in `tests/pattern.rs`, `egui_backtick_drag_uses_key_state_at_release` in `canvas/tests.rs`, and `synthetic_pointer_backquote_keeps_solid_object_in_place` in `control/tests.rs`. Scores and hours unchanged.
- **Existing-guide Escape cancellation (G1):** Escape clears a pending guide press or drag through Selection and Direct/Group Selection, cancelling a begun preview. Eight app-input regressions cover both axes, move/copy, temporary selection-tool borrowing from idle Pen, preselected-guide document/interaction-start selection and Undo/Redo/saved-state preservation, later on-canvas motion/release, Shift-press cancellation, and ordinary-release/Enter/idle controls. Historical focused checks on `8ab574b7` recorded five expected RED failures with three passing controls, then all eight GREEN tests and the Direct Selection Escape control passed. All nine also passed in current full CI. A full CI retry on the five-unit combined source at `a0671777` passed all seven stages with 6,613 test outcomes on 2026-10-14. An earlier full-CI run on the identical source panicked in the randomized `ai_mutated_save_never_panics` test; the cause remains unresolved. This five-fix batch does not claim to repair that failure. The original 1280×960 headless app capture from `b076025a` was reviewed for one vertical Selection guide after Escape; source review found its scene unchanged on `a0671777`. Scores and hours unchanged.
- **Escape in more tool drags:** Escape during an Anchor Point drag, a Mesh tool drag, a Direct Selection drag of a mesh point, a corner widget or the brackets of type on a path, or a Shape Builder drag cancels it: the art goes back to how the press found it, no undo step is added, the release does nothing, and the tool stays chosen. Escape during a Measure tool drag brings back the measurement before it. Regression tests: `escape_cancels_*` in `draw2/anchor/tests.rs`, `meshedit.rs`, `direct/tests.rs`, `builder/tests.rs` and `xform/measure.rs`, and `escape_cancels_an_anchor_point_mesh_shape_builder_or_measure_drag` in `tests/toolescape.rs`.
+1 -1
View File
@@ -305,7 +305,7 @@ next unit in the order a right-click on a ruler lists them, from Feet back to Po
| Actions panel: Delete Selection (bottom bar) deletes the selected set with its actions and keeps the other sets ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html): "Removes the selected action, command, or action set from the panel") | partial | `ui.actionSet.delete`; `panels/actions.rs` tests. VectorCraft's own rules, which the overview does not cover: deleting a set asks first (a selected action is still deleted at once), Shift-click on a set's name selects a range of sets, ≡ › Delete does the same as the button, and Default Actions and the last set can be deleted. Undo does not restore a deleted set, and recorded commands cannot be selected or deleted |
| Actions panel: ≡ › Duplicate copies the selected action or set ([Actions panel overview](https://helpx.adobe.com/illustrator/desktop/automate-visualize-data/automate-actions/actions-panel-overview.html): "Makes a copy of the selected action or set.") | partial | `ui.actionSet.duplicate`; `panels/actions.rs` tests. VectorCraft's own rules, which the overview does not cover: the copy goes right after the original, is named after it with " copy" added and is selected, and a copied set holds copies of all its actions. Undo does not remove the copy, and recorded commands cannot be duplicated |
| Properties panel per context (every selection type's Quick Actions) | partial | one clip group: Edit Clipping Path and Edit Contents, at the top of the panel rather than in Quick Actions (G1); the overview's additional controls ([Properties panel overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/properties-panel-overview.html)): Blend Options (one blend selected, with any tool, #4817) and Merge Live Paint (a Live Paint group with more) in Quick Actions, the Width Profile list (Width tool or a variable-width stroke) and an applied effect's Delete icon in Appearance, Corner and Corner Radius for corners selected with Direct Selection; Variable Font was already there |
| Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs | partial | #3521, `select.globalEdit` / `select.globalEditOptions`, `crates/engine/src/tests/globaledit.rs`; paths, compound paths, symbol instances and groups of them (similar groups, #4939: edits inside one group reach the same object in the others; [public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-groups-with-similar-objects.html)); the Control panel's copy of the options, Shift-click to leave a match out, rotated copies and highlighting matches before the start remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) |
| Select › Start Global Edit / Stop Global Edit; Properties › Quick Actions with the Global Edit Options popover (Match: Appearance, Size; On Artboards: All Artboards or a range, Include Objects On Canvas; Result(s)); Escape stops it; the matched objects are highlighted in their layer colour while it runs; a Shift-click on a highlighted match leaves it out | partial | #3521, `select.globalEdit` / `select.globalEditOptions` / `select.globalEditExclude`, `crates/engine/src/tests/globaledit.rs`; paths, compound paths, symbol instances and groups of them (similar groups, #4939: edits inside one group reach the same object in the others; [public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-groups-with-similar-objects.html)); Shift-click to leave a match out ([public source](https://helpx.adobe.com/ph_fil/illustrator/how-to/global-editing-sensei.html)); the Control panel's copy of the options, rotated copies and highlighting matches before the start remain ([public source](https://helpx.adobe.com/illustrator/desktop/manage-objects/edit-objects/edit-similar-objects.html)) |
| Contextual task bar | done | |
| Control bar with Transform and Stroke popovers | done | |
| Pathfinder shape results keep their source names and SVG IDs | done | #1293; Minus Front keeps the back name, Minus Back the front name; Unite/Intersect/Exclude use the backmost named input; compound output carries it once; geometry and paint unchanged |