From 45b266c8056e0f9b4648efbce5c638907e08c7c1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 01:14:24 -0700 Subject: [PATCH] Appearance: Reduce to Basic Appearance finds the active row in the first selected object The targets are in paint order, so the first selected object is not always the first target. The active row is now matched against the first selected object wherever it is among them. --- crates/engine/src/cmd/appearance.rs | 16 ++++++++++------ crates/engine/src/tests/appearance.rs | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/engine/src/cmd/appearance.rs b/crates/engine/src/cmd/appearance.rs index df691490f..3da1cb56c 100644 --- a/crates/engine/src/cmd/appearance.rs +++ b/crates/engine/src/cmd/appearance.rs @@ -615,12 +615,16 @@ fn reduce_basic(s: &mut Session, p: &Value) -> Result { // When every object comes out as it was, the document and its undo history stay as they are. let before = s.doc()?.doc.clone(); // The Appearance panel's active row (a row of the first selected object's stack, and in each - // other target the row equal to it there), when the command acts on that selection. - let active = match s.appearance_item() { - Some(index) - if p.get("ids").is_none() && p.get("id").is_none() && !targets_contents(p)? && s.doc()?.selection.subjects().first() == ids.first() => - { - items_per_target(&before, &ids, &[index], true) + // other target the row equal to it there), when the command acts on that selection. The targets + // are in paint order, so the first selected object can be any of them. + let first = s.doc()?.selection.subjects().first().and_then(|f| ids.iter().position(|id| id == f)); + let active = match (s.appearance_item(), first) { + (Some(index), Some(k)) if p.get("ids").is_none() && p.get("id").is_none() && !targets_contents(p)? => { + let mut from = ids.clone(); + from.swap(0, k); + let mut rows = items_per_target(&before, &from, &[index], true); + rows.swap(0, k); + rows } _ => vec![Vec::new(); ids.len()], }; diff --git a/crates/engine/src/tests/appearance.rs b/crates/engine/src/tests/appearance.rs index 490622812..3a56ff7f1 100644 --- a/crates/engine/src/tests/appearance.rs +++ b/crates/engine/src/tests/appearance.rs @@ -444,6 +444,25 @@ fn reduce_to_basic_keeps_the_active_row() { assert_eq!(stroke(&n, 1).width, 1.0); } +/// The active row is a row of the first selected object even when that object paints above the +/// others; another object without an equal row keeps its topmost fill. +#[test] +fn reduce_to_basic_keeps_the_active_row_of_the_first_selected_object() { + let mut s = session(); + let a = rect(&mut s, 50.0); + run(&mut s, "appearance.addFill", json!({})); + run(&mut s, "appearance.setItem", json!({"index": 2, "color": "#0000ff"})); // [Fill, Stroke, Fill blue] + let b = rect(&mut s, 200.0); + run(&mut s, "appearance.addFill", json!({})); + run(&mut s, "appearance.setItem", json!({"index": 0, "color": "#00ff00"})); + run(&mut s, "appearance.setItem", json!({"index": 2, "color": "#ff0000"})); // [Fill green, Stroke, Fill red] + run(&mut s, "select.set", json!({"ids": [b.0, a.0]})); + run(&mut s, "appearance.setActiveItem", json!({"index": 0})); + run(&mut s, "appearance.reduceToBasic", json!({})); + assert_eq!(fill_paint(&node(&s, b), 0).color().unwrap().to_hex(), "#00ff00"); + assert_eq!(fill_paint(&node(&s, a), 0).color().unwrap().to_hex(), "#0000ff"); +} + /// How many steps there are to undo and to redo, and whether the document has unsaved changes. fn history(s: &Session) -> (usize, usize, bool) { let st = s.doc().unwrap();