From f3019723f3993ad26bdea8e62e28fc6182e06bb6 Mon Sep 17 00:00:00 2001 From: devindudeman Date: Sat, 14 Feb 2026 13:32:52 -0800 Subject: [PATCH 1/3] Add drag-to-snap areas for eighths Adds compound snap area support for eighths (4x2 grid), following the same pattern as sixths. Can be enabled via terminal command or selected in the Snap Areas preferences tab. Terminal command: defaults write com.knollsoft.Rectangle eighthsSnapArea -bool true Particularly useful for ultra-wide monitors. --- Rectangle.xcodeproj/project.pbxproj | 4 + Rectangle/Defaults.swift | 2 + .../CompoundSnapArea/CompoundSnapArea.swift | 22 +++++- .../EighthsCompoundCalculation.swift | 76 +++++++++++++++++++ Rectangle/Snapping/SnapAreaModel.swift | 5 ++ Rectangle/WindowAction.swift | 34 +++++++-- Rectangle/WindowActionCategory.swift | 4 +- Rectangle/mul.lproj/Main.xcstrings | 9 +++ TerminalCommands.md | 11 +++ 9 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 Rectangle/Snapping/CompoundSnapArea/EighthsCompoundCalculation.swift diff --git a/Rectangle.xcodeproj/project.pbxproj b/Rectangle.xcodeproj/project.pbxproj index cab097da4..3de5b6aa2 100644 --- a/Rectangle.xcodeproj/project.pbxproj +++ b/Rectangle.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 9818E01228B59B64004AA524 /* ThirdsCompoundCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9818E01128B59B64004AA524 /* ThirdsCompoundCalculation.swift */; }; 9818E01428B5A4FD004AA524 /* SixthsCompoundCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9818E01328B5A4FD004AA524 /* SixthsCompoundCalculation.swift */; }; 9818E01828B63C48004AA524 /* FourthsCompoundCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9818E01728B63C48004AA524 /* FourthsCompoundCalculation.swift */; }; + 9818E01A28B63C49004AA524 /* EighthsCompoundCalculation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9818E01928B63C49004AA524 /* EighthsCompoundCalculation.swift */; }; 98192DDA270F606C00015E66 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98192DD9270F606C00015E66 /* Debounce.swift */; }; 98192DDE2717201100015E66 /* ReverseAllManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98192DDD2717201000015E66 /* ReverseAllManager.swift */; }; 981F27D12340E3E1006CD263 /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = 981F27D02340E3E1006CD263 /* InternetAccessPolicy.plist */; }; @@ -209,6 +210,7 @@ 9818E01128B59B64004AA524 /* ThirdsCompoundCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdsCompoundCalculation.swift; sourceTree = ""; }; 9818E01328B5A4FD004AA524 /* SixthsCompoundCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SixthsCompoundCalculation.swift; sourceTree = ""; }; 9818E01728B63C48004AA524 /* FourthsCompoundCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FourthsCompoundCalculation.swift; sourceTree = ""; }; + 9818E01928B63C49004AA524 /* EighthsCompoundCalculation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EighthsCompoundCalculation.swift; sourceTree = ""; }; 98192DD9270F606C00015E66 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; 98192DDD2717201000015E66 /* ReverseAllManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseAllManager.swift; sourceTree = ""; }; 981F27D02340E3E1006CD263 /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = ""; }; @@ -378,6 +380,7 @@ 9818E00F28B59396004AA524 /* HalvesCompoundCalculation.swift */, 9818E01128B59B64004AA524 /* ThirdsCompoundCalculation.swift */, 9818E01728B63C48004AA524 /* FourthsCompoundCalculation.swift */, + 9818E01928B63C49004AA524 /* EighthsCompoundCalculation.swift */, 9818E01328B5A4FD004AA524 /* SixthsCompoundCalculation.swift */, ); path = CompoundSnapArea; @@ -873,6 +876,7 @@ 9821402322B3886100ABFB3F /* CenterCalculation.swift in Sources */, 98A009AB2512491300CFBF0C /* CenterHalfCalculation.swift in Sources */, 9818E01828B63C48004AA524 /* FourthsCompoundCalculation.swift in Sources */, + 9818E01A28B63C49004AA524 /* EighthsCompoundCalculation.swift in Sources */, D0CFE33327A8CCB1004DA47B /* TopRightThirdCalculation.swift in Sources */, 94E9B08E2C3B8D97004C7F41 /* MacTilingDefaults.swift in Sources */, 9821403522B38A2B00ABFB3F /* UpperRightCalculation.swift in Sources */, diff --git a/Rectangle/Defaults.swift b/Rectangle/Defaults.swift index 4ec4cce26..b1cb04eb1 100644 --- a/Rectangle/Defaults.swift +++ b/Rectangle/Defaults.swift @@ -76,6 +76,7 @@ class Defaults { static let shortEdgeSnapAreaSize = FloatDefault(key: "shortEdgeSnapAreaSize", defaultValue: 145) static let cascadeAllDeltaSize = FloatDefault(key: "cascadeAllDeltaSize", defaultValue: 30) static let sixthsSnapArea = OptionalBoolDefault(key: "sixthsSnapArea") + static let eighthsSnapArea = OptionalBoolDefault(key: "eighthsSnapArea") static let stageSize = FloatDefault(key: "stageSize", defaultValue: 190) static let dragFromStage = OptionalBoolDefault(key: "dragFromStage") static let alwaysAccountForStage = OptionalBoolDefault(key: "alwaysAccountForStage") @@ -160,6 +161,7 @@ class Defaults { shortEdgeSnapAreaSize, cascadeAllDeltaSize, sixthsSnapArea, + eighthsSnapArea, stageSize, dragFromStage, alwaysAccountForStage, diff --git a/Rectangle/Snapping/CompoundSnapArea/CompoundSnapArea.swift b/Rectangle/Snapping/CompoundSnapArea/CompoundSnapArea.swift index ddd02f35a..e5162aa58 100644 --- a/Rectangle/Snapping/CompoundSnapArea/CompoundSnapArea.swift +++ b/Rectangle/Snapping/CompoundSnapArea/CompoundSnapArea.swift @@ -10,9 +10,9 @@ import Foundation enum CompoundSnapArea: Int, Codable { - case leftTopBottomHalf = -2, rightTopBottomHalf = -3, thirds = -4, portraitThirdsSide = -5, halves = -6, topSixths = -7, bottomSixths = -8, fourths = -9, portraitTopBottomHalves = -10 - - static let all = [leftTopBottomHalf, rightTopBottomHalf, thirds, portraitThirdsSide, halves, topSixths, bottomSixths, fourths, portraitTopBottomHalves] + case leftTopBottomHalf = -2, rightTopBottomHalf = -3, thirds = -4, portraitThirdsSide = -5, halves = -6, topSixths = -7, bottomSixths = -8, fourths = -9, portraitTopBottomHalves = -10, topEighths = -11, bottomEighths = -12 + + static let all = [leftTopBottomHalf, rightTopBottomHalf, thirds, portraitThirdsSide, halves, topSixths, bottomSixths, fourths, portraitTopBottomHalves, topEighths, bottomEighths] static let leftCompoundCalculation = LeftTopBottomHalfCalculation() static let rightCompoundCalculation = RightTopBottomHalfCalculation() @@ -23,6 +23,8 @@ enum CompoundSnapArea: Int, Codable { static let bottomSixthsCalculation = BottomSixthsCompoundCalculation() static let fourthsColumnCalculation = FourthsColumnCompoundCalculation() static let portraitTopBottomCalculation = TopBottomHalvesCalculation() + static let topEighthsCalculation = TopEighthsCompoundCalculation() + static let bottomEighthsCalculation = BottomEighthsCompoundCalculation() var displayName: String { switch self { @@ -44,6 +46,10 @@ enum CompoundSnapArea: Int, Codable { return NSLocalizedString("Fourths columns", tableName: "Main", value: "", comment: "") case .portraitTopBottomHalves: return NSLocalizedString("Top/bottom halves", tableName: "Main", value: "", comment: "") + case .topEighths: + return NSLocalizedString("Top eighths from corners; maximize", tableName: "Main", value: "", comment: "") + case .bottomEighths: + return NSLocalizedString("Bottom eighths from corners; thirds", tableName: "Main", value: "", comment: "") } } @@ -67,6 +73,10 @@ enum CompoundSnapArea: Int, Codable { return Self.fourthsColumnCalculation case .portraitTopBottomHalves: return Self.portraitTopBottomCalculation + case .topEighths: + return Self.topEighthsCalculation + case .bottomEighths: + return Self.bottomEighthsCalculation } } @@ -90,6 +100,10 @@ enum CompoundSnapArea: Int, Codable { return [.t, .b] case .portraitTopBottomHalves: return [.l, .r] + case .topEighths: + return [.t] + case .bottomEighths: + return [.b] } } @@ -99,7 +113,7 @@ enum CompoundSnapArea: Int, Codable { return [.portrait, .landscape] case .portraitThirdsSide, .portraitTopBottomHalves: return [.portrait] - case .thirds, .topSixths, .bottomSixths, .fourths: + case .thirds, .topSixths, .bottomSixths, .fourths, .topEighths, .bottomEighths: return [.landscape] } } diff --git a/Rectangle/Snapping/CompoundSnapArea/EighthsCompoundCalculation.swift b/Rectangle/Snapping/CompoundSnapArea/EighthsCompoundCalculation.swift new file mode 100644 index 000000000..f8a069990 --- /dev/null +++ b/Rectangle/Snapping/CompoundSnapArea/EighthsCompoundCalculation.swift @@ -0,0 +1,76 @@ +// +// EighthsCompoundCalculation.swift +// Rectangle +// +// Copyright © 2024 Ryan Hanson. All rights reserved. +// + +import Foundation + +struct TopEighthsCompoundCalculation: CompoundSnapAreaCalculation { + + func snapArea(cursorLocation loc: NSPoint, screen: NSScreen, directional: Directional, priorSnapArea: SnapArea?) -> SnapArea? { + guard let priorAction = priorSnapArea?.action + else { return SnapArea(screen: screen, directional: directional, action: .maximize) } + + let frame = screen.frame + let quarterWidth = floor(frame.width / 4) + if loc.x <= frame.minX + quarterWidth { + if priorAction == .topLeft || priorAction == .topLeftEighth || priorAction == .topCenterLeftEighth { + return SnapArea(screen: screen, directional: directional, action: .topLeftEighth) + } + } + if loc.x >= frame.minX + quarterWidth, loc.x <= frame.midX { + if priorAction == .topLeftEighth || priorAction == .topCenterLeftEighth || priorAction == .topCenterRightEighth { + return SnapArea(screen: screen, directional: directional, action: .topCenterLeftEighth) + } + } + if loc.x >= frame.midX, loc.x <= frame.maxX - quarterWidth { + if priorAction == .topCenterLeftEighth || priorAction == .topCenterRightEighth || priorAction == .topRightEighth { + return SnapArea(screen: screen, directional: directional, action: .topCenterRightEighth) + } + } + if loc.x >= frame.maxX - quarterWidth { + if priorAction == .topRight || priorAction == .topRightEighth || priorAction == .topCenterRightEighth { + return SnapArea(screen: screen, directional: directional, action: .topRightEighth) + } + } + if priorAction == .topLeftEighth || priorAction == .topCenterLeftEighth || priorAction == .topCenterRightEighth || priorAction == .topRightEighth { + return SnapArea(screen: screen, directional: directional, action: .maximize) + } + return SnapArea(screen: screen, directional: directional, action: .maximize) + } +} + +struct BottomEighthsCompoundCalculation: CompoundSnapAreaCalculation { + + func snapArea(cursorLocation loc: NSPoint, screen: NSScreen, directional: Directional, priorSnapArea: SnapArea?) -> SnapArea? { + guard let priorAction = priorSnapArea?.action else { + return CompoundSnapArea.thirdsCompoundCalculation.snapArea(cursorLocation: loc, screen: screen, directional: directional, priorSnapArea: priorSnapArea) + } + + let frame = screen.frame + let quarterWidth = floor(frame.width / 4) + if loc.x <= frame.minX + quarterWidth { + if priorAction == .bottomLeft || priorAction == .bottomLeftEighth || priorAction == .bottomCenterLeftEighth { + return SnapArea(screen: screen, directional: directional, action: .bottomLeftEighth) + } + } + if loc.x >= frame.minX + quarterWidth, loc.x <= frame.midX { + if priorAction == .bottomLeftEighth || priorAction == .bottomCenterLeftEighth || priorAction == .bottomCenterRightEighth { + return SnapArea(screen: screen, directional: directional, action: .bottomCenterLeftEighth) + } + } + if loc.x >= frame.midX, loc.x <= frame.maxX - quarterWidth { + if priorAction == .bottomCenterLeftEighth || priorAction == .bottomCenterRightEighth || priorAction == .bottomRightEighth { + return SnapArea(screen: screen, directional: directional, action: .bottomCenterRightEighth) + } + } + if loc.x >= frame.maxX - quarterWidth { + if priorAction == .bottomRight || priorAction == .bottomRightEighth || priorAction == .bottomCenterRightEighth { + return SnapArea(screen: screen, directional: directional, action: .bottomRightEighth) + } + } + return CompoundSnapArea.thirdsCompoundCalculation.snapArea(cursorLocation: loc, screen: screen, directional: directional, priorSnapArea: priorSnapArea) + } +} diff --git a/Rectangle/Snapping/SnapAreaModel.swift b/Rectangle/Snapping/SnapAreaModel.swift index 249dee0c4..05d892828 100644 --- a/Rectangle/Snapping/SnapAreaModel.swift +++ b/Rectangle/Snapping/SnapAreaModel.swift @@ -80,6 +80,11 @@ class SnapAreaModel { setLandscape(directional: .t, snapAreaConfig: SnapAreaConfig(compound: .topSixths)) setLandscape(directional: .b, snapAreaConfig: SnapAreaConfig(compound: .bottomSixths)) } + + if Defaults.eighthsSnapArea.userEnabled { + setLandscape(directional: .t, snapAreaConfig: SnapAreaConfig(compound: .topEighths)) + setLandscape(directional: .b, snapAreaConfig: SnapAreaConfig(compound: .bottomEighths)) + } let ignoredSnapAreas = SnapAreaOption(rawValue: Defaults.ignoredSnapAreas.value) guard ignoredSnapAreas.rawValue > 0 else { return } diff --git a/Rectangle/WindowAction.swift b/Rectangle/WindowAction.swift index d95dcacb2..604a35a0a 100644 --- a/Rectangle/WindowAction.swift +++ b/Rectangle/WindowAction.swift @@ -154,7 +154,7 @@ enum WindowAction: Int, Codable { // Determines where separators should be used in the menu var firstInGroup: Bool { switch self { - case .leftHalf, .topLeft, .firstThird, .maximize, .nextDisplay, .moveLeft, .firstFourth, .topLeftSixth: + case .leftHalf, .topLeft, .firstThird, .maximize, .nextDisplay, .moveLeft, .firstFourth, .topLeftSixth, .topLeftEighth: return true default: return false @@ -386,9 +386,30 @@ enum WindowAction: Int, Codable { return nil case .topLeftThird, .topRightThird, .bottomLeftThird, .bottomRightThird: return nil - case .topLeftEighth, .topCenterLeftEighth, .topCenterRightEighth, .topRightEighth, - .bottomLeftEighth, .bottomCenterLeftEighth, .bottomCenterRightEighth, .bottomRightEighth: - return nil + case .topLeftEighth: + key = "topLeftEighth.title" + value = "Top Left Eighth" + case .topCenterLeftEighth: + key = "topCenterLeftEighth.title" + value = "Top Center Left Eighth" + case .topCenterRightEighth: + key = "topCenterRightEighth.title" + value = "Top Center Right Eighth" + case .topRightEighth: + key = "topRightEighth.title" + value = "Top Right Eighth" + case .bottomLeftEighth: + key = "bottomLeftEighth.title" + value = "Bottom Left Eighth" + case .bottomCenterLeftEighth: + key = "bottomCenterLeftEighth.title" + value = "Bottom Center Left Eighth" + case .bottomCenterRightEighth: + key = "bottomCenterRightEighth.title" + value = "Bottom Center Right Eighth" + case .bottomRightEighth: + key = "bottomRightEighth.title" + value = "Bottom Right Eighth" case .doubleHeightUp, .doubleHeightDown, .doubleWidthLeft, .doubleWidthRight, .halveHeightUp, .halveHeightDown, .halveWidthLeft, .halveWidthRight: return nil case .specified, .reverseAll, .tileAll, .cascadeAll, .leftTodo, .rightTodo, .cascadeActiveApp, .tileActiveApp: @@ -429,9 +450,7 @@ enum WindowAction: Int, Codable { // Ninths .topLeftNinth, .topCenterNinth, .topRightNinth, .middleLeftNinth, .middleCenterNinth, .middleRightNinth, .bottomLeftNinth, .bottomCenterNinth, .bottomRightNinth, // Corner thirds - .topLeftThird, .topRightThird, .bottomLeftThird, .bottomRightThird, - // Eighths - .topLeftEighth, .topCenterLeftEighth, .topCenterRightEighth, .topRightEighth, .bottomLeftEighth, .bottomCenterLeftEighth, .bottomCenterRightEighth, .bottomRightEighth: + .topLeftThird, .topRightThird, .bottomLeftThird, .bottomRightThird: return false default: return true @@ -635,6 +654,7 @@ enum WindowAction: Int, Codable { switch self { case .firstFourth, .secondFourth, .thirdFourth, .lastFourth, .firstThreeFourths, .centerThreeFourths, .lastThreeFourths: return .fourths case .topLeftSixth, .topCenterSixth, .topRightSixth, .bottomLeftSixth, .bottomCenterSixth, .bottomRightSixth: return .sixths + case .topLeftEighth, .topCenterLeftEighth, .topCenterRightEighth, .topRightEighth, .bottomLeftEighth, .bottomCenterLeftEighth, .bottomCenterRightEighth, .bottomRightEighth: return .eighths case .moveUp, .moveDown, .moveLeft, .moveRight: return .move default: return nil } diff --git a/Rectangle/WindowActionCategory.swift b/Rectangle/WindowActionCategory.swift index f548c760a..d24051f27 100644 --- a/Rectangle/WindowActionCategory.swift +++ b/Rectangle/WindowActionCategory.swift @@ -10,7 +10,7 @@ import Foundation enum WindowActionCategory { - case halves, corners, thirds, max, size, display, move, other, sixths, fourths + case halves, corners, thirds, max, size, display, move, other, sixths, fourths, eighths var displayName: String { switch self { @@ -34,6 +34,8 @@ enum WindowActionCategory { return NSLocalizedString("Fourths", tableName: "Main", value: "", comment: "") case .sixths: return NSLocalizedString("Sixths", tableName: "Main", value: "", comment: "") + case .eighths: + return NSLocalizedString("Eighths", tableName: "Main", value: "", comment: "") } } } diff --git a/Rectangle/mul.lproj/Main.xcstrings b/Rectangle/mul.lproj/Main.xcstrings index 1b614c416..cc9abb8c3 100644 --- a/Rectangle/mul.lproj/Main.xcstrings +++ b/Rectangle/mul.lproj/Main.xcstrings @@ -10362,6 +10362,9 @@ } } } + }, + "Bottom eighths from corners; thirds" : { + }, "Bottom sixths from corners; thirds" : { "localizations" : { @@ -16055,6 +16058,9 @@ } } } + }, + "Eighths" : { + }, "Enable Todo Mode" : { "localizations" : { @@ -41104,6 +41110,9 @@ } } } + }, + "Top eighths from corners; maximize" : { + }, "Top screen edge tiling in macOS is now disabled" : { "extractionState" : "manual", diff --git a/TerminalCommands.md b/TerminalCommands.md index 4098eb80c..05fdd8693 100644 --- a/TerminalCommands.md +++ b/TerminalCommands.md @@ -31,6 +31,7 @@ The preferences window is purposefully slim, but there's a lot that can be modif - [Ignore specific drag to snap areas](#ignore-specific-drag-to-snap-areas) - [Disabling gaps when maximizing](#disabling-gaps-when-maximizing) - [Enabling snap areas for sixths](#enabling-snap-areas-for-sixths) +- [Enabling snap areas for eighths](#enabling-snap-areas-for-eighths) - [Move cursor with window](#move-cursor-with-window) - [Prevent a window that is quickly dragged above the menu bar from going into Mission Control](#prevent-a-window-that-is-quickly-dragged-above-the-menu-bar-from-going-into-mission-control) - [Change the behavior of double-click window title bar](#change-the-behavior-of-double-click-window-title-bar) @@ -458,6 +459,16 @@ defaults write com.knollsoft.Rectangle sixthsSnapArea -bool true Once enabled, you can drag a window to the corner, then move it along the edge towards the thirds area to snap to a sixth. +## Enabling snap areas for eighths + +To enable snap areas for eighths, execute: + +```bash +defaults write com.knollsoft.Rectangle eighthsSnapArea -bool true +``` + +Once enabled, you can drag a window to the top or bottom edge, then move it along the edge to snap to an eighth (4x2 grid). This is particularly useful for ultra-wide monitors. + ## Move cursor with window There's an option in the UI for moving the cursor with the window when going across displays, but here's an option for moving it with any shortcut: From f0bfaad16524cbc13553d0cf7109b6ff18231369 Mon Sep 17 00:00:00 2001 From: devindudeman Date: Sat, 14 Feb 2026 13:41:58 -0800 Subject: [PATCH 2/3] Remove unused terminal command and migration for eighths The snap area migration only runs for versions < 64, so the terminal command would not take effect on modern installs. Eighths snap areas are configurable directly in the Snap Areas preferences tab. --- Rectangle/Defaults.swift | 2 -- Rectangle/Snapping/SnapAreaModel.swift | 5 ----- TerminalCommands.md | 11 ----------- 3 files changed, 18 deletions(-) diff --git a/Rectangle/Defaults.swift b/Rectangle/Defaults.swift index b1cb04eb1..4ec4cce26 100644 --- a/Rectangle/Defaults.swift +++ b/Rectangle/Defaults.swift @@ -76,7 +76,6 @@ class Defaults { static let shortEdgeSnapAreaSize = FloatDefault(key: "shortEdgeSnapAreaSize", defaultValue: 145) static let cascadeAllDeltaSize = FloatDefault(key: "cascadeAllDeltaSize", defaultValue: 30) static let sixthsSnapArea = OptionalBoolDefault(key: "sixthsSnapArea") - static let eighthsSnapArea = OptionalBoolDefault(key: "eighthsSnapArea") static let stageSize = FloatDefault(key: "stageSize", defaultValue: 190) static let dragFromStage = OptionalBoolDefault(key: "dragFromStage") static let alwaysAccountForStage = OptionalBoolDefault(key: "alwaysAccountForStage") @@ -161,7 +160,6 @@ class Defaults { shortEdgeSnapAreaSize, cascadeAllDeltaSize, sixthsSnapArea, - eighthsSnapArea, stageSize, dragFromStage, alwaysAccountForStage, diff --git a/Rectangle/Snapping/SnapAreaModel.swift b/Rectangle/Snapping/SnapAreaModel.swift index 05d892828..1e8e636e1 100644 --- a/Rectangle/Snapping/SnapAreaModel.swift +++ b/Rectangle/Snapping/SnapAreaModel.swift @@ -81,11 +81,6 @@ class SnapAreaModel { setLandscape(directional: .b, snapAreaConfig: SnapAreaConfig(compound: .bottomSixths)) } - if Defaults.eighthsSnapArea.userEnabled { - setLandscape(directional: .t, snapAreaConfig: SnapAreaConfig(compound: .topEighths)) - setLandscape(directional: .b, snapAreaConfig: SnapAreaConfig(compound: .bottomEighths)) - } - let ignoredSnapAreas = SnapAreaOption(rawValue: Defaults.ignoredSnapAreas.value) guard ignoredSnapAreas.rawValue > 0 else { return } diff --git a/TerminalCommands.md b/TerminalCommands.md index 05fdd8693..4098eb80c 100644 --- a/TerminalCommands.md +++ b/TerminalCommands.md @@ -31,7 +31,6 @@ The preferences window is purposefully slim, but there's a lot that can be modif - [Ignore specific drag to snap areas](#ignore-specific-drag-to-snap-areas) - [Disabling gaps when maximizing](#disabling-gaps-when-maximizing) - [Enabling snap areas for sixths](#enabling-snap-areas-for-sixths) -- [Enabling snap areas for eighths](#enabling-snap-areas-for-eighths) - [Move cursor with window](#move-cursor-with-window) - [Prevent a window that is quickly dragged above the menu bar from going into Mission Control](#prevent-a-window-that-is-quickly-dragged-above-the-menu-bar-from-going-into-mission-control) - [Change the behavior of double-click window title bar](#change-the-behavior-of-double-click-window-title-bar) @@ -459,16 +458,6 @@ defaults write com.knollsoft.Rectangle sixthsSnapArea -bool true Once enabled, you can drag a window to the corner, then move it along the edge towards the thirds area to snap to a sixth. -## Enabling snap areas for eighths - -To enable snap areas for eighths, execute: - -```bash -defaults write com.knollsoft.Rectangle eighthsSnapArea -bool true -``` - -Once enabled, you can drag a window to the top or bottom edge, then move it along the edge to snap to an eighth (4x2 grid). This is particularly useful for ultra-wide monitors. - ## Move cursor with window There's an option in the UI for moving the cursor with the window when going across displays, but here's an option for moving it with any shortcut: From 1205052fdebbce7d0e3538da0237cb61c7b39e2b Mon Sep 17 00:00:00 2001 From: devindudeman Date: Thu, 19 Feb 2026 10:55:20 -0800 Subject: [PATCH 3/3] Add eighths image assets and shortcut recorders Add icon assets for all 8 eighth window positions and wire them up in WindowAction.image. Add shortcut recorders for eighths to the Extra Shortcuts popover in the General tab. --- .../blEighthTemplate.imageset/Contents.json | 21 ++ .../blEighthTemplate.png | Bin 0 -> 1343 bytes .../brEighthTemplate.imageset/Contents.json | 21 ++ .../brEighthTemplate.png | Bin 0 -> 1340 bytes .../cblEighthTemplate.imageset/Contents.json | 21 ++ .../cblEighthTemplate.png | Bin 0 -> 1347 bytes .../cbrEighthTemplate.imageset/Contents.json | 21 ++ .../cbrEighthTemplate.png | Bin 0 -> 1343 bytes .../ctlEighthTemplate.imageset/Contents.json | 21 ++ .../ctlEighthTemplate.png | Bin 0 -> 1352 bytes .../ctrEighthTemplate.imageset/Contents.json | 21 ++ .../ctrEighthTemplate.png | Bin 0 -> 1347 bytes .../tlEighthTemplate.imageset/Contents.json | 21 ++ .../tlEighthTemplate.png | Bin 0 -> 1342 bytes .../trEighthTemplate.imageset/Contents.json | 21 ++ .../trEighthTemplate.png | Bin 0 -> 1346 bytes .../PrefsWindow/SettingsViewController.swift | 219 ++++++++++++++++++ Rectangle/WindowAction.swift | 16 +- 18 files changed, 395 insertions(+), 8 deletions(-) create mode 100644 Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/blEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/brEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/cblEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/cblEighthTemplate.imageset/cblEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/cbrEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/ctlEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/ctrEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/tlEighthTemplate.png create mode 100644 Rectangle/Assets.xcassets/WindowPositions/trEighthTemplate.imageset/Contents.json create mode 100644 Rectangle/Assets.xcassets/WindowPositions/trEighthTemplate.imageset/trEighthTemplate.png diff --git a/Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..81514a4c4 --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "blEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/blEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/blEighthTemplate.imageset/blEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..afa34953628e42549da17ea18bb0e31c64716e9c GIT binary patch literal 1343 zcmZ`(O=uHA6rTLF#z+;k7Hy@hQEDq@|6-eFNS4}0+dxx8jFMWAZkvr=Om@TWmaHI% zUZkMlId}+ouuv>T1;umGOBKN$y?SU5DtghIZ#LUxo2DCP=Iy-i``&xAZ?cq{8t?FT z`w>DNiHUd`)?OH$zIK=^D^J#8ap~#tQMCH`)E2yakg|zVdNjl$ZO;8!AhP97@$;JQ?e-)ilU)vR7z7Ro(|E2 z;PXE9S{j{TzpA<#@vH8-UT@V?w+RmMEEmCS1oL5v<8Ua9ISvi;KYxSkU}<7nN9fQo zGF+(g>;yud6*-eNvdKw2uNG-Br_M>VSu7DH6g4rJMad8;vsh4cY{mi>0)tJqnE+)$ zj61PFHkqQvR869WXqILJf}f(OsFus)bo`1?FkWs0SJNrs%?Cl>M4Xx3rWa*UD;u=T;G<8o0_bf`z**{GG;9!H}% z2Qk|qhISDh4hIp+!eg35+AE`~1)){9MVV$)D*-7k8WJSSMS^T3$cHmK9d^^fuYsEA zN$8fGXo$7|d}@{ta>E&J2!~kAhJx%MW}z06#V)Kvhny$QrX^i1ERrq|!aNHP!;v}4 z>~S|!8_es9YzVwfY1(dPO*j0l0y^Ez)T^2-K`&dma&|Q5o{f((q@5k@UAsHhE?geL z567}|5e5J2?~DO=b0!hLl$q$5&&|&MJ+f{bxRC1kx>fiy*@tI*ZDpX#=lZ@4EIeMf*Uq_@Sn1;OlJD$x|H-v#-uwRkNJT&| XH`)eXES!Bn{sR)1r{b%lvk(6R2)A%j literal 0 HcmV?d00001 diff --git a/Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..07e9394b1 --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "brEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/brEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/brEighthTemplate.imageset/brEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..f0873fe9566a3dd318f4560cf31bfa3490b7af31 GIT binary patch literal 1340 zcmZ`(&ubGw6rMD(#z?i)D%wg}B1kD_e@xn@J8S}NqfMaE5Tm3Dl5MlGtI2NM-I5h5 z6fdG6c<@v_)N`ST2#P0>>P=DXUm#w3QP4|!^UY?PY}0nb%)FiVecyX;_DvpVFHd!a z`a%eyu5>DygLME#Z?F^Q<<+O_u=vc})C5}ldUg|DJ}ZTEIg>#{U#RWgUK2?gV`uM z0=~erfTPh1_Ujd2EB;DHQ*U6!-?S+XVO|gkFA^MM1c5|jL=eyz{__`HM{21V6QQFg z=Oud4ZiRme<}IlaotC4E+5?P`rGA;~6SRuxNT>}o|bi5(9+2m&_U=E95v zvF^shg-n*2)D49hWqFnlCqfLvNJgnla>+|B9p2*MdCRI1j;q(}Y(2{A#%)dzMUmq% zhj9cD5%a!g$#z6D&oxALbdriWXQ(wx)is9d%Cq`{6%U7Lpyt!aQ?b=nB+Ycof&w|} z;RKfF_M<_u&HQ$;Y`JzI$x_P)#G;-C%{h!ljuOp)I3Ii_sa6%ugnAN~mz>o0I9kPd zh=mq0x{K&>c!*FInKTsIUU^-wB=+kziEBsgC!i!{OMzqsF~W-x9Lw)?*bNWA0cxXX zpj%3^CA$Lfsd*d`#`3}_!6JzcNB9xKLoGCmU09C}Jx@}|DW+aopk0uNVICe%ptHp7 zako=j%xjuzC9q3r+iqvgwEX)8^t#)rR}58wUUqU7+-TlC7cX(NojvW{xI4}+T%JS- zj%DW}CPL3YrJlguETog;`Bc|jsaV`PzHS}9knR7mS@}18p4<$+)i!4B?6+m1Z{y7| z`Q5hv)W)lh!0(MU-836rOm;(dOSa&l z^ddb7^%^{s7Ccxe;t%xbp%nBW1@lMPi;99+Prlh~lWm%An3=cpzVCbQ&A!QUVtTU2 z)9*nD^~9o)B&>rl`ntPdu5LWtf`!tPljCUf%Y|Kd*_P9>d_0b>g58a}s9xkE5cnag zA9Y$bLbs`bhJBB^Y-2hA!y}l{Ss3{z3Ub@{$FPzo3ImktYeF&QLQyibGMy-Ew7~e8 zVeq+E+?GZk*soWqX8hHThTh<+t6>uy5;#7HxgZt-G|yvy0P{Q=6@L5z*YQ#`qa$?U z3>g%vK0l9;YePw;jdXkp&uK+Q%4u1dF^eUlghD0;vnU%9Z59ivj?J*wLSV4THtVG= zi18@wO~(`TgjSa65r$(pujrv^I#kZ(aWZnlro&s<`_M2-m}M)K3RCejTKNIX2ZKSD z6Iem;0m7#*sD@vLtLWGI?S6J05*%^P8_mjr4&^*m)$X+~0YyDTV> zB_5V%IJO-Pif!a~fMrUhBT0smKOz?K)M?gX)N>4x46yaVGZCdIt2)#p3S7ua?U18c zoP(Hd68#5=4u^vXW#NgkOxi1@X$7%ew}seNR67AVA{jCy%Ljd2&?f{^`yF=2!LNf_ z=yB+loMcG00DNjr@bRN5egq3aJk0yJVa!1-B#Q%BhYmSUoKDKRR+uMUAO-{u9+oF_ zh&|+Pr8b!}s$z(OO=;O~Wo4TFb^)F4R_e{NB111*x$<^2=bnuZv80_H?One+)-GHg z(F4b_e-TB`i%&Z>xSRK5k+D>?XD&B8`}fqAaqL=R;OlPT_tYhPxBIw=)#3i$JG4DDBnlP(%A3y2V&9*|J+qcgt}T1tt&hcT{d~5z@Nubn`rBtp Z`G=6nbzX7Jyj&;$1F`GVkbK(F1 literal 0 HcmV?d00001 diff --git a/Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..2f063d591 --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "cbrEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/cbrEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/cbrEighthTemplate.imageset/cbrEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..d152c96941a1c2eba248ea0c67c7e1283b45ad5a GIT binary patch literal 1343 zcmZ`(O=uHA6rMCOt&u3SwP-74i6AP?{>3)kAqlmOHj$==5GA!B-6os5nCyn#Em^^n z7b#lmQHqCx$3hVi^r9DwUZe=^#haiW>OsLD(1YNc%{JMl=`1tzX5RO`pP4tike(d( z`+9u{A%8Lv&%oRdy}QE;v77UT}(b#)>)fxw4c zz36}?BXrf(SC_B5PTQC^z;FmgbQF5=u8cf3{x-~{C7^?)+MJe6sZnC z)J3`rEk8Pjkb70m=8Rlw0_W8-Ef&;Si8jj>VhKe|3}RU_M9M6e6djw}WBsWDZPs3DrA*+9%kQB*`L6mceg(Wb*H8n|H?70fWzYL%{rXjPkII6)8? zmS=cA2#BD5OEE+1Pir5ur)p;Wt2oOj0R*O?I<%TCkx6^_I z8KPl0nq^wiAlZ6+yI7`J*_UL<#eIAcO^s$9MlHq&i2z#{J{6bClA=RCF`kWBq3uyL zl5-Gq4Ps~)(V=h!E!>76@q*?yHjD;9Q+!n ziJpRLDTsz>8-S~3`5-r(<%TdX;B%oMJBV4xg+#Fn>##%46X!CLu9oIW6~w|k3ytB( z7-9CPo2d=v6-72;yiIA^Ze~q2^sO8^)y>q)nk+#rTd{JsH|L&>k1(X19pznnJJv2d zo|q4gW#=KnVkv(Q-sW^Nemsw0XXDb{>z7{C;Kr`Z(~=`|j=Io=*$(*Gr4u!THx+OEQ4&kBs{ho9*bs W7tfiObD#f_e?aoWWPELO=FWfgm2qVN literal 0 HcmV?d00001 diff --git a/Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..2c05b7ff9 --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ctlEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/ctlEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/ctlEighthTemplate.imageset/ctlEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..4a37a9c1e17d78f63a3e0e75bbec39cf802c58ae GIT binary patch literal 1352 zcmZ`(O-LI-6rT03HWDn=N>R}zLTO90e^HYiHUTx(1dPUzmL!zonrzy&NjA-{WJ`Op z2k{_!>Om}oo}_|@o_gq|AU#wlg?i|**Y;8<6ngBNO-$ApH_Xi2dEfWF_h#Q@GrBP6 zbPPHWLe6j~7=v{LMt^q~%)19$N3c+OY;GDIei{A=FQ1e|I2(ze+hBL2F6t_3ClL4{ zY7li=HbVEQp}PH$x>>`t0ft8~qiZm-8!GCk;a|W?o)8RBs;*JhRC0OQ&`NZ)q|pN7 zVJ5)WvD0B`^n?AbLN(%7+Uj~EmG-(#a7f^I5pyCIyfn{aj~DYi@(I8Hfa`KGl++RG z=_7+eyL;CWYCllp2_q4i$7wCk$Qf-#Va$AyC?UzjVCEG=rpHyckqof)!IMEXuM~8sCm?W=mD)K+ zqc|He(I9%x5N!?{5z4|dC55zCT+?!aR^2AC&8SuaN>DZwNR}7foah$3@zV}V+4xmZ z6Fmaml93I$CIFwB6WqKn&QD@N#QeCMo4_2@Lb5o6wds)a#EF=qYq>Sj1p%+X!Nc-o zme_OdW@>|Zub>(Mp+;%iZe}GL{#F6)?q=#@NmZbitz7w9H2Yo+FR`SZZS7sXJJv2- zo`3_6<@6#3948g;54f9|aBwOfa;7uO%l~?hj7xW-L*Jf$m-;q*A=7h3`#83}xO$^6 zkW78vO3fdCo#mg8?=P*te6>gQtR|@SWBt8j;K8rWfl+RBJ2h_%FTE{}@85Dxz4_al fh9hI{DDKqxXnu@@Ej9T?igdFR%Xx??!HV literal 0 HcmV?d00001 diff --git a/Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..a3bb27e4e --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "ctrEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/ctrEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/ctrEighthTemplate.imageset/ctrEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..579d56db0b756368bdda6de3510e7a83c16e64eb GIT binary patch literal 1347 zcmZ`(O=uHA6rS|oND#DYX{BA_MaBJ1+jNH{Xd7)JO${NIZ7H?eW}~Y~HtcT6iXNmF z>7lmuSQG*UsT6t;Z(c--&`TA1@-BjS5Cp}eZ#LUxo2DCP=Iy-i``&xAZ?cpeAN6^= zy$B&+JQhvC+6$wzqYdWut>qn9JVt7C7;W!e_yI4U6e*rfB+wPGJ5ZbFG-@Ug_#sa> zYPD^ICOkb=d&YCg!88GeM=+zaFtX27)Z*Zuz)GGN3{a}9N!3zvdD+x-DyeIfzyz29 z@U^VA*czQ+zb$)e@yktBz20(j)h0M3aJ+~)5eq?z=W!s2c^(Z3zkY-3L@_pLAawE! z86LF$vJ0W+Ej2BfQeq6xYk5Y_Xmbi<<%>iKg)9taUNL3L%I69Ow!(fJfx#x*te>(W z=EJaGN+hWfO;@NvhGRH?#7j|BNY7+(DtgnQ!&}&Y-!zMuWlN=VibfSteud78<)e02RmFKhtGwk=1K-H&`r(&tKNCm?w3kqb3 zhvgZLZA61&tN9&bS#t4MlBs5oiA6jWntd3R977}nY=7{{sG3&_2GkP~xR9ON5l5{! z7g4Ga1BZw%hl>bh;SpUS?UmNFT%=LAh1hyjBLO8Un+hb$i!>+FLNI;MVN))C1yn~* zK(}OMQ+5R4Q*#2%52g7*EI_*s(A)s#pcazFA*@S>oF|r2ilOBeNEbwc0tXMvlR3m5 zao1C8%sU0uj0g^;Zo8f}S@Smv=yumrZ|kZ8y=>>oJJH;G4nD+^c6POQ^RPse=onc3NYr*_QttI3}I-FIKxH$R_WnVJ5+m-)D{^g6Y1 zZol7J^Y!^2eEU+q{P@FS_r%lHpYQ2w%hZ_A#r3UBjp@^CTXO&Au1|dN$0sENO|LMO Z=tG%Xt!w$V-M{32Abw*!x;;F*_#gRDaZdmM literal 0 HcmV?d00001 diff --git a/Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/Contents.json b/Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/Contents.json new file mode 100644 index 000000000..08a760dec --- /dev/null +++ b/Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "tlEighthTemplate.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/tlEighthTemplate.png b/Rectangle/Assets.xcassets/WindowPositions/tlEighthTemplate.imageset/tlEighthTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..c90052b12a075ba3a90fcc9bf493d620f13839eb GIT binary patch literal 1342 zcmZ`(O=uHA6rQxHt+An?sc0;9NwKuj{f%w9V-mDYZ3Cu;G)mfoben8+HOYqEEm^^f z9@HMh9z1C95D}zMDB{g~5l@0rZ-O_WC&iN&!8e<2vQ5(sGxK)d_kHiZ**95B&d+*1 zeh)&(8=s4%U>$_v>p22*b@S;qEG|7YJB7AB5A4FrM>!MEClcs9*gfco>p1Ep5cnaN zA9YzaLN{Dz8um@sh>ht043A(&{V?*6738+@_hBW^91KvZuSvy}3q{FLH7cp8l)wa; zaqzj<-Ij(A?DsWSGk&e3p*L9TY}f>c1dbOmCt@K)@jMQOFwdh&;nyE<9WBo->IfY> zL52%eU!6jzb5luYj7;Jx&ZGdI$}AR2IyNH#3xUBV+iZZc zAjaKDAd^T^)2b#@6AZ_2fvAU~sIZpH<5cX5O^3Hg;I?6uG0Rpe6{ZqoRP7eai=xPK z0xJkKAZYzw$&gICqz~0a_H<&hp4F7Hp{ON_=t?W#)W~7pCmjwl~ z#KZCo$F`$Ev5oxpu}rCaD9KRrhr}YDI?XzadX8a|0k%H)VoWK@B^~OC3S8Jq?SP|M zoP(HY5`+7Q4u^vXW#MT}Che70)k3sgw}shOR67AVCK)m$%ZoH8(n2V`*I`QzejU_8 zPe8ZiBtx5uPv$Ut zz}-r1GOw2uBP!UGmhD#7V$KYIi3mlS{fljqC0T46Z30aVAxc_G&9>Rp#UvYcw`2tm zS`hIdir}Sq2=r2{^k6G^RA@y|L{Oo4QV)s;@$V9EzS*S7HcdCo%-eb2_r3RK-{e+o zeAMaab|8eDk#HyuYcGt>_BNOoS8i{>LTK^PVYK?@*mrn&AtfW3XcV0UyB)O=hmZ|J z;D?BAWH)VuE)YF6dz$F0Vp;&hBAC$;7@3nbjW>cW6TyRx zw`mt?LiEc)S27wSM-){e2PuYPT!Mon$$*;9@bS>uDjnW}u1mUJ;Ay&8EK)@erKoc> z>-YO4gHMFyoRrs~9)V*5 zW@@_}_2MkVWS!{QL9{q5L@0|NQ6=172}Q{Y&AKf>H=>#eNFh;|AX(P$X8dl>o7nEK zNejOMYM@7*>TaZ-Q)LNy+02!#Mzii!@c|mQv!%T& zcgNg?%Og18Shg>s;CS|aVGHi&bR;yC2s>xfsZ_aRL*LgQ>v<=q);ivfU7Y&LeZE(E zQeNI%KPW!^w>0GJa7=pI`j@+|3%2`H>wTwx*+0G}{{CLxEPuK2P+b~|e)zO8w{^`v g@CZHs=39J8pgU#ziM3Zhy71pX