mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 06:01:12 +00:00
Contour: keep a Contour's corners when dragged again; ignore a no-distance drag
Two fixes from review of #5694, each with a test that fails without it: - Dragging a selected Contour sent the tool's own corner options, so a Round or Square Contour came back Sharp and lost its miter limit, and the Contour bar (which reads the effect) disagreed with the shape. The press now reads the Contour's own corners and miter limit and every preview of that drag carries them; a new Contour still takes the tool's options. - A drag that never moved the distance (along the edge, or rounded to nothing by Shift) previewed offset 0 and committed an unchanged copy and an undo step. Nothing is previewed until the distance leaves where it started. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Sonnet 5.5
parent
8584bf1ab6
commit
8ce22e71eb
@@ -299,6 +299,40 @@ mod tool {
|
||||
assert_eq!(effects_of(&s, contour)[0].1["joins"], json!("round"));
|
||||
}
|
||||
|
||||
/// A Contour keeps its own corner style and miter limit when it is dragged again, whatever the
|
||||
/// tool's options are by then (they said Sharp when the Contour was made Square).
|
||||
#[test]
|
||||
fn dragging_a_contour_again_keeps_its_corners_and_miter_limit() {
|
||||
let (mut s, _) = ready();
|
||||
s.execute("object.contour", &json!({"offset": 5, "corners": "square", "miterLimit": 7})).unwrap();
|
||||
let contour = s.doc().unwrap().selection.objects[0];
|
||||
s.set_tool_option("corners", &json!("sharp"));
|
||||
at(&mut s, PointerKind::Down, 150.0, 120.0, Mods::default());
|
||||
at(&mut s, PointerKind::Drag, 158.0, 120.0, Mods::default());
|
||||
at(&mut s, PointerKind::Up, 158.0, 120.0, Mods::default());
|
||||
let fx = effects_of(&s, contour);
|
||||
assert_eq!(fx.len(), 1);
|
||||
assert!((offset_of(&s, contour) - 13.0).abs() < 1e-6, "{}", offset_of(&s, contour));
|
||||
assert_eq!(fx[0].1["joins"], json!("bevel"), "{:?}", fx[0].1);
|
||||
assert_eq!(fx[0].1["miterLimit"], json!(7.0), "{:?}", fx[0].1);
|
||||
}
|
||||
|
||||
/// A drag that never moves the distance (along the edge, or rounded back to nothing by Shift)
|
||||
/// leaves no unchanged copy and no undo step.
|
||||
#[test]
|
||||
fn a_drag_along_the_edge_changes_nothing() {
|
||||
let (mut s, _) = ready();
|
||||
let (before, nodes) = (undo_depth(&s), node_count(&s));
|
||||
let shift = Mods { shift: true, ..Mods::default() };
|
||||
for (mods, to) in [(Mods::default(), (150.0, 135.0)), (shift, (150.3, 120.0))] {
|
||||
at(&mut s, PointerKind::Down, 150.0, 120.0, mods);
|
||||
at(&mut s, PointerKind::Drag, to.0, to.1, mods);
|
||||
at(&mut s, PointerKind::Up, to.0, to.1, mods);
|
||||
assert_eq!((undo_depth(&s), node_count(&s)), (before, nodes), "{to:?}");
|
||||
assert!(!s.in_interaction());
|
||||
}
|
||||
}
|
||||
|
||||
/// The outline a Contour was made from shows as a dashed guide over a solid halo (so it reads
|
||||
/// over any fill); a shape that is not a Contour yet has none.
|
||||
#[test]
|
||||
|
||||
@@ -51,6 +51,22 @@ fn contour_offset(n: &Node) -> Option<f64> {
|
||||
Some(e.params.get("offset").and_then(Value::as_f64).unwrap_or(0.0))
|
||||
}
|
||||
|
||||
/// The corner style and miter limit of a Contour's Offset Path effect, as `object.contour` takes
|
||||
/// them.
|
||||
fn contour_style(n: &Node) -> Option<Value> {
|
||||
let e = n.appearance.effects.iter().rev().find(|e| e.id == EFFECT)?;
|
||||
let corners = match e.params.get("joins").and_then(Value::as_str) {
|
||||
Some("round") => "round",
|
||||
Some("bevel") => "square",
|
||||
_ => "sharp",
|
||||
};
|
||||
let mut style = json!({ "corners": corners });
|
||||
if let Some(m) = e.params.get("miterLimit").and_then(Value::as_f64) {
|
||||
style["miterLimit"] = json!(m);
|
||||
}
|
||||
Some(style)
|
||||
}
|
||||
|
||||
/// How far `p` is from the nearest of `bases`' edges: positive outside, negative inside.
|
||||
fn signed_distance(bases: &[Base], p: Point) -> Option<f64> {
|
||||
let mut nearest = f64::INFINITY;
|
||||
@@ -69,6 +85,9 @@ struct Drag {
|
||||
pressed_at: f64,
|
||||
/// The Contour's distance at the press (0 for a shape that is not one yet).
|
||||
start: f64,
|
||||
/// The corner style and miter limit every preview carries: the pressed Contour's own, else the
|
||||
/// tool's options.
|
||||
style: Value,
|
||||
/// The distance last previewed.
|
||||
last: Option<f64>,
|
||||
/// Where the pointer is, for the distance label.
|
||||
@@ -108,8 +127,9 @@ impl Corners {
|
||||
}
|
||||
|
||||
impl ContourTool {
|
||||
fn params(&self, offset: f64) -> Value {
|
||||
let mut p = json!({"offset": offset, "corners": self.corners.id()});
|
||||
/// The corner style and miter limit of a new Contour: the tool's options.
|
||||
fn style(&self) -> Value {
|
||||
let mut p = json!({ "corners": self.corners.id() });
|
||||
if let Some(m) = self.miter_limit {
|
||||
p["miterLimit"] = json!(m);
|
||||
}
|
||||
@@ -142,21 +162,25 @@ impl Tool for ContourTool {
|
||||
PointerKind::Down => {
|
||||
let shapes = selected_shapes(cx);
|
||||
let start = shapes.first().and_then(|n| contour_offset(n)).unwrap_or(0.0);
|
||||
// A Contour keeps its own corners; a new one takes the tool's.
|
||||
let style = shapes.first().and_then(|n| contour_style(n)).unwrap_or_else(|| self.style());
|
||||
let bases: Vec<Base> = shapes.into_iter().filter_map(base_of).collect();
|
||||
let Some(pressed_at) = signed_distance(&bases, ev.pos) else { return vec![] };
|
||||
self.drag = Some(Drag { bases, pressed_at, start, last: None, pos: ev.pos });
|
||||
self.drag = Some(Drag { bases, pressed_at, start, style, last: None, pos: ev.pos });
|
||||
vec![Action::Begin("Contour".into())]
|
||||
}
|
||||
PointerKind::Drag => {
|
||||
let mut params = self.params(0.0);
|
||||
let Some(drag) = self.drag.as_mut() else { return vec![] };
|
||||
drag.pos = ev.pos;
|
||||
let Some(now) = signed_distance(&drag.bases, ev.pos) else { return vec![] };
|
||||
let offset = round_distance(drag.start + now - drag.pressed_at, ev.mods);
|
||||
if !offset.is_finite() || drag.last == Some(offset) {
|
||||
// Nothing is previewed until the distance has left where it started: a drag along
|
||||
// the edge (or one Shift rounds back) would otherwise leave an unchanged copy.
|
||||
if !offset.is_finite() || drag.last == Some(offset) || (drag.last.is_none() && offset == drag.start) {
|
||||
return vec![];
|
||||
}
|
||||
drag.last = Some(offset);
|
||||
let mut params = drag.style.clone();
|
||||
params["offset"] = json!(offset);
|
||||
vec![Action::Preview("object.contour".into(), params)]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user