CocoaExtension // Introduce Alexis Bridoux's NSWindowPositioner.
This commit is contained in:
parent
225e95655c
commit
3b2ef22c88
|
@ -0,0 +1,158 @@
|
|||
// Free to use
|
||||
// Written by Alexis Bridoux - https://github.com/ABridoux
|
||||
// Ref: https://gist.github.com/ABridoux/b935c21c7ead92033d39b357fae6366b
|
||||
|
||||
import Cocoa
|
||||
import SwiftUI
|
||||
|
||||
// MARK: Model
|
||||
|
||||
extension NSWindow {
|
||||
public struct Position {
|
||||
public static let defaultPadding: CGFloat = 16
|
||||
|
||||
public var vertical: Vertical
|
||||
public var horizontal: Horizontal
|
||||
public var padding = Self.defaultPadding
|
||||
}
|
||||
}
|
||||
|
||||
extension NSWindow.Position {
|
||||
public enum Horizontal {
|
||||
case left, center, right
|
||||
}
|
||||
|
||||
public enum Vertical {
|
||||
case top, center, bottom
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Logic
|
||||
|
||||
extension NSWindow.Position {
|
||||
public func value(forWindow windowRect: CGRect, inScreen screenRect: CGRect) -> CGPoint {
|
||||
let xPosition = horizontal.valueFor(
|
||||
screenRange: screenRect.minX..<screenRect.maxX,
|
||||
width: windowRect.width,
|
||||
padding: padding
|
||||
)
|
||||
|
||||
let yPosition = vertical.valueFor(
|
||||
screenRange: screenRect.minY..<screenRect.maxY,
|
||||
height: windowRect.height,
|
||||
padding: padding
|
||||
)
|
||||
|
||||
return CGPoint(x: xPosition, y: yPosition)
|
||||
}
|
||||
}
|
||||
|
||||
extension NSWindow.Position.Horizontal {
|
||||
public func valueFor(
|
||||
screenRange: Range<CGFloat>,
|
||||
width: CGFloat,
|
||||
padding: CGFloat
|
||||
)
|
||||
-> CGFloat
|
||||
{
|
||||
switch self {
|
||||
case .left: return screenRange.lowerBound + padding
|
||||
case .center: return (screenRange.upperBound + screenRange.lowerBound - width) / 2
|
||||
case .right: return screenRange.upperBound - width - padding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension NSWindow.Position.Vertical {
|
||||
public func valueFor(
|
||||
screenRange: Range<CGFloat>,
|
||||
height: CGFloat,
|
||||
padding: CGFloat
|
||||
)
|
||||
-> CGFloat
|
||||
{
|
||||
switch self {
|
||||
case .top: return screenRange.upperBound - height - padding
|
||||
case .center: return (screenRange.upperBound + screenRange.lowerBound - height) / 2
|
||||
case .bottom: return screenRange.lowerBound + padding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppKit extension
|
||||
|
||||
extension NSWindow {
|
||||
public func setPosition(_ position: Position, in screen: NSScreen?) {
|
||||
guard let visibleFrame = (screen ?? self.screen)?.visibleFrame else { return }
|
||||
let origin = position.value(forWindow: frame, inScreen: visibleFrame)
|
||||
setFrameOrigin(origin)
|
||||
}
|
||||
|
||||
public func setPosition(
|
||||
vertical: Position.Vertical,
|
||||
horizontal: Position.Horizontal,
|
||||
padding: CGFloat = Position.defaultPadding,
|
||||
screen: NSScreen? = nil
|
||||
) {
|
||||
setPosition(
|
||||
Position(vertical: vertical, horizontal: horizontal, padding: padding),
|
||||
in: screen
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SwiftUI modifier
|
||||
|
||||
#if canImport(SwiftUI)
|
||||
|
||||
/// - note: Idea from [LostMoa](https://lostmoa.com/blog/ReadingTheCurrentWindowInANewSwiftUILifecycleApp/)
|
||||
@available(macOS 10.15, *)
|
||||
public struct HostingWindowFinder: NSViewRepresentable {
|
||||
public var callback: (NSWindow?) -> Void
|
||||
|
||||
public func makeNSView(context _: Self.Context) -> NSView {
|
||||
let view = NSView()
|
||||
DispatchQueue.main.async { self.callback(view.window) }
|
||||
return view
|
||||
}
|
||||
|
||||
public func updateNSView(_ nsView: NSView, context _: Context) {
|
||||
DispatchQueue.main.async { self.callback(nsView.window) }
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
private struct WindowPositionModifier: ViewModifier {
|
||||
let position: NSWindow.Position
|
||||
let screen: NSScreen?
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.background(
|
||||
HostingWindowFinder {
|
||||
$0?.setPosition(position, in: screen)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@available(macOS 10.15, *)
|
||||
extension View {
|
||||
public func hostingWindowPosition(
|
||||
vertical: NSWindow.Position.Vertical,
|
||||
horizontal: NSWindow.Position.Horizontal,
|
||||
padding: CGFloat = NSWindow.Position.defaultPadding,
|
||||
screen: NSScreen? = nil
|
||||
) -> some View {
|
||||
modifier(
|
||||
WindowPositionModifier(
|
||||
position: NSWindow.Position(
|
||||
vertical: vertical,
|
||||
horizontal: horizontal,
|
||||
padding: padding
|
||||
),
|
||||
screen: screen
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
#endif
|
Loading…
Reference in New Issue