Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Loop/Extensions/CGGeometry+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,15 @@ extension CGRect {

return result
}

/// Returns a new rectangle with integer values for the origin and size.
/// - Returns: A new rectangle with integer values for the origin and size.
func integerRect() -> CGRect {
CGRect(
x: floor(minX),
y: floor(minY),
width: floor(width),
height: floor(height)
)
}
}
43 changes: 29 additions & 14 deletions Loop/Window Management/WindowAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Defaults
import OSLog
import SwiftUI

struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serializable {
Expand Down Expand Up @@ -133,28 +134,32 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial
return result.normalized()
}

/// Returns the frame for the specified window action within a given boundary.
/// - Parameters:
/// - window: the window to be manipulated.
/// - bounds: the boundary within which the window should be manipulated.
/// - disablePadding: whether to disable padding. `true` when calculating non-AX-usage frames, such as for angle calculations in radial menu or in config UI.
/// - screen: the screen on which the bounds are located. Only used to determine if padding should be applied (see `getBounds()`).
/// - isPreview: ensures that when manipulating the preview window, the last target frame does not affect the actual resizing of the window.
/// - Returns: the calculated frame for the specified window action.
func getFrame(window: Window?, bounds: CGRect, disablePadding: Bool = false, screen: NSScreen? = nil, isPreview: Bool = false) -> CGRect {
let noFrameActions: [WindowDirection] = [.noAction, .cycle, .minimize, .hide]
guard !noFrameActions.contains(direction) else {
return NSRect(origin: bounds.center, size: .zero)
}

var bounds = bounds
var result: CGRect = .zero

// Get padded bounds only if padding can be applied
if !disablePadding && Defaults[.enablePadding],
Defaults[.paddingMinimumScreenSize] == .zero || screen?.diagonalSize ?? .zero > Defaults[.paddingMinimumScreenSize] {
bounds = getPaddedBounds(bounds)
}

if !willManipulateExistingWindowFrame {
LoopManager.sidesToAdjust = nil
}

result = calculateTargetFrame(direction, window, bounds, isPreview)
var bounds: CGRect = getBounds(from: bounds, disablePadding: disablePadding, screen: screen)
var result: CGRect = calculateTargetFrame(direction, window, bounds, isPreview)

if !disablePadding {
// Convert rects to integers as that's what the AX API works with to move windows
bounds = bounds.integerRect()
result = result.integerRect()

// If window can't be resized, center it within the already-resized frame.
if let window, window.isResizable == false {
result = window.frame.size
Expand Down Expand Up @@ -183,6 +188,16 @@ struct WindowAction: Codable, Identifiable, Hashable, Equatable, Defaults.Serial
// MARK: - Window Frame Calculations

private extension WindowAction {
func getBounds(from originalBounds: CGRect, disablePadding: Bool, screen: NSScreen?) -> CGRect {
// Get padded bounds only if padding can be applied
if !disablePadding && Defaults[.enablePadding],
Defaults[.paddingMinimumScreenSize] == .zero || screen?.diagonalSize ?? .zero > Defaults[.paddingMinimumScreenSize] {
getPaddedBounds(originalBounds)
} else {
originalBounds
}
}

func calculateTargetFrame(_ direction: WindowDirection, _ window: Window?, _ bounds: CGRect, _ isPreview: Bool) -> CGRect {
var result: CGRect = .zero

Expand Down Expand Up @@ -535,19 +550,19 @@ private extension WindowAction {
return croppedWindowFrame
}

if croppedWindowFrame.minX != bounds.minX {
if abs(croppedWindowFrame.minX - bounds.minX) > 1 {
croppedWindowFrame = croppedWindowFrame.padding(.leading, halfPadding)
}

if croppedWindowFrame.maxX != bounds.maxX {
if abs(croppedWindowFrame.maxX - bounds.maxX) > 1 {
croppedWindowFrame = croppedWindowFrame.padding(.trailing, halfPadding)
}

if croppedWindowFrame.minY != bounds.minY {
if abs(croppedWindowFrame.minY - bounds.minY) > 1 {
croppedWindowFrame = croppedWindowFrame.padding(.top, halfPadding)
}

if croppedWindowFrame.maxY != bounds.maxY {
if abs(croppedWindowFrame.maxY - bounds.maxY) > 1 {
croppedWindowFrame = croppedWindowFrame.padding(.bottom, halfPadding)
}

Expand Down