mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
Select > Same: type matches on its characters' fill and stroke (fixes #1737)
This commit is contained in:
1 parent
2f4cc8a1cc
commit
d9328c7668
3 files changed
+53
-7
No files matched your search
@@ -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"))?;
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in new issue
Block a user