Skip to content

Instantly share code, notes, and snippets.

@MaximBazarov
Last active October 31, 2024 01:20
Show Gist options
  • Save MaximBazarov/30240366d60763efbc50324084ea8b47 to your computer and use it in GitHub Desktop.
Save MaximBazarov/30240366d60763efbc50324084ea8b47 to your computer and use it in GitHub Desktop.
import AppKit
import SwiftUI
import Combine
import OSLog
/// Full screen window that hosts the SwiftUI view.
/// - Can not become active nor main so user stays in the app they were in.
/// - Ignores all mouse events allowing user to interact with content behind it.
/// - Enables mouse events, only when mouse is over its content view.
@MainActor
final class PassThroughWindow<Content: View>: NSPanel {
/// Provided ``CurrentValueSubject`` to listen to the hover over SwiftUI view events.
var isMouseTrackingEnabled: CurrentValueSubject<Bool, Never>
private let log = Logger(subsystem: "Window Management", category: "PassThroughWindow<Content: View>")
private var subscriptions: Set<AnyCancellable> = []
private let content: Content
// Prevents window to be activated, we don't want that because then we remove focus
// from the active application and change it to our app, which creates a subpar user experience.
override var canBecomeKey: Bool { false }
override var canBecomeMain: Bool { false }
public init(
isMouseTrackingEnabled: CurrentValueSubject<Bool, Never>,
@ViewBuilder _ content: @escaping () -> Content
) {
self.isMouseTrackingEnabled = isMouseTrackingEnabled
let contentRect = NSRect.zero
self.content = content()
super.init(
contentRect: contentRect,
styleMask: [
.nonactivatingPanel,
.fullSizeContentView,
],
backing: .buffered,
defer: false
)
collectionBehavior = [
.canJoinAllSpaces,
.ignoresCycle,
.transient
]
// Allow the panel to appear in a fullscreen space
collectionBehavior.insert(.fullScreenAuxiliary)
// UI tweaks to be invisible.
isOpaque = false
hasShadow = false
backgroundColor = .clear
// Allow the panel to be on top of almost all other windows
isFloatingPanel = true
level = .floating
hidesOnDeactivate = false
becomesKeyOnlyIfNeeded = true
// While we may set a title for the window, don't show it
titleVisibility = .hidden
titlebarAppearsTransparent = true
isMovableByWindowBackground = false
acceptsMouseMovedEvents = true
orderFrontRegardless()
allowsToolTipsWhenApplicationIsInactive = true
let content = NSHostingView(rootView: self.content)
content.autoresizingMask = [.height, .width]
contentView?.addSubview(content)
// We activate/deactivate based on what the container tells us.
isMouseTrackingEnabled.sink { isEnabled in
isEnabled
? self.activateInteractions()
: self.deactivateInteractions()
}.store(in: &subscriptions)
}
/// Activates interaction with the window
func activateInteractions() {
ignoresMouseEvents = false
}
/// Deactivates interaction with the window
func deactivateInteractions() {
ignoresMouseEvents = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment