mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
Select: Same commands for paint, appearance and shape select compound paths
Select > Same > Fill Color, Stroke Color, Stroke Weight, Fill & Stroke, Opacity, Blending Mode, Appearance and Shape skipped containers, so they never selected a compound path, even when the compound path was the object selected first. With a member path of a compound path selected first, these commands and Same Graphic Style and Appearance Attribute compared the other objects with the member, which has no paint or style of its own. Graphic Style then reported that the selection had no graphic style, and Appearance Attribute that the object had no fill, stroke or effect. The eight commands now match a compound path on its own paint and attributes, as select.color does, and skip its member paths. When a member path is selected first, all ten commands compare the other objects with its compound path. In isolation mode inside a compound path, the member paths are matched as before.
This commit is contained in:
@@ -8,6 +8,7 @@ use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
|
||||
|
||||
use super::wand::{fill_of, stroke_of};
|
||||
use super::*;
|
||||
use crate::DocState;
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
@@ -663,10 +664,27 @@ fn paint_reported(d: &Document, n: &Node) -> bool {
|
||||
compound(n) || (!n.is_container() && !d.parent_of(n.id).and_then(|p| d.node(p)).is_some_and(compound))
|
||||
}
|
||||
|
||||
/// The object Select > Same compares the others with, and whether isolation mode is inside a
|
||||
/// compound path. The object is the first selected one, or its compound path when it is a member
|
||||
/// of one; in isolation mode inside a compound path it is the member path itself.
|
||||
fn same_reference<'a>(st: &'a DocState, cmd: &str) -> Result<(&'a Node, bool)> {
|
||||
let compound = |n: &Node| matches!(n.kind, NodeKind::Compound { .. });
|
||||
let members = st.isolation.and_then(|i| st.doc.ancestry(i)).is_some_and(|a| a.iter().any(|i| st.doc.node(*i).is_some_and(compound)));
|
||||
let first = st.selection.objects.first().and_then(|id| st.doc.node(*id)).ok_or_else(|| bad(cmd, "nothing selected"))?;
|
||||
let refn = match st.doc.parent_of(first.id).and_then(|p| st.doc.node(p)) {
|
||||
Some(p) if !members && compound(p) => p,
|
||||
_ => first,
|
||||
};
|
||||
Ok((refn, members))
|
||||
}
|
||||
|
||||
/// Select > Same: the objects `eq` matches with the object [`same_reference`] returns, among those
|
||||
/// [`paint_reported`] accepts. A compound path matches as a whole. In isolation mode inside a
|
||||
/// compound path, its member paths are matched instead.
|
||||
fn same(s: &mut Session, cmd: &str, eq: impl Fn(&Node, &Node) -> bool) -> Result<Value> {
|
||||
let st = s.doc()?;
|
||||
let refn = st.selection.objects.first().and_then(|id| st.doc.node(*id)).cloned().ok_or_else(|| bad(cmd, "nothing selected"))?;
|
||||
select_where(s, cmd, &json!({}), |_, n| !n.is_container() && eq(n, &refn))
|
||||
let (refn, members) = same_reference(s.doc()?, cmd)?;
|
||||
let refn = refn.clone();
|
||||
select_where(s, cmd, &json!({}), |d, n| (if members { !n.is_container() } else { paint_reported(d, n) }) && eq(n, &refn))
|
||||
}
|
||||
|
||||
/// Select the objects [`walk_selectable`] visits that `f` accepts (not looking inside accepted
|
||||
@@ -690,13 +708,8 @@ fn select_where(s: &mut Session, cmd: &str, p: &Value, f: impl Fn(&Document, &No
|
||||
fn same_graphic_style(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
let cmd = "select.same.graphicStyle";
|
||||
let st = s.doc()?;
|
||||
let id = st
|
||||
.selection
|
||||
.objects
|
||||
.first()
|
||||
.and_then(|id| super::style::linked_style(&st.doc, st.doc.node(*id)?))
|
||||
.map(|g| g.id)
|
||||
.ok_or_else(|| bad(cmd, "the selection has no graphic style"))?;
|
||||
let (refn, _) = same_reference(st, cmd)?;
|
||||
let id = super::style::linked_style(&st.doc, refn).map(|g| g.id).ok_or_else(|| bad(cmd, "the selection has no graphic style"))?;
|
||||
select_where(s, cmd, &json!({}), |d, n| n.extras().graphic_style == Some(id) && super::style::linked_style(d, n).is_some())
|
||||
}
|
||||
|
||||
@@ -709,7 +722,7 @@ enum Attribute {
|
||||
fn same_attribute(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cmd = "select.same.appearanceAttribute";
|
||||
let st = s.doc()?;
|
||||
let refn = st.selection.objects.first().and_then(|id| st.doc.node(*id)).ok_or_else(|| bad(cmd, "nothing selected"))?;
|
||||
let (refn, _) = same_reference(st, cmd)?;
|
||||
let ap = &refn.appearance;
|
||||
let item = p.get("item").and_then(Value::as_u64).map(index).or(s.appearance_item());
|
||||
let attr = match (item.and_then(|i| ap.items.get(i)), ap.effects.first()) {
|
||||
|
||||
@@ -1303,6 +1303,40 @@ fn select_same_paint_reads_type_characters() {
|
||||
assert_eq!(same(&mut s, t1, "select.same.fillAndStroke"), vec![t1, r]);
|
||||
}
|
||||
|
||||
/// Select > Same matches a compound path on its own paint, as a whole: never its member paths.
|
||||
/// When a member path is selected first, the other objects are compared with its compound path.
|
||||
#[test]
|
||||
fn select_same_matches_compound_paths_on_their_own_paint() {
|
||||
let mut s = session();
|
||||
let [a, b] = [0.0, 20.0].map(|x| rect(&mut s, x, 0.0, 10.0, 10.0));
|
||||
let outer = rect(&mut s, 100.0, 0.0, 50.0, 50.0);
|
||||
let inner = rect(&mut s, 110.0, 10.0, 20.0, 20.0);
|
||||
let r = rect(&mut s, 40.0, 0.0, 10.0, 10.0);
|
||||
for id in [a, b, outer, inner] {
|
||||
fill(&mut s, id, "#000000");
|
||||
}
|
||||
fill(&mut s, r, "#ff0000");
|
||||
sel(&mut s, &[outer, inner]);
|
||||
let c = id_of(&s.execute("object.compoundPath.make", &json!({})).unwrap());
|
||||
let same = |s: &mut Session, from: NodeId, cmd: &str| {
|
||||
sel(s, &[from]);
|
||||
s.execute(cmd, &json!({})).unwrap();
|
||||
let mut v = selected(s);
|
||||
v.sort();
|
||||
v
|
||||
};
|
||||
for from in [a, c, outer] {
|
||||
assert_eq!(same(&mut s, from, "select.same.fillColor"), vec![a, b, c], "from {from:?}");
|
||||
}
|
||||
assert_eq!(same(&mut s, inner, "select.same.shapeType"), vec![c]);
|
||||
fill(&mut s, c, "#ff0000");
|
||||
assert_eq!(same(&mut s, a, "select.same.fillColor"), vec![a, b]);
|
||||
assert_eq!(same(&mut s, r, "select.same.fillColor"), vec![r, c]);
|
||||
// Isolated in the compound path, its member paths are the objects to match.
|
||||
s.execute("object.isolate", &json!({"id": c.0})).unwrap();
|
||||
assert_eq!(same(&mut s, outer, "select.same.shapeType"), vec![outer, inner]);
|
||||
}
|
||||
|
||||
/// Select by Color needs no object to start from: it matches fills and strokes as
|
||||
/// `document.inspect` reports them, `#rrggbb`.
|
||||
#[test]
|
||||
|
||||
@@ -256,6 +256,36 @@ fn select_same_appearance_attribute() {
|
||||
assert_eq!(s.doc().unwrap().selection.objects, vec![a, b]);
|
||||
}
|
||||
|
||||
/// With a member path of a compound path selected first, Select > Same > Graphic Style and
|
||||
/// Appearance Attribute compare the other objects with the compound path, except in isolation mode
|
||||
/// inside it.
|
||||
#[test]
|
||||
fn select_same_style_and_attribute_from_a_compound_path_member() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 10.0);
|
||||
set_fill(&mut s, a, "#ff0000");
|
||||
run(&mut s, "effect.apply", json!({"effect": "distort.roughen"}));
|
||||
run(&mut s, "graphicStyle.new", json!({"name": "Rough"}));
|
||||
run(&mut s, "graphicStyle.apply", json!({"name": "Rough"}));
|
||||
let outer = rect(&mut s, 100.0);
|
||||
let inner = rect(&mut s, 120.0);
|
||||
run(&mut s, "select.set", json!({"ids": [outer.0, inner.0]}));
|
||||
let c = NodeId(run(&mut s, "object.compoundPath.make", json!({}))["id"].as_u64().unwrap());
|
||||
run(&mut s, "graphicStyle.apply", json!({"name": "Rough"}));
|
||||
rect(&mut s, 300.0);
|
||||
for cmd in ["select.same.graphicStyle", "select.same.appearanceAttribute"] {
|
||||
for from in [c, outer] {
|
||||
run(&mut s, "select.set", json!({"ids": [from.0]}));
|
||||
run(&mut s, cmd, json!({}));
|
||||
assert_eq!(s.doc().unwrap().selection.objects, vec![a, c], "{cmd} from {from:?}");
|
||||
}
|
||||
}
|
||||
// Isolated in the compound path, its member paths are compared, and they carry no style.
|
||||
run(&mut s, "object.isolate", json!({"id": c.0}));
|
||||
run(&mut s, "select.set", json!({"ids": [outer.0]}));
|
||||
assert!(s.execute("select.same.graphicStyle", &json!({})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_info_lists_style_names() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user