Fix #3480: pattern edit preview honors Overlap stacking

## Summary
In pattern editing mode, preview copies were drawn in row-major order and the
centre tile was always composited on top, so Pattern Options Overlap had no
visible effect when negative spacing made tiles overlap (as in Illustrator).
Preview copies now paint in the same order as tiled pattern fills.

## Tests
- vectorcraft-doc: preview_paint_order_respects_overlap

Closes storytold/vectorcraft#3480

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-10-22 09:23:27 +07:00
committed by bflatastic
co-authored by nrq6993-a11y
parent db42a68e3f
commit e87534342b
2 changed files with 42 additions and 10 deletions
+27 -1
View File
@@ -279,7 +279,7 @@ impl PatternDef {
out
}
/// Offsets of the pattern-editing preview copies (`copies`×`copies` around the tile, the
/// centre `(0, 0)` excluded).
/// centre `(0, 0)` excluded), in row-major order (not paint order).
pub fn preview_offsets(&self) -> Vec<Vec2> {
let k = (self.copies.clamp(1, 15) / 2) as i64;
let mut out = vec![];
@@ -292,6 +292,21 @@ impl PatternDef {
}
out
}
/// Offsets of every pattern-editing preview copy, including the centre tile, in the same paint
/// order [`offsets_covering`] uses for [`Overlap`].
pub fn preview_offsets_in_paint_order(&self) -> Vec<Vec2> {
let k = (self.copies.clamp(1, 15) / 2) as i64;
let (i0, i1, j0, j1) = (-k, k, -k, k);
let mut out = vec![];
let rows: Vec<i64> = if self.overlap.bottom_in_front { (j0..=j1).collect() } else { (j0..=j1).rev().collect() };
for &j in &rows {
let cols: Vec<i64> = if self.overlap.right_in_front { (i0..=i1).collect() } else { (i0..=i1).rev().collect() };
for i in cols {
out.push(self.offset(i, j));
}
}
out
}
/// Instances covering `region` (document space) as plain nodes for a pattern painted with
/// `paint_xf` (pattern space → document), fresh-id-less (id 0). Used by exporters and Distort
/// Pattern Fills; Expand uses [`Self::instances_in_with`].
@@ -744,6 +759,17 @@ mod tests {
}
}
#[test]
fn preview_paint_order_respects_overlap() {
let mut def = PatternDef::new("p", vec![sq(0.0, 0.0, 10.0)]);
def.copies = 3;
assert!(close(*def.preview_offsets_in_paint_order().last().unwrap(), Vec2::new(-10.0, -10.0)));
let mut d2 = def.clone();
d2.overlap = Overlap { right_in_front: true, bottom_in_front: true };
assert!(close(*d2.preview_offsets_in_paint_order().last().unwrap(), Vec2::new(10.0, 10.0)));
assert_eq!(def.preview_offsets_in_paint_order().len(), 9);
}
#[test]
fn covering_offsets_and_overlap_order() {
let def = PatternDef::new("p", vec![sq(0.0, 0.0, 10.0)]);
+15 -9
View File
@@ -308,17 +308,23 @@ impl Renderer {
let children: Vec<Arc<Node>> = layer.children().cloned().unwrap_or_default();
if layer.visible && !children.is_empty() {
let dim = (def.dim_copies / 100.0).clamp(0.0, 1.0);
let comp = crate::group::Composite { opacity: dim, ..Default::default() };
self.group(ctx, f, comp, &mut |r, c, fr| {
for o in def.preview_offsets() {
let frame = Frame { view: fr.view * Affine::translate(o), visible: fr.visible - o, ..*fr };
for a in &children {
r.draw_arc(c, &frame, a);
}
for o in def.preview_offsets_in_paint_order() {
let is_center = o.x == 0.0 && o.y == 0.0;
if is_center {
self.draw_arc(ctx, f, layer);
} else {
let comp = crate::group::Composite { opacity: dim, ..Default::default() };
self.group(ctx, f, comp, &mut |r, c, fr| {
let frame = Frame { view: fr.view * Affine::translate(o), visible: fr.visible - o, ..*fr };
for a in &children {
r.draw_arc(c, &frame, a);
}
});
}
});
}
} else {
self.draw_arc(ctx, f, layer);
}
self.draw_arc(ctx, f, layer);
// The tile edge and swatch bounds are editing aids: exports and thumbnails leave them out,
// as they do guides (#3261).
if f.opts.skip_templates {