mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-07 12:13:14 +00:00
Merge pull request #1854 from TrevorBrown-dev/fix/control-arrows-pan
Control channel: ui.key arrows pan the view when nothing is selected, as the window does
This commit is contained in:
@@ -1036,7 +1036,10 @@ fn ui_key(app: &mut FigApp, ctx: &Context, p: &Value) -> Result<Value, String> {
|
||||
{
|
||||
return app.session.execute("tool.crosshair", &json!({"dx": dx, "dy": dy, "big": m.shift})).map_err(|e| e.to_string());
|
||||
}
|
||||
let (cmd, params) = if (m.cmd || m.ctrl) && m.alt {
|
||||
// Otherwise, with nothing selected the arrows pan the view, as the window's own key path does.
|
||||
let (cmd, params) = if app.session.selection().is_empty() {
|
||||
("view.pan", json!({"dx": dx, "dy": dy, "big": m.shift}))
|
||||
} else if (m.cmd || m.ctrl) && m.alt {
|
||||
// Rotate a degree, 15 with Shift: down and right clockwise, up and left back, about
|
||||
// the selection's rotation origin; nothing while points are being edited.
|
||||
if app.session.ds().is_ok_and(|d| d.vector_edit.is_some()) {
|
||||
|
||||
@@ -46,3 +46,36 @@ fn control_inspection_rejects_invalid_options_consistently() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `ui.key` with an arrow and nothing selected pans the view, as the window's own key path does
|
||||
/// (before, it answered that nothing was selected); with a selection it nudges (Shift: farther).
|
||||
#[test]
|
||||
fn control_key_arrows_pan_with_nothing_selected() {
|
||||
let mut u = Ui::new(Session::new());
|
||||
let r =
|
||||
call(&mut u, "engine.execute", json!({"command": "shape.rectangle", "params": {"x": 0, "y": 0, "width": 40, "height": 40}})).unwrap()["id"]
|
||||
.clone();
|
||||
u.app.run("select.set", json!({"ids": []}));
|
||||
u.app.run("view.set", json!({"zoom": 2.0, "cx": 0, "cy": 0}));
|
||||
let view = |u: &Ui| {
|
||||
let v = &u.app.session.ds().unwrap().view;
|
||||
(v.cx, v.cy)
|
||||
};
|
||||
call(&mut u, "ui.key", json!({"key": "ArrowRight"})).unwrap();
|
||||
assert_eq!(view(&u), (10.0, 0.0), "20 screen pixels at 200%");
|
||||
call(&mut u, "ui.key", json!({"key": "ArrowUp", "shift": true})).unwrap();
|
||||
assert_eq!(view(&u), (10.0, -100.0), "Shift: ten times as far");
|
||||
u.app.run("select.set", json!({"ids": [r]}));
|
||||
call(&mut u, "ui.key", json!({"key": "ArrowRight"})).unwrap();
|
||||
assert_eq!(view(&u), (10.0, -100.0), "with a selection the view stays");
|
||||
let id = figcraft_engine::params::id_of(&r).unwrap();
|
||||
assert_eq!(u.app.session.doc().unwrap().world_bounds(id).unwrap().x0, 1.0, "the selection moved instead");
|
||||
// With a shape tool chosen and nothing selected the arrows move the placement crosshair
|
||||
// first (#1869), and the view stays.
|
||||
u.app.run("select.set", json!({"ids": []}));
|
||||
u.app.run("tool.rectangle", json!({}));
|
||||
let before = view(&u);
|
||||
call(&mut u, "ui.key", json!({"key": "ArrowRight"})).unwrap();
|
||||
assert_eq!(view(&u), before, "the crosshair, not a pan");
|
||||
assert!(u.app.session.crosshair().is_some());
|
||||
}
|
||||
|
||||
+1
-1
@@ -181,13 +181,13 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-02 | patch | M6, #1839: 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 by the nudge amounts (`tool.crosshair`), Enter places the object there (`tool.place`), Escape or a tool change takes it away; the canvas draws the crosshair and `ui.inspect` reports it (`tool.crosshair`), the Keyboard shortcuts panel lists the gesture ("Use the arrow keys to activate the crosshair cursor...", Accessibility at Figma). The board Shapes tool places on it too (its 160 px shape centred, as the tool centres a press), and the view pans by the overshoot when the crosshair is nudged past its edge. Tests: `place_crosshair_tests`, `shortcut_keys_tests::arrows_show_and_move_the_placement_crosshair_with_a_shape_tool`. |
|
||||
| 2026-11-02 | patch | Fill section: a text whose characters were coloured differently while editing shows mixed content, as a mixed selection does ("Text nodes can have multiple sets of fills if some characters are colored differently than others", the developer documentation's `fills`); characters that all share fills the layer doesn't have (all of them styled while editing) show those instead of the layer's older list; and a layer-level fill edit (a row, its picker, +, reordering, `fill.*`, `node.set`) reaches every character, their own fills going in the same undo step. The section used to show the layer's own fill, a single colour the canvas no longer showed (#1856). Tests: `text_mixed_fill_tests`, engine `text_fill_tests`. Numbers unchanged |
|
||||
| 2026-11-06 | patch | M1, #2028: Edit ▸ Set default properties copied the selected layer's properties into the Paste properties clipboard and nothing else, so a new rectangle drawn afterwards was still grey (and what Copy properties had copied was lost). The defaults are now kept apart (`Session::default_props`, one set for shapes and one for text): rectangles, ellipses, polygons and stars drawn or clicked with their tools take the shape defaults (`apply_copied_props`, the same rules as Paste properties: corners only where a layer has them), and new text from the Text tool takes a text layer's fills and font and is laid out at that size. Frames, sections, slices, lines, arrows, vectors and layers made by commands keep their own look. `edit.setDefaultProperties` answers `{for: shapes|text, properties}`. Tests: `default_props_tests` (3) |
|
||||
| 2026-11-01 | patch | Number fields: dragging the field itself with Alt held scrubs it ("You can also hover the input field itself and hold down the modifier key: Mac: ⌥ Option Windows: Alt", Adjust alignment, rotation, position, and dimensions), with the resize cursor and the label's rates (Alt is the enabler there, so it isn't the fine rate); a click with Alt doesn't open the editor. Found by a documentation audit of the ui-parity rows. Test: `field_tests::alt_held_over_a_field_scrubs_it`. Counts unchanged |
|
||||
| 2026-11-02 | minor | M8: layer hierarchy backgrounds compute each visible row's selection root once, plus its immediate offscreen neighbors, rather than walking ancestors three times per row. Selection membership uses a set instead of a linear search. The scrolling regression keeps the internal seams square at viewport boundaries. Numbers unchanged. |
|
||||
| 2026-11-02 | minor | M8: selected layer hierarchies have a continuous background across row spacing, with rounded outer corners; nested selections stay in the same block and separate selected trees keep their own corners. Regressions cover continuous fills, the selected-row highlight above them, scrolling and the original palette's dim-text contrast. Numbers unchanged. |
|
||||
| 2026-11-06 | patch | M7, #2032: closing a tab whose file has unsaved changes, by its X, a middle click, File ▸ Close tab or its shortcut, only showed a toast ("save first, or close again with Alt to discard") and left the tab open, so closing seemed not to work. It now asks the unsaved-changes question the window asks (`windows::TabClose`): Don't Save closes the tab, Cancel or Escape keeps it, Save saves the file and then closes it (a file without a path, or saved as .fig, asks where with Save as first and stays open if that is cancelled); Alt+close still drops the changes at once, and `file.close`'s `dialog.unsaved` request asks too. The three toasts left the catalogs. Tests: `titlebar_tests::closing_a_tab_with_unsaved_changes_asks`, `titlebar_tests::save_in_the_tab_question_saves_then_closes`. Reordering tabs by dragging, the issue's other half, landed on 2026-10-19 (`dragging_a_tab_along_the_bar_reorders_it_without_detaching`) |
|
||||
| 2026-11-02 | patch | M6, #1839: 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 by the nudge amounts (`tool.crosshair`), Enter places the object there (`tool.place`), Escape or a tool change takes it away; the canvas draws the crosshair and `ui.inspect` reports it (`tool.crosshair`), the Keyboard shortcuts panel lists the gesture ("Use the arrow keys to activate the crosshair cursor...", Accessibility at Figma). The board Shapes tool places on it too (its 160 px shape centred, as the tool centres a press), and the view pans by the overshoot when the crosshair is nudged past its edge. Tests: `place_crosshair_tests`, `shortcut_keys_tests::arrows_show_and_move_the_placement_crosshair_with_a_shape_tool`. |
|
||||
| 2026-11-05 | minor | M4, #1830: a paragraph's base direction can be forced (`TextDirection`: Auto, Ltr, Rtl on `ParaList`): Text ▸ Text direction ▸ Auto / Left to right / Right to left (`text.directionAuto` / `Ltr` / `Rtl`) and `text.direction {direction, ids?}` set the edited paragraphs while editing, else the layer's; `node.set textDirection`; the Type settings control once a right-to-left script is in the text; inspect `textDirection` / `lineDirections`; CSS `direction`; `.fig` lines' `sourceDirectionality` in and out. Layout gives each paragraph its base level (`Bidi::of` runs the algorithm per paragraph, as it starts over at each). Before, direction came from the letters only ("controls are available to override text direction per paragraph", Add right-to-left text). Tests: `text_behaviour_tests::text_direction_is_forced_per_paragraph`, `fig::text_direction_tests::a_lines_source_directionality_is_its_paragraphs_direction_both_ways`. |
|
||||
| 2026-11-05 | patch | M1, #2003: the Design panel's boolean operation menu leads Union, Subtract, Intersect and Exclude with original icons drawn in code (two offset squares, the result filled at reduced alpha). Numbers unchanged. |
|
||||
| 2026-11-05 | patch | M1, #1758: a mouse's side buttons (4 and 5) pan the canvas as the middle button does, the pan ending on release over the canvas or off it; the grab cursor shows while they're held. |
|
||||
|
||||
Reference in New Issue
Block a user