Fix #3188: arranging art inside a clipping mask keeps the clipping path at the bottom

In a clip group or a clipping layer the bottom child is the clipping path.
Object › Arrange moved the selected children with no regard for that slot:
Send to Back (and Send Backward from just above it) put the art under the
clipping path, so the art became the clipping path, lost its role as art,
and the group drew nothing. Bring Forward / to Front on the clipping path
itself would likewise have made art the clip. Layers-panel moves already
keep the clipping path at the bottom (layer.rs, `index.max(1)`); arrange
didn't.

- object.rs `arrange`: in a parent that clips, the clipping path is left
  where it is and the art is arranged within the slots above it (Back goes
  to just above the clipping path, Backward stops there).
- tests: Back, Backward, several objects, and Forward/Front on the
  clipping path in a clip group (with a render check); Send to Back in a
  clipping layer.
This commit is contained in:
Taha-Mahmoodi
2026-10-21 15:42:35 +07:00
committed by bflatastic
parent d2625ec108
commit 4fdb3e1f9b
3 changed files with 69 additions and 5 deletions
+11 -5
View File
@@ -639,18 +639,24 @@ fn arrange(s: &mut Session, how: Arrange) -> Result<Value> {
None => by_parent.push((par, vec![*id])),
}
}
for (par, group) in by_parent {
for (par, mut group) in by_parent {
// In a clip group or a clipping layer the bottom child is the clipping path: it stays
// there, and the art above it never goes below it (#3188).
let floor = usize::from(par.and_then(|p| d.node(p)).is_some_and(vectorcraft_doc::Node::clips));
if floor > 0 {
group.retain(|id| d.position(*id).is_some_and(|(_, idx, _)| idx >= floor));
}
for (k, id) in group.iter().enumerate() {
let Some((_, idx, len)) = d.position(*id) else { continue };
if len == 0 {
if len <= floor {
continue;
}
let last = len - 1;
let to = match how {
Arrange::Front => last.saturating_sub(k),
Arrange::Back => k.min(last),
Arrange::Front => last.saturating_sub(k).max(floor),
Arrange::Back => (floor + k).min(last),
Arrange::Forward => (idx + 1).min(last.saturating_sub(k)).max(idx),
Arrange::Backward => idx.saturating_sub(1).max(k.min(idx)),
Arrange::Backward => idx.saturating_sub(1).max(floor + k.min(idx.saturating_sub(floor))),
};
if to != idx {
d.move_node(*id, par, to)?;