mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-28 03:37:00 -06:00
Select: Bristle Brush Strokes selects objects by their brush's type
Select > Object > Bristle Brush Strokes selected the objects whose brush name contains "bristle". It missed objects with a bristle brush of any other name, and it selected objects whose brush is of another type with "Bristle" in its name. select.object.bristleBrushStrokes now looks up the brush of each object's topmost stroke in the document's brush library and selects the object when the brush is a bristle brush, whatever its name. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -83,13 +83,18 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Bristle Brush Strokes",
|
||||
["Select", "Object"],
|
||||
None,
|
||||
"{} objects whose brush name contains \"bristle\" → {count}",
|
||||
"{} objects whose topmost stroke has a bristle brush (type bristle in brush.list), whatever the brush is named → {count}",
|
||||
has_doc,
|
||||
|s, _| by_kind(s, |n| n
|
||||
.appearance
|
||||
.stroke()
|
||||
.and_then(|st| st.brush.as_deref())
|
||||
.is_some_and(|b| b.to_ascii_lowercase().contains("bristle")))
|
||||
|s, _| {
|
||||
let brushes = vectorcraft_brush::library(&s.doc()?.doc);
|
||||
by_kind(s, |n| {
|
||||
n.appearance
|
||||
.stroke()
|
||||
.and_then(|st| st.brush.as_deref())
|
||||
.and_then(|name| brushes.iter().find(|b| b.name == name))
|
||||
.is_some_and(|b| matches!(b.kind, vectorcraft_brush::BrushKind::Bristle(_)))
|
||||
})
|
||||
}
|
||||
),
|
||||
cmd!(
|
||||
"select.save",
|
||||
@@ -305,7 +310,7 @@ fn same_text(s: &mut Session, cmd: &str, eq: fn(&CharStyle, &CharStyle) -> bool)
|
||||
finish_same(s, cmd, ids)
|
||||
}
|
||||
|
||||
fn by_kind(s: &mut Session, f: fn(&Node) -> bool) -> Result<Value> {
|
||||
fn by_kind(s: &mut Session, f: impl Fn(&Node) -> bool) -> Result<Value> {
|
||||
let ids = candidates(s, f)?;
|
||||
let n = ids.len();
|
||||
s.select(|_, sel| sel.set(ids))?;
|
||||
|
||||
@@ -1217,6 +1217,27 @@ fn select_point_and_area_text() {
|
||||
assert_eq!(s.execute("select.object.brushStrokes", &json!({})).unwrap()["count"], 0);
|
||||
}
|
||||
|
||||
/// Select › Object › Bristle Brush Strokes goes by the brush's type: a bristle brush counts
|
||||
/// whatever it is named, and a brush of another type with "Bristle" in its name does not.
|
||||
#[test]
|
||||
fn bristle_brush_strokes_select_by_brush_type() {
|
||||
let mut s = session();
|
||||
let round = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
|
||||
let soft = rect(&mut s, 20.0, 0.0, 10.0, 10.0);
|
||||
let nib = rect(&mut s, 40.0, 0.0, 10.0, 10.0);
|
||||
let _plain = rect(&mut s, 60.0, 0.0, 10.0, 10.0);
|
||||
s.execute("brush.apply", &json!({"name": "Bristle Round", "ids": [round.0]})).unwrap();
|
||||
s.execute("brush.apply", &json!({"name": "Bristle Flat Fan", "ids": [soft.0]})).unwrap();
|
||||
s.execute("brush.options", &json!({"name": "Bristle Flat Fan", "newName": "Soft"})).unwrap();
|
||||
s.execute("brush.new", &json!({"type": "calligraphic", "name": "Bristle-like"})).unwrap();
|
||||
s.execute("brush.apply", &json!({"name": "Bristle-like", "ids": [nib.0]})).unwrap();
|
||||
let brush = |s: &Session, id: NodeId| s.doc().unwrap().doc.node(id).and_then(|n| n.appearance.stroke()).and_then(|st| st.brush.clone());
|
||||
assert_eq!(brush(&s, soft).as_deref(), Some("Soft"));
|
||||
assert_eq!(brush(&s, nib).as_deref(), Some("Bristle-like"));
|
||||
assert_eq!(s.execute("select.object.bristleBrushStrokes", &json!({})).unwrap()["count"], 2);
|
||||
assert_eq!(selected(&s), vec![round, soft]);
|
||||
}
|
||||
|
||||
/// Select › Object and Select › Same leave out hidden and locked objects, and the objects in hidden
|
||||
/// or locked groups and sublayers, as Select All does.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user