1+ // swiftlint:disable file_header
2+ import Foundation
3+ import CoreGraphics
4+ import AppKit
5+
6+ /// Utility functions for coordinate system conversions between AppKit and CoreGraphics
7+ /// with proper multi-monitor support
8+ struct CoordinateUtils {
9+
10+ // MARK: - AppKit to CoreGraphics Conversion
11+
12+ /// Converts AppKit coordinates to CoreGraphics coordinates for multi-monitor setups
13+ /// - Parameter appKitPosition: Position in AppKit coordinate system (Y increases upward from screen bottom)
14+ /// - Returns: Position in CoreGraphics coordinate system (Y increases downward from screen top)
15+ static func convertAppKitToCoreGraphics( _ appKitPosition: CGPoint ) -> CGPoint {
16+ // Find which screen contains this point
17+ for screen in NSScreen . screens {
18+ if screen. frame. contains ( appKitPosition) {
19+ // FIXED: Proper multi-monitor coordinate conversion
20+ // AppKit Y increases upward from screen bottom
21+ // CoreGraphics Y increases downward from screen top
22+ // Formula: CG_Y = screen.origin.Y + (screen.height - (AppKit_Y - screen.origin.Y))
23+ let relativeY = appKitPosition. y - screen. frame. origin. y // Y relative to screen bottom
24+ let cgY = screen. frame. origin. y + ( screen. frame. height - relativeY) // Convert to CG coordinates
25+ let cgPosition = CGPoint ( x: appKitPosition. x, y: cgY)
26+
27+ print ( " [CoordinateUtils] Multi-monitor conversion: AppKit \( appKitPosition) → CoreGraphics \( cgPosition) on screen \( screen. frame) " )
28+ print ( " [CoordinateUtils] Calculation: relativeY= \( relativeY) , cgY= \( screen. frame. origin. y) + ( \( screen. frame. height) - \( relativeY) ) = \( cgY) " )
29+
30+ return cgPosition
31+ }
32+ }
33+
34+ // Fallback to main screen if no screen contains the point
35+ let mainScreenHeight = NSScreen . main? . frame. height ?? 0
36+ let fallbackPosition = CGPoint ( x: appKitPosition. x, y: mainScreenHeight - appKitPosition. y)
37+ print ( " [CoordinateUtils] Fallback conversion: AppKit \( appKitPosition) → CoreGraphics \( fallbackPosition) " )
38+ return fallbackPosition
39+ }
40+
41+ // MARK: - CoreGraphics to AppKit Conversion
42+
43+ /// Converts CoreGraphics coordinates back to AppKit coordinates for multi-monitor setups
44+ /// - Parameter cgPosition: Position in CoreGraphics coordinate system (Y increases downward from screen top)
45+ /// - Returns: Position in AppKit coordinate system (Y increases upward from screen bottom)
46+ static func convertCoreGraphicsToAppKit( _ cgPosition: CGPoint ) -> CGPoint {
47+ // Find which screen this CoreGraphics position would map to
48+ // This is a reverse lookup - we need to find the screen that would contain the original AppKit position
49+ for screen in NSScreen . screens {
50+ // Check if this position could have come from this screen
51+ let potentialAppKitY = screen. frame. maxY - cgPosition. y
52+ let potentialAppKitPosition = CGPoint ( x: cgPosition. x, y: potentialAppKitY)
53+
54+ if screen. frame. contains ( potentialAppKitPosition) {
55+ print ( " [CoordinateUtils] CoreGraphics \( cgPosition) → AppKit \( potentialAppKitPosition) on screen \( screen. frame) " )
56+ return potentialAppKitPosition
57+ }
58+ }
59+
60+ // Fallback to main screen conversion
61+ let mainScreenHeight = NSScreen . main? . frame. height ?? 0
62+ let fallbackPosition = CGPoint ( x: cgPosition. x, y: mainScreenHeight - cgPosition. y)
63+ print ( " [CoordinateUtils] Fallback reverse conversion: CoreGraphics \( cgPosition) → AppKit \( fallbackPosition) " )
64+ return fallbackPosition
65+ }
66+
67+ // MARK: - Multi-Monitor Support Functions
68+
69+ /// Checks if a position is within any available screen bounds
70+ /// - Parameter position: Position to check (in AppKit coordinates)
71+ /// - Returns: True if position is within any screen bounds
72+ static func isPositionWithinAnyScreen( _ position: CGPoint ) -> Bool {
73+ for screen in NSScreen . screens {
74+ if screen. frame. contains ( position) {
75+ print ( " [CoordinateUtils] Position \( position) is valid on screen: \( screen. frame) " )
76+ return true
77+ }
78+ }
79+ print ( " [CoordinateUtils] Position \( position) is not within any screen bounds " )
80+ return false
81+ }
82+
83+ /// Gets all available screen frames for debugging purposes
84+ /// - Returns: Array of all screen frames
85+ static func getAllScreenFrames( ) -> [ CGRect ] {
86+ return NSScreen . screens. map { $0. frame }
87+ }
88+
89+ /// Gets the main screen frame
90+ /// - Returns: Main screen frame, or zero rect if unavailable
91+ static func getMainScreenFrame( ) -> CGRect {
92+ return NSScreen . main? . frame ?? CGRect . zero
93+ }
94+ }
0 commit comments