mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Viewer: double-clicking a mask point selects the whole mask (#290)
Double-clicking a mask or shape path's point (or segment) put its points in a free-transform box but left only the clicked point selected, so once the box was gone a drag moved that one point. As in After Effects, the double-click now selects all the path's points too: dragging inside the box, or afterwards any of the points, moves the whole mask. The test also draws a mask with the Pen, moves a point and undoes: each undo takes back one action (the move, the closing, the last point). Test: pen_mask_undoes_step_by_step_and_double_click_moves_the_whole_mask (ui_viewer). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
ed5ad56eb5
commit
3174652ce4
2 files changed
+78
-2
No files matched your search
@@ -1603,8 +1603,13 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
&& let Some(pos) = resp.interact_pointer_pos()
|
||||
&& let Some((l, m)) = vertex_at(pos, false).map(|h| (h.layer, h.mask)).or_else(|| ov::segment_at(&paths, &map, pos, 6.0).map(|s| (s.0, s.1)))
|
||||
{
|
||||
// Double-click a path: free transform its points (Layer ▸ Mask and Shape Path ▸ Free
|
||||
// Transform Points).
|
||||
// Double-click a path: all its points selected in a free-transform box (Layer ▸ Mask and
|
||||
// Shape Path ▸ Free Transform Points), so dragging any of them, or inside the box, moves
|
||||
// the whole path (After Effects).
|
||||
if let Some(p) = paths.iter().find(|p| p.layer == l && p.uid == m) {
|
||||
let all: Vec<serde_json::Value> = (0..p.sp.vertices.len()).map(|index| json!({"layer": l.0, "mask": m, "index": index})).collect();
|
||||
let _ = app.session.execute("mask.selectVertices", json!({"vertices": all}));
|
||||
}
|
||||
ov::begin_free_transform(&ctx, l, m);
|
||||
} else if resp.double_clicked()
|
||||
&& !text_owns
|
||||
|
||||
@@ -479,6 +479,77 @@ fn motion_path_key_drag_edits_that_key() {
|
||||
assert_eq!(k[0].value.as_vec3(), [100.0, 100.0, 0.0]);
|
||||
}
|
||||
|
||||
/// The first mask path of layer `name`.
|
||||
fn mask_path(h: &Harness<'_, EffectcraftApp>, name: &str) -> effectcraft_engine::keyframe::ShapePath {
|
||||
let l = h.state().session.active_comp().unwrap().layers.iter().find(|l| l.name == name).unwrap().clone();
|
||||
l.masks().unwrap().groups().next().unwrap().get("path").unwrap().value.as_path().unwrap().clone()
|
||||
}
|
||||
|
||||
/// 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).
|
||||
#[test]
|
||||
fn pen_mask_undoes_step_by_step_and_double_click_moves_the_whole_mask() {
|
||||
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 corners = [[100.0, 60.0], [500.0, 60.0], [500.0, 300.0], [100.0, 300.0]];
|
||||
for p in corners.iter().chain([&corners[0]]) {
|
||||
let at = screen(&h, [p[0] as f32, p[1] as f32]);
|
||||
click(&mut h, at);
|
||||
}
|
||||
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!(drawn.closed && drawn.vertices.len() == 4 && drawn.vertices.iter().zip(&corners).all(|(v, c)| near(*v, *c)), "{drawn:?}");
|
||||
h.state_mut().ui.tool = Tool::Selection;
|
||||
h.run_steps(2);
|
||||
// Move one point: one undo step puts it back, the next reopens the path, the next removes the
|
||||
// last point.
|
||||
let (from, to) = (screen(&h, [500.0, 60.0]), screen(&h, [520.0, 80.0]));
|
||||
drag(&mut h, from, to);
|
||||
let moved = mask_path(&h, "Plate");
|
||||
assert!(near(moved.vertices[1], [520.0, 80.0]) && moved.vertices[0] == drawn.vertices[0], "{moved:?}");
|
||||
let undo = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
|
||||
h.run_steps(2);
|
||||
mask_path(h, "Plate")
|
||||
};
|
||||
assert_eq!(undo(&mut h), drawn);
|
||||
assert!(!undo(&mut h).closed);
|
||||
assert_eq!(undo(&mut h).vertices.len(), 3);
|
||||
for _ in 0..3 {
|
||||
h.state_mut().session.execute("edit.redo", json!({})).unwrap();
|
||||
}
|
||||
h.run_steps(2);
|
||||
// Double-click a point: all of them selected, in a free-transform box.
|
||||
let at = screen(&h, [100.0, 300.0]);
|
||||
h.input_mut().events.push(Event::PointerMoved(at));
|
||||
h.step();
|
||||
for pressed in [true, false, true, false] {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() });
|
||||
}
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().session.state.selected_vertices.len(), 4);
|
||||
assert!(h.state().auto.find("viewer.freeTransform.handle.0").is_some(), "the free-transform box is up");
|
||||
// Dragging inside the box moves the whole mask.
|
||||
let (from, to) = (screen(&h, [300.0, 120.0]), screen(&h, [310.0, 130.0]));
|
||||
drag(&mut h, from, to);
|
||||
let path = mask_path(&h, "Plate");
|
||||
assert!(path.vertices.iter().zip(&moved.vertices).all(|(v, c)| near(*v, [c[0] + 10.0, c[1] + 10.0])), "{path:?}");
|
||||
// A click away ends the box; the points stay selected and dragging one moves them all.
|
||||
let away = screen(&h, [600.0, 340.0]);
|
||||
click(&mut h, away);
|
||||
h.run_steps(2);
|
||||
assert!(h.state().auto.find("viewer.freeTransform.handle.0").is_none());
|
||||
let (from, to) = (screen(&h, [110.0, 310.0]), screen(&h, [100.0, 300.0]));
|
||||
drag(&mut h, from, to);
|
||||
let back = mask_path(&h, "Plate");
|
||||
assert!(back.vertices.iter().zip(&moved.vertices).all(|(v, c)| near(*v, *c)), "{back:?}");
|
||||
}
|
||||
|
||||
/// A straight two-key motion path shows Bezier handles at both keys (it had none), and dragging
|
||||
/// one pulls the path into a curve (#290).
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user