Resume an open mask path when the Pen clicks its last vertex (#912)

This commit is contained in:
MarekSpodBiedry committed 2026-10-11 23:39:07 +02:00
1 parent 67434e9038
commit 050bc4723e
2 files changed
+42 -5

No files matched your search

+10 -5
View File
@@ -1203,7 +1203,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
let mods = ui.input(|i| i.modifiers);
let space_pan = super::space_hand(&ctx);
// Pen tool: each press places a vertex (dragging pulls Bezier tangents); pressing on the
// first vertex closes the path.
// first vertex closes the path, pressing an open path's last vertex resumes it.
if app.ui.tool == Tool::Pen
&& !space_pan
&& resp.hovered()
@@ -1928,10 +1928,10 @@ fn draw_rigs(app: &mut EffectcraftApp, painter: &egui::Painter, map: &ViewerMap,
}
/// Pen tool press at `pos`: continue the path in progress (pressing its first vertex closes it),
/// add a vertex on a visible path's segment, or start a new path: a mask on the selected
/// footage/solid layer (or shape layer, with Tool Creates Mask), a shape path (Shape n group with
/// the Tools bar's Fill and Stroke) on the selected shape layer, or a new shape layer when
/// nothing is selected. The placed point snaps.
/// resume an open path whose last vertex is pressed, add a vertex on a visible path's segment, or
/// start a new path: a mask on the selected footage/solid layer (or shape layer, with Tool Creates
/// Mask), a shape path (Shape n group with the Tools bar's Fill and Stroke) on the selected shape
/// layer, or a new shape layer when nothing is selected. The placed point snaps.
/// Add a Position keyframe at a motion path's frame, keeping the path where it is.
fn add_motion_key(app: &mut EffectcraftApp, f: ov::PathFrame) {
if let Err(e) = app.session.execute("prop.addKey", json!({"layer": f.layer.0, "prop": f.prop, "time": f.time.seconds()})) {
@@ -2017,6 +2017,11 @@ fn pen_press(
}
return;
}
// Clicking the last vertex of an open path (e.g. after Esc ended it) resumes drawing it.
if let Some(cur) = ov::open_end_at(paths, map, pos, 8.0) {
ui.data_mut(|d| d.insert_temp(pen_id(), cur));
return;
}
// On a visible path's segment the Pen adds a vertex there; on a motion path, a keyframe.
if let Some(f) = motion_frame {
add_motion_key(app, f);
@@ -87,6 +87,15 @@ pub(crate) fn segment_at(paths: &[PathInfo], map: &ViewerMap, pos: Pos2, tol: f3
best.map(|b| b.1)
}
/// The open path whose last vertex lies within `tol` points of `pos`: the Pen resumes it there.
pub(crate) fn open_end_at(paths: &[PathInfo], map: &ViewerMap, pos: Pos2, tol: f32) -> Option<(LayerId, u64)> {
paths.iter().find_map(|p| {
let v = p.sp.vertices.last().filter(|_| !p.sp.closed)?;
let q = p.m.apply(gv2(v[0], v[1]));
(map.to_screen([q.x, q.y]).distance(pos) < tol).then_some((p.layer, p.uid))
})
}
/// A mask or shape path of a layer by group uid, evaluated at the context time.
pub(crate) fn path_of(ectx: &EvalCtx, layer: LayerId, uid: u64) -> Option<PathInfo> {
let l = ectx.comp.layer(layer)?;
@@ -640,3 +649,26 @@ pub(crate) fn update(
Drag::Roi { .. } => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn info(vertices: Vec<[f64; 2]>, closed: bool) -> PathInfo {
let n = vertices.len();
let sp = ShapePath { vertices, in_tangents: vec![[0.0; 2]; n], out_tangents: vec![[0.0; 2]; n], closed, feather: Vec::new() };
PathInfo { layer: LayerId(3), uid: 7, m: Mat3::IDENTITY, sp, is_mask: true, feather: Vec::new() }
}
/// #912: clicking the last vertex of an open path with the Pen resumes that path.
#[test]
fn pen_resumes_an_open_path_at_its_last_vertex() {
let map = ViewerMap { origin: Pos2::ZERO, zoom: 1.0, comp: [100.0, 100.0], area: Rect::EVERYTHING };
let open = info(vec![[10.0, 10.0], [50.0, 10.0], [50.0, 50.0]], false);
assert_eq!(open_end_at(std::slice::from_ref(&open), &map, egui::pos2(52.0, 49.0), 8.0), Some((LayerId(3), 7)));
assert_eq!(open_end_at(std::slice::from_ref(&open), &map, egui::pos2(10.0, 10.0), 8.0), None, "first vertex");
assert_eq!(open_end_at(std::slice::from_ref(&open), &map, egui::pos2(80.0, 80.0), 8.0), None, "far away");
let closed = info(vec![[10.0, 10.0], [50.0, 10.0], [50.0, 50.0]], true);
assert_eq!(open_end_at(&[closed], &map, egui::pos2(50.0, 50.0), 8.0), None, "closed path");
}
}