mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-14 06:12:28 +00:00
M8: Align is unavailable for a layer in an auto layout frame's flow, as in Figma
Measured in Figma's web app on 2026-11-12: with one child of an auto layout frame selected, the Design panel's Align buttons are greyed out, Alt+A and Alt+S do nothing and the frame's alignment doesn't change. align_target now returns none for a single layer that takes part in its auto layout frame's flow, so can_align is false and the align commands leave the layout alone. An absolutely positioned child still aligns to its frame, and multi-layer selections are unchanged (neither measured).
The panel's disabled tip says why ("A layer in an auto layout frame is placed by its frame"), through the new align_blocked_by_auto_layout. Tidy up was measured to differ from Figma and is not changed here.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Fable 5.2 queued; Opus 5.5 briefed; Sonnet 5.5 measured and wrote at medium
This commit is contained in:
1 parent
0d6ce1124c
commit
76965c78ca
6 files changed
+78
-4
No files matched your search
@@ -336,3 +336,36 @@ fn auto_layout_keys_beside_no_auto_layout_are_refused() {
|
||||
set(&mut s, f, json!({"layout": null, "name": "Plain"}));
|
||||
assert!(s.doc().unwrap().node(f).unwrap().layout.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn align_is_unavailable_for_a_child_in_an_auto_layout_flow() {
|
||||
use crate::cmd::arrange::{align_blocked_by_auto_layout, can_align};
|
||||
let mut s = Session::new();
|
||||
let f = al_frame(&mut s, json!({"gap": 10}));
|
||||
let a = rect(&mut s, f, 20.0, 20.0, "#ff0000");
|
||||
let b = rect(&mut s, f, 20.0, 20.0, "#00ff00");
|
||||
let at = |s: &Session, i: NodeId| s.doc().unwrap().world_bounds(i).unwrap();
|
||||
let (before_a, before_b) = (at(&s, a), at(&s, b));
|
||||
assert_eq!(before_b.x0, 30.0, "the layout placed the second one after the first");
|
||||
let doc = s.doc().unwrap();
|
||||
assert!(!can_align(doc, &[b]), "Align is greyed out for one child of the frame");
|
||||
assert!(align_blocked_by_auto_layout(doc, &[b]), "because its frame places it");
|
||||
for cmd in ["align.left", "align.hcenter", "align.right", "align.top", "align.vcenter", "align.bottom"] {
|
||||
s.execute(cmd, &json!({"ids": [b]})).unwrap();
|
||||
assert_eq!((at(&s, a), at(&s, b)), (before_a, before_b), "{cmd} leaves the layout alone");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_absolute_child_of_an_auto_layout_frame_still_aligns_to_it() {
|
||||
use crate::cmd::arrange::{align_blocked_by_auto_layout, can_align};
|
||||
let mut s = Session::new();
|
||||
let f = al_frame(&mut s, json!({}));
|
||||
let kid = rect(&mut s, f, 20.0, 20.0, "#ff0000");
|
||||
set(&mut s, kid, json!({"absolute": true}));
|
||||
let doc = s.doc().unwrap();
|
||||
assert!(can_align(doc, &[kid]));
|
||||
assert!(!align_blocked_by_auto_layout(doc, &[kid]));
|
||||
s.execute("align.right", &json!({"ids": [kid]})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().world_bounds(kid).unwrap().x1, 200.0);
|
||||
}
|
||||
@@ -164,12 +164,27 @@ pub fn can_align(doc: &Document, ids: &[NodeId]) -> bool {
|
||||
align_target(doc, &roots(doc, ids)).is_some()
|
||||
}
|
||||
|
||||
/// Whether `ids` is one layer that its auto layout frame places: Figma greys out Align for it,
|
||||
/// since the frame decides where it goes (measured in Figma's web app, 2026-11-12).
|
||||
pub fn align_blocked_by_auto_layout(doc: &Document, ids: &[NodeId]) -> bool {
|
||||
matches!(roots(doc, ids).as_slice(), [id] if in_auto_layout_flow(doc, *id))
|
||||
}
|
||||
|
||||
/// Whether the layer sits in an auto layout frame and takes part in its flow (not absolutely positioned).
|
||||
fn in_auto_layout_flow(doc: &Document, id: NodeId) -> bool {
|
||||
let Some(n) = doc.node(id) else { return false };
|
||||
!n.child_layout.absolute && n.parent.and_then(|p| doc.node(p)).is_some_and(|p| p.layout.is_some())
|
||||
}
|
||||
|
||||
/// What `roots` align to: one layer to its parent frame, several to their combined bounds.
|
||||
fn align_target(doc: &Document, roots: &[NodeId]) -> Option<Rect> {
|
||||
match roots {
|
||||
[] => None,
|
||||
[id] => {
|
||||
let parent = doc.node(*id)?.parent.filter(|pp| !doc.node(*pp).is_some_and(Node::is_page))?;
|
||||
if in_auto_layout_flow(doc, *id) {
|
||||
return None;
|
||||
}
|
||||
Some(doc.world_bounds(parent).unwrap_or_default())
|
||||
}
|
||||
_ => Some(doc.bounds_of(roots).unwrap_or_default()),
|
||||
|
||||
Reference in new issue
Block a user