diff --git a/crates/ui-egui/src/layers_behaviour_tests.rs b/crates/ui-egui/src/layers_behaviour_tests.rs index c56adecdf..93a80d9bd 100644 --- a/crates/ui-egui/src/layers_behaviour_tests.rs +++ b/crates/ui-egui/src/layers_behaviour_tests.rs @@ -841,3 +841,100 @@ fn search_fields_keep_their_text_in_the_middle() { let off = off_centre(&mut u, "Search assets"); assert!(off < 1.0, "Search assets is {off} points off the middle"); } + +/// The centre of `id`'s icon, a top-level row: the row's left edge is the first point from the +/// left at which the pointer is over it, and the icon's centre is 8 (margin) + 4 + 22 points +/// further right. +fn icon_of(u: &mut Ui, id: NodeId) -> Pos2 { + let y = u.row_at(id).y; + let mut left = None; + for x in 0..150 { + let p = Pos2::new(x as f32, y); + u.app.session.ds_mut().unwrap().hover = None; + u.frame(vec![Event::PointerMoved(p)]); + if u.app.session.ds().unwrap().hover == Some(id) { + left = Some(x as f32); + break; + } + } + Pos2::new(left.expect("the row has a left edge") + 34.0, y) +} + +/// Measured in Figma's web app, 2026-11-12: a double-click on a layer's icon zooms the canvas to +/// the layer and starts no rename (a double-click on the name renames, as before). +#[test] +fn a_double_click_on_a_layers_icon_zooms_to_it_instead_of_renaming() { + let mut s = Session::new(); + rect(&mut s, "near", 0.0, None); + let far = rect(&mut s, "far", 3000.0, None); + s.execute("select.none", &json!({})).unwrap(); + let mut u = Ui::new(s); + u.frame(vec![]); + let icon = icon_of(&mut u, far); + let view = |u: &Ui| { + let v = &u.app.session.ds().unwrap().view; + (v.cx, v.cy, v.zoom) + }; + let before = view(&u); + for _ in 0..2 { + u.button(icon, true); + u.button(icon, false); + } + u.frame(vec![]); + assert_eq!(u.app.ui.rename, None, "a double-click on the icon renames nothing"); + assert_eq!(u.app.session.selection(), vec![far], "the layer is selected"); + let after = view(&u); + assert_ne!(after, before, "the view moved"); + u.app.session.execute("view.set", &json!({"cx": before.0, "cy": before.1, "zoom": before.2})).unwrap(); + u.app.session.execute("view.zoomSelection", &json!({})).unwrap(); + assert_eq!(after, view(&u), "the view frames the layer, as view.zoomSelection does"); +} + +/// A double-click's release is also a click on the row; with Ctrl held it must not toggle the layer +/// back out of the selection the zoom just made, nor can Shift restore a range. +#[test] +fn an_icon_double_click_keeps_its_selection_when_a_modifier_is_held() { + let mut s = Session::new(); + rect(&mut s, "near", 0.0, None); + let far = rect(&mut s, "far", 3000.0, None); + s.execute("select.none", &json!({})).unwrap(); + let mut u = Ui::new(s); + u.frame(vec![]); + let icon = icon_of(&mut u, far); + let before = (u.app.session.ds().unwrap().view.cx, u.app.session.ds().unwrap().view.zoom); + for _ in 0..2 { + for pressed in [true, false] { + let e = Event::PointerButton { pos: icon, button: egui::PointerButton::Primary, pressed, modifiers: CMD }; + u.frame_with(vec![Event::PointerMoved(icon), e], CMD); + } + } + u.frame(vec![]); + assert_eq!(u.app.session.selection(), vec![far], "the layer stays selected"); + assert_eq!(u.app.ui.rename, None); + let v = &u.app.session.ds().unwrap().view; + assert_ne!((v.cx, v.zoom), before, "the view frames the layer"); +} + +/// A Shift-double-click on an icon leaves just that layer selected, and it becomes the anchor: the +/// next Shift-click selects the range from it, not a toggle. +#[test] +fn an_icon_double_click_becomes_the_range_anchor() { + let (mut u, [a, b, c, d]) = four(); + u.frame(vec![]); + let a_row = u.row_at(a); + click(&mut u, a_row, Modifiers::NONE); + let icon = icon_of(&mut u, b); + for _ in 0..2 { + for pressed in [true, false] { + let e = Event::PointerButton { pos: icon, button: egui::PointerButton::Primary, pressed, modifiers: SHIFT }; + u.frame_with(vec![Event::PointerMoved(icon), e], SHIFT); + } + } + for _ in 0..30 { + u.frame(vec![]); + } + assert_eq!(u.app.session.selection(), vec![b], "the icon's layer is all that is selected"); + let d_row = u.row_at(d); + click(&mut u, d_row, SHIFT); + assert_eq!(sorted(u.app.session.selection()), sorted(vec![b, c, d]), "the range from the icon's layer"); +} diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 7179012b5..138e3ec6f 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -1241,7 +1241,8 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { let icon = node_icon(&doc, row.id); let shown = n.visible && !row.dimmed; let icon_col = row_color(&t, is_comp || icon == Icon::Slot, shown, t.icon, t.text_tertiary); - icons::paint(ui.painter(), Rect::from_center_size(pos2(x0 + 22.0, r.center().y), vec2(14.0, 14.0)), icon, icon_col); + let icon_rect = Rect::from_center_size(pos2(x0 + 22.0, r.center().y), vec2(14.0, 14.0)); + icons::paint(ui.painter(), icon_rect, icon, icon_col); let name_x = x0 + 36.0; if app.ui.rename.is_some_and(|rn| rn.id == row.id) { if let Some(new) = rename_field(app, ui, row.id, row.depth, &n.name, r) { @@ -1262,8 +1263,21 @@ fn layers(app: &mut FigApp, ui: &mut Ui) { if resp.clicked() { clicked = Some((row.id, ui.input(|i| i.modifiers))); } - if resp.double_clicked() && !crate::menus::dev_mode_refuses(app, "object.rename") { - app.ui.rename = Some(Rename { id: row.id, start: true }); + if resp.double_clicked() { + // On the icon it zooms to the layer and renames nothing (measured in Figma's web + // app, 2026-11-12); elsewhere on the row it renames. + if resp.interact_pointer_pos().is_some_and(|p| icon_rect.contains(p)) { + if sel.as_slice() != [row.id] { + app.run("select.set", json!({"ids": [row.id]})); + } + app.run("view.zoomSelection", json!({})); + // The release is also a click: it must not toggle or extend the selection. + clicked = None; + // The layer is the anchor of the next Shift-click's range. + app.ui.layer_anchor = Some(row.id); + } else if !crate::menus::dev_mode_refuses(app, "object.rename") { + app.ui.rename = Some(Rename { id: row.id, start: true }); + } } if resp.secondary_clicked() { if !sel.contains(&row.id) { diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 8eede34c4..4c6581510 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 (Tidy up keeps the order and sizes and evens the gaps, as measured in Figma's web app on 2026-11-12, rounds the gap half up, tidies overlapping rows and packs a grid along its longer axis, and is greyed out for fewer than three layers, a diagonal and an irregular layout (M8); after Crop is chosen in the image fill's colour picker, a drag on the image crops it instead of moving the layer, and no internal gesture message shows (#2307); Align is disabled for a layer in an auto layout frame's flow, as measured in Figma's web app on 2026-11-12, with a tip saying the frame places it; Select layer lists locked layers with a padlock and selects them, and shows only with more than one layer under the pointer (#1560); M8: Scale tool handle drags pivot on the opposite handle whatever the origin, and the origin starts at the centre (measured in Figma's web app, 2026-11-09); M8: a selected polygon or star has a Count handle on the canvas, on its first point clockwise from the top; Zoom to selection leaves a 40 pt margin and clears the bottom toolbar, measured in Figma's web app; M1: Paste over selection puts the pasted layers just above the selected layer; drag modifiers compared in Figma's web app (2026-11-08): Shift locks a move to the dominant axis, Alt copies, Shift keeps a resize's ratio, Alt keeps its centre, all read afresh on every move, and FigCraft matches (Ctrl/Cmd not measurable); a mirror mode (angle, or angle and length) on a sharp point turns it smooth, as in Figma (#2110); M8: Tab while renaming the last layer wraps to the first row, measured in Figma's web app on 2026-11-12; the text caret takes the text's solid fill colour (#2156); the Rotation field shows and sets the rotation on the page, in frames and groups, through node.set absoluteRotation (#2196); the eyedropper picks a magnified image's pixel under the pointer, not a blend with its neighbours (#2189); Selection colors shows from one distinct colour when a selected layer has children, and from two otherwise, measured in Figma's web app on 2026-11-09 on a frame, a group and plain rectangles; 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) +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-10 · **Change:** minor (M8: a double-click on a layer's icon zooms to it instead of renaming, measured in Figma's web app on 2026-11-12; Tidy up keeps the order and sizes and evens the gaps, as measured in Figma's web app on 2026-11-12, rounds the gap half up, tidies overlapping rows and packs a grid along its longer axis, and is greyed out for fewer than three layers, a diagonal and an irregular layout (M8); after Crop is chosen in the image fill's colour picker, a drag on the image crops it instead of moving the layer, and no internal gesture message shows (#2307); Align is disabled for a layer in an auto layout frame's flow, as measured in Figma's web app on 2026-11-12, with a tip saying the frame places it; Select layer lists locked layers with a padlock and selects them, and shows only with more than one layer under the pointer (#1560); M8: Scale tool handle drags pivot on the opposite handle whatever the origin, and the origin starts at the centre (measured in Figma's web app, 2026-11-09); M8: a selected polygon or star has a Count handle on the canvas, on its first point clockwise from the top; Zoom to selection leaves a 40 pt margin and clears the bottom toolbar, measured in Figma's web app; M1: Paste over selection puts the pasted layers just above the selected layer; drag modifiers compared in Figma's web app (2026-11-08): Shift locks a move to the dominant axis, Alt copies, Shift keeps a resize's ratio, Alt keeps its centre, all read afresh on every move, and FigCraft matches (Ctrl/Cmd not measurable); a mirror mode (angle, or angle and length) on a sharp point turns it smooth, as in Figma (#2110); M8: Tab while renaming the last layer wraps to the first row, measured in Figma's web app on 2026-11-12; the text caret takes the text's solid fill colour (#2156); the Rotation field shows and sets the rotation on the page, in frames and groups, through node.set absoluteRotation (#2196); the eyedropper picks a magnified image's pixel under the pointer, not a blend with its neighbours (#2189); Selection colors shows from one distinct colour when a selected layer has children, and from two otherwise, measured in Figma's web app on 2026-11-09 on a frame, a group and plain rectangles; 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: @@ -49,7 +49,7 @@ interaction feel only partly compared: the interactions measured so far, case by | Expand and collapse: Alt-click opens or closes everything inside; Collapse layers (Alt+L) keeps the way to the selection open | done | Alt-click pinned (the documentation is silent) | | Drag to reorder and into frames, groups and auto layout: insertion line, spring-loaded open, several layers together, Escape cancels | done | several layers dropped together land together (they split apart before); instances and a layer's own children refuse drops | | Eye and padlock: click, or drag across rows to set several in one step; layers in a locked frame show a faint padlock | done | the toggles no longer vanish under the pointer (clicks on them could miss) (a child's padlock can't be toggled while its parent is locked) | -| Rename: double click, F2 / Cmd+R, Tab to the next row; several layers open Rename layers (match, regular expressions, current name and number codes, start value, preview) | done | numbered in stacking order, pinned; Tab on the last row wraps to the first row's rename, measured in Figma's web app on 2026-11-12 (`tab_on_the_last_row_commits_and_wraps_to_the_first`); Shift+Tab on the first row ends the rename (not measured); Tab and Shift+Tab move only the rename, the selection stays on the layer first renamed, measured in Figma's web app on 2026-11-12 (`tab_while_renaming_leaves_the_selection_on_the_first_layer`) | +| Rename: double click, F2 / Cmd+R, Tab to the next row; several layers open Rename layers (match, regular expressions, current name and number codes, start value, preview) | done | numbered in stacking order, pinned; Tab on the last row wraps to the first row's rename, measured in Figma's web app on 2026-11-12 (`tab_on_the_last_row_commits_and_wraps_to_the_first`); Shift+Tab on the first row ends the rename (not measured); Tab and Shift+Tab move only the rename, the selection stays on the layer first renamed, measured in Figma's web app on 2026-11-12 (`tab_while_renaming_leaves_the_selection_on_the_first_layer`); a double-click on a row's icon zooms to the layer (selecting it first) and renames nothing, while a double-click on the name or elsewhere on the row still renames, measured in Figma's web app on 2026-11-12 (`a_double_click_on_a_layers_icon_zooms_to_it_instead_of_renaming`) | | Find layers (filter by name) | done | not covered by the documentation | | Pages: add, rename, drag to reorder, duplicate ("Copy of"), delete, dividers (a blank page named with a hyphen) | done | the line between Pages and Layers drags to size the pages list, kept in the preferences; a long list scrolls in its own area and a page dropped outside the visible list stays put (#272) (`layers_behaviour_tests`) | | Assets panel: folders from page, frames and slash names (fold away), search, thumbnails, drag onto the canvas or into frames and auto layout, Alt-drop swap, enabled libraries | done | Shift+I (Quick insert), Alt+2 and View ▸ Panels ▸ Assets open it; the toolbar's Resources button was removed (the left rail's Assets tab and those shortcuts are the ways in) | @@ -216,6 +216,7 @@ This audit records candidates for subsequent fixes; their behaviour is unchanged | Date | Change | Summary | |---|---|---| +| 2026-11-12 | patch | M8: a double-click on a layer's icon in the Layers panel selects the layer and zooms the canvas to it (`view.zoomSelection`) and starts no rename, as measured in Figma's web app on 2026-11-12; a double-click on the name or elsewhere on the row still renames; the icon's hit area is the rect the row paints the icon in (`a_double_click_on_a_layers_icon_zooms_to_it_instead_of_renaming`), Rename row | | 2026-11-12 | minor | M8: Tidy up keeps the order and sizes and evens the gaps, as measured in Figma's web app on 2026-11-12. A row or column evens one gap (the most common one, the smaller of a tie, else the mean rounded half up) and keeps the other axis, layers of a row may overlap along X (also in a chain, and a negative gap is raised so no layer reaches the one two before it, measured in Figma's web app on 2026-11-13), and a grid is packed along the axis with more groups; fewer than three layers, layers that all overlap, a diagonal and an irregular layout grey the row out and move nothing. Measured: those rules and the unavailable cases; FigCraft's choices: a 2 x 2 grid as columns, auto layout flow children, the 2,000 layer cap, rotated layers by their box, `spacing` with two layers, and no rounding with pixel snapping off. Tests: tidy_tests (a_row_keeps_its_order_sizes_and_y_and_evens_the_gaps, gaps_that_all_differ_take_their_mean_taken_down, a_mean_is_rounded_half_up, two_equally_common_gaps_give_the_smaller, an_overlapping_pair_in_a_row_is_tidied_and_its_negative_gap_counts, a_grid_is_tidied_by_columns, a_transposed_grid_is_tidied_by_rows, layers_on_a_diagonal_are_unavailable, an_irregular_selection_is_unavailable and the rest). | | 2026-11-13 | patch | M8, #2307: with Crop chosen in an image fill's colour picker, a press and drag on the image moved the whole layer: the press closed the picker, which committed the crop as a plain move. The picker now treats a crop drag as its own control (as it does the open gradient's handles, #1852), so the drag moves the picture inside the layer. A drag whose gesture something else committed under the held button no longer shows the engine's internal "no gesture in progress" as a message (`EngineError::NoGesture`, dropped by the canvas). Tests: `crop_image_tests::crop_from_the_colour_picker_then_a_drag_on_the_image_moves_the_crop_not_the_layer`, `a_drag_whose_gesture_was_committed_under_the_button_shows_no_message`. | | 2026-11-12 | minor | M8: Align is unavailable for the one selected layer of an auto layout frame that takes part in its flow, as in Figma (measured in Figma's web app on 2026-11-12: the Design panel's Align buttons are greyed out, Alt+A and Alt+S do nothing and the frame's alignment doesn't change). `align_target` returns none for it, so `can_align` is false and the `align.*` commands leave the layout alone; the disabled tip reads "A layer in an auto layout frame is placed by its frame" (`align_blocked_by_auto_layout`). An absolutely positioned child still aligns to its frame and multi-layer selections are unchanged (neither measured). Single and multi-layer Align and Distribute otherwise matched Figma; Tidy up differs (Figma keeps the order and sizes and evens one gap per axis) and is not yet changed. Tests: `align_is_unavailable_for_a_child_in_an_auto_layout_flow`, `an_absolute_child_of_an_auto_layout_frame_still_aligns_to_it`, `the_align_buttons_are_off_for_a_layer_in_an_auto_layout_flow` |