Skip to content

Commit

Permalink
Merge pull request #1146 from rudifa/add-cascadeActiveApp-3
Browse files Browse the repository at this point in the history
add action cascadeActiveApp; add cascadeActiveAppWindowsOnScreen
  • Loading branch information
rxhanson authored May 9, 2023
2 parents c723afc + a5fad8a commit c20c450
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 24 deletions.
71 changes: 53 additions & 18 deletions Rectangle/MultiWindow/MultiWindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -22,14 +22,17 @@ class MultiWindowManager {
case .cascadeAll:
cascadeAllWindowsOnScreen(windowElement: parameters.windowElement)
return true
case .cascadeActiveApp:
cascadeActiveAppWindowsOnScreen(windowElement: parameters.windowElement)
return true
default:
return false
}
}

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 {
Expand All @@ -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))
Expand All @@ -85,38 +89,69 @@ 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)

// 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
}
}

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()
}
Expand Down
14 changes: 9 additions & 5 deletions Rectangle/WindowAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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() {
Expand Down Expand Up @@ -202,6 +204,7 @@ enum WindowAction: Int, Codable {
case .cascadeAll: return "cascadeAll"
case .leftTodo: return "leftTodo"
case .rightTodo: return "rightTodo"
case .cascadeActiveApp: return "cascadeActiveApp"
}
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -491,6 +494,7 @@ enum WindowAction: Int, Codable {
case .cascadeAll: return NSImage()
case .leftTodo: return NSImage()
case .rightTodo: return NSImage()
case .cascadeActiveApp: return NSImage()
}
}

Expand Down Expand Up @@ -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
}
}
Expand Down
23 changes: 22 additions & 1 deletion TerminalCommands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -408,4 +429,4 @@ To disable restore when double-clicked again:

```bash
defaults write com.knollsoft.Rectangle doubleClickTitleBarRestore -int 2
```
```

0 comments on commit c20c450

Please sign in to comment.