diff --git a/crates/engine/src/cmd/view.rs b/crates/engine/src/cmd/view.rs index 1fc2ef22a..60a5c26f1 100644 --- a/crates/engine/src/cmd/view.rs +++ b/crates/engine/src/cmd/view.rs @@ -321,6 +321,13 @@ pub fn specs() -> Vec { spec!("view.snapObjects", "Snap to objects", "{on?}", false, |s, p| toggle(s, p, "view.snapObjects")), spec!("view.snapPixel", "Snap to pixel grid", "{on?}", false, |s, p| toggle(s, p, "view.snapPixel")), spec!("view.toggleUi", "Show/Hide UI", "{on?}", false, |s, p| flag(s, p, |v| &mut v.ui_hidden)), + spec!( + "view.minimize", + "Minimize the interface", + "{on?} collapses the navigation bar and both sidebars and keeps the toolbar (Shift+Cmd/Ctrl+\\, the Minimize button by the file name); the right sidebar shows while something is selected", + false, + |s, p| flag(s, p, |v| &mut v.ui_minimized) + ), spec!("view.fullscreen", "Full screen", "{on?} the window fills the screen (the desktop app; a no-op headless)", false, |s, p| { s.request_ui("window.fullscreen", json!({"on": boolean(p, "on")})); ok() diff --git a/crates/engine/src/keymap.rs b/crates/engine/src/keymap.rs index 4f55fa4ad..cec22d62f 100644 --- a/crates/engine/src/keymap.rs +++ b/crates/engine/src/keymap.rs @@ -225,7 +225,7 @@ pub static KEYS: &[Binding] = &[ k(View, "view.prototype", &["Alt+9"]).label("Prototype panel"), k(View, "view.designPrototype", &["Shift+E"]), k(View, "view.devMode", &["Shift+D"]).label("Dev Mode on/off"), - k(View, "view.leftSidebar", &["Shift+Cmd+\\"]), + k(View, "view.minimize", &["Shift+Cmd+\\"]), k(View, "view.nextRegion", &["F6"]).win(&["F6", "Ctrl+F6"]).label("Focus the next region (toolbar, layers, canvas, properties)").both(), k(View, "view.previousRegion", &["Shift+F6"]).label("Focus the previous region").both(), k(View, "view.contextMenu", &["Shift+F10", "Menu"]).label("Context menu of the selection (the focused layer row or the canvas)"), diff --git a/crates/engine/src/shortcut_parity_tests.rs b/crates/engine/src/shortcut_parity_tests.rs index 31ce9d18e..46eb14447 100644 --- a/crates/engine/src/shortcut_parity_tests.rs +++ b/crates/engine/src/shortcut_parity_tests.rs @@ -145,7 +145,7 @@ static ROWS: &[Row] = &[ doc(View, "Outline mode", &["Shift+Cmd+O"], &[], Bound("view.outlines"), ""), doc(View, "Pixel preview", &["Ctrl+P"], &["Ctrl+Alt+P"], Bound("view.pixelPreview"), ""), doc(View, "Others' cursors in a live session", &["Alt+Cmd+\\"], &["Ctrl+Alt+\\"], Bound("view.multiplayerCursors"), "new command"), - doc(View, "Hide the side panels (minimize the interface)", &["Shift+Cmd+\\"], &[], Bound("view.leftSidebar"), ""), + doc(View, "Minimize the interface: the navigation bar and both sidebars", &["Shift+Cmd+\\"], &[], Bound("view.minimize"), "the toolbar stays; the right sidebar shows while something is selected"), doc(View, "Layers panel", &["Alt+1"], &["Alt+1", "Ctrl+1"], Bound("view.layers"), "one article writes Ctrl+1 for Windows, another Alt+1; both bound there"), doc(View, "Interface scale: make larger", &["Alt+Shift+Cmd+="], &[], Bound("pref.uiScaleUp"), "Accessibility at Figma, Adjust the scale of the Figma UI"), doc(View, "Interface scale: make smaller", &["Alt+Shift+Cmd+-"], &[], Bound("pref.uiScaleDown"), "Accessibility at Figma"), diff --git a/crates/engine/src/view_state.rs b/crates/engine/src/view_state.rs index 54102d6b7..fd70b4b11 100644 --- a/crates/engine/src/view_state.rs +++ b/crates/engine/src/view_state.rs @@ -38,6 +38,9 @@ pub struct ViewState { pub snap_objects: bool, pub snap_pixel: bool, pub ui_hidden: bool, + /// Minimized interface (Shift+Cmd/Ctrl+\): the navigation bar and both sidebars are collapsed + /// and the toolbar stays; the right sidebar shows while something is selected. + pub ui_minimized: bool, } impl Default for ViewState { @@ -67,6 +70,7 @@ impl Default for ViewState { snap_objects: true, snap_pixel: true, ui_hidden: false, + ui_minimized: false, } } } diff --git a/crates/ui-egui/locales/ar.toml b/crates/ui-egui/locales/ar.toml index 86a21e234..a3dcf0844 100644 --- a/crates/ui-egui/locales/ar.toml +++ b/crates/ui-egui/locales/ar.toml @@ -288,7 +288,6 @@ status = "draft — needs native review" "Code" = "الكود" "Code syntax" = "صياغة الكود" "Collapse" = "طي" -"Collapse panel" = "طي اللوحة" "Collection" = "مجموعة" "Collections" = "المجموعات" "Color" = "اللون" diff --git a/crates/ui-egui/locales/bn.toml b/crates/ui-egui/locales/bn.toml index 9f2e6421a..32d66721f 100644 --- a/crates/ui-egui/locales/bn.toml +++ b/crates/ui-egui/locales/bn.toml @@ -275,7 +275,6 @@ status = "draft — needs native review" "Code" = "কোড" "Code syntax" = "কোড সিনট্যাক্স" "Collapse" = "সংকুচিত করুন" -"Collapse panel" = "প্যানেল সংকুচিত করুন" "Collection" = "সংগ্রহ" "Collections" = "সংগ্রহসমূহ" "Color" = "রঙ" diff --git a/crates/ui-egui/locales/bs.toml b/crates/ui-egui/locales/bs.toml index 67e0a14cd..72f0268bd 100644 --- a/crates/ui-egui/locales/bs.toml +++ b/crates/ui-egui/locales/bs.toml @@ -288,7 +288,6 @@ status = "draft — needs native review" "Code" = "Kod" "Code syntax" = "Sintaksa koda" "Collapse" = "Skupi" -"Collapse panel" = "Skupi panel" "Collection" = "Kolekcija" "Collections" = "Kolekcije" "Color" = "Boja" diff --git a/crates/ui-egui/locales/cs.toml b/crates/ui-egui/locales/cs.toml index e77610771..16de8065a 100644 --- a/crates/ui-egui/locales/cs.toml +++ b/crates/ui-egui/locales/cs.toml @@ -265,7 +265,6 @@ status = "draft — needs native review" "Code" = "Kód" "Code syntax" = "Syntaxe kódu" "Collapse" = "Sbalit" -"Collapse panel" = "Sbalit panel" "Collection" = "Kolekce" "Collections" = "Kolekce" "Color" = "Barva" diff --git a/crates/ui-egui/locales/de.toml b/crates/ui-egui/locales/de.toml index b088b2d32..344e70912 100644 --- a/crates/ui-egui/locales/de.toml +++ b/crates/ui-egui/locales/de.toml @@ -263,7 +263,6 @@ status = "draft — needs native review" "Code" = "Code" "Code syntax" = "Code-Syntax" "Collapse" = "Einklappen" -"Collapse panel" = "Bereich einklappen" "Collection" = "Sammlung" "Collections" = "Sammlungen" "Color" = "Farbe" diff --git a/crates/ui-egui/locales/en.toml b/crates/ui-egui/locales/en.toml index 034e3381a..a23c281ba 100644 --- a/crates/ui-egui/locales/en.toml +++ b/crates/ui-egui/locales/en.toml @@ -293,7 +293,6 @@ name = "English" "Code" = "Code" "Code syntax" = "Code syntax" "Collapse" = "Collapse" -"Collapse panel" = "Collapse panel" "Collection" = "Collection" "Collections" = "Collections" "Color" = "Color" @@ -536,6 +535,7 @@ name = "English" "Exclusion" = "Exclusion" "Exit focus view (Esc)" = "Exit focus view (Esc)" "Expand" = "Expand" +"Expand the interface" = "Expand the interface" "Export" = "Export" "Export all" = "Export all" "Export frames and layers as PNG, JPG, SVG or PDF" = "Export frames and layers as PNG, JPG, SVG or PDF" diff --git a/crates/ui-egui/locales/es.toml b/crates/ui-egui/locales/es.toml index 3b1f7296e..b9e004dd0 100644 --- a/crates/ui-egui/locales/es.toml +++ b/crates/ui-egui/locales/es.toml @@ -293,7 +293,6 @@ status = "draft — needs native review" "Code" = "Código" "Code syntax" = "Sintaxis de código" "Collapse" = "Contraer" -"Collapse panel" = "Contraer panel" "Collection" = "Colección" "Collections" = "Colecciones" "Color" = "Color" diff --git a/crates/ui-egui/locales/fi.toml b/crates/ui-egui/locales/fi.toml index f181ba8a5..0c74f9de7 100644 --- a/crates/ui-egui/locales/fi.toml +++ b/crates/ui-egui/locales/fi.toml @@ -281,7 +281,6 @@ status = "draft — needs native review" "Code or invite link" = "Koodi tai kutsulinkki" "Code syntax" = "Koodin syntaksi" "Collapse" = "Tiivistä muunnokset" -"Collapse panel" = "Supista paneeli" "Collection" = "Kokoelma" "Collections" = "Kokoelmat" "Color" = "Väri" diff --git a/crates/ui-egui/locales/fr.toml b/crates/ui-egui/locales/fr.toml index 42a35007b..b30abe731 100644 --- a/crates/ui-egui/locales/fr.toml +++ b/crates/ui-egui/locales/fr.toml @@ -269,7 +269,6 @@ status = "draft — needs native review" "Code" = "Code" "Code syntax" = "Syntaxe du code" "Collapse" = "Réduire" -"Collapse panel" = "Réduire le panneau" "Collection" = "Collection" "Collections" = "Collections" "Color" = "Couleur" diff --git a/crates/ui-egui/locales/id.toml b/crates/ui-egui/locales/id.toml index a3457d820..da08f159d 100644 --- a/crates/ui-egui/locales/id.toml +++ b/crates/ui-egui/locales/id.toml @@ -281,7 +281,6 @@ status = "draft — needs native review" "Code" = "Kode" "Code syntax" = "Sintaks kode" "Collapse" = "Ciutkan" -"Collapse panel" = "Ciutkan panel" "Collection" = "Koleksi" "Collections" = "Koleksi" "Color" = "Warna" diff --git a/crates/ui-egui/locales/it.toml b/crates/ui-egui/locales/it.toml index ce54d15db..3d2877e7e 100644 --- a/crates/ui-egui/locales/it.toml +++ b/crates/ui-egui/locales/it.toml @@ -259,7 +259,6 @@ status = "draft — needs native review" "Code" = "Codice" "Code syntax" = "Sintassi del codice" "Collapse" = "Comprimi" -"Collapse panel" = "Comprimi pannello" "Collection" = "Collezione" "Collections" = "Collezioni" "Color" = "Colore" diff --git a/crates/ui-egui/locales/ja.toml b/crates/ui-egui/locales/ja.toml index 8e2334f4b..8cf2da2d5 100644 --- a/crates/ui-egui/locales/ja.toml +++ b/crates/ui-egui/locales/ja.toml @@ -270,7 +270,6 @@ status = "draft — needs native review" "Code" = "コード" "Code syntax" = "コード構文" "Collapse" = "折りたたむ" -"Collapse panel" = "パネルを折りたたむ" "Collection" = "コレクション" "Collections" = "コレクション" "Color" = "カラー" diff --git a/crates/ui-egui/locales/ka.toml b/crates/ui-egui/locales/ka.toml index 90dffe678..f634a2e7e 100644 --- a/crates/ui-egui/locales/ka.toml +++ b/crates/ui-egui/locales/ka.toml @@ -279,7 +279,6 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv "Code" = "კოდი" "Code syntax" = "კოდის სინტაქსი" "Collapse" = "ჩაკეცვა" -"Collapse panel" = "პანელის ჩაკეცვა" "Collection" = "კოლექცია" "Collections" = "კოლექციები" "Color" = "ფერი" diff --git a/crates/ui-egui/locales/ko.toml b/crates/ui-egui/locales/ko.toml index 691c1cbc7..d52310997 100644 --- a/crates/ui-egui/locales/ko.toml +++ b/crates/ui-egui/locales/ko.toml @@ -260,7 +260,6 @@ status = "draft — needs native review" "Code" = "코드" "Code syntax" = "코드 구문" "Collapse" = "접기" -"Collapse panel" = "패널 접기" "Collection" = "컬렉션" "Collections" = "컬렉션" "Color" = "색상" diff --git a/crates/ui-egui/locales/nl.toml b/crates/ui-egui/locales/nl.toml index 027b0b3a2..070ca4bb4 100644 --- a/crates/ui-egui/locales/nl.toml +++ b/crates/ui-egui/locales/nl.toml @@ -271,7 +271,6 @@ status = "draft — needs native review" "Code or invite link" = "Code of uitnodigingslink" "Code syntax" = "Code-syntaxis" "Collapse" = "Inklappen" -"Collapse panel" = "Paneel samenvouwen" "Collection" = "Verzameling" "Collections" = "Verzamelingen" "Color" = "Kleur" diff --git a/crates/ui-egui/locales/pl.toml b/crates/ui-egui/locales/pl.toml index 28406332a..05d9b60f8 100644 --- a/crates/ui-egui/locales/pl.toml +++ b/crates/ui-egui/locales/pl.toml @@ -275,7 +275,6 @@ name = "Polski" "Code" = "Kod" "Code syntax" = "Składnia kodu" "Collapse" = "Zwiń" -"Collapse panel" = "Zwiń panel" "Collection" = "Kolekcja" "Collections" = "Kolekcje" "Color" = "Kolor" diff --git a/crates/ui-egui/locales/pt-BR.toml b/crates/ui-egui/locales/pt-BR.toml index 7924ed0f8..1b00ce28c 100644 --- a/crates/ui-egui/locales/pt-BR.toml +++ b/crates/ui-egui/locales/pt-BR.toml @@ -258,7 +258,6 @@ status = "draft — needs native review" "Code" = "Código" "Code syntax" = "Sintaxe de código" "Collapse" = "Recolher" -"Collapse panel" = "Recolher painel" "Collection" = "Coleção" "Collections" = "Coleções" "Color" = "Cor" diff --git a/crates/ui-egui/locales/ru.toml b/crates/ui-egui/locales/ru.toml index 90e6c607b..a9a155818 100644 --- a/crates/ui-egui/locales/ru.toml +++ b/crates/ui-egui/locales/ru.toml @@ -272,7 +272,6 @@ name = "Русский" "Code" = "Код" "Code syntax" = "Синтаксис кода" "Collapse" = "Свернуть" -"Collapse panel" = "Свернуть панель" "Collection" = "Коллекция" "Collections" = "Коллекции" "Color" = "Цвет" diff --git a/crates/ui-egui/locales/sv.toml b/crates/ui-egui/locales/sv.toml index 1c9cf0ee2..9384ba152 100644 --- a/crates/ui-egui/locales/sv.toml +++ b/crates/ui-egui/locales/sv.toml @@ -293,7 +293,6 @@ name = "Svenska" "Code" = "Kod" "Code syntax" = "Kodsyntax" "Collapse" = "Komprimera" -"Collapse panel" = "Komprimera panel" "Collection" = "Samling" "Collections" = "Samlingar" "Color" = "Färg" diff --git a/crates/ui-egui/locales/th.toml b/crates/ui-egui/locales/th.toml index d3268bdf9..45de7b858 100644 --- a/crates/ui-egui/locales/th.toml +++ b/crates/ui-egui/locales/th.toml @@ -273,7 +273,6 @@ status = "draft — needs native review" "Code" = "โค้ด" "Code syntax" = "ไวยากรณ์โค้ด" "Collapse" = "ยุบ" -"Collapse panel" = "ยุบแผง" "Collection" = "คอลเลกชัน" "Collections" = "คอลเลกชัน" "Color" = "สี" diff --git a/crates/ui-egui/locales/tr.toml b/crates/ui-egui/locales/tr.toml index 6c80e6864..db05d1c93 100644 --- a/crates/ui-egui/locales/tr.toml +++ b/crates/ui-egui/locales/tr.toml @@ -269,7 +269,6 @@ name = "Türkçe" "Code" = "Kod" "Code syntax" = "Kod söz dizimi" "Collapse" = "Daralt" -"Collapse panel" = "Paneli daralt" "Collection" = "Koleksiyon" "Collections" = "Koleksiyonlar" "Color" = "Renk" diff --git a/crates/ui-egui/locales/vi.toml b/crates/ui-egui/locales/vi.toml index 8149ed30b..8b58fc67e 100644 --- a/crates/ui-egui/locales/vi.toml +++ b/crates/ui-egui/locales/vi.toml @@ -256,7 +256,6 @@ status = "draft — needs native review" "Code or invite link" = "Mã hoặc liên kết mời" "Code syntax" = "Cú pháp mã" "Collapse" = "Thu gọn" -"Collapse panel" = "Thu gọn bảng" "Collection" = "Bộ sưu tập" "Collections" = "Bộ sưu tập" "Color" = "Màu" diff --git a/crates/ui-egui/locales/zh-Hans.toml b/crates/ui-egui/locales/zh-Hans.toml index 2fed25ce2..a6ecb8ab3 100644 --- a/crates/ui-egui/locales/zh-Hans.toml +++ b/crates/ui-egui/locales/zh-Hans.toml @@ -261,7 +261,6 @@ status = "draft — needs native review" "Code" = "代码" "Code syntax" = "代码语法" "Collapse" = "折叠" -"Collapse panel" = "折叠面板" "Collection" = "集合" "Collections" = "集合" "Color" = "颜色" diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs index c625f342a..e13acce68 100644 --- a/crates/ui-egui/src/app.rs +++ b/crates/ui-egui/src/app.rs @@ -274,12 +274,21 @@ impl FigApp { return; } let hidden = self.session.ds().map(|d| d.view.ui_hidden).unwrap_or(false); + // Minimized (Shift+Cmd/Ctrl+\, the Minimize button by the file name): "collapses the + // navigation bar, left sidebar, and right sidebar"; "If you select an object while the + // panels are minimized, the left sidebar remains minimized while the right sidebar expands + // [...] Once all objects are deselected, the right panel minimizes again" (Explore the + // navigation bar and left sidebar). The toolbar stays; Hide UI takes everything. let narrow = !hidden && ui.available_width() < NARROW_WIDTH; + // A narrow window has no sidebars beside the canvas to take: its panels open from their + // buttons as before, and the interface is minimized again once the window is wide. + let minimized = !hidden && !narrow && self.session.ds().is_ok_and(|d| d.view.ui_minimized); + let right_minimized = minimized && self.session.selection().is_empty(); let mobile = self.ui.mobile_panel.filter(|_| narrow); self.ui.narrow = narrow; // A narrow window shows the right panel when its button asks for it, a board's too. - let right_open = (self.right_panel_shown(hidden) && !narrow) || mobile == Some(MobilePanel::Right); - if !hidden && (!narrow || mobile == Some(MobilePanel::Left)) { + let right_open = (self.right_panel_shown(hidden) && !narrow && !right_minimized) || mobile == Some(MobilePanel::Right); + if !hidden && !minimized && (!narrow || mobile == Some(MobilePanel::Left)) { egui::Panel::left("left_rail") .exact_size(layers::RAIL_WIDTH) .resizable(false) @@ -290,7 +299,7 @@ impl FigApp { }); } // The panels are as wide as asked, less in a window too small for them and the canvas. - let left_open = !hidden && if narrow { mobile == Some(MobilePanel::Left) } else { !self.ui.left_collapsed }; + let left_open = !hidden && !minimized && if narrow { mobile == Some(MobilePanel::Left) } else { !self.ui.left_collapsed }; let (left_w, right_w) = if narrow { let w = (ui.available_width() - MOBILE_STRIP).max(0.0); (if left_open { w } else { 0.0 }, w) @@ -353,6 +362,8 @@ impl FigApp { if narrow { let x = if mobile == Some(MobilePanel::Left) { ctx.content_rect().max.x - MOBILE_STRIP + 4.0 } else { ctx.content_rect().min.x + 4.0 }; self.mobile_buttons(&ctx, egui::pos2(x, rows.min + 8.0)); + } else if minimized { + self.expand_button(&ctx, egui::pos2(ctx.content_rect().min.x + 8.0, rows.min + 8.0)); } // The panels' edges, over the canvas and the panels (drawn after them: they take the press). for (name, x, shown, grows) in edges { @@ -385,8 +396,9 @@ impl FigApp { updates_ui::show(self, &ctx); self.toast_ui(&ctx); // A rename started after the layers panel drew (Cmd/Ctrl+R, a double-click) opens its field - // next frame. - if self.ui.rename.is_some_and(|r| r.start) && !hidden && !self.ui.left_collapsed && self.ui.left_tab == LeftTab::File { + // next frame; while the panel isn't drawn (the interface hidden or minimized, the panel + // closed in a narrow window) the rename waits for it without asking for frames. + if self.ui.rename.is_some_and(|r| r.start) && left_open && self.ui.left_tab == LeftTab::File { ctx.request_repaint(); } a11y::end(self, &ctx); diff --git a/crates/ui-egui/src/app/control.rs b/crates/ui-egui/src/app/control.rs index 72435673c..f63ab334a 100644 --- a/crates/ui-egui/src/app/control.rs +++ b/crates/ui-egui/src/app/control.rs @@ -102,6 +102,7 @@ impl FigApp { "importReport": self.ui.import_report.as_ref().map(import_report::Report::inspect), }); state["homeSearch"] = json!(self.ui.home_view.file_query); + state["panels"]["minimized"] = json!(self.session.ds().is_ok_and(|d| d.view.ui_minimized)); // Set apart from the literal above, which is at the macro recursion limit. state["uiScale"] = json!({"choice": self.session.prefs.ui_scale, "zoom": self.ui.ui_scale.zoom, "drawn": self.ui.ui_scale.drawn, "limited": self.ui.ui_scale.limited}); state["pendingOpens"] = json!(self.ui.pending_opens.len()); diff --git a/crates/ui-egui/src/app/frame.rs b/crates/ui-egui/src/app/frame.rs index 128cd0294..49040b7d9 100644 --- a/crates/ui-egui/src/app/frame.rs +++ b/crates/ui-egui/src/app/frame.rs @@ -247,6 +247,22 @@ impl FigApp { }); } + /// The way back from a minimized interface by mouse: a small Panels button at the top left of + /// the canvas, where the navigation bar was (the shortcut works too). + pub(super) fn expand_button(&mut self, ctx: &egui::Context, at: egui::Pos2) { + use a11y::Tip as _; + let t = theme::Tokens::get(ctx); + egui::Area::new(egui::Id::unique("expand_interface")).order(egui::Order::Foreground).fixed_pos(at).show(ctx, |ui| { + let (r, resp) = ui.allocate_exact_size(egui::vec2(32.0, 32.0), egui::Sense::click()); + let resp = resp.tip(i18n::tr("Expand the interface")); + ui.painter().rect(r, 8.0, t.panel, egui::Stroke::new(1.0, t.border), egui::StrokeKind::Inside); + icons::paint(ui.painter(), r.shrink(8.0), icons::Icon::Panels, t.icon); + if resp.clicked() { + self.run("view.minimize", serde_json::json!({})); + } + }); + } + /// Draw the toast: start its lifetime on the frame **after** the first drawn frame, request /// that frame and the frame that hides it, expire and remove it at the deadline, and place it /// at the bottom centre. diff --git a/crates/ui-egui/src/idle_tests.rs b/crates/ui-egui/src/idle_tests.rs index e2054bc84..ae34ded78 100644 --- a/crates/ui-egui/src/idle_tests.rs +++ b/crates/ui-egui/src/idle_tests.rs @@ -513,3 +513,20 @@ fn a_discarded_sizing_pass_sets_up_and_counts_one_frame_only() { assert_eq!(passes, 2, "egui reran the frame"); assert_eq!(app.frame_stats.frames, frames + 1, "one frame was drawn, not one per pass"); } + +#[test] +fn a_rename_started_while_minimized_waits_for_the_layers_panel_quietly() { + let (mut h, _) = document(); + let r = h.run("shape.rectangle", json!({"x": 500, "y": 500, "width": 40, "height": 40})); + h.run("view.minimize", json!({"on": true})); + h.settle(); + // What Cmd/Ctrl+R leaves: the field opens when the layers panel next draws, which a minimized + // interface doesn't do; no frame is asked for meanwhile. + h.app.ui.rename = Some(crate::panels::layers::Rename { id: id(&r), start: true }); + h.assert_idle("a rename started while the interface is minimized"); + h.run("view.minimize", json!({"on": false})); + for _ in 0..3 { + h.frame(vec![]); + } + assert!(h.app.ui.rename.is_some_and(|r| !r.start), "the field opened once the interface expanded"); +} diff --git a/crates/ui-egui/src/panel_width_tests.rs b/crates/ui-egui/src/panel_width_tests.rs index 6f3eacf7e..357b13669 100644 --- a/crates/ui-egui/src/panel_width_tests.rs +++ b/crates/ui-egui/src/panel_width_tests.rs @@ -160,7 +160,7 @@ fn the_command_sets_and_resets_the_widths() { u.app.run("view.panelWidths", json!({"left": 320, "right": 400})); u.frame(vec![]); assert_eq!(widths(&u), (320.0, 400.0)); - assert_eq!(u.app.inspect()["panels"], json!({"left": 320.0, "right": 400.0, "leftCollapsed": false})); + assert_eq!(u.app.inspect()["panels"], json!({"left": 320.0, "right": 400.0, "leftCollapsed": false, "minimized": false})); u.app.run("view.panelWidths", json!({"left": 5000})); assert_eq!(widths(&u), (*LEFT_PANEL.end(), 400.0), "within its limits; the other panel as it was"); for junk in [json!({"left": "wide"}), json!({"left": null, "right": null}), json!({})] { @@ -210,6 +210,58 @@ fn minimize_ui_hides_the_panels_and_keeps_their_widths() { assert_eq!(edges(&u), (56.0, shown.1)); } +/// The middle of the accessible control with `role` and `label`, if one shows. +fn control(u: &mut Ui, role: egui::accesskit::Role, label: &str) -> Option { + u.ctx.enable_accesskit(); + u.frame(vec![]); + let out = u.frame_output(vec![]); + let nodes = out.platform_output.accesskit_update.expect("an AccessKit update").nodes; + let n = nodes.iter().map(|(_, n)| n).find(|n| n.role() == role && n.label() == Some(label))?; + let b = n.bounds()?; + Some(pos2(((b.x0 + b.x1) / 2.0) as f32, ((b.y0 + b.y1) / 2.0) as f32)) +} + +/// "You can click Minimize next to the file name to collapse the navigation bar, left sidebar, +/// and right sidebar" (Explore the navigation bar and left sidebar; Shift+Cmd/Ctrl+\ too): the +/// rail and both sidebars go, the toolbar and the widths stay. "If you select an object while +/// the panels are minimized, the left sidebar remains minimized while the right sidebar expands +/// [...] Once all objects are deselected, the right panel minimizes again." The Expand button at +/// the top left puts everything back (#1835). +#[test] +fn minimize_collapses_the_rail_and_both_sidebars_and_the_right_one_follows_the_selection() { + use egui::accesskit::Role; + let (mut u, _) = ui(); + u.app.run("view.panelWidths", json!({"left": 320, "right": 400})); + u.app.run("select.set", json!({"ids": []})); + u.frame(vec![]); + let shown = edges(&u); + let minimize = control(&mut u, Role::Button, "Minimize").expect("the Minimize button by the file name"); + u.button(minimize, true); + u.button(minimize, false); + u.frame(vec![]); + assert_eq!(edges(&u), (0.0, 1440.0), "no rail and no sidebars: the canvas has the window"); + assert!(control(&mut u, Role::Toolbar, "Tools").is_some(), "the toolbar stays"); + assert_eq!(widths(&u), (320.0, 400.0), "the widths are kept"); + // A selection brings the right sidebar, the left stays away; deselecting takes it again. + u.app.run("select.all", json!({})); + u.frame(vec![]); + assert!(edges(&u).0 == 0.0 && edges(&u).1 < 1440.0, "the right sidebar shows for the selection: {:?}", edges(&u)); + u.app.run("select.set", json!({"ids": []})); + u.frame(vec![]); + assert_eq!(edges(&u), (0.0, 1440.0), "and goes once nothing is selected"); + // Expand puts the interface back as it was. + let expand = control(&mut u, Role::Button, "Expand the interface").expect("the Expand button at the top left"); + u.button(expand, true); + u.button(expand, false); + u.frame(vec![]); + assert_eq!((edges(&u), widths(&u)), (shown, (320.0, 400.0)), "back as it was"); + assert!(control(&mut u, Role::Button, "Expand the interface").is_none()); + // Hide UI hides the toolbar as well, as before. + u.app.run("view.toggleUi", json!({})); + assert!(control(&mut u, Role::Toolbar, "Tools").is_none(), "Hide UI takes the toolbar too"); + u.app.run("view.toggleUi", json!({})); +} + #[test] fn the_smallest_window_keeps_its_canvas_whatever_the_widths() { for asked in [(240.0, 240.0), (480.0, 480.0), (480.0, 240.0), (200.0, 480.0)] { @@ -231,6 +283,37 @@ fn the_smallest_window_keeps_its_canvas_whatever_the_widths() { } } +/// Minimize takes nothing from a phone-sized window, which has no sidebars beside the canvas: its +/// panels open from their buttons as before, and the interface is minimized again once the window +/// is wide. +#[test] +fn a_narrow_window_keeps_its_panel_buttons_while_minimized() { + use crate::MobilePanel; + let ctx = egui::Context::default(); + let mut app = FigApp::new(Session::new(), Box::new(NoServices)); + let mut k = 0.0; + let mut canvas_width = |app: &mut FigApp, size: egui::Vec2| { + for _ in 0..4 { + k += 1.0 / 60.0; + let input = RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, size)), time: Some(k), ..Default::default() }; + ctx.run_ui(input, |ui| app.ui(ui)).textures_delta.clear(); + } + app.canvas.rect.width() + }; + let phone = vec2(390.0, 844.0); + app.run("view.minimize", json!({"on": true})); + assert!(canvas_width(&mut app, phone) >= 380.0, "closed panels leave the whole width"); + app.ui.mobile_panel = Some(MobilePanel::Left); + let w = canvas_width(&mut app, phone); + assert!((1.0..=60.0).contains(&w), "the Layers button opens the left panel while minimized: {w}"); + app.ui.mobile_panel = None; + let wide = canvas_width(&mut app, vec2(1440.0, 900.0)); + assert!(wide >= 1439.0, "wide again, the interface is minimized: the canvas has the window ({wide})"); + app.run("view.minimize", json!({"on": false})); + let wide = canvas_width(&mut app, vec2(1440.0, 900.0)); + assert!(wide > 600.0 && wide < 1100.0, "expanded, both panels are beside the canvas again ({wide})"); +} + /// A phone-sized window: the canvas has the whole width, and each side panel opens alone over it, /// leaving the strip with the panel buttons. A wide window ignores the choice. #[test] diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index feeed46e6..164ec7e4a 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -159,13 +159,15 @@ pub fn left_panel(app: &mut FigApp, ui: &mut Ui) { ui.painter().text(pos2(head.min.x + 16.0, head.min.y + 46.0), Align2::LEFT_CENTER, tr("Drafts"), regular(11.0), t.text_secondary); } let hr = Rect::from_center_size(pos2(head.max.x - 24.0, head.min.y + 24.0), vec2(24.0, 24.0)); - let hresp = ui.interact(hr, ui.scope_id().with("hideui"), Sense::click()).tip(tr("Collapse panel")); + // Minimize, by the file name: "click Minimize next to the file name to collapse the + // navigation bar, left sidebar, and right sidebar" (Explore the navigation bar and left sidebar). + let hresp = ui.interact(hr, ui.scope_id().with("hideui"), Sense::click()).tip(tr("Minimize")); if hresp.hovered() { ui.painter().rect_filled(hr, CornerRadius::same(5), t.hover); } icons::paint(ui.painter(), hr.shrink(5.0), Icon::Panels, t.icon); if hresp.clicked() { - app.ui.close_left_panel(); + app.run("view.minimize", json!({})); } ui.painter().line_segment([head.left_bottom(), head.right_bottom()], Stroke::new(1.0, t.border)); match app.ui.left_tab { diff --git a/crates/ui-egui/src/shortcuts_tests.rs b/crates/ui-egui/src/shortcuts_tests.rs index 5c1e06fab..4b223b211 100644 --- a/crates/ui-egui/src/shortcuts_tests.rs +++ b/crates/ui-egui/src/shortcuts_tests.rs @@ -124,8 +124,12 @@ fn panel_keys_and_cmd_k() { press(&mut u, Key::D, Modifiers::SHIFT); assert_eq!(u.app.ui.right_tab, RightTab::Design); let collapsed = u.app.ui.left_collapsed; + let minimized = |u: &Ui| u.app.session.ds().unwrap().view.ui_minimized; press(&mut u, Key::Backslash, Modifiers::SHIFT | Modifiers::COMMAND); - assert_eq!(u.app.ui.left_collapsed, !collapsed, "Shift+Cmd+\\ toggles the left sidebar"); + assert!(minimized(&u), "Shift+Cmd+\\ minimizes the interface"); + assert_eq!(u.app.ui.left_collapsed, collapsed, "without collapsing the left sidebar for good"); + press(&mut u, Key::Backslash, Modifiers::SHIFT | Modifiers::COMMAND); + assert!(!minimized(&u), "and back"); press(&mut u, Key::K, Modifiers::COMMAND); assert!(u.app.ui.palette.is_some(), "Cmd+K opens Actions on the canvas"); } diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 7a416f2d6..1d3c01373 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-02 · **Change:** patch (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; 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-02 · **Change:** patch (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: @@ -17,7 +17,7 @@ interaction feel not yet compared. |---|---|---| | UI3 layout: tab strip as title bar, icon rail, file header, pages + layers, floating bottom toolbar with flyouts, Design / Prototype / Inspect panels | done | frameless window on Windows/Linux with drawn buttons; on a Wayland compositor that offers no server-side decorations (GNOME) the window gets winit's Adwaita controls instead (#592; checked by its author on GNOME, KDE not run) | | Light and dark themes | done | colours from `theme::Tokens`; a file or a page made while the dark theme shows gets a `#1E1E1E` page (`#F5F5F5` in the light theme), its own from then on: changing the theme repaints no page and opened files keep theirs; the file the app opens with takes the theme once it is known; the CLI, MCP and exports have no theme and keep `#F5F5F5`; text drawn with the Text tool is white on a dark page and black on a light one (so is text a command makes in the app without naming a fill; headless sessions keep black) (`theme_pages_tests`, `dark_page_text_tests`) | -| Minimize UI (Cmd+\\) | done | | +| Minimize UI (Shift+Cmd/Ctrl+\\, the Minimize button by the file name) and Show/Hide UI (Cmd/Ctrl+\\) | done | Minimize collapses the navigation bar and both sidebars and keeps the toolbar and the panel widths; "If you select an object while the panels are minimized, the left sidebar remains minimized while the right sidebar expands [...] Once all objects are deselected, the right panel minimizes again" (Explore the navigation bar and left sidebar); a small Expand button at the top left of the canvas puts them back (FigCraft's own, the article names no control for it). Hide UI takes the toolbar too (`panel_width_tests`, `shortcuts_tests`; #1835) | | Resizable panels | done | each panel's edge drags its width (left 200–480, right 240–480, 240 by default; a double-click on the edge or View ▸ Panels ▸ Reset panel widths puts the default back); the widths are each window's own, the main window's are kept in the preferences when a drag ends and come back at the next start, a new window opens with those of the window it comes from; Minimize UI hides the panels and brings them back as they were; the view keeps its centre and zoom while a panel is resized; in a window too small for them the panels give way so the canvas keeps 160 px (the left one first), down to the smallest window (800×500) (`panel_width_tests`). Not compared with Figma's widths; no snap points | | Design panel's sections on one grid | done | every section lays its rows out on the same grid: the panel is 240 pt, its content 16 pt in from the left and 8 pt from the right, two 88 pt columns at that width with 8 pt gutters and a 24 pt column for the buttons at a row's end (the columns are flex, so they share the panel when it is dragged wider); rows 24 pt in a 32 pt track, 10 pt captions over them; section titles 11 pt semibold and a row's values 11 pt, their buttons 24 pt and 4 pt apart. The panel's edge is drawn inside it: a frame stroke counts as margin and took 1 pt off each side, putting every right margin 1–2 pt out (checked in the app on Windows over the control channel) | | Actions palette (Cmd+K, Cmd+/) | done | tabs: All (every command, grouped by menu, or Other; a command that needs parameters only through its menu item, and none twice under one label, #1574), Assets (components and styles) and Plugins & widgets; results ranked prefix first, then shortest, at most 60 rows; Up/Down move a highlight and Enter runs it; Recents list what was run from the palette (matching translated labels); one open/toggle path from every entry point (`palette_tests`). Ranking and the Assets results not compared with Figma's | @@ -181,6 +181,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look | 2026-11-02 | patch | M4: max lines on an auto-width text keep it auto width: the box hugs up to that many lines and still grows with its longest line ("Max lines is available only if truncate text is enabled, and: Text resizing is set to auto height or auto width", Explore text properties). `text.maxLines` turned such a text into a fixed truncating box with a stale height, so later typing no longer widened it; fixed-size text still becomes a truncating box, auto-height text hugs as before. Test: `text_behaviour_tests::max_lines_on_auto_width_text_keeps_auto_width_and_hugs_that_many_lines` | | 2026-11-02 | patch | M4, #1828: superscript and subscript synthesised where the font has no `sups` / `subs` glyphs (the standard glyph shrunk and shifted by the OS/2 metrics, the usual 0.65 em and 0.35 / 0.14 em offsets without them), and a run mixing available and unavailable glyphs synthesised whole, as Explore text properties says. Tests: `text::tests::superscript_native_or_synthesised_and_never_mixed`, `text_behaviour_tests::superscript_and_subscript_are_synthesised_without_glyphs_for_them`. | | 2026-11-02 | patch | Shortcuts: Alt+Enter while editing text opens the link at the caret ("When editing text, press ⌥Option Return/Alt Enter to activate a focused link", Accessibility at Figma) through the new `text.openLink` command, which hands a web or mail link to the platform's opener and refuses any other scheme as the prototype player does; editing goes on. The key went to the editor as a plain Enter before. A documented row: 134 documented, 133 bound. Tests: engine `text_link_tests`, `text_link_tests` (UI) | +| 2026-11-02 | patch | M7, #1835: Minimize (`view.minimize`, Shift+Cmd/Ctrl+\\, the Minimize button by the file name) collapses the navigation bar, the left sidebar and the right sidebar and keeps the toolbar and the panel widths ("click Minimize next to the file name to collapse the navigation bar, left sidebar, and right sidebar"; "This collapses the navigation bar, left sidebar, and right sidebar for an expanded view", Explore the navigation bar and left sidebar); the right sidebar shows while something is selected and goes once nothing is; an Expand button at the top left of the canvas puts everything back. The shortcut and the button collapsed the left sidebar alone before (`view.leftSidebar` stays as a command without a key). Tests: `panel_width_tests`, `shortcuts_tests`. Numbers unchanged (the row is bound to the new command) | | 2026-11-01 | patch | Shortcuts: Cmd/Ctrl+Shift+Alt+= and - make the interface larger and smaller through the sizes Preferences offers, and Cmd/Ctrl+Shift+Alt+0 puts it back to the system's size ("Adjust the scale of the Figma UI [...] Reset to Default: Control ⇧Shift Alt 0; Make Larger: Control ⇧Shift Alt =; Make Smaller: Control ⇧Shift Alt -", Accessibility at Figma); the size is kept at once, as one picked in Preferences is. `pref.uiScaleUp`, `pref.uiScaleDown`, `pref.uiScaleReset`; the sizes list now lives in the engine (`UI_SCALE_SIZES`) and Preferences reads it from there. Found by a documentation audit of the shortcuts. Test: `keymap_tests::the_interface_size_keys_step_through_the_preferences_sizes`. Documented rows 130 → 133, bound 129 → 132 | | 2026-11-01 | patch | Shortcuts: Cmd/Ctrl+Alt+↑/↓ (and ←/→) rotate the selection one degree at a time, 15 with Shift ("Press ⌘Command ⌥Option ↑/ ↓ on Mac or Control Alt ↑/↓ on Windows to rotate an object one degree at a time", Accessibility at Figma); they resized by the nudge before, which Cmd/Ctrl+arrows without Alt still do. Down and right turn clockwise, up and left back (the documentation is silent on the direction). In the shortcuts panel's Transform tab, over the control channel's `ui.key` too. Found by a documentation audit of the shortcuts. Test: `shortcut_keys_tests::cmd_alt_arrows_rotate_a_degree_and_fifteen_with_shift`. Documented rows 129 → 130, bound 128 → 129 | | 2026-11-01 | patch | Shortcuts: the Layers panel opens with Ctrl+1 as well as Alt+1 on Windows and Linux ("Mac: Option 1 Windows: Ctrl 1" in Select layers and objects; "Windows: Alt 1" in Explore the navigation bar and left sidebar). macOS keeps Option+1. Found by a documentation audit of the shortcuts (every chord driven over the control channel). Tests: `keymap_tests::the_layers_panel_answers_both_of_its_documented_windows_keys`, the parity row. Counts unchanged |