mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 16:33:22 -06:00
feat: move and copy ruler guides and art together when selected
This commit is contained in:
@@ -960,7 +960,7 @@ fn persp_edit(
|
||||
return Err(EngineError::Other("select the objects to transform in perspective".into()));
|
||||
}
|
||||
let mut g = grid_of(&s.doc()?.doc);
|
||||
let kept = if copy && !stores_attachments(&s.doc()?.doc) { super::object::preview_copies(s, ids)? } else { None };
|
||||
let kept = if copy && !stores_attachments(&s.doc()?.doc) { super::object::preview_copies(s, ids, false)? } else { None };
|
||||
s.edit(label, |d, sel| {
|
||||
g.adopt_stored_attachments(d);
|
||||
let targets = match &kept {
|
||||
@@ -1164,7 +1164,7 @@ fn plane_move(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// lists attachments of its own (`stores_attachments`).
|
||||
let kept = if objects == "copy" && s.in_interaction() && !stores_attachments(&s.doc()?.doc) {
|
||||
let on = on_plane(&s.doc()?.doc);
|
||||
super::object::preview_copies(s, &on)?
|
||||
super::object::preview_copies(s, &on, false)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -659,6 +659,17 @@ pub(crate) fn guide_move(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Err(bad("guide.move", "no guide selected (or give an index)"));
|
||||
}
|
||||
let (dx, dy, copy) = (f64_or(p, "dx", 0.0), f64_or(p, "dy", 0.0), bool_or(p, "copy", false));
|
||||
if !s.doc()?.selection.is_empty() {
|
||||
let ids = super::edit::selected_roots(s)?;
|
||||
return super::object::apply_transform(
|
||||
s,
|
||||
if copy { "Copy Guide" } else { "Move Guide" },
|
||||
ids,
|
||||
vectorcraft_geom::Affine::translate((dx, dy)),
|
||||
p,
|
||||
true,
|
||||
);
|
||||
}
|
||||
s.edit(if copy { "Copy Guide" } else { "Move Guide" }, |d, sel| {
|
||||
let mut copies = vec![];
|
||||
for &i in &moving {
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
["Object", "Transform"],
|
||||
Some("Cmd+Shift+M"),
|
||||
"{dx, dy, copy?, objects?: bool (Transform Objects, default true; false: the art stays and, with patterns, only its pattern tiles transform), patterns?: bool (Transform Patterns: pattern fills and strokes transform with the art; default: prefs transformPatternTiles), typeAreas?: bool (area type: the frame is reshaped and the text reflows at its size)}. With anchors selected with Direct Selection (and no copy), only those anchors and their handles transform, about the centre of their own box.",
|
||||
has_selection,
|
||||
has_selection_or_guides,
|
||||
move_cmd
|
||||
),
|
||||
cmd!(
|
||||
@@ -297,11 +297,16 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
|
||||
let objects = transforms_objects(p);
|
||||
let mut sc = if Scaling::factor(xf).is_some() { scaling(s, p) } else { Scaling::default() };
|
||||
sc.patterns = transform_patterns(s, p, &ids)?;
|
||||
let c = xf.as_coeffs();
|
||||
let is_translation = (c[0] - 1.0).abs() < 1e-9 && c[1].abs() < 1e-9 && c[2].abs() < 1e-9 && (c[3] - 1.0).abs() < 1e-9;
|
||||
let delta = Vec2::new(c[4], c[5]);
|
||||
let with_guides = objects && ids_param(p, "ids").is_none() && is_translation;
|
||||
let has_guides = with_guides && !s.doc()?.selection.guides.is_empty();
|
||||
// A transform of ids that name no object, or an identity transform without a copy, moves
|
||||
// nothing: the document and its undo history stay as they are, and Transform Again repeats it.
|
||||
// With a copy, the reply lists the copies, and there are none.
|
||||
let d = &s.doc()?.doc;
|
||||
let none = d.index_paths(ids.iter().copied()).is_empty();
|
||||
let none = d.index_paths(ids.iter().copied()).is_empty() && !has_guides;
|
||||
let ids = if anchors && objects && !copy && !s.doc()?.selection.anchors.is_empty() {
|
||||
if xf != Affine::IDENTITY {
|
||||
super::path::transform_anchors(s, label, xf, sc)?;
|
||||
@@ -310,7 +315,7 @@ 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 };
|
||||
let kept = if copy { preview_copies(s, &ids, with_guides)? } else { None };
|
||||
s.edit(label, |d, sel| {
|
||||
let targets = match &kept {
|
||||
// The document becomes the snapshot with the kept copies, untransformed, and the
|
||||
@@ -320,7 +325,21 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
|
||||
sel.clone_from(&k.selection);
|
||||
k.ids.clone()
|
||||
}
|
||||
None if copy => duplicate_in(d, sel, &ids, Affine::IDENTITY)?,
|
||||
None if copy => {
|
||||
let guide_copies = if with_guides {
|
||||
let copies: Vec<_> = sel.guides.iter().filter_map(|&i| d.guides.get(i).cloned()).collect();
|
||||
(!copies.is_empty()).then_some(copies)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let new_ids = duplicate_in(d, sel, &ids, Affine::IDENTITY)?;
|
||||
if let Some(copies) = guide_copies {
|
||||
let first = d.guides.len();
|
||||
d.guides.extend(copies);
|
||||
sel.guides = (first..d.guides.len()).collect();
|
||||
}
|
||||
new_ids
|
||||
}
|
||||
None => ids.clone(),
|
||||
};
|
||||
// Transforms don't move nodes in the tree, so the paths found up front stay right.
|
||||
@@ -332,6 +351,13 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
|
||||
transform_node(n, xf, sc, objects);
|
||||
}
|
||||
}
|
||||
if with_guides && (delta.x != 0.0 || delta.y != 0.0) {
|
||||
for i in sel.guides.clone() {
|
||||
if let Some(g) = d.guides.get_mut(i) {
|
||||
*g = g.moved(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(targets)
|
||||
})?
|
||||
};
|
||||
@@ -355,21 +381,30 @@ pub(crate) fn apply_transform(s: &mut Session, label: &str, ids: Vec<NodeId>, xf
|
||||
/// 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 command then copies the current document.
|
||||
pub(crate) fn preview_copies(s: &mut Session, roots: &[NodeId]) -> Result<Option<Arc<crate::PreviewCopies>>> {
|
||||
pub(crate) fn preview_copies(s: &mut Session, roots: &[NodeId], with_guides: bool) -> 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);
|
||||
}
|
||||
let has_guide_copies = with_guides && !st.selection.guides.is_empty();
|
||||
if let Some(k) = &it.copies
|
||||
&& Arc::ptr_eq(&k.from, &it.doc)
|
||||
&& k.roots == roots
|
||||
&& (!k.selection.guides.is_empty() == has_guide_copies)
|
||||
{
|
||||
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)?;
|
||||
if has_guide_copies {
|
||||
let d = Arc::make_mut(&mut doc);
|
||||
let first = d.guides.len();
|
||||
let copies: Vec<_> = st.selection.guides.iter().filter_map(|&i| d.guides.get(i).cloned()).collect();
|
||||
d.guides.extend(copies);
|
||||
selection.guides = (first..d.guides.len()).collect();
|
||||
}
|
||||
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))
|
||||
|
||||
@@ -181,6 +181,9 @@ pub(crate) fn move_anchors(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// objects selected whole (those with no entry in the selection's `anchors`).
|
||||
pub(crate) fn transform_anchors(s: &mut Session, label: &str, xf: vectorcraft_geom::Affine, sc: Scaling) -> Result<()> {
|
||||
let sel = s.doc()?.selection.clone();
|
||||
let c = xf.as_coeffs();
|
||||
let is_translation = (c[0] - 1.0).abs() < 1e-9 && c[1].abs() < 1e-9 && c[2].abs() < 1e-9 && (c[3] - 1.0).abs() < 1e-9;
|
||||
let delta = vectorcraft_geom::Vec2::new(c[4], c[5]);
|
||||
s.edit(label, |doc, _| {
|
||||
for id in &sel.objects {
|
||||
match sel.anchors.get(id) {
|
||||
@@ -199,6 +202,13 @@ pub(crate) fn transform_anchors(s: &mut Session, label: &str, xf: vectorcraft_ge
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_translation && (delta.x != 0.0 || delta.y != 0.0) {
|
||||
for &i in &sel.guides {
|
||||
if let Some(g) = doc.guides.get_mut(i) {
|
||||
*g = g.moved(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user