diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index f0b7f4b60..2a9fb1628 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -206,6 +206,9 @@ pub struct Session { /// The id and persistent options of the tool when the press under way began, which a dropped /// press puts back ([`Session::drop_tool_press`]). pub(crate) press_options: Option<(String, serde_json::Map)>, + /// The freeform gradient point selected once the tool took the press under way, which a + /// dropped press selects again ([`Session::drop_tool_press`]). + pub(crate) press_point: Option>, /// A guide being dragged out of a ruler ([`Session::ruler_guide`]). pub(crate) ruler_guide: Option, /// Which selected type overflows its frame, for the ports every tool but the Type tools shows. diff --git a/crates/engine/src/session/documents.rs b/crates/engine/src/session/documents.rs index da2c65988..ba15d3175 100644 --- a/crates/engine/src/session/documents.rs +++ b/crates/engine/src/session/documents.rs @@ -93,6 +93,7 @@ impl Session { plane_widget_press: false, pointer_held: false, press_options: None, + press_point: None, ruler_guide: None, port_marks: Default::default(), font_search: None, diff --git a/crates/engine/src/tests/freeform.rs b/crates/engine/src/tests/freeform.rs index df9ed3e4b..4e65c8689 100644 --- a/crates/engine/src/tests/freeform.rs +++ b/crates/engine/src/tests/freeform.rs @@ -331,3 +331,29 @@ fn type_objects_keep_points_in_text_space() { assert!(close(f.points[i].at, Point::new(90.0, 150.0)), "back in document coordinates: {:?}", f.points[i].at); assert_eq!(f.points[i].color, Color::rgb(1.0, 0.0, 1.0)); } + +/// A dropped press (a long touch, a canceled touch) of the Gradient tool that dragged a selected +/// point out of the object, and out and back in, keeps the point and its selection. +#[test] +fn a_dropped_drag_of_a_point_keeps_it_selected() { + use vectorcraft_tools::{PointerEvent, PointerKind}; + let (mut s, id) = freeform_rect(); + let v = ViewInfo::default(); + s.select_tool("gradient", v).unwrap(); + let count = points(&s, id).points.len(); + assert!(count > 1, "a point can be dragged out"); + let at = s.proxy_freeform(false).unwrap().0.points[0].at; + for back in [false, true] { + run(&mut s, "paint.freeform.selectPoint", json!({"index": 0})); + s.pointer(&PointerEvent::new(PointerKind::Down, at.x, at.y), v).unwrap(); + s.pointer(&PointerEvent::new(PointerKind::Drag, 400.0, 400.0), v).unwrap(); + assert_eq!(s.selected_freeform_point(), None, "the removal the drag previews deselects the point"); + if back { + s.pointer(&PointerEvent::new(PointerKind::Drag, at.x + 5.0, at.y + 5.0), v).unwrap(); + } + s.drop_tool_press(v).unwrap(); + assert!(!s.tool_busy(), "the tool is at rest"); + assert_eq!(points(&s, id).points.len(), count, "back inside: {back}"); + assert_eq!(s.selected_freeform_point(), Some(0), "back inside: {back}"); + } +} diff --git a/crates/engine/src/tooling.rs b/crates/engine/src/tooling.rs index 0a959959e..4eb191229 100644 --- a/crates/engine/src/tooling.rs +++ b/crates/engine/src/tooling.rs @@ -366,16 +366,22 @@ impl Session { /// Escape, as during a drag, for most tools), and a release returns the tool to rest. Of what /// the tool asks for along the way, only a cancel and a notify run, so a marquee selects /// nothing and a click opens no size dialog. The tool gets back the persistent options it had - /// when the press began, and none of the press's changes to them are stored. + /// when the press began, and none of the press's changes to them are stored. The freeform + /// gradient point selected once the tool took the press is selected again. pub fn drop_tool_press(&mut self, view: ViewInfo) -> Result<()> { // Nothing to roll back without a document; the tool still drops the press and gets the release. let _ = self.cancel_interaction(); let before = self.press_options.take(); + let point = self.press_point.take(); let mut acts = self.with_tool_cx(view, |t, cx| t.drop_press(cx)); acts.retain(|a| matches!(a, Action::Cancel | Action::Notify(_))); let dropped = self.take_tool_panic().and_then(|()| self.apply_actions(acts)); let up = PointerEvent { kind: PointerKind::Up, pos: Point::ZERO, mods: Mods::default(), backquote: false, pressure: 1.0, pen: None }; let r = dropped.and(self.pointer_event(&up, view, false)); + // A drag out of the object previews the point's removal, which deselects it. + if let Some(p) = point { + self.freeform_point = p; + } if let Some((id, opts)) = before && id == self.tool.id() { @@ -428,6 +434,11 @@ impl Session { } self.apply_actions(acts) }); + match ev.kind { + PointerKind::Down => self.press_point = Some(self.freeform_point), + PointerKind::Up => self.press_point = None, + _ => {} + } if ev.kind == PointerKind::Up { self.give_back_tool(view)?; } diff --git a/crates/tools/src/xform/freeform.rs b/crates/tools/src/xform/freeform.rs index fe77ce607..89c6faa93 100644 --- a/crates/tools/src/xform/freeform.rs +++ b/crates/tools/src/xform/freeform.rs @@ -274,10 +274,11 @@ pub(super) fn release(cx: &ToolContext, a: Option<&Annotator>, g: Gesture, lines } } -/// Escape during a drag: the point a drag-out deselected is selected again. +/// Escape during a drag: the point a drag-out deselected is selected again, also when the drag +/// came back inside the object. pub(super) fn cancel(g: &Gesture) -> Vec { match g.grab { - Grab::Point(i) if g.out => vec![Action::Cancel, select(Some(i))], + Grab::Point(i) if g.left => vec![Action::Cancel, select(Some(i))], _ => vec![Action::Cancel], } } @@ -499,6 +500,11 @@ mod tests { t.pointer(&cx, &ev(PointerKind::Down, 120.0, 120.0)); t.pointer(&cx, &ev(PointerKind::Drag, 60.0, 60.0)); assert_eq!(t.key(&cx, ToolKey::Escape, Default::default()), vec![Action::Cancel, select(Some(0))]); + // Also after the drag came back inside. + t.pointer(&cx, &ev(PointerKind::Down, 120.0, 120.0)); + t.pointer(&cx, &ev(PointerKind::Drag, 60.0, 60.0)); + t.pointer(&cx, &ev(PointerKind::Drag, 110.0, 110.0)); + assert_eq!(t.key(&cx, ToolKey::Escape, Default::default()), vec![Action::Cancel, select(Some(0))]); } #[test]