mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 18:36:44 -06:00
Batch r2-1 review: .fig export grid sizing in helpers; instance layers clear inherited track sizes
auto_layout's grid branch moves to grid_layout and grid_sizing (SonarCloud cognitive complexity). unset() clears gridColumnsSizing and gridRowsSizing, so an instance layer with equal tracks no longer reads back its main layer's track sizes.
This commit is contained in:
@@ -296,6 +296,23 @@ fn grid_track_size(track: &GridTrack) -> Option<(&'static str, f64)> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A grid axis's track sizes, keyed by track id like the order map. A track the document doesn't
|
||||
/// set is left out, and one the format can't hold keeps its id (the import equalises it) and sets
|
||||
/// `dropped`.
|
||||
fn grid_sizing(tracks: &[GridTrack], ids: &[u32], dropped: &mut bool) -> Option<Kv<'static>> {
|
||||
let mut entries = Vec::new();
|
||||
for (track, id) in tracks.iter().zip(ids) {
|
||||
match grid_track_size(track) {
|
||||
Some((kind, value)) => {
|
||||
let size = || o(vec![("type", e(kind)), ("value", fl(value))]);
|
||||
entries.push(o(vec![("id", guid(AUX, *id)), ("trackSize", o(vec![("minSizing", size()), ("maxSizing", size())]))]));
|
||||
}
|
||||
None => *dropped = true,
|
||||
}
|
||||
}
|
||||
(!entries.is_empty()).then(|| o(vec![("entries", arr(entries))]))
|
||||
}
|
||||
|
||||
fn guid(session: u32, local: u32) -> Kv<'static> {
|
||||
o(vec![("sessionID", u(session)), ("localID", u(local))])
|
||||
}
|
||||
@@ -1419,49 +1436,7 @@ impl<'d> Exporter<'d> {
|
||||
("stackReverseZIndex", b(l.reverse_z)),
|
||||
]);
|
||||
if grid {
|
||||
// Instance layers share their main layer's track ids, so unchanged tracks and the cells
|
||||
// their layers sit in still match.
|
||||
let key = self.main_layer(n.id);
|
||||
let (want_c, want_r) = (l.grid_columns.min(1000) as usize, l.grid_rows.min(1000) as usize);
|
||||
let (cols, rows) = match self.tracks.get(&key) {
|
||||
Some((c, r)) if (c.len(), r.len()) == (want_c, want_r) => (c.clone(), r.clone()),
|
||||
_ => ((0..want_c).map(|_| self.next_aux()).collect(), (0..want_r).map(|_| self.next_aux()).collect()),
|
||||
};
|
||||
let map = |ids: &[u32]| {
|
||||
o(vec![(
|
||||
"entries",
|
||||
arr(ids.iter().enumerate().map(|(i, t)| o(vec![("id", guid(AUX, *t)), ("position", s(&position(i)))])).collect()),
|
||||
)])
|
||||
};
|
||||
f.extend([("gridColumnGap", fl(l.gap)), ("gridRowGap", fl(l.cross_gap)), ("gridColumns", map(&cols)), ("gridRows", map(&rows))]);
|
||||
// Sizing is keyed by track id like the order map. A track the document doesn't set is
|
||||
// left out, and one the format can't hold keeps its id (the import equalises it).
|
||||
let mut dropped = false;
|
||||
let sizing = |tracks: &[GridTrack], ids: &[u32], dropped: &mut bool| -> Option<Kv<'static>> {
|
||||
let mut entries = Vec::new();
|
||||
for (i, id) in ids.iter().enumerate() {
|
||||
let Some(track) = tracks.get(i) else { continue };
|
||||
match grid_track_size(track) {
|
||||
Some((kind, value)) => {
|
||||
let size = || o(vec![("type", e(kind)), ("value", fl(value))]);
|
||||
entries.push(o(vec![("id", guid(AUX, *id)), ("trackSize", o(vec![("minSizing", size()), ("maxSizing", size())]))]));
|
||||
}
|
||||
None => *dropped = true,
|
||||
}
|
||||
}
|
||||
(!entries.is_empty()).then(|| o(vec![("entries", arr(entries))]))
|
||||
};
|
||||
let column_sizing = sizing(&l.column_tracks, &cols, &mut dropped);
|
||||
let row_sizing = sizing(&l.row_tracks, &rows, &mut dropped);
|
||||
if dropped {
|
||||
self.warn("grid tracks whose size the format can't hold were written as equal tracks");
|
||||
}
|
||||
for (name, sizing) in [("gridColumnsSizing", column_sizing), ("gridRowsSizing", row_sizing)] {
|
||||
if let Some(v) = sizing {
|
||||
f.push((name, v));
|
||||
}
|
||||
}
|
||||
self.tracks.insert(key, (cols, rows));
|
||||
self.grid_layout(n, l, f);
|
||||
} else {
|
||||
let spread = l.content_align == ContentAlign::SpaceBetween;
|
||||
f.extend([
|
||||
@@ -1472,6 +1447,34 @@ impl<'d> Exporter<'d> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A grid layout's track gaps, order and sizes.
|
||||
fn grid_layout(&mut self, n: &Node, l: &AutoLayout, f: &mut Fields) {
|
||||
// Instance layers share their main layer's track ids, so unchanged tracks and the cells
|
||||
// their layers sit in still match.
|
||||
let key = self.main_layer(n.id);
|
||||
let (want_c, want_r) = (l.grid_columns.min(1000) as usize, l.grid_rows.min(1000) as usize);
|
||||
let (cols, rows) = match self.tracks.get(&key) {
|
||||
Some((c, r)) if (c.len(), r.len()) == (want_c, want_r) => (c.clone(), r.clone()),
|
||||
_ => ((0..want_c).map(|_| self.next_aux()).collect(), (0..want_r).map(|_| self.next_aux()).collect()),
|
||||
};
|
||||
let map = |ids: &[u32]| {
|
||||
o(vec![("entries", arr(ids.iter().enumerate().map(|(i, t)| o(vec![("id", guid(AUX, *t)), ("position", s(&position(i)))])).collect()))])
|
||||
};
|
||||
f.extend([("gridColumnGap", fl(l.gap)), ("gridRowGap", fl(l.cross_gap)), ("gridColumns", map(&cols)), ("gridRows", map(&rows))]);
|
||||
let mut dropped = false;
|
||||
let column_sizing = grid_sizing(&l.column_tracks, &cols, &mut dropped);
|
||||
let row_sizing = grid_sizing(&l.row_tracks, &rows, &mut dropped);
|
||||
if dropped {
|
||||
self.warn("grid tracks whose size the format can't hold were written as equal tracks");
|
||||
}
|
||||
for (name, sizing) in [("gridColumnsSizing", column_sizing), ("gridRowsSizing", row_sizing)] {
|
||||
if let Some(v) = sizing {
|
||||
f.push((name, v));
|
||||
}
|
||||
}
|
||||
self.tracks.insert(key, (cols, rows));
|
||||
}
|
||||
|
||||
/// How a layer sizes and sits in its auto layout parent. Min/max sizes are the layer's own
|
||||
/// and are written whatever its parent is.
|
||||
fn child_layout(&mut self, n: &Node, parent: Option<&Node>, f: &mut Fields) {
|
||||
@@ -2417,7 +2420,7 @@ fn unset(key: &str) -> Option<Kv<'static>> {
|
||||
"styleIdForFill" | "styleIdForStrokeFill" | "styleIdForEffect" | "styleIdForGrid" | "styleIdForText" => {
|
||||
o(vec![("guid", guid(u32::MAX, u32::MAX))])
|
||||
}
|
||||
"variableConsumptionMap" | "variableModeBySetMap" => o(vec![("entries", arr(Vec::new()))]),
|
||||
"variableConsumptionMap" | "variableModeBySetMap" | "gridColumnsSizing" | "gridRowsSizing" => o(vec![("entries", arr(Vec::new()))]),
|
||||
"mask" | "rectangleCornerRadiiIndependent" | "borderStrokeWeightsIndependent" => b(false),
|
||||
"effects" | "dashPattern" | "layoutGrids" | "exportSettings" | "prototypeInteractions" | "fillGeometry" | "variableWidthPoints" => {
|
||||
arr(Vec::new())
|
||||
|
||||
@@ -1061,6 +1061,42 @@ fn grid_track_sizes_round_trip() {
|
||||
assert_eq!(l.column_tracks[0], GridTrack::fill(1.0), "an unwritable track reads back equal");
|
||||
}
|
||||
|
||||
/// An instance layer whose grid tracks are equal doesn't read back its main layer's track sizes.
|
||||
#[test]
|
||||
fn instance_grid_tracks_clear_the_mains_sizes() {
|
||||
let mut doc = Document::new("File");
|
||||
let page = doc.first_page().unwrap();
|
||||
let main = doc.create(page, NodeKind::Component { description: String::new(), props: BTreeMap::new() }, "Main", 0.0, 0.0, 400.0, 200.0).unwrap();
|
||||
let grid = doc.create(main, NodeKind::Frame, "Grid", 0.0, 0.0, 400.0, 200.0).unwrap();
|
||||
let layout = AutoLayout {
|
||||
mode: LayoutMode::Grid,
|
||||
grid_columns: 2,
|
||||
grid_rows: 1,
|
||||
column_tracks: vec![GridTrack::fixed(120.0); 2],
|
||||
..AutoLayout::default()
|
||||
};
|
||||
doc.node_mut(grid).unwrap().layout = Some(layout.clone());
|
||||
let inst = doc.alloc_id();
|
||||
let mut n = doc.node(main).unwrap().clone();
|
||||
(n.id, n.kind, n.parent, n.children) = (inst, NodeKind::Instance { component: main, props: BTreeMap::new() }, None, Vec::new());
|
||||
doc.insert(n, page, None).unwrap();
|
||||
let mut layer = doc.node(grid).unwrap().clone();
|
||||
(layer.id, layer.parent, layer.source) = (doc.alloc_id(), None, Some(grid));
|
||||
layer.layout = Some(AutoLayout { column_tracks: Vec::new(), ..layout });
|
||||
doc.insert(layer, inst, None).unwrap();
|
||||
let (bytes, _) = export(&doc, None).unwrap();
|
||||
let (back, _) = import(&bytes, "File").unwrap();
|
||||
let tracks = |inside: bool| {
|
||||
let n = back.nodes().find(|n| {
|
||||
n.name == "Grid"
|
||||
&& back.ancestors(n.id).iter().any(|a| matches!(back.node(*a).map(|p| &p.kind), Some(NodeKind::Instance { .. }))) == inside
|
||||
});
|
||||
n.and_then(|n| n.layout.clone()).unwrap().column_tracks
|
||||
};
|
||||
assert_eq!(tracks(false), vec![GridTrack::fixed(120.0); 2]);
|
||||
assert!(tracks(true).iter().all(|t| *t == GridTrack::fill(1.0)), "{:?}", tracks(true));
|
||||
}
|
||||
|
||||
/// A frame's own min/max size survives whatever its parent is, not only under auto layout.
|
||||
#[test]
|
||||
fn frame_size_limits_survive_without_auto_layout_parent() {
|
||||
|
||||
Reference in New Issue
Block a user