mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-31 05:07:51 -06:00
Layers: Cmd/Ctrl+Alt-click places a new layer below the current one and a new sublayer at the bottom
The reference app places a new layer below the selected layer with Ctrl+Alt-click (Command+Option-click) on the New Layer button, and a new sublayer at the bottom of the layer list with the same click on the New Sublayer button. In VectorCraft that click opened Layer Options, and New Layer and New Sublayer always added the layer above the current layer and the sublayer on top of the layer's contents. layer.new takes below, which adds the layer directly below the current layer at its level, and layer.newSublayer takes bottom, which adds the sublayer under the parent's contents. In a clipping layer the new sublayer goes above the clipping path. Each is one undo step, and below is refused together with top. A Cmd/Ctrl+Alt-click on the Layers panel's New Layer and New Sublayer buttons runs them. A plain click, an Alt-click and a Cmd/Ctrl-click work as before. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -29,7 +29,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"New Layer…",
|
||||
["Window", "Layers"],
|
||||
Some("Cmd+L"),
|
||||
"{name?, top?: bool, color?, template?, locked?, visible?, printable?, preview?, dimImages?} a layer directly above the current layer, at its level (a sublayer beside a current sublayer); `top`: above all layers (Ctrl-click the button). Options as layer.setProps → {id}",
|
||||
"{name?, top?: bool, below?: bool, color?, template?, locked?, visible?, printable?, preview?, dimImages?} a layer directly above the current layer, at its level (a sublayer beside a current sublayer); `top`: above all layers (Ctrl/Cmd-click the button); `below`: directly below the current layer, at its level (Ctrl/Cmd+Alt-click the button), not with `top`. Options as layer.setProps → {id}",
|
||||
has_doc,
|
||||
new_layer
|
||||
),
|
||||
@@ -38,7 +38,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"New Sublayer…",
|
||||
["Window", "Layers"],
|
||||
None,
|
||||
"{parent?: layer id (default: the highlighted row's layer, else the current layer), name?, …options as layer.new} a sublayer on top of the parent's contents → {id}",
|
||||
"{parent?: layer id (default: the highlighted row's layer, else the current layer), bottom?: bool, name?, …options as layer.new} a sublayer on top of the parent's contents; `bottom`: under the parent's contents, above a clipping layer's clipping path (Ctrl/Cmd+Alt-click the button) → {id}",
|
||||
has_doc,
|
||||
new_sublayer
|
||||
),
|
||||
@@ -485,7 +485,11 @@ fn add_layer(s: &mut Session, p: &Value, cmd: &str, label: &str, place: impl FnO
|
||||
check_options(cmd, p, xml_id)?;
|
||||
let name = str_param(p, "name").filter(|n| !n.trim().is_empty()).map(str::to_string);
|
||||
let id = s.edit(label, |d, _| {
|
||||
let (parent, index) = place(d)?;
|
||||
let (parent, mut index) = place(d)?;
|
||||
// The clipping path stays at the bottom of a clipping layer.
|
||||
if d.children(parent).is_some_and(|c| !c.is_empty()) && parent.and_then(|p| d.node(p)).is_some_and(Node::clips) {
|
||||
index = index.max(1);
|
||||
}
|
||||
let mut n = make_layer(d, name.as_deref());
|
||||
apply_options(&mut n, p, xml_id);
|
||||
Ok(d.insert(parent, index, n)?)
|
||||
@@ -497,12 +501,17 @@ fn add_layer(s: &mut Session, p: &Value, cmd: &str, label: &str, place: impl FnO
|
||||
}
|
||||
|
||||
fn new_layer(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "layer.new";
|
||||
let below = switch_or(p, "below", false, C)?;
|
||||
if below && bool_or(p, "top", false) {
|
||||
return Err(bad(C, "`top` and `below` can't both be true"));
|
||||
}
|
||||
let st = s.doc()?;
|
||||
// Above the current layer, at its level; on top of everything with `top`.
|
||||
// Above the current layer, at its level; below it with `below`; on top of everything with `top`.
|
||||
let cur = st.current_layer().filter(|_| !bool_or(p, "top", false));
|
||||
add_layer(s, p, "layer.new", "New Layer", |d| match cur.and_then(|c| d.position(c)) {
|
||||
Some((parent, i, _)) => Ok((parent, i + 1)),
|
||||
None => Ok((None, usize::MAX)),
|
||||
add_layer(s, p, C, "New Layer", |d| match cur.and_then(|c| d.position(c)) {
|
||||
Some((parent, i, _)) => Ok((parent, if below { i } else { i + 1 })),
|
||||
None => Ok((None, if below { 0 } else { usize::MAX })),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -514,7 +523,8 @@ fn new_sublayer(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if !st.doc.node(parent).is_some_and(Node::is_layer) {
|
||||
return Err(bad(C, "the parent must be a layer or sublayer"));
|
||||
}
|
||||
add_layer(s, p, C, "New Sublayer", |_| Ok((Some(parent), usize::MAX)))
|
||||
let index = if switch_or(p, "bottom", false, C)? { 0 } else { usize::MAX };
|
||||
add_layer(s, p, C, "New Sublayer", |_| Ok((Some(parent), index)))
|
||||
}
|
||||
|
||||
fn delete_rows(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
@@ -403,6 +403,58 @@ fn new_layers_go_above_the_current_one_at_its_level() {
|
||||
assert_eq!(unique.len(), names.len(), "{names:?}");
|
||||
}
|
||||
|
||||
/// `below` puts a new layer directly below the current layer, at its level, and `bottom` puts a
|
||||
/// new sublayer under the parent's contents, each in one undo step.
|
||||
#[test]
|
||||
fn new_layers_go_below_the_current_one_and_sublayers_under_the_contents() {
|
||||
let mut s = session();
|
||||
let (layer, sub, a, g, _, c) = tree(&mut s);
|
||||
run(&mut s, "layer.setCurrent", json!({"id": sub.0}));
|
||||
let before = undos(&s);
|
||||
let n = id(&run(&mut s, "layer.new", json!({"below": true})));
|
||||
assert_eq!(children(&s, layer), vec![c, n, sub], "a sublayer directly below the current sublayer");
|
||||
assert_eq!(undos(&s), before + 1);
|
||||
assert_eq!(s.doc().unwrap().highlighted_rows(), vec![n]);
|
||||
assert_eq!(s.doc().unwrap().current_layer(), Some(n));
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
assert_eq!(children(&s, layer), vec![c, sub]);
|
||||
// At the top level: below the current layer, above the layers under it.
|
||||
let top = id(&run(&mut s, "layer.new", json!({"top": true, "name": "Top"})));
|
||||
let middle = id(&run(&mut s, "layer.new", json!({"below": true, "name": "Middle", "locked": true})));
|
||||
let order = s.doc().unwrap().doc.layers.iter().map(|l| l.id).collect::<Vec<_>>();
|
||||
assert_eq!(order, vec![layer, middle, top]);
|
||||
assert!(node(&s, middle).locked, "options as without `below`");
|
||||
run(&mut s, "layer.setCurrent", json!({"id": layer.0}));
|
||||
let bottom = id(&run(&mut s, "layer.new", json!({"below": true})));
|
||||
assert_eq!(s.doc().unwrap().doc.layers[0].id, bottom, "below the bottom layer");
|
||||
// Sublayers: on top of the parent's contents, or under them with `bottom`.
|
||||
let before = undos(&s);
|
||||
let under = id(&run(&mut s, "layer.newSublayer", json!({"parent": sub.0, "bottom": true})));
|
||||
assert_eq!(children(&s, sub), vec![under, a, g]);
|
||||
assert_eq!(undos(&s), before + 1);
|
||||
assert_eq!(s.doc().unwrap().highlighted_rows(), vec![under]);
|
||||
let over = id(&run(&mut s, "layer.newSublayer", json!({"parent": sub.0, "bottom": false})));
|
||||
assert_eq!(children(&s, sub), vec![under, a, g, over]);
|
||||
// Bad values change nothing.
|
||||
let count = s.doc().unwrap().doc.node_count();
|
||||
for (cmd, p) in [
|
||||
("layer.new", json!({"below": true, "top": true})),
|
||||
("layer.new", json!({"below": "yes"})),
|
||||
("layer.newSublayer", json!({"parent": sub.0, "bottom": 1})),
|
||||
] {
|
||||
assert!(s.execute(cmd, &p).is_err(), "{cmd} {p}");
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().doc.node_count(), count);
|
||||
// In a clipping layer the clipping path stays at the bottom.
|
||||
let mut s = session();
|
||||
let layer = s.doc().unwrap().doc.layers[0].id;
|
||||
let (back, front) = (rect(&mut s, 0.0), rect(&mut s, 100.0));
|
||||
run(&mut s, "layer.clippingMask.toggle", json!({"id": layer.0}));
|
||||
assert_eq!(children(&s, layer), vec![front, back], "the top object clips");
|
||||
let under = id(&run(&mut s, "layer.newSublayer", json!({"parent": layer.0, "bottom": true})));
|
||||
assert_eq!(children(&s, layer), vec![front, under, back]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rows_move_above_below_and_into_layers_and_groups() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user