Merge pull request #1035 from vbasky/fix/1004-mask-multiselect

# Conflicts:
#	crates/ui-egui/tests/ui_viewer.rs
This commit is contained in:
Brandon Thomas 2026-10-13 04:28:49 -04:00
commit 9396e39ae5
1 changed files with 90 additions and 0 deletions

View File

@ -2134,3 +2134,93 @@ fn rotate_tool_counts_revolutions_both_ways() {
assert!(seen.windows(2).all(|w| (w[1] - w[0]).abs() <= 20.0), "no jump: {seen:?}");
assert_eq!(h.state().session.history.undo.len(), undo_before + 1, "one drag, one undo step");
}
/// Mask vertices multi-select with the Selection tool: Shift-click adds a vertex to the
/// selection and Shift-clicking it again removes it; a plain click collapses to one (#1004).
#[test]
fn mask_shift_click_toggles_vertices() {
use egui::Modifiers;
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("mask.new", json!({"layer": plate, "vertices": [[100.0, 60.0], [500.0, 60.0], [500.0, 300.0], [100.0, 300.0]], "closed": true}))
.unwrap();
h.state_mut().ui.tool = Tool::Selection;
h.run_steps(2);
let (p0, p1) = (screen(&h, [100.0, 60.0]), screen(&h, [500.0, 60.0]));
let shift_click = |h: &mut Harness<'_, EffectcraftApp>, p: Pos2| {
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::SHIFT));
h.step();
h.input_mut().events.push(Event::PointerMoved(p));
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: true, modifiers: Modifiers::SHIFT });
h.step();
h.input_mut().events.push(Event::PointerButton { pos: p, button: egui::PointerButton::Primary, pressed: false, modifiers: Modifiers::SHIFT });
h.step();
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
h.run_steps(2);
};
click(&mut h, p0);
assert_eq!(h.state().session.state.selected_vertices.len(), 1, "plain click selects one vertex");
shift_click(&mut h, p1);
assert_eq!(h.state().session.state.selected_vertices.len(), 2, "shift-click adds a second vertex");
shift_click(&mut h, p1);
assert_eq!(h.state().session.state.selected_vertices.len(), 1, "shift-click again removes it");
}
/// Dragging a marquee from empty viewer area selects the mask vertices inside it, additive
/// with Shift, without moving the layer (#1004).
#[test]
fn mask_marquee_selects_vertices() {
let mut h = harness();
let plate = layer_id(&h, "Plate");
let bx = layer_id(&h, "Box");
// A full-frame layer behind leaves no empty area to start a marquee from: remove it.
h.state_mut().session.execute("layer.select", json!({"layers": [plate]})).unwrap();
h.state_mut().session.execute("edit.clear", json!({"layers": [plate]})).unwrap();
assert_eq!(h.state().session.active_comp().unwrap().layers.len(), 1);
h.state_mut().session.execute("layer.select", json!({"layers": [bx]})).unwrap();
h.state_mut()
.session
.execute("mask.new", json!({"layer": bx, "vertices": [[10.0, 10.0], [70.0, 10.0], [70.0, 70.0], [10.0, 70.0]], "closed": true}))
.unwrap();
h.state_mut().ui.tool = Tool::Selection;
h.state_mut().session.execute("mask.selectVertices", json!({"vertices": []})).unwrap();
h.run_steps(2);
// Mask vertices are layer-local; map vertex 0 to comp space (no rotation, uniform scale).
let (pp, aa) = {
let l = h.state().session.active_comp().unwrap().layer(LayerId(bx)).unwrap().clone();
(l.props.prop("transform/position").unwrap().value.as_vec3(), l.props.prop("transform/anchor").unwrap().value.as_vec3())
};
let v0 = [10.0 - aa[0] + pp[0], 10.0 - aa[1] + pp[1]];
let pos = |h: &Harness<'_, EffectcraftApp>| {
h.state().session.active_comp().unwrap().layer(LayerId(bx)).unwrap().props.prop("transform/position").unwrap().value.as_vec3()
};
let before = pos(&h);
// Drag a box around the top-left vertex only, starting/ending on empty area.
let (from, to) = (screen(&h, [(v0[0] - 90.0) as f32, (v0[1] - 50.0) as f32]), screen(&h, [(v0[0] + 10.0) as f32, (v0[1] + 10.0) as f32]));
drag(&mut h, from, to);
let sel = &h.state().session.state.selected_vertices;
assert_eq!(sel.len(), 1, "marquee selects the enclosed vertex: {sel:?}");
assert_eq!(sel[0].index, 0);
assert_eq!(pos(&h), before, "the layer does not move under a vertex marquee");
// Shift-marquee adds the vertices it encloses.
let v1 = [70.0 - aa[0] + pp[0], 10.0 - aa[1] + pp[1]];
let (from, to) = (screen(&h, [(v1[0] - 10.0) as f32, (v1[1] - 50.0) as f32]), screen(&h, [(v1[0] + 60.0) as f32, (v1[1] + 50.0) as f32]));
h.input_mut().events.push(Event::ModifiersChanged(egui::Modifiers::SHIFT));
h.step();
h.input_mut().events.push(Event::PointerMoved(from));
h.input_mut().events.push(Event::PointerButton { pos: from, button: egui::PointerButton::Primary, pressed: true, modifiers: egui::Modifiers::SHIFT });
h.step();
for i in 1..=8 {
h.input_mut().events.push(Event::PointerMoved(from + (to - from) * (i as f32 / 8.0)));
h.step();
}
h.input_mut().events.push(Event::PointerButton { pos: to, button: egui::PointerButton::Primary, pressed: false, modifiers: egui::Modifiers::SHIFT });
h.step();
h.input_mut().events.push(Event::ModifiersChanged(egui::Modifiers::NONE));
h.run_steps(2);
let sel = &h.state().session.state.selected_vertices;
assert_eq!(sel.len(), 2, "shift-marquee adds: {sel:?}");
}