Shape Builder: a plane key held with a dropped press is still made

When a press held during a merge is dropped, the held pointer events
and tool keys after it are removed, as before, but a perspective plane
key (1 to 4 with the grid shown) stays held and is made after the
merge, as it is when no merge is running. Pointer events held after the
last held release, which follow a press the tool already has, are
removed with that press.
This commit is contained in:
dotCooCoo
2026-11-08 14:57:46 -08:00
parent 87cd34bbd1
commit 342cab2f99
2 changed files with 61 additions and 22 deletions
+36 -13
View File
@@ -941,10 +941,11 @@ fn shape_builder_holds_gestures_made_during_a_merge() {
/// A press held while a merge is computed on another thread, then dropped (a long touch, a
/// canceled touch, a second finger), is never made: once the merge is applied, the tool is at
/// rest and the document is the one the input before the press makes. A held click before the
/// press is still made.
/// rest and the document is the one the same input makes, dropped the same way, after the merge.
/// A held click before the press is still made, and so is a perspective plane key held after it.
#[test]
fn shape_builder_drops_a_press_held_during_a_merge() {
use vectorcraft_tools::distort::perspective::{PerspectiveGrid, Plane};
type Gesture = fn(&mut Session, ViewInfo);
fn press(s: &mut Session, v: ViewInfo, x: f64, mods: Mods) {
s.pointer(&PointerEvent::new(PointerKind::Down, x, 10.0).with_mods(mods), v).unwrap();
@@ -954,7 +955,8 @@ fn shape_builder_drops_a_press_held_during_a_merge() {
s.pointer(&PointerEvent::new(kind, 55.0, 10.0), v).unwrap();
}
}
let cases: [(&str, bool, Gesture); 5] = [
// What a case sends, and whether the perspective grid is shown.
let cases: [(&str, bool, Gesture); 7] = [
("a press", false, |s, v| press(s, v, 75.0, Mods::default())),
("a drag", false, |s, v| {
press(s, v, 75.0, Mods::default());
@@ -965,14 +967,27 @@ fn shape_builder_drops_a_press_held_during_a_merge() {
press(s, v, 75.0, Mods::default());
s.tool_key(ToolKey::Escape, Mods::default(), v).unwrap();
}),
("a click, then a press", true, |s, v| {
("a press and an arrow key", false, |s, v| {
press(s, v, 75.0, Mods::default());
s.tool_key(ToolKey::Right, Mods::default(), v).unwrap();
}),
("a press and a plane key", true, |s, v| {
press(s, v, 75.0, Mods::default());
s.tool_key(ToolKey::Digit(3), Mods::default(), v).unwrap();
}),
("a click, then a press", false, |s, v| {
click(s, v);
press(s, v, 135.0, Mods::default());
}),
];
let v = ViewInfo::default();
let start = || {
let start = |grid: bool| {
let mut s = session();
s.select_tool("shapeBuilder", v).unwrap();
s.set_tool_option_cmd(&json!({"key": "pickColorFrom", "value": "swatches"})).unwrap();
if grid {
s.execute("perspective.grid.preset", &json!({"kind": 2})).unwrap();
}
square_row(&mut s);
click_row(&mut s, v);
s
@@ -987,15 +1002,18 @@ fn shape_builder_drops_a_press_held_during_a_merge() {
}
};
let labels = |s: &Session| s.doc().unwrap().history.undo.iter().map(|e| e.label.clone()).collect::<Vec<_>>();
for (what, clicked, send) in cases {
// The input before the press, one after the other.
let mut one = start();
let plane = |s: &Session| PerspectiveGrid::from_doc(&s.doc().unwrap().doc).unwrap().plane;
for (what, grid, send) in cases {
// The same input after the merge, dropped the same way.
let mut one = start(grid);
settle(&mut one);
if clicked {
click(&mut one, v);
settle(&mut one);
send(&mut one, v);
one.drop_tool_press(v).unwrap();
settle(&mut one);
let mut s = start(grid);
if grid {
assert_ne!(plane(&s), Plane::Right, "{what}: the plane before the key");
}
let mut s = start();
let from = s.input_count();
send(&mut s, v);
let to = s.input_count();
@@ -1005,9 +1023,14 @@ fn shape_builder_drops_a_press_held_during_a_merge() {
assert!(!s.tool_waiting() && !s.tool_editing() && s.input_applied(from, to), "{what}");
assert!(!s.tool_busy(), "{what}: the tool is at rest");
assert_eq!(s.tool_id(), "shapeBuilder", "{what}: no tool is lent");
assert!(undated(&s) == undated(&one), "{what}: the document the input before the press makes");
assert!(undated(&s) == undated(&one), "{what}: the document the input makes after the merge");
assert_eq!(labels(&s), labels(&one), "{what}");
assert_eq!(s.doc().unwrap().selection, one.doc().unwrap().selection, "{what}");
assert_eq!(s.paint.fill, one.paint.fill, "{what}");
if grid {
assert_eq!(plane(&one), Plane::Right, "{what}: the plane key is made after the merge");
assert_eq!(plane(&s), Plane::Right, "{what}: the plane key is made");
}
}
}
+25 -9
View File
@@ -512,11 +512,22 @@ impl Session {
/// when the press began, and none of the press's changes to them are stored. A dropped Type
/// press restores the prior selection. The freeform gradient point selected once the tool
/// took the press is selected again. A press held while the tool computes an edit on another
/// thread has not reached the tool: it is removed from the held input with everything held
/// after it, and the tool is left as it is.
/// thread has not reached the tool: it is removed from the held input with the pointer events
/// and tool keys held after it, and the tool is left as it is. A perspective plane key held
/// after it stays held, since the plane it picks outlasts a dropped press. Pointer events held
/// after the last held release belong to the press the tool has, and are removed with it.
pub fn drop_tool_press(&mut self, view: ViewInfo) -> Result<()> {
if let Some(i) = self.held_press() {
self.held_input.truncate(i);
let (from, held) = self.held_press();
let keep: Vec<bool> = (self.held_input.iter().enumerate())
.map(|(j, (_, input, _))| match input {
_ if j < from => true,
HeldInput::Pointer(_) => false,
HeldInput::Key(key, _) => !held || self.plane_key(*key).is_some(),
})
.collect();
let mut keep = keep.into_iter();
self.held_input.retain(|_| keep.next().unwrap_or(true));
if held {
return Ok(());
}
// Nothing to roll back without a document; the tool still drops the press and gets the release.
@@ -560,12 +571,17 @@ impl Session {
r.map(drop)
}
/// Where the press in progress is in the held input: the last held press, when no release is
/// held after it.
fn held_press(&self) -> Option<usize> {
/// Where the input of the press in progress starts in the held input, and whether the press
/// itself is held: the last held press when no release is held after it; otherwise the input
/// after the last held release (all of it when none is held), which follows a press the tool
/// already has.
fn held_press(&self) -> (usize, bool) {
let ends = |kind: PointerKind| matches!(kind, PointerKind::Down | PointerKind::Up);
let i = self.held_input.iter().rposition(|(_, input, _)| matches!(input, HeldInput::Pointer(ev) if ends(ev.kind)))?;
matches!(self.held_input.get(i), Some((_, HeldInput::Pointer(ev), _)) if ev.kind == PointerKind::Down).then_some(i)
match self.held_input.iter().rposition(|(_, input, _)| matches!(input, HeldInput::Pointer(ev) if ends(ev.kind))) {
Some(i) if matches!(self.held_input.get(i), Some((_, HeldInput::Pointer(ev), _)) if ev.kind == PointerKind::Down) => (i, true),
Some(i) => (i + 1, false),
None => (0, false),
}
}
/// The options of `t` that [`Self::store_tool_settings`] stores.