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.

<img width="913" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/7547ee2a-e376-4d55-9114-e6fc2f5110bc">
<img width="994" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/b36fbc14-6e4d-4140-9448-69cad803c45a">
<img width="1020" alt="image"
src="https://github.com/zed-industries/zed/assets/2588851/d70e2005-54fd-4991-a211-ed484ccf26ef">

---------

Co-authored-by: Luiz Marcondes <luizgustavodevergennes@gmail.com>
Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
jansol 2024-03-29 17:10:47 +02:00 committed by GitHub
parent 1360dffead
commit 49144d94bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 173 additions and 17 deletions

View file

@ -48,6 +48,7 @@ fn main() {
display_id: Some(screen.id()),
titlebar: None,
window_background: WindowBackgroundAppearance::default(),
focus: false,
show: true,
kind: WindowKind::PopUp,

View file

@ -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<DisplayId>,
/// 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<DisplayId>,
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 {

View file

@ -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)
}

View file

@ -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:
//

View file

@ -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];

View file

@ -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;

View file

@ -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;
}

View file

@ -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) {}

View file

@ -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);