diff --git a/crates/tools/src/draw2/gesture.rs b/crates/tools/src/draw2/gesture.rs index e4811418c7..aee21e9375 100644 --- a/crates/tools/src/draw2/gesture.rs +++ b/crates/tools/src/draw2/gesture.rs @@ -6,9 +6,9 @@ //! points with straight segments and the release fits them; Alt on release (or ending near the start) closes the path, and starting //! near an end of a selected open path continues it. Shift held at the press draws a straight //! Pencil line at 45° steps from the Constrain Angle instead, and Alt held at the press (with Alt -//! key toggles to Smooth Tool off) draws a straight line at any angle. The Pencil's new path starts and ends on -//! Smart Guides ([`DrawSnap`]: an anchor, a path, in line with the art), hovering too; the points -//! between follow the hand. The Paintbrush records the pen pressure of each point (a stroke a pen +//! key toggles to Smooth Tool off) draws a straight line at any angle. The Pencil ignores Smart +//! Guides and Snap to Point: it shows no Smart Guides, hovering or drawing, and its ends follow the +//! hand. Snap to Grid and Snap to Pixel still move its ends (`grid_snaps`). The Paintbrush records the pen pressure of each point (a stroke a pen //! pressed less than fully on sends `[x, y, pressure]` points) for pressure-sensitive brushes, and //! the pen's tilt and barrel rotation where the pen reads them (`[x, y, pressure, tiltX, tiltY, //! rotation]` points, `null` for what a point lacks) for brushes that vary with them. The Blob @@ -210,27 +210,29 @@ impl GestureTool { } } - /// Do the ends of its strokes snap (the Pencil)? - fn snaps(&self) -> bool { - self.id == "pencil" - } - /// Does a straight Knife cut snap to anchors and paths? Only with Smart Guides on, which Snap to - /// Pixel and Snap to Grid turn off (`PointSnap::new`). Its ends snap as the Pencil's straight - /// line's do: the press, and the release unless Shift keeps it on a 45° step. + /// Pixel and Snap to Grid turn off (`PointSnap::new`). Its ends snap: the press, and the + /// release unless Shift keeps it on a 45° step. fn knife_snaps(&self, cx: &ToolContext) -> bool { self.id == "knife" && self.straight && cx.smart_guides && !cx.snap_to_pixel && !cx.snap_to_grid } - /// Does the end of a straight line snap: the Pencil's Alt line's, or a straight Knife cut's - /// without Shift? + /// Do the Pencil's ends snap? Only to the grid or to pixels, with View → Snap to Grid or Snap + /// to Pixel on: those are separate from Smart Guides (and turn them off), and the grid case is + /// its own question (#2958). Smart Guides and Snap to Point never move them. + fn grid_snaps(&self, cx: &ToolContext) -> bool { + self.id == "pencil" && (cx.snap_to_grid || cx.snap_to_pixel) + } + + /// Does the end of a straight line without Shift snap: the Pencil's Alt line's to the grid or + /// pixels, a straight Knife cut's to anchors and paths? fn line_end_snaps(&self, cx: &ToolContext) -> bool { - self.straight && self.unconstrained && (self.snaps() || self.knife_snaps(cx)) + self.straight && self.unconstrained && (self.grid_snaps(cx) || self.knife_snaps(cx)) } /// Where a stroke or straight line released at `p` ends: where it snaps when its end snaps. fn release_end(&mut self, cx: &ToolContext, p: Point) -> Point { - if (self.snaps() && !self.smoothing && !self.straight) || self.line_end_snaps(cx) { self.snap.drag(cx, p, None) } else { p } + if (self.grid_snaps(cx) && !self.smoothing && !self.straight) || self.line_end_snaps(cx) { self.snap.drag(cx, p, None) } else { p } } /// Is a straight line with its end at `end` back at its press point (within a pixel), so that @@ -555,10 +557,6 @@ impl Tool for GestureTool { self.tip_at = (!ev.mods.cmd && !self.blob_alt_smooths(ev.mods)).then_some(p); } match ev.kind { - PointerKind::Move if self.snaps() => { - self.snap.hover(cx, p, &[], None); - vec![] - } PointerKind::Down => { self.active = true; self.began = false; @@ -581,11 +579,12 @@ impl Tool for GestureTool { _ => vec![], }; self.redraw = if self.draws_path() && !self.smoothing && self.extend.is_none() { self.find_redraw(cx, p) } else { None }; - // A path continued starts at its end, one redrawn on it; a new one where it snaps. - // A straight line is constrained from the end it continues, so it starts there. + // A path continued starts at its end, one redrawn on it; a new one at the press, + // or on the grid or a pixel when they snap. A straight line is constrained from the + // end it continues, so it starts there. let start = if let Some((_, _, end)) = extend.filter(|_| self.straight) { end - } else if self.snaps() && !self.smoothing && self.extend.is_none() && self.redraw.is_none() { + } else if self.grid_snaps(cx) && !self.smoothing && self.extend.is_none() && self.redraw.is_none() { self.snap.press(cx, p, &[], None) } else if self.knife_snaps(cx) { // A straight Knife cut, its overlay and Shift's 45° steps start from the snapped point. @@ -609,13 +608,13 @@ impl Tool for GestureTool { self.moved |= self.points.first().is_some_and(|start| p.distance(*start) > vectorcraft_geom::EPS); let p = self.constrained(cx, p, ev.mods); let mut end = p; - if self.snaps() && !self.smoothing && !self.straight { - // Where the stroke would end: shown, taken on release. + if self.grid_snaps(cx) && !self.smoothing && !self.straight { + // Where the stroke would end on the grid or a pixel: taken on release. end = self.snap.drag(cx, p, None); } let p = if self.line_end_snaps(cx) { - // Alt's line ends where a stroke would, and a Knife cut's on the anchors and - // paths its press snaps to: shown, taken on release. + // Alt's line ends on the grid or a pixel when they snap, and a Knife cut on the + // anchors and paths its press snaps to: shown, taken on release. self.snap.drag(cx, p, None) } else { p @@ -635,7 +634,7 @@ impl Tool for GestureTool { } else if self.points.last().is_some_and(|l| l.distance(p) < cx.tol(1.0)) || self.points.len() >= crate::MAX_STROKE_SAMPLES { // A release here keeps the last point unless it snaps (`Up`), so the ring // follows the point `finish` will test. - let kept = self.points.last().copied().filter(|l| l.distance(p) < cx.tol(1.0) && !self.snaps()); + let kept = self.points.last().copied().filter(|l| l.distance(p) < cx.tol(1.0) && !self.grid_snaps(cx)); let release_end = kept.unwrap_or(end); self.near_start = self.release_closes_near_start(cx, release_end); return vec![]; @@ -690,7 +689,7 @@ impl Tool for GestureTool { } } else if self.points.last().is_some_and(|l| l.distance(p) >= cx.tol(1.0)) { self.push(end, ev); - } else if self.snaps() + } else if self.grid_snaps(cx) && !self.smoothing && self.points.len() > 1 && let Some(last) = self.points.last_mut() diff --git a/crates/tools/src/draw2/gesture/tests.rs b/crates/tools/src/draw2/gesture/tests.rs index a0fe487289..b1875d56d4 100644 --- a/crates/tools/src/draw2/gesture/tests.rs +++ b/crates/tools/src/draw2/gesture/tests.rs @@ -22,25 +22,67 @@ fn drag_held(t: &mut GestureTool, cx: &ToolContext, pts: &[(f64, f64)], m: Mods) v } -/// The Pencil's new path starts and ends on Smart Guides (#506), the points between where -/// the hand went. +/// The Pencil follows the hand from the press to the release: it ignores Smart Guides and Snap to Point and shows no +/// Smart Guides, hovering or drawing over the art, with Smart Guides on or with only Snap to Point. #[test] -fn pencil_ends_snap_to_smart_guides() { +fn pencil_follows_the_hand_and_shows_no_smart_guides() { let (d, _) = doc_with_rect(); let s = Selection::default(); let p = paint(); - let cx = cx(&d, &s, &p); - let mut t = GestureTool::new("pencil"); - let acts = drag(&mut t, &cx, &[(102.0, 98.0), (151.0, 300.0), (250.0, 320.0), (198.0, 203.0)], Mods::default()); - let Some(Action::Preview(_, v)) = acts.last().and_then(|a| a.first()) else { panic!("{acts:?}") }; - assert_eq!(v["points"], json!([[100.0, 100.0], [151.0, 300.0], [250.0, 320.0], [200.0, 200.0]])); + let on = cx(&d, &s, &p); + let snap_to_point = ToolContext { smart_guides: false, ..cx(&d, &s, &p) }; + // Near the corners at (100, 100) and (200, 200), within Snap to Point's 2 px of them. + let hand = [(101.0, 99.0), (151.0, 300.0), (250.0, 320.0), (199.0, 201.0)]; + for cx in [&on, &snap_to_point] { + let mut t = GestureTool::new("pencil"); + t.pointer(cx, &PointerEvent::new(PointerKind::Move, hand[0].0, hand[0].1)); + assert!(t.overlays(cx).is_empty(), "hovering: {:?}", t.overlays(cx)); + t.pointer(cx, &PointerEvent::new(PointerKind::Down, hand[0].0, hand[0].1)); + for &(x, y) in &hand[1..] { + t.pointer(cx, &PointerEvent::new(PointerKind::Drag, x, y)); + assert!(t.snap.guides().is_empty(), "drawing: {:?}", t.snap.guides()); + } + let last = hand[hand.len() - 1]; + let acts = t.pointer(cx, &PointerEvent::new(PointerKind::Up, last.0, last.1)); + let Some(Action::Preview(_, v)) = acts.first() else { panic!("{acts:?}") }; + assert_eq!(v["points"], json!(hand.map(|(x, y)| [x, y]))); + } // The Paintbrush follows the hand all the way. let mut t = GestureTool::new("paintbrush"); - let acts = drag(&mut t, &cx, &[(102.0, 98.0), (151.0, 300.0)], Mods::default()); + let acts = drag(&mut t, &on, &[(102.0, 98.0), (151.0, 300.0)], Mods::default()); let Some(Action::Preview(_, v)) = acts.last().and_then(|a| a.first()) else { panic!("{acts:?}") }; assert_eq!(v["points"][0], json!([102.0, 98.0])); } +/// Snap to Grid and Snap to Pixel are not Smart Guides: they still move the Pencil's ends (and an +/// Alt line's), while the points between follow the hand. +#[test] +fn pencil_ends_still_snap_to_the_grid_and_pixels() { + let (d, _) = doc_with_rect(); + let s = Selection::default(); + let p = paint(); + let grid = ToolContext { snap_to_grid: true, ..cx(&d, &s, &p) }; + let pixel = ToolContext { snap_to_pixel: true, ..cx(&d, &s, &p) }; + let on = |cx: &ToolContext, q: (f64, f64)| match cx.snap_to_grid { + true => vectorcraft_geom::snap::snap_point_to_grid(Point::new(q.0, q.1), cx.grid_step()), + false => Point::new(q.0.round(), q.1.round()), + }; + let hand = [(301.3, 302.6), (351.4, 420.2), (447.7, 452.9)]; + for cx in [&grid, &pixel] { + let (first, last) = (on(cx, hand[0]), on(cx, hand[2])); + assert!(first != Point::new(hand[0].0, hand[0].1) && last != Point::new(hand[2].0, hand[2].1)); + let mut t = GestureTool::new("pencil"); + let acts = drag(&mut t, cx, &hand, Mods::default()); + let Some(Action::Preview(_, v)) = acts.last().and_then(|a| a.first()) else { panic!("{acts:?}") }; + assert_eq!(v["points"], json!([[first.x, first.y], [351.4, 420.2], [last.x, last.y]])); + // Alt's straight line ends on the grid or a pixel too. + let mut t = GestureTool::new("pencil"); + let acts = drag_held(&mut t, cx, &[hand[0], hand[2]], Mods { alt: true, ..Mods::default() }); + let Some(Action::Preview(_, v)) = acts.last().and_then(|a| a.first()) else { panic!("{acts:?}") }; + assert_eq!(v["points"][1], json!([last.x, last.y])); + } +} + /// #852: the Paintbrush sends each point's pen pressure; a mouse (always fully pressed) and /// the Pencil send plain points. #[test] @@ -639,10 +681,10 @@ fn pencil_shift_draws_a_straight_line_at_45_degree_steps() { } } -/// #3799: a Shift line leaves no Smart Guides of its press on screen while it is dragged -/// (its end doesn't snap), and Alt at its release leaves it open. +/// #3799: a Shift line shows no Smart Guides, at its press or while it is dragged, and Alt at its +/// release leaves it open. #[test] -fn pencil_shift_line_drops_the_press_guides_and_stays_open_with_alt() { +fn pencil_shift_line_shows_no_guides_and_stays_open_with_alt() { let (d, _) = doc_with_rect(); let s = Selection::default(); let p = paint(); @@ -650,7 +692,7 @@ fn pencil_shift_line_drops_the_press_guides_and_stays_open_with_alt() { let shift = Mods { shift: true, ..Default::default() }; let mut t = GestureTool::new("pencil"); t.pointer(&cx, &PointerEvent::new(PointerKind::Down, 102.0, 98.0).with_mods(shift)); - assert!(!t.overlays(&cx).is_empty(), "the press snaps to the corner and shows it"); + assert!(t.snap.guides().is_empty(), "the press: {:?}", t.snap.guides()); t.pointer(&cx, &PointerEvent::new(PointerKind::Drag, 300.0, 140.0).with_mods(shift)); assert!(t.overlays(&cx).is_empty(), "{:?}", t.overlays(&cx)); let shift_alt = Mods { shift: true, alt: true, ..Default::default() }; @@ -1094,10 +1136,11 @@ fn paintbrush_alt_with_only_two_anchor_line_selected_draws_a_closed_stroke() { assert!(matches!(acts.last().and_then(|a| a.get(1)), Some(Action::Commit)), "{acts:?}"); } -/// #3799: Alt's line starts and ends on Smart Guides as a stroke does, and pressed on an end of -/// a selected open path it continues the path from that end to the pointer. +/// #3799: Alt's line runs from the press to the pointer as a stroke does, snapping to nothing, +/// and pressed on an end of a selected open path it continues the path from that end to the +/// pointer. #[test] -fn pencil_alt_line_snaps_its_ends_and_continues_a_selected_path() { +fn pencil_alt_line_follows_the_hand_and_continues_a_selected_path() { let (mut d, _) = doc_with_rect(); let alt = Mods { alt: true, ..Default::default() }; let p = paint(); @@ -1107,7 +1150,7 @@ fn pencil_alt_line_snaps_its_ends_and_continues_a_selected_path() { let mut t = GestureTool::new("pencil"); let acts = drag_held(&mut t, &cx, &[(102.0, 98.0), (151.0, 300.0), (198.0, 203.0)], alt); let Some(Action::Preview(_, v)) = acts.last().and_then(|a| a.first()) else { panic!("{acts:?}") }; - assert_eq!(v["points"], json!([[100.0, 100.0], [200.0, 200.0]])); + assert_eq!(v["points"], json!([[102.0, 98.0], [198.0, 203.0]])); } let l = d.layers[0].id; let id = d.alloc_id(); diff --git a/docs/mcp.md b/docs/mcp.md index 89c7b1f683..b31c124b77 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4812,9 +4812,11 @@ snap the pointer as drawing tools do, so dragging a corner onto another object's ``` Every point a drawing tool places snaps the same way, through one snapper (#506): the Pen's and Curvature tool's -anchors, the Pencil's first and last point, the shape tools' start and dragged corner or radius (`rectangle`, +anchors, the shape tools' start and dragged corner or radius (`rectangle`, `roundedRectangle`, `ellipse`, `polygon`, `star`, `lineSegment`, `arc`, `spiral`, `rectangularGrid`, `polarGrid`), the -Type tool's click and frame, and a new artboard's corners. The point lands on another object's anchor, centre or +Type tool's click and frame, and a new artboard's corners. The Pencil ignores Smart Guides and Snap to Point and +shows no Smart Guides: its strokes and straight lines follow the hand (Snap to Grid and Snap to Pixel still move +their ends). The point lands on another object's anchor, centre or path ("anchor", "center", "path"), else lines up with the anchors, edges and centres of the art and the artboards (a line from the target and "align"); the Pen's lands on the path being drawn too, so a click on its first anchor closes it exactly. With `mods.shift` the point keeps to its 45° step from the anchor before it (from `constrainAngle`; a box diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index a305e9d29d..fd496a7a5f 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -941,7 +941,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur With the Blob Brush, Alt held before the press, or pressed before the drag has moved, switches to the Smooth tool, as the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists: the cursor shows the Smooth tool, the drag shows a thin line without the length label, Shift does not constrain it, and the release smooths the selected paths under the drag (`path.smoothRegion`) instead of painting; with nothing selected the release changes no path. A stroke that has moved keeps painting when Alt goes down. 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 curve through the points (`curvature_spline` in `crates/tools/src/draw2.rs`) makes each stretch of three points follow the circle through them: a smooth point's handles lie along that circle's tangent, each `chord / (3 cos²(α/2))` long (α between handle and chord, capped at 90°), and the ends of an open path mirror their neighbour's tangent in their chord, so a J or a U curves evenly, a turn tighter than 30° comes to a point, and uneven spacing doesn't loop; the rubber band, a dragged or inserted point and `path.convertAnchor` to smooth use the same rule, while the Pencil's fitting keeps its own; `tests/draw2.rs` checks that the rubber band is the path the click makes (#1236, in part). The Smooth tool smooths only the stretch of path under the drag (#1547, fixes #1512). While Alt is held during a Paintbrush stroke or before a press with no path selected that the Smooth tool can act on, the Paintbrush shows a closing cursor (a crosshair with a ring), and so does the Pencil with Alt key toggles to Smooth Tool off when Alt is pressed after the drag starts; releasing the mouse button with Alt still held closes the path. Before a Paintbrush press with such a path selected, Alt shows the Smooth cursor, and holding Alt at the press smooths that path with the Smooth tool's radius and Smooth Tool Options' Fidelity. A Pencil drag started with Shift or Alt held draws a straight line instead, which stays open. - With Shift held at the press, the Pencil draws a straight line from the press point to the pointer at 45° steps from the Constrain Angle, as [Draw straight lines with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html) describes; pressed on an end of a selected open path, the line continues the path from that end (part of #3799). With Option/Alt held at the press and Alt key toggles to Smooth Tool off, the Pencil draws a straight line at any angle, whose ends snap to Smart Guides, and it stays open when Alt is still held at the release; Shift and Alt together keep the 45° steps (#3799). While Shift or Alt is held before the press, the Pencil shows a straight-line cursor (a crosshair with a short line), and over an end of a selected open path, where the line continues the path, a path-continuation cursor (the same line leaving a small square), as the page describes. + With Shift held at the press, the Pencil draws a straight line from the press point to the pointer at 45° steps from the Constrain Angle, as [Draw straight lines with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-straight-lines-with-the-pencil-tool.html) describes; pressed on an end of a selected open path, the line continues the path from that end (part of #3799). With Option/Alt held at the press and Alt key toggles to Smooth Tool off, the Pencil draws a straight line at any angle, and it stays open when Alt is still held at the release; Shift and Alt together keep the 45° steps (#3799). While Shift or Alt is held before the press, the Pencil shows a straight-line cursor (a crosshair with a short line), and over an end of a selected open path, where the line continues the path, a path-continuation cursor (the same line leaving a small square), as the page describes. - **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. A shape just drawn stays adjustable with its tool: its Live Shape widgets drag, a press on its centre point (Show Center) moves the selection with the Selection tool's move arrow, snapping and Alt copy, and a press in the rotate zone just outside a bounding-box corner turns it about the centre with the Selection tool's rotate cursor (#5477; tests `a_shape_tool_moves_the_selection_by_its_centre_point_and_turns_it_from_a_corner` and, through the engine, `shapetools.rs`). 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). With the bounding box showing, the Selection tool shows a selected ellipse's two pie widgets just outside it; dragging one sets its angle in one undo step and double-clicking one makes the ellipse whole again. 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). - **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. The Flare tool's center drag turns the rays by 0.1 radian at each drag update in which the pointer's document y grows, and back by as much at each one in which it falls, Shift held keeps their turn, and Space held during either of its drags moves the flare. 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). With the bounding box showing, the Selection tool shows a selected ellipse's two pie widgets just outside it; dragging one sets its angle in one undo step and double-clicking one makes the ellipse whole again. 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). - **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. The Flare tool's center drag turns the rays with the pointer, Shift keeps them in their original orientation (unturned, or turned by a nonzero General › Constrain Angle), and Space held during either of its drags moves the flare. 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). With the bounding box showing, the Selection tool shows a selected ellipse's two pie widgets just outside it; dragging one sets its angle in one undo step and double-clicking one makes the ellipse whole again. 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).