diff --git a/crates/ui-egui/src/panels/viewer.rs b/crates/ui-egui/src/panels/viewer.rs index 5fe7697c..2f0002bc 100644 --- a/crates/ui-egui/src/panels/viewer.rs +++ b/crates/ui-egui/src/panels/viewer.rs @@ -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); diff --git a/crates/ui-egui/src/panels/viewer_overlays.rs b/crates/ui-egui/src/panels/viewer_overlays.rs index 54218e9f..cc901302 100644 --- a/crates/ui-egui/src/panels/viewer_overlays.rs +++ b/crates/ui-egui/src/panels/viewer_overlays.rs @@ -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 { 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"); + } +}