Draw Inside: a double-click outside the object returns to Draw Normal

In Draw Inside, a double-click with the Selection, Direct Selection or
Group Selection tool outside the object being drawn into now sets the
drawing mode to Draw Normal (view.drawMode {mode: "normal"}), as
Shift+D does. The object's geometric bounds, grown by the selection
tolerance, count as inside, and a double-click after the object was
deleted also returns to Draw Normal. The tool then takes the
double-click as before: with the Selection tool, a double-click on the
empty canvas still exits isolation mode, and one on another object
still isolates that object. Other tools leave Draw Inside on.

Docs: docs/target-app-parity.md.
This commit is contained in:
dotCooCoo
2026-10-16 02:25:55 +07:00
committed by bflatastic
parent 25df070576
commit 103c6ebdfb
3 changed files with 60 additions and 0 deletions
+38
View File
@@ -110,6 +110,44 @@ fn draw_behind_and_inside() {
assert!(s.draw_inside.is_none());
}
/// In Draw Inside, a double-click with a selection tool outside the object being drawn into sets
/// the drawing mode to Draw Normal, and the tool still takes the double-click. Inside the object's
/// bounds, grown by the selection tolerance, and with other tools, Draw Inside stays on.
#[test]
fn double_clicking_outside_the_draw_inside_object_returns_to_draw_normal() {
use vectorcraft_tools::{PointerEvent, PointerKind};
let mut s = session();
let a = s.execute("shape.rectangle", &json!({"x": 100, "y": 100, "width": 100, "height": 100})).unwrap()["id"].as_u64().unwrap();
let inside = |s: &mut Session| {
s.execute("select.set", &json!({"ids": [a]})).unwrap();
s.execute("view.drawMode", &json!({"mode": "inside"})).unwrap();
};
let double = |s: &mut Session, x: f64, y: f64| {
s.pointer(&PointerEvent::new(PointerKind::DoubleClick, x, y), ViewInfo::default()).unwrap();
(s.draw_mode, s.draw_inside)
};
inside(&mut s);
s.select_tool("selection", ViewInfo::default()).unwrap();
// Inside the rectangle the Selection tool isolates it, as in Draw Normal; 2 pt past its edge
// is within the 3 px selection tolerance at 100%.
assert_eq!(double(&mut s, 150.0, 150.0), (DrawMode::Inside, Some(NodeId(a))));
assert_eq!(s.doc().unwrap().isolation, Some(NodeId(a)));
assert_eq!(double(&mut s, 202.0, 150.0), (DrawMode::Inside, Some(NodeId(a))));
// A double-click outside, on the empty canvas, sets Draw Normal and still leaves isolation mode.
assert_eq!(double(&mut s, 300.0, 250.0), (DrawMode::Normal, None));
assert_eq!(s.doc().unwrap().isolation, None);
// With the Rectangle tool a double-click leaves Draw Inside on; with Direct Selection it sets Draw Normal.
inside(&mut s);
s.select_tool("rectangle", ViewInfo::default()).unwrap();
assert_eq!(double(&mut s, 300.0, 250.0), (DrawMode::Inside, Some(NodeId(a))));
s.select_tool("directSelection", ViewInfo::default()).unwrap();
assert_eq!(double(&mut s, 300.0, 250.0), (DrawMode::Normal, None));
// Once the object is gone, any double-click with a selection tool sets Draw Normal.
inside(&mut s);
s.execute("edit.clear", &json!({})).unwrap();
assert_eq!(double(&mut s, 150.0, 150.0), (DrawMode::Normal, None));
}
#[test]
fn export_for_screens_writes_every_combination() {
let mut s = Session::new();
+21
View File
@@ -267,6 +267,7 @@ impl Session {
if let Some(r) = self.plane_widget_pointer(ev, view) {
return r;
}
self.leave_draw_inside(ev, view)?;
let ev = &self.lend_selection_tool(ev);
let acts = self.with_tool_cx(view, |t, cx| t.pointer(cx, ev));
let r = self.take_tool_panic().and_then(|()| {
@@ -282,6 +283,26 @@ impl Session {
r
}
/// In Draw Inside, a double-click with a selection tool outside the geometric bounds of the
/// object new art goes inside, grown by the selection tolerance, or after that object is gone,
/// sets the drawing mode to Draw Normal (`view.drawMode {mode: "normal"}`, as Shift+D does).
/// The tool then takes the double-click as in Draw Normal.
fn leave_draw_inside(&mut self, ev: &PointerEvent, view: ViewInfo) -> Result<()> {
if ev.kind != PointerKind::DoubleClick
|| self.draw_mode != crate::DrawMode::Inside
|| !vectorcraft_tools::catalog::is_selection_tool(self.tool.id())
{
return Ok(());
}
let Some(d) = self.active() else { return Ok(()) };
let tol = self.prefs.selection_tolerance / view.zoom.max(1e-9);
let inside = self.draw_inside.and_then(|t| d.doc.bounds_of(&[t], false)).is_some_and(|b| b.inflate(tol, tol).contains(ev.pos));
if !inside {
self.execute("view.drawMode", &serde_json::json!({ "mode": "normal" }))?;
}
Ok(())
}
/// A guide dragged out of a ruler, whatever the tool: a vertical one out of the left ruler, a
/// horizontal one out of the top ruler. `ev` is the pointer in document space (a drag, then the
/// release), over the canvas or not (`on_canvas`). The guide shows where the pointer is over