Part of #1429: Alt-drags with the selection and transform tools copy the objects once per drag

On each pointer move of an Alt-drag with the Selection, Direct
Selection, Group Selection, Rotate, Scale, Reflect or Shear tool,
Session::preview put the drag's snapshot back and ran object.transform
with copy again, and duplicate_in copied every selected object again.
The Move, Rotate, Scale, Reflect and Shear dialogs with Copy and Preview
on copied every selected object again on each change of a value.

The interaction now keeps the untransformed copies that its first
preview with copy makes of the snapshot. Each later preview that
copies the same objects of the same snapshot transforms only those
copies. The copies keep their ids, and the document, the selection,
the undo step and the journal are the same as before. With 25,000
rectangles selected, a release build of vectorcraft-cli took a median
of 138.5 ms for each Alt-drag move after the first preview and takes
105.7 ms with this change.

Docs: docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-13 02:21:35 +07:00
committed by bflatastic
parent 08c8c11798
commit 571289ddb6
4 changed files with 239 additions and 2 deletions
+42 -2
View File
@@ -269,7 +269,9 @@ pub(crate) fn transform_patterns(s: &mut Session, p: &Value, ids: &[NodeId]) ->
/// Apply `xf` to `ids` (`copy` param: duplicate first; `strokes`/`corners`: see [`scaling`];
/// `patterns`: see [`transform_patterns`]; `typeAreas`: see [`resize_type_area`]). Outside an
/// interaction, records `xf` and the options it used for Transform Again.
/// interaction, records `xf` and the options it used for Transform Again. In an interaction whose
/// document is still its snapshot, a transform with `copy` keeps its copies ([`preview_copies`]),
/// and the next one that copies the same objects of the same snapshot transforms those copies.
///
/// `anchors` (`ids` being the selection): with anchors selected with Direct Selection and no
/// copy, only those anchors transform, their handles with them, and the objects selected whole.
@@ -291,8 +293,19 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
} else if none || (!copy && xf == Affine::IDENTITY) {
if copy { vec![] } else { ids }
} else {
let kept = if copy { preview_copies(s, &ids)? } else { None };
s.edit(label, |d, sel| {
let targets = if copy { duplicate_in(d, sel, &ids, Affine::IDENTITY)? } else { ids.clone() };
let targets = match &kept {
// The document becomes the snapshot with the kept copies, untransformed, and the
// loop below transforms only the copies.
Some(k) => {
*d = Document::clone(&k.doc);
sel.clone_from(&k.selection);
k.ids.clone()
}
None if copy => duplicate_in(d, sel, &ids, Affine::IDENTITY)?,
None => ids.clone(),
};
// Transforms don't move nodes in the tree, so the paths found up front stay right.
let paths = d.index_paths(targets.iter().copied());
for id in &targets {
@@ -318,6 +331,33 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
Ok(json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>() }))
}
/// Returns the copies of `roots` for a transform with `copy` in an interaction whose document is
/// still its snapshot, as in a preview ([`Session::preview`] puts the snapshot back first). These
/// are the copies the interaction keeps when they were made of that snapshot for the same `roots`;
/// otherwise this copies `roots` with [`duplicate_in`] in a clone of the snapshot, untransformed,
/// and the interaction keeps that clone. It returns `None` outside an interaction, and when the
/// document is no longer the snapshot, as for a command run in a typing session or a script batch
/// after an earlier edit there: the transform then copies the current document.
fn preview_copies(s: &mut Session, roots: &[NodeId]) -> Result<Option<Arc<crate::PreviewCopies>>> {
let st = s.doc_mut()?;
let Some(it) = &mut st.interaction else { return Ok(None) };
if !Arc::ptr_eq(&st.doc, &it.doc) {
return Ok(None);
}
if let Some(k) = &it.copies
&& Arc::ptr_eq(&k.from, &it.doc)
&& k.roots == roots
{
return Ok(Some(k.clone()));
}
let mut doc = it.doc.clone();
let mut selection = st.selection.clone();
let ids = duplicate_in(Arc::make_mut(&mut doc), &mut selection, roots, Affine::IDENTITY)?;
let k = Arc::new(crate::PreviewCopies { from: it.doc.clone(), roots: roots.to_vec(), doc, selection, ids });
it.copies = Some(k.clone());
Ok(Some(k))
}
/// Area type resized by its bounding box: `xf` reshapes its frame (the type area) and the text
/// reflows at its size. False for anything else (and type in perspective), which transforms as
/// usual.