-
-
Notifications
You must be signed in to change notification settings - Fork 154
/
moveWithMouse.swift
96 lines (87 loc) · 3.95 KB
/
moveWithMouse.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import AppKit
func movedObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: UnsafeMutableRawPointer?) {
if let window = data?.window, TrayMenuModel.shared.isEnabled {
moveWithMouseIfTheCase(window)
}
refreshAndLayout(.ax(notif as String), screenIsDefinitelyUnlocked: false)
}
private func moveWithMouseIfTheCase(_ window: Window) { // todo cover with tests
if window.isHiddenInCorner || // Don't allow to move windows of hidden workspaces
!isLeftMouseButtonPressed ||
currentlyManipulatedWithMouseWindowId != nil && window.windowId != currentlyManipulatedWithMouseWindowId ||
getNativeFocusedWindow(startup: false) != window
{
return
}
resetClosedWindowsCache()
switch window.parent.cases {
case .workspace:
moveFloatingWindow(window)
case .tilingContainer:
moveTilingWindow(window)
case .macosMinimizedWindowsContainer, .macosFullscreenWindowsContainer,
.macosPopupWindowsContainer, .macosHiddenAppsWindowsContainer:
return // Unconventional windows can't be moved with mouse
}
}
private func moveFloatingWindow(_ window: Window) {
guard let targetWorkspace = window.getCenter()?.monitorApproximation.activeWorkspace else { return }
if targetWorkspace != window.parent {
window.bindAsFloatingWindow(to: targetWorkspace)
}
}
private func moveTilingWindow(_ window: Window) {
currentlyManipulatedWithMouseWindowId = window.windowId
window.lastAppliedLayoutPhysicalRect = nil
let mouseLocation = mouseLocation
let targetWorkspace = mouseLocation.monitorApproximation.activeWorkspace
let swapTarget = mouseLocation.findIn(tree: targetWorkspace.rootTilingContainer, virtual: false)?.takeIf { $0 != window }
if targetWorkspace != window.nodeWorkspace { // Move window to a different monitor
let index: Int = if let swapTarget, let parent = swapTarget.parent as? TilingContainer, let targetRect = swapTarget.lastAppliedLayoutPhysicalRect {
mouseLocation.getProjection(parent.orientation) >= targetRect.center.getProjection(parent.orientation)
? swapTarget.ownIndex + 1
: swapTarget.ownIndex
} else {
0
}
window.bind(
to: swapTarget?.parent ?? targetWorkspace.rootTilingContainer,
adaptiveWeight: WEIGHT_AUTO,
index: index
)
} else if let swapTarget {
swapWindows(window, swapTarget)
}
}
func swapWindows(_ window1: Window, _ window2: Window) {
if window1 == window2 { return }
if window1.ownIndex < window2.ownIndex {
let binding2 = window2.unbindFromParent()
let binding1 = window1.unbindFromParent()
window2.bind(to: binding1.parent, adaptiveWeight: binding1.adaptiveWeight, index: binding1.index)
window1.bind(to: binding2.parent, adaptiveWeight: binding2.adaptiveWeight, index: binding2.index)
} else {
let binding1 = window1.unbindFromParent()
let binding2 = window2.unbindFromParent()
window1.bind(to: binding2.parent, adaptiveWeight: binding2.adaptiveWeight, index: binding2.index)
window2.bind(to: binding1.parent, adaptiveWeight: binding1.adaptiveWeight, index: binding1.index)
}
}
extension CGPoint {
func findIn(tree: TilingContainer, virtual: Bool) -> Window? {
let point = self
let target: TreeNode? = switch tree.layout {
case .tiles:
tree.children.first(where: {
(virtual ? $0.lastAppliedLayoutVirtualRect : $0.lastAppliedLayoutPhysicalRect)?.contains(point) == true
})
case .accordion:
tree.mostRecentChild
}
guard let target else { return nil }
return switch target.tilingTreeNodeCasesOrThrow() {
case .window(let window): window
case .tilingContainer(let container): findIn(tree: container, virtual: virtual)
}
}
}