Merge pull request #2256 from dotCooCoo/feat/draw-inside-double-click

Draw Inside: a double-click outside the object returns to Draw Normal
This commit is contained in:
BFlat
2026-10-16 03:13:41 +07:00
committed by GitHub
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
+1
View File
@@ -450,6 +450,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
Clear deletes individual selected compound-path members without deleting their siblings, and explicit object ids work without a selection; Cut still removes the same objects it copies (#786).
- **Compound paths:** Make keeps the backmost object's appearance and each contour; selecting a member in Direct Selection reads and changes the compound's fill rule in Attributes. Direction remains a per-member edit. Rendered holes, unchanged contour geometry, no-op history and Undo/Redo are covered by `engine/tests/compound_attributes.rs` (#1197). Behaviour reference: [public compound-path documentation](https://helpx.adobe.com/illustrator/desktop/manage-objects/reshape-transform-objects/create-compound-paths.html). Make Compound Path on a group (such as outlined type) keeps the paint of its backmost path instead of drawing nothing, and compound paths joined into one keep their holes (#1537).
- **Saved selections:** Select ▸ Save Selection… asks for a name and keeps the selection in the document (saved with the file, undoable, up to 25); each one is listed at the bottom of the Select menu and a click selects its objects again; Edit Selection… lists them to rename or delete, applied together on OK. A file's saved selections are capped and checked against its objects when it opens. Not yet: Update Selection and the Layers panel's saved-selection menu.
- **Drawing modes:** in Draw Inside, a double-click with a selection tool outside the object being drawn into sets the drawing mode to Draw Normal, as Shift+D does.
- **Drawing:** Pen, Curvature, anchor tools, Pencil, Paintbrush, Blob Brush, Smooth, Path Eraser, Join. Direction handles dragged with Direct Selection or the Anchor Point tool snap to smart guides, and Shift keeps them at 45° steps from their anchor. The Pen continues any open path from the end clicked, and while drawing, a click on an end of another open path joins the two into one (#776). The Curvature tool edits any selected path, whatever drew it, keeping its shape except where edited (#798): a click on a point makes it current and a drag moves it, re-curving only its two segments; Alt-click or double-click toggles it smooth or corner and Delete removes it; a click on a segment adds a point without changing the shape; with an end of an open path current, clicks go on from it and its other end closes it (`path.curvatureEdit`). Every point a drawing tool places snaps to Smart Guides through one snapper (#506): the Pen's and Curvature tool's anchors, the Pencil's ends, the shape tools' start and dragged corner or radius (Rectangle, Rounded Rectangle, Ellipse, Polygon, Star, Line Segment, Arc, Spiral, the grids), the Type tool's click and frame, a new artboard's corners. A filled Pencil path keeps a stroke of None (#1804, part of #1679). The [ and ] keys reach the Blob Brush, the Eraser and the Symbolism tools in the app (#1734, part of #1661).
Curvature point markers are circular, with no direction handles. While drawing, its own point feedback replaces generic selection markers; existing paths use the selection overlay, including partial selections and Hide Edges. Anchor Size applies, and Direct Selection and Pen retain their handles. The real egui-frame regressions in `crates/ui-egui/src/canvas.rs` verify marker positions, counts and unchanged documents (#1236, in part); the existing-path interpolation request remains open. The Smooth tool smooths only the stretch of path under the drag (#1547, fixes #1512).
- **Shapes:** all shape tools (including Flare) and the line, arc, spiral and grid tools. Space held while dragging out a shape, line, arc, spiral or grid moves it at its current size. While a grid is dragged out, Up/Down change its rows (concentric dividers) and Left/Right its columns (radial dividers). A click with a grid tool asks for the size, the divider counts and the grid options (#1377, in part): a skew for each set of dividers, Use Outside Rectangle As Frame or Create Compound Path From Ellipses, and Fill Grid; they are parameters of `shape.rectangularGrid` and `shape.polarGrid`, stay as the tool's options and apply to drags too. The curve a skew percentage follows and the direction of the radial skew are our own (the public documentation gives neither), and the dialogs have no reference point locator. An ellipse's Pie Start and End Angle and Invert Pie (Transform and Properties panels' Ellipse Properties, `object.setLiveShape {pieStart, pieEnd, invertPie}`) cut it as a pie (#812; the on-canvas pie widget is still to come). A live polygon's Polygon Properties there: side count, angle, corner type and radius, radius, side length and Make Sides Equal once an uneven scale has made its sides differ (`object.setLiveShape {sides, polygonAngle, polygonRadius, sideLength, makeSidesEqual}`, part of #812); a star is a plain path, as in Illustrator. The Flare Tool Options last as the tool's options: the next flare dragged out uses them, a click's dialog shows them and double-clicking the tool opens them (part of #812).