Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Center fixed size windows within their target frame + Window resize code optimizations #560

Merged
merged 6 commits into from
Sep 17, 2024
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
9 changes: 9 additions & 0 deletions Loop/Extensions/AXUIElement+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ extension AXUIElement {
}
}

func canSetValue(_ attribute: NSAccessibility.Attribute) throws -> Bool {
var isSettable = DarwinBoolean(false)
let error = AXUIElementIsAttributeSettable(self, attribute as CFString, &isSettable)
guard error == .success else {
throw error
}
return isSettable.boolValue
}

func getElementAtPosition(_ position: CGPoint) throws -> AXUIElement? {
var element: AXUIElement?
let error = AXUIElementCopyElementAtPosition(self, Float(position.x), Float(position.y), &element)
Expand Down
23 changes: 22 additions & 1 deletion Loop/Extensions/CGGeometry+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ extension CGSize {
func approximatelyEqual(to size: CGSize, tolerance: CGFloat = 10) -> Bool {
abs(width - size.width) < tolerance && abs(height - size.height) < tolerance
}

func center(inside parentRect: CGRect) -> CGRect {
let parentRectCenter = parentRect.center
let newX = parentRectCenter.x - width / 2
let newY = parentRectCenter.y - height / 2

return CGRect(
x: newX,
y: newY,
width: width,
height: height
)
}
}

extension CGRect {
Expand Down Expand Up @@ -99,9 +112,17 @@ extension CGRect {
abs(height - rect.height) < tolerance
}

func pushBottomRightPointInside(_ rect2: CGRect) -> CGRect {
func pushInside(_ rect2: CGRect) -> CGRect {
var result = self

if result.minX < rect2.minX {
result.origin.x = rect2.minX
}

if result.minY < rect2.minY {
result.origin.y = rect2.minY
}

if result.maxX > rect2.maxX {
result.origin.x = rect2.maxX - result.width
}
Expand Down
3 changes: 0 additions & 3 deletions Loop/Managers/LoopManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class LoopManager: ObservableObject {
// Size Adjustment
static var sidesToAdjust: Edge.Set?
static var lastTargetFrame: CGRect = .zero
static var canAdjustSize: Bool = true

private let keybindMonitor = KeybindMonitor.shared

Expand Down Expand Up @@ -168,7 +167,6 @@ private extension LoopManager {
isLoopActive {
if let screenToResizeOn,
Defaults[.previewVisibility] {
LoopManager.canAdjustSize = false
WindowEngine.resize(
targetWindow!,
to: currentAction,
Expand All @@ -191,7 +189,6 @@ private extension LoopManager {
isLoopActive = false
LoopManager.sidesToAdjust = nil
LoopManager.lastTargetFrame = .zero
LoopManager.canAdjustSize = true
}

func openWindows() {
Expand Down
2 changes: 1 addition & 1 deletion Loop/Managers/WindowDragManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class WindowDragManager {
if let screen = NSScreen.screenWithMouse {
var newWindowFrame = window.frame
newWindowFrame.size = initialFrame.size
newWindowFrame = newWindowFrame.pushBottomRightPointInside(screen.frame)
newWindowFrame = newWindowFrame.pushInside(screen.frame)
window.setFrame(newWindowFrame)
} else {
window.size = initialFrame.size
Expand Down
26 changes: 24 additions & 2 deletions Loop/Window Management/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Window {

var observer: Observer?

/// Initialize a window from an AXUIElement
/// - Parameter element: The AXUIElement to initialize the window with. If it is not a window, an error will be thrown
init(element: AXUIElement) throws {
self.axWindow = element

Expand All @@ -51,6 +53,8 @@ class Window {
}
}

/// Initialize a window from a PID. The frontmost app with the given PID will be used.
/// - Parameter pid: The PID of the app to get the window from
convenience init(pid: pid_t) throws {
let element = AXUIElementCreateApplication(pid)
guard let window: AXUIElement = try element.getValue(.focusedWindow) else {
Expand Down Expand Up @@ -125,6 +129,7 @@ class Window {
}
}

/// Activate the window. This will bring it to the front and focus it if possible
func activate() {
do {
try self.axWindow.setValue(.main, value: true)
Expand Down Expand Up @@ -255,15 +260,32 @@ class Window {
}
}

var isResizable: Bool {
do {
let result: Bool = try self.axWindow.canSetValue(.size)
return result
} catch {
print("Failed to determine if window size can be set: \(error.localizedDescription)")
return true
}
}

var frame: CGRect {
CGRect(origin: self.position, size: self.size)
}

/// Set the frame of this Window.
/// - Parameters:
/// - rect: The new frame for the window
/// - animate: Whether or not to animate the window resizing
/// - sizeFirst: This will set the size first, which is useful when switching screens. Only does something when window animations are off
/// - bounds: This will prevent the window from going outside the bounds. Only does something when window animations are on
/// - completionHandler: Something to run after the window has been resized. This can include things like moving the cursor to the center of the window
func setFrame(
_ rect: CGRect,
animate: Bool = false,
sizeFirst: Bool = false, // Only does something when window animations are off
bounds: CGRect = .zero, // Only does something when window animations are on
sizeFirst: Bool = false,
bounds: CGRect = .zero,
completionHandler: @escaping (() -> ()) = {}
) {
let enhancedUI = self.enhancedUserInterface
Expand Down
Loading
Loading