mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-21 14:44:18 -04:00
feat(window): adopt shared craft-window for Linux decorations (depends on craft-libs#217) (#3366)
* feat(window): adopt shared craft-window for Linux decorations
Decide the window frame on Linux/BSD via the shared craft-window crate
(storytold/craft-libs) instead of VectorCraft's unconditional
`CUSTOM_TITLEBAR = !macos`, so the app matches the rest of the Craft
suite per desktop.
- Probe `craft_window::Choice::probe(Prefix::new("VECTORCRAFT"))` once at
startup; custom titlebar is on when the frame is `Frame::App(_)` and the
user has not opted into the system title bar.
- Preserve the `systemTitleBar` preference: opting in forces the native
(desktop) frame back on, as before.
- Keep macOS and Windows behavior exactly as today (decided by
`CUSTOM_TITLEBAR`, not the probe): macOS integrated title strip,
Windows app-drawn caption. craft-window returns `Frame::Desktop` for
both; VectorCraft's overdraw is its own choice.
- The only behavioral change: KDE/Wayland now keeps KWin's native
decorations (Frame::Desktop) instead of always drawing a custom
titlebar; GNOME still draws its own (Frame::App).
- `VECTORCRAFT_TITLEBAR` override and the `systemTitleBar` pref both still
work (the env var is craft-window's override for the `VECTORCRAFT` prefix).
NOT MERGEABLE YET: craft-window is open PR storytold/craft-libs#217; the
dep points at the PR branch and must be repointed to a merged `rev`
before this can merge.
* fix(window): native frame when X11 is forced; pin craft-window to merged rev
- When Preferences > Linux Display Server > X11 forces the window onto
XWayland, treat the frame as the desktop's (native X11 decorations)
rather than letting a GNOME/Wayland probe pick Frame::App and draw our
own title bar on what is really an X11 window. An explicit
VECTORCRAFT_TITLEBAR override still wins (checked via Choice::overridden).
- Repoint craft-window from the (now-merged) feature branch to the squash
commit on storytold/craft-libs (craft-libs#217); regenerate Cargo.lock.
- The WAYLAND_SOCKET concern raised in review is handled in the merged
craft-window crate itself (the probe skips when WAYLAND_SOCKET is set).
Verified: cargo build + test -p vectorcraft (131 tests) pass against the
merged dep; windows7 (--no-default-features) still compiles; rustfmt clean.
* fix(window): only force native frame when X11 libs are actually present
Follow-up to review: the XWayland title-bar override now also checks
xwayland::x11_libraries_found(), matching the launch path's own condition.
If X11 is requested but the libraries are missing, startup stays on
Wayland, so the frame decision must keep the probe's app-drawn result
rather than flipping to native. Verified: tests pass, rustfmt clean.
This commit is contained in:
1 parent
0b94055cfe
commit
cb0a7d9b31
4 files changed
+345
-286
No files matched your search
Generated
+243
-274
File diff suppressed because it is too large.
Load diff
@@ -15,6 +15,9 @@ craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "ca50d63b77
|
||||
craft-print = { git = "https://github.com/storytold/craft-libs", rev = "d991bf6792f8ade99c51358bf3c1d902c8cd96b5" }
|
||||
craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" }
|
||||
craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "5c874d51eeb3de70e735c8ce87a59a7dedd47675", features = ["serde"] }
|
||||
# Decides who draws the window frame (desktop vs. app) so the suite shares one native titlebar per
|
||||
# desktop. The `egui` feature adds `craft_window::decorate`. (craft-libs#217.)
|
||||
craft-window = { git = "https://github.com/storytold/craft-libs", rev = "1f805378d454dac48b5c214029d254055fdf961b", features = ["egui"] }
|
||||
# internal
|
||||
vectorcraft-geom = { path = "crates/geom" }
|
||||
vectorcraft-color = { path = "crates/color" }
|
||||
|
||||
@@ -20,6 +20,9 @@ windows7 = ["eframe/glow"]
|
||||
vectorcraft-engine = { workspace = true }
|
||||
vectorcraft-ui-egui = { workspace = true }
|
||||
vectorcraft-text = { workspace = true }
|
||||
# Shared across the Craft suite: decides who draws the window frame per desktop (compositor vs app)
|
||||
# so VectorCraft matches the rest on Linux. `egui` adds `craft_window::decorate`. See #217.
|
||||
craft-window = { workspace = true }
|
||||
eframe = { workspace = true, features = ["default_fonts", "wayland", "x11"] }
|
||||
egui = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -624,13 +624,73 @@ fn adapter_summary(info: &eframe::wgpu::AdapterInfo) -> String {
|
||||
|
||||
/// Windows and Linux: 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.
|
||||
#[cfg_attr(not(any(target_os = "macos", target_os = "windows")), allow(dead_code))]
|
||||
const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos");
|
||||
|
||||
/// Whether this start draws its own title bar: Windows and Linux do, unless Preferences ›
|
||||
/// User Interface › System Title Bar asks for the system's. Read from the saved preferences
|
||||
/// before the window opens; a missing or unreadable value keeps the default.
|
||||
/// 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.
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// 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.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
fn custom_titlebar_from(_frame: &craft_window::Frame, 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 {
|
||||
CUSTOM_TITLEBAR && !saved.map(|ui| ui.engine_prefs.get("systemTitleBar").and_then(serde_json::Value::as_bool).unwrap_or(false)).unwrap_or(false)
|
||||
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);
|
||||
}
|
||||
}
|
||||
custom_titlebar_from(choice.frame(), opted_into_system)
|
||||
}
|
||||
|
||||
fn native_options(custom_titlebar: bool) -> eframe::NativeOptions {
|
||||
@@ -1043,17 +1103,41 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_system_title_bar_preference_keeps_the_window_decorations() {
|
||||
// Preferences › User Interface › System Title Bar gives the window back its system
|
||||
// decorations on Windows and Linux; macOS always has them.
|
||||
assert_eq!(super::custom_titlebar(None), super::CUSTOM_TITLEBAR, "no preferences: the default");
|
||||
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");
|
||||
let off = ui_state_with_prefs(serde_json::json!({"systemTitleBar": false}));
|
||||
assert_eq!(super::custom_titlebar(Some(&off)), super::CUSTOM_TITLEBAR);
|
||||
assert!(!super::opted_into_system_title_bar(Some(&off)));
|
||||
let on = ui_state_with_prefs(serde_json::json!({"systemTitleBar": true}));
|
||||
assert!(!super::custom_titlebar(Some(&on)));
|
||||
assert_eq!(super::native_options(false).viewport.decorations, Some(true));
|
||||
if super::CUSTOM_TITLEBAR {
|
||||
assert_eq!(super::native_options(true).viewport.decorations, Some(false));
|
||||
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.
|
||||
#[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::native_options(false).viewport.decorations, Some(true));
|
||||
assert_eq!(super::native_options(true).viewport.decorations, Some(false));
|
||||
}
|
||||
|
||||
/// The file extensions the macOS bundle declares: its document types and its own exported type.
|
||||
|
||||
Reference in new issue
Block a user