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
2 changes: 1 addition & 1 deletion Loop/Core/LoopManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ extension LoopManager {

let adjustedBounds = PaddingConfiguration
.getConfiguredPadding(for: currentScreen)
.applyToBounds(currentScreen.cgSafeScreenFrame)
.applyToBounds(currentScreen.cgSafeScreenFrame, screen: currentScreen)

let proportionalSize = CGRect(
x: (currentFrame.minX - adjustedBounds.minX) / adjustedBounds.width,
Expand Down
6 changes: 6 additions & 0 deletions Loop/Extensions/Defaults+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ extension Defaults.Keys {
/// Reset with `defaults delete com.MrKai77.Loop paddingMinimumScreenSize`
static let paddingMinimumScreenSize = Key<CGFloat>("paddingMinimumScreenSize", default: 0, iCloud: true)

/// Ignore the notch height when calculating top padding, so the effective
/// distance from the screen top matches non-notch displays.
/// Adjust with `defaults write com.MrKai77.Loop ignoreNotch -bool true`
/// Reset with `defaults delete com.MrKai77.Loop ignoreNotch`
static let ignoreNotch = Key<Bool>("ignoreNotch", default: false, iCloud: true)

/// Snap threshold for window snapping, defined in points.
/// Adjust with `defaults write com.MrKai77.Loop snapThreshold -float x`
/// Reset with `defaults delete com.MrKai77.Loop snapThreshold`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,21 @@ struct PaddingConfiguration: Codable, Defaults.Serializable, Hashable {
}

func applyToBounds(
_ bounds: CGRect
_ bounds: CGRect,
screen: NSScreen? = nil
) -> CGRect {
bounds
let notchOffset: CGFloat = if Defaults[.ignoreNotch], let screen {
screen.menubarHeight
} else {
0
}
let effectiveTopPadding = max(0, totalTopPadding - notchOffset)

return bounds
.padding(.leading, left)
.padding(.trailing, right)
.padding(.bottom, bottom)
.padding(.top, totalTopPadding)
.padding(.top, effectiveTopPadding)
}

/// Applies padding to a frame that was calculated using non-padded bounds.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class ResizeContext {
self.screen = screen
self.bounds = bounds
self.padding = padding
self.paddedBounds = padding.applyToBounds(bounds)
self.paddedBounds = padding.applyToBounds(bounds, screen: screen)
self.action = action
self.parentAction = parentAction
self.initialMousePosition = initialMousePosition
Expand All @@ -65,7 +65,7 @@ final class ResizeContext {
self.screen = screen
bounds = screen?.cgSafeScreenFrame ?? .zero
padding = PaddingConfiguration.getConfiguredPadding(for: screen)
paddedBounds = padding.applyToBounds(bounds)
paddedBounds = padding.applyToBounds(bounds, screen: screen)
needsRecompute = true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ enum WindowFrameResolver {
/// - Returns: a tuple containing the computed frame and the sides to adjust for grow/shrink actions.
static func getFrame(resizeContext: ResizeContext) -> FrameResult {
let action = resizeContext.action
let window = resizeContext.window
let bounds = resizeContext.paddedBounds
let direction = action.direction

Expand All @@ -52,11 +51,8 @@ enum WindowFrameResolver {
}

var result: CGRect = calculateTargetFrame(
for: action,
window: window,
bounds: bounds,
sidesToAdjust: &sidesToAdjust,
resizeContext: resizeContext
context: resizeContext
)

if result.size.width < 0 || result.size.height < 0 || !result.isFinite {
Expand All @@ -72,19 +68,17 @@ enum WindowFrameResolver {
extension WindowFrameResolver {
/// Calculates the target frame for the specified window action based on the direction, window, bounds, and whether it is a preview.
/// - Parameters:
/// - action: the window action to calculate the frame for.
/// - window: the window to be manipulated.
/// - bounds: the bounds within which the window should be manipulated.
/// - sidesToAdjust: inout parameter for tracking which edges to adjust during grow/shrink actions.
/// - resizeContext: the context tracking frame and edge adjustment state.
/// - context: the context tracking frame and edge adjustment state.
/// - Returns: the calculated target frame for the specified window action.
private static func calculateTargetFrame(
for action: WindowAction,
window: Window?,
bounds: CGRect,
sidesToAdjust: inout Edge.Set?,
resizeContext: ResizeContext
context: ResizeContext
) -> CGRect {
let bounds = context.paddedBounds
let action = context.action
let window = context.window

let direction = action.direction
var result: CGRect = .zero

Expand All @@ -97,7 +91,7 @@ extension WindowFrameResolver {
return window.frame
}

let frameToResizeFrom = resizeContext.cachedTargetFrame.raw
let frameToResizeFrom = context.cachedTargetFrame.raw

// Compute which edges to adjust based on edges touching bounds
let edgesTouchingBounds = frameToResizeFrom.getEdgesTouchingBounds(bounds)
Expand All @@ -119,7 +113,7 @@ extension WindowFrameResolver {
}

// This allows for control over each side
let frameToResizeFrom = resizeContext.cachedTargetFrame.raw
let frameToResizeFrom = context.cachedTargetFrame.raw

// Compute which edges to adjust based on direction
switch direction {
Expand All @@ -145,7 +139,7 @@ extension WindowFrameResolver {
)

} else if direction.willMove {
let frameToResizeFrom = resizeContext.getTargetFrame().raw
let frameToResizeFrom = context.getTargetFrame().raw

result = calculatePositionAdjustment(for: action, frameToResizeFrom: frameToResizeFrom)

Expand All @@ -165,10 +159,10 @@ extension WindowFrameResolver {
result = getInitialFrame(window: window)

} else if direction == .maximizeHeight, let window {
result = getMaximizeHeightFrame(window: window, bounds: bounds)
result = getMaximizeHeightFrame(window: window, bounds: bounds, padding: context.padding)

} else if direction == .maximizeWidth, let window {
result = getMaximizeWidthFrame(window: window, bounds: bounds)
result = getMaximizeWidthFrame(window: window, bounds: bounds, padding: context.padding)

} else if direction == .unstash, let window {
result = getInitialFrame(window: window)
Expand Down Expand Up @@ -395,12 +389,17 @@ extension WindowFrameResolver {
/// - Parameters:
/// - window: the window whose current frame is used as a reference.
/// - bounds: the area within which the window should be resized.
/// - padding: the padding that the user has configured to apply to windows.
/// - Returns: a CGRect representing a frame that maximizes the window's height.
private static func getMaximizeHeightFrame(window: Window, bounds: CGRect) -> CGRect {
private static func getMaximizeHeightFrame(
window: Window,
bounds: CGRect,
padding: PaddingConfiguration
) -> CGRect {
CGRect(
x: window.frame.minX,
x: window.frame.minX - padding.window / 2,
y: bounds.minY,
width: window.frame.width,
width: window.frame.width + padding.window,
height: bounds.height
)
}
Expand All @@ -409,13 +408,18 @@ extension WindowFrameResolver {
/// - Parameters:
/// - window: the window whose current frame is used as a reference.
/// - bounds: the area within which the window should be resized.
/// - padding: the padding that the user has configured to apply to windows.
/// - Returns: a CGRect representing a frame that maximizes the window's width.
private static func getMaximizeWidthFrame(window: Window, bounds: CGRect) -> CGRect {
private static func getMaximizeWidthFrame(
window: Window,
bounds: CGRect,
padding: PaddingConfiguration
) -> CGRect {
CGRect(
x: bounds.minX,
y: window.frame.minY,
y: window.frame.minY - padding.window / 2,
width: bounds.width,
height: window.frame.height
height: window.frame.height + padding.window
)
}

Expand All @@ -434,9 +438,9 @@ extension WindowFrameResolver {
.filter { !$0.intersects(currentFrame) } // Ensure it doesn't intersect with the current window
.map { $0.intersection(screenFrame) } // Crop it to the screen frame

// Computes the closest window obstacle in each of the four cardinal directions
// (left, right, top, bottom) relative to the current window, and returns the boundaries
// formed by these obstacles, constrained to the screen frame.
/// Computes the closest window obstacle in each of the four cardinal directions
/// (left, right, top, bottom) relative to the current window, and returns the boundaries
/// formed by these obstacles, constrained to the screen frame.
func computeBoundaries() -> (minX: CGFloat, minY: CGFloat, maxX: CGFloat, maxY: CGFloat) {
var minX = screenFrame.minX
var minY = screenFrame.minY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ enum WindowRecords {
let windowFrame = window.frame
let adjustedBounds = PaddingConfiguration
.getConfiguredPadding(for: screen)
.applyToBounds(screen.cgSafeScreenFrame)
.applyToBounds(screen.cgSafeScreenFrame, screen: screen)

let proportionalSize = CGRect(
x: (windowFrame.minX - adjustedBounds.minX) / adjustedBounds.width,
Expand Down