From c5d67bc53e2ab3ec37c3c21377f3a0c4f5a244b5 Mon Sep 17 00:00:00 2001 From: Rudi Farkas Date: Sat, 6 May 2023 18:46:02 +0200 Subject: [PATCH 1/2] add action cascadeActiveApp; add MultiWindowManager.cascadeActiveAppWindowsOnScreen --- .../MultiWindow/MultiWindowManager.swift | 60 +++++++++++++------ Rectangle/WindowAction.swift | 14 +++-- TerminalCommands.md | 23 ++++++- 3 files changed, 73 insertions(+), 24 deletions(-) diff --git a/Rectangle/MultiWindow/MultiWindowManager.swift b/Rectangle/MultiWindow/MultiWindowManager.swift index a00ba4f43..fdb410a7a 100644 --- a/Rectangle/MultiWindow/MultiWindowManager.swift +++ b/Rectangle/MultiWindow/MultiWindowManager.swift @@ -5,11 +5,11 @@ // Created by Mikhail (Dirondin) Polubisok on 2/20/22. // Copyright © 2021 Ryan Hanson. All rights reserved. // + import Cocoa import MASShortcut class MultiWindowManager { - static func execute(parameters: ExecutionParameters) -> Bool { // TODO: Protocol and factory for all multi-window positioning algorithms switch parameters.action { @@ -22,6 +22,9 @@ class MultiWindowManager { case .cascadeAll: cascadeAllWindowsOnScreen(windowElement: parameters.windowElement) return true + case .cascadeActiveApp: + cascadeActiveAppWindowsOnScreen(windowElement: parameters.windowElement) + return true default: return false } @@ -29,7 +32,7 @@ class MultiWindowManager { static private func allWindowsOnScreen(windowElement: AccessibilityElement? = nil, sortByPID: Bool = false) -> (screens: UsableScreens, windows: [AccessibilityElement])? { let screenDetection = ScreenDetection() - + guard let windowElement = windowElement ?? AccessibilityElement.getFrontWindowElement(), let screens = screenDetection.detectScreens(using: windowElement) else { @@ -49,29 +52,30 @@ class MultiWindowManager { var actualWindows = [AccessibilityElement]() for w in windows { - if Defaults.todo.userEnabled && TodoManager.isTodoWindow(w) { continue } + if Defaults.todo.userEnabled, TodoManager.isTodoWindow(w) { continue } let screen = screenDetection.detectScreens(using: w)?.currentScreen if screen == currentScreen && w.isWindow == true && w.isSheet != true && w.isMinimized != true && w.isHidden != true - && w.isSystemDialog != true { + && w.isSystemDialog != true + { actualWindows.append(w) } } - + return (screens, actualWindows) } - + static func tileAllWindowsOnScreen(windowElement: AccessibilityElement? = nil) { guard let (screens, windows) = allWindowsOnScreen(windowElement: windowElement, sortByPID: true) else { return } - + let screenFrame = screens.currentScreen.adjustedVisibleFrame().screenFlipped let count = windows.count - + let colums = Int(ceil(sqrt(CGFloat(count)))) let rows = Int(ceil(CGFloat(count) / CGFloat(colums))) let size = CGSize(width: (screenFrame.maxX - screenFrame.minX) / CGFloat(colums), height: (screenFrame.maxY - screenFrame.minY) / CGFloat(rows)) @@ -85,38 +89,58 @@ class MultiWindowManager { private static func tileWindow(_ w: AccessibilityElement, screenFrame: CGRect, size: CGSize, column: Int, row: Int) { var rect = w.frame - + // TODO: save previous position in history - + rect.origin.x = screenFrame.origin.x + size.width * CGFloat(column) rect.origin.y = screenFrame.origin.y + size.height * CGFloat(row) rect.size = size - + w.setFrame(rect) } - + static func cascadeAllWindowsOnScreen(windowElement: AccessibilityElement? = nil) { guard let (screens, windows) = allWindowsOnScreen(windowElement: windowElement, sortByPID: true) else { return } - + let screenFrame = screens.currentScreen.adjustedVisibleFrame().screenFlipped - + let delta = CGFloat(Defaults.cascadeAllDeltaSize.value) - + for (ind, w) in windows.enumerated() { cascadeWindow(w, screenFrame: screenFrame, delta: delta, index: ind) } } + static func cascadeActiveAppWindowsOnScreen(windowElement: AccessibilityElement? = nil) { + guard let (screens, windows) = allWindowsOnScreen(windowElement: windowElement, sortByPID: true), + let frontWindowElement = AccessibilityElement.getFrontWindowElement() + else { + return + } + + let screenFrame = screens.currentScreen.adjustedVisibleFrame().screenFlipped + + let delta = CGFloat(Defaults.cascadeAllDeltaSize.value) + + for (ind, w) in windows.enumerated() { + if let pid = w.pid { + if pid == frontWindowElement.pid { + cascadeWindow(w, screenFrame: screenFrame, delta: delta, index: ind) + } + } + } + } + private static func cascadeWindow(_ w: AccessibilityElement, screenFrame: CGRect, delta: CGFloat, index: Int) { var rect = w.frame - + // TODO: save previous position in history - + rect.origin.x = screenFrame.origin.x + delta * CGFloat(index) rect.origin.y = screenFrame.origin.y + delta * CGFloat(index) - + w.setFrame(rect) w.bringToFront() } diff --git a/Rectangle/WindowAction.swift b/Rectangle/WindowAction.swift index 2c272c3a6..da05e57d8 100644 --- a/Rectangle/WindowAction.swift +++ b/Rectangle/WindowAction.swift @@ -82,7 +82,8 @@ enum WindowAction: Int, Codable { tileAll = 66, cascadeAll = 67, leftTodo = 68, - rightTodo = 69 + rightTodo = 69, + cascadeActiveApp = 70 // Order matters here - it's used in the menu static let active = [leftHalf, rightHalf, centerHalf, topHalf, bottomHalf, @@ -101,7 +102,8 @@ enum WindowAction: Int, Codable { topLeftEighth, topCenterLeftEighth, topCenterRightEighth, topRightEighth, bottomLeftEighth, bottomCenterLeftEighth, bottomCenterRightEighth, bottomRightEighth, tileAll, cascadeAll, - leftTodo, rightTodo + leftTodo, rightTodo, + cascadeActiveApp ] func post() { @@ -202,6 +204,7 @@ enum WindowAction: Int, Codable { case .cascadeAll: return "cascadeAll" case .leftTodo: return "leftTodo" case .rightTodo: return "rightTodo" + case .cascadeActiveApp: return "cascadeActiveApp" } } @@ -334,7 +337,7 @@ enum WindowAction: Int, Codable { case .topLeftEighth, .topCenterLeftEighth, .topCenterRightEighth, .topRightEighth, .bottomLeftEighth, .bottomCenterLeftEighth, .bottomCenterRightEighth, .bottomRightEighth: return nil - case .specified, .reverseAll, .tileAll, .cascadeAll, .leftTodo, .rightTodo: + case .specified, .reverseAll, .tileAll, .cascadeAll, .leftTodo, .rightTodo, .cascadeActiveApp: return nil } @@ -362,7 +365,7 @@ enum WindowAction: Int, Codable { var isDragSnappable: Bool { switch self { - case .restore, .previousDisplay, .nextDisplay, .moveUp, .moveDown, .moveLeft, .moveRight, .specified, .reverseAll, .tileAll, .cascadeAll, .smaller, .larger, + case .restore, .previousDisplay, .nextDisplay, .moveUp, .moveDown, .moveLeft, .moveRight, .specified, .reverseAll, .tileAll, .cascadeAll, .smaller, .larger, .cascadeActiveApp, // Ninths .topLeftNinth, .topCenterNinth, .topRightNinth, .middleLeftNinth, .middleCenterNinth, .middleRightNinth, .bottomLeftNinth, .bottomCenterNinth, .bottomRightNinth, // Corner thirds @@ -491,6 +494,7 @@ enum WindowAction: Int, Codable { case .cascadeAll: return NSImage() case .leftTodo: return NSImage() case .rightTodo: return NSImage() + case .cascadeActiveApp: return NSImage() } } @@ -531,7 +535,7 @@ enum WindowAction: Int, Codable { return Defaults.applyGapsToMaximize.userDisabled ? .none : .both; case .maximizeHeight: return Defaults.applyGapsToMaximizeHeight.userDisabled ? .none : .vertical; - case .almostMaximize, .previousDisplay, .nextDisplay, .larger, .smaller, .center, .restore, .specified, .reverseAll, .tileAll, .cascadeAll: + case .almostMaximize, .previousDisplay, .nextDisplay, .larger, .smaller, .center, .restore, .specified, .reverseAll, .tileAll, .cascadeAll, .cascadeActiveApp: return .none } } diff --git a/TerminalCommands.md b/TerminalCommands.md index 099fbfcff..cce73642e 100644 --- a/TerminalCommands.md +++ b/TerminalCommands.md @@ -16,6 +16,7 @@ The preferences window is purposefully slim, but there's a lot that can be modif - [Add extra "ninths" sizing commands](#add-extra-ninths-sizing-commands) - [Add extra "eighths" sizing commands](#add-extra-eighths-sizing-commands) - [Add additional "thirds" sizing commands](#add-additional-thirds-sizing-commands) +- [Add additional tiling and cascading commands](#add-additional-tiling-and-cascading-commands) - [Modify the "footprint" displayed for drag to snap area](#modify-the-footprint-displayed-for-drag-to-snap-area) - [Move Up/Down/Left/Right: Don't center on edge](#move-updownleftright-dont-center-on-edge) - [Make Smaller limits](#make-smaller-limits) @@ -205,6 +206,26 @@ For example, the command for setting the top left two-thirds shortcut to `ctrl o defaults write com.knollsoft.Rectangle topLeftThird -dict-add keyCode -float 18 modifierFlags -float 917504 ``` +## Add additional tiling and cascading commands + +Commands for tiling and cascading the visible windows are not available in the UI but can be configured via CLI. + +The key codes are: + +* tileAll +* cascadeAll +* cascadeActiveApp + +_tileAll_ and _cascadeAll_ act on all visible windows. + +_cascadeActiveApp_ cascades and brings to the front only windows belonging too the currently active (foremost) app, leaving all other windows alonne. + +For example, the command for setting the cascadeActiveApp shortcut to `ctrl shift 2` would be: + +```bash +defaults write com.knollsoft.Rectangle cascadeActiveApp -dict-add keyCode -float 2 modifierFlags -float 393475 +``` + ## Modify the "footprint" displayed for drag to snap area Adjust the alpha (transparency). Default is 0.3. @@ -408,4 +429,4 @@ To disable restore when double-clicked again: ```bash defaults write com.knollsoft.Rectangle doubleClickTitleBarRestore -int 2 -``` \ No newline at end of file +``` From a5fad8a879240a7f74987d568d4b296e220003de Mon Sep 17 00:00:00 2001 From: Rudi Farkas Date: Sun, 7 May 2023 10:59:55 +0200 Subject: [PATCH 2/2] cascadeActiveAppWindowsOnScreen: modify to put the frontmost window on top of the cascade --- .../MultiWindow/MultiWindowManager.swift | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/Rectangle/MultiWindow/MultiWindowManager.swift b/Rectangle/MultiWindow/MultiWindowManager.swift index fdb410a7a..b085c218e 100644 --- a/Rectangle/MultiWindow/MultiWindowManager.swift +++ b/Rectangle/MultiWindow/MultiWindowManager.swift @@ -124,12 +124,23 @@ class MultiWindowManager { let delta = CGFloat(Defaults.cascadeAllDeltaSize.value) - for (ind, w) in windows.enumerated() { - if let pid = w.pid { - if pid == frontWindowElement.pid { - cascadeWindow(w, screenFrame: screenFrame, delta: delta, index: ind) - } - } + // keep windows with a pid equal to the front window's pid + var filtered = windows.filter(hasFrontWindowPid(_:)) + + // move the first to become the last + if let first = filtered.first, hasFrontWindowPid(first) { + filtered.removeFirst() + filtered.append(first) + } + + // cascade the filtered windows + for (ind, w) in filtered.enumerated() { + cascadeWindow(w, screenFrame: screenFrame, delta: delta, index: ind) + } + + // func returning true for a pid equal to the front window's pid + func hasFrontWindowPid(_ w: AccessibilityElement) -> Bool { + return w.pid == frontWindowElement.pid } }