diff --git a/DockDoor.xcodeproj/project.pbxproj b/DockDoor.xcodeproj/project.pbxproj index ac8d88d..093a528 100644 --- a/DockDoor.xcodeproj/project.pbxproj +++ b/DockDoor.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 3A1622CC2C8D5F5B00D318EE /* FirstTimePermissionsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1622CB2C8D5F5B00D318EE /* FirstTimePermissionsTabView.swift */; }; 3A1622CE2C8D5F8500D318EE /* FirstTimeCongratsTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1622CD2C8D5F8500D318EE /* FirstTimeCongratsTabView.swift */; }; 3A1622D02C8D691700D318EE /* SquiggleDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A1622CF2C8D691700D318EE /* SquiggleDivider.swift */; }; + BB0036D02D24AAD20072C61E /* CGPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB0036CF2D24AAD10072C61E /* CGPoint.swift */; }; BB04405B2C77A013009F1D33 /* GradientColorPaletteSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB04405A2C77A011009F1D33 /* GradientColorPaletteSettings.swift */; }; BB04405E2C77A87F009F1D33 /* PreferencesProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB04405D2C77A87E009F1D33 /* PreferencesProtocol.swift */; }; BB0440602C77AD2C009F1D33 /* CodableColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB04405F2C77AD27009F1D33 /* CodableColor.swift */; }; @@ -121,6 +122,7 @@ 3A1622CB2C8D5F5B00D318EE /* FirstTimePermissionsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimePermissionsTabView.swift; sourceTree = ""; }; 3A1622CD2C8D5F8500D318EE /* FirstTimeCongratsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeCongratsTabView.swift; sourceTree = ""; }; 3A1622CF2C8D691700D318EE /* SquiggleDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquiggleDivider.swift; sourceTree = ""; }; + BB0036CF2D24AAD10072C61E /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = ""; }; BB04405A2C77A011009F1D33 /* GradientColorPaletteSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientColorPaletteSettings.swift; sourceTree = ""; }; BB04405D2C77A87E009F1D33 /* PreferencesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesProtocol.swift; sourceTree = ""; }; BB04405F2C77AD27009F1D33 /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = ""; }; @@ -241,10 +243,9 @@ 3A105FD72C1BF11D0015EC66 /* Extensions */ = { isa = PBXGroup; children = ( - BB1F60E42D238A9700808E05 /* CGSize.swift */, + BB0036D12D24AAE60072C61E /* CoreGraphics */, BBE153C12D22024E00CD3D4A /* View.swift */, BBAC6B442D15FA8800A0F370 /* AXValue.swift */, - BB918BB42CF7613200931236 /* CGRect.swift */, 27DC8C162CFA4DA7005F8F31 /* Formatters */, BB3D6EAF2C84E58000FFE584 /* NSScreen.swift */, BBBD4AF42C8D671A0074FFCF /* NSImage.swift */, @@ -300,6 +301,16 @@ path = Tabs; sourceTree = ""; }; + BB0036D12D24AAE60072C61E /* CoreGraphics */ = { + isa = PBXGroup; + children = ( + BB918BB42CF7613200931236 /* CGRect.swift */, + BB1F60E42D238A9700808E05 /* CGSize.swift */, + BB0036CF2D24AAD10072C61E /* CGPoint.swift */, + ); + path = CoreGraphics; + sourceTree = ""; + }; BB04405C2C77A878009F1D33 /* Defaults */ = { isa = PBXGroup; children = ( @@ -652,6 +663,7 @@ BBF413942C40978400AA6733 /* WindowPreviewHoverContainer.swift in Sources */, BBF413922C40618D00AA6733 /* FullSizePreviewView.swift in Sources */, BB15E1F12C8FBCD4002ECFB9 /* BugAndFeatureReporting.swift in Sources */, + BB0036D02D24AAD20072C61E /* CGPoint.swift in Sources */, BB15E1F52C8FBEAB002ECFB9 /* DonationView.swift in Sources */, BBCFAE402C266234002E1516 /* FirstTimeView.swift in Sources */, BB4B444B2C372A4E006680DC /* WindowPreview.swift in Sources */, diff --git a/DockDoor/Extensions/CGSize.swift b/DockDoor/Extensions/CGSize.swift deleted file mode 100644 index 8a120cf..0000000 --- a/DockDoor/Extensions/CGSize.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Cocoa - -extension CGSize { - static func + (lhs: CGSize, rhs: CGSize) -> CGSize { - CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) - } -} diff --git a/DockDoor/Extensions/CoreGraphics/CGPoint.swift b/DockDoor/Extensions/CoreGraphics/CGPoint.swift new file mode 100644 index 0000000..62c33d2 --- /dev/null +++ b/DockDoor/Extensions/CoreGraphics/CGPoint.swift @@ -0,0 +1,39 @@ +import Cocoa + +extension CGPoint { + func screen() -> NSScreen? { + // Try direct containment first + if let screen = NSScreen.screens.first(where: { $0.frame.contains(self) }) { + return screen + } + + // If that fails, try using the screen's visible frame + if let screen = NSScreen.screens.first(where: { $0.visibleFrame.contains(self) }) { + return screen + } + + // If still no match, find the nearest screen by calculating distance to center + return NSScreen.screens.min(by: { screen1, screen2 in + let distance1 = self.distance(to: CGPoint(x: screen1.frame.midX, y: screen1.frame.midY)) + let distance2 = self.distance(to: CGPoint(x: screen2.frame.midX, y: screen2.frame.midY)) + return distance1 < distance2 + }) ?? NSScreen.main + } + + func distance(to point: CGPoint) -> CGFloat { + let dx = x - point.x + let dy = y - point.y + return sqrt(dx * dx + dy * dy) + } + + func displace(by point: CGPoint = .init(x: 0.0, y: 0.0)) -> CGPoint { + CGPoint(x: x + point.x, + y: y + point.y) + } + + /// Caps the point to the unit space + func capped() -> CGPoint { + CGPoint(x: max(min(x, 1), 0), + y: max(min(y, 1), 0)) + } +} diff --git a/DockDoor/Extensions/CGRect.swift b/DockDoor/Extensions/CoreGraphics/CGRect.swift similarity index 100% rename from DockDoor/Extensions/CGRect.swift rename to DockDoor/Extensions/CoreGraphics/CGRect.swift diff --git a/DockDoor/Extensions/CoreGraphics/CGSize.swift b/DockDoor/Extensions/CoreGraphics/CGSize.swift new file mode 100644 index 0000000..ee05995 --- /dev/null +++ b/DockDoor/Extensions/CoreGraphics/CGSize.swift @@ -0,0 +1,20 @@ +import Cocoa + +extension CGSize { + static func + (lhs: CGSize, rhs: CGSize) -> CGSize { + CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) + } +} + +extension CGSize { + func scaleToFit(within maxSize: CGSize) -> CGSize { + let widthRatio = maxSize.width / width + let heightRatio = maxSize.height / height + let scale = min(widthRatio, heightRatio) + + return CGSize( + width: width * scale, + height: height * scale + ) + } +} diff --git a/DockDoor/Utilities/Misc Utils.swift b/DockDoor/Utilities/Misc Utils.swift index 9da390a..15b0c1d 100644 --- a/DockDoor/Utilities/Misc Utils.swift +++ b/DockDoor/Utilities/Misc Utils.swift @@ -27,26 +27,6 @@ func getWindowSize() -> CGSize { CGSize(width: optimisticScreenSizeWidth / Defaults[.sizingMultiplier], height: optimisticScreenSizeHeight / Defaults[.sizingMultiplier]) } -// Helper extension to calculate distance between CGPoints -extension CGPoint { - func distance(to point: CGPoint) -> CGFloat { - let dx = x - point.x - let dy = y - point.y - return sqrt(dx * dx + dy * dy) - } - - func displace(by point: CGPoint = .init(x: 0.0, y: 0.0)) -> CGPoint { - CGPoint(x: x + point.x, - y: y + point.y) - } - - /// Caps the point to the unit space - func capped() -> CGPoint { - CGPoint(x: max(min(x, 1), 0), - y: max(min(y, 1), 0)) - } -} - // Measure string length in px func measureString(_ string: String, fontSize: CGFloat, fontWeight: NSFont.Weight = .regular) -> CGSize { let font = NSFont.systemFont(ofSize: fontSize, weight: fontWeight) diff --git a/DockDoor/Views/Hover Window/FullSizePreviewView.swift b/DockDoor/Views/Hover Window/FullSizePreviewView.swift index 859f37a..505141f 100644 --- a/DockDoor/Views/Hover Window/FullSizePreviewView.swift +++ b/DockDoor/Views/Hover Window/FullSizePreviewView.swift @@ -3,30 +3,18 @@ import SwiftUI struct FullSizePreviewView: View { let windowInfo: WindowInfo - let maxSize: CGSize - + let windowSize: CGSize @Default(.uniformCardRadius) var uniformCardRadius var body: some View { - VStack(alignment: .center) { - Group { - HStack(alignment: .center) { - if let image = windowInfo.image { - Image(decorative: image, scale: 1.0) - .resizable() - .aspectRatio(contentMode: .fit) - .modifier(FluidGradientBorder(cornerRadius: uniformCardRadius ? 12 : 6, lineWidth: 2)) - } - } + Group { + if let image = windowInfo.image { + Image(decorative: image, scale: 1.0) + .resizable() + .aspectRatio(windowSize, contentMode: .fit) + .modifier(FluidGradientBorder(cornerRadius: uniformCardRadius ? 12 : 0, lineWidth: 2)) } } - .frame(idealHeight: maxSize.height) - .background { - RoundedRectangle(cornerRadius: 6, style: .continuous) - .fill(Color.clear.shadow(.drop(color: .black.opacity(0.25), radius: 8, y: 4))) - } .clipShape(uniformCardRadius ? AnyShape(RoundedRectangle(cornerRadius: 12, style: .continuous)) : AnyShape(Rectangle())) - .padding(.all, 24) - .dockStyle(cornerRadius: 16) } } diff --git a/DockDoor/Views/Hover Window/SharedPreviewWindowCoordinator.swift b/DockDoor/Views/Hover Window/SharedPreviewWindowCoordinator.swift index d1420a1..fccf64e 100644 --- a/DockDoor/Views/Hover Window/SharedPreviewWindowCoordinator.swift +++ b/DockDoor/Views/Hover Window/SharedPreviewWindowCoordinator.swift @@ -133,18 +133,19 @@ final class SharedPreviewWindowCoordinator: NSWindow { fullPreviewWindow?.hasShadow = true } - let padding: CGFloat = 40 - let maxSize = CGSize( - width: screen.visibleFrame.width - padding * 2, - height: screen.visibleFrame.height - padding * 2 - ) + let windowSize = (try? windowInfo.axElement.size()) ?? CGSize(width: screen.frame.width, height: screen.frame.height) + let axPosition = (try? windowInfo.axElement.position()) ?? CGPoint(x: screen.frame.midX, y: screen.frame.midY) + + let convertedPosition = DockObserver.cgPointFromNSPoint(axPosition, forScreen: screen) + let adjustedPosition = CGPoint(x: convertedPosition.x, y: convertedPosition.y - windowSize.height) + + let flippedIconRect = CGRect(origin: adjustedPosition, size: windowSize) - let previewView = FullSizePreviewView(windowInfo: windowInfo, maxSize: maxSize) + let previewView = FullSizePreviewView(windowInfo: windowInfo, windowSize: windowSize) let hostingView = NSHostingView(rootView: previewView) fullPreviewWindow?.contentView = hostingView - let centerPoint = centerWindowOnScreen(size: maxSize, screen: screen) - fullPreviewWindow?.setFrame(CGRect(origin: centerPoint, size: maxSize), display: true) + fullPreviewWindow?.setFrame(flippedIconRect, display: true) fullPreviewWindow?.makeKeyAndOrderFront(nil) } @@ -293,8 +294,12 @@ final class SharedPreviewWindowCoordinator: NSWindow { hideFullPreviewWindow() // clean up any lingering fullscreen previews before presenting a new one // If in full window preview mode, show the full preview window and return early - if centeredHoverWindowState == .fullWindowPreview, let windowInfo = windows.first { - showFullPreviewWindow(for: windowInfo, on: screen) + if centeredHoverWindowState == .fullWindowPreview, + let windowInfo = windows.first, + let windowPosition = try? windowInfo.axElement.position(), + let windowScreen = windowPosition.screen() + { + showFullPreviewWindow(for: windowInfo, on: windowScreen) } else { self.appName = appName self.windows = windows diff --git a/DockDoor/Views/Hover Window/WindowPreview.swift b/DockDoor/Views/Hover Window/WindowPreview.swift index 8eb1acf..e650cd8 100644 --- a/DockDoor/Views/Hover Window/WindowPreview.swift +++ b/DockDoor/Views/Hover Window/WindowPreview.swift @@ -271,8 +271,7 @@ struct WindowPreview: View { } case .previewFullSize: - // If the interval is 0, show the full window preview immediately - if tapEquivalentInterval == 0 { + let showFullPreview = { DispatchQueue.main.async { SharedPreviewWindowCoordinator.shared.showWindow( appName: windowInfo.app.localizedName ?? "Unknown", @@ -283,19 +282,15 @@ struct WindowPreview: View { centeredHoverWindowState: .fullWindowPreview ) } + } + + if tapEquivalentInterval == 0 { + showFullPreview() } else { - // If the interval is greater than 0, set a timer to show the full window preview after the specified interval - fullPreviewTimer = Timer.scheduledTimer(withTimeInterval: tapEquivalentInterval, repeats: false) { [self] _ in - DispatchQueue.main.async { - SharedPreviewWindowCoordinator.shared.showWindow( - appName: windowInfo.app.localizedName ?? "Unknown", - windows: [windowInfo], - mouseScreen: bestGuessMonitor, - iconRect: nil, - overrideDelay: true, - centeredHoverWindowState: .fullWindowPreview - ) - } + fullPreviewTimer = Timer.scheduledTimer(withTimeInterval: tapEquivalentInterval, + repeats: false) + { _ in + showFullPreview() } } } diff --git a/DockDoor/consts.swift b/DockDoor/consts.swift index 39946e2..32d9b31 100644 --- a/DockDoor/consts.swift +++ b/DockDoor/consts.swift @@ -186,7 +186,7 @@ enum PreviewHoverAction: String, CaseIterable, Defaults.Serializable { case .tap: String(localized: "Simulate a click (open the window)", comment: "Window popup hover action option") case .previewFullSize: - String(localized: "See a large preview of the window", comment: "Window popup hover action option") + String(localized: "Present a full size preview of the window", comment: "Window popup hover action option") } } }