Gradient: a dropped or canceled point drag keeps the point selected

A drag that takes a selected freeform gradient point out of the object previews its removal,
which deselects it. A dropped press now selects again the point that was selected once the
tool took the press, and Escape reselects a point dragged out and back inside the object.
This commit is contained in:
dotCooCoo
2026-11-03 16:03:39 +07:00
committed by bflatastic
parent 5d0cffbba1
commit e01823c2eb
5 changed files with 50 additions and 3 deletions
+3
View File
@@ -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<String, serde_json::Value>)>,
/// 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<Option<(usize, cmd::gradient::StopOwner)>>,
/// A guide being dragged out of a ruler ([`Session::ruler_guide`]).
pub(crate) ruler_guide: Option<vectorcraft_tools::rulerguide::NewGuide>,
/// Which selected type overflows its frame, for the ports every tool but the Type tools shows.
+1
View File
@@ -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,
+26
View File
@@ -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}");
}
}
+12 -1
View File
@@ -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)?;
}
+8 -2
View File
@@ -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<Action> {
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]