From 0a7bf3a7e25edb71b37fb30a9433b750130be2f5 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 13 Nov 2026 09:10:10 -0800 Subject: [PATCH] Live Paint Selection: cursor shows a face, an edge or nothing to select The Live Paint Selection tool always showed the plain arrow. Its cursor now says what a click at the pointer selects, by the same rules as the click (Alt, Select Fills, Select Strokes, locked, hidden and isolated groups): the arrow with a filled square over a face, with a short line between two anchor marks over an edge, and with an x where a click selects no face or edge. From a press to the release the cursor keeps the look it had at the press. Use Precise Cursors shows the crosshair. - Cursor: LivePaintSelectFace, LivePaintSelectEdge and LivePaintSelectOutside, with cursors.toml entries and drawn glyphs. - Tests: live_paint_selection_cursor_shows_what_a_click_selects, live_paint_selection_shows_the_arrow_with_a_face_edge_or_x_badge, and the precise-cursor list. Docs: docs/mcp.md, docs/target-app-parity.md. --- crates/engine/src/tests/prefs.rs | 3 + crates/tools/src/builder/live_paint.rs | 32 +++++++++-- crates/tools/src/builder/tests.rs | 78 ++++++++++++++++++++++++++ crates/tools/src/cursor.rs | 14 ++++- crates/tools/src/cursors.toml | 35 +++++++++++- crates/ui-egui/src/canvas/cursor.rs | 5 +- crates/ui-egui/src/cursors.rs | 27 ++++++++- crates/ui-egui/src/cursors/tests.rs | 39 +++++++++++++ docs/mcp.md | 4 +- docs/target-app-parity.md | 10 ++++ 10 files changed, 237 insertions(+), 10 deletions(-) diff --git a/crates/engine/src/tests/prefs.rs b/crates/engine/src/tests/prefs.rs index 3808d06797..37127f686a 100644 --- a/crates/engine/src/tests/prefs.rs +++ b/crates/engine/src/tests/prefs.rs @@ -756,6 +756,9 @@ fn precise_cursors_replace_every_pen_state() { Cursor::LivePaintBucket, Cursor::LivePaintBucketFace, Cursor::LivePaintBucketEdge, + Cursor::LivePaintSelectFace, + Cursor::LivePaintSelectEdge, + Cursor::LivePaintSelectOutside, ] { assert_eq!(c.precise(), Cursor::Crosshair, "{c:?}"); } diff --git a/crates/tools/src/builder/live_paint.rs b/crates/tools/src/builder/live_paint.rs index 2b6cef4532..e2d6c9f77e 100644 --- a/crates/tools/src/builder/live_paint.rs +++ b/crates/tools/src/builder/live_paint.rs @@ -480,6 +480,8 @@ pub const LP_SELECT_WIDTH_RANGE: (f64, f64) = (1.0, 20.0); /// Select Strokes off, Alt-click selects the face under the pointer as a click does and a marquee /// takes no edges; with Select Fills off, a click selects the edge nearest the pointer as Alt-click /// does and a marquee takes no faces; with both off, a click or a marquee selects no face or edge. +/// The pointer shows what a click at that point selects ([`LivePaintSelectionTool::pick_at`]): a +/// face, an edge, or nothing. From the press to the release it keeps the look it had at the press. pub struct LivePaintSelectionTool { pub(super) cache: RegionCache, pub(super) hover: LpHover, @@ -502,6 +504,8 @@ struct LpDrag { shift: bool, /// The face or edge under the press, which a click selects (or with Shift toggles). hit: Option, + /// The cursor at the press, which the drag keeps. + cursor: Cursor, } impl LpDrag { @@ -538,6 +542,22 @@ impl LivePaintSelectionTool { (false, false) => None, } } + + /// The face or edge a click at `p` selects, Alt held or not, with its cursor: the visible part of + /// the kind [`LivePaintSelectionTool::picks_edge`] asks for, of a Live Paint group the tool takes + /// ([`lp_part_at`], [`lp_selectable`]), and [`Cursor::LivePaintSelectFace`] or + /// [`Cursor::LivePaintSelectEdge`]; None and [`Cursor::LivePaintSelectOutside`] where a click + /// selects no face or edge. + fn pick_at(&self, cx: &ToolContext, p: Point, alt: bool) -> (Option, Cursor) { + let Some(edge) = self.picks_edge(alt) else { return (None, Cursor::LivePaintSelectOutside) }; + let hit = lp_part_at(cx.doc, p, cx.tol(4.0), edge, &|id| lp_selectable(cx, id)).and_then(|(_, part)| part).map(|n| n.id); + let cursor = match (hit, edge) { + (None, _) => Cursor::LivePaintSelectOutside, + (Some(_), true) => Cursor::LivePaintSelectEdge, + (Some(_), false) => Cursor::LivePaintSelectFace, + }; + (hit, cursor) + } } /// The faces (with `faces`) and edges (with `edges`) that marquee `r` touches, among the faces and @@ -592,9 +612,8 @@ impl Tool for LivePaintSelectionTool { vec![] } PointerKind::Down => { - let part = edge.and_then(|e| lp_part_at(cx.doc, p, cx.tol(4.0), e, &|id| lp_selectable(cx, id))); - let hit = part.and_then(|(_, part)| part).map(|n| n.id); - self.drag = Some(LpDrag { start: p, cur: p, shift: ev.mods.shift, hit }); + let (hit, cursor) = self.pick_at(cx, p, ev.mods.alt); + self.drag = Some(LpDrag { start: p, cur: p, shift: ev.mods.shift, hit, cursor }); vec![] } PointerKind::Drag => { @@ -652,8 +671,11 @@ impl Tool for LivePaintSelectionTool { lp_overlays(&self.hover, &self.cache, self.color, self.width as f32, self.width as f32) } - fn cursor(&self, _cx: &ToolContext, _p: Point, _m: Mods) -> Cursor { - Cursor::Arrow + fn cursor(&self, cx: &ToolContext, p: Point, m: Mods) -> Cursor { + match &self.drag { + Some(d) => d.cursor, + None => self.pick_at(cx, p, m.alt).1, + } } fn options(&self) -> Value { diff --git a/crates/tools/src/builder/tests.rs b/crates/tools/src/builder/tests.rs index 89ce04eb3d..298e6a1907 100644 --- a/crates/tools/src/builder/tests.rs +++ b/crates/tools/src/builder/tests.rs @@ -1636,6 +1636,84 @@ fn live_paint_selection_click_outside_the_faces_selects_the_edge_it_highlights() assert_eq!(lp_click(&mut t, &cx, ev(PointerKind::Down, 106.0, 50.0)), vec![Action::Exec("select.none".into(), json!({}))]); } +/// The Live Paint Selection pointer shows what a click at the point selects, from the point and +/// the modifier keys alone (no move first): a face, an edge, or nothing. Groups the tool does not +/// take (locked, hidden, outside isolation) show nothing to select. A press keeps its look until +/// the release. +#[test] +fn live_paint_selection_cursor_shows_what_a_click_selects() { + let mut d = Document::new(500.0, 500.0); + let (g, _) = live_square(&mut d, Rect::new(0.0, 0.0, 100.0, 100.0)); + let (other, _) = live_square(&mut d, Rect::new(200.0, 0.0, 300.0, 100.0)); + let s = Selection::default(); + let p = paint(); + let plain = Mods::default(); + let alt = Mods { alt: true, ..Default::default() }; + let (face, edge, out) = (Cursor::LivePaintSelectFace, Cursor::LivePaintSelectEdge, Cursor::LivePaintSelectOutside); + let at = |t: &LivePaintSelectionTool, cx: &ToolContext, x: f64, m: Mods| t.cursor(cx, Point::new(x, 50.0), m); + let mut t = LivePaintSelectionTool::default(); + { + let cx = cx(&d, &s, &p); + // Both options on: a face inside the square; with Alt the side within reach, inside or + // outside the face; nothing away from the groups or from every side. + assert_eq!( + [at(&t, &cx, 50.0, plain), at(&t, &cx, 98.5, plain), at(&t, &cx, 102.0, plain), at(&t, &cx, 150.0, plain)], + [face, face, out, out] + ); + assert_eq!([at(&t, &cx, 98.5, alt), at(&t, &cx, 102.0, alt), at(&t, &cx, 50.0, alt), at(&t, &cx, 150.0, alt)], [edge, edge, out, out]); + // The cursor agrees with what a click there selects. + let face_id = faces(d.node(g).unwrap())[0].id; + assert_eq!(lp_click(&mut t, &cx, ev(PointerKind::Down, 50.0, 50.0)), vec![Action::Exec("select.set".into(), json!({ "ids": [face_id.0] }))]); + assert_eq!(lp_click(&mut t, &cx, ev(PointerKind::Down, 50.0, 50.0).with_mods(alt)), vec![Action::Exec("select.none".into(), json!({}))]); + // Select Fills off: a side without Alt too, never a face. + t.set_option("selectFills", &json!(false)); + assert_eq!([at(&t, &cx, 98.5, plain), at(&t, &cx, 50.0, plain), at(&t, &cx, 98.5, alt)], [edge, out, edge]); + // Select Strokes off: the face with Alt too, never a side. + t.set_option("selectFills", &json!(true)); + t.set_option("selectStrokes", &json!(false)); + assert_eq!([at(&t, &cx, 98.5, alt), at(&t, &cx, 102.0, alt)], [face, out]); + // Both off: nothing. + t.set_option("selectFills", &json!(false)); + assert_eq!([at(&t, &cx, 50.0, plain), at(&t, &cx, 98.5, alt)], [out, out]); + } + let mut t = LivePaintSelectionTool::default(); + // A locked group shows nothing to select; the other group still does. + d.node_mut(g).unwrap().locked = true; + { + let cx = cx(&d, &s, &p); + assert_eq!([at(&t, &cx, 50.0, plain), at(&t, &cx, 98.5, alt), at(&t, &cx, 250.0, plain)], [out, out, face]); + } + d.node_mut(g).unwrap().locked = false; + // In isolation mode, a group outside the isolated art shows nothing to select. + { + let cx = ToolContext { isolation: Some(other), ..cx(&d, &s, &p) }; + assert_eq!([at(&t, &cx, 50.0, plain), at(&t, &cx, 250.0, plain)], [out, face]); + } + // A hidden group shows nothing to select. + d.node_mut(g).unwrap().visible = false; + { + let cx = cx(&d, &s, &p); + assert_eq!([at(&t, &cx, 50.0, plain), at(&t, &cx, 98.5, alt)], [out, out]); + } + d.node_mut(g).unwrap().visible = true; + // A press keeps its look while the drag goes elsewhere, and the release lets it follow again. + let cx = cx(&d, &s, &p); + t.pointer(&cx, &ev(PointerKind::Down, 50.0, 50.0)); + t.pointer(&cx, &ev(PointerKind::Drag, 150.0, 50.0)); + assert_eq!(at(&t, &cx, 150.0, plain), face); + t.pointer(&cx, &ev(PointerKind::Up, 150.0, 50.0)); + assert_eq!(at(&t, &cx, 150.0, plain), out); + t.pointer(&cx, &ev(PointerKind::Down, 150.0, 50.0)); + t.pointer(&cx, &ev(PointerKind::Drag, 50.0, 50.0)); + assert_eq!(at(&t, &cx, 50.0, plain), out); + t.key(&cx, ToolKey::Escape, plain); + assert_eq!(at(&t, &cx, 50.0, plain), face, "Escape ends the press"); + for c in [face, edge, out] { + assert_eq!(c.precise(), Cursor::Crosshair, "{c:?}"); + assert_eq!(c.stand_in(), None, "{c:?} is drawn"); + } +} + /// Presses at `from` with `m`, drags to `to` and releases there; returns what the release does /// (the press and the drag do nothing). fn lp_drag(t: &mut LivePaintSelectionTool, cx: &ToolContext, from: (f64, f64), to: (f64, f64), m: Mods) -> Vec { diff --git a/crates/tools/src/cursor.rs b/crates/tools/src/cursor.rs index d5cdd05bc5..584149da8f 100644 --- a/crates/tools/src/cursor.rs +++ b/crates/tools/src/cursor.rs @@ -148,6 +148,14 @@ pub enum Cursor { LivePaintBucketFace, /// The Live Paint Bucket where a click paints an edge of a Live Paint group: a brush. LivePaintBucketEdge, + /// Live Paint Selection over a face of a Live Paint group that a click selects: the arrow with a + /// filled square. + LivePaintSelectFace, + /// Live Paint Selection over an edge of a Live Paint group that a click selects: the arrow with + /// a short line. + LivePaintSelectEdge, + /// Live Paint Selection where a click selects no face or edge: the arrow with an x. + LivePaintSelectOutside, /// The Measure tool. Measure, /// The Print Tiling tool. @@ -248,7 +256,8 @@ impl Cursor { } /// General › Use Precise Cursors: the drawing tools' pointers (the Pen's in every state, the - /// Anchor Point and Curvature tools', the Eyedropper's, the Slice and Blend tools') become a plain crosshair at the hotspot. + /// Anchor Point and Curvature tools', the Eyedropper's, the Slice and Blend tools', the Live + /// Paint tools') become a plain crosshair at the hotspot. pub fn precise(self) -> Self { match self { Cursor::Pen @@ -287,6 +296,9 @@ impl Cursor { | Cursor::LivePaintBucket | Cursor::LivePaintBucketFace | Cursor::LivePaintBucketEdge + | Cursor::LivePaintSelectFace + | Cursor::LivePaintSelectEdge + | Cursor::LivePaintSelectOutside | Cursor::Measure | Cursor::PrintTiling | Cursor::SymbolSprayer diff --git a/crates/tools/src/cursors.toml b/crates/tools/src/cursors.toml index 107eb34aeb..6f5b32e56a 100644 --- a/crates/tools/src/cursors.toml +++ b/crates/tools/src/cursors.toml @@ -28,7 +28,7 @@ shown_when = [ "The Selection tool over empty canvas or over an unselected object (no Alt).", "Dragging with the Selection tool (a move), with Direct Selection (an anchor, a segment or a handle), or with a shape tool (from the selection's centre point).", "Any tool with no cursor of its own (Touch Type, Rotate View).", - "The resting state of Live Paint Selection, Reshape (away from a path), Free Transform, Puppet Warp and Crop Image (nothing selected or outside the box), the Perspective Grid (locked or away from handles) and Perspective Selection.", + "The resting state of Reshape (away from a path), Free Transform, Puppet Warp and Crop Image (nothing selected or outside the box), the Perspective Grid (locked or away from handles) and Perspective Selection.", ] hotspot = "The arrow's tip." look = "The default pointer: a solid arrow. ArrowHollow is its hollow twin." @@ -89,6 +89,39 @@ shown_when = [ hotspot = "The front arrowhead's tip." look = "Says \"a copy\": a double arrowhead, one in front of the other." +[[cursor]] +id = "LivePaintSelectFace" +group = "Selection" +shown_when = [ + "Live Paint Selection over a face of a Live Paint group that a click selects: inside the face, with Select Fills on, and without Alt unless Select Strokes is off.", + "From a press there until the release, wherever the drag goes.", + "With Use Precise Cursors on, the crosshair shows instead.", +] +hotspot = "The arrow's tip." +look = "Arrow with a small filled square badge (a face). LivePaintSelectEdge and LivePaintSelectOutside share the arrow." + +[[cursor]] +id = "LivePaintSelectEdge" +group = "Selection" +shown_when = [ + "Live Paint Selection within reach of an edge of a Live Paint group that a click selects: Select Strokes is on, and Alt is held or Select Fills is off.", + "From a press there until the release, wherever the drag goes.", + "With Use Precise Cursors on, the crosshair shows instead.", +] +hotspot = "The arrow's tip." +look = "Arrow with a short line badge (an edge)." + +[[cursor]] +id = "LivePaintSelectOutside" +group = "Selection" +shown_when = [ + "Live Paint Selection where a click selects no face or edge: away from Live Paint groups, over a group that is locked, hidden or outside the isolated art, inside a face with Alt held and no edge within reach, or with Select Fills and Select Strokes both off.", + "From a press there until the release, wherever the drag goes.", + "With Use Precise Cursors on, the crosshair shows instead.", +] +hotspot = "The arrow's tip." +look = "Arrow with a small x badge (nothing to select)." + [[cursor]] id = "Move" group = "Selection" diff --git a/crates/ui-egui/src/canvas/cursor.rs b/crates/ui-egui/src/canvas/cursor.rs index 8b368fa496..ca017bef3e 100644 --- a/crates/ui-egui/src/canvas/cursor.rs +++ b/crates/ui-egui/src/canvas/cursor.rs @@ -83,7 +83,10 @@ pub(super) fn cursor_icon(c: Cursor) -> egui::CursorIcon { | Cursor::CornerRadius | Cursor::PathBracket | Cursor::TypeWidget - | Cursor::LoadedText => C::Default, + | Cursor::LoadedText + | Cursor::LivePaintSelectFace + | Cursor::LivePaintSelectEdge + | Cursor::LivePaintSelectOutside => C::Default, Cursor::SelectionCopy => C::Copy, Cursor::Move => C::Move, Cursor::Crosshair => C::Crosshair, diff --git a/crates/ui-egui/src/cursors.rs b/crates/ui-egui/src/cursors.rs index 34156f2c41..a438a71881 100644 --- a/crates/ui-egui/src/cursors.rs +++ b/crates/ui-egui/src/cursors.rs @@ -468,6 +468,27 @@ fn paint_brush(p: &mut Ink, o: Pos2) { } } +/// Live Paint Selection: the solid arrow with a badge at its lower right saying what a click +/// selects: a filled square over a face ([`Cursor::LivePaintSelectFace`]), a short line between two +/// anchor marks over an edge ([`Cursor::LivePaintSelectEdge`]), an x where it selects nothing. +fn live_paint_select(p: &mut Ink, o: Pos2, part: Cursor) { + arrow(p, o, false); + let b = o + vec2(12.0, 13.0); + match part { + Cursor::LivePaintSelectFace => { + poly(p, [(0.0, 0.0), (6.0, 0.0), (6.0, 6.0), (0.0, 6.0)].map(|(x, y)| b + vec2(x, y)).to_vec(), INK, INK); + } + Cursor::LivePaintSelectEdge => { + let (a, c) = (b + vec2(0.0, 7.0), b + vec2(7.0, 0.0)); + strokes(p, &[vec![a, c]]); + for m in [a, c] { + poly(p, [(-1.5, -1.5), (1.5, -1.5), (1.5, 1.5), (-1.5, 1.5)].map(|(x, y)| m + vec2(x, y)).to_vec(), INK, INK); + } + } + _ => strokes(p, &[vec![b, b + vec2(6.0, 6.0)], vec![b + vec2(6.0, 0.0), b + vec2(0.0, 6.0)]]), + } +} + /// The Hand tool and Space: an open hand (palm and four fingers, thumb to the left), or the same /// hand closed while it drags the view. Windows has no system hand that isn't a pointing finger /// (winit maps egui's Grab and Grabbing to the four-headed move cursor), so it is drawn like the @@ -516,7 +537,7 @@ fn magnifier(p: &mut Ink, o: Pos2, out: bool) { /// Every cursor with a glyph: its own, drawn here, or the one it borrows. #[cfg(test)] -const GLYPHS: [Cursor; 95] = [ +const GLYPHS: [Cursor; 98] = [ Cursor::Hand, Cursor::HandGrab, Cursor::ZoomIn, @@ -586,6 +607,9 @@ const GLYPHS: [Cursor; 95] = [ Cursor::LivePaintBucket, Cursor::LivePaintBucketFace, Cursor::LivePaintBucketEdge, + Cursor::LivePaintSelectFace, + Cursor::LivePaintSelectEdge, + Cursor::LivePaintSelectOutside, Cursor::Measure, Cursor::PrintTiling, Cursor::SymbolSprayer, @@ -710,6 +734,7 @@ fn glyph(c: Cursor, p: Pos2) -> Option> { Cursor::LivePaintBucket => paint_bucket(ink, p, false), Cursor::LivePaintBucketFace => paint_bucket(ink, p, true), Cursor::LivePaintBucketEdge => paint_brush(ink, p), + Cursor::LivePaintSelectFace | Cursor::LivePaintSelectEdge | Cursor::LivePaintSelectOutside => live_paint_select(ink, p, c), Cursor::Hand => hand(ink, p, false), Cursor::HandGrab => hand(ink, p, true), Cursor::ZoomIn => magnifier(ink, p, false), diff --git a/crates/ui-egui/src/cursors/tests.rs b/crates/ui-egui/src/cursors/tests.rs index 4a12ff91b6..ae1947616f 100644 --- a/crates/ui-egui/src/cursors/tests.rs +++ b/crates/ui-egui/src/cursors/tests.rs @@ -331,6 +331,45 @@ fn the_live_paint_bucket_shows_a_bucket_or_a_brush() { } } +/// Live Paint Selection's cursors are the solid arrow, its tip on the hotspot, with a badge at its +/// lower right: a filled square over a face, a line between two anchor marks over an edge, and an x +/// where a click selects nothing. +#[test] +fn live_paint_selection_shows_the_arrow_with_a_face_edge_or_x_badge() { + for ppp in [2.0f32, 3.0] { + let img = |c| rasterize(c, ppp).unwrap_or_else(|| panic!("{c:?} at {ppp}")); + let (arrow, cross) = (img(Cursor::Arrow), img(Cursor::Crosshair)); + let (face, edge, out) = (img(Cursor::LivePaintSelectFace), img(Cursor::LivePaintSelectEdge), img(Cursor::LivePaintSelectOutside)); + // The pixel at (dx, dy) points from the hotspot. + let at = |img: &CustomCursorImage, dx: f32, dy: f32| { + let [hx, hy] = img.hotspot.map(usize::from); + let (x, y) = ((hx as f32 + dx * ppp) as usize, (hy as f32 + dy * ppp) as usize); + if x < img.size[0] as usize && y < img.size[1] as usize { pixel(img, x, y) } else { [0; 4] } + }; + let inked = |p: [u8; 4]| p[3] > 200 && p[0] < 64; + // Inside the square, away from the x's strokes and the edge's line. + let square = (15.0, 14.0); + // The edge's upper anchor mark, beyond the square and the x. + let mark = (19.0, 13.0); + // On the x's stroke from top left to bottom right, away from the edge's line. + let x_stroke = (13.0, 14.0); + for (c, i, want) in [ + (Cursor::LivePaintSelectFace, &face, [true, false, true]), + (Cursor::LivePaintSelectEdge, &edge, [false, true, false]), + (Cursor::LivePaintSelectOutside, &out, [false, false, true]), + ] { + assert_eq!(i.hotspot, arrow.hotspot, "{c:?} has the arrow's hotspot at {ppp}"); + assert!(inked(at(i, 1.5, 6.0)), "{c:?} is the solid arrow at {ppp}"); + let got = [square, mark, x_stroke].map(|(x, y)| inked(at(i, x, y))); + assert_eq!(got, want, "{c:?} at {ppp}"); + assert!(c.stand_in().is_none(), "{c:?} is drawn"); + assert_ne!(i, &arrow, "{c:?} is not the plain arrow at {ppp}"); + assert_eq!(rasterize(c.precise(), ppp).as_ref(), Some(&cross), "{c:?} with Use Precise Cursors at {ppp}"); + } + assert!(face != edge && edge != out && face != out, "three looks at {ppp}"); + } +} + #[test] fn each_bitmap_is_made_once_so_the_os_cursor_is_kept() { let mut images = Images::default(); diff --git a/docs/mcp.md b/docs/mcp.md index 59bb3c78ad..c5186d32bc 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3338,7 +3338,9 @@ already walks Image Trace. Short agent entry points: nothing when it touches none. `selectFills` off leaves the faces out, and `selectStrokes` off leaves the edges out. A click and a marquee run their command on the release, and Escape between the press and the release removes the marquee so that the release runs nothing. The tool does not highlight or select the faces and edges of a Live Paint group that is locked, on a locked layer or on - a locked artboard, or in isolation mode outside the isolated art. + a locked artboard, or in isolation mode outside the isolated art. In the app, the tool's cursor shows what a click + at the pointer selects under these rules: the arrow with a filled square for a face, with a short line for an edge, + and with an x for nothing. It keeps the look it had at the press until the release. The Live Paint Bucket's options (`tool.setOption {tool: "livePaintBucket", values}`; double-clicking the tool opens them, dialog `livePaintBucketOptions`) are `paintFills` (on), `paintStrokes` (off), `highlight` (on), `color` (the highlight color, `#rrggbb` or a layer color's name; `#ff2a2a`) and `width` (the face highlight's width, 1 to 20 screen diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 7044797d5e..af6332309e 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -2213,6 +2213,16 @@ Edit › Copy and Cut put the selected ruler guides on the clipboard, with the s `the_live_paint_bucket_shows_a_bucket_or_a_brush`. Behavior source: [the help page](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/paint-with-the-live-paint-bucket-tool.html). The cursor swatch preview is not built. Scores and estimates unchanged. +- **Live Paint Selection cursors:** the pointer changes over a face, over an edge and outside the + Live Paint groups ([the help page](https://helpx.adobe.com/illustrator/desktop/paint-and-fill/learn-painting-basics/modify-live-paint-groups.html)). + The cursor is the arrow with a filled square where a click selects a face, with a short line + where it selects an edge, and with an x where it selects nothing. It follows Alt, Select Fills + and Select Strokes as a click does, shows nothing to select over a locked or hidden group or + outside the isolated art, keeps its look from the press to the release, and turns into the + crosshair with Use Precise Cursors. The drawings and these rules are VectorCraft's own. Tests: + `live_paint_selection_cursor_shows_what_a_click_selects`, + `live_paint_selection_shows_the_arrow_with_a_face_edge_or_x_badge`. An edge is still selected + with Alt-click while both options are on. Scores and estimates unchanged. ## Revision history