Skip to content

Commit

Permalink
Full size preview rework (#427)
Browse files Browse the repository at this point in the history
* feat: maintain window sizing for preview action

* chore: enlarge stroke on full size preview

* chore: reword option
  • Loading branch information
ejbills authored Jan 3, 2025
1 parent bb07c1f commit d7586dc
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 73 deletions.
16 changes: 14 additions & 2 deletions DockDoor.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -121,6 +122,7 @@
3A1622CB2C8D5F5B00D318EE /* FirstTimePermissionsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimePermissionsTabView.swift; sourceTree = "<group>"; };
3A1622CD2C8D5F8500D318EE /* FirstTimeCongratsTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeCongratsTabView.swift; sourceTree = "<group>"; };
3A1622CF2C8D691700D318EE /* SquiggleDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquiggleDivider.swift; sourceTree = "<group>"; };
BB0036CF2D24AAD10072C61E /* CGPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGPoint.swift; sourceTree = "<group>"; };
BB04405A2C77A011009F1D33 /* GradientColorPaletteSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientColorPaletteSettings.swift; sourceTree = "<group>"; };
BB04405D2C77A87E009F1D33 /* PreferencesProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesProtocol.swift; sourceTree = "<group>"; };
BB04405F2C77AD27009F1D33 /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -300,6 +301,16 @@
path = Tabs;
sourceTree = "<group>";
};
BB0036D12D24AAE60072C61E /* CoreGraphics */ = {
isa = PBXGroup;
children = (
BB918BB42CF7613200931236 /* CGRect.swift */,
BB1F60E42D238A9700808E05 /* CGSize.swift */,
BB0036CF2D24AAD10072C61E /* CGPoint.swift */,
);
path = CoreGraphics;
sourceTree = "<group>";
};
BB04405C2C77A878009F1D33 /* Defaults */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -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 */,
Expand Down
7 changes: 0 additions & 7 deletions DockDoor/Extensions/CGSize.swift

This file was deleted.

39 changes: 39 additions & 0 deletions DockDoor/Extensions/CoreGraphics/CGPoint.swift
Original file line number Diff line number Diff line change
@@ -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))
}
}
File renamed without changes.
20 changes: 20 additions & 0 deletions DockDoor/Extensions/CoreGraphics/CGSize.swift
Original file line number Diff line number Diff line change
@@ -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
)
}
}
20 changes: 0 additions & 20 deletions DockDoor/Utilities/Misc Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 7 additions & 19 deletions DockDoor/Views/Hover Window/FullSizePreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
25 changes: 15 additions & 10 deletions DockDoor/Views/Hover Window/SharedPreviewWindowCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down
23 changes: 9 additions & 14 deletions DockDoor/Views/Hover Window/WindowPreview.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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()
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion DockDoor/consts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}
}
Expand Down

0 comments on commit d7586dc

Please sign in to comment.