mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-02 16:29:34 -06:00
node.reparent: index is the layer's final position (#992)
`node.reparent {ids, parent, index}` read `index` against the parent's
children as they were before the move, so within the same parent moving a
layer up one place did nothing and asking for two places moved it one.
`index` now counts the parent's children after the move, the way
`node.reorder` already does, in the same parent or a new one; several
layers still land together in their stacking order.
The layers panel was the only caller passing an index other than 0. It
drops on a gap between rows counted before the move, so it now subtracts
the dragged layers below that gap and drags behave as before.
Documents `index` in the command's params and in docs/mcp.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
35da7b7cff
commit
3146ad2b2f
@@ -216,7 +216,13 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
let i = req_id("node.get", p, "id")?;
|
||||
Ok(crate::inspect::node_json(s.doc()?, i, 1))
|
||||
}),
|
||||
spec!("node.reparent", "Move layer into", "{ids?, parent, index?}", true, reparent),
|
||||
spec!(
|
||||
"node.reparent",
|
||||
"Move layer into",
|
||||
"{ids?, parent, index? (final position among parent's children, bottom first; default top)}",
|
||||
true,
|
||||
reparent
|
||||
),
|
||||
spec!("node.reorder", "Reorder layer", "{id, index}", true, |s, p| {
|
||||
let i = req_id("node.reorder", p, "id")?;
|
||||
let idx = req_num("node.reorder", p, "index")?.max(0.0) as usize;
|
||||
@@ -1302,9 +1308,10 @@ fn reparent(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let index = num(p, "index").map(|i| i.max(0.0) as usize);
|
||||
let doc = s.doc_mut()?;
|
||||
let ids = z_sorted(doc, &roots(doc, &ids));
|
||||
// The layers land together under the first sibling at or above `index` that stays put (none: on
|
||||
// top), wherever they came from.
|
||||
let above = index.and_then(|at| doc.get(parent).ok()?.children.iter().skip(at).find(|c| !ids.contains(c)).copied());
|
||||
// `index` is where the first moved layer ends up among the parent's children (bottom first),
|
||||
// counted after the move, the way `node.reorder` counts it. The layers land together, keeping
|
||||
// their order, under the sibling that stays put and ends up just after them (none: on top).
|
||||
let above = index.and_then(|at| doc.get(parent).ok()?.children.iter().filter(|c| !ids.contains(c)).nth(at).copied());
|
||||
for i in &ids {
|
||||
// Counted with the layer still in place, as `reparent` expects.
|
||||
let at = above.and_then(|a| doc.node(parent)?.children.iter().position(|c| *c == a));
|
||||
|
||||
@@ -99,16 +99,18 @@ fn scene(s: &mut Session) -> Scene {
|
||||
|
||||
#[test]
|
||||
fn dragging_in_the_layers_panel_reorders_by_the_documented_rules() {
|
||||
// The panel drops layers with `node.reparent {ids, parent, index}`: `index` counts the
|
||||
// parent's children bottom first, as they are before the move.
|
||||
// The panel drops layers with `node.reparent {ids, parent, index}`: `index` is where the first
|
||||
// moved layer ends up among the parent's children, bottom first, counted after the move.
|
||||
// (moved, index in F, F's children after (bottom first), derivation)
|
||||
let rows: [(&[&str], usize, [&str; 4], &str); 6] = [
|
||||
(&["a"], 4, ["b", "c", "d", "a"], "a layer dragged to the top of its parent's list comes to the front"),
|
||||
let rows: [(&[&str], usize, [&str; 4], &str); 8] = [
|
||||
(&["a"], 3, ["b", "c", "d", "a"], "a layer dragged to the top of its parent's list comes to the front"),
|
||||
(&["a"], 9, ["b", "c", "d", "a"], "an index past the end is the front"),
|
||||
(&["d"], 0, ["d", "a", "b", "c"], "a layer dragged to the bottom of the list goes to the back"),
|
||||
(&["a"], 2, ["b", "a", "c", "d"], "a layer dropped between two layers lands between them"),
|
||||
(&["a", "b"], 4, ["c", "d", "a", "b"], "several selected layers move together, keeping their order"),
|
||||
(&["a", "d"], 2, ["b", "a", "d", "c"], "several layers from apart land together where they are dropped"),
|
||||
(&["b", "c"], 1, ["a", "b", "c", "d"], "silent: dropping a block of layers onto itself changes nothing (pinned)"),
|
||||
(&["a"], 1, ["b", "a", "c", "d"], "a layer moved up one place goes up one place (#992)"),
|
||||
(&["a"], 0, ["a", "b", "c", "d"], "silent: a layer asked to stay where it is stays (pinned)"),
|
||||
(&["a", "b"], 2, ["c", "d", "a", "b"], "several selected layers move together, keeping their order"),
|
||||
(&["a", "d"], 1, ["b", "a", "d", "c"], "several layers from apart land together where they are asked to"),
|
||||
(&["b", "c"], 1, ["a", "b", "c", "d"], "silent: moving a block of layers to where it is changes nothing (pinned)"),
|
||||
];
|
||||
for (moved, index, after, why) in rows {
|
||||
let mut s = Session::new();
|
||||
@@ -172,6 +174,32 @@ fn dragging_into_frames_groups_and_auto_layout() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reparent_index_is_the_final_position_in_auto_layout_and_across_parents() {
|
||||
// #992: `index` is the layer's place among the parent's children after the move.
|
||||
let mut s = Session::new();
|
||||
let row = id(&ex(&mut s, "node.create", json!({"type": "frame", "name": "Row", "props": {"layout": "horizontal"}})));
|
||||
let text = |s: &mut Session, t: &str, parent: NodeId| id(&ex(s, "node.create", json!({"type": "text", "name": t, "text": t, "parent": parent})));
|
||||
let (first, second, third) = (text(&mut s, "first", row), text(&mut s, "second", row), text(&mut s, "third", row));
|
||||
let names = |s: &Session, p: NodeId| kids(s, p).iter().map(|i| name(s, *i)).collect::<Vec<_>>();
|
||||
ex(&mut s, "node.reparent", json!({"ids": [first], "parent": row, "index": 1}));
|
||||
assert_eq!(names(&s, row), ["second", "first", "third"], "moved down one place");
|
||||
ex(&mut s, "node.reparent", json!({"ids": [first], "parent": row, "index": 2}));
|
||||
assert_eq!(names(&s, row), ["second", "third", "first"], "moved to the end");
|
||||
ex(&mut s, "node.reparent", json!({"ids": [first], "parent": row, "index": 0}));
|
||||
assert_eq!(names(&s, row), ["first", "second", "third"], "moved to the start");
|
||||
let x = |s: &Session, i: NodeId| s.doc().unwrap().node(i).unwrap().transform.as_coeffs()[4];
|
||||
assert!(x(&s, first) < x(&s, second) && x(&s, second) < x(&s, third), "the flow follows the new order");
|
||||
// Into another parent, `index` means the same thing.
|
||||
let other = id(&ex(&mut s, "node.create", json!({"type": "frame", "name": "Other", "props": {"layout": "horizontal"}})));
|
||||
let (p, q) = (text(&mut s, "p", other), text(&mut s, "q", other));
|
||||
ex(&mut s, "node.reparent", json!({"ids": [second], "parent": other, "index": 1}));
|
||||
assert_eq!(kids(&s, other), [p, second, q], "lands at index 1 of the new parent");
|
||||
ex(&mut s, "node.reparent", json!({"ids": [first, third], "parent": other, "index": 3}));
|
||||
assert_eq!(kids(&s, other), [p, second, q, first, third], "several layers start at index 3, keeping their order");
|
||||
assert!(kids(&s, row).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renaming_several_layers_by_the_documented_codes() {
|
||||
// Three layers named `names`, bottom to top; (names, params, new names, derivation).
|
||||
|
||||
Reference in New Issue
Block a user