Shape Builder: a Cmd-drag held during a merge can carry art to another tab

A Cmd-press held while a merge is computed reached the lent Selection
tool after the merge, but the drag had no document-drag origin, because
the press was handed over while Shape Builder was still the active
tool. The frontend now records the origin when the held press is made.
This commit is contained in:
dotCooCoo
2026-11-08 15:34:02 -08:00
parent 342cab2f99
commit f17ee6e702
2 changed files with 56 additions and 0 deletions
+4
View File
@@ -413,6 +413,10 @@ pub fn apply_requests(app: &mut VectorcraftApp, r: vectorcraft_engine::Result<Ve
vectorcraft_engine::UiRequest::HeldPress { doc, pos, tool } => {
let index = doc.and_then(|uid| app.session.documents().iter().position(|d| d.uid == uid));
pressed_with(app, index, pos, tool);
// The selection tool Cmd lent has the press now, so its drag can carry art to another tab.
if let Some(ctx) = app.ui_ctx.clone() {
crate::document_drag::begin(app, &ctx, pos);
}
}
}
}
+52
View File
@@ -4,6 +4,7 @@ use egui::{Event, PointerButton, Pos2};
use serde_json::json;
use vectorcraft_engine::Session;
use vectorcraft_geom::Point;
use vectorcraft_tools::{PointerEvent, PointerKind};
use crate::{VectorcraftApp, canvas, chrome};
@@ -142,6 +143,57 @@ fn drag_fixture_modifiers(modifiers: egui::Modifiers) -> (VectorcraftApp, egui::
(app, ctx, grab, tabs)
}
/// A Cmd-press held while a Shape Builder merge is computed reaches the lent Selection tool once
/// the merge is applied, and its drag carries the artwork over another tab.
#[test]
fn a_cmd_drag_held_during_a_merge_activates_another_tab() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.run("file.new", json!({"width":800,"height":200,"name":"Source"})).unwrap();
for k in 0..40 {
app.run("shape.rectangle", json!({"x": k * 10, "y": 0, "width": 20, "height": 20})).unwrap();
}
app.run("select.all", json!({})).unwrap();
app.run("file.new", json!({"width":800,"height":200,"name":"Target"})).unwrap();
app.session.set_active(0);
app.select_tool("shapeBuilder");
app.ui.task_bar = false;
app.ui.view.rulers = false;
let ctx = egui::Context::default();
crate::theme::install_fonts(&ctx);
app.ui_ctx = Some(ctx.clone());
frame(&mut app, &ctx, 0.0, vec![]);
let tabs = frame(&mut app, &ctx, 0.01, vec![]);
let view = app.view_info();
app.session.pointer(&PointerEvent::new(PointerKind::Move, 15.0, 10.0), view).unwrap();
app.session.tool_poll(view, true).unwrap();
app.session.pointer(&PointerEvent::new(PointerKind::Down, 15.0, 10.0), view).unwrap();
app.session.pointer(&PointerEvent::new(PointerKind::Up, 15.0, 10.0), view).unwrap();
assert!(app.session.tool_editing(), "the click starts a merge on another thread");
let grab = canvas::Xf::new(app.canvas_rect.unwrap(), app.view().unwrap()).to_screen(Point::new(355.0, 10.0));
let modifiers = egui::Modifiers::COMMAND;
frame(
&mut app,
&ctx,
0.05,
vec![
Event::ModifiersChanged(modifiers),
Event::PointerMoved(grab),
Event::PointerButton { pos: grab, button: PointerButton::Primary, pressed: true, modifiers },
],
);
assert_eq!(app.session.tool_id(), "shapeBuilder", "the press waits for the merge");
let r = app.session.tool_poll(view, true);
canvas::apply_requests(&mut app, r);
frame(&mut app, &ctx, 0.08, vec![]);
assert_eq!(app.session.tool_id(), "selection", "the held press borrowed the Selection tool");
frame(&mut app, &ctx, 0.1, vec![Event::PointerMoved(grab + egui::vec2(20.0, 10.0))]);
let tab = tabs[1].center();
frame(&mut app, &ctx, 0.2, vec![Event::PointerMoved(tab)]);
frame(&mut app, &ctx, 0.25, vec![]);
frame(&mut app, &ctx, 0.8, vec![]);
assert_eq!(app.session.active_index(), Some(1), "holding the artwork over the tab activates it");
}
/// Verify that leaving a tab restarts its dwell timer and hovering its close button never activates it.
#[test]
fn tab_hover_delay_restarts_after_leaving_and_close_button_does_not_activate() {