Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
83bfa72
Add cooperative window planner to corner actions
Jun 15, 2026
80c94f7
Cooperative cycles on side actions.
Jun 16, 2026
9521bc7
Allow the cooperative window manager to resize both corner and side a…
Jun 16, 2026
c234731
Fix issue where a corner window doesn't resize when hidden by side wi…
Jun 16, 2026
58b489b
Fix issue where <50% side split ratios would invoke a cyclic action a…
Jun 16, 2026
42852e2
Update Main.xcstrings to upstream/main and add branch changes
Jun 17, 2026
9b0c86b
refactor cooperative resize behavior so it is constraint-aware and ap…
Jun 19, 2026
b1dfadc
Logic refactor WIP gate. Respects min size on refresh. wider toleranc…
Jun 19, 2026
51edcf0
Wider tolerances for nearby matches for windows not exactly grid size.
Jun 19, 2026
dcf86be
Fix outer perimeter to prevent window creep into gap area
Jun 20, 2026
0d2372b
If enabled, use cooperative resizer in dragggable snap areas.
Jun 20, 2026
ac4b8de
Add a window target resolver to avoid pushing to default size and cap…
Jun 20, 2026
8e3efec
Refactor Cooperative Resizer functionality in Window Manager into an …
Jul 1, 2026
d167fa1
Refine cooperative corner resize preflight and cleanup
Jul 1, 2026
299a73f
Dynamic Side Split Ratio
Jul 1, 2026
315f0f0
Revert "Dynamic Side Split Ratio"
Jul 1, 2026
5a79c54
Fix cleanup resize for focused adjacent corner
Jul 2, 2026
f98ab23
Reapply "Dynamic Side Split Ratio"
Jul 7, 2026
0d458d7
* WindowCalculationResult.initialRect can now be updated after calcul…
Jul 8, 2026
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
16 changes: 16 additions & 0 deletions Rectangle.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
729E0A982AFF76B1006E2F48 /* CenterProminentlyCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 729E0A972AFF76B1006E2F48 /* CenterProminentlyCalculation.swift */; };
74804F0B2E25521C009F1F7D /* CenterTwoThirdsCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74804F0A2E25521C009F1F7D /* CenterTwoThirdsCalculation.swift */; };
7BE578EF2C5BF4EE0083DAE3 /* CycleSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */; };
7BE578F12EFA00010083DAE3 /* CooperativeCornerResize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE578F02EFA00010083DAE3 /* CooperativeCornerResize.swift */; };
7BE578F32EFE11010083DAE3 /* ActiveSideSplitRatios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BE578F22EFE11010083DAE3 /* ActiveSideSplitRatios.swift */; };
7EC9E3BAD5BC57E49A7BCEE5 /* TwelfthsRepeated.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3592F8E85CE82B8FA662A31 /* TwelfthsRepeated.swift */; };
866661F2257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 866661F1257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift */; };
88763FEFFA3AECF8502604D6 /* LowerMiddleCenterLeftSixteenthCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378C96D832D4CC6BCCF9D604 /* LowerMiddleCenterLeftSixteenthCalculation.swift */; };
Expand Down Expand Up @@ -77,11 +79,13 @@
9824702F22AFA2E50037B409 /* AccessibilityAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824702E22AFA2E50037B409 /* AccessibilityAuthorization.swift */; };
9824703122AFA8470037B409 /* RectangleStatusItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703022AFA8470037B409 /* RectangleStatusItem.swift */; };
9824703722B0F3200037B409 /* WindowAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703622B0F3200037B409 /* WindowAction.swift */; };
982470382EFA00010037B409 /* WindowActionCooperativeResize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982470372EFA00010037B409 /* WindowActionCooperativeResize.swift */; };
9824703922B0F37C0037B409 /* ShortcutManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703822B0F37C0037B409 /* ShortcutManager.swift */; };
9824703B22B139780037B409 /* CUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703A22B139780037B409 /* CUtil.swift */; };
9824703D22B13C7E0037B409 /* AccessibilityElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703C22B13C7E0037B409 /* AccessibilityElement.swift */; };
9824703F22B13FBC0037B409 /* ScreenDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824703E22B13FBC0037B409 /* ScreenDetection.swift */; };
9824704122B186D00037B409 /* WindowManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824704022B186D00037B409 /* WindowManager.swift */; };
982470432EFA00010037B409 /* WindowManager+CooperativeCornerResize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982470422EFA00010037B409 /* WindowManager+CooperativeCornerResize.swift */; };
9824704B22B189250037B409 /* StandardWindowMover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824704622B189240037B409 /* StandardWindowMover.swift */; };
9824704C22B189250037B409 /* WindowMover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824704722B189240037B409 /* WindowMover.swift */; };
9824704D22B189250037B409 /* WindowCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9824704822B189250037B409 /* WindowCalculation.swift */; };
Expand Down Expand Up @@ -241,6 +245,8 @@
729E0A972AFF76B1006E2F48 /* CenterProminentlyCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenterProminentlyCalculation.swift; sourceTree = "<group>"; };
74804F0A2E25521C009F1F7D /* CenterTwoThirdsCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenterTwoThirdsCalculation.swift; sourceTree = "<group>"; };
7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CycleSize.swift; sourceTree = "<group>"; };
7BE578F02EFA00010083DAE3 /* CooperativeCornerResize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CooperativeCornerResize.swift; sourceTree = "<group>"; };
7BE578F22EFE11010083DAE3 /* ActiveSideSplitRatios.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSideSplitRatios.swift; sourceTree = "<group>"; };
7E8357E928530D10A806F3CD /* TopRightTwelfthCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopRightTwelfthCalculation.swift; sourceTree = "<group>"; };
84E2DBE0733F31BC159BC99C /* UpperMiddleLeftSixteenthCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpperMiddleLeftSixteenthCalculation.swift; sourceTree = "<group>"; };
866661F1257D248A00A9CD2D /* RepeatedExecutionsInThirdsCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatedExecutionsInThirdsCalculation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -298,11 +304,13 @@
9824702E22AFA2E50037B409 /* AccessibilityAuthorization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityAuthorization.swift; sourceTree = "<group>"; };
9824703022AFA8470037B409 /* RectangleStatusItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RectangleStatusItem.swift; sourceTree = "<group>"; };
9824703622B0F3200037B409 /* WindowAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowAction.swift; sourceTree = "<group>"; };
982470372EFA00010037B409 /* WindowActionCooperativeResize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowActionCooperativeResize.swift; sourceTree = "<group>"; };
9824703822B0F37C0037B409 /* ShortcutManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutManager.swift; sourceTree = "<group>"; };
9824703A22B139780037B409 /* CUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CUtil.swift; sourceTree = "<group>"; };
9824703C22B13C7E0037B409 /* AccessibilityElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityElement.swift; sourceTree = "<group>"; };
9824703E22B13FBC0037B409 /* ScreenDetection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenDetection.swift; sourceTree = "<group>"; };
9824704022B186D00037B409 /* WindowManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowManager.swift; sourceTree = "<group>"; };
982470422EFA00010037B409 /* WindowManager+CooperativeCornerResize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WindowManager+CooperativeCornerResize.swift"; sourceTree = "<group>"; };
9824704622B189240037B409 /* StandardWindowMover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StandardWindowMover.swift; sourceTree = "<group>"; };
9824704722B189240037B409 /* WindowMover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowMover.swift; sourceTree = "<group>"; };
9824704822B189250037B409 /* WindowCalculation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowCalculation.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -681,15 +689,19 @@
984EDB0E29A42ED200D119D2 /* LaunchOnLogin.swift */,
98C1008B2305F1FA006E5344 /* SubsequentExecutionMode.swift */,
7BE578EE2C5BF4ED0083DAE3 /* CycleSize.swift */,
7BE578F22EFE11010083DAE3 /* ActiveSideSplitRatios.swift */,
7BE578F02EFA00010083DAE3 /* CooperativeCornerResize.swift */,
985B9BF422B93EEC00A2E8F0 /* ApplicationToggle.swift */,
9824703022AFA8470037B409 /* RectangleStatusItem.swift */,
9824703622B0F3200037B409 /* WindowAction.swift */,
982470372EFA00010037B409 /* WindowActionCooperativeResize.swift */,
98A6EDEB2528FFC100F74B10 /* WindowActionCategory.swift */,
9824703822B0F37C0037B409 /* ShortcutManager.swift */,
9824703C22B13C7E0037B409 /* AccessibilityElement.swift */,
9824703E22B13FBC0037B409 /* ScreenDetection.swift */,
98C275662322E2DA009B9292 /* WindowHistory.swift */,
9824704022B186D00037B409 /* WindowManager.swift */,
982470422EFA00010037B409 /* WindowManager+CooperativeCornerResize.swift */,
AA49DD1029B8C1B100690E13 /* TitleBarManager.swift */,
AA49DD2029B8C1B100690E13 /* GreenButtonManager.swift */,
98987AA62391890C00BE72C4 /* Logging */,
Expand Down Expand Up @@ -1047,14 +1059,18 @@
D04CE31027817ABE00BD47B3 /* BottomRightNinthCalculation.swift in Sources */,
98A009B92512538D00CFBF0C /* TopRightSixthCalculation.swift in Sources */,
98C275672322E2DA009B9292 /* WindowHistory.swift in Sources */,
7BE578F32EFE11010083DAE3 /* ActiveSideSplitRatios.swift in Sources */,
9821403722B3D16700ABFB3F /* MaximizeHeightCalculation.swift in Sources */,
9824704122B186D00037B409 /* WindowManager.swift in Sources */,
982470432EFA00010037B409 /* WindowManager+CooperativeCornerResize.swift in Sources */,
D0423D8327A8D31D008A4894 /* HorizontalThirdsRepeated.swift in Sources */,
98C97FFD25893B040061F01F /* Config.swift in Sources */,
989DA30E243FC0DF008C7AA4 /* WelcomeViewController.swift in Sources */,
9818E01228B59B64004AA524 /* ThirdsCompoundCalculation.swift in Sources */,
7BE578F12EFA00010083DAE3 /* CooperativeCornerResize.swift in Sources */,
98C27561231FFA5F009B9292 /* SnappingManager.swift in Sources */,
9824703722B0F3200037B409 /* WindowAction.swift in Sources */,
982470382EFA00010037B409 /* WindowActionCooperativeResize.swift in Sources */,
B4521F932BD7CEFB00FD43CC /* ChangeWindowDimensionCalculation.swift in Sources */,
9821402922B3889100ABFB3F /* LowerLeftCalculation.swift in Sources */,
7BE578EF2C5BF4EE0083DAE3 /* CycleSize.swift in Sources */,
Expand Down
5 changes: 5 additions & 0 deletions Rectangle/AccessibilityElement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ class AccessibilityElement {
Logger.log("AX sizing proposed: \(newValue.debugDescription), result: \(size?.debugDescription ?? "N/A")")
}
}

