From 90788b632afd76f9e3232346f0a62f8b0928a3cb Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Mon, 26 Oct 2026 09:50:51 -0700 Subject: [PATCH 1/7] Linux: VectorCraft draws its own title bar on every desktop unless System Title Bar is on MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On KDE Plasma, Sway, Hyprland, COSMIC and X11 window managers the window kept the desktop's frame, and the app bar with its brand mark sat below it. Where the desktop draws a title bar, as KDE Plasma does, the window showed two. When the desktop's bar was hidden through the window manager, the app bar left behind did not move the window and had no caption buttons. On Linux and the BSDs VectorCraft now draws its own title bar on every desktop unless Preferences › User Interface › System Title Bar is on, as PhotoCraft does. The craft-window probe no longer picks the frame. VECTORCRAFT_TITLEBAR decides for the run it is set in: app draws VectorCraft's bar and system or desktop keeps the desktop's frame, also when the preference says otherwise. Before, System Title Bar on gave the desktop's frame even with app. macOS and Windows are unchanged. When the variable keeps the desktop's frame, the app bar leaves out the brand mark, as it does with System Title Bar. While the variable is set, Preferences dims System Title Bar and says that the variable chooses the title bar; the dialog's __titlebarFromEnv field is true. The note has a row in the 24 catalogs. Docs: docs/control-protocol.md, docs/development.md, docs/target-app-parity.md. --- apps/vectorcraft/src/main.rs | 126 +++++++++++++--------------- crates/ui-egui/src/chrome.rs | 24 ++++-- crates/ui-egui/src/i18n/bg.tsv | 1 + crates/ui-egui/src/i18n/cs.tsv | 1 + crates/ui-egui/src/i18n/da.tsv | 1 + crates/ui-egui/src/i18n/de.tsv | 1 + crates/ui-egui/src/i18n/el.tsv | 1 + crates/ui-egui/src/i18n/es.tsv | 1 + crates/ui-egui/src/i18n/fi.tsv | 1 + crates/ui-egui/src/i18n/fr.tsv | 1 + crates/ui-egui/src/i18n/hu.tsv | 1 + crates/ui-egui/src/i18n/id.tsv | 1 + crates/ui-egui/src/i18n/it.tsv | 1 + crates/ui-egui/src/i18n/ja.tsv | 1 + crates/ui-egui/src/i18n/ka.tsv | 1 + crates/ui-egui/src/i18n/ko.tsv | 1 + crates/ui-egui/src/i18n/nl.tsv | 1 + crates/ui-egui/src/i18n/pl.tsv | 1 + crates/ui-egui/src/i18n/pt-br.tsv | 1 + crates/ui-egui/src/i18n/ru.tsv | 1 + crates/ui-egui/src/i18n/sv.tsv | 1 + crates/ui-egui/src/i18n/tr.tsv | 1 + crates/ui-egui/src/i18n/uk.tsv | 1 + crates/ui-egui/src/i18n/vi.tsv | 1 + crates/ui-egui/src/i18n/zh-hans.tsv | 1 + crates/ui-egui/src/i18n/zh-hant.tsv | 1 + crates/ui-egui/src/lib.rs | 4 + crates/ui-egui/src/prefs_dialog.rs | 70 +++++++++++++++- docs/control-protocol.md | 6 +- docs/development.md | 2 +- docs/target-app-parity.md | 1 + 31 files changed, 178 insertions(+), 79 deletions(-) diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index ee4232dc3..a92cda204 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -705,11 +705,11 @@ fn adapter_summary(info: &eframe::wgpu::AdapterInfo) -> String { format!("{} ({:?}, {:?})", info.name.trim(), info.backend, info.device_type) } -/// Windows and Linux: no OS title bar; the app bar is the title bar (`vectorcraft_ui_egui::titlebar`). +/// Windows: no OS title bar; the app bar is the title bar (`vectorcraft_ui_egui::titlebar`). /// macOS keeps its traffic lights over the integrated title strip. /// -/// Used on macOS and Windows (where VectorCraft keeps today's frame regardless of the -/// `craft-window` probe) and by the tests; Linux and the BSDs take the decision from the probe. +/// Used on macOS and Windows; Linux and the BSDs decide in [`custom_titlebar_from`], where +/// VectorCraft also draws its own title bar by default. #[cfg_attr(not(any(target_os = "macos", target_os = "windows")), allow(dead_code))] const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos"); @@ -720,60 +720,42 @@ fn opted_into_system_title_bar(saved: Option<&vectorcraft_ui_egui::UiState>) -> saved.map(|ui| ui.engine_prefs.get("systemTitleBar").and_then(serde_json::Value::as_bool).unwrap_or(false)).unwrap_or(false) } -/// Whether this start draws its own title bar, given the frame the shared `craft-window` crate -/// decided and the user's System Title Bar preference. +/// Whether this start draws its own title bar, given `VECTORCRAFT_TITLEBAR` (`overridden`, as +/// [`craft_window::Choice::overridden`] reads it) and the user's System Title Bar preference. /// -/// The suite-wide rule (`craft_window::Choice`) is: the desktop draws the frame -/// ([`craft_window::Frame::Desktop`]) on macOS, Windows, X11 and any Wayland compositor with -/// server-side decorations (KDE, Sway, Hyprland, COSMIC); the app draws it -/// ([`craft_window::Frame::App`]) only where the compositor leaves it to the app (GNOME's Mutter), -/// or when `VECTORCRAFT_TITLEBAR` overrides the probe. VectorCraft draws its own title bar exactly -/// when `craft-window` says the app owns the frame **and** the user has not opted into the system -/// title bar; the preference always forces the native frame back on. -/// -/// macOS and Windows keep today's behavior and are decided by [`CUSTOM_TITLEBAR`] rather than the -/// probe: `craft-window` returns `Frame::Desktop` for both (the OS always draws a frame), but -/// VectorCraft insets its content under the macOS traffic lights and draws its own caption buttons -/// on Windows — an overdraw on an OS-decorated window that is VectorCraft's choice, not -/// `craft-window`'s. Only Linux and the BSDs take the decision from the probe. +/// Linux and the BSDs: VectorCraft draws its own title bar on every desktop (GNOME, KDE, Sway, +/// Hyprland, COSMIC, X11 window managers) unless System Title Bar is on, as PhotoCraft does. The +/// variable decides for one run when it is set: `app` draws VectorCraft's bar and `system` or +/// `desktop` keeps the desktop's frame, whatever the preference says. The desktop the +/// `craft-window` probe finds does not change the result. #[cfg(not(any(target_os = "macos", target_os = "windows")))] -fn custom_titlebar_from(frame: &craft_window::Frame, opted_into_system: bool) -> bool { - matches!(frame, craft_window::Frame::App(_)) && !opted_into_system +fn custom_titlebar_from(overridden: Option, opted_into_system: bool) -> bool { + overridden.unwrap_or(!opted_into_system) } -/// macOS and Windows: today's behavior, independent of the `craft-window` probe (see -/// [`custom_titlebar_from`]). macOS never draws its own frame (`CUSTOM_TITLEBAR` is `false`); -/// Windows draws its own caption unless the user opted into the system title bar. +/// macOS and Windows: decided by [`CUSTOM_TITLEBAR`] and the preference; `VECTORCRAFT_TITLEBAR` +/// is not read. macOS never draws its own frame (`CUSTOM_TITLEBAR` is `false`); Windows draws its +/// own caption unless the user opted into the system title bar. #[cfg(any(target_os = "macos", target_os = "windows"))] -fn custom_titlebar_from(_frame: &craft_window::Frame, opted_into_system: bool) -> bool { +fn custom_titlebar_from(_overridden: Option, opted_into_system: bool) -> bool { CUSTOM_TITLEBAR && !opted_into_system } -/// Whether this start draws its own title bar. Probes the shared `craft-window` crate once (its -/// override variable is `VECTORCRAFT_TITLEBAR`) and combines its decision with the saved System -/// Title Bar preference. Call once at startup, before [`native_options`]. -/// -/// On Linux, Preferences › Performance › Linux Display Server › X11 opens the window through -/// XWayland (below). A GNOME/Wayland probe would say `Frame::App` there, but the window really runs -/// on X11, whose window manager draws the frame — so when XWayland is being forced, keep the native -/// frame, the same as a real X11 session. An explicit `VECTORCRAFT_TITLEBAR` override still wins. -fn custom_titlebar(saved: Option<&vectorcraft_ui_egui::UiState>) -> bool { - let choice = craft_window::Choice::probe(craft_window::Prefix::new("VECTORCRAFT")); - let opted_into_system = opted_into_system_title_bar(saved); - #[cfg(target_os = "linux")] - { - let pref = saved.and_then(|ui| ui.engine_prefs.get("linuxDisplayServer")).and_then(serde_json::Value::as_str); - // `overridden() == None` means the probe decided (no VECTORCRAFT_TITLEBAR). When the window - // will actually open on X11 (XWayland) — the user asked for it *and* the X11 libraries are - // present, the same two conditions the launch path checks below — that run is effectively - // X11, so keep the native frame. If the libraries are missing, startup stays on Wayland, so - // the probe's own decision (app-drawn on GNOME) must stand. - let forced_to_x11 = xwayland::wanted(pref, |name| std::env::var_os(name), xwayland::pen_attached) && xwayland::x11_libraries_found(); - if choice.overridden().is_none() && forced_to_x11 { - return custom_titlebar_from(&craft_window::Frame::Desktop, opted_into_system); - } +/// What `VECTORCRAFT_TITLEBAR` asks for on Linux and the BSDs: `Some(true)` VectorCraft's own title +/// bar, `Some(false)` the desktop's frame, `None` when it is unset or holds another value. Always +/// `None` on macOS and Windows. The `craft-window` probe runs once per process. +fn titlebar_override() -> Option { + static OVERRIDE: std::sync::OnceLock> = std::sync::OnceLock::new(); + if cfg!(any(target_os = "macos", target_os = "windows")) { + return None; } - custom_titlebar_from(choice.frame(), opted_into_system) + *OVERRIDE.get_or_init(|| craft_window::Choice::probe(craft_window::Prefix::new("VECTORCRAFT")).overridden()) +} + +/// Whether this start draws its own title bar: [`custom_titlebar_from`] with `VECTORCRAFT_TITLEBAR` +/// and the saved System Title Bar preference. Call once at startup, before [`native_options`]. +fn custom_titlebar(saved: Option<&vectorcraft_ui_egui::UiState>) -> bool { + custom_titlebar_from(titlebar_override(), opted_into_system_title_bar(saved)) } fn native_options(custom_titlebar: bool) -> eframe::NativeOptions { @@ -1128,6 +1110,7 @@ fn main() -> std::process::ExitCode { app.graphics_adapter = Some("OpenGL (Windows 7 compatibility)".into()); } app.custom_titlebar = custom_titlebar; + app.titlebar_from_env = titlebar_override().is_some(); if let Some(port) = control_port { let rx = control_server::start(port, cc.egui_ctx.clone()); app = app.with_control(rx).with_automation_roots(roots.clone()); @@ -1314,8 +1297,6 @@ mod tests { #[test] fn the_system_title_bar_preference_keeps_the_window_decorations() { - use craft_window::{Frame, Layout}; - // The System Title Bar preference (Preferences › User Interface) is read the same way on // every platform; a missing or unreadable value is `false`. assert!(!super::opted_into_system_title_bar(None), "no preferences: the default"); @@ -1324,33 +1305,40 @@ mod tests { let on = ui_state_with_prefs(serde_json::json!({"systemTitleBar": true})); assert!(super::opted_into_system_title_bar(Some(&on))); - // The suite-wide mapping (Frame + preference -> whether VectorCraft draws its own title - // bar). The desktop frame (macOS, Windows, X11, KDE/Wayland, …) always keeps native - // decorations; the app frame (GNOME/Wayland, or a `VECTORCRAFT_TITLEBAR` override) draws - // our own title bar — unless the user opted into the system title bar, which forces the - // native frame back on. - let app = Frame::App(Layout::default()); - let desktop = Frame::Desktop; - #[cfg(not(any(target_os = "macos", target_os = "windows")))] - { - assert!(super::custom_titlebar_from(&app, false), "GNOME/Wayland: VectorCraft draws its own"); - assert!(!super::custom_titlebar_from(&app, true), "System Title Bar preference forces native"); - assert!(!super::custom_titlebar_from(&desktop, false), "KDE/X11/…: keep the compositor's frame"); - assert!(!super::custom_titlebar_from(&desktop, true)); - } - // macOS and Windows keep today's behavior, independent of the probed frame: the pref still - // forces the native frame back on where a custom one would otherwise be drawn. + // macOS and Windows: `VECTORCRAFT_TITLEBAR` is not read, and the preference turns the + // native frame back on where VectorCraft would draw its own. #[cfg(any(target_os = "macos", target_os = "windows"))] { - assert_eq!(super::custom_titlebar_from(&desktop, false), super::CUSTOM_TITLEBAR); - assert_eq!(super::custom_titlebar_from(&app, false), super::CUSTOM_TITLEBAR, "the probe does not change macOS/Windows"); - assert!(!super::custom_titlebar_from(&desktop, true)); + assert_eq!(super::titlebar_override(), None); + for overridden in [None, Some(true), Some(false)] { + assert_eq!(super::custom_titlebar_from(overridden, false), super::CUSTOM_TITLEBAR, "{overridden:?}"); + assert!(!super::custom_titlebar_from(overridden, true), "{overridden:?}"); + } } assert_eq!(super::native_options(false).viewport.decorations, Some(true)); assert_eq!(super::native_options(true).viewport.decorations, Some(false)); } + /// Linux and the BSDs: VectorCraft's own title bar on every desktop unless System Title Bar is + /// on, and `VECTORCRAFT_TITLEBAR` decides over the preference for the run it is set in. + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + #[test] + fn every_linux_desktop_gets_the_app_title_bar_unless_system_title_bar_is_on() { + // What the `craft-window` probe reports: the desktop's frame on KDE Plasma, Sway, Hyprland, + // COSMIC and X11, the app's on GNOME's Wayland session; none of them sets the override. + for desktop in ["GNOME Wayland", "KDE Plasma", "Sway", "Hyprland", "COSMIC", "X11 window manager", "GNOME on X11"] { + assert!(super::custom_titlebar_from(None, false), "{desktop}: VectorCraft's own title bar"); + assert!(!super::custom_titlebar_from(None, true), "{desktop}: System Title Bar keeps the desktop's frame"); + } + // VECTORCRAFT_TITLEBAR=app draws VectorCraft's bar and =system (or =desktop) keeps the + // desktop's frame, whatever the preference says. + for opted_into_system in [false, true] { + assert!(super::custom_titlebar_from(Some(true), opted_into_system), "app, preference {opted_into_system}"); + assert!(!super::custom_titlebar_from(Some(false), opted_into_system), "system, preference {opted_into_system}"); + } + } + /// The file extensions the macOS bundle declares: its document types and its own exported type. fn plist_extensions(plist: &str) -> Vec<&str> { ["CFBundleTypeExtensions", "public.filename-extension"] diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 5eeda2590..e8340df52 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -111,12 +111,13 @@ pub fn app_bar(app: &mut VectorcraftApp, ui: &mut Ui) { } /// Whether the window shows the system's title bar instead of the app drawing its own: Windows -/// and Linux with Preferences › User Interface › System Title Bar. macOS always has system -/// decorations, but keeps the in-app brand mark, so it never counts as system mode here; nor -/// does the web build (the browser tab has no document title), nor an embedder that never -/// turned the preference on. +/// and Linux with Preferences › User Interface › System Title Bar, and Linux when +/// `VECTORCRAFT_TITLEBAR` kept the desktop's frame ([`VectorcraftApp::titlebar_from_env`]). macOS +/// always has system decorations, but keeps the in-app brand mark, so it never counts as system +/// mode here; nor does the web build (the browser tab has no document title), nor an embedder +/// that never turned the preference on. pub fn system_title_bar(app: &VectorcraftApp) -> bool { - !app.custom_titlebar && app.session.prefs.system_title_bar && !cfg!(any(target_os = "macos", target_arch = "wasm32")) + !app.custom_titlebar && (app.session.prefs.system_title_bar || app.titlebar_from_env) && !cfg!(any(target_os = "macos", target_arch = "wasm32")) } /// The OS window title: the active document's name suffixed with the app name (a `*` prefix @@ -2216,6 +2217,19 @@ mod tests { } } + #[test] + fn a_desktop_frame_from_the_environment_variable_is_system_mode() { + // VECTORCRAFT_TITLEBAR=system with System Title Bar off: the desktop draws the title bar. + let mut app = crate::VectorcraftApp::new(Session::new(), Default::default()); + app.titlebar_from_env = true; + assert!(!app.session.prefs.system_title_bar); + assert_eq!(super::system_title_bar(&app), !cfg!(target_os = "macos")); + // VECTORCRAFT_TITLEBAR=app: VectorCraft draws it, whatever the preference says. + app.custom_titlebar = true; + app.run("prefs.set", json!({"key": "systemTitleBar", "value": true})).unwrap(); + assert!(!super::system_title_bar(&app)); + } + #[test] fn sync_window_title_sends_only_on_change() { let mut app = title_test_app(true); diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index f4f5b1fc1..197ba640b 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -190,6 +190,7 @@ font-technology Variable Променливи Appearance panel Панел „Външен вид“ Applies when the linked file is relinked or updated. Прилага се, когато свързаният файл бъде пресвързан или актуализиран. Applies the next time VectorCraft starts. Прилага се при следващото стартиране на VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Докато е зададена, променливата на средата VECTORCRAFT_TITLEBAR избира заглавната лента. Apply Прилагане Apply Brush Прилагане на четка Apply Character Style Прилагане на стил на знаци diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 07c73f238..094e937d5 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3890,6 +3890,7 @@ font-technology Variable Variabilní Panel menu Nabídka panelu Unembed… Zrušit vložení… Applies the next time VectorCraft starts. Projeví se při příštím spuštění VectorCraftu. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Dokud je proměnná prostředí VECTORCRAFT_TITLEBAR nastavena, záhlaví okna volí ona. Enable Contextual Task Bar Povolit kontextový panel úloh Next Další OK OK diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 994f115ab..b0bdbb99f 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3633,6 +3633,7 @@ font-technology Variable Variable Panel menu Panelmenu Unembed… Ophæv integrering… Applies the next time VectorCraft starts. Anvendes næste gang VectorCraft starter. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Miljøvariablen VECTORCRAFT_TITLEBAR vælger titellinjen, så længe den er angivet. Enable Contextual Task Bar Aktivér kontekstuel opgavelinje Next Næste OK OK diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index a3534cce1..88ede5921 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -179,6 +179,7 @@ font-technology Variable Variabel Appearance panel Bedienfeld „Aussehen“ Applies when the linked file is relinked or updated. Gilt, wenn die verknüpfte Datei neu verknüpft oder aktualisiert wird. Applies the next time VectorCraft starts. Gilt ab dem nächsten Start von VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Solange die Umgebungsvariable VECTORCRAFT_TITLEBAR gesetzt ist, bestimmt sie die Titelleiste. Apply Anwenden Apply Brush Pinsel anwenden Apply Character Style Zeichenformat anwenden diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index f537725c0..aa1bb7452 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -183,6 +183,7 @@ font-technology Variable Μεταβλητές Appearance Panel Πίνακας εμφάνισης Appearance panel Πίνακας εμφάνισης Applies the next time VectorCraft starts. Εφαρμόζεται στην επόμενη εκκίνηση του VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Όσο είναι ορισμένη, η μεταβλητή περιβάλλοντος VECTORCRAFT_TITLEBAR επιλέγει τη γραμμή τίτλου. Apply Εφαρμογή Apply Brush Εφαρμογή πινέλου Apply Character Style Εφαρμογή στυλ χαρακτήρων diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index da1f4b362..86bab2cf7 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -204,6 +204,7 @@ font-technology Variable Variables Appearance panel Panel Apariencia Applies when the linked file is relinked or updated. Se aplica al volver a vincular o actualizar el archivo vinculado. Applies the next time VectorCraft starts. Se aplica la próxima vez que se inicie VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Mientras está definida, la variable de entorno VECTORCRAFT_TITLEBAR elige la barra de título. Apply Aplicar Apply Brush Aplicar pincel Apply Character Style Aplicar estilo de carácter diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 1ebb354f9..b3b5eac8f 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3814,6 +3814,7 @@ font-technology Variable Muuttuvat Panel menu Paneelivalikko Unembed… Poista upotus… Applies the next time VectorCraft starts. Tulee voimaan, kun VectorCraft käynnistetään seuraavan kerran. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Ympäristömuuttuja VECTORCRAFT_TITLEBAR valitsee otsikkorivin niin kauan kuin se on asetettu. Enable Contextual Task Bar Ota kontekstuaalinen tehtäväpalkki käyttöön Next Seuraava OK OK diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 943ca1140..9e9d6f113 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -205,6 +205,7 @@ font-technology Variable Variables Appearance panel Panneau Aspect Applies when the linked file is relinked or updated. S’applique lorsque le fichier lié est de nouveau lié ou mis à jour. Applies the next time VectorCraft starts. S’applique au prochain démarrage de VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Tant qu’elle est définie, la variable d’environnement VECTORCRAFT_TITLEBAR choisit la barre de titre. Apply Appliquer Apply Brush Appliquer la forme Apply Character Style Appliquer le style de caractère diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 670c93a99..7c8050626 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -286,6 +286,7 @@ font-technology Variable Változó Appearance panel Megjelenés panel Appearance Panel Megjelenés panel Applies the next time VectorCraft starts. A VectorCraft következő indításakor lép életbe. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Amíg a VECTORCRAFT_TITLEBAR környezeti változó be van állítva, az választja ki a címsort. Applies when the linked file is relinked or updated. A csatolt fájl újracsatolásakor vagy frissítésekor érvényes. Apply Alkalmaz Apply Brush Ecset alkalmazása diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index c91631178..cb01172bd 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3633,6 +3633,7 @@ font-technology Variable Variabel Panel menu Menu panel Unembed… Lepaskan sematan… Applies the next time VectorCraft starts. Berlaku saat VectorCraft dimulai berikutnya. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Selama ditetapkan, variabel lingkungan VECTORCRAFT_TITLEBAR memilih bilah judul. Enable Contextual Task Bar Aktifkan bilah tugas kontekstual Next Berikutnya OK OK diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index cf329b283..8fca3e13e 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -204,6 +204,7 @@ font-technology Variable Variabili Appearance panel Pannello Aspetto Applies when the linked file is relinked or updated. Si applica quando il file collegato viene ricollegato o aggiornato. Applies the next time VectorCraft starts. Si applica al prossimo avvio di VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Finché è impostata, la variabile d'ambiente VECTORCRAFT_TITLEBAR sceglie la barra del titolo. Apply Applica Apply Brush Applica pennello Apply Character Style Applica stile carattere diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 7449d1f09..6c586d2eb 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -920,6 +920,7 @@ font-technology Variable バリアブル Appearance of Black ブラックのアピアランス Appearance panel アピアランスパネル Applies the next time VectorCraft starts. VectorCraft の次回起動時に適用されます。 + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. 環境変数 VECTORCRAFT_TITLEBAR が設定されている間は、この変数でタイトルバーが決まります。 Applies when the linked file is relinked or updated. リンクファイルを再リンクまたは更新したときに適用されます。 Apply Brush ブラシを適用 Apply Character Style 文字スタイルを適用 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index b22d3d09a..19e939a95 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3412,6 +3412,7 @@ font-technology Variable ცვლადი Panel menu პანელის მენიუ Unembed… ჩაშენების მოხსნა… Applies the next time VectorCraft starts. ძალაში შევა VectorCraft-ის მომდევნო გაშვებისას. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. სანამ გარემოს ცვლადი VECTORCRAFT_TITLEBAR დაყენებულია, სათაურის ზოლს ის ირჩევს. Enable Contextual Task Bar კონტექსტური ამოცანების ზოლის ჩართვა Next შემდეგი OK OK diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 6d64b057b..78d691dfe 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -915,6 +915,7 @@ font-technology Variable 가변 Appearance of Black 검정 표시 방식 Appearance panel 모양새 패널 Applies the next time VectorCraft starts. 다음에 VectorCraft를 시작할 때 적용됩니다. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. 환경 변수 VECTORCRAFT_TITLEBAR가 설정되어 있는 동안에는 이 변수가 제목 표시줄을 정합니다. Applies when the linked file is relinked or updated. 링크된 파일을 다시 연결하거나 업데이트할 때 적용됩니다. Apply Brush 브러시 적용 Apply Character Style 문자 스타일 적용 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index b0e9ed9b8..52ad92ce3 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3647,6 +3647,7 @@ font-technology Variable Variabel Panel menu Paneelmenu Unembed… Insluiten ongedaan maken… Applies the next time VectorCraft starts. Wordt toegepast de volgende keer dat VectorCraft start. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Zolang de omgevingsvariabele VECTORCRAFT_TITLEBAR is ingesteld, kiest die de titelbalk. Enable Contextual Task Bar Contextuele taakbalk inschakelen Next Volgende OK OK diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 8a52c4c51..b82d72bbd 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3729,6 +3729,7 @@ font-technology Variable Zmienne Panel menu Menu panelu Unembed… Wyodrębnij… Applies the next time VectorCraft starts. Zostanie zastosowane przy następnym uruchomieniu VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Dopóki zmienna środowiskowa VECTORCRAFT_TITLEBAR jest ustawiona, to ona wybiera pasek tytułu. Enable Contextual Task Bar Włącz kontekstowy pasek zadań Next Dalej OK OK diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 3f3bdccb2..42b97e938 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -199,6 +199,7 @@ Appearance of Black Aparência do Preto Appearance panel Painel de Aparência Applies the next time VectorCraft starts. Aplica na próxima inicialização do VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Enquanto estiver definida, a variável de ambiente VECTORCRAFT_TITLEBAR escolhe a barra de título. Applies when the linked file is relinked or updated. Aplica quando o arquivo vinculado é revinculado ou atualizado. Apply Brush Aplicar Pincel Apply Character Style Aplicar Estilo de Caractere diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 59f8cd4f0..85ae7ece8 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -202,6 +202,7 @@ font-technology Variable Вариативные Appearance panel Панель «Оформление» Applies when the linked file is relinked or updated. Применяется при повторной привязке или обновлении связанного файла. Applies the next time VectorCraft starts. Применяется при следующем запуске VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Пока переменная среды VECTORCRAFT_TITLEBAR задана, строку заголовка выбирает она. Apply Применить Apply Brush Применить кисть Apply Character Style Применить стиль символа diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index c46f2970f..1e1700e43 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -192,6 +192,7 @@ font-technology Variable Variabla Appearance panel Utseendepanelen Applies when the linked file is relinked or updated. Gäller när den länkade filen länkas om eller uppdateras. Applies the next time VectorCraft starts. Gäller nästa gång VectorCraft startas. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Miljövariabeln VECTORCRAFT_TITLEBAR väljer namnlisten så länge den är angiven. Apply Använd Apply Brush Använd pensel Apply Character Style Använd teckenstil diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index ce6b2229e..31d4a4e67 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -189,6 +189,7 @@ font-technology Variable Değişken Appearance panel Görünüm paneli Applies when the linked file is relinked or updated. Bağlantılı dosya yeniden bağlandığında veya güncellendiğinde geçerlidir. Applies the next time VectorCraft starts. VectorCraft'ın bir sonraki başlatılışında uygulanır. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. VECTORCRAFT_TITLEBAR ortam değişkeni ayarlı olduğu sürece başlık çubuğunu o seçer. Apply Uygula Apply Brush Fırça Uygula Apply Character Style Karakter Stilini Uygula diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 65384df71..2ab613e4c 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -198,6 +198,7 @@ font-technology Variable Варіативні Appearance panel Панель «Оформлення» Applies when the linked file is relinked or updated. Використовується при повторній прив’язці або оновленні пов’язаного файлу. Applies the next time VectorCraft starts. Застосовується при наступному запуску VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Поки змінну середовища VECTORCRAFT_TITLEBAR задано, рядок заголовка вибирає вона. Apply Застосувати Apply Brush Застосувати пензель Apply Character Style Застосувати стиль символу diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index d9ab99612..82ce7e7f3 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -221,6 +221,7 @@ font-technology Variable Biến thiên Appearance Panel Bảng Diện mạo Appearance panel Bảng diện mạo Applies the next time VectorCraft starts. Áp dụng vào lần tiếp theo VectorCraft khởi động. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Khi biến môi trường VECTORCRAFT_TITLEBAR được đặt, biến này chọn thanh tiêu đề. Applies when the linked file is relinked or updated. Áp dụng khi tệp liên kết được liên kết lại hoặc cập nhật. Apply Áp dụng Apply Brush Áp dụng cọ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 0daa3138f..66b79c6a4 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4663,6 +4663,7 @@ imageTrace Outline 轮廓 Font Technology 字体技术 font-technology Variable 可变 Applies the next time VectorCraft starts. 下次启动 VectorCraft 时生效。 + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. 设置了环境变量 VECTORCRAFT_TITLEBAR 时,由它选择标题栏。 Collapse to Icons 折叠为图标 Decorative 装饰 Dim Images to: 图像变暗至: diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 427c20da2..1a11d526d 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -162,6 +162,7 @@ font-technology Variable 可變 Appearance panel 外觀面板 Applies when the linked file is relinked or updated. 重新連結或更新連結檔案時套用。 Applies the next time VectorCraft starts. 下次啟動 VectorCraft 時生效。 + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. 設定了環境變數 VECTORCRAFT_TITLEBAR 時,由它選擇標題列。 Apply 套用 Apply Brush 套用筆刷 Apply Character Style 套用字元樣式 diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 4efc27211..c3c55d6ee 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -521,6 +521,9 @@ pub struct VectorcraftApp { /// Windows and Linux: the window has no OS decorations, so the app bar is the title bar (drag, /// double-click to maximize, caption buttons) and invisible edge zones resize the window. pub custom_titlebar: bool, + /// `VECTORCRAFT_TITLEBAR` chose this window's frame (Linux and the BSDs), so Preferences › User + /// Interface › System Title Bar does not apply while it is set. `false` unless the host sets it. + pub titlebar_from_env: bool, /// Last window title sent to the OS (`ViewportCommand::Title`, see `chrome::sync_window_title`): /// sent again only when it changes, so idle frames don't spam the backend. pub(crate) last_window_title: String, @@ -813,6 +816,7 @@ impl VectorcraftApp { canvas_rect: None, hover_doc: None, custom_titlebar: false, + titlebar_from_env: false, last_window_title: String::new(), graphics_adapter: None, place: Default::default(), diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index 9057aeb13..afe1af349 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -32,6 +32,8 @@ pub fn open(app: &mut VectorcraftApp, category: Option<&str>) { fields.insert("__smartGuides".into(), json!(app.ui.view.smart_guides)); fields.insert("__snapToGrid".into(), json!(app.ui.view.snap_to_grid)); fields.insert("__taskBar".into(), json!(app.ui.task_bar)); + // Read-only: whether VECTORCRAFT_TITLEBAR chose this window's frame (System Title Bar's note). + fields.insert("__titlebarFromEnv".into(), json!(app.titlebar_from_env)); app.ui.dialog = Some(Dialog { kind: "preferences".into(), fields }); } @@ -278,9 +280,16 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) { // Windows and Linux only: macOS always has the system's title bar, the web has none. PrefKind::Bool if sp.key == "systemTitleBar" => { if cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) { - bool_row(ui, d, sp.key, sp.label); - // The window's decorations are chosen once, when the app starts. - ui.label(egui::RichText::new(tl!("Applies the next time VectorCraft starts.")).color(t.text_dim).size(11.0)); + // The window's decorations are chosen once, when the app starts; while + // VECTORCRAFT_TITLEBAR is set it chooses them, and the checkbox is dimmed. + let from_env = d.bool("__titlebarFromEnv"); + ui.add_enabled_ui(!from_env, |ui| bool_row(ui, d, sp.key, sp.label)); + let note = if from_env { + tl!("The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set.") + } else { + tl!("Applies the next time VectorCraft starts.") + }; + ui.label(egui::RichText::new(note).color(t.text_dim).size(11.0)); } } PrefKind::Bool => bool_row(ui, d, sp.key, sp.label), @@ -646,6 +655,61 @@ mod tests { assert_eq!(saved["engine_prefs"]["systemTitleBar"], json!(true)); } + /// While VECTORCRAFT_TITLEBAR chooses the frame, System Title Bar says so in place of the + /// next-launch note, and its checkbox does not take clicks. + #[test] + fn system_title_bar_names_the_environment_variable_that_overrides_it() { + fn texts(s: &egui::Shape, out: &mut Vec) { + match s { + egui::Shape::Text(t) => out.push(t.galley.text().to_string()), + egui::Shape::Vec(v) => v.iter().for_each(|s| texts(s, out)), + _ => {} + } + } + let note = "The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set."; + let supported = cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))); + for from_env in [false, true] { + let mut a = app(); + a.titlebar_from_env = from_env; + open(&mut a, Some("User Interface")); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + let mut shown = vec![]; + out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown)); + assert_eq!(shown.iter().any(|t| t == note), supported && from_env, "{shown:?}"); + assert_eq!(shown.iter().any(|t| t == "Applies the next time VectorCraft starts."), supported && !from_env, "{shown:?}"); + // A click on the checkbox's label: it turns the preference on only without the variable. + let label = out.shapes.iter().find_map(|c| match &c.shape { + egui::Shape::Text(t) if t.galley.text() == "System Title Bar" => Some(t.visual_bounding_rect()), + _ => None, + }); + if let Some(r) = label { + for pressed in [true, false] { + let input = egui::RawInput { + events: vec![ + egui::Event::PointerMoved(r.center()), + egui::Event::PointerButton { + pos: r.center(), + button: egui::PointerButton::Primary, + pressed, + modifiers: Default::default(), + }, + ], + ..Default::default() + }; + let mut out = ctx.run_ui(input, |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + } + assert_eq!(a.ui.dialog.as_ref().unwrap().bool("systemTitleBar"), !from_env, "clicked with the variable {from_env}"); + } + assert_eq!(label.is_some(), supported); + } + } + /// Performance › Linux Display Server (#2028): on Linux it shows the drawing-tablet note, and /// X11 (XWayland) When a Pen Is Attached is applied by OK and saved under the key the desktop /// app reads before the window opens. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 7bfbcfe16..3735659e9 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -310,7 +310,11 @@ not commands, so drive them with `ui.click` if needed. Close runs `app.quit` (th modified documents), and empty bar space and the 5 pt window edges move and resize the window. Preferences › User Interface › System Title Bar (`systemTitleBar`, read when the app starts) gives the window its OS decorations back instead: the OS title then shows the active document (`*name — VectorCraft` with unsaved changes) and the app bar -drops its brand mark. On macOS the window buttons are centred on the app bar; the web build is unchanged. +drops its brand mark. On Linux and the BSDs the environment variable `VECTORCRAFT_TITLEBAR` chooses the frame for one +run, whatever the preference says: `app` keeps the app bar as the title bar, and `system` or `desktop` gives the +window the desktop's decorations. While it is set, the Preferences dialog dims System Title Bar and its +`__titlebarFromEnv` field is `true`. On macOS the window buttons are centred on the app bar; the web build is +unchanged. Fill/Stroke chips and panel shortcuts: the Control bar's and Properties' Fill and Stroke chips bring their proxy forward (`paint.toggleActive {fill}`) and open a popover with the Swatches panel (Shift-click: the Color panel's mixer); diff --git a/docs/development.md b/docs/development.md index 74dc55a30..ac458332a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -109,7 +109,7 @@ On macOS the menus live in the system menu bar, not in the window's app bar (whi ## Linux: Wayland and X11 -On GNOME/Wayland, enable **Preferences › User Interface › System Title Bar** and restart to use winit's font-free Adwaita window controls (`wayland-csd-adwaita-notitle`); the existing custom title bar remains the Linux default. +On every Linux desktop the app bar is the window's title bar by default. Enable **Preferences › User Interface › System Title Bar** and restart to give the window the desktop's frame instead: KDE Plasma, Sway, Hyprland, COSMIC and X11 window managers draw it, and on GNOME/Wayland winit draws its font-free Adwaita window controls (`wayland-csd-adwaita-notitle`). `VECTORCRAFT_TITLEBAR=app`, `=system` or `=desktop` chooses the frame for one run, whatever the preference says. The window runs natively on Wayland (eframe's `wayland` feature) and on X11. On machines with two GPUs it renders on the one the desktop runs on unless Preferences say otherwise; see [Desktop graphics processor](#desktop-graphics-processor) (#502). The system clipboard (`apps/vectorcraft/src/clipboard.rs`) is arboard with its `wayland-data-control` feature: diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 4f5dbcba1..c2fa2912c 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -610,6 +610,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Cmd/Ctrl+Shift+X (`type.resetHorizontalScale`) sets the horizontal scale of the selected type, or of the text the Type tool has selected, to 100% as the Character panel shows it (the object's own stretch included), in one undo step; at a caret it leaves the text as it is. - Cmd/Ctrl+Alt+N (`file.newWithoutDialog`) makes a document without opening the New Document dialog, on the key the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists for making a new document with no New Document dialog box. The page does not say which settings the document gets. VectorCraft gives it the settings the dialog opens on (the first Recent entry, or the Print category's Letter while Recent is empty) and the next untitled name. - Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`). + - On Linux the app bar is the title bar on every desktop, KDE Plasma, Sway, Hyprland, COSMIC and X11 window managers included, unless Preferences › User Interface › System Title Bar gives the window the desktop's frame; `VECTORCRAFT_TITLEBAR=app` or `=system` chooses the frame for one run. - On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one. - **Tools:** - **Shaper:** uneven squares and rectangles become live rectangles even when drawing begins mid-edge. Recognition uses uniformly spaced samples and closed-loop corners, fits rectangle edges/angles rather than requiring a nearly perfect bounding-box fill, and includes the pointer-release position. Rectangles/squares and ellipses snap to 45° steps, hexagons to 90°, and triangles point up or down; lines keep their free angle even with Shift. Shaper Groups retain the live originals, merge regions with the gesture-origin fill, punch fills independently from strokes, remove boundaries on exiting scribbles and trim protruding stroke pieces. Source moves/resizes re-evaluate the result in the same undo step; double-click enters construction editing, and with the Shaper or Selection tool a click on the arrow widget beside a Shaper Group's original shapes switches the group between Face Selection and Construction mode. Native save/reopen, face recoloring, Release and Expand are covered. Finer gesture heuristics remain. From eb65e64882712989e72dea9b5348935696350a3e Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 28 Oct 2026 01:07:48 -0700 Subject: [PATCH 2/7] Docs: name VECTORCRAFT_TITLEBAR=desktop and qualify the Linux app bar line --- docs/target-app-parity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 09f540fa4..93492a652 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -610,8 +610,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Cmd/Ctrl+Shift+X (`type.resetHorizontalScale`) sets the horizontal scale of the selected type, or of the text the Type tool has selected, to 100% as the Character panel shows it (the object's own stretch included), in one undo step; at a caret it leaves the text as it is. - Cmd/Ctrl+Alt+N (`file.newWithoutDialog`) makes a document without opening the New Document dialog, on the key the reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists for making a new document with no New Document dialog box. The page does not say which settings the document gets. VectorCraft gives it the settings the dialog opens on (the first Recent entry, or the Print category's Letter while Recent is empty) and the next untitled name. - Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`). - - On Linux the app bar is the title bar on every desktop, KDE Plasma, Sway, Hyprland, COSMIC and X11 window managers included, unless Preferences › User Interface › System Title Bar gives the window the desktop's frame; `VECTORCRAFT_TITLEBAR=app` or `=system` chooses the frame for one run. - - On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one. + - On Linux the app bar is the title bar on every desktop, KDE Plasma, Sway, Hyprland, COSMIC and X11 window managers included, unless Preferences › User Interface › System Title Bar gives the window the desktop's frame; `VECTORCRAFT_TITLEBAR=app` chooses VectorCraft's bar and `=system` or `=desktop` (the two are the same) chooses the desktop's frame for one run. + - On Windows, and on Linux unless the desktop's frame is chosen, the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one. - On Windows a maximized window stays inside the work area with the taskbar on any edge (#3242, #3723). - On Windows a maximized window stops one pixel short of each monitor edge where an auto-hidden taskbar sits. - **Tools:** From 8b63892265337fec6a98f99754bfe4d36afa4008 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 29 Oct 2026 01:37:29 -0700 Subject: [PATCH 3/7] Linux: drop the xwayland import that the main merge left unused in titlebar.rs --- apps/vectorcraft/src/titlebar.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/vectorcraft/src/titlebar.rs b/apps/vectorcraft/src/titlebar.rs index 5ff29657f..280965456 100644 --- a/apps/vectorcraft/src/titlebar.rs +++ b/apps/vectorcraft/src/titlebar.rs @@ -1,6 +1,4 @@ use super::window; -#[cfg(target_os = "linux")] -use super::xwayland; /// The window, Dock, taskbar and app-switcher icon (`assets/app-icon/`, see its README). macOS gets /// the version with Apple's transparent margin; elsewhere the full-bleed tile. The app ID matches From 547b0aa2dd3d34780ff3fd9fc9b362992fce7e5c Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Thu, 29 Oct 2026 02:48:37 -0700 Subject: [PATCH 4/7] i18n: Catalan row for the VECTORCRAFT_TITLEBAR note in Preferences --- crates/ui-egui/src/i18n/ca.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 4022efa29..da234a9c9 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -303,6 +303,7 @@ Appearance panel Panell Aparença Applied Filters Filtres aplicats Applies the next time VectorCraft starts. S’aplica la propera vegada que s’iniciï VectorCraft. + The VECTORCRAFT_TITLEBAR environment variable chooses the title bar while it is set. Mentre està definida, la variable d’entorn VECTORCRAFT_TITLEBAR determina la barra de títol. Applies when the linked file is relinked or updated. S’aplica quan el fitxer enllaçat es torna a enllaçar o s’actualitza. Apply Aplica Apply Brush Aplica el pinzell From a1c908fbd20f5ec11f083d7649678c1d3078becf Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 00:06:04 -0700 Subject: [PATCH 5/7] Linux: test VECTORCRAFT_TITLEBAR read from the environment with System Title Bar off and on Runs the test binary again with VECTORCRAFT_TITLEBAR set to app, system, desktop and an unknown value, and checks titlebar_override, custom_titlebar with no saved preferences and with System Title Bar off and on, and the window decorations native_options sets before the window opens. On macOS and Windows the variable is not read. --- apps/vectorcraft/src/tests.rs | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/vectorcraft/src/tests.rs b/apps/vectorcraft/src/tests.rs index aead748a9..1cc5ba1e9 100644 --- a/apps/vectorcraft/src/tests.rs +++ b/apps/vectorcraft/src/tests.rs @@ -171,6 +171,45 @@ fn every_linux_desktop_gets_the_app_title_bar_unless_system_title_bar_is_on() { } } +/// `VECTORCRAFT_TITLEBAR` read from the environment at startup, with System Title Bar off and on. +/// The variable is process-wide and read once, so each value runs in a separate test process. +#[test] +fn the_titlebar_variable_decides_the_frame_before_the_window_opens() { + const CHILD: &str = "VECTORCRAFT_TEST_TITLEBAR_CHILD"; + if std::env::var_os(CHILD).is_none() { + for value in ["app", "system", "desktop", "unknown"] { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["--exact", "tests::the_titlebar_variable_decides_the_frame_before_the_window_opens", "--nocapture"]) + .env(CHILD, "1") + .env("VECTORCRAFT_TITLEBAR", value) + .output() + .unwrap(); + assert!( + output.status.success(), + "VECTORCRAFT_TITLEBAR={value}\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + return; + } + let value = std::env::var("VECTORCRAFT_TITLEBAR").unwrap(); + let expected = match value.as_str() { + _ if cfg!(any(target_os = "macos", target_os = "windows")) => None, + "app" => Some(true), + "system" | "desktop" => Some(false), + _ => None, + }; + assert_eq!(super::titlebar_override(), expected, "VECTORCRAFT_TITLEBAR={value}"); + let off = ui_state_with_prefs(serde_json::json!({"systemTitleBar": false})); + let on = ui_state_with_prefs(serde_json::json!({"systemTitleBar": true})); + for (saved, opted_into_system) in [(None, false), (Some(&off), false), (Some(&on), true)] { + let custom = super::custom_titlebar(saved); + assert_eq!(custom, super::custom_titlebar_from(expected, opted_into_system), "{value}, preference {opted_into_system}"); + assert_eq!(super::native_options(custom).viewport.decorations, Some(!custom), "{value}, preference {opted_into_system}"); + } +} + /// The file extensions the macOS bundle declares: its document types and its own exported type. fn plist_extensions(plist: &str) -> Vec<&str> { ["CFBundleTypeExtensions", "public.filename-extension"] From 5e1a50c75ae5ffe93187ce643248a9013a16ab88 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 1 Nov 2026 01:52:37 -0700 Subject: [PATCH 6/7] Linux: hold spawning while the title bar test starts its child process On Linux and the BSDs, the_titlebar_variable_decides_the_frame_before_the_window_opens now holds spawning() while it starts each child test process, as the portal save test does around dbus-daemon, so the child does not keep a copy of a GPU start test's locked start file. --- apps/vectorcraft/src/tests.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/vectorcraft/src/tests.rs b/apps/vectorcraft/src/tests.rs index 1cc5ba1e9..e6ebaedcf 100644 --- a/apps/vectorcraft/src/tests.rs +++ b/apps/vectorcraft/src/tests.rs @@ -178,12 +178,21 @@ fn the_titlebar_variable_decides_the_frame_before_the_window_opens() { const CHILD: &str = "VECTORCRAFT_TEST_TITLEBAR_CHILD"; if std::env::var_os(CHILD).is_none() { for value in ["app", "system", "desktop", "unknown"] { - let output = std::process::Command::new(std::env::current_exe().unwrap()) + // Linux and the BSDs: hold `spawning` while the child starts, so it does not keep a + // copy of a GPU start test's locked start file (see `CHILD_SPAWN`). + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))] + let spawning = super::spawning(); + let child = std::process::Command::new(std::env::current_exe().unwrap()) .args(["--exact", "tests::the_titlebar_variable_decides_the_frame_before_the_window_opens", "--nocapture"]) .env(CHILD, "1") .env("VECTORCRAFT_TITLEBAR", value) - .output() - .unwrap(); + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn(); + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))] + drop(spawning); + let output = child.unwrap().wait_with_output().unwrap(); assert!( output.status.success(), "VECTORCRAFT_TITLEBAR={value}\n{}\n{}", From 81149bb945727eea8b7a0c53c0fabb5b55096061 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sun, 8 Nov 2026 18:04:37 -0800 Subject: [PATCH 7/7] Linux: VECTORCRAFT_TITLEBAR takes precedence over System Title Bar for one run On Linux and the BSDs, VECTORCRAFT_TITLEBAR=app now uses the app bar as the title bar with System Title Bar on. Before, System Title Bar kept the desktop's frame while the Preferences note said the variable chooses the title bar. While the variable is set, Preferences dims System Title Bar, and the note that System Title Bar takes precedence over VectorCraft Title Bar on Every Desktop does not show. macOS and Windows do not read the variable. --- apps/vectorcraft/src/main.rs | 2 +- apps/vectorcraft/src/tests.rs | 73 ++++++++++++++++++++++++ apps/vectorcraft/src/titlebar.rs | 21 +++++-- crates/ui-egui/src/lib.rs | 4 +- crates/ui-egui/src/prefs_dialog.rs | 9 +-- crates/ui-egui/src/prefs_dialog/tests.rs | 41 ++++++++++++- docs/control-protocol.md | 4 +- docs/development.md | 2 +- docs/target-app-parity.md | 2 +- 9 files changed, 140 insertions(+), 18 deletions(-) diff --git a/apps/vectorcraft/src/main.rs b/apps/vectorcraft/src/main.rs index b3a1fd06b..fd062e504 100644 --- a/apps/vectorcraft/src/main.rs +++ b/apps/vectorcraft/src/main.rs @@ -97,7 +97,7 @@ use titlebar::opted_into_system_title_bar; #[cfg(windows)] use titlebar::set_taskbar_icon; #[cfg(all(test, feature = "wgpu"))] -use titlebar::{app_draws_frame, opted_into_app_title_bar, start_frame}; +use titlebar::{app_draws_frame, opted_into_app_title_bar, start_frame, system_title_bar_applies}; use titlebar::{custom_titlebar, native_options, titlebar_from_env}; struct App { diff --git a/apps/vectorcraft/src/tests.rs b/apps/vectorcraft/src/tests.rs index 555d829e0..e81dd7815 100644 --- a/apps/vectorcraft/src/tests.rs +++ b/apps/vectorcraft/src/tests.rs @@ -245,6 +245,79 @@ fn the_start_frame_applies_the_app_title_bar_preference() { assert!(!super::custom_titlebar_from(&both, true), "System Title Bar takes precedence over the app title bar preference"); } +/// `VECTORCRAFT_TITLEBAR` chooses the frame for the run it is set in, with System Title Bar off and +/// on. The saved preference still decides while the variable is unset. +#[test] +fn the_titlebar_variable_takes_precedence_over_system_title_bar() { + use craft_window::Frame; + assert!(super::system_title_bar_applies(true, false)); + assert!(!super::system_title_bar_applies(true, true)); + assert!(!super::system_title_bar_applies(false, false)); + assert!(!super::system_title_bar_applies(false, true)); + // VECTORCRAFT_TITLEBAR=app with System Title Bar on: VectorCraft draws its own title bar. + let frame = super::start_frame(Frame::Desktop, Some(true), false, false); + assert_eq!(super::custom_titlebar_from(&frame, super::system_title_bar_applies(true, true)), !cfg!(target_os = "macos")); + // VECTORCRAFT_TITLEBAR=desktop with System Title Bar off keeps the desktop's frame. + #[cfg(not(any(target_os = "macos", target_os = "windows")))] + { + let frame = super::start_frame(Frame::App(craft_window::Layout::default()), Some(false), false, false); + assert!(!super::custom_titlebar_from(&frame, super::system_title_bar_applies(false, true))); + } +} + +/// `VECTORCRAFT_TITLEBAR` read from the environment at startup, with System Title Bar off and on. +/// The variable is process-wide and read once, so each value runs in a separate test process. +#[test] +fn the_titlebar_variable_decides_the_frame_before_the_window_opens() { + const CHILD: &str = "VECTORCRAFT_TEST_TITLEBAR_CHILD"; + if std::env::var_os(CHILD).is_none() { + for value in ["app", "system", "desktop", "unknown"] { + // Linux and the BSDs: hold `spawning` while the child starts, so it does not keep a + // copy of a GPU start test's locked start file (see `CHILD_SPAWN`). + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))] + let spawning = super::spawning(); + let child = std::process::Command::new(std::env::current_exe().unwrap()) + .args(["--exact", "tests::the_titlebar_variable_decides_the_frame_before_the_window_opens", "--nocapture"]) + .env(CHILD, "1") + .env("VECTORCRAFT_TITLEBAR", value) + .stdin(std::process::Stdio::null()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn(); + #[cfg(any(target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "netbsd", target_os = "openbsd"))] + drop(spawning); + let output = child.unwrap().wait_with_output().unwrap(); + assert!( + output.status.success(), + "VECTORCRAFT_TITLEBAR={value}\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); + } + return; + } + let value = std::env::var("VECTORCRAFT_TITLEBAR").unwrap(); + let not_read = cfg!(any(target_os = "macos", target_os = "windows")); + assert_eq!(super::titlebar_from_env(), !not_read && value != "unknown", "VECTORCRAFT_TITLEBAR={value}"); + let off = ui_state_with_prefs(serde_json::json!({"systemTitleBar": false})); + let on = ui_state_with_prefs(serde_json::json!({"systemTitleBar": true})); + for (saved, opted_into_system) in [(None, false), (Some(&off), false), (Some(&on), true)] { + let custom = super::custom_titlebar(saved); + let expected = match value.as_str() { + _ if not_read => !cfg!(target_os = "macos") && !opted_into_system, + "app" => true, + "system" | "desktop" => false, + // Not a value the variable takes: System Title Bar decides when it is on. Otherwise the + // `craft-window` probe decides, the same with System Title Bar off as with no saved + // preferences. + _ if opted_into_system => false, + _ => super::custom_titlebar(None), + }; + assert_eq!(custom, expected, "{value}, preference {opted_into_system}"); + assert_eq!(super::native_options(custom).viewport.decorations, Some(!custom), "{value}, preference {opted_into_system}"); + } +} + /// The file extensions the macOS bundle declares: its document types and its own exported type. fn plist_extensions(plist: &str) -> Vec<&str> { ["CFBundleTypeExtensions", "public.filename-extension"] diff --git a/apps/vectorcraft/src/titlebar.rs b/apps/vectorcraft/src/titlebar.rs index 6e7451ed9..17c847040 100644 --- a/apps/vectorcraft/src/titlebar.rs +++ b/apps/vectorcraft/src/titlebar.rs @@ -41,7 +41,8 @@ pub(crate) const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos"); /// Whether the user asked for the system title bar in Preferences › User Interface › System Title /// Bar. Read from the saved preferences before the window opens; a missing or unreadable value is -/// `false` (the default). This preference forces the desktop/native frame back on. +/// `false` (the default). This preference forces the desktop/native frame back on unless +/// `VECTORCRAFT_TITLEBAR` is set ([`system_title_bar_applies`]). pub(crate) fn opted_into_system_title_bar(saved: Option<&vectorcraft_ui_egui::UiState>) -> bool { saved.map(|ui| ui.engine_prefs.get("systemTitleBar").and_then(serde_json::Value::as_bool).unwrap_or(false)).unwrap_or(false) } @@ -87,12 +88,18 @@ fn probed_choice() -> &'static craft_window::Choice { } /// Whether `VECTORCRAFT_TITLEBAR` chose this window's frame (Linux and the BSDs), so Preferences › -/// User Interface › VectorCraft Title Bar on Every Desktop does not apply while it is set. Always -/// `false` on macOS and Windows. +/// User Interface › System Title Bar and VectorCraft Title Bar on Every Desktop do not apply while +/// it is set. Always `false` on macOS and Windows. pub(crate) fn titlebar_from_env() -> bool { cfg!(not(any(target_os = "macos", target_os = "windows"))) && probed_choice().overridden().is_some() } +/// Whether System Title Bar decides this start: `opted_into_system` unless `VECTORCRAFT_TITLEBAR` +/// is set (`from_env`), which chooses the frame for the run it is set in. +pub(crate) fn system_title_bar_applies(opted_into_system: bool, from_env: bool) -> bool { + opted_into_system && !from_env +} + /// Whether this start draws its own title bar, given the frame the shared `craft-window` crate /// decided and the user's System Title Bar preference. /// @@ -102,7 +109,8 @@ pub(crate) fn titlebar_from_env() -> bool { /// ([`craft_window::Frame::App`]) only where the compositor leaves it to the app (GNOME's Mutter), /// or when `VECTORCRAFT_TITLEBAR` overrides the probe. VectorCraft draws its own title bar exactly /// when `craft-window` says the app owns the frame **and** the user has not opted into the system -/// title bar; the preference always forces the native frame back on. +/// title bar; the preference forces the native frame back on unless `VECTORCRAFT_TITLEBAR` is set +/// ([`system_title_bar_applies`]). /// /// macOS and Windows keep today's behavior and are decided by [`CUSTOM_TITLEBAR`] rather than the /// probe: `craft-window` returns `Frame::Desktop` for both (the OS always draws a frame), but @@ -124,7 +132,8 @@ pub(crate) fn custom_titlebar_from(_frame: &craft_window::Frame, opted_into_syst /// Whether this start draws its own title bar. Probes the shared `craft-window` crate once (its /// override variable is `VECTORCRAFT_TITLEBAR`) and combines its decision with the saved System -/// Title Bar preference. Call once at startup, before [`native_options`]. +/// Title Bar preference, which does not apply while the variable is set. Call once at startup, +/// before [`native_options`]. /// /// On Linux, Preferences › Performance › Linux Display Server › X11 opens the window through /// XWayland (below). A GNOME/Wayland probe would say `Frame::App` there, but the window really runs @@ -133,7 +142,7 @@ pub(crate) fn custom_titlebar_from(_frame: &craft_window::Frame, opted_into_syst /// and so does VectorCraft Title Bar on Every Desktop ([`app_draws_frame`]). pub(crate) fn custom_titlebar(saved: Option<&vectorcraft_ui_egui::UiState>) -> bool { let choice = probed_choice(); - let opted_into_system = opted_into_system_title_bar(saved); + let opted_into_system = system_title_bar_applies(opted_into_system_title_bar(saved), titlebar_from_env()); // macOS and Windows never read VectorCraft Title Bar on Every Desktop (`custom_titlebar_from` // ignores the frame there). let opted_into_app = cfg!(not(any(target_os = "macos", target_os = "windows"))) && opted_into_app_title_bar(saved); diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 4b931aaef..802bb695d 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -196,8 +196,8 @@ pub struct VectorcraftApp { /// A URL to open through egui next frame (when the host has no `open_url` service). pending_url: Option, /// `VECTORCRAFT_TITLEBAR` chose this window's frame (Linux and the BSDs), so Preferences › User - /// Interface › VectorCraft Title Bar on Every Desktop does not apply while it is set. `false` - /// unless the host sets it. + /// Interface › System Title Bar and VectorCraft Title Bar on Every Desktop do not apply while + /// it is set. `false` unless the host sets it. pub titlebar_from_env: bool, /// Windows and Linux: the window has no OS decorations, so the app bar is the title bar (drag, /// double-click to maximize, caption buttons) and invisible edge zones resize the window. diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index 733aaf200..d5f391433 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -36,8 +36,8 @@ pub fn open(app: &mut VectorcraftApp, category: Option<&str>) { fields.insert("__smartGuides".into(), json!(app.ui.view.smart_guides)); fields.insert("__snapToGrid".into(), json!(app.ui.view.snap_to_grid)); fields.insert("__taskBar".into(), json!(app.ui.task_bar)); - // Read-only: whether VECTORCRAFT_TITLEBAR chose this window's frame (VectorCraft Title Bar on - // Every Desktop's note). + // Read-only: whether VECTORCRAFT_TITLEBAR chose this window's frame (System Title Bar's dimmed + // row and VectorCraft Title Bar on Every Desktop's note). fields.insert("__titlebarFromEnv".into(), json!(app.titlebar_from_env)); fields.insert("__search".into(), json!("")); fields.insert("__focusSearch".into(), json!(true)); @@ -483,7 +483,8 @@ fn category_fields(ui: &mut egui::Ui, app: &mut VectorcraftApp, d: &mut Dialog, // Windows and Linux only: macOS always has the system's title bar, the web has none. PrefKind::Bool if sp.key == "systemTitleBar" => { if cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) { - bool_row(ui, d, sp.key, sp.label); + // VECTORCRAFT_TITLEBAR chooses the frame while it is set: the checkbox is dimmed. + ui.add_enabled_ui(!d.bool("__titlebarFromEnv"), |ui| bool_row(ui, d, sp.key, sp.label)); // The window's decorations are chosen once, when the app starts. On Linux and the // BSDs the note goes under the next row, which also applies at the next start. if !APP_TITLE_BAR_SHOWN { @@ -499,7 +500,7 @@ fn category_fields(ui: &mut egui::Ui, app: &mut VectorcraftApp, d: &mut Dialog, let from_env = d.bool("__titlebarFromEnv"); let system = d.bool("systemTitleBar"); ui.add_enabled_ui(!from_env && !system, |ui| bool_row(ui, d, sp.key, sp.label)); - if system { + if system && !from_env { let note = tl!("System Title Bar takes precedence over this option while it is on."); ui.label(egui::RichText::new(note).color(t.text_dim).size(11.0)); } diff --git a/crates/ui-egui/src/prefs_dialog/tests.rs b/crates/ui-egui/src/prefs_dialog/tests.rs index 11fd0f418..9ef8f3ed7 100644 --- a/crates/ui-egui/src/prefs_dialog/tests.rs +++ b/crates/ui-egui/src/prefs_dialog/tests.rs @@ -336,7 +336,8 @@ fn app_title_bar_preference_shows_and_persists() { /// VectorCraft Title Bar on Every Desktop does not take clicks while System Title Bar is on or /// VECTORCRAFT_TITLEBAR is set. With the variable set its note names the variable in place of the -/// next-launch note, and with System Title Bar on a note says that System Title Bar takes precedence. +/// next-launch note, and with System Title Bar on and the variable unset a note says that System +/// Title Bar takes precedence. #[test] fn app_title_bar_is_dimmed_while_something_takes_precedence() { fn texts(s: &egui::Shape, out: &mut Vec) { @@ -364,7 +365,7 @@ fn app_title_bar_is_dimmed_while_something_takes_precedence() { let mut shown = vec![]; out.shapes.iter().for_each(|c| texts(&c.shape, &mut shown)); assert_eq!(shown.iter().any(|t| t == note), supported && from_env, "{shown:?}"); - assert_eq!(shown.iter().any(|t| t == system_note), supported && system, "{shown:?}"); + assert_eq!(shown.iter().any(|t| t == system_note), supported && system && !from_env, "{shown:?}"); // The next-launch note stays under this row on Linux and the BSDs, where it also covers // System Title Bar, and under System Title Bar on Windows. let restart_shown = if supported { !from_env } else { cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))) }; @@ -392,6 +393,42 @@ fn app_title_bar_is_dimmed_while_something_takes_precedence() { } } +/// While VECTORCRAFT_TITLEBAR chooses the frame, System Title Bar does not take clicks. +#[test] +fn system_title_bar_is_dimmed_while_the_environment_variable_chooses_the_frame() { + let supported = cfg!(all(not(target_arch = "wasm32"), not(target_os = "macos"))); + for from_env in [false, true] { + let mut a = app(); + a.titlebar_from_env = from_env; + open(&mut a, Some("User Interface")); + let ctx = egui::Context::default(); + crate::theme::install_fonts(&ctx); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + let mut out = ctx.run_ui(egui::RawInput::default(), |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + let label = out.shapes.iter().find_map(|c| match &c.shape { + egui::Shape::Text(t) if t.galley.text() == "System Title Bar" => Some(t.visual_bounding_rect()), + _ => None, + }); + assert_eq!(label.is_some(), supported); + if let Some(r) = label { + for pressed in [true, false] { + let input = egui::RawInput { + events: vec![ + egui::Event::PointerMoved(r.center()), + egui::Event::PointerButton { pos: r.center(), button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }, + ], + ..Default::default() + }; + let mut out = ctx.run_ui(input, |ui| show(&mut a, ui.ctx())); + out.textures_delta.clear(); + } + assert_eq!(a.ui.dialog.as_ref().unwrap().bool("systemTitleBar"), !from_env, "clicked with the variable {from_env}"); + } + } +} + /// Performance › Linux Display Server (#2028): on Linux it shows the drawing-tablet note, and /// X11 (XWayland) When a Pen Is Attached is applied by OK and saved under the key the desktop /// app reads before the window opens. diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 0e6611a85..4e362d17b 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -336,7 +336,9 @@ when the app starts, off by default) turns off the desktop's frame and uses the sessions where the desktop would draw the frame: X11 sessions, Wayland compositors that draw server-side decorations such as KDE Plasma, and a window opened through XWayland. System Title Bar and `VECTORCRAFT_TITLEBAR` (`app`, or `system` or `desktop`) take precedence over it. The checkbox is dimmed while either applies, and a note under it names -each one that applies. +each one that applies. `VECTORCRAFT_TITLEBAR` also takes precedence over System Title Bar for the run it is set in: +`app` keeps the app bar as the title bar with System Title Bar on. While the variable is set, the Preferences dialog dims +System Title Bar and its `__titlebarFromEnv` field is `true`. Fill/Stroke chips and panel shortcuts: the Control bar's and Properties' Fill and Stroke chips bring their proxy forward (`paint.toggleActive {fill}`) and open a popover with the Swatches panel (Shift-click: the Color panel's mixer); diff --git a/docs/development.md b/docs/development.md index 3204c445b..a4077abc6 100644 --- a/docs/development.md +++ b/docs/development.md @@ -118,7 +118,7 @@ A file opened from the file manager (double-click, Open With, a drop on the exec ## Linux: Wayland and X11 -On GNOME/Wayland, enable **Preferences › User Interface › System Title Bar** and restart to use winit's font-free Adwaita window controls (`wayland-csd-adwaita-notitle`); the existing custom title bar remains the Linux default. On X11, on a Wayland compositor that draws server-side decorations such as KDE Plasma, and in a window opened through XWayland, enable **Preferences › User Interface › VectorCraft Title Bar on Every Desktop** and restart to use the app bar as the title bar instead of the desktop's frame. `VECTORCRAFT_TITLEBAR=app`, `=system` or `=desktop` chooses the frame for one run and takes precedence over VectorCraft Title Bar on Every Desktop. +On GNOME/Wayland, enable **Preferences › User Interface › System Title Bar** and restart to use winit's font-free Adwaita window controls (`wayland-csd-adwaita-notitle`); the existing custom title bar remains the Linux default. On X11, on a Wayland compositor that draws server-side decorations such as KDE Plasma, and in a window opened through XWayland, enable **Preferences › User Interface › VectorCraft Title Bar on Every Desktop** and restart to use the app bar as the title bar instead of the desktop's frame. `VECTORCRAFT_TITLEBAR=app`, `=system` or `=desktop` chooses the frame for one run and takes precedence over System Title Bar and VectorCraft Title Bar on Every Desktop. The window runs natively on Wayland (eframe's `wayland` feature) and on X11. On machines with two GPUs it renders on the one the desktop runs on unless Preferences say otherwise; see [Desktop graphics processor](#desktop-graphics-processor) (#502). The system clipboard (`apps/vectorcraft/src/clipboard.rs`) is arboard with its `wayland-data-control` feature: diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index f95308865..eb4e964a5 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -819,7 +819,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - Shortcut sets other apps save as text (`.kys`) open in Import Shortcut Set from File › Open, from Edit › Keyboard Shortcuts › Import… in the desktop app, or from a drop on the window. It matches each entry to a tool, command or panel by name and decodes the file's key and modifier codes where VectorCraft's default shortcuts agree on them; the user answers the rest, and Apply fills the Keyboard Shortcuts dialog's working set. The answers are kept with the preferences for the next import (`shortcutSet.read`, `importMatches.*`). - Edit › Keyboard Shortcuts › Export Text… saves the set shown in the dialog as a UTF-8 text file, as the reference app's [Export keyboard shortcuts list as a text document](https://helpx.adobe.com/illustrator/desktop/get-started/preferences-and-settings/export-keyboard-shortcuts-list.html) page describes (`shortcuts.exportText {path?}`). The layout is VectorCraft's own: the set's name, then the Tools and Menu Commands rows in the dialog's order, each with its name, a tab and its shortcuts as the dialog shows them, and nothing after the tab for a row with no shortcut. Tests `shortcut_editor::tests::export_text_*` and `tests::picks::keyboard_shortcuts_export_text_writes_the_working_set_as_text`. - On Windows and Linux the app bar is the window's title bar (its own minimize, maximize and close buttons, drag to move, edges to resize), with the dragon app icon as the brand mark; dialogs and menus size to their content. As on a native menu bar, once one menu is open, moving the pointer onto another title opens that one. - - On Linux and the BSDs, Preferences › User Interface › VectorCraft Title Bar on Every Desktop (off by default) uses the app bar as the title bar from the next start in the sessions where the desktop would draw the window's frame (X11, Wayland compositors with server-side decorations such as KDE Plasma, a window opened through XWayland). System Title Bar and `VECTORCRAFT_TITLEBAR` take precedence over it. + - On Linux and the BSDs, Preferences › User Interface › VectorCraft Title Bar on Every Desktop (off by default) uses the app bar as the title bar from the next start in the sessions where the desktop would draw the window's frame (X11, Wayland compositors with server-side decorations such as KDE Plasma, a window opened through XWayland). System Title Bar and `VECTORCRAFT_TITLEBAR` take precedence over it, and `VECTORCRAFT_TITLEBAR` also takes precedence over System Title Bar for the run it is set in. - Edit › Keyboard Shortcuts lists items of the menu bar and panel menus, as the reference app's [Customize keyboard shortcuts](https://web.archive.org/web/20221205131209id_/https://helpx.adobe.com/illustrator/using/customizing-keyboard-shortcuts.html) page describes for its Menu Commands. A menu command with no default shortcut that needs a value a key press does not give, such as File › Document Setup › Units (`document.setUnits {units}`), is listed only while the user has changed its shortcut. A command stays listed when a menu bar item runs it without a value or when it opens a dialog run without one, as the Variables panel's commands do. - A shortcut on the key left of 1 runs whatever character that key types, and Edit › Keyboard Shortcuts, the menus in the window, the command palette and tool tips show it as that character once the key has been pressed: `"` on a Turkish Q layout, `~` on a US layout as before. Shortcuts saved as `~` keep working (#4559). - On Windows a maximized window stays inside the work area with the taskbar on any edge (#3242, #3723).