diff --git a/crates/engine/src/cmd/clipboard.rs b/crates/engine/src/cmd/clipboard.rs index c487425d3..cf6718d8e 100644 --- a/crates/engine/src/cmd/clipboard.rs +++ b/crates/engine/src/cmd/clipboard.rs @@ -103,6 +103,12 @@ impl Clipboard { self.nodes.is_empty() && self.artboards.is_empty() && self.guides.is_empty() } + /// Does it hold art (objects or artboards) for other apps? Ruler guides stay in VectorCraft: + /// guides alone put nothing on the system clipboard (#3226). + pub fn has_art(&self) -> bool { + !self.nodes.is_empty() || !self.artboards.is_empty() + } + /// Objects `roots` of `st`'s document with their resources, source artboard and layers. pub(crate) fn copy(st: &DocState, roots: &[NodeId]) -> Self { let d = &*st.doc; @@ -198,7 +204,7 @@ impl Clipboard { /// The objects as standalone SVG (`None` when there are none). pub fn svg(&self) -> Option { - if self.is_empty() { + if !self.has_art() { return None; } let d = self.to_document(); diff --git a/crates/engine/src/cmd/clipboard/flavours.rs b/crates/engine/src/cmd/clipboard/flavours.rs index b2e1114e1..f4c096d9b 100644 --- a/crates/engine/src/cmd/clipboard/flavours.rs +++ b/crates/engine/src/cmd/clipboard/flavours.rs @@ -286,7 +286,7 @@ impl Clipboard { /// The MIME types of [`Self::flavours`], without making them. pub fn flavour_types(&self, svg: bool, pdf: bool) -> Vec<&'static str> { - if self.is_empty() { + if !self.has_art() { return vec![]; } let text = (svg && self.image_bytes() <= COPY_TEXT_IMAGE_BYTES) || self.nodes.iter().all(type_only); diff --git a/crates/engine/src/tests/guide_clipboard.rs b/crates/engine/src/tests/guide_clipboard.rs index d71c1dd42..b6fceead0 100644 --- a/crates/engine/src/tests/guide_clipboard.rs +++ b/crates/engine/src/tests/guide_clipboard.rs @@ -88,3 +88,44 @@ fn guides_paste_in_place_onto_another_artboard_and_cut_removes_them() { run(&mut s, "select.none", json!({})); assert!(s.execute("edit.copy", &json!({})).is_err()); } + +/// #3226 review: each paste adds guides of its own (a guide is its place and layer; there is no +/// id to share), on the layer new art goes to, so they take that layer's colour, visibility and +/// lock; and the SVG put on the system clipboard carries no guides. +#[test] +fn two_pastes_add_their_own_guides_on_the_current_layer_and_svg_has_none() { + let mut s = Session::new(); + run(&mut s, "file.new", json!({"width": 400, "height": 300})); + run(&mut s, "guide.add", json!({"vertical": true, "pos": 120})); + run(&mut s, "guide.select", json!({"indexes": [0]})); + run(&mut s, "edit.copy", json!({})); + // Guides alone: no art for the system clipboard's SVG. + assert!(s.clipboard_svg().is_none(), "guides alone put no SVG on the system clipboard"); + assert!(s.clipboard.flavour_types(true, true).is_empty(), "nor any other format (it keeps what other apps put there)"); + let layer = NodeId(run(&mut s, "layer.new", json!({}))["id"].as_u64().unwrap()); + run(&mut s, "edit.paste", json!({})); + assert_eq!(s.doc().unwrap().selection.guides, [1], "the first paste selects its own guide"); + run(&mut s, "edit.paste", json!({})); + assert_eq!(s.doc().unwrap().selection.guides, [2], "the second paste selects its own guide"); + let g = guides(&s); + assert_eq!(g.len(), 3, "two pastes, two more guides: {g:?}"); + assert_eq!((g[1].2, g[2].2), (Some(layer), Some(layer)), "on the layer new art goes to"); + // Moving the second pasted guide leaves the first where it is. + run(&mut s, "guide.move", json!({"index": 2, "pos": 200})); + assert_eq!(guides(&s).iter().map(|g| g.1).collect::>(), [120.0, 120.0, 200.0]); + // Hiding that layer hides the guides pasted on it: they follow its visibility (and lock). + run(&mut s, "layer.setProps", json!({"id": layer.0, "visible": false})); + let st = s.doc().unwrap(); + let shown: Vec = st.doc.guides.iter().map(|g| st.doc.guide_shown(g)).collect(); + assert_eq!(shown, [true, false, false]); + // With an object, the SVG holds the object and no guide. + run(&mut s, "layer.setProps", json!({"id": layer.0, "visible": true})); + let rect = run(&mut s, "shape.rectangle", json!({"x": 10, "y": 10, "width": 20, "height": 20}))["id"].as_u64().unwrap(); + run(&mut s, "select.set", json!({"ids": [rect]})); + run(&mut s, "guide.select", json!({"indexes": [0], "toggle": true})); + let r = run(&mut s, "edit.copy", json!({})); + assert_eq!(r["guides"], 1); + let svg = s.clipboard_svg().unwrap(); + assert!(svg.contains("