mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-22 03:32:39 -05:00
Merge pull request #1561 from AyberkA0/pen-shift-45
fix(viewer): Shift-click with the Pen draws a straight segment (#1544)
This commit is contained in:
2 files changed
+66
No files matched your search
@@ -2228,6 +2228,12 @@ fn pen_press(
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Shift keeps the new segment at a multiple of 45° from the vertex before it (#1544).
|
||||
let last = p.sp.vertices.last().map(|v| p.m.apply(gv2(v[0], v[1])));
|
||||
let c = match last {
|
||||
Some(q) if mods.shift => constrain_45([q.x, q.y], c),
|
||||
_ => c,
|
||||
};
|
||||
let lp = inv.apply(gv2(c[0], c[1]));
|
||||
let lp = [lp.x, lp.y];
|
||||
if let Ok(v) = app.session.execute("mask.addVertex", json!({"layer": lid.0, "mask": uid, "point": lp})) {
|
||||
@@ -2289,6 +2295,20 @@ fn pen_press(
|
||||
});
|
||||
}
|
||||
|
||||
/// `to` moved onto the nearest line through `from` at a multiple of 45° (horizontal, vertical or
|
||||
/// diagonal), as far along it as `to` lies: the Pen's Shift-click.
|
||||
fn constrain_45(from: [f64; 2], to: [f64; 2]) -> [f64; 2] {
|
||||
let (dx, dy) = (to[0] - from[0], to[1] - from[1]);
|
||||
if !(dx.is_finite() && dy.is_finite()) || (dx == 0.0 && dy == 0.0) {
|
||||
return to;
|
||||
}
|
||||
let step = std::f64::consts::FRAC_PI_4;
|
||||
let a = (dy.atan2(dx) / step).round() * step;
|
||||
let (ux, uy) = (a.cos(), a.sin());
|
||||
let along = dx * ux + dy * uy;
|
||||
[from[0] + ux * along, from[1] + uy * along]
|
||||
}
|
||||
|
||||
/// Visible, unlocked layers eligible for viewer hit tests, in front-to-back order.
|
||||
/// Solo scope matches the renderer: any soloed AV layer activates it at every time (as in After
|
||||
/// Effects), even when hidden, locked or outside its in-out range.
|
||||
@@ -2692,6 +2712,18 @@ mod tests {
|
||||
use super::*;
|
||||
use effectcraft_engine::geom::{Mat4, vec3};
|
||||
|
||||
/// #1544: Shift-clicking with the Pen keeps the new segment horizontal, vertical or diagonal.
|
||||
#[test]
|
||||
fn pen_shift_click_keeps_the_segment_at_45_degrees() {
|
||||
let near = |a: [f64; 2], b: [f64; 2]| (a[0] - b[0]).abs() < 1e-9 && (a[1] - b[1]).abs() < 1e-9;
|
||||
let from = [100.0, 100.0];
|
||||
assert!(near(constrain_45(from, [104.0, 180.0]), [100.0, 180.0]), "straight down");
|
||||
assert!(near(constrain_45(from, [30.0, 97.0]), [30.0, 100.0]), "left");
|
||||
assert!(near(constrain_45(from, [150.0, 46.0]), [152.0, 48.0]), "up and right, on the diagonal");
|
||||
assert!(near(constrain_45(from, from), from), "on the vertex");
|
||||
assert!(constrain_45(from, [f64::NAN, 3.0])[0].is_nan(), "a non-finite point stays as it is");
|
||||
}
|
||||
|
||||
/// The Rotation tool counts past a full turn in both directions, as in After Effects: the
|
||||
/// pointer going round the anchor 1.5 turns clockwise gives 540°, then back 2 turns gives
|
||||
/// −180°, with no jump where atan2 wraps from 180° to −180°.
|
||||
|
||||
@@ -763,6 +763,40 @@ fn mask_path(h: &Harness<'_, EffectcraftApp>, name: &str) -> effectcraft_engine:
|
||||
l.masks().unwrap().groups().next().unwrap().get("path").unwrap().value.as_path().unwrap().clone()
|
||||
}
|
||||
|
||||
/// #1544: a Shift-click with the Pen puts the next point straight below, beside or diagonal to the
|
||||
/// one before it; without Shift it goes where it was clicked.
|
||||
#[test]
|
||||
fn pen_shift_click_draws_a_straight_segment() {
|
||||
let mut h = harness();
|
||||
let plate = layer_id(&h, "Plate");
|
||||
h.state_mut().session.execute("layer.select", json!({"layers": [plate]})).unwrap();
|
||||
h.state_mut().session.execute("view.snapping", json!({"value": false})).unwrap();
|
||||
h.state_mut().ui.tool = Tool::Pen;
|
||||
h.run_steps(2);
|
||||
let first = screen(&h, [100.0, 60.0]);
|
||||
click(&mut h, first);
|
||||
let shift_click = |h: &mut Harness<'_, EffectcraftApp>, p: Pos2| {
|
||||
let shift = egui::Modifiers::SHIFT;
|
||||
h.input_mut().events.push(Event::ModifiersChanged(shift));
|
||||
h.input_mut().events.push(Event::PointerMoved(p));
|
||||
h.step();
|
||||
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers: shift });
|
||||
h.step();
|
||||
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: shift });
|
||||
h.input_mut().events.push(Event::ModifiersChanged(egui::Modifiers::NONE));
|
||||
h.run_steps(2);
|
||||
};
|
||||
let down = screen(&h, [112.0, 250.0]);
|
||||
shift_click(&mut h, down);
|
||||
let free = screen(&h, [300.0, 290.0]);
|
||||
click(&mut h, free);
|
||||
let drawn = mask_path(&h, "Plate");
|
||||
let near = |a: [f64; 2], b: [f64; 2]| (a[0] - b[0]).abs() < 1.0 && (a[1] - b[1]).abs() < 1.0;
|
||||
assert_eq!(drawn.vertices.len(), 3, "{drawn:?}");
|
||||
assert!(near(drawn.vertices[1], [100.0, 250.0]), "straight down: {drawn:?}");
|
||||
assert!(near(drawn.vertices[2], [300.0, 290.0]), "no Shift, no constraint: {drawn:?}");
|
||||
}
|
||||
|
||||
/// Drawing a mask with the Pen, moving a point and undoing take one action back per undo; a
|
||||
/// double-click on a point selects all of them in a free-transform box, and dragging inside it
|
||||
/// (or, once the box is gone, dragging a point) moves the whole mask (#290).
|
||||
|
||||
Reference in new issue
Block a user