mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-14 19:27:05 -04:00
Viewer: Pan Behind Alt-drag, Shift-drag and Ctrl+double-click to center (#162)
The Pan Behind gesture ignored modifiers other than Ctrl (snapping): it always moved Anchor Point and Position together and never constrained the move, and the tool's button had no double-click action. - Alt held as the drag begins moves the Anchor Point alone (Position is left as it is, so the layer shifts). - Shift keeps the move to the axis dragged along most; Move and Pan Behind now share that rule (one_axis). - Ctrl+double-clicking the Pan Behind tool's button runs layer.centerAnchor (Center Anchor Point in Layer Content). Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
83f17dda37
commit
72b039c6d5
3 files changed
+76
-12
No files matched your search
@@ -62,6 +62,14 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
if resp.clicked() {
|
||||
app.ui.tool = cur;
|
||||
}
|
||||
// Ctrl+double-click Pan Behind: Center Anchor Point in Layer Content.
|
||||
if cur == Tool::PanBehind
|
||||
&& resp.double_clicked()
|
||||
&& ui.input(|i| i.modifiers.command)
|
||||
&& let Err(e) = crate::menus::invoke(app, ui.ctx(), "layer.centerAnchor", json!({}))
|
||||
{
|
||||
app.ui.status = e;
|
||||
}
|
||||
if slot.len() > 1 {
|
||||
resp.context_menu(|ui| {
|
||||
for tool in slot.iter() {
|
||||
|
||||
@@ -95,6 +95,8 @@ enum Gesture {
|
||||
l2p: Mat3,
|
||||
/// The layer's own snap targets as the drag began (its box and path vertices).
|
||||
own: Vec<effectcraft_engine::viewer::SnapTarget>,
|
||||
/// Alt held as the drag began: the anchor point moves alone, so the layer shifts.
|
||||
anchor_only: bool,
|
||||
},
|
||||
Pan {
|
||||
start_pan: [f32; 2],
|
||||
@@ -276,6 +278,17 @@ fn drag_scale(start_scale: [f64; 3], start: [f64; 2], cur: [f64; 2], axes: [bool
|
||||
[start_scale[0] * fx, start_scale[1] * fy, start_scale[2]]
|
||||
}
|
||||
|
||||
/// A drag `d` (comp pixels), kept to the axis it moves along most when `shift` is held.
|
||||
fn one_axis(d: [f64; 2], shift: bool) -> [f64; 2] {
|
||||
if !shift {
|
||||
d
|
||||
} else if d[0].abs() > d[1].abs() {
|
||||
[d[0], 0.0]
|
||||
} else {
|
||||
[0.0, d[1]]
|
||||
}
|
||||
}
|
||||
|
||||
fn point_in_quad(p: [f64; 2], q: &[[f64; 2]; 4]) -> bool {
|
||||
let mut sign = 0.0;
|
||||
for i in 0..4 {
|
||||
@@ -1060,6 +1073,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
inv: l2c.inverse().unwrap_or(Mat3::IDENTITY),
|
||||
l2p,
|
||||
own: effectcraft_engine::viewer::layer_targets(&ectx, layer, false),
|
||||
anchor_only: mods.alt,
|
||||
})
|
||||
}),
|
||||
t if t.is_shape() => Some(Gesture::Create { tool: t, start: cpt }),
|
||||
@@ -1201,10 +1215,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
}
|
||||
Gesture::Move { layers, start, snap_src } => {
|
||||
let mut d = [cpt[0] - start[0], cpt[1] - start[1]];
|
||||
if mods.shift {
|
||||
if d[0].abs() > d[1].abs() { d[1] = 0.0 } else { d[0] = 0.0 }
|
||||
}
|
||||
let mut d = one_axis([cpt[0] - start[0], cpt[1] - start[1]], mods.shift);
|
||||
if let Some(src) = snap_src {
|
||||
let ids: Vec<LayerId> = layers.iter().map(|x| x.0).collect();
|
||||
let c = vt::snap(app, &ctx, &ectx, &map, &ids, &[[src[0] + d[0], src[1] + d[1]]], mods);
|
||||
@@ -1228,21 +1239,24 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
let _ = app.session.execute("prop.set", json!({"layer": layer.0, "path": "transform/rotation", "value": r, "merge": merge}));
|
||||
}
|
||||
Gesture::Anchor { layer, start_anchor, start_pos, start, inv, l2p, own } => {
|
||||
Gesture::Anchor { layer, start_anchor, start_pos, start, inv, l2p, own, anchor_only } => {
|
||||
// Pan Behind snaps the anchor point (to its own layer's box, other layers' features,
|
||||
// guides and the grid).
|
||||
// guides and the grid); Shift keeps the move to one axis.
|
||||
let d = one_axis([cpt[0] - start[0], cpt[1] - start[1]], mods.shift);
|
||||
let anchor_c = inv.inverse().map(|m| m.apply(gv2(start_anchor[0], start_anchor[1]))).unwrap_or(gv2(start[0], start[1]));
|
||||
let want = [anchor_c.x + cpt[0] - start[0], anchor_c.y + cpt[1] - start[1]];
|
||||
let want = [anchor_c.x + d[0], anchor_c.y + d[1]];
|
||||
let sc = vt::snap_with(app, &ctx, &ectx, &map, &[layer], &[want], mods, &own);
|
||||
let cpt = [cpt[0] + sc[0], cpt[1] + sc[1]];
|
||||
let a = inv.apply(gv2(start[0], start[1]));
|
||||
let b = inv.apply(gv2(cpt[0], cpt[1]));
|
||||
let b = inv.apply(gv2(start[0] + d[0] + sc[0], start[1] + d[1] + sc[1]));
|
||||
let dl = gv2(b.x - a.x, b.y - a.y);
|
||||
let na = [start_anchor[0] + dl.x, start_anchor[1] + dl.y, start_anchor[2]];
|
||||
let dp = l2p.apply_vec(dl);
|
||||
let np = [start_pos[0] + dp.x, start_pos[1] + dp.y, start_pos[2]];
|
||||
let _ = app.session.execute("prop.set", json!({"layer": layer.0, "path": "transform/anchor", "value": na, "merge": merge}));
|
||||
let _ = app.session.execute("prop.set", json!({"layer": layer.0, "path": "transform/position", "value": np, "merge": merge}));
|
||||
// Position follows so the layer stays put, unless Alt moves the anchor point alone.
|
||||
if !anchor_only {
|
||||
let dp = l2p.apply_vec(dl);
|
||||
let np = [start_pos[0] + dp.x, start_pos[1] + dp.y, start_pos[2]];
|
||||
let _ = app.session.execute("prop.set", json!({"layer": layer.0, "path": "transform/position", "value": np, "merge": merge}));
|
||||
}
|
||||
}
|
||||
Gesture::Camera { tool } => {
|
||||
let d = resp.drag_delta();
|
||||
|
||||
@@ -472,6 +472,48 @@ fn pan_behind_snaps_the_anchor_to_its_own_layer() {
|
||||
assert!((p[0] - 100.0).abs() < 0.01 && (p[1] - 100.0).abs() < 0.01, "{p:?}");
|
||||
}
|
||||
|
||||
/// Issue #162: with Pan Behind, Alt-drag moves the anchor point alone (the layer shifts, its
|
||||
/// position stays), Shift keeps the move to one axis, and Ctrl+double-clicking the tool's button
|
||||
/// centres the anchor point in the layer content.
|
||||
#[test]
|
||||
fn pan_behind_alt_moves_the_anchor_alone_shift_constrains_and_the_tool_button_centres_it() {
|
||||
let mut h = harness();
|
||||
let box_id = h.state().session.active_comp().unwrap().layers[0].id;
|
||||
h.state_mut().session.execute("prop.set", json!({"layer": box_id.0, "path": "transform/position", "value": [100, 100, 0]})).unwrap();
|
||||
h.state_mut().session.execute("layer.select", json!({"layers": [box_id.0]})).unwrap();
|
||||
h.state_mut().session.execute("view.snapping", json!({"value": false})).unwrap();
|
||||
h.state_mut().ui.tool = Tool::PanBehind;
|
||||
h.run_steps(2);
|
||||
let close = |a: [f64; 3], b: [f64; 2]| (a[0] - b[0]).abs() < 0.01 && (a[1] - b[1]).abs() < 0.01;
|
||||
// Alt: the anchor point moves by the drag, Position stays.
|
||||
let (from, to) = (screen(&h, [100.0, 100.0]), screen(&h, [120.0, 105.0]));
|
||||
hold_drag(&mut h, from, to, egui::Modifiers::ALT);
|
||||
let (p, a) = position_and_anchor(&h, box_id);
|
||||
assert!(close(a, [60.0, 45.0]) && close(p, [100.0, 100.0]), "Alt moves the anchor point alone: {a:?} {p:?}");
|
||||
// Shift: the move keeps to the axis dragged along most (Position compensates).
|
||||
h.state_mut().session.execute("prop.set", json!({"layer": box_id.0, "path": "transform/anchor", "value": [40, 40, 0]})).unwrap();
|
||||
h.run_steps(2);
|
||||
let (from, to) = (screen(&h, [100.0, 100.0]), screen(&h, [130.0, 110.0]));
|
||||
hold_drag(&mut h, from, to, egui::Modifiers::SHIFT);
|
||||
let (p, a) = position_and_anchor(&h, box_id);
|
||||
assert!(close(a, [70.0, 40.0]) && close(p, [130.0, 100.0]), "Shift keeps to x: {a:?} {p:?}");
|
||||
// Ctrl+double-click the tool's button: Center Anchor Point in Layer Content (the layer stays).
|
||||
let ctrl = egui::Modifiers { ctrl: true, command: true, ..Default::default() };
|
||||
let b = rect(&h, "tools.PanBehind").center();
|
||||
h.input_mut().events.push(Event::ModifiersChanged(ctrl));
|
||||
h.input_mut().events.push(Event::PointerMoved(b));
|
||||
h.step();
|
||||
for _ in 0..2 {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: b, button: egui::PointerButton::Primary, pressed: true, modifiers: ctrl });
|
||||
h.input_mut().events.push(Event::PointerButton { pos: b, button: egui::PointerButton::Primary, pressed: false, modifiers: ctrl });
|
||||
}
|
||||
h.run_steps(2);
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Default::default()));
|
||||
h.step();
|
||||
let (p, a) = position_and_anchor(&h, box_id);
|
||||
assert!(close(a, [40.0, 40.0]) && close(p, [100.0, 100.0]), "anchor point centred: {a:?} {p:?}");
|
||||
}
|
||||
|
||||
/// Drag from `from` to `to` with `modifiers` held, holding the end point a frame (the viewer's
|
||||
/// gestures apply the pointer of the previous frame).
|
||||
fn hold_drag(h: &mut Harness<'_, EffectcraftApp>, from: Pos2, to: Pos2, modifiers: egui::Modifiers) {
|
||||
|
||||
Reference in new issue
Block a user