var minimumSize: CGSize? {
wrappedElement.getWrappedValue(.minSize)
?? wrappedElement.getWrappedValue(.minimumSize)
}

var frame: CGRect {
guard let position = position, let size = size else { return .null }
Expand Down
152 changes: 152 additions & 0 deletions Rectangle/ActiveSideSplitRatios.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/// ActiveSideSplitRatios.swift

import Foundation

final class ActiveSideSplitRatios {

static let shared = ActiveSideSplitRatios()

private struct ScreenKey: Hashable {
let minX: Int
let minY: Int
let width: Int
let height: Int

init(_ frame: CGRect) {
minX = Self.keyPart(frame.minX)
minY = Self.keyPart(frame.minY)
width = Self.keyPart(frame.width)
height = Self.keyPart(frame.height)
}

private static func keyPart(_ value: CGFloat) -> Int {
Int(round(value * 1000.0))
}
}

private struct SplitRatios {
var horizontal: Float?
var vertical: Float?
}

private var ratiosByScreen = [ScreenKey: SplitRatios]()
private var configuredHorizontalPercent: Float
private var configuredVerticalPercent: Float

private init() {
configuredHorizontalPercent = Defaults.horizontalSplitRatio.value
configuredVerticalPercent = Defaults.verticalSplitRatio.value
}

func horizontalRatio(for screenFrame: CGRect) -> Float {
resetChangedConfiguredDefaults()
return ratiosByScreen[ScreenKey(screenFrame)]?.horizontal ?? configuredHorizontalRatio
}

func verticalRatio(for screenFrame: CGRect) -> Float {
resetChangedConfiguredDefaults()
return ratiosByScreen[ScreenKey(screenFrame)]?.vertical ?? configuredVerticalRatio
}

func recordSideAction(_ action: WindowAction, targetFrame: CGRect, screenFrame: CGRect) {
resetChangedConfiguredDefaults()

guard !targetFrame.isNull,
!screenFrame.isNull,
screenFrame.width > 0,
screenFrame.height > 0
else {
return
}

switch action {
case .leftHalf:
setHorizontalRatio(Float(targetFrame.width / screenFrame.width), for: screenFrame)
case .rightHalf:
setHorizontalRatio(1.0 - Float(targetFrame.width / screenFrame.width), for: screenFrame)
case .topHalf:
setVerticalRatio(Float(targetFrame.height / screenFrame.height), for: screenFrame)
case .bottomHalf:
setVerticalRatio(1.0 - Float(targetFrame.height / screenFrame.height), for: screenFrame)
default:
return
}
}

func resetAll() {
ratiosByScreen.removeAll()
configuredHorizontalPercent = Defaults.horizontalSplitRatio.value
configuredVerticalPercent = Defaults.verticalSplitRatio.value
}

func reset(for screenFrame: CGRect) {
ratiosByScreen.removeValue(forKey: ScreenKey(screenFrame))
}

private var configuredHorizontalRatio: Float {
normalized(Defaults.horizontalSplitRatio.value / 100.0)
}

private var configuredVerticalRatio: Float {
normalized(Defaults.verticalSplitRatio.value / 100.0)
}

private func setHorizontalRatio(_ ratio: Float, for screenFrame: CGRect) {
updateRatios(for: screenFrame) { splitRatios in
splitRatios.horizontal = normalized(ratio)
}
}

private func setVerticalRatio(_ ratio: Float, for screenFrame: CGRect) {
updateRatios(for: screenFrame) { splitRatios in
splitRatios.vertical = normalized(ratio)
}
}

private func updateRatios(for screenFrame: CGRect, update: (inout SplitRatios) -> Void) {
let key = ScreenKey(screenFrame)
var splitRatios = ratiosByScreen[key] ?? SplitRatios()
update(&splitRatios)
ratiosByScreen[key] = splitRatios
}

private func resetChangedConfiguredDefaults() {
let currentHorizontalPercent = Defaults.horizontalSplitRatio.value
let currentVerticalPercent = Defaults.verticalSplitRatio.value

if abs(currentHorizontalPercent - configuredHorizontalPercent) > CycleSize.matchingTolerance {
resetHorizontalRatios()
configuredHorizontalPercent = currentHorizontalPercent
}

if abs(currentVerticalPercent - configuredVerticalPercent) > CycleSize.matchingTolerance {
resetVerticalRatios()
configuredVerticalPercent = currentVerticalPercent
}
}

private func resetHorizontalRatios() {
Array(ratiosByScreen.keys).forEach { key in
ratiosByScreen[key]?.horizontal = nil
removeEmptyRatios(for: key)
}
}

private func resetVerticalRatios() {
Array(ratiosByScreen.keys).forEach { key in
ratiosByScreen[key]?.vertical = nil
removeEmptyRatios(for: key)
}
}

private func removeEmptyRatios(for key: ScreenKey) {
if ratiosByScreen[key]?.horizontal == nil,
ratiosByScreen[key]?.vertical == nil {
ratiosByScreen.removeValue(forKey: key)
}
}

private func normalized(_ ratio: Float) -> Float {
min(1.0, max(0.0, ratio))
}
}
Loading
Loading