CUTCLIP and CUTBASE leave the clipboard alone when every object is on a locked layer, and say how many were locked

This commit is contained in:
aiongg
2026-10-25 04:12:57 +00:00
parent 642c3a59a3
commit c42763ba0f
2 changed files with 65 additions and 10 deletions
+35 -9
View File
@@ -463,29 +463,53 @@ fn run_copybase(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!({ "copied": n }))
}
/// Copy the objects that are not on locked layers to the clipboard and erase them.
fn cut_to_clip(s: &mut Session, hs: &[Handle], base: Option<Vec2>) -> Result<usize> {
let hs: Vec<Handle> = hs.iter().copied().filter(|h| !super::curves::is_locked(s, *h)).collect();
/// Copy the objects that are not on locked layers to the clipboard and erase them. Returns how
/// many were cut and the message to show: how many were on a locked layer, and that nothing was
/// cut when that is all of them (the clipboard then keeps what it held).
fn cut_to_clip(s: &mut Session, hs: &[Handle], base: Option<Vec2>) -> Result<(usize, Option<String>)> {
let mut seen = std::collections::HashSet::new();
let live: Vec<Handle> = {
let d = s.doc()?;
hs.iter().copied().filter(|h| seen.insert(*h) && d.entity(*h).is_some()).collect()
};
let (hs, locked): (Vec<Handle>, Vec<Handle>) = live.into_iter().partition(|h| !super::curves::is_locked(s, *h));
let note = match locked.len() {
0 => None,
1 => Some("1 object was on a locked layer.".to_string()),
n => Some(format!("{n} objects were on a locked layer.")),
};
if hs.is_empty() {
let msg = note.map_or_else(|| "Nothing was cut.".to_string(), |m| format!("{m} Nothing was cut."));
return Ok((0, Some(msg)));
}
let n = copy_to_clip(s, &hs, base)?;
let d = s.doc_mut()?;
for h in &hs {
d.remove_entity(*h);
}
s.set_selection(Vec::new());
Ok(n)
Ok((n, note))
}
fn cut_result(n: usize, msg: Option<String>) -> Value {
let mut r = json!({ "cut": n });
if let Some(m) = msg {
r["message"] = json!(m);
}
r
}
fn run_cut(s: &mut Session, p: &Value) -> Result<Value> {
let hs = targets(s, p)?;
let n = cut_to_clip(s, &hs, None)?;
Ok(json!({ "cut": n }))
let (n, msg) = cut_to_clip(s, &hs, None)?;
Ok(cut_result(n, msg))
}
fn run_cutbase(s: &mut Session, p: &Value) -> Result<Value> {
let base = point_req("cutbase", p, "base")?;
let hs = targets(s, p)?;
let n = cut_to_clip(s, &hs, Some(base))?;
Ok(json!({ "cut": n }))
let (n, msg) = cut_to_clip(s, &hs, Some(base))?;
Ok(cut_result(n, msg))
}
/// Paste the clipboard moved by `offset` (the clipboard brings the layers, styles and blocks its
@@ -680,7 +704,9 @@ struct CopyBaseM {
impl CopyBaseM {
fn copy(&self, s: &mut Session, hs: &[Handle]) -> Result<Step> {
if self.cut {
cut_to_clip(s, hs, self.base)?;
if let (_, Some(m)) = cut_to_clip(s, hs, self.base)? {
s.echo(m);
}
} else {
copy_to_clip(s, hs, self.base)?;
}
+30 -1
View File
@@ -182,7 +182,7 @@ fn cutbase_cuts_with_the_given_base_point() {
s.active = a;
let r = s.execute("cutbase", &json!({ "handles": handles(&s), "base": [5, 5] })).unwrap();
// The wall is on a locked layer, so only the door is cut.
assert_eq!(r, json!({ "cut": 1 }));
assert_eq!(r, json!({ "cut": 1, "message": "1 object was on a locked layer." }));
assert_eq!(s.clipboard.len(), 1);
assert_eq!(s.clipboard_base, Vec2::new(5.0, 5.0));
let left = model(&s);
@@ -201,6 +201,35 @@ fn cutbase_cuts_with_the_given_base_point() {
assert_eq!(s.doc().unwrap().model.len(), 2);
}
#[test]
fn cutting_only_locked_objects_keeps_the_clipboard() {
let mut s = Session::empty();
let mut da = drawing_a();
for l in &mut da.layers {
l.locked = true;
}
s.open_drawing(da, "A", None);
let hs = handles(&s);
s.execute("copyclip", &json!({ "handles": [hs[0]] })).unwrap();
let held = s.clipboard.clone();
let r = s.execute("cutclip", &json!({ "handles": hs })).unwrap();
assert_eq!(r, json!({ "cut": 0, "message": "2 objects were on a locked layer. Nothing was cut." }));
assert_eq!(model(&s).len(), 2, "nothing was erased");
assert_eq!(s.clipboard.len(), 1, "the clipboard keeps what it held");
assert_eq!(s.clipboard[0].handle, held[0].handle);
// CUTBASE at the command line says so too.
s.start("cutbase").unwrap();
s.input(cadcraft_engine::Input::Point(Vec2::new(3.0, 4.0))).unwrap();
let wall = s.doc().unwrap().model.iter().next().unwrap().handle;
s.input(cadcraft_engine::Input::Pick(vec![wall])).unwrap();
s.input(cadcraft_engine::Input::Enter).unwrap();
assert!(s.running.is_none());
assert!(s.log.iter().any(|l| l.contains("1 object was on a locked layer. Nothing was cut.")), "{:?}", s.log);
assert_eq!(s.clipboard.len(), 1);
assert_eq!(model(&s).len(), 2);
}
#[test]
fn cutbase_and_copybase_need_a_base_point_and_take_each_object_once() {
let mut s = Session::empty();