collective

Browse / github.com/storytold/craft-launcher / patch

Add persistent interface sizing and correct native window restoration

Add persistent interface sizing and correct native window restoration Native windows should reopen at their previous normal size or maximized state, and restoring a maximized window should recover the latest normal rectangle. The launcher draws its own header as the title bar on Linux, so the shared Adwaita frame doesn't change how it looks. Add Appearance → Interface size with Follow display scale and a custom 50–300% field (including 150% and 157%). Persist the choice, apply it independently of native display scale, and save whole-interface pinch zoom into this preference. The release-notes text zoom stays separate. Add the new labels to every complete menu c

Status
open
Score
0.00
Adopters
0 other operators
Author
abdallah-shehawey (imported from pull request #904, open)
Base
6c5f5ea2f5186cefe35797ddacd7d8a866ae2eeb
Size
+225 −45 in 17 files
Review
pending
Shared
2026-10-28
Id
03943fd516f94c81ac29cd02

How it went

No other operator has reported using it yet.

Diff

raw · ATTRIBUTION.md Cargo.lock Cargo.toml NOTICE crates/ui-egui/src/app.rs crates/ui-egui/src/i18n/cs.tsv crates/ui-egui/src/i18n/de.tsv crates/ui-egui/src/i18n/es.tsv crates/ui-egui/src/i18n/fr.tsv crates/ui-egui/src/i18n/hu.tsv crates/ui-egui/src/i18n/ja.tsv crates/ui-egui/src/i18n/pl.tsv crates/ui-egui/src/i18n/pt-BR.tsv crates/ui-egui/src/i18n/ru.tsv crates/ui-egui/src/i18n/zh-CN.tsv crates/ui-egui/src/settings.rs crates/ui-egui/src/settings_dialog.rs

diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md--- a/ATTRIBUTION.md+++ b/ATTRIBUTION.md@@ -22,11 +22,11 @@ next to them. | `assets/fonts/Inter-Variable.ttf` | Inter 4.001, variable (body text) | The Inter Project Authors (Rasmus Andersson) | Google Fonts, <https://github.com/google/fonts/tree/main/ofl/inter> (`Inter[opsz,wght].ttf`, renamed) | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) | | `assets/fonts/Archivo-Variable.ttf` | Archivo 2.001, variable (headings and app names) | The Archivo Project Authors (Omnibus-Type) | Google Fonts, <https://github.com/google/fonts/tree/main/ofl/archivo> (`Archivo[wdth,wght].ttf`, renamed) | SIL OFL 1.1, [`assets/fonts/OFL-Archivo.txt`](assets/fonts/OFL-Archivo.txt) | | `assets/fonts/GeistMono-Variable.ttf` | Geist Mono 1.701, variable (labels and buttons) | The Geist Project Authors (Vercel) | Google Fonts, <https://github.com/google/fonts/tree/main/ofl/geistmono> (`GeistMono[wght].ttf`, renamed) | SIL OFL 1.1, [`assets/fonts/OFL-GeistMono.txt`](assets/fonts/OFL-GeistMono.txt) |-| (dependency) `epaint_default_fonts/fonts/NotoEmoji-Regular.ttf` | Noto Emoji Regular, monochrome emoji fallback for arbitrary release-note, feed and alert text | The Noto Project Authors (Google) | Bundled transitively by `egui 0.37`'s `monochrome_emoji_fonts` feature from `epaint_default_fonts`; <https://github.com/storytold/egui/tree/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts> | SIL OFL 1.1, `epaint_default_fonts/fonts/OFL.txt`; see also the dependency `README` and egui `README.md` |+| (dependency) `epaint_default_fonts/fonts/NotoEmoji-Regular.ttf` | Noto Emoji Regular, monochrome emoji fallback for arbitrary release-note, feed and alert text | The Noto Project Authors (Google) | Bundled transitively by `egui 0.37`'s `monochrome_emoji_fonts` feature from `epaint_default_fonts`; <https://github.com/storytold/egui/tree/69750b71a97dc4672e2fbb70863b838e6795c910/crates/epaint_default_fonts/fonts> | SIL OFL 1.1, `epaint_default_fonts/fonts/OFL.txt`; see also the dependency `README` and egui `README.md` | | (dependency) `epaint_default_fonts/fonts/emoji-icon-font.ttf` | emoji-icon-font, monochrome symbol/emoji fallback | John Slegers | Bundled transitively by `egui 0.37`'s `monochrome_emoji_fonts` feature from `epaint_default_fonts`; <https://github.com/jslegers/emoji-icon-font> | MIT, Copyright (c) 2014 John Slegers, `epaint_default_fonts/fonts/emoji-icon-font-mit-license.txt` |-| (dependency) `epaint_default_fonts/fonts/egui-icons.ttf` | egui-icons, five-glyph subset of emoji-icon-font | John Slegers | [egui 6d518fc7](https://github.com/storytold/egui/tree/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts); included by default_fonts independently of monochrome_emoji_fonts | MIT, Copyright (c) 2014 John Slegers; full upstream text in `NOTICE` |-| (dependency) `epaint_default_fonts/fonts/Ubuntu-Light.ttf` | Ubuntu Light, default font fallback | Canonical Ltd. | [Pinned egui source](https://github.com/storytold/egui/tree/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts) | Ubuntu Font Licence 1.0; full upstream text and copyright in `NOTICE` |-| (dependency) `epaint_default_fonts/fonts/Hack-Regular.ttf` | Hack Regular, default monospace fallback | Source Foundry Authors; Bitstream Inc.; DejaVu contributors | [Pinned egui source](https://github.com/storytold/egui/tree/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts) | MIT and Bitstream Vera License; DejaVu work public domain; full upstream notices in `NOTICE` |+| (dependency) `epaint_default_fonts/fonts/egui-icons.ttf` | egui-icons, five-glyph subset of emoji-icon-font | John Slegers | [egui 69750b71](https://github.com/storytold/egui/tree/69750b71a97dc4672e2fbb70863b838e6795c910/crates/epaint_default_fonts/fonts); included by default_fonts independently of monochrome_emoji_fonts | MIT, Copyright (c) 2014 John Slegers; full upstream text in `NOTICE` |+| (dependency) `epaint_default_fonts/fonts/Ubuntu-Light.ttf` | Ubuntu Light, default font fallback | Canonical Ltd. | [Pinned egui source](https://github.com/storytold/egui/tree/69750b71a97dc4672e2fbb70863b838e6795c910/crates/epaint_default_fonts/fonts) | Ubuntu Font Licence 1.0; full upstream text and copyright in `NOTICE` |+| (dependency) `epaint_default_fonts/fonts/Hack-Regular.ttf` | Hack Regular, default monospace fallback | Source Foundry Authors; Bitstream Inc.; DejaVu contributors | [Pinned egui source](https://github.com/storytold/egui/tree/69750b71a97dc4672e2fbb70863b838e6795c910/crates/epaint_default_fonts/fonts) | MIT and Bitstream Vera License; DejaVu work public domain; full upstream notices in `NOTICE` |  ### Craft App Suite icons diff --git a/Cargo.lock b/Cargo.lock--- a/Cargo.lock+++ b/Cargo.lock@@ -1216,7 +1216,7 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "ecolor" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "bytemuck",  "emath",@@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "ahash",  "bytemuck",@@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "egui" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "accesskit",  "ahash",@@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "ahash",  "bytemuck",@@ -1303,7 +1303,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "accesskit_winit",  "arboard",@@ -1330,7 +1330,7 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "ahash",  "egui",@@ -1345,7 +1345,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "bytemuck",  "egui",@@ -1358,7 +1358,7 @@ dependencies = [ [[package]] name = "egui_kittest" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "dify",  "eframe",@@ -1384,7 +1384,7 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" [[package]] name = "emath" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "bytemuck",  "serde",@@ -1474,7 +1474,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910" dependencies = [  "ahash",  "bytemuck",@@ -1501,7 +1501,7 @@ dependencies = [ [[package]] name = "epaint_default_fonts" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910"  [[package]] name = "equivalent"@@ -4160,6 +4160,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]]+name = "sctk-adwaita"+version = "0.10.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec"+dependencies = [+ "log",+ "smithay-client-toolkit 0.19.2",+ "tiny-skia",+]+ [[package]] name = "security-framework" version = "3.7.0"@@ -5857,6 +5868,7 @@ dependencies = [  "raw-window-handle",  "redox_syscall 0.4.1",  "rustix 0.38.44",+ "sctk-adwaita",  "smithay-client-toolkit 0.19.2",  "smol_str",  "tracing",@@ -6349,14 +6361,14 @@ dependencies = [ [[patch.unused]] name = "egui_demo_lib" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910"  [[patch.unused]] name = "egui_inspection" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910"  [[patch.unused]] name = "egui_system_font_provider" version = "0.37.0"-source = "git+https://github.com/storytold/egui.git?rev=6d518fc799681571963842f95b55a6b68431b15a#6d518fc799681571963842f95b55a6b68431b15a"+source = "git+https://github.com/storytold/egui.git?rev=69750b71a97dc4672e2fbb70863b838e6795c910#69750b71a97dc4672e2fbb70863b838e6795c910"diff --git a/Cargo.toml b/Cargo.toml--- a/Cargo.toml+++ b/Cargo.toml@@ -50,18 +50,18 @@ strip = "debuginfo"  # Keep the complete egui family and native event bridge on reviewed fork commits. [patch.crates-io]-ecolor = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-emath = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-epaint = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-epaint_default_fonts = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui-winit = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui-wgpu = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_glow = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_extras = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_kittest = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_system_font_provider = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-eframe = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_demo_lib = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }-egui_inspection = { git = "https://github.com/storytold/egui.git", rev = "6d518fc799681571963842f95b55a6b68431b15a" }+ecolor = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+emath = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+epaint = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+epaint_default_fonts = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui-winit = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui-wgpu = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_glow = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_extras = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_kittest = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_system_font_provider = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+eframe = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_demo_lib = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" }+egui_inspection = { git = "https://github.com/storytold/egui.git", rev = "69750b71a97dc4672e2fbb70863b838e6795c910" } winit = { git = "https://github.com/storytold/winit.git", rev = "a517b3846ee8457d84ee4975110bfda6797c44dc" }diff --git a/NOTICE b/NOTICE--- a/NOTICE+++ b/NOTICE@@ -45,7 +45,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI Noto Emoji font (dependency asset) ---------------------------------- epaint_default_fonts 0.37, pinned with egui 0.37 at-https://github.com/storytold/egui/tree/6d518fc799681571963842f95b55a6b68431b15a/crates/epaint_default_fonts/fonts,+https://github.com/storytold/egui/tree/69750b71a97dc4672e2fbb70863b838e6795c910/crates/epaint_default_fonts/fonts, embeds NotoEmoji-Regular.ttf, the monochrome emoji fallback used when egui's `monochrome_emoji_fonts` feature is enabled. The font's copyright notice and the upstream SIL Open Font License 1.1 text follow unchanged.@@ -149,7 +149,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE. Ubuntu-Light.ttf (dependency asset) ---------------------------------------- Included by epaint_default_fonts at egui revision-6d518fc799681571963842f95b55a6b68431b15a.+69750b71a97dc4672e2fbb70863b838e6795c910. Copyright 2011 Canonical Ltd.  Licensed under the Ubuntu Font Licence 1.0  -------------------------------@@ -253,7 +253,7 @@ DEALINGS IN THE FONT SOFTWARE. Hack-Regular.ttf (dependency asset) ---------------------------------------- Included by epaint_default_fonts at egui revision-6d518fc799681571963842f95b55a6b68431b15a.+69750b71a97dc4672e2fbb70863b838e6795c910.  The work in the Hack project is Copyright 2018 Source Foundry Authors and licensed under the MIT License @@ -286,3 +286,29 @@ The Font Software may be sold as part of a larger software package but no copy o THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.  Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.++sctk-adwaita 0.10.1 — Wayland client window decorations+https://crates.io/crates/sctk-adwaita/0.10.1+Built without title fonts; caption controls are drawn as vectors.++MIT License++Copyright (c) 2022 Bartłomiej Maryńczak++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.diff --git a/crates/ui-egui/src/app.rs b/crates/ui-egui/src/app.rs--- a/crates/ui-egui/src/app.rs+++ b/crates/ui-egui/src/app.rs@@ -160,6 +160,7 @@ pub struct LauncherUi {     pub(crate) pictures: std::collections::HashMap<(String, String), crate::details::Picture>,     started: bool,     applied_theme: Option<theme::ThemeChoice>,+    applied_interface_size: Option<(u16, f32)>,     /// The OS asks for fewer animations: what the host last said, and the context's time to ask     /// it again at.     pub(crate) reduce_motion: bool,@@ -256,6 +257,7 @@ impl LauncherUi {             pictures: std::collections::HashMap::new(),             started: false,             applied_theme: None,+            applied_interface_size: None,             reduce_motion: false,             motion_recheck: 0.0,             applied_motion: None,@@ -281,6 +283,7 @@ impl LauncherUi {         egui_extras::install_image_loaders(ctx);         self.fonts_ready = false;         self.applied_theme = None;+        self.applied_interface_size = None;         self.applied_motion = None;         self.motion_recheck = 0.0;         self.toasts.clear();@@ -449,6 +452,17 @@ impl LauncherUi {      /// Non-drawing work: runs every frame and also while the window is hidden in the tray.     pub fn logic(&mut self, ctx: &egui::Context, host: &mut dyn Host) {+        let percent = self.settings.interface_size;+        let native = ctx.native_pixels_per_point().filter(|s| s.is_finite() && *s > 0.0).unwrap_or(1.0);+        let zoom = if percent == 0 { 1.0 } else { f32::from(percent.clamp(50, 300)) / (100.0 * native) };+        if self.applied_interface_size != Some((percent, native)) {+            // Auto keeps the host's transient zoom (including headless/control views). Only an+            // explicit preference, or returning from one to Auto, changes that zoom here.+            if percent != 0 || self.applied_interface_size.is_some_and(|(previous, _)| previous != 0) {+                ctx.set_zoom_factor(zoom);+            }+            self.applied_interface_size = Some((percent, native));+        }         // Messages made here (toasts, alerts) are drawn later, so use the language the UI can         // actually show: English where the system has no CJK face for the preference.         set_current(effective_lang(&self.settings.language));@@ -708,23 +722,41 @@ impl LauncherUi {     fn zoom_input(&mut self, ctx: &egui::Context) {         use egui::gui_zoom::kb_shortcuts;         let notes_open = matches!(self.dialog, Some(Dialog::Notes(_)));+        // The zoom keys are consumed in every view: left to egui, they would change the interface+        // size without changing the saved preference, and it would revert on the next start.         let (delta, zoom_in, zoom_out, reset) = ctx.input_mut(|i| {             let delta = i.zoom_delta();-            let reset = notes_open && i.consume_shortcut(&kb_shortcuts::ZOOM_RESET);-            let zoom_in = notes_open && (i.consume_shortcut(&kb_shortcuts::ZOOM_IN) || i.consume_shortcut(&kb_shortcuts::ZOOM_IN_SECONDARY));-            let zoom_out = notes_open && i.consume_shortcut(&kb_shortcuts::ZOOM_OUT);+            let reset = i.consume_shortcut(&kb_shortcuts::ZOOM_RESET);+            let zoom_in = i.consume_shortcut(&kb_shortcuts::ZOOM_IN) || i.consume_shortcut(&kb_shortcuts::ZOOM_IN_SECONDARY);+            let zoom_out = i.consume_shortcut(&kb_shortcuts::ZOOM_OUT);             (delta, zoom_in, zoom_out, reset)         });         let delta = if delta.is_finite() && delta > 0.0 { delta } else { 1.0 };+        let step = match (zoom_in, zoom_out) {+            (true, false) => 0.1,+            (false, true) => -0.1,+            _ => 0.0,+        };         if notes_open {-            let step = match (zoom_in, zoom_out) {-                (true, false) => 0.1,-                (false, true) => -0.1,-                _ => 0.0,-            };             self.notes_zoom = if reset { 1.0 } else { ((self.notes_zoom + step) * delta).clamp(0.5, 3.0) };-        } else if delta != 1.0 {-            ctx.set_zoom_factor((ctx.zoom_factor() * delta).clamp(0.2, 5.0));+        } else if reset {+            // Like egui's own reset: back to the display scale.+            self.settings.interface_size = 0;+            ctx.request_repaint();+        } else if step != 0.0 || delta != 1.0 {+            // Start from the saved size: a zoom set from it reaches the context only a pass later,+            // so quick repeated presses would otherwise count once.+            let current = match self.settings.interface_size {+                0 => ctx.pixels_per_point() * 100.0,+                percent => f32::from(percent),+            };+            let mut percent = (current + step * 100.0) * delta;+            if step != 0.0 {+                // Keyboard steps land on whole tens, as egui's own zoom keys do.+                percent = (percent / 10.0).round() * 10.0;+            }+            self.settings.interface_size = percent.round().clamp(50.0, 300.0) as u16;+            ctx.request_repaint();         }     } @@ -991,6 +1023,68 @@ mod tests {     use std::cell::RefCell;     use std::rc::Rc; +    #[test]+    fn interface_size_is_persisted_and_applied_independently_of_display_scale() {+        let (mut app, _, ctx) = launcher();+        for native in [1.0, 1.5, 2.0] {+            for percent in [0, 100, 150, 157, 200] {+                app.settings.interface_size = percent;+                let saved = serde_json::to_string(&app.settings).unwrap();+                app.settings = serde_json::from_str(&saved).unwrap();+                let mut input = egui::RawInput::default();+                input.viewports.entry(egui::ViewportId::ROOT).or_default().native_pixels_per_point = Some(native);+                for _ in 0..2 {+                    let mut output = ctx.run_ui(input.clone(), |ui| app.logic(ui.ctx(), &mut NoHost));+                    output.textures_delta.clear();+                }+                let expected = if percent == 0 { native } else { f32::from(percent) / 100.0 };+                assert!((ctx.pixels_per_point() - expected).abs() < 0.001);+                assert_eq!(app.settings.interface_size, percent);+            }+        }+    }++    /// Ctrl/Cmd + Plus, Minus and 0 change the saved interface size, not only egui's zoom, so the+    /// Appearance value matches what is shown and survives a restart.+    #[test]+    fn zoom_keys_change_the_saved_interface_size() {+        let (mut app, _, ctx) = launcher();+        let key = |key| egui::Event::Key { key, physical_key: None, pressed: true, repeat: false, modifiers: egui::Modifiers::COMMAND };+        let pass = |app: &mut LauncherUi, events: Vec<egui::Event>| {+            let mut input = egui::RawInput { events, ..Default::default() };+            input.viewports.entry(egui::ViewportId::ROOT).or_default().native_pixels_per_point = Some(1.5);+            // The native shell's order: logic, then the UI pass.+            let mut output = ctx.run_ui(input, |ui| {+                app.logic(ui.ctx(), &mut NoHost);+                app.ui(ui, &mut NoHost);+            });+            output.textures_delta.clear();+        };+        for _ in 0..3 {+            pass(&mut app, vec![]);+        }+        assert_eq!(app.settings.interface_size, 0);+        // A zoom set in one pass is the context's from the pass after next.+        let settle = |app: &mut LauncherUi| {+            pass(app, vec![]);+            pass(app, vec![]);+        };+        pass(&mut app, vec![key(egui::Key::Plus)]);+        assert_eq!(app.settings.interface_size, 160);+        settle(&mut app);+        assert!((ctx.pixels_per_point() - 1.6).abs() < 0.001);+        // Two quick presses are two steps, though the first hasn't reached the context yet.+        pass(&mut app, vec![key(egui::Key::Minus)]);+        pass(&mut app, vec![key(egui::Key::Minus)]);+        assert_eq!(app.settings.interface_size, 140);+        settle(&mut app);+        assert!((ctx.pixels_per_point() - 1.4).abs() < 0.001);+        pass(&mut app, vec![key(egui::Key::Num0)]);+        assert_eq!(app.settings.interface_size, 0);+        settle(&mut app);+        assert!((ctx.pixels_per_point() - 1.5).abs() < 0.001);+    }+     use egui_kittest::Harness;      use super::*;diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv--- a/crates/ui-egui/src/i18n/cs.tsv+++ b/crates/ui-egui/src/i18n/cs.tsv@@ -349,3 +349,6 @@ filter	Installed	Nainstalováno 	Show {n} more changes	Zobrazit dalších změn: {n} 	Last updated: {date}	Naposledy aktualizováno: {date} column	Version	Verze++	Interface size	Velikost rozhraní+	Follow display scale	Podle měřítka obrazovkydiff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv--- a/crates/ui-egui/src/i18n/de.tsv+++ b/crates/ui-egui/src/i18n/de.tsv@@ -381,3 +381,6 @@ filter	Installed	Installiert 	Show {n} more changes	{n} weitere Änderungen anzeigen 	Last updated: {date}	Zuletzt aktualisiert: {date} column	Version	Version++	Interface size	Oberflächengröße+	Follow display scale	Bildschirmskalierung folgendiff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv--- a/crates/ui-egui/src/i18n/es.tsv+++ b/crates/ui-egui/src/i18n/es.tsv@@ -362,3 +362,6 @@ filter	Installed	Instaladas 	Show {n} more changes	Mostrar {n} cambios más 	Last updated: {date}	Última actualización: {date} column	Version	Versión++	Interface size	Tamaño de la interfaz+	Follow display scale	Seguir la escala de la pantalladiff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv--- a/crates/ui-egui/src/i18n/fr.tsv+++ b/crates/ui-egui/src/i18n/fr.tsv@@ -379,3 +379,6 @@ filter	Installed	Installées 	Show {n} more changes	Afficher {n} modifications de plus 	Last updated: {date}	Dernière mise à jour : {date} column	Version	Version++	Interface size	Taille de l’interface+	Follow display scale	Suivre l’échelle de l’écrandiff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv--- a/crates/ui-egui/src/i18n/hu.tsv+++ b/crates/ui-egui/src/i18n/hu.tsv@@ -382,3 +382,6 @@ filter	Installed	Telepítve 	Show {n} more changes	{n} további változás megjelenítése 	Last updated: {date}	Utoljára frissítve: {date} column	Version	Verzió++	Interface size	Felület mérete+	Follow display scale	Kijelző méretezésének követésediff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv--- a/crates/ui-egui/src/i18n/ja.tsv+++ b/crates/ui-egui/src/i18n/ja.tsv@@ -384,3 +384,6 @@ filter	Installed	インストール済み 	Show {n} more changes	さらに{n}件の変更を表示 	Last updated: {date}	最終更新: {date} column	Version	バージョン++	Interface size	インターフェースのサイズ+	Follow display scale	画面の拡大率に合わせるdiff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv--- a/crates/ui-egui/src/i18n/pl.tsv+++ b/crates/ui-egui/src/i18n/pl.tsv@@ -383,3 +383,6 @@ filter	Installed	Zainstalowane 	Show {n} more changes	Pokaż {n} więcej zmian 	Last updated: {date}	Ostatnia aktualizacja: {date} column	Version	Wersja++	Interface size	Rozmiar interfejsu+	Follow display scale	Użyj skali ekranudiff --git a/crates/ui-egui/src/i18n/pt-BR.tsv b/crates/ui-egui/src/i18n/pt-BR.tsv--- a/crates/ui-egui/src/i18n/pt-BR.tsv+++ b/crates/ui-egui/src/i18n/pt-BR.tsv@@ -369,3 +369,6 @@ filter	Installed	Instalados 	Show {n} more changes	Mostrar mais {n} alterações 	Last updated: {date}	Última atualização: {date} column	Version	Versão++	Interface size	Tamanho da interface+	Follow display scale	Usar a escala da teladiff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv--- a/crates/ui-egui/src/i18n/ru.tsv+++ b/crates/ui-egui/src/i18n/ru.tsv@@ -377,3 +377,6 @@ filter	Installed	Установлены 	Show {n} more changes	Показать ещё изменений: {n} 	Last updated: {date}	Последнее обновление: {date} column	Version	Версия++	Interface size	Размер интерфейса+	Follow display scale	Использовать масштаб экранаdiff --git a/crates/ui-egui/src/i18n/zh-CN.tsv b/crates/ui-egui/src/i18n/zh-CN.tsv--- a/crates/ui-egui/src/i18n/zh-CN.tsv+++ b/crates/ui-egui/src/i18n/zh-CN.tsv@@ -349,3 +349,6 @@ figcraft	Interface design and prototyping	界面设计与原型制作 	Show {n} more changes	显示另外 {n} 项更改 	Last updated: {date}	最后更新:{date} column	Version	版本++	Interface size	界面大小+	Follow display scale	跟随显示缩放diff --git a/crates/ui-egui/src/settings.rs b/crates/ui-egui/src/settings.rs--- a/crates/ui-egui/src/settings.rs+++ b/crates/ui-egui/src/settings.rs@@ -93,6 +93,8 @@ pub struct Settings {     pub sort: SortKey,     pub sort_descending: bool,     pub theme: ThemeChoice,+    /// Absolute interface size in percent, or zero to follow the display scale.+    pub interface_size: u16,     /// Interface language: `auto` (follow the system) or a language code from the registry     /// (`en`, `ru`, `zh-CN`, `de`, `fr`, `pt-BR`, `pl`).     pub language: String,@@ -130,6 +132,9 @@ impl Settings {      /// Clear saved catalog filters whose controls were removed from the toolbar.     pub fn migrate(&mut self) {+        if self.interface_size != 0 {+            self.interface_size = self.interface_size.clamp(50, 300);+        }         if !matches!(self.filter, Filter::All | Filter::Hidden) {             self.filter = Filter::All;         }@@ -190,6 +195,7 @@ impl Default for Settings {             sort: SortKey::Suite,             sort_descending: false,             theme: ThemeChoice::System,+            interface_size: 0,             language: "auto".to_string(),             close_to_tray: true,             show_on_current_desktop: true,diff --git a/crates/ui-egui/src/settings_dialog.rs b/crates/ui-egui/src/settings_dialog.rs--- a/crates/ui-egui/src/settings_dialog.rs+++ b/crates/ui-egui/src/settings_dialog.rs@@ -351,6 +351,18 @@ impl LauncherUi {     fn settings_appearance(&mut self, ui: &mut Ui) {         let mut theme_choice = self.settings.theme;         sheet::panel(ui, tl!("Appearance"), |ui| {+            ui.horizontal(|ui| {+                ui.label(tl!("Interface size"));+                let percent = &mut self.settings.interface_size;+                let mut automatic = *percent == 0;+                if ui.checkbox(&mut automatic, tl!("Follow display scale")).changed() {+                    *percent = if automatic { 0 } else { 100 };+                }+                let mut value = if automatic { 100 } else { (*percent).clamp(50, 300) };+                if ui.add_enabled(!automatic, egui::DragValue::new(&mut value).range(50..=300).suffix("%")).changed() {+                    *percent = value;+                }+            });             ui.horizontal(|ui| {                 widgets::cells(ui, &mut theme_choice, &ThemeChoice::ALL.map(|c| (c, Cell { label: c.label(), tip: c.label(), count: None })));             });