import AppKit import ConjureKit @MainActor final class LauncherAlignmentGuides { private let view = AlignmentGuideView() private lazy var overlay: NSPanel = { let panel = NSPanel(contentRect: .zero, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false) panel.backgroundColor = .clear panel.isOpaque = false panel.hasShadow = false panel.ignoresMouseEvents = true panel.isReleasedWhenClosed = false panel.collectionBehavior = [.moveToActiveSpace, .fullScreenAuxiliary] panel.contentView = view panel.setAccessibilityElement(false) return panel }() private var dragging = false private var updating = false private var dragOrigin = NSPoint.zero private var mouseOrigin = NSPoint.zero func begin(for window: NSWindow) { guard !dragging, window.screen != nil else { return } dragging = true dragOrigin = window.frame.origin mouseOrigin = NSEvent.mouseLocation update(for: window) overlay.level = window.level overlay.order(.below, relativeTo: window.windowNumber) } func update(for window: NSWindow) { guard dragging, !updating, let screen = window.screen else { return } updating = true defer { updating = false } let frame = screen.visibleFrame let mouse = NSEvent.mouseLocation let proposed = NSRect( origin: NSPoint(x: dragOrigin.x + mouse.x - mouseOrigin.x, y: dragOrigin.y + mouse.y - mouseOrigin.y), size: window.frame.size ) let alignment = LauncherAlignment(windowFrame: proposed, visibleFrame: frame) if window.frame.origin != alignment.snappedOrigin { window.setFrameOrigin(alignment.snappedOrigin) } if overlay.frame != frame { overlay.setFrame(frame, display: false) } view.targetFrame = alignment.targetFrame.offsetBy(dx: -frame.minX, dy: -frame.minY) view.launcherFrame = window.frame.offsetBy(dx: -frame.minX, dy: -frame.minY) view.alignmentTolerance = 1 / screen.backingScaleFactor view.needsDisplay = true } func end() { guard dragging else { return } dragging = false overlay.orderOut(nil) } } @MainActor private final class AlignmentGuideView: NSView { var launcherFrame = NSRect.zero var targetFrame = NSRect.zero var alignmentTolerance: CGFloat = 0.5 private static let dash: [CGFloat] = [1, 5] private static let muted = SunsetPalette.accent.withAlphaComponent(0.4).cgColor private static let aligned = SunsetPalette.accent.withAlphaComponent(0.95).cgColor private static let shadow = NSColor.black.withAlphaComponent(0.6).cgColor override func hitTest(_ point: NSPoint) -> NSView? { nil } override func draw(_ dirtyRect: NSRect) { guard let context = NSGraphicsContext.current?.cgContext else { return } context.saveGState() defer { context.restoreGState() } context.addRect(bounds) context.addRect(launcherFrame.insetBy(dx: 0.75, dy: 0.75)) context.clip(using: .evenOdd) context.setLineWidth(1.5) context.setLineCap(.round) context.setLineDash(phase: 0, lengths: Self.dash) context.setShadow(offset: .zero, blur: 2, color: Self.shadow) let centeredX = abs(launcherFrame.minX - targetFrame.minX) <= alignmentTolerance context.setStrokeColor(centeredX ? Self.aligned : Self.muted) context.move(to: NSPoint(x: targetFrame.minX, y: bounds.minY)) context.addLine(to: NSPoint(x: targetFrame.minX, y: bounds.maxY)) context.move(to: NSPoint(x: targetFrame.maxX, y: bounds.minY)) context.addLine(to: NSPoint(x: targetFrame.maxX, y: bounds.maxY)) context.strokePath() let centeredY = abs(launcherFrame.maxY - targetFrame.maxY) <= alignmentTolerance context.setStrokeColor(centeredY ? Self.aligned : Self.muted) context.move(to: NSPoint(x: bounds.minX, y: targetFrame.maxY)) context.addLine(to: NSPoint(x: bounds.maxX, y: targetFrame.maxY)) context.strokePath() } }