diff --git a/Loop/Extensions/Defaults+Extensions.swift b/Loop/Extensions/Defaults+Extensions.swift index 4ba6edd7..55e30d16 100644 --- a/Loop/Extensions/Defaults+Extensions.swift +++ b/Loop/Extensions/Defaults+Extensions.swift @@ -101,3 +101,11 @@ extension Defaults.Keys { static let excludedApps = Key<[URL]>("excludedApps", default: [], iCloud: true) static let sizeIncrement = Key("sizeIncrement", default: 20, iCloud: true) } + +// MARK: - Extra Advanced + +extension Defaults.Keys { + /// Adjust with `defaults write com.MrKai77.Loop paddingMinimumScreenSize -float x` + /// Reset with `defaults delete com.MrKai77.Loop paddingMinimumScreenSize` + static let paddingMinimumScreenSize = Key("paddingMinimumScreenSize", default: 0, iCloud: true) +} diff --git a/Loop/Extensions/NSScreen+Extensions.swift b/Loop/Extensions/NSScreen+Extensions.swift index 742629cb..86d0ae72 100644 --- a/Loop/Extensions/NSScreen+Extensions.swift +++ b/Loop/Extensions/NSScreen+Extensions.swift @@ -77,3 +77,41 @@ extension NSScreen { frame.maxY - visibleFrame.maxY } } + +// MARK: - Calculate physical screen size + +extension NSScreen { + // Returns diagonal size in inches + var diagonalSize: CGFloat { + let unitsPerInch = unitsPerInch + let screenSizeInInches = CGSize( + width: frame.width / unitsPerInch.width, + height: frame.height / unitsPerInch.height + ) + + // Just the pythagorean theorem + let diagonalSize = sqrt(pow(screenSizeInInches.width, 2) + pow(screenSizeInInches.height, 2)) + + return diagonalSize + } + + private var unitsPerInch: CGSize { + // We need to convert from mm to inch because CGDisplayScreenSize returns units in mm. + let millimetersPerInch: CGFloat = 25.4 + + let screenDescription = deviceDescription + if let displayUnitSize = (screenDescription[NSDeviceDescriptionKey.size] as? NSValue)?.sizeValue, + let screenNumber = (screenDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value { + let displayPhysicalSize = CGDisplayScreenSize(screenNumber) + + return CGSize( + width: millimetersPerInch * displayUnitSize.width / displayPhysicalSize.width, + height: millimetersPerInch * displayUnitSize.height / displayPhysicalSize.height + ) + } else { + // this is the same as what CoreGraphics assumes if no EDID data is available from the display device + // https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize?language=objc + return CGSize(width: 72.0, height: 72.0) + } + } +} diff --git a/Loop/Luminare/Settings/Behavior/BehaviorConfiguration.swift b/Loop/Luminare/Settings/Behavior/BehaviorConfiguration.swift index 7f6ac2a3..063c4c8b 100644 --- a/Loop/Luminare/Settings/Behavior/BehaviorConfiguration.swift +++ b/Loop/Luminare/Settings/Behavior/BehaviorConfiguration.swift @@ -60,9 +60,7 @@ class BehaviorConfigurationModel: ObservableObject { } @Published var focusWindowOnResize = Defaults[.focusWindowOnResize] { - didSet { - Defaults[.focusWindowOnResize] = focusWindowOnResize - } + didSet { Defaults[.focusWindowOnResize] = focusWindowOnResize } } @Published var respectStageManager = Defaults[.respectStageManager] { diff --git a/Loop/Preview Window/PreviewController.swift b/Loop/Preview Window/PreviewController.swift index 49bc1d9d..c048d1ad 100644 --- a/Loop/Preview Window/PreviewController.swift +++ b/Loop/Preview Window/PreviewController.swift @@ -91,7 +91,8 @@ class PreviewController { let targetWindowFrame = action.getFrame( window: window, - bounds: screen.safeScreenFrame + bounds: screen.safeScreenFrame, + screen: screen ) .flipY(maxY: NSScreen.screens[0].frame.maxY) diff --git a/Loop/Window Management/WindowAction.swift b/Loop/Window Management/WindowAction.swift index 570bc7b8..f24cc6f4 100644 --- a/Loop/Window Management/WindowAction.swift +++ b/Loop/Window Management/WindowAction.swift @@ -114,12 +114,13 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial return result.normalized() } - func getFrame(window: Window?, bounds: CGRect, disablePadding: Bool = false) -> CGRect { + func getFrame(window: Window?, bounds: CGRect, disablePadding: Bool = false, screen: NSScreen? = nil) -> CGRect { guard direction != .cycle, direction != .noAction else { return NSRect(origin: bounds.center, size: .zero) } var bounds = bounds - if !disablePadding && Defaults[.enablePadding] { + if !disablePadding && Defaults[.enablePadding], + Defaults[.paddingMinimumScreenSize] == .zero || screen?.diagonalSize ?? .zero > Defaults[.paddingMinimumScreenSize] { bounds = getPaddedBounds(bounds) } var result = CGRect(origin: bounds.origin, size: .zero) @@ -223,7 +224,7 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial if !disablePadding { if direction != .undo, direction != .initialFrame { - result = cropThenApplyPadding(result, bounds) + result = cropThenApplyInnerPadding(result, bounds) } LoopManager.lastTargetFrame = result @@ -398,13 +399,19 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial return bounds } - private func cropThenApplyPadding(_ windowFrame: CGRect, _ bounds: CGRect) -> CGRect { + private func cropThenApplyInnerPadding(_ windowFrame: CGRect, _ bounds: CGRect, _ screen: NSScreen? = nil) -> CGRect { guard !direction.willMove else { return windowFrame } var croppedWindowFrame = windowFrame.intersection(bounds) + let paddingMinimumScreenSize = Defaults[.paddingMinimumScreenSize] + if paddingMinimumScreenSize != .zero, + screen?.diagonalSize ?? .zero < paddingMinimumScreenSize { + return windowFrame + } + guard !willManipulateExistingWindowFrame, Defaults[.enablePadding] diff --git a/Loop/Window Management/WindowEngine.swift b/Loop/Window Management/WindowEngine.swift index f988fbef..817bde91 100644 --- a/Loop/Window Management/WindowEngine.swift +++ b/Loop/Window Management/WindowEngine.swift @@ -51,7 +51,7 @@ enum WindowEngine { return } - let targetFrame = action.getFrame(window: window, bounds: screen.safeScreenFrame) + let targetFrame = action.getFrame(window: window, bounds: screen.safeScreenFrame, screen: screen) if action.direction == .undo { WindowRecords.removeLastAction(for: window) @@ -86,7 +86,8 @@ enum WindowEngine { let screenFrame = action.direction.willMove ? .zero : screen.safeScreenFrame - let bounds = if Defaults[.enablePadding] { + let bounds = if Defaults[.enablePadding], + Defaults[.paddingMinimumScreenSize] == 0 || screen.diagonalSize > Defaults[.paddingMinimumScreenSize] { Defaults[.padding].apply(on: screenFrame) } else { screenFrame