Skip to content

Instantly share code, notes, and snippets.

@avaidyam
Created July 2, 2020 17:23
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save avaidyam/d3c76df710651edbf4da56bad3fea9d2 to your computer and use it in GitHub Desktop.
Save avaidyam/d3c76df710651edbf4da56bad3fea9d2 to your computer and use it in GitHub Desktop.
DIY NSVisualEffectView using Private API for macOS
// TODO: setting window transforms (or mission control) flattens layers...
// TODO: Mask image vs path (layer)?
/// `NSVisualEffectView`:
///
/// A view that adds translucency and vibrancy effects to the views in your interface.
/// When you want views to be more prominent in your interface, place them in a
/// backdrop view. The backdrop view is partially transparent, allowing some of
/// the underlying content to show through. Typically, you use a backdrop view
/// to blur background content, instead of obscuring it completely. It can also
/// make its contained content more vibrant to ensure that it remains prominent.
///
/// A suggested use in designing visual containers is the use of "cards"; apply
/// a `.light` or `.dark` effect to the backdrop view, set its `cornerRadius = 4.5`,
/// `rimOpacity = 0.25`, and add a `NSView.shadow` (visually similar to `NSWindow`).
///
/// Note: if set as the containing `window`'s `contentView`, the window's
/// `isOpaque` value will be changed. If the window's `contentView` is changed,
/// the original settings are restored. However, unlike `NSVisualEffectView`,
/// no desktop bleed blending will occur. In addition, `.behindWindow` blending
/// cannot be applied if the view is not the `window`'s `contentView`.
///
/// In addition, if the containing `NSWindow` is transformed in any way, including
/// through Mission Control/Exposé, the background blending will fail.
///
/// Note: A NotificationCenter effect is simulated with `kCAFilterColorBrightness @ 0.5`.
public class BackdropView: NSVisualEffectView {
/// The `Effect` structure describes the parameters used by the `BackdropView`
/// to produce its effects. Note that these effects do not cascade to any
/// subviews; that is instead governed by the `BackdropView.effectiveAppearance`.
public struct Effect {
/// The `backgroundColor` is and autoclosure used to dynamically blend with
/// the layers and contents behind the `BackdropView`.
public let backgroundColor: () -> (NSColor)
/// The `tintColor` is an autoclosure used to dynamically set the tint color.
/// This is also the color used when the `BackdropView` is visually inactive.
public let tintColor: () -> (NSColor)
/// The `tintFilter` can be any object accepted by `CALayer.compositingFilter`.
/// `nil`, `darkenBlendMode`, and `lightenBlendMode` are special values.
public let tintFilter: Any?
/// Create a new `BackdropView.Effect` with the provided parameters.
public init(_ backgroundColor: @autoclosure @escaping () -> (NSColor),
_ tintColor: @autoclosure @escaping () -> (NSColor),
_ tintFilter: Any?)
{
self.backgroundColor = backgroundColor
self.tintColor = tintColor
self.tintFilter = tintFilter
}
/// A clear effect (only applies blur and saturation); when inactive,
/// appears transparent. Not suggested for typical use.
public static var clear = Effect(NSColor(calibratedWhite: 1.00, alpha: 0.05),
NSColor(calibratedWhite: 1.00, alpha: 0.00),
nil)
/// A medium light effect.
public static var mediumLight = Effect(NSColor(calibratedWhite: 1.00, alpha: 0.30),
NSColor(calibratedWhite: 0.94, alpha: 1.00),
kCAFilterDarkenBlendMode)
/// A light effect.
public static var light = Effect(NSColor(calibratedWhite: 0.97, alpha: 0.70),
NSColor(calibratedWhite: 0.94, alpha: 1.00),
kCAFilterDarkenBlendMode)
/// An ultra light effect.
public static var ultraLight = Effect(NSColor(calibratedWhite: 0.97, alpha: 0.85),
NSColor(calibratedWhite: 0.94, alpha: 1.00),
kCAFilterDarkenBlendMode)
/// A medium dark effect.
public static var mediumDark = Effect(NSColor(calibratedWhite: 1.00, alpha: 0.40),
NSColor(calibratedWhite: 0.84, alpha: 1.00),
kCAFilterDarkenBlendMode)
/// A dark effect.
public static var dark = Effect(NSColor(calibratedWhite: 0.12, alpha: 0.45),
NSColor(calibratedWhite: 0.16, alpha: 1.00),
kCAFilterLightenBlendMode)
/// An ultra dark effect.
public static var ultraDark = Effect(NSColor(calibratedWhite: 0.12, alpha: 0.80),
NSColor(calibratedWhite: 0.01, alpha: 1.00),
kCAFilterLightenBlendMode)
/// A selection effect that matches the user's current aqua color preference.
public static var selection = Effect(NSColor.keyboardFocusIndicatorColor.withAlphaComponent(0.7),
NSColor.keyboardFocusIndicatorColor,
kCAFilterDestOver)
// Note: `keyboardFocusIndicatorColor` was used because it's the only
// dynamic color that isn't a pattern image color.
}
/// If multiple `BackdropView`s within the same layer tree (that is, window)
/// share the same `BlendGroup`, they will be composited and blended
/// together as a single continuous backdrop. However, setting different
/// `effect`s may cause visual disparity; use with caution.
public final class BlendGroup {
/// The notification posted upon deinit of a `BlendGroup`.
fileprivate static let removedNotification = Notification.Name("BackdropView.BlendGroup.deinit")
/// The internal value used for `CABackdropLayer.groupName`.
fileprivate let value = UUID().uuidString
/// Create a new `BlendGroup`.
public init() {}
deinit {
// Alert all `BackdropView`s that we're about to be removed.
// The `BackdropView` will figure out if it needs to update itself.
NotificationCenter.default.post(name: BlendGroup.removedNotification,
object: nil, userInfo: ["value": self.value])
}
/// The `global` BlendGroup, if it is desired that all backdrops share
/// the same blending group through the layer tree (window).
public static let global = BlendGroup()
/// The default internal value used for `CABackdropLayer.groupName`.
/// This is to be used if no `BlendGroup` is set on the `BackdropView`.
fileprivate static func `default`() -> String {
return UUID().uuidString
}
}
/// If `state` is set to `.followsWindowActiveState` or `NSWorkspace`'s
/// `accessibilityDisplayShouldReduceTransparency` is true, the true visual
/// state of the `BackdropView` may actually be `.active` or `.inactive`,
/// and may change without notice. If such a state change occurs, this property
/// governs whether or not that the visual change is animated.
///
/// Note: this property is disregarded if properties are set within an active
/// `NSAnimationContext` grouping.
public var animatesImplicitStateChanges: Bool = false
/// The visual effect to present within the `BackdropView`. See `BackdropView.Effect`.
public var effect: BackdropView.Effect = .clear {
didSet {
self.transaction {
self.backdrop?.backgroundColor = self.effect.backgroundColor().cgColor
self.tint?.backgroundColor = self.effect.tintColor().cgColor
self.tint?.compositingFilter = self.effect.tintFilter
}
}
}
/// If multiple `BackdropView`s within the same layer tree (that is, window)
/// share the same `BlendGroup`, they will be composited and blended
/// together as a single continuous backdrop. However, setting different
/// `effect`s may cause visual disparity; use with caution.
///
/// Note: you must retain any non-`global` `BlendGroup`s yourself.
public weak var blendingGroup: BlendGroup? = nil {
didSet {
self.transaction {
self.backdrop?.groupName = self.blendingGroup?.value ?? BlendGroup.default()
}
}
}
/// The gaussian blur radius of the visual effect. Animatable.
public var blurRadius: CGFloat {
get { return self.backdrop?.value(forKeyPath: "filters.gaussianBlur.inputRadius") as? CGFloat ?? 0 }
set {
self.transaction {
self.backdrop?.setValue(newValue, forKeyPath: "filters.gaussianBlur.inputRadius")
}
}
}
/// The background color saturation factor of the visual effect. Animatable.
public var saturationFactor: CGFloat {
get { return self.backdrop?.value(forKeyPath: "filters.colorSaturate.inputAmount") as? CGFloat ?? 0 }
set {
self.transaction {
self.backdrop?.setValue(newValue, forKeyPath: "filters.colorSaturate.inputAmount")
}
}
}
/// The corner radius of the view.
public var cornerRadius: CGFloat = 0.0 {
didSet {
self.transaction {
self.container?.cornerRadius = self.cornerRadius
self.rim?.cornerRadius = self.cornerRadius
}
}
}
/// The `BackdropView`'s rim serves to provide a visual contrast at its edges.
/// If `rimOpacity > 0.0`, a slight hairline border is rendered around the view.
///
/// Note: this property, along with `shadow` requires the superview to be
/// layer-backed. The rim is contained 0.5px outside of the view.
public var rimOpacity: CGFloat = 0.0 {
didSet {
self.transaction {
self.rim!.opacity = Float(self.rimOpacity)
}
}
}
/// Automatically `.behindWindow` if set as the contentView of the window.
/// Otherwise, the view is ALWAYS `.withinWindow` blended. Thus, it is not
/// possible to "punch out" the window in specific regions.
public override var blendingMode: NSVisualEffectView.BlendingMode {
get { return self.window?.contentView == self ? .behindWindow : .withinWindow }
set { }
}
/// Always `.appearanceBased`; use `effect` instead.
public override var material: NSVisualEffectView.Material {
get { return .appearanceBased }
set { }
}
/// Specify how the `effect` should reflect window activity or accessibility state.
public override var state: NSVisualEffectView.State {
get { return self._state }
set { self._state = newValue }
}
/// We handle the state differently from our superview, which requires `.active`.
/// Bounce the `state.didSet` onto `reduceTransparencyChanged`.
private var _state: NSVisualEffectView.State = .active {
didSet {
// Don't be called when `commonInit` hasn't finished.
guard let _ = self.backdrop else { return }
self.reduceTransparencyChanged(nil)
}
}
private var backdrop: CABackdropLayer? = nil
private var tint: CALayer? = nil
private var container: CALayer? = nil
private var rim: CALayer? = nil
//private var fallback: CAProxyLayer? = nil
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.commonInit()
}
public required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
self.commonInit()
}
private func commonInit() {
self.wantsLayer = true
self.layerContentsRedrawPolicy = .onSetNeedsDisplay
self.layer?.masksToBounds = false
self.layer?.name = "view"
// Essentially, tell the `NSVisualEffectView` to not do its job:
super.state = .active
super.blendingMode = .withinWindow
super.material = .appearanceBased
self.setValue(true, forKey: "clear") // internal material
// Set up our backdrop view:
self.backdrop = CABackdropLayer()
self.backdrop!.name = "backdrop"
self.backdrop!.allowsGroupBlending = true
self.backdrop!.allowsGroupOpacity = true
self.backdrop!.allowsEdgeAntialiasing = false
self.backdrop!.disablesOccludedBackdropBlurs = true
self.backdrop!.ignoresOffscreenGroups = true
self.backdrop!.allowsInPlaceFiltering = false // blendgroups don't work otherwise
self.backdrop!.scale = 1.0 // 0.25 typically
self.backdrop!.bleedAmount = 0.0
// Set up the backdrop filters:
let blur = CAFilter(type: kCAFilterGaussianBlur)!
let saturate = CAFilter(type: kCAFilterColorSaturate)!
blur.setValue(true, forKey: "inputNormalizeEdges")
self.backdrop!.filters = [blur, saturate]
// Set up the fallback layer used when the window is transformed:
/*
self.fallback = CAProxyLayer()
self.fallback!.name = "fallback"
self.fallback!.proxyProperties = [
kCAProxyLayerLevel: 1,
kCAProxyLayerActive: true,
kCAProxyLayerBlendMode: "PlusD",
kCAProxyLayerMaterial: "L"
]
*/
// Set up the tint and container view:
self.tint = CALayer()
self.tint!.name = "tint"
self.container = CALayer()
self.container!.name = "container"
self.container!.masksToBounds = true
self.container!.allowsGroupBlending = true
self.container!.allowsEdgeAntialiasing = false
self.container!.sublayers = [self.backdrop!, self.tint!]
self.layer?.insertSublayer(self.container!, at: 0)
// Set up rim:
self.rim = CALayer()
self.rim!.name = "rim"
self.rim!.borderWidth = 0.5
self.rim!.opacity = 0.0
self.layer?.addSublayer(self.rim!)
// Set our effect-related properties:
self._state = .followsWindowActiveState
self.blendingGroup = nil
self.blurRadius = 30.0
self.saturationFactor = 2.5
self.effect = .dark
// [Note] macOS 11+: no longer necessary to call `removeObserver` upon `deinit`.
NotificationCenter.default.addObserver(self, selector: #selector(self.reduceTransparencyChanged(_:)),
name: NSWorkspace.accessibilityDisplayOptionsDidChangeNotification,
object: NSWorkspace.shared)
NotificationCenter.default.addObserver(self, selector: #selector(self.colorVariantsChanged(_:)),
name: NSColor.systemColorsDidChangeNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.blendGroupsChanged(_:)),
name: BlendGroup.removedNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.layerSurfaceChanged(_:)),
name: BackdropView.layerSurfaceFlattenedNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.layerSurfaceChanged(_:)),
name: BackdropView.layerSurfaceFlushedNotification, object: nil)
}
/// Update sublayer `frame`.
public override func layout() {
super.layout()
self.transaction(false) {
self.container!.frame = self.layer?.bounds ?? .zero
self.backdrop!.frame = self.layer?.bounds ?? .zero
self.tint!.frame = self.layer?.bounds ?? .zero
self.rim!.frame = self.layer?.bounds.insetBy(dx: -0.5, dy: -0.5) ?? .zero
//self.fallback?.frame = self.layer?.bounds ?? .zero
}
}
/// Update sublayer `contentsScale`.
public override func viewDidChangeBackingProperties() {
super.viewDidChangeBackingProperties()
let scale = self.window?.backingScaleFactor ?? 1.0
self.transaction(false) {
self.layer?.contentsScale = scale
self.container!.contentsScale = scale
self.backdrop!.contentsScale = scale
self.tint!.contentsScale = scale
self.rim!.contentsScale = scale
//self.fallback?.contentsScale = scale
}
}
/// Toggle `CAProxyLayer` visibility if our layers were flattened.
@objc private func layerSurfaceChanged(_ note: NSNotification!) {
guard let win = note.userInfo?["window"] as? NSWindow, win.contentView == self else { return }
//let proxyVisible = note.userInfo?["proxy"] as? Bool ?? false
/*
// Update the `material` based on our `effectiveAppearance`.
var props = self.fallback!.proxyProperties!
props[kCAProxyLayerMaterial] = "L"//self.effectiveAppearance.name == .vibrantDark ? "D" : "L"
self.fallback!.proxyProperties = props
// Toggle visibility.
CATransaction.begin()
if proxyVisible {
self.container!.insertSublayer(self.fallback!, at: 1)
} else {
self.fallback!.removeFromSuperlayer()
}
CATransaction.commit()
CATransaction.flush()
*/
}
/// Adjust our `BlendGroup` information if we need to.
@objc private func blendGroupsChanged(_ note: NSNotification!) {
guard let removed = note.userInfo?["value"] as? String else { return }
guard let backdrop = self.backdrop, backdrop.groupName == removed else { return }
self.transaction(self.animatesImplicitStateChanges) {
backdrop.groupName = BlendGroup.default() // was nil'd out
}
}
/// Allow dynamic/system colors update themselves.
@objc private func colorVariantsChanged(_ note: NSNotification!) {
guard let _ = self.backdrop else { return }
DispatchQueue.main.async {
self.transaction(self.animatesImplicitStateChanges) {
self.backdrop!.backgroundColor = self.effect.backgroundColor().cgColor
self.tint!.backgroundColor = self.effect.tintColor().cgColor
}
}
}
/// Modifies sublayers if the dynamic property `reduceTransparency` has changed.
@objc private func reduceTransparencyChanged(_ note: NSNotification!) {
// If `note` is `nil`, it is considered that we invoked this method from
// `BackdropView.state.didSet` - if so, allow animation of the visual state
// if called within an `NSAnimationContext` grouping.
let actions = (
self.animatesImplicitStateChanges ||
(note == nil && (CATransaction.value(forKey: "NSAnimationContextBeganGroup") as? Bool ?? false))
)
let reduceTransparency = (
NSWorkspace.shared.accessibilityDisplayShouldReduceTransparency ||
self._state == .inactive ||
(self._state == .followsWindowActiveState && !(self.window?.isMainWindow ?? false))
)
// Enable/disable the backdrop layer and tint layer's `compositingFilter`.
self.transaction(actions) {
self.backdrop!.isEnabled = !reduceTransparency
self.tint!.compositingFilter = !reduceTransparency ? self.effect.tintFilter : nil
// Allows the actual animation to work; `-setEnabled:` isn't animated.
if reduceTransparency {
self.backdrop!.removeFromSuperlayer()
} else {
self.container!.insertSublayer(self.backdrop!, at: 0)
}
}
}
/// Creates a nested transaction whose actions are only enabled by default if
/// called within an active `NSAnimationContext` grouping.
///
/// Note: also sets the current NSAppearance for drawing purposes.
private func transaction(_ actions: Bool? = nil, _ handler: () -> ()) {
let actions = actions ?? CATransaction.value(forKey: "NSAnimationContextBeganGroup") as? Bool ?? false
// NSAnimationContext handles per-thread activation of CATransaction for us.
NSAnimationContext.beginGrouping()
CATransaction.setDisableActions(!actions)
let saved = NSAppearance.current
NSAppearance.current = self.effectiveAppearance
handler()
NSAppearance.current = saved
NSAnimationContext.endGrouping()
}
public override func viewWillMove(toWindow newWindow: NSWindow?) {
super.viewWillMove(toWindow: newWindow)
// Restore our window's settings, if we were the `contentView` only.
if let oldWindow = self.window, oldWindow.contentView == self {
self.configurator.unapply(from: oldWindow)
}
// Unregister window main-ness changes:
guard let _ = self.window else { return }
NotificationCenter.default.removeObserver(self, name: NSWindow.didBecomeMainNotification,
object: self.window!)
NotificationCenter.default.removeObserver(self, name: NSWindow.didResignMainNotification,
object: self.window!)
}
public override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
self.state = .active
// Adjust the backdrop layer's WindowServer awareness.
self.backdrop?.windowServerAware = (self.window?.contentView == self)
// Set parent window configuration, if we're the `contentView` only.
if let newWindow = self.window, newWindow.contentView == self {
self.configurator.apply(to: newWindow)
}
self.cornerRadius = 4.5
self.rimOpacity = 0.25
let s = NSShadow()
s.shadowColor = NSColor.black.withAlphaComponent(0.8)
s.shadowBlurRadius = 20
self.shadow = s
// Register for window main-ness changes:
guard let _ = self.window else { return }
NotificationCenter.default.addObserver(self, selector: #selector(self.reduceTransparencyChanged(_:)),
name: NSWindow.didBecomeMainNotification, object: self.window!)
NotificationCenter.default.addObserver(self, selector: #selector(self.reduceTransparencyChanged(_:)),
name: NSWindow.didResignMainNotification, object: self.window!)
self.reduceTransparencyChanged(NSNotification(name: NSWindow.didBecomeMainNotification, object: nil))
}
//
// [Private SPI] HERE LIE DRAGONS!
//
private var configurator = WindowConfigurator()
/// Declared for NSVisualEffectView; affects non-contentView backdrops.
@objc private func _shouldAutoFlattenLayerTree() -> Bool {
return false
}
/// Controls key `NSWindow` operations if the `BackdropView` is its `contentView`.
private struct WindowConfigurator {
private var observer: Any? = nil
private var shouldAutoFlattenLayerTree = true
private var canHostLayersInWindowServer = true
private var isOpaque = false
private var backgroundColor: NSColor? = nil
/// Call upon migration to a new window.
mutating func apply(to newWindow: NSWindow) {
let cid = NSApp.value(forKey: "contextID") as! Int32
self.shouldAutoFlattenLayerTree = newWindow.value(forKey: "shouldAutoFlattenLayerTree") as? Bool ?? true
self.canHostLayersInWindowServer = newWindow.value(forKey: "canHostLayersInWindowServer") as? Bool ?? true
self.isOpaque = newWindow.isOpaque
self.backgroundColor = newWindow.backgroundColor
// The WindowServer automatically flattens the render layer tree on its
// end after a delayed duration (currently 1.05s). This is likely to
// allow higher performance in windows that don't require effects.
newWindow.setValue(false, forKey: "shouldAutoFlattenLayerTree")
// `CGSSetSurfaceLayerBackingOptions` needs to be set to prevent layer
// tree flattening, and `NSWindow` doesn't inform its `NSViewLayerSurface`s
// to do this, EXCEPT upon initial surface creation, which happens
// during the first call to `-[NSWindow displayIfNeeded]`, where the layer
// tree is set up to match the `NSView` tree.
//
// A possible fix would be to grab "borderView.layerSurface.surface.surfaceID"
// and call `CGSSetSurfaceLayerBackingOptions` ourselves, but we don't
// consider currently set AppKit defaults.
//
// Instead, a simple workaround is to toggle `canHostLayersInWindowServer`
// off and back on again, as this recreates the layer tree immediately
// in both cases. This is, however, an "expensive" operation, but we
// don't expect to be swapping `contentView` in and out rapidly anyway.
newWindow.setValue(false, forKey: "canHostLayersInWindowServer")
newWindow.setValue(true, forKey: "canHostLayersInWindowServer")
// If the window is not opaque, the `CABackdropLayer` cannot sample behind it.
newWindow.isOpaque = false
// If the window's `backgroundColor` is `.clear`, the theme frame/`borderView`
// will unfortunately turn off corner masking, which then causes terrible
// window resize lag. This is likely because without a mask, WindowServer
// recomputes the "real shape" for any non-opaque windows.
newWindow.backgroundColor = NSColor.white.withAlphaComponent(0.001)
// The kCGSNeverFlattenSurfacesDuringSwipesTagBit tells WindowServer to
// not flatten the layer tree on its end, during Spaces swipes.
let fixSurfaces: () -> () = { [weak newWindow] in
guard let newWindow = newWindow else { return }
var x: [Int32] = [0x0, (1 << 23)/*kCGSNeverFlattenSurfacesDuringSwipesTagBit?*/]
_ = CGSSetWindowTags(cid, Int32(newWindow.windowNumber),
&x, 0x40/*kCGSRealMaximumTagSize*/)
}
// Since `_startLiveResize` and the balanced `_endLiveResize` calls made
// to `NSWindow` add and then reset this tag, respectively, we want to
// make sure we restore it ourselves upon `_endLiveResize` using this note.
DispatchQueue.main.async(execute: fixSurfaces)
self.observer = NotificationCenter.default.addObserver(forName: NSWindow.didEndLiveResizeNotification, object: newWindow, queue: nil) { _ in
DispatchQueue.main.async(execute: fixSurfaces)
}
var transformed = false
let flushSurfaces: () -> () = { [weak newWindow] in
guard let newWindow = newWindow else { return }
let wid = Int32(newWindow.windowNumber)
var q = CGAffineTransform.identity, p = CGAffineTransform.identity
CGSGetCatenatedWindowTransform(cid, wid, &q)
let _transformed = !(q.a == p.a && q.b == p.b && q.c == p.c && q.d == p.d)
if (transformed != _transformed) && _transformed /* transformed */ {
//DispatchQueue.main.async {
NotificationCenter.default.post(name: BackdropView.layerSurfaceFlattenedNotification,
object: nil, userInfo: ["window": newWindow, "proxy": true])
//}
} else if (transformed != _transformed) && !_transformed /* untransformed */ {
// Flush the layer surface, as we were just un-transformed, and
// WindowServer doesn't do this for us.
if let sid = newWindow.value(forKeyPath: "borderView.layerSurface.surface.surfaceID") as? Int32 {
CGSFlushSurface(cid, wid, sid, 0)
}
//DispatchQueue.main.async {
NotificationCenter.default.post(name: BackdropView.layerSurfaceFlushedNotification,
object: nil, userInfo: ["window": newWindow, "proxy": false])
//}
}
transformed = _transformed
}
// Wrap into a "timer" of some sort.
func follow() {
flushSurfaces()
DispatchQueue.global().asyncAfter(deadline: .now() + .milliseconds(500), execute: follow)
}
DispatchQueue.global().async(execute: follow)
}
/// Call upon migration away from an existing window.
mutating func unapply(from oldWindow: NSWindow) {
// See the above notes for the particular order of operations.
oldWindow.setValue(self.shouldAutoFlattenLayerTree, forKey: "shouldAutoFlattenLayerTree")
oldWindow.setValue(false, forKey: "canHostLayersInWindowServer")
oldWindow.setValue(self.canHostLayersInWindowServer, forKey: "canHostLayersInWindowServer")
oldWindow.isOpaque = self.isOpaque
oldWindow.backgroundColor = self.backgroundColor
// There's no need to clear the kCGSNeverFlattenSurfacesDuringSwipesTagBit
// window tag, as the window will manage that itself upon resize.
NotificationCenter.default.removeObserver(self.observer!)
}
}
/// Emitted when we detect that our containing window was transformed.
/// As of macOS 13, all layer surfaces are forcibly flattened when a window is transformed.
///
/// `userInfo` keys:
/// - `window`: the window.
/// - `proxy`: boolean indicating CAProxyLayer usage.
private static let layerSurfaceFlattenedNotification = Notification.Name("BackdropView.layerSurfaceFlattenedNotification")
/// Emitted when we flush the layer surface after our containing window was un-transformed.
///
/// `userInfo` keys:
/// - `window`: the window.
/// - `proxy`: boolean indicating CAProxyLayer usage.
private static let layerSurfaceFlushedNotification = Notification.Name("BackdropView.layerSurfaceFlushedNotification")
}
@_silgen_name("CGSSetWindowTags")
func CGSSetWindowTags(_ cid: Int32, _ wid: Int32, _ tags: UnsafePointer<Int32>!, _ maxTagSize: Int) -> CGError
@OmidRostami
Copy link

What should be imported? I am getting so many errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment