From 49144d94bf0f34c95d18d89c5e9bb18ac90bf08c Mon Sep 17 00:00:00 2001 From: jansol Date: Fri, 29 Mar 2024 17:10:47 +0200 Subject: [PATCH] gpui: Add support for window transparency & blur on macOS (#9610) This PR adds support for transparent and blurred window backgrounds on macOS. Release Notes: - Added support for transparent and blurred window backgrounds on macOS ([#5040](https://github.com/zed-industries/zed/issues/5040)). - This requires themes to specify a new `background.appearance` key ("opaque", "transparent" or "blurred") and to include an alpha value in colors that should be transparent. image image image --------- Co-authored-by: Luiz Marcondes Co-authored-by: Marshall Bowers --- crates/collab_ui/src/collab_ui.rs | 5 ++- crates/gpui/examples/window_positioning.rs | 1 + crates/gpui/src/platform.rs | 28 ++++++++++++ .../gpui/src/platform/linux/wayland/window.rs | 6 ++- crates/gpui/src/platform/linux/x11/window.rs | 6 ++- .../gpui/src/platform/mac/metal_renderer.rs | 2 +- crates/gpui/src/platform/mac/window.rs | 45 +++++++++++++++++-- crates/gpui/src/platform/test/window.rs | 6 ++- crates/gpui/src/platform/windows/window.rs | 4 ++ crates/gpui/src/window.rs | 15 +++++-- crates/theme/src/default_theme.rs | 4 ++ crates/theme/src/one_themes.rs | 4 +- crates/theme/src/registry.rs | 8 ++++ crates/theme/src/schema.rs | 24 +++++++++- crates/theme/src/settings.rs | 6 +++ crates/theme/src/styles/colors.rs | 4 +- crates/theme/src/theme.rs | 10 ++++- crates/theme_importer/src/vscode/converter.rs | 1 + crates/zed/src/main.rs | 9 ++++ crates/zed/src/zed.rs | 2 + 20 files changed, 173 insertions(+), 17 deletions(-) diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 515cd4d506..ad5e25198f 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -13,8 +13,8 @@ use call::{report_call_event_for_room, ActiveCall}; pub use collab_panel::CollabPanel; pub use collab_titlebar_item::CollabTitlebarItem; use gpui::{ - actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task, WindowContext, - WindowKind, WindowOptions, + actions, point, AppContext, DevicePixels, Pixels, PlatformDisplay, Size, Task, + WindowBackgroundAppearance, WindowContext, WindowKind, WindowOptions, }; use panel_settings::MessageEditorSettings; pub use panel_settings::{ @@ -121,5 +121,6 @@ fn notification_window_options( is_movable: false, display_id: Some(screen.id()), fullscreen: false, + window_background: WindowBackgroundAppearance::default(), } } diff --git a/crates/gpui/examples/window_positioning.rs b/crates/gpui/examples/window_positioning.rs index 4c3300c693..ef00f88a7b 100644 --- a/crates/gpui/examples/window_positioning.rs +++ b/crates/gpui/examples/window_positioning.rs @@ -48,6 +48,7 @@ fn main() { display_id: Some(screen.id()), titlebar: None, + window_background: WindowBackgroundAppearance::default(), focus: false, show: true, kind: WindowKind::PopUp, diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 34a13f673a..24c6a62e84 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -190,6 +190,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn activate(&self); fn is_active(&self) -> bool; fn set_title(&mut self, title: &str); + fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance); fn set_edited(&mut self, edited: bool); fn show_character_palette(&self); fn minimize(&self); @@ -533,6 +534,9 @@ pub struct WindowOptions { /// The display to create the window on, if this is None, /// the window will be created on the main display pub display_id: Option, + + /// The appearance of the window background. + pub window_background: WindowBackgroundAppearance, } /// The variables that can be configured when creating a new window @@ -555,6 +559,8 @@ pub(crate) struct WindowParams { pub show: bool, pub display_id: Option, + + pub window_background: WindowBackgroundAppearance, } impl Default for WindowOptions { @@ -572,6 +578,7 @@ impl Default for WindowOptions { is_movable: true, display_id: None, fullscreen: false, + window_background: WindowBackgroundAppearance::default(), } } } @@ -633,6 +640,27 @@ impl Default for WindowAppearance { } } +/// The appearance of the background of the window itself, when there is +/// no content or the content is transparent. +#[derive(Copy, Clone, Debug, Default, PartialEq)] +pub enum WindowBackgroundAppearance { + /// Opaque. + /// + /// This lets the window manager know that content behind this + /// window does not need to be drawn. + /// + /// Actual color depends on the system and themes should define a fully + /// opaque background color instead. + #[default] + Opaque, + /// Plain alpha transparency. + Transparent, + /// Transparency, but the contents behind the window are blurred. + /// + /// Not always supported. + Blurred, +} + /// The options that can be configured for a file dialog prompt #[derive(Copy, Clone, Debug)] pub struct PathPromptOptions { diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index cce210d4ca..3938256673 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -23,7 +23,7 @@ use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow}; use crate::scene::Scene; use crate::{ px, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformDisplay, PlatformInput, Point, - PromptLevel, Size, WindowAppearance, WindowParams, + PromptLevel, Size, WindowAppearance, WindowBackgroundAppearance, WindowParams, }; #[derive(Default)] @@ -355,6 +355,10 @@ impl PlatformWindow for WaylandWindow { self.0.toplevel.set_title(title.to_string()); } + fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) { + // todo(linux) + } + fn set_edited(&mut self, edited: bool) { // todo(linux) } diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index 84681d41d8..9bd0635031 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -4,7 +4,7 @@ use crate::{ platform::blade::BladeRenderer, size, Bounds, DevicePixels, Modifiers, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel, - Scene, Size, WindowAppearance, WindowOptions, WindowParams, + Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowOptions, WindowParams, }; use blade_graphics as gpu; use parking_lot::Mutex; @@ -423,6 +423,10 @@ impl PlatformWindow for X11Window { // todo(linux) fn set_edited(&mut self, edited: bool) {} + fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) { + // todo(linux) + } + // todo(linux), this corresponds to `orderFrontCharacterPalette` on macOS, // but it looks like the equivalent for Linux is GTK specific: // diff --git a/crates/gpui/src/platform/mac/metal_renderer.rs b/crates/gpui/src/platform/mac/metal_renderer.rs index 569227e081..28a8b86c70 100644 --- a/crates/gpui/src/platform/mac/metal_renderer.rs +++ b/crates/gpui/src/platform/mac/metal_renderer.rs @@ -73,7 +73,7 @@ impl MetalRenderer { let layer = metal::MetalLayer::new(); layer.set_device(&device); layer.set_pixel_format(MTLPixelFormat::RGBA8Unorm); - layer.set_opaque(true); + layer.set_opaque(false); layer.set_maximum_drawable_count(3); unsafe { let _: () = msg_send![&*layer, setAllowsNextDrawableTimeout: NO]; diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index e321b9b16c..9f167ceb51 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -4,12 +4,12 @@ use crate::{ DisplayLink, ExternalPaths, FileDropEvent, ForegroundExecutor, KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, - Size, Timer, WindowAppearance, WindowKind, WindowParams, + Size, Timer, WindowAppearance, WindowBackgroundAppearance, WindowKind, WindowParams, }; use block::ConcreteBlock; use cocoa::{ appkit::{ - CGPoint, NSApplication, NSBackingStoreBuffered, NSEventModifierFlags, + CGPoint, NSApplication, NSBackingStoreBuffered, NSColor, NSEvent, NSEventModifierFlags, NSFilenamesPboardType, NSPasteboard, NSScreen, NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowCollectionBehavior, NSWindowOcclusionState, NSWindowStyleMask, NSWindowTitleVisibility, @@ -83,6 +83,17 @@ const NSDragOperationNone: NSDragOperation = 0; #[allow(non_upper_case_globals)] const NSDragOperationCopy: NSDragOperation = 1; +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + // Widely used private APIs; Apple uses them for their Terminal.app. + fn CGSMainConnectionID() -> id; + fn CGSSetWindowBackgroundBlurRadius( + connection_id: id, + window_id: NSInteger, + radius: i64, + ) -> i32; +} + #[ctor] unsafe fn build_classes() { WINDOW_CLASS = build_window_class("GPUIWindow", class!(NSWindow)); @@ -509,6 +520,7 @@ impl MacWindow { pub fn open( handle: AnyWindowHandle, WindowParams { + window_background, bounds, titlebar, kind, @@ -606,7 +618,7 @@ impl MacWindow { ) }; - let window = Self(Arc::new(Mutex::new(MacWindowState { + let mut window = Self(Arc::new(Mutex::new(MacWindowState { handle, executor, native_window, @@ -685,6 +697,8 @@ impl MacWindow { native_window.setContentView_(native_view.autorelease()); native_window.makeFirstResponder_(native_view); + window.set_background_appearance(window_background); + match kind { WindowKind::Normal => { native_window.setLevel_(NSNormalWindowLevel); @@ -967,6 +981,31 @@ impl PlatformWindow for MacWindow { } } + fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) { + let this = self.0.as_ref().lock(); + let blur_radius = if background_appearance == WindowBackgroundAppearance::Blurred { + 80 + } else { + 0 + }; + let opaque = if background_appearance == WindowBackgroundAppearance::Opaque { + YES + } else { + NO + }; + unsafe { + this.native_window.setOpaque_(opaque); + let clear_color = if opaque == YES { + NSColor::colorWithSRGBRed_green_blue_alpha_(nil, 0f64, 0f64, 0f64, 1f64) + } else { + NSColor::clearColor(nil) + }; + this.native_window.setBackgroundColor_(clear_color); + let window_number = this.native_window.windowNumber(); + CGSSetWindowBackgroundBlurRadius(CGSMainConnectionID(), window_number, blur_radius); + } + } + fn set_edited(&mut self, edited: bool) { unsafe { let window = self.0.lock().native_window; diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index db85feba88..75c42dafcd 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -2,7 +2,7 @@ use crate::{ AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels, DispatchEventResult, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, Size, TestPlatform, TileId, WindowAppearance, - WindowParams, + WindowBackgroundAppearance, WindowParams, }; use collections::HashMap; use parking_lot::Mutex; @@ -190,6 +190,10 @@ impl PlatformWindow for TestWindow { self.0.lock().title = Some(title.to_owned()); } + fn set_background_appearance(&mut self, _background: WindowBackgroundAppearance) { + unimplemented!() + } + fn set_edited(&mut self, edited: bool) { self.0.lock().edited = edited; } diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 5adf899701..4f54a74253 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -1468,6 +1468,10 @@ impl PlatformWindow for WindowsWindow { .ok(); } + fn set_background_appearance(&mut self, _background_appearance: WindowBackgroundAppearance) { + // todo(windows) + } + // todo(windows) fn set_edited(&mut self, _edited: bool) {} diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index a30ef0e40f..84cebeafca 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -7,8 +7,8 @@ use crate::{ Modifiers, ModifiersChangedEvent, MouseButton, MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, TaffyLayoutEngine, Task, - TextStyle, TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, WindowOptions, - WindowParams, WindowTextSystem, + TextStyle, TextStyleRefinement, View, VisualContext, WeakView, WindowAppearance, + WindowBackgroundAppearance, WindowOptions, WindowParams, WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -398,6 +398,7 @@ impl Window { is_movable, display_id, fullscreen, + window_background, } = options; let bounds = bounds.unwrap_or_else(|| default_bounds(display_id, cx)); @@ -411,6 +412,7 @@ impl Window { focus, show, display_id, + window_background, }, ); let display_id = platform_window.display().id(); @@ -872,7 +874,7 @@ impl<'a> WindowContext<'a> { self.window.platform_window.bounds() } - /// Retusn whether or not the window is currently fullscreen + /// Returns whether or not the window is currently fullscreen pub fn is_fullscreen(&self) -> bool { self.window.platform_window.is_fullscreen() } @@ -911,6 +913,13 @@ impl<'a> WindowContext<'a> { self.window.platform_window.set_title(title); } + /// Sets the window background appearance. + pub fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) { + self.window + .platform_window + .set_background_appearance(background_appearance); + } + /// Mark the window as dirty at the platform level. pub fn set_window_edited(&mut self, edited: bool) { self.window.platform_window.set_edited(edited); diff --git a/crates/theme/src/default_theme.rs b/crates/theme/src/default_theme.rs index 7719789cc3..bbcab499af 100644 --- a/crates/theme/src/default_theme.rs +++ b/crates/theme/src/default_theme.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use gpui::WindowBackgroundAppearance; + use crate::prelude::*; use crate::{ @@ -15,6 +17,7 @@ fn zed_pro_daylight() -> Theme { name: "Zed Pro Daylight".into(), appearance: Appearance::Light, styles: ThemeStyles { + window_background_appearance: WindowBackgroundAppearance::Opaque, system: SystemColors::default(), colors: ThemeColors::light(), status: StatusColors::light(), @@ -45,6 +48,7 @@ pub(crate) fn zed_pro_moonlight() -> Theme { name: "Zed Pro Moonlight".into(), appearance: Appearance::Dark, styles: ThemeStyles { + window_background_appearance: WindowBackgroundAppearance::Opaque, system: SystemColors::default(), colors: ThemeColors::dark(), status: StatusColors::dark(), diff --git a/crates/theme/src/one_themes.rs b/crates/theme/src/one_themes.rs index c556b87de2..df297208a6 100644 --- a/crates/theme/src/one_themes.rs +++ b/crates/theme/src/one_themes.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use gpui::{hsla, FontStyle, FontWeight, HighlightStyle}; +use gpui::{hsla, FontStyle, FontWeight, HighlightStyle, WindowBackgroundAppearance}; use crate::{ default_color_scales, Appearance, PlayerColors, StatusColors, SyntaxTheme, SystemColors, Theme, @@ -39,8 +39,8 @@ pub(crate) fn one_dark() -> Theme { id: "one_dark".to_string(), name: "One Dark".into(), appearance: Appearance::Dark, - styles: ThemeStyles { + window_background_appearance: WindowBackgroundAppearance::Opaque, system: SystemColors::default(), colors: ThemeColors { border: hsla(225. / 360., 13. / 100., 12. / 100., 1.), diff --git a/crates/theme/src/registry.rs b/crates/theme/src/registry.rs index 830a461726..7e17a5472c 100644 --- a/crates/theme/src/registry.rs +++ b/crates/theme/src/registry.rs @@ -122,6 +122,13 @@ impl ThemeRegistry { AppearanceContent::Light => SyntaxTheme::light(), AppearanceContent::Dark => SyntaxTheme::dark(), }; + + let window_background_appearance = user_theme + .style + .window_background_appearance + .map(Into::into) + .unwrap_or_default(); + if !user_theme.style.syntax.is_empty() { syntax_colors.highlights = user_theme .style @@ -153,6 +160,7 @@ impl ThemeRegistry { }, styles: ThemeStyles { system: SystemColors::default(), + window_background_appearance, colors: theme_colors, status: status_colors, player: player_colors, diff --git a/crates/theme/src/schema.rs b/crates/theme/src/schema.rs index e88cff85f4..024802e630 100644 --- a/crates/theme/src/schema.rs +++ b/crates/theme/src/schema.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla}; +use gpui::{FontStyle, FontWeight, HighlightStyle, Hsla, WindowBackgroundAppearance}; use indexmap::IndexMap; use palette::FromColor; use schemars::gen::SchemaGenerator; @@ -33,6 +33,25 @@ pub enum AppearanceContent { Dark, } +/// The background appearance of the window. +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum WindowBackgroundContent { + Opaque, + Transparent, + Blurred, +} + +impl From for WindowBackgroundAppearance { + fn from(value: WindowBackgroundContent) -> Self { + match value { + WindowBackgroundContent::Opaque => WindowBackgroundAppearance::Opaque, + WindowBackgroundContent::Transparent => WindowBackgroundAppearance::Transparent, + WindowBackgroundContent::Blurred => WindowBackgroundAppearance::Blurred, + } + } +} + /// The content of a serialized theme family. #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct ThemeFamilyContent { @@ -53,6 +72,9 @@ pub struct ThemeContent { #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(default)] pub struct ThemeStyleContent { + #[serde(default, rename = "background.appearance")] + pub window_background_appearance: Option, + #[serde(flatten, default)] pub colors: ThemeColorsContent, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 2bce00c408..f51944b86e 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -244,6 +244,12 @@ impl ThemeSettings { if let Some(theme_overrides) = &self.theme_overrides { let mut base_theme = (*self.active_theme).clone(); + if let Some(window_background_appearance) = theme_overrides.window_background_appearance + { + base_theme.styles.window_background_appearance = + window_background_appearance.into(); + } + base_theme .styles .colors diff --git a/crates/theme/src/styles/colors.rs b/crates/theme/src/styles/colors.rs index b4bdc8f342..af5d746fcc 100644 --- a/crates/theme/src/styles/colors.rs +++ b/crates/theme/src/styles/colors.rs @@ -1,4 +1,4 @@ -use gpui::Hsla; +use gpui::{Hsla, WindowBackgroundAppearance}; use refineable::Refineable; use std::sync::Arc; @@ -235,6 +235,8 @@ pub struct ThemeColors { #[derive(Refineable, Clone)] pub struct ThemeStyles { + /// The background appearance of the window. + pub window_background_appearance: WindowBackgroundAppearance, pub system: SystemColors, /// An array of colors used for theme elements that iterate through a series of colors. /// diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 14cddafa7a..01347007a5 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -27,7 +27,9 @@ pub use schema::*; pub use settings::*; pub use styles::*; -use gpui::{AppContext, AssetSource, Hsla, SharedString, WindowAppearance}; +use gpui::{ + AppContext, AssetSource, Hsla, SharedString, WindowAppearance, WindowBackgroundAppearance, +}; use serde::Deserialize; #[derive(Debug, PartialEq, Clone, Copy, Deserialize)] @@ -158,6 +160,12 @@ impl Theme { pub fn appearance(&self) -> Appearance { self.appearance } + + /// Returns the [`WindowBackgroundAppearance`] for the theme. + #[inline(always)] + pub fn window_background_appearance(&self) -> WindowBackgroundAppearance { + self.styles.window_background_appearance + } } pub fn color_alpha(color: Hsla, alpha: f32) -> Hsla { diff --git a/crates/theme_importer/src/vscode/converter.rs b/crates/theme_importer/src/vscode/converter.rs index bf11ac67ac..0bab3c2950 100644 --- a/crates/theme_importer/src/vscode/converter.rs +++ b/crates/theme_importer/src/vscode/converter.rs @@ -56,6 +56,7 @@ impl VsCodeThemeConverter { name: self.theme_metadata.name, appearance, style: ThemeStyleContent { + window_background_appearance: Some(theme::WindowBackgroundContent::Opaque), colors: theme_colors, status: status_colors, players: Vec::new(), diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 63fe28264e..31649d5266 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -208,12 +208,21 @@ fn main() { watch_file_types(fs.clone(), cx); languages.set_theme(cx.theme().clone()); + cx.observe_global::({ let languages = languages.clone(); let http = http.clone(); let client = client.clone(); move |cx| { + for &mut window in cx.windows().iter_mut() { + let background_appearance = cx.theme().window_background_appearance(); + window + .update(cx, |_, cx| { + cx.set_background_appearance(background_appearance) + }) + .ok(); + } languages.set_theme(cx.theme().clone()); let new_host = &client::ClientSettings::get_global(cx).server_url; if &http.base_url() != new_host { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 9115af2813..e8018b362c 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -34,6 +34,7 @@ use task::{ oneshot_source::OneshotSource, static_source::{StaticSource, TrackedFile}, }; +use theme::ActiveTheme; use terminal_view::terminal_panel::{self, TerminalPanel}; use util::{ @@ -104,6 +105,7 @@ pub fn build_window_options(display_uuid: Option, cx: &mut AppContext) -> is_movable: true, display_id: display.map(|display| display.id()), fullscreen: false, + window_background: cx.theme().window_background_appearance(), } }