M8: absoluteRotation turns a selected parent before the layers in it (#2196)

A page angle depends on the parents', so a frame and a layer inside it set together both land on the typed page angle whatever the order of the ids (the layer went to 80 for 45 when it came first).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote (review fix, no brief)
This commit is contained in:
Trevor Brown
2026-11-10 03:06:32 -05:00
co-authored by Claude Opus 5.5
parent d0379dc70d
commit cc36771713
2 changed files with 18 additions and 0 deletions
+5
View File
@@ -165,6 +165,11 @@ pub(super) fn set(s: &mut Session, p: &Value) -> Result<Value> {
}
let doc = s.doc_mut()?;
let before = crate::cmd::styles::bound_before(doc, &ids);
let mut ids = ids;
if props.contains_key("absoluteRotation") {
// A page angle depends on the parents': turn a selected parent before the layers in it.
ids.sort_by_key(|i| doc.ancestors(*i).len());
}
for i in ids {
crate::cmd::props::apply("node.set", doc, i, skipped.get(&i).unwrap_or(&props))?;
}
@@ -508,6 +508,19 @@ fn absolute_rotation_sets_the_rotation_on_the_page_keeping_the_centre() {
assert!(s.execute("node.set", &json!({"ids": [r], "props": {"rotation": 1, "absoluteRotation": 2}})).is_err());
}
#[test]
fn absolute_rotation_on_a_frame_and_a_layer_inside_it_lands_both_on_the_page_angle() {
let mut s = session();
let page = |s: &Session, i: NodeId| rotation_degrees(s.doc().unwrap().world_transform(i));
let f = id(&ex(&mut s, "frame.create", json!({"x": 0, "y": 100, "width": 400, "height": 400})));
set(&mut s, &[f], json!({"rotation": 10}));
let r = rect(&mut s, 100.0, 100.0, 120.0, 80.0, Some(f));
// The layer inside comes first in the ids: the frame's turn must not carry it past 45.
set(&mut s, &[r, f], json!({"absoluteRotation": 45}));
assert!(near(page(&s, f), 45.0), "{}", page(&s, f));
assert!(near(page(&s, r), 45.0), "{}", page(&s, r));
}
#[test]
fn constrain_proportions_keeps_the_other_side_in_ratio() {
let mut s = session();