mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-01 05:37:23 -06:00
Merge pull request #4858 from dotCooCoo/fix/reduce-basic-keeps-selected-fill
Appearance: Reduce to Basic Appearance keeps the selected fill or stroke row
This commit is contained in:
@@ -71,7 +71,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Reduce to Basic Appearance",
|
||||
["Window", "Appearance"],
|
||||
None,
|
||||
"{ids?|id?, target?} keep only the topmost visible fill and stroke, without their own opacity, blend mode or effects (a group keeps no rows it lacked), and drop the object's effects",
|
||||
"{ids?|id?, target?} keep only one visible fill and stroke (without ids: the Appearance panel's active row in place of the topmost one of its kind; else the topmost), without their own opacity, blend mode or effects (a group keeps no rows it lacked), and drop the object's effects",
|
||||
has_doc,
|
||||
reduce_basic
|
||||
),
|
||||
@@ -614,11 +614,30 @@ fn reduce_basic(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids = require_targets(s, p, "appearance.reduceToBasic")?;
|
||||
// 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. 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()],
|
||||
};
|
||||
super::overprint::edit_counted(s, "Reduce to Basic Appearance", |d| {
|
||||
for id in &ids {
|
||||
for (id, at) in ids.iter().zip(&active) {
|
||||
if let Some(n) = d.node_mut(*id) {
|
||||
// The topmost visible fill and stroke stay, without their own transparency or effects.
|
||||
let top = |fill: bool| n.appearance.items.iter().rev().find(|i| i.visible() && i.is_fill() == fill).cloned();
|
||||
// The active row stays when it is a visible fill or stroke; otherwise the topmost
|
||||
// visible one of each kind does. Both lose their own transparency and effects.
|
||||
let picked = at.first().and_then(|i| n.appearance.items.get(*i)).filter(|i| i.visible()).cloned();
|
||||
let top = |fill: bool| match &picked {
|
||||
Some(i) if i.is_fill() == fill => Some(i.clone()),
|
||||
_ => n.appearance.items.iter().rev().find(|i| i.visible() && i.is_fill() == fill).cloned(),
|
||||
};
|
||||
let (fill, stroke) = (top(true), top(false));
|
||||
// Other objects always keep a fill row (None when there was none); a group only
|
||||
// keeps the rows it had.
|
||||
|
||||
@@ -400,6 +400,69 @@ fn clear_and_reduce_to_basic() {
|
||||
assert_eq!(node(&s, id).opacity, 0.4);
|
||||
}
|
||||
|
||||
/// Reduce to Basic Appearance keeps the Appearance panel's active fill or stroke row instead of the
|
||||
/// topmost one of its kind, in each selected object with the same stack. With `ids` it keeps the
|
||||
/// topmost ones.
|
||||
#[test]
|
||||
fn reduce_to_basic_keeps_the_active_row() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 50.0);
|
||||
let b = rect(&mut s, 200.0);
|
||||
let c = rect(&mut s, 300.0);
|
||||
let stack = |s: &mut Session| {
|
||||
run(s, "appearance.addFill", json!({}));
|
||||
run(s, "appearance.addStroke", json!({})); // [Fill, Stroke, Fill, Stroke]
|
||||
run(s, "appearance.setItem", json!({"index": 0, "color": "#00ff00"}));
|
||||
run(s, "appearance.setItem", json!({"index": 2, "color": "#ff0000"}));
|
||||
run(s, "appearance.setItem", json!({"index": 1, "weight": 7}));
|
||||
};
|
||||
run(&mut s, "select.set", json!({"ids": [a.0, b.0]}));
|
||||
stack(&mut s);
|
||||
// The lower, green fill row is active: both objects keep it and their top stroke.
|
||||
run(&mut s, "appearance.setActiveItem", json!({"index": 0}));
|
||||
run(&mut s, "appearance.reduceToBasic", json!({}));
|
||||
for id in [a, b] {
|
||||
let n = node(&s, id);
|
||||
assert!(n.appearance.is_basic(), "{:?}", n.appearance);
|
||||
assert_eq!(fill_paint(&n, 0).color().unwrap().to_hex(), "#00ff00");
|
||||
assert_eq!(stroke(&n, 1).width, 1.0);
|
||||
}
|
||||
// The lower stroke row is active: it stays, with the top fill.
|
||||
run(&mut s, "edit.undo", json!({}));
|
||||
run(&mut s, "appearance.setActiveItem", json!({"index": 1}));
|
||||
run(&mut s, "appearance.reduceToBasic", json!({}));
|
||||
let n = node(&s, a);
|
||||
assert_eq!(fill_paint(&n, 0).color().unwrap().to_hex(), "#ff0000");
|
||||
assert_eq!(stroke(&n, 1).width, 7.0);
|
||||
// Given ids, the topmost fill and stroke stay.
|
||||
run(&mut s, "select.set", json!({"ids": [c.0]}));
|
||||
stack(&mut s);
|
||||
run(&mut s, "appearance.setActiveItem", json!({"index": 0}));
|
||||
run(&mut s, "appearance.reduceToBasic", json!({"ids": [c.0]}));
|
||||
let n = node(&s, c);
|
||||
assert_eq!(fill_paint(&n, 0).color().unwrap().to_hex(), "#ff0000");
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user