Merge #1325: Edit Points: Esc before releasing the mouse button cancels a point or handle drag

This commit is contained in:
aiongg
2026-10-25 02:22:39 +00:00
4 changed files with 68 additions and 0 deletions
+3
View File
@@ -54,6 +54,9 @@ impl Session {
let has_shapes = self.active().is_some_and(|d| !d.selection.shapes.is_empty());
let k = key_token(key);
let k = k.as_str();
if matches!(k, "escape" | "esc") && self.cancel_point_drag() {
return Ok(json!({"handled": true}));
}
if self.tool.drawing_freeform() && matches!(k, "enter" | "return" | "escape") {
return self.finish_freeform(false);
}
+45
View File
@@ -2293,6 +2293,51 @@ fn edit_points_with_the_pointer_and_curves() {
assert!(s.tool.edit_points.is_none());
}
/// Esc before the button is released cancels a point or handle drag in Edit Points: the shape
/// is as it was at the press, no undo step is added, and Edit Points stays on.
#[test]
fn edit_points_escape_cancels_a_point_or_handle_drag() {
use crate::tools::{Mods, PointerEvent, PointerKind};
let mut s = Session::with_new();
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
s.execute("shape.insert", &json!({"preset": "ellipse", "rect": [100, 100, 200, 100]})).unwrap();
s.execute("shape.editPoints", &json!({"on": true})).unwrap();
let id = deckcraft_model::ShapeId(s.execute("shape.points", &json!({})).unwrap()["id"].as_u64().unwrap() as u32);
let path = |s: &Session| s.doc().unwrap().shape(id).map(|sh| format!("{:?}", sh.geom)).unwrap_or_default();
let undo_len = |s: &Session| s.doc().unwrap().history.undo.len();
let ev = |kind, x: f64, y: f64| PointerEvent { kind, x, y, mods: Mods::default(), ..Default::default() };
let (before, steps) = (path(&s), undo_len(&s));
// A vertex: drag it, press Esc, keep dragging, release.
let v: Vec<[f64; 2]> = serde_json::from_value(s.execute("shape.points", &json!({})).unwrap()["paths"][0].clone()).unwrap();
let [x0, y0] = v[0];
s.pointer(ev(PointerKind::Down, x0, y0)).unwrap();
s.pointer(ev(PointerKind::Drag, x0 - 30.0, y0)).unwrap();
assert_ne!(path(&s), before, "the drag moves the point");
assert_eq!(s.key("Escape", Mods::default()).unwrap()["handled"], true);
assert_eq!(path(&s), before, "Esc puts the point back");
assert!(!s.tool.dragging());
s.pointer(ev(PointerKind::Drag, x0 - 50.0, y0)).unwrap();
s.pointer(ev(PointerKind::Up, x0 - 50.0, y0)).unwrap();
assert_eq!(path(&s), before, "the rest of the drag does nothing");
assert_eq!(undo_len(&s), steps, "no undo step");
assert_eq!(s.tool.editing_points(s.doc().unwrap()), Some(id), "Edit Points stays on");
// A handle of the same point, which the press on the point shows.
let st = s.doc().unwrap();
let (_, ho, _) = crate::cmd::points::slide_handles(&st.doc, &st.selection, st.shape(id).unwrap(), 0, 0).unwrap();
let ho = ho.unwrap();
s.pointer(ev(PointerKind::Down, ho.x, ho.y)).unwrap();
s.pointer(ev(PointerKind::Drag, ho.x + 20.0, ho.y + 20.0)).unwrap();
assert_ne!(path(&s), before, "the drag moves the handle");
s.key("Escape", Mods::default()).unwrap();
s.pointer(ev(PointerKind::Up, ho.x + 20.0, ho.y + 20.0)).unwrap();
assert_eq!(path(&s), before, "Esc puts the handle back");
assert_eq!(undo_len(&s), steps, "no undo step");
assert_eq!(s.tool.editing_points(s.doc().unwrap()), Some(id), "Edit Points stays on");
// Without a drag, Esc still leaves Edit Points.
s.key("Escape", Mods::default()).unwrap();
assert!(s.tool.edit_points.is_none());
}
#[test]
fn edit_points_path_parse_and_write() {
use crate::cmd::points::{Seg, parse, vertices, write};
+19
View File
@@ -542,6 +542,25 @@ impl Session {
}
}
/// Cancels a point or handle drag in Edit Points when Esc is pressed: ends the drag, puts back the
/// document and selection saved at the press, and adds no undo step. Returns whether a drag was cancelled.
pub(crate) fn cancel_point_drag(&mut self) -> bool {
if !matches!(self.tool.drag, Some(Drag::Vertex { .. } | Drag::Handle { .. })) {
return false;
}
self.tool.drag = None;
if let Some(st) = self.active_mut()
&& st.interaction.as_ref().is_some_and(|i| i.label == "Edit Points")
&& let Some(i) = st.interaction.take()
&& !Arc::ptr_eq(&i.doc, &st.doc)
{
st.doc = i.doc;
st.selection = i.selection;
st.revision += 1;
}
true
}
/// End the interaction only when it is the one `label` started. A twist must not commit a drag.
pub(crate) fn commit_named(&mut self, label: &str) {
let ours = self.active().and_then(|st| st.interaction.as_ref()).is_some_and(|i| i.label == label);