M1: full screen (#227) (#238)

View > Full screen (`view.fullscreen {on?}`, F11 on Windows and Linux,
Ctrl+Cmd+F on macOS) makes the window fill the screen, taskbar included,
and leaves it again. FigCraft's own menu item, translated in every catalog.

Tests: the key and the command toggle the window, junk parameters are
ignored; the shortcut table pins the new binding (49 pinned).

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: bflatastic <bombayapirat@gmail.com>
This commit is contained in:
Nathan Fontenele
2026-10-14 10:31:59 +07:00
committed by GitHub
co-authored by Claude Sonnet 5.5 bflatastic
parent ce84d1f27f
commit fb8ace5bf4
15 changed files with 47 additions and 3 deletions
+2
View File
@@ -104,6 +104,8 @@ pub static MENU: &[MenuItem] = &[
m!("View", "Soft-proof sRGB", "view.proofSrgb"),
m!("View", SEP, ""),
m!("View/Panels", "Show/Hide UI", "view.toggleUi"),
// FigCraft's own: the window fills the screen (F11), for setups that hide the taskbar then.
m!("View", "Full screen", "view.fullscreen"),
m!("View/Panels", "Reset panel widths", "view.panelWidths"),
m!("View/Panels", "Layers", "view.layers"),
m!("View/Panels", "Assets", "view.assets"),
+4
View File
@@ -268,6 +268,10 @@ pub fn specs() -> Vec<CommandSpec> {
spec!("view.snapObjects", "Snap to objects", "{on?}", false, |s, p| flag(s, p, |v| &mut v.snap_objects)),
spec!("view.snapPixel", "Snap to pixel grid", "{on?}", false, |s, p| flag(s, p, |v| &mut v.snap_pixel)),
spec!("view.toggleUi", "Show/Hide UI", "{on?}", false, |s, p| flag(s, p, |v| &mut v.ui_hidden)),
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()
}),
spec!("view.layers", "Layers panel", "{}", false, |s, _| ui(s, "panel.layers")),
spec!("view.assets", "Assets panel", "{}", false, |s, _| ui(s, "panel.assets")),
spec!("view.design", "Design panel", "{}", false, |s, _| ui(s, "panel.design")),
+1
View File
@@ -162,6 +162,7 @@ pub static KEYS: &[Binding] = &[
k(Essential, "app.actions", &["Cmd+K", "Cmd+/"]).label("Actions…"),
k(Essential, "help.shortcuts", &["Ctrl+Shift+?"]).both(),
k(Essential, "view.toggleUi", &["Cmd+\\"]),
k(Essential, "view.fullscreen", &["Ctrl+Cmd+F"]).win(&["F11"]),
k(Essential, "edit.pickColor", &["I", "Ctrl+C"]).win(&["I"]),
k(Essential, "app.preferences", &["Cmd+,"]),
k(Essential, "file.new", &["Cmd+N"]),
+2 -1
View File
@@ -91,6 +91,7 @@ static ROWS: &[Row] = &[
doc(Essential, "Save to version history", &["Alt+Cmd+S"], &["Ctrl+Alt+S"], Bound("file.saveVersion"), ""),
doc(Essential, "Run the last plugin", &["Alt+Cmd+P"], &["Ctrl+Alt+P"], Moved("plugins.runLast", &["Alt+Cmd+P"], &["Ctrl+Alt+Shift+P"]),
"conflict: the documentation also gives Ctrl+Alt+P to pixel preview on Windows; pixel preview keeps it, the plugin takes Ctrl+Alt+Shift+P"),
pin(Essential, "view.fullscreen", &["Ctrl+Cmd+F"], &["F11"]),
pin(Essential, "app.preferences", &["Cmd+,"], &[]),
pin(Essential, "file.new", &["Cmd+N"], &[]).reserved(BROWSER_TAB),
pin(Essential, "file.open", &["Cmd+O"], &[]),
@@ -397,7 +398,7 @@ fn counts_match_the_docs() {
let reserved = documented.iter().filter(|r| !r.reserved.is_empty()).count();
let pinned = ROWS.len() - documented.len();
// docs/ui-parity.md quotes these.
assert_eq!((documented.len(), bound, moved, missing, reserved, pinned), (129, 128, 1, 0, 2, 48));
assert_eq!((documented.len(), bound, moved, missing, reserved, pinned), (129, 128, 1, 0, 2, 49));
}
#[test]
+1
View File
@@ -2034,6 +2034,7 @@ status = "draft — needs native review"
"view.prevPage" = "Vorherige Seite"
"view.previousRegion" = "Vorherigen Bereich fokussieren"
"view.proofSrgb" = "Softproof sRGB"
"view.fullscreen" = "Vollbild"
"view.prototype" = "Bereich Prototyp"
"view.rulers" = "Lineale"
"view.set" = "Ansicht festlegen"
+1
View File
@@ -2087,6 +2087,7 @@ status = "draft — needs native review"
"view.prevPage" = "Página anterior"
"view.previousRegion" = "Enfocar la región anterior"
"view.proofSrgb" = "Prueba en pantalla sRGB"
"view.fullscreen" = "Pantalla completa"
"view.prototype" = "Panel de prototipo"
"view.rulers" = "Reglas"
"view.set" = "Establecer vista"
+1
View File
@@ -2034,6 +2034,7 @@ status = "draft — needs native review"
"view.prevPage" = "Page précédente"
"view.previousRegion" = "Activer la région précédente"
"view.proofSrgb" = "Épreuve écran sRGB"
"view.fullscreen" = "Plein écran"
"view.prototype" = "Panneau Prototype"
"view.rulers" = "Règles"
"view.set" = "Définir l’affichage"
+1
View File
@@ -2036,6 +2036,7 @@ status = "draft — needs native review"
"view.prevPage" = "前のページ"
"view.previousRegion" = "前の領域にフォーカス"
"view.proofSrgb" = "sRGB でソフトプルーフ"
"view.fullscreen" = "全画面表示"
"view.prototype" = "プロトタイプパネル"
"view.rulers" = "定規"
"view.set" = "ビューを設定"
+1
View File
@@ -2035,6 +2035,7 @@ status = "draft — needs native review"
"view.prevPage" = "이전 페이지"
"view.previousRegion" = "이전 영역에 포커스"
"view.proofSrgb" = "sRGB 교정 미리 보기"
"view.fullscreen" = "전체 화면"
"view.prototype" = "프로토타입 패널"
"view.rulers" = "눈금자"
"view.set" = "보기 설정"
+1
View File
@@ -2033,6 +2033,7 @@ status = "draft — needs native review"
"view.prevPage" = "Página anterior"
"view.previousRegion" = "Focar a região anterior"
"view.proofSrgb" = "Prova em tela sRGB"
"view.fullscreen" = "Tela cheia"
"view.prototype" = "Painel de protótipo"
"view.rulers" = "Réguas"
"view.set" = "Definir visualização"
+1
View File
@@ -2036,6 +2036,7 @@ status = "draft — needs native review"
"view.prevPage" = "上一页"
"view.previousRegion" = "聚焦上一个区域"
"view.proofSrgb" = "sRGB 软打样"
"view.fullscreen" = "全屏"
"view.prototype" = "原型面板"
"view.rulers" = "标尺"
"view.set" = "设置视图"
+1 -1
View File
@@ -735,7 +735,7 @@ impl FigApp {
"dialog.unsaved" => self.toast(i18n::tr("This file has unsaved changes")),
"dialog.plugins" => self.ui.plugins_window = true,
"dialog.cursorChat" => live::open_chat(self),
"window.new" | "window.focus" => self.window_requests.push(r),
"window.new" | "window.focus" | "window.fullscreen" => self.window_requests.push(r),
_ => {}
}
}
+22
View File
@@ -189,3 +189,25 @@ fn enter_with_a_shape_tool_places_one_in_the_middle_of_the_view() {
press(&mut u, Key::Enter, Modifiers::NONE);
assert!(u.app.session.ds().unwrap().vector_edit.is_some());
}
#[test]
fn full_screen_key_and_command_toggle_the_window() {
let mut u = Ui::new(Session::new());
let fullscreen = |out: &egui::FullOutput| -> Vec<ViewportCommand> {
out.viewport_output.get(&ViewportId::ROOT).map(|v| v.commands.clone()).unwrap_or_default()
};
// F11 (Windows and Linux) or Ctrl+Cmd+F (macOS) asks the window to fill the screen.
let out = if crate::shortcuts::platform() == Platform::Mac {
press(&mut u, Key::F, Modifiers::MAC_CMD | Modifiers::COMMAND | Modifiers::CTRL)
} else {
press(&mut u, Key::F11, Modifiers::NONE)
};
assert!(fullscreen(&out).contains(&ViewportCommand::Fullscreen(true)), "full screen key, {:?}", fullscreen(&out));
// `on: false` leaves it, whatever the window reports.
u.app.run("view.fullscreen", json!({"on": false}));
let out = u.frame_output(vec![]);
assert!(fullscreen(&out).contains(&ViewportCommand::Fullscreen(false)), "{:?}", fullscreen(&out));
// Junk parameters are ignored, not fatal.
u.app.run("view.fullscreen", json!({"on": "yes"}));
u.frame(vec![]);
}
+6
View File
@@ -242,6 +242,12 @@ pub fn focus(app: &mut FigApp, ctx: &Context, id: u64) -> Result<Value, String>
pub(crate) fn requests(app: &mut FigApp, ctx: &Context) {
for r in std::mem::take(&mut app.window_requests) {
let n = |k: &str| r.data.get(k).and_then(Value::as_u64);
if r.kind == "window.fullscreen" {
let full = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false));
let on = r.data.get("on").and_then(Value::as_bool).unwrap_or(!full);
ctx.send_viewport_cmd(ViewportCommand::Fullscreen(on));
continue;
}
let res = if r.kind == "window.new" {
open(app, ctx, n("index").map(|i| i as usize), r.data.get("path").and_then(Value::as_str), None).map(|_| ())
} else {
+2 -1
View File
@@ -124,7 +124,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look
| Bound on other keys (documented conflict) | 1 | Run last plugin: the documentation also gives Ctrl+Alt+P to pixel preview on Windows; the plugin takes Ctrl+Alt+Shift+P there |
| Missing | 0 | moving the rotation origin (Alt+R, `transform.rotationOrigin`) and placing an object with Enter after choosing its tool (`tool.place`) were the last two |
| Documented keys a platform keeps | 2 | Ctrl+Space (Windows/Linux keyboard box selection) switches some CJK input methods; F6 goes to a browser's address bar in the web build |
| Undocumented bindings pinned | 48 | of which Cmd/Ctrl+W, Cmd/Ctrl+N and Ctrl+Tab are taken by browsers (web build) and Ctrl+Alt+T / Ctrl+Alt+L by some Linux desktops |
| Undocumented bindings pinned | 49 | of which Cmd/Ctrl+W, Cmd/Ctrl+N and Ctrl+Tab are taken by browsers (web build) and Ctrl+Alt+T / Ctrl+Alt+L by some Linux desktops |
- **Newly bound:** A for the Frame tool, Shift+' for the pixel grid (Cmd+' kept: the articles
disagree), Alt+Cmd+\ / Ctrl+Alt+\ for others' cursors (`view.multiplayerCursors`, new), Ctrl+F6
@@ -165,6 +165,7 @@ in the table. `crates/ui-egui/src/shortcut_keys_tests.rs` drives the keys a look
| Date | Change | Summary |
|---|---|---|
| 2026-10-13 | minor | Home screen: a Templates tab (eight starter templates as cards with thumbnails, search and a kind filter; click or Enter opens a new document) replaces the Community placeholder, the Drafts placeholder is removed, Recents offers four templates while there are no recent files and shows a Getting started card (closable; Help ▸ Getting started brings it back), File ▸ New from template…, and a first run opens on the home screen |
| 2026-10-13 | minor | Full screen (#227): View ▸ Full screen (`view.fullscreen {on?}`, F11 on Windows and Linux, Ctrl+Cmd+F on macOS) makes the window fill the screen, taskbar and title bar included, and leaves it again; FigCraft's own item (not in the incumbent's menu). Percentages and hours unchanged |
| 2026-10-12 | minor | Everyday editing partials: number fields (scrub a step a point, ×10 with Shift, ×0.1 with Alt, on past the screen edge where the platform reports it; arrow keys by the nudge preferences, opacity and rotation by 1 and 10; brackets and units in arithmetic; `50%` relative outside percentage fields; Tab commits and moves on; an untouched field commits nothing; relative edits and arrow keys on Mixed go to each layer in one undo step) done; outline mode's Include hidden layers and Include object bounds, drawn and clickable, done; Hide when publishing from the Assets panel done (the name's leading `.`); cursors per tool and modifier done (a table in the engine, drawn rotate, pen, eyedropper and comment cursors, not-allowed on refused edits; guides and handles keep their cursors); resizable panels done (limits, double-click reset, kept per window and in the preferences, the canvas keeps its room in a small window); a file or page made in the dark theme has a #1E1E1E page (the light theme and sessions without an interface: #F5F5F5) and text drawn on a dark page is white; Enter on a text selects all of it (test added). Menu items: View ▸ Outlines ▸ Include hidden layers / Include object bounds, View ▸ Panels ▸ Reset panel widths, Libraries ▸ Hide when publishing |
| 2026-10-12 | minor | Drag threshold and double-clicks: the second press of a double-click began a move or resize that was never finished, so double-clicking text inside a group often didn't start editing (from the middle of the text) or dragged and resized it when the hand moved a pixel or two; a move or resize now starts only after 3 px of travel on screen and a double-click drops the press's gesture first |
| 2026-10-12 | minor | Rotation origin and placing with Enter: Alt+R shows a rotation origin handle to drag (snapping to the box's corners, edge middles and centre; double-click resets; Escape hides; kept while the selection lasts, not saved), which the corner handles, the rotation field, Rotate 90°/180° and `transform.rotate` turn about (`transform.rotationOrigin`, `transform.setRotationOrigin`); Enter with a shape or frame tool chosen, on the canvas or the toolbar's tool, places a click-sized one in the middle of the view (`tool.place`). Documented shortcuts: 128 bound, 0 missing. The keyboard box selection's toast and screen-reader text name Cmd/Ctrl+arrows too; the shortcuts panel cuts a label that doesn't fit before its keycaps with "…" (measured in the font it's drawn in; long labels ran into the keycaps) and shows it in full on hover |