diff --git a/crates/ui-egui/src/canvas/cursors.rs b/crates/ui-egui/src/canvas/cursors.rs index dec2a1b31..d518dde37 100644 --- a/crates/ui-egui/src/canvas/cursors.rs +++ b/crates/ui-egui/src/canvas/cursors.rs @@ -1,7 +1,7 @@ //! The pointer on the canvas: each engine cursor ([`figcraft_engine::tools::Cursor`]) as one of //! the system's cursors, or as a small glyph FigCraft draws at the pointer with the system's //! hidden (a rotate cursor turned to its corner, the pen's states, the eyedropper, the comment -//! pin). The glyphs are original, drawn from lines and the code-drawn icons: no cursor images. +//! pin, the crosshair of the shape and Text tools). The glyphs are original, drawn from lines and the code-drawn icons: no cursor images. use egui::{Color32, CursorIcon, Painter, Pos2, Shape, Stroke, pos2, vec2}; use figcraft_engine::tools::{Cursor, PenCursor}; @@ -17,6 +17,9 @@ pub enum Glyph { Pen(PenCursor), Eyedropper, Comment, + /// A plus open at the centre, for the shape and Text tools (the system's follows the + /// system's pointer colour, and a white one is lost on a white frame). + Crosshair, } /// What the pointer shows. @@ -46,7 +49,7 @@ pub fn of(cursor: Cursor) -> Shown { use Shown::{Drawn, System}; match cursor { Cursor::Default => System(CursorIcon::Default), - Cursor::Crosshair => System(CursorIcon::Crosshair), + Cursor::Crosshair => Drawn(Glyph::Crosshair), Cursor::Move => System(CursorIcon::Move), Cursor::Resize(a) if a.is_finite() => System(resize_icon(a)), Cursor::Resize(_) => System(CursorIcon::Default), @@ -85,7 +88,7 @@ impl Shown { /// A name for it (`ui.inspect`): the CSS cursor names where there is one ("default", /// "nwse-resize", "copy"), and "rotate" (with its angle), "pen", "pen-close", "pen-add", - /// "pen-bend", "eyedropper" and "comment" for the drawn ones. + /// "pen-bend", "eyedropper", "comment" and "crosshair" for the drawn ones. pub fn name(self) -> String { match self { Shown::System(i) => match i { @@ -114,6 +117,7 @@ impl Shown { Shown::Drawn(Glyph::Pen(PenCursor::Bend)) => "pen-bend".into(), Shown::Drawn(Glyph::Eyedropper) => "eyedropper".into(), Shown::Drawn(Glyph::Comment) => "comment".into(), + Shown::Drawn(Glyph::Crosshair) => "crosshair".into(), } } @@ -127,7 +131,7 @@ impl Shown { } /// Dark lines over a light edge: readable on any page and artwork. -const INK: Color32 = Color32::from_rgb(0x1E, 0x1E, 0x1E); +pub(crate) const INK: Color32 = Color32::from_rgb(0x1E, 0x1E, 0x1E); const EDGE: Color32 = Color32::WHITE; /// A line through `pts` with the light edge under it. @@ -167,6 +171,22 @@ pub fn paint(painter: &Painter, at: Pos2, glyph: Glyph) { line(painter, &[tip - t * 4.5 + n * 3.5, tip + t * 1.0, tip - t * 4.5 - n * 3.5]); } } + Glyph::Crosshair => { + // A plus centred on the pointer, open at the centre so its pixel stays visible; the + // light edge goes under both arms of each line first, then the dark line on top. + let arms = [ + (vec2(-8.0, 0.0), vec2(-2.0, 0.0)), + (vec2(2.0, 0.0), vec2(8.0, 0.0)), + (vec2(0.0, -8.0), vec2(0.0, -2.0)), + (vec2(0.0, 2.0), vec2(0.0, 8.0)), + ]; + for (a, b) in arms { + painter.add(Shape::line_segment([at + a, at + b], Stroke::new(3.0, EDGE))); + } + for (a, b) in arms { + painter.add(Shape::line_segment([at + a, at + b], Stroke::new(1.0, INK))); + } + } Glyph::Pen(state) => { // A cross open at the centre (the point it lands on stays visible), and what a click // does at its lower right. diff --git a/crates/ui-egui/src/cursors_tests.rs b/crates/ui-egui/src/cursors_tests.rs index 94109dc22..bb24a2469 100644 --- a/crates/ui-egui/src/cursors_tests.rs +++ b/crates/ui-egui/src/cursors_tests.rs @@ -22,7 +22,7 @@ fn every_engine_cursor_has_a_system_cursor_or_a_glyph() { // (engine cursor, what shows, its name in `ui.inspect`) let rows: Vec<(Cursor, Shown, &str)> = vec![ (Cursor::Default, System(CursorIcon::Default), "default"), - (Cursor::Crosshair, System(CursorIcon::Crosshair), "crosshair"), + (Cursor::Crosshair, Drawn(Glyph::Crosshair), "crosshair"), (Cursor::Move, System(CursorIcon::Move), "move"), (Cursor::Resize(0.0), System(CursorIcon::ResizeHorizontal), "ew-resize"), (Cursor::Resize(90.0), System(CursorIcon::ResizeVertical), "ns-resize"), @@ -59,6 +59,44 @@ fn every_engine_cursor_has_a_system_cursor_or_a_glyph() { assert_eq!(of(Cursor::Rotate(f64::INFINITY)), Drawn(Glyph::Rotate(0.0))); } +#[test] +fn the_crosshair_is_drawn_dark_with_a_white_edge_so_it_shows_on_white() { + use egui::epaint::Shape; + let shown = of(Cursor::Crosshair); + assert_eq!(shown, Shown::Drawn(Glyph::Crosshair)); + // The system's pointer is hidden under it, whatever its colour. + assert_eq!(shown.icon(), CursorIcon::None); + let at = pos2(50.0, 40.0); + let ctx = egui::Context::default(); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| { + let ctx = ui.ctx().clone(); + let layer = egui::LayerId::new(egui::Order::Tooltip, egui::Id::unique("crosshair_test")); + crate::cursors::paint(&ctx.layer_painter(layer), at, Glyph::Crosshair); + }); + out.textures_delta.clear(); + let mut strokes = Vec::new(); + for clipped in &out.shapes { + if let Shape::LineSegment { points, stroke } = &clipped.shape { + strokes.push((points.to_vec(), stroke.color, stroke.width)); + } + } + let dark: Vec<_> = strokes.iter().filter(|s| s.1 == crate::cursors::INK).collect(); + let white: Vec<_> = strokes.iter().filter(|s| s.1 == egui::Color32::WHITE).collect(); + assert!(!dark.is_empty() && !white.is_empty(), "{strokes:?}"); + // The white edge is wider than the dark line and is painted first. + assert!(white.iter().all(|w| w.2 > dark[0].2), "{strokes:?}"); + assert_eq!(strokes[0].1, egui::Color32::WHITE); + // The arms are centred on the pointer and leave its pixel clear. + for (pts, ..) in &dark { + for p in pts { + let d = (p.x - at.x).abs().max((p.y - at.y).abs()); + assert!((2.0..=8.0).contains(&d), "{p:?}"); + assert!((p.x - at.x).abs() < 0.01 || (p.y - at.y).abs() < 0.01, "{p:?}"); + } + } + assert_eq!(dark.len(), 4); +} + #[test] fn resize_arrows_follow_the_angle_in_eighths() { // (degrees, the two-headed arrow): each covers 45° about its own direction, any turn. @@ -134,10 +172,10 @@ fn the_window_asks_for_the_cursor_of_the_tool_under_the_pointer() { ("move", edge, &[], no, CursorIcon::ResizeHorizontal, "ew-resize"), ("move", on, &[], alt, CursorIcon::Copy, "copy"), ("hand", empty, &[], no, CursorIcon::Grab, "grab"), - ("rectangle", empty, &[], no, CursorIcon::Crosshair, "crosshair"), - ("frame", on, &[], no, CursorIcon::Crosshair, "crosshair"), - ("slice", empty, &[], no, CursorIcon::Crosshair, "crosshair"), - ("text", empty, &[], no, CursorIcon::Crosshair, "crosshair"), + ("rectangle", empty, &[], no, CursorIcon::None, "crosshair"), + ("frame", on, &[], no, CursorIcon::None, "crosshair"), + ("slice", empty, &[], no, CursorIcon::None, "crosshair"), + ("text", empty, &[], no, CursorIcon::None, "crosshair"), ("pen", empty, &[], no, CursorIcon::None, "pen"), ("comment", empty, &[], no, CursorIcon::None, "comment"), ("move", empty, &[Key::Space], no, CursorIcon::Grab, "grab"), diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 382c242ff..a9883b4d3 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI and interaction parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (a boolean group takes a corner radius (and smoothing) that rounds every sharp corner of its result (#2222); the Point section's X and Y in vector edit mode show Mixed and set every selected point of the layer being edited, as in Figma; Preferences ▸ Invert zoom direction reverses wheel zoom, pinch unchanged (#2145); Shift while rotating lands on absolute multiples of 15°, measured in Figma; Flatten bakes a vector's point corner radius into its path (#2195); a click on a fill, stroke or effect row's grip or background highlights it, Cmd/Ctrl+C copies that paint or effect and Cmd/Ctrl+V pastes it onto the selected layers, as the help center's Copy and paste properties between layers says (#1494); wrap style per paragraph and in text styles (#1827); the Annotation tool (Shift+T, `tool.annotation {id?}`): with a layer selected (or an `id`) it asks the UI for that layer's note at once, and in the tool a press on a layer selects it and asks for its note, a press on empty canvas does nothing; the marquee follows Figma's measured rules: a partly covered frame or section gives the layers in it the marquee touches, a rotated shape is touched by its outline, a drag from inside a frame selects that frame only when the marquee also takes another top-level frame (then whole) (`marquee_behaviour_tests`); copy, cut and paste in vector edit mode act on the selected points and segments (#1875); M8, #2146: K focuses the Scale tool's factor field with its text selected, so K, 2, Enter scales the selection by two about the panel's origin (FigCraft's starts at the top left; Figma's grid starts at the centre, left for a follow-up because the origin also sets where a handle drag pivots, which is not measured) in one undo step, after which the tool returns to Move with the selection kept; Escape in the field closes it with the tool back on Move and nothing changed (measured in Figma's web app, 2026-11-09; `pressing_k_focuses_the_scale_field_with_its_text_selected`, `k_two_enter_scales_the_selection_by_two`, `escape_in_the_scale_field_closes_it_and_changes_nothing`); the main menu and its submenus stay inside the window, moving up to fit and scrolling when taller (#2217); locked layers ignore the arrow keys and a locked parent's children can't be locked on their own (#1560); the left rail's and tab strip's icons are larger, 20 pt (#553); pick select: Tab while the keyboard selection box shows (or the chord again) puts a pink outline on a layer and the keys walk the layer tree from it, as Accessibility at Figma says (#1836); scrubbing a field faster or slower by the pointer's height, with a notice of the speed, as the help center's Scrub fields says (#1837); component rows in the Layers panel at AA contrast in the theme's component tint, with the rows inside a selected component in a tone of its row (#1903); Cmd/Ctrl-click on a padding field sets all four sides with the value typed, a documented auto layout shortcut; a fill per region of a vector: with regions selected in edit mode, the Fill section and the fill commands edit those regions' own fills, as Edit vector layers says (#1875); the Fill section of a text whose characters were coloured differently while editing shows mixed content, characters that all share fills the layer doesn't have show those, and a layer-level fill edit reaches every character (#1856); a number field scrubs when dragged with Alt held, as the help center's scrub section says, not only by its label; reuse visible layer selection roots (M8); ; with a shape or frame tool chosen and nothing selected, the arrows show a placement crosshair in the middle of the view and move it, and Enter places there, as Accessibility at Figma says (#1839); text direction per paragraph: Text ▸ Text direction forces a paragraph left to right or right to left, as Add right-to-left text says (#1830), with `text.direction`, `node.set textDirection`, the Type settings control once a right-to-left script is in the text, inspect, the CSS `direction` and the `.fig` lines' `sourceDirectionality`; the boolean operation menu in the Design panel leads each entry with an original icon drawn in code, a filled preview of the result (#2003); a press on the open gradient's handles or stops on the canvas keeps the colour picker open through the drag, as its own control (#1852); a click with the Bend tool on a sharp point curves it, its handles along the line between its neighbours, as Edit vector layers says (#1875); wrap style: Auto, Balance and Pretty on a text layer decide where its lines break, as Explore text properties says (#1827), with the Type settings control, `text.wrapStyle`, `node.set wrapStyle`, inspect and the CSS `text-wrap`; Backspace or Delete at the start of a list item keeps the item's indentation level, as the lists article says; max lines on auto-width text keep it auto width, hugging that many lines, as the text properties article says; superscript and subscript are synthesised where the font has no glyphs for them, from the standard glyphs shrunk and shifted by the font's OS/2 metrics, and a run mixing available and unavailable glyphs is synthesised whole, as Explore text properties says (#1828); Alt+Enter while editing text opens the link at the caret, as the accessibility page documents; Minimize, by its shortcut or the button by the file name, collapses the navigation bar and both sidebars as the help center says, with the right sidebar back while something is selected (#1835); the interface size keys of the help center's accessibility page: Cmd/Ctrl+Shift+Alt with =, - and 0 step the interface through the Preferences sizes and back to the system's size; Cmd/Ctrl+Alt+arrows rotate the selection a degree at a time, 15 with Shift, as the help center's accessibility page says; they resized before; the Layers panel answers Ctrl+1 as well as Alt+1 on Windows and Linux, the two keys the help center gives it; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the keyboard's Menu key opens the context menu, as Shift+F10 does; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; sidebar number and text fields keep their text vertically centered when focused, including typography spacing; with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); colour picker review fixes for text undo, fitted sidebars, moving controls and gradient variables; Corner smoothing: an original icon and the shared number-field drag gesture, preserving fractional baselines; guides snap to layers when dragged; Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); the Design panel's sections on one grid — 16 / 8 margins, 88 + 8 + 88 + 8 + 24 columns, 24 pt rows, 4 pt between a row's buttons (#858); the toolbar on one 32 pt grid, without its Resources button (#1210); colour picker LCH and OKLCH models; an XML (Android Vector Drawable) export format; Context menus: the Menu key reaches egui 0.37 but is unbound (row done → partial); the Text on a path tool; board tool keys pinned; community fixes: the text box by dragging, the font picker keys, the pen path on leaving Design, a still canvas in vector edit mode, a scrolling pages list, colour emoji, readable new text in frames, system theme on first run; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; a rename selects the whole name, so typing replaces it; Escape or Enter after a pen path stays on the same vector (#760); IME composition kept on through idle frames of a text edit, candidates at the caret (#747); Selection colors for mixed selections and smart-selection spacing (#753); submenus with a hover delay and a safe triangle; the Actions palette's tabs, ranked search, highlight and recents; menu items that ask for a value; editor fixes from the community batches) · **Target:** Figma Design UI3 (desktop app 126.10.7) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (the crosshair of the shape and Text tools is drawn dark with a white edge, so it shows on a white frame whatever the system's pointer style (#2198); a boolean group takes a corner radius (and smoothing) that rounds every sharp corner of its result (#2222); the Point section's X and Y in vector edit mode show Mixed and set every selected point of the layer being edited, as in Figma; Preferences ▸ Invert zoom direction reverses wheel zoom, pinch unchanged (#2145); Shift while rotating lands on absolute multiples of 15°, measured in Figma; Flatten bakes a vector's point corner radius into its path (#2195); a click on a fill, stroke or effect row's grip or background highlights it, Cmd/Ctrl+C copies that paint or effect and Cmd/Ctrl+V pastes it onto the selected layers, as the help center's Copy and paste properties between layers says (#1494); wrap style per paragraph and in text styles (#1827); the Annotation tool (Shift+T, `tool.annotation {id?}`): with a layer selected (or an `id`) it asks the UI for that layer's note at once, and in the tool a press on a layer selects it and asks for its note, a press on empty canvas does nothing; the marquee follows Figma's measured rules: a partly covered frame or section gives the layers in it the marquee touches, a rotated shape is touched by its outline, a drag from inside a frame selects that frame only when the marquee also takes another top-level frame (then whole) (`marquee_behaviour_tests`); copy, cut and paste in vector edit mode act on the selected points and segments (#1875); M8, #2146: K focuses the Scale tool's factor field with its text selected, so K, 2, Enter scales the selection by two about the panel's origin (FigCraft's starts at the top left; Figma's grid starts at the centre, left for a follow-up because the origin also sets where a handle drag pivots, which is not measured) in one undo step, after which the tool returns to Move with the selection kept; Escape in the field closes it with the tool back on Move and nothing changed (measured in Figma's web app, 2026-11-09; `pressing_k_focuses_the_scale_field_with_its_text_selected`, `k_two_enter_scales_the_selection_by_two`, `escape_in_the_scale_field_closes_it_and_changes_nothing`); the main menu and its submenus stay inside the window, moving up to fit and scrolling when taller (#2217); locked layers ignore the arrow keys and a locked parent's children can't be locked on their own (#1560); the left rail's and tab strip's icons are larger, 20 pt (#553); pick select: Tab while the keyboard selection box shows (or the chord again) puts a pink outline on a layer and the keys walk the layer tree from it, as Accessibility at Figma says (#1836); scrubbing a field faster or slower by the pointer's height, with a notice of the speed, as the help center's Scrub fields says (#1837); component rows in the Layers panel at AA contrast in the theme's component tint, with the rows inside a selected component in a tone of its row (#1903); Cmd/Ctrl-click on a padding field sets all four sides with the value typed, a documented auto layout shortcut; a fill per region of a vector: with regions selected in edit mode, the Fill section and the fill commands edit those regions' own fills, as Edit vector layers says (#1875); the Fill section of a text whose characters were coloured differently while editing shows mixed content, characters that all share fills the layer doesn't have show those, and a layer-level fill edit reaches every character (#1856); a number field scrubs when dragged with Alt held, as the help center's scrub section says, not only by its label; reuse visible layer selection roots (M8); ; with a shape or frame tool chosen and nothing selected, the arrows show a placement crosshair in the middle of the view and move it, and Enter places there, as Accessibility at Figma says (#1839); text direction per paragraph: Text ▸ Text direction forces a paragraph left to right or right to left, as Add right-to-left text says (#1830), with `text.direction`, `node.set textDirection`, the Type settings control once a right-to-left script is in the text, inspect, the CSS `direction` and the `.fig` lines' `sourceDirectionality`; the boolean operation menu in the Design panel leads each entry with an original icon drawn in code, a filled preview of the result (#2003); a press on the open gradient's handles or stops on the canvas keeps the colour picker open through the drag, as its own control (#1852); a click with the Bend tool on a sharp point curves it, its handles along the line between its neighbours, as Edit vector layers says (#1875); wrap style: Auto, Balance and Pretty on a text layer decide where its lines break, as Explore text properties says (#1827), with the Type settings control, `text.wrapStyle`, `node.set wrapStyle`, inspect and the CSS `text-wrap`; Backspace or Delete at the start of a list item keeps the item's indentation level, as the lists article says; max lines on auto-width text keep it auto width, hugging that many lines, as the text properties article says; superscript and subscript are synthesised where the font has no glyphs for them, from the standard glyphs shrunk and shifted by the font's OS/2 metrics, and a run mixing available and unavailable glyphs is synthesised whole, as Explore text properties says (#1828); Alt+Enter while editing text opens the link at the caret, as the accessibility page documents; Minimize, by its shortcut or the button by the file name, collapses the navigation bar and both sidebars as the help center says, with the right sidebar back while something is selected (#1835); the interface size keys of the help center's accessibility page: Cmd/Ctrl+Shift+Alt with =, - and 0 step the interface through the Preferences sizes and back to the system's size; Cmd/Ctrl+Alt+arrows rotate the selection a degree at a time, 15 with Shift, as the help center's accessibility page says; they resized before; the Layers panel answers Ctrl+1 as well as Alt+1 on Windows and Linux, the two keys the help center gives it; the colour picker's Image tab shows a placeholder and Upload from computer, an asset preview, and a file dropped on it replaces the image, keeping the fill mode, crop and position (#1682); the keyboard's Menu key opens the context menu, as Shift+F10 does; the eyedropper with Cmd/Ctrl+Shift makes a colour style or variable of what it picks; sidebar number and text fields keep their text vertically centered when focused, including typography spacing; with the pen out, Enter or a double-click ends the path and edits the vector with the Move tool instead of drawing on (#1430); the Actions palette offers only commands that run from it, and no second row reading like a menu item: Place image opens the file dialog (#1574); Preferences ▸ Scroll wheel zoom: a mouse wheel zooms the canvas about the pointer and Cmd/Ctrl+wheel scrolls (#1390); boolean operations refuse input past their caps, and a boolean group past them draws without its result (#1104); the selection and hover outlines of rounded rectangles and frames follow their corners (#1506); colour picker review fixes for text undo, fitted sidebars, moving controls and gradient variables; Corner smoothing: an original icon and the shared number-field drag gesture, preserving fractional baselines; guides snap to layers when dragged; Crop image: Object ▸ Crop image, the right-click menu and the image fill row enter crop mode (`image.crop`, #1464); the Design panel's sections on one grid — 16 / 8 margins, 88 + 8 + 88 + 8 + 24 columns, 24 pt rows, 4 pt between a row's buttons (#858); the toolbar on one 32 pt grid, without its Resources button (#1210); colour picker LCH and OKLCH models; an XML (Android Vector Drawable) export format; Context menus: the Menu key reaches egui 0.37 but is unbound (row done → partial); the Text on a path tool; board tool keys pinned; community fixes: the text box by dragging, the font picker keys, the pen path on leaving Design, a still canvas in vector edit mode, a scrolling pages list, colour emoji, readable new text in frames, system theme on first run; the Small caps sample in Type settings drawn from capitals; recent files as cards; vector edit mode: hovering, hatching, selecting and dragging filled regions; home screen: Templates tab, first-run templates and Getting started tips; number fields: scrub rates, arrow steps, arithmetic with brackets, relative edits of mixed values; outline mode's hidden layers and object bounds; Hide when publishing; cursors per tool and modifier; panel widths; dark pages for files made in the dark theme; drag threshold and double-clicks into text in groups; rotation origin on Alt+R and placing with Enter: no documented shortcut missing; keyboard shortcut parity suite and bindings; cursor chat and spotlight in live sessions; layers and assets panels behaviour suite and fixes; vector multi-edit and the vector edit tail; canvas navigation, guides and grids behaviour suite and fixes; shapes and vector behaviour suite and fixes; paint and effects behaviour suite and fixes; frames, groups, sections and masks behaviour suite and fixes; layers panel keyboard: F2 rename, Shift+F10 context menu, F8 box selection; selection and transform behaviour suite and fixes; Display P3: gamut warning and soft-proof (true P3 blocked upstream); several windows; boolean hit testing on the result, group drags, frame outlines and slices; outlines for the selection, mask outlines; arc fields, live boolean editing, shape builder on strokes, one-sided widths; fields select their text on focus; Typography spacing icons support scrubbing (#373); CSS colour names in hex fields; a rename selects the whole name, so typing replaces it; Escape or Enter after a pen path stays on the same vector (#760); IME composition kept on through idle frames of a text edit, candidates at the caret (#747); Selection colors for mixed selections and smart-selection spacing (#753); submenus with a hover delay and a safe triangle; the Actions palette's tabs, ranked search, highlight and recents; menu items that ask for a value; editor fixes from the community batches) · **Target:** Figma Design UI3 (desktop app 126.10.7) Fine canvas control is what Figma users feel first: handles, snapping, nudging, modifier keys, drag behaviour, numeric precision. This checklist covers the tools, gestures and panels. Status: @@ -130,7 +130,7 @@ interaction feel not yet compared. | Gamut warnings for Display P3 files | done | colour picker note; View ▸ Soft-proof sRGB stripes what sRGB can't show (the canvas itself is sRGB) | | Gradient and padding/gap handles on the canvas | done | the gradient's handles and stops show while its picker is open, and dragging one keeps the picker open (#1852) | | Hover outline, selection colours, handle sizes | done | measured from UI3 screenshots; the outline follows the layer's geometry, rounded corners (each radius and the smoothing) included, whatever the stroke alignment (#1506) | -| Cursors per tool and modifier | done | the engine names a cursor for every tool, modifier and hover target (`tool.overlay.cursor`, tabled in `cursor_tests`) and the UI shows the system's or draws its own at the pointer (`cursors.rs`, `cursors_tests`; original glyphs from lines and the code-drawn icons). Arrow for Move and Scale; two-headed arrows on the handles that follow the handle and the box's rotation; outside a corner a curved rotate arrow turned to that corner, turning with the pointer while rotating; crosshair for the frame, section, slice, shape, pencil and Text tools; the I-beam over a text with the Text tool and over the text being edited; the pen as a cross with what a click does beside it (nothing: a new point; a ring: closes, connects to or goes on from a point; a plus: adds a point on a segment, also Alt in vector edit mode; a curve: bends, the Bend tool or Cmd/Ctrl); open and closed hand for the Hand tool, Space and the middle button; zoom in and out (Alt) while Z is held; eyedropper; comment pin; the copy cursor with Alt over a selected layer and while Alt-dragging; not-allowed while a drag resizes, rotates or moves a layer inside an instance (it is undone on release) and over a layers row that takes no drop. Ruler guides and gradient, crop and padding handles keep their own cursors (the tool's used to replace them). `ui.inspect` reports `cursor`. Left: no cursor for removing a point with the pen (the pen doesn't remove points); the drawn cursors follow the pointer a frame behind the system's; not compared with Figma's side by side | +| Cursors per tool and modifier | done | the engine names a cursor for every tool, modifier and hover target (`tool.overlay.cursor`, tabled in `cursor_tests`) and the UI shows the system's or draws its own at the pointer (`cursors.rs`, `cursors_tests`; original glyphs from lines and the code-drawn icons). Arrow for Move and Scale; two-headed arrows on the handles that follow the handle and the box's rotation; outside a corner a curved rotate arrow turned to that corner, turning with the pointer while rotating; crosshair for the frame, section, slice, shape, pencil and Text tools, drawn dark with a white edge so it shows on any canvas colour whatever the system's pointer style (#2198); the I-beam over a text with the Text tool and over the text being edited; the pen as a cross with what a click does beside it (nothing: a new point; a ring: closes, connects to or goes on from a point; a plus: adds a point on a segment, also Alt in vector edit mode; a curve: bends, the Bend tool or Cmd/Ctrl); open and closed hand for the Hand tool, Space and the middle button; zoom in and out (Alt) while Z is held; eyedropper; comment pin; the copy cursor with Alt over a selected layer and while Alt-dragging; not-allowed while a drag resizes, rotates or moves a layer inside an instance (it is undone on release) and over a layers row that takes no drop. Ruler guides and gradient, crop and padding handles keep their own cursors (the tool's used to replace them). `ui.inspect` reports `cursor`. Left: no cursor for removing a point with the pen (the pen doesn't remove points); the drawn cursors follow the pointer a frame behind the system's; not compared with Figma's side by side | | Multiplayer cursors, cursor chat, spotlight | partial | in live sessions: named cursors, chat bubbles that fade, `/` to chat, spotlight from your avatar's menu with a banner; no audio or reactions | ## Keyboard shortcuts @@ -213,6 +213,7 @@ This audit records candidates for subsequent fixes; their behaviour is unchanged | Date | Change | Summary | |---|---|---| +| 2026-11-10 | patch | M1, #2198: with a shape, frame, slice or Text tool the pointer was the system's crosshair, white under some pointer styles on Windows and lost on the default white frame; it is now drawn at the pointer in dark lines with a white edge, open at the centre (`Glyph::Crosshair`), so it shows on any canvas colour. | | 2026-11-10 | minor | M4, #2222: a boolean group takes a corner radius that rounds every sharp corner of its result (measured in Figma's web app), Boolean groups row | | 2026-11-08 | minor | M8: measured in Figma on 2026-11-08, vector edit mode's Point section: X and Y show the value the selected points share, or Mixed when they differ, and a typed value sets every selected point's own coordinate in the layer being edited, points of other edited layers left as they are (it moved the group's top-left before); a relative edit (`+10`, `*2`, `/2`) typed into a Mixed field applies to each point's own value (`vector.edit.position` takes a number or `{add}`, `{mul}`, `{div}` per axis); the W and H row and other edited layers' points are unchanged. | | 2026-11-09 | patch | Preferences ▸ Invert zoom direction (#2145): Cmd/Ctrl+wheel, and a plain mouse wheel with Scroll wheel zoom on, zoom the other way; pinch and panning unchanged. Numbers unchanged. |