Select > Same: type matches on its characters' fill and stroke (fixes #1737)

This commit is contained in:
Marco Arnulfo authored and bflatastic committed 2026-10-14 03:55:41 +07:00
1 parent 2f4cc8a1cc
commit d9328c7668
3 files changed
+53 -7

No files matched your search

+12 -5
View File
@@ -6,6 +6,7 @@ use serde_json::{Value, json};
use vectorcraft_color::Paint;
use vectorcraft_doc::{Document, Node, NodeId, NodeKind};
use super::wand::{fill_of, stroke_of};
use super::*;
pub fn specs() -> Vec<CommandSpec> {
@@ -97,7 +98,7 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
|s, _| {
let tint = s.prefs.select_same_tint_percent;
same(s, "select.same.fillColor", |a, b| same_paint(&a.appearance.fill_paint(), &b.appearance.fill_paint(), tint))
same(s, "select.same.fillColor", |a, b| same_paint(&fill_of(a), &fill_of(b), tint))
}
),
cmd!(
@@ -109,13 +110,13 @@ pub fn specs() -> Vec<CommandSpec> {
has_selection,
|s, _| {
let tint = s.prefs.select_same_tint_percent;
same(s, "select.same.strokeColor", |a, b| same_paint(&a.appearance.stroke_paint(), &b.appearance.stroke_paint(), tint))
same(s, "select.same.strokeColor", |a, b| same_paint(&stroke_of(a).0, &stroke_of(b).0, tint))
}
),
cmd!("select.same.strokeWeight", "Stroke Weight", ["Select", "Same"], None, "{}", has_selection, |s, _| same(
s,
"select.same.strokeWeight",
|a, b| (a.appearance.stroke_width() - b.appearance.stroke_width()).abs() < 1e-9
|a, b| (stroke_weight(a) - stroke_weight(b)).abs() < 1e-9
)),
cmd!(
"select.same.fillAndStroke",
@@ -127,8 +128,7 @@ pub fn specs() -> Vec<CommandSpec> {
|s, _| {
let tint = s.prefs.select_same_tint_percent;
same(s, "select.same.fillAndStroke", |a, b| {
let (x, y) = (&a.appearance, &b.appearance);
same_paint(&x.fill_paint(), &y.fill_paint(), tint) && same_paint(&x.stroke_paint(), &y.stroke_paint(), tint)
same_paint(&fill_of(a), &fill_of(b), tint) && same_paint(&stroke_of(a).0, &stroke_of(b).0, tint)
})
}
),
@@ -514,6 +514,13 @@ fn same_paint(a: &Paint, b: &Paint, tint: bool) -> bool {
}
}
/// Select › Same › Stroke Weight: an unpainted stroke weighs nothing, as
/// [`vectorcraft_doc::Appearance::stroke_width`] has it.
fn stroke_weight(n: &Node) -> f64 {
let (paint, width) = stroke_of(n);
if paint.is_none() { 0.0 } else { width }
}
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"))?;
+4 -2
View File
@@ -86,14 +86,16 @@ fn set(s: &mut Session, p: &Value) -> Result<Value> {
Ok(json!(s.menu.wand))
}
fn fill_of(n: &Node) -> Paint {
/// The fill an object shows: type's comes from its characters (the first run speaks for all).
pub(crate) fn fill_of(n: &Node) -> Paint {
match &n.kind {
NodeKind::Text(t) => t.runs.first().map(|r| r.style.fill.clone()).unwrap_or_default(),
_ => n.appearance.fill_paint(),
}
}
fn stroke_of(n: &Node) -> (Paint, f64) {
/// The stroke paint and weight an object shows, type's from its first run as [`fill_of`].
pub(crate) fn stroke_of(n: &Node) -> (Paint, f64) {
match &n.kind {
NodeKind::Text(t) => t.runs.first().map(|r| (r.style.stroke.clone(), r.style.stroke_width)).unwrap_or_default(),
_ => (n.appearance.stroke_paint(), n.appearance.stroke_width()),
+37
View File
@@ -766,6 +766,43 @@ fn select_same_font_size_and_fill() {
assert!(s.execute("select.same.fontSize", &json!({})).is_err());
}
/// #1737: Select > Same reads type's fill and stroke from its characters (the first run), so type
/// matches type and paths both ways.
#[test]
fn select_same_paint_reads_type_characters() {
let mut s = session();
let t1 = text(&mut s, "a");
let t2 = text(&mut s, "b");
let t3 = text(&mut s, "c");
let r = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
let q = rect(&mut s, 20.0, 0.0, 10.0, 10.0);
sel(&mut s, &[t1, t2, r]);
s.execute("paint.setFill", &json!({"color": "#ff0000"})).unwrap();
sel(&mut s, &[t3, q]);
s.execute("paint.setFill", &json!({"color": "#0000ff"})).unwrap();
sel(&mut s, &[t1, r]);
s.execute("paint.setStroke", &json!({"color": "#00ff00"})).unwrap();
s.execute("stroke.set", &json!({"weight": 3})).unwrap();
sel(&mut s, &[t3]);
s.execute("paint.setStroke", &json!({"color": "#000000"})).unwrap();
let q_stroke = node(&s, q).appearance;
assert_eq!((q_stroke.stroke_paint(), q_stroke.stroke_width()), (Paint::solid(Color::BLACK), 1.0));
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
};
assert_eq!(same(&mut s, t1, "select.same.fillColor"), vec![t1, t2, r]);
assert_eq!(same(&mut s, q, "select.same.fillColor"), vec![t3, q]);
assert_eq!(same(&mut s, r, "select.same.strokeColor"), vec![t1, r]);
assert_eq!(same(&mut s, t3, "select.same.strokeColor"), vec![t3, q]);
assert_eq!(same(&mut s, q, "select.same.strokeWeight"), vec![t3, q]);
assert_eq!(same(&mut s, t1, "select.same.fillAndStroke"), vec![t1, r]);
}
#[test]
fn select_direction_handles_selects_all_anchors() {
let mut s = session();