Skip to content

Commit

Permalink
Implement resize with mouse
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitabobko committed Oct 10, 2023
1 parent 54282a8 commit 21e7cff
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 29 deletions.
12 changes: 8 additions & 4 deletions AeroSpace.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
2CC46A952773F148AC398144 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6352ADEE6625D9703CFCA99A /* Window.swift */; };
2E06134604F2510189F1FA85 /* ExecAndWaitCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9EDB8770FBA8B2E6FE91BBB /* ExecAndWaitCommand.swift */; };
374CE35B85B941B8F584C113 /* FlattenWorkspaceTreeCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FDECECC773EBA30661EB8A /* FlattenWorkspaceTreeCommandTest.swift */; };
3BD6FF4CC51532977DA0C05A /* AeroAny.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82476B9BEBAC00EB9E32256F /* AeroAny.swift */; };
42197B9C71A0CDDE65804A6A /* accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D31BF26EAFA96F675D2C14B /* accessibility.swift */; };
45AA5FD4A023AF751922BC22 /* BundleEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B7A2DF0D1F72B80B1F04240 /* BundleEx.swift */; };
45EA2D1C90430C432E123B51 /* keysMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1C0D40CBD65704BA9595C2FA /* keysMap.swift */; };
4D62EC19C6A1E3BBF54306BA /* Copyable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B4C778D2594EB23C295741 /* Copyable.swift */; };
56E72B24303F5F337B31B776 /* TrayMenuModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */; };
5DA2DA21600E8B5BCA3DCFC0 /* LayoutCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2B673C67B00DBFCC27FFE7 /* LayoutCommand.swift */; };
5FB1B80967DF269BA45F84A4 /* resizeWithMouse.swift in Sources */ = {isa = PBXBuildFile; fileRef = A90E205F7C4A569840262EB8 /* resizeWithMouse.swift */; };
6317AB471F4C4F5D66A25784 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EEDBFFCA7A77D96B18FB0732 /* Assets.xcassets */; };
635733FDDF37E44364372B74 /* MruStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 954A434EE57D76F5A9D4140D /* MruStack.swift */; };
64A058E536F1EEF7F01043AF /* TOMLKit in Frameworks */ = {isa = PBXBuildFile; productRef = EC8E4F2CA4FF8884F9F59975 /* TOMLKit */; };
Expand All @@ -42,7 +44,6 @@
991943D50DF9EDBF321A66F1 /* SelectorComparator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1316FFED5D1044CB693EA45 /* SelectorComparator.swift */; };
9A138A729245BD2723148583 /* focused.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61FE8B343B068F0FFFC2373 /* focused.swift */; };
9D34BD7DE311254BF52F5EA2 /* testUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77C03164B8BFD1E59779C6E /* testUtil.swift */; };
9FF829BA48F9AE6E46D8467C /* ScopeFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D113ECDD9173131C0C89630 /* ScopeFunctions.swift */; };
A0765C31043BCFB0420BF1C9 /* parseConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67DBAF4ECF8A0B931FC34EAD /* parseConfig.swift */; };
A2CBF9674964F9083BB198D2 /* ArrayEx.swift in Sources */ = {isa = PBXBuildFile; fileRef = 883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */; };
A55F31B0CC357B37108B8F54 /* MoveContainerToWorkspaceCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7DBD002C4A68AED07BB63EFA /* MoveContainerToWorkspaceCommandTest.swift */; };
Expand Down Expand Up @@ -98,7 +99,6 @@
3C2E5977331398421A4FC168 /* GlobalObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalObserver.swift; sourceTree = "<group>"; };
43DD32B1711B8EFCC834B68E /* WorkspaceCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCommand.swift; sourceTree = "<group>"; };
4B0CD3C2A0E86CDB9DF312AB /* ModeCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModeCommand.swift; sourceTree = "<group>"; };
4D113ECDD9173131C0C89630 /* ScopeFunctions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopeFunctions.swift; sourceTree = "<group>"; };
51CE37C1B8D858C81A396F40 /* CollectionEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionEx.swift; sourceTree = "<group>"; };
526B113159987FA43EA41120 /* refresh.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = refresh.swift; sourceTree = "<group>"; };
5274C575044C2A7123C57584 /* AeroSpace-Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "AeroSpace-Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
Expand All @@ -113,6 +113,7 @@
6F1905935B0C61590A96EFEF /* TestWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestWindow.swift; sourceTree = "<group>"; };
7A247B616D1951777C565D02 /* MoveThroughCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveThroughCommandTest.swift; sourceTree = "<group>"; };
7DBD002C4A68AED07BB63EFA /* MoveContainerToWorkspaceCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveContainerToWorkspaceCommandTest.swift; sourceTree = "<group>"; };
82476B9BEBAC00EB9E32256F /* AeroAny.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AeroAny.swift; sourceTree = "<group>"; };
883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArrayEx.swift; sourceTree = "<group>"; };
8B7A2DF0D1F72B80B1F04240 /* BundleEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleEx.swift; sourceTree = "<group>"; };
8FE45A887100EB70912B07F0 /* default-config.toml */ = {isa = PBXFileReference; path = "default-config.toml"; sourceTree = "<group>"; };
Expand All @@ -127,6 +128,7 @@
9D31BF26EAFA96F675D2C14B /* accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = accessibility.swift; sourceTree = "<group>"; };
9F6B8A501483ACBB62560101 /* TreeNodeEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNodeEx.swift; sourceTree = "<group>"; };
A60709210745C60D64F82D53 /* TreeNodeKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNodeKind.swift; sourceTree = "<group>"; };
A90E205F7C4A569840262EB8 /* resizeWithMouse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = resizeWithMouse.swift; sourceTree = "<group>"; };
A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionalEx.swift; sourceTree = "<group>"; };
AAE5DCAEC5EE619CE33859E7 /* SequenceEx.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SequenceEx.swift; sourceTree = "<group>"; };
AD1645D9939F3F896EF21393 /* TreeNodeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeNodeTest.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -251,6 +253,7 @@
D61FE8B343B068F0FFFC2373 /* focused.swift */,
3C2E5977331398421A4FC168 /* GlobalObserver.swift */,
526B113159987FA43EA41120 /* refresh.swift */,
A90E205F7C4A569840262EB8 /* resizeWithMouse.swift */,
5F5F52E346D024960EAF5938 /* TrayMenuModel.swift */,
A711A25EEC57098295B90C17 /* command */,
50A41C4CCFC95C514CA1EAD5 /* config */,
Expand Down Expand Up @@ -308,6 +311,7 @@
isa = PBXGroup;
children = (
9D31BF26EAFA96F675D2C14B /* accessibility.swift */,
82476B9BEBAC00EB9E32256F /* AeroAny.swift */,
883D7F7F87FBE7D0BDE4E87F /* ArrayEx.swift */,
8B7A2DF0D1F72B80B1F04240 /* BundleEx.swift */,
91E1664607E2B1A6030978BB /* CardinalDirection.swift */,
Expand All @@ -319,7 +323,6 @@
AF3BB3DD434C75536217CB88 /* NSScreenEx.swift */,
A9EDFD4A9F45182CA6E0BD7B /* OptionalEx.swift */,
28B788A95DD3C267878E05B5 /* Rect.swift */,
4D113ECDD9173131C0C89630 /* ScopeFunctions.swift */,
B1316FFED5D1044CB693EA45 /* SelectorComparator.swift */,
AAE5DCAEC5EE619CE33859E7 /* SequenceEx.swift */,
976540EBEACF846D598CD6E1 /* util.swift */,
Expand Down Expand Up @@ -438,6 +441,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3BD6FF4CC51532977DA0C05A /* AeroAny.swift in Sources */,
059308740527CC10D1A79DDD /* AeroApp.swift in Sources */,
66E6CDA75DDD5E4B9647EDE2 /* AeroSpaceApp.swift in Sources */,
A2CBF9674964F9083BB198D2 /* ArrayEx.swift in Sources */,
Expand Down Expand Up @@ -468,7 +472,6 @@
78EE0CEF814ABDBA67941B84 /* Rect.swift in Sources */,
FC35D6D0A678CC802972C6FE /* ReloadConfigCommand.swift in Sources */,
D24D02B1FD87424B908986AF /* ResizeCommand.swift in Sources */,
9FF829BA48F9AE6E46D8467C /* ScopeFunctions.swift in Sources */,
991943D50DF9EDBF321A66F1 /* SelectorComparator.swift in Sources */,
AE76A183D0454E4C8ADCE380 /* SequenceEx.swift in Sources */,
E5682579AEC6B84CF6FCE90D /* TilingContainer.swift in Sources */,
Expand All @@ -487,6 +490,7 @@
A5BFF75CF8021A585BC1F9D5 /* parseCommand.swift in Sources */,
A0765C31043BCFB0420BF1C9 /* parseConfig.swift in Sources */,
B3702BB393A9B03CCAE4C60E /* refresh.swift in Sources */,
5FB1B80967DF269BA45F84A4 /* resizeWithMouse.swift in Sources */,
ED96E36786C941AB3AF780BC /* startAtLogin.swift in Sources */,
93D44EA41776738B4758C28D /* util.swift in Sources */,
);
Expand Down
4 changes: 4 additions & 0 deletions src/GlobalObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class GlobalObserver {
subscribe(NSWorkspace.activeSpaceDidChangeNotification)
subscribe(NSWorkspace.didTerminateApplicationNotification)

NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseUp]) { event in
resetResizeWithMouseIfPossible()
}

// window.observe(windowIsDestroyedObs, kAXUIElementDestroyedNotification)


Expand Down
6 changes: 3 additions & 3 deletions src/command/MoveThroughCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ struct MoveThroughCommand: Command {
}

private func moveOut(window: Window, direction: CardinalDirection) {
let topMostChild = window.parents.first(where: {
let innerMostChild = window.parents.first(where: {
// todo rewrite "is Workspace" part once "sticky" is introduced
$0.parent is Workspace || ($0.parent as? TilingContainer)?.orientation == direction.orientation
}) as! TilingContainer
let bindTo: TilingContainer
let bindToIndex: Int
switch topMostChild.parent.kind {
switch innerMostChild.parent.kind {
case .tilingContainer(let parent):
precondition(parent.orientation == direction.orientation)
bindTo = parent
bindToIndex = topMostChild.ownIndex + direction.insertionOffset
bindToIndex = innerMostChild.ownIndex + direction.insertionOffset
case .workspace(let parent): // create implicit container
let prevRoot = parent.rootTilingContainer
prevRoot.unbindFromParent()
Expand Down
79 changes: 79 additions & 0 deletions src/resizeWithMouse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
func resizedObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: UnsafeMutableRawPointer?) {
if let window = data?.window {
resizeWithMouseIfNeeded(window)
}
refresh()
}

func resetResizeWithMouseIfPossible() {
precondition(Thread.current.isMainThread)
if currentlyResizedWithMouseWindowId != nil {
currentlyResizedWithMouseWindowId = nil
for workspace in Workspace.all {
workspace.resetResizeWeightBeforeResizeRecursive()
}
refresh()
}
}

var currentlyResizedWithMouseWindowId: UInt32? = nil

private let adaptiveWeightBeforeResizeWithMouseKey = TreeNodeUserDataKey<CGFloat>(key: "adaptiveWeightBeforeResizeWithMouseKey")

private func resizeWithMouseIfNeeded(_ window: MacWindow) {
if window.workspace != Workspace.focused {
return // Don't allow to resize windows of hidden workspaces
}
if focusedWindow != window {
return
}
if NSEvent.pressedMouseButtons != 1 { // If mouse left button isn't pressed
return
}
switch window.parent.kind {
case .window:
windowsCantHaveChildren()
case .workspace:
return // Nothing to do for floating windows
case .tilingContainer:
guard let rect = window.getRect() else { return }
guard let lastLayoutedRect = window.lastLayoutedRect else { return }
let (lParent, lOwnIndex) = window.closestParent(hasChildrenInDirection: .left) ?? (nil, nil)
let (dParent, dOwnIndex) = window.closestParent(hasChildrenInDirection: .down) ?? (nil, nil)
let (uParent, uOwnIndex) = window.closestParent(hasChildrenInDirection: .up) ?? (nil, nil)
let (rParent, rOwnIndex) = window.closestParent(hasChildrenInDirection: .right) ?? (nil, nil)
let table: [(CGFloat, TilingContainer?, Int?, Int?, Int?)] = [
(lastLayoutedRect.minX - rect.minX, lParent, lOwnIndex, 0, lOwnIndex), // Horizontal, to the left of the window
(rect.maxY - lastLayoutedRect.maxY, dParent, dOwnIndex, dOwnIndex.map { $0 + 1 }, dParent?.children.count), // Vertical, to the down of the window
(lastLayoutedRect.minY - rect.minY, uParent, uOwnIndex, 0, uOwnIndex), // Vertical, to the up of the window
(rect.maxX - lastLayoutedRect.maxX, rParent, rOwnIndex, rOwnIndex.map { $0 + 1 }, rParent?.children.count), // Horizontal, to the right of the window
]
for (diff, parent, ownIndex, startIndex, pastTheEndIndex) in table {
if let parent, let ownIndex, let startIndex, let pastTheEndIndex, let child = parent.children.getOrNil(atIndex: ownIndex),
pastTheEndIndex - startIndex > 0 && abs(diff) > EPS {
let delta = diff.div(pastTheEndIndex - startIndex)!
let orientation = parent.orientation

child.setWeight(orientation, child.getWeightBeforeResize(orientation) + diff)
for sibling in parent.children[startIndex..<pastTheEndIndex] {
sibling.setWeight(orientation, sibling.getWeightBeforeResize(orientation) - delta)
}
}
}
currentlyResizedWithMouseWindowId = window.windowId
}
}

private extension TreeNode {
func getWeightBeforeResize(_ orientation: Orientation) -> CGFloat {
getUserData(key: adaptiveWeightBeforeResizeWithMouseKey)
?? getWeight(orientation).also { putUserData(key: adaptiveWeightBeforeResizeWithMouseKey, data: $0) }
}

func resetResizeWeightBeforeResizeRecursive() {
cleanUserData(key: adaptiveWeightBeforeResizeWithMouseKey)
for child in children {
child.resetResizeWeightBeforeResizeRecursive()
}
}
}
2 changes: 1 addition & 1 deletion src/tree/MacWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ final class MacWindow: Window, CustomStringConvertible {
window.observe(refreshObs, kAXWindowDeminiaturizedNotification) &&
window.observe(refreshObs, kAXWindowMiniaturizedNotification) &&
window.observe(refreshObs, kAXMovedNotification) &&
window.observe(refreshObs, kAXResizedNotification) {
window.observe(resizedObs, kAXResizedNotification) {
debug("New window detected: \(window)")
allWindowsMap[id] = window
return window
Expand Down
13 changes: 12 additions & 1 deletion src/tree/TreeNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class TreeNode: Equatable {
case .workspace:
error("Can't change weight for floating windows and workspace root containers")
case .window:
error("Windows can't be parent containers")
windowsCantHaveChildren()
case nil:
error("Can't change weight if TreeNode doesn't have parent")
}
Expand Down Expand Up @@ -140,12 +140,23 @@ class TreeNode: Equatable {
lhs === rhs
}

private var userData: [String:Any] = [:]
func getUserData<T>(key: TreeNodeUserDataKey<T>) -> T? { userData[key.key] as! T? }
func putUserData<T>(key: TreeNodeUserDataKey<T>, data: T) {
userData[key.key] = data
}
@discardableResult
func cleanUserData<T>(key: TreeNodeUserDataKey<T>) -> T? { userData.removeValue(forKey: key.key) as! T? }

@discardableResult
func focus() -> Bool { error("Not implemented") }
func getRect() -> Rect? { error("Not implemented") }
}

struct TreeNodeUserDataKey<T> {
let key: String
}

private let WEIGHT_FLOATING = CGFloat(-2)
/// Splits containers evenly if tiling.
///
Expand Down
7 changes: 5 additions & 2 deletions src/tree/TreeNodeEx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,11 @@ extension TreeNode {
case .workspace(let workspace):
workspace.rootTilingContainer.layoutRecursive(_point, width: width, height: height)
case .window(let window):
window.setTopLeftCorner(_point)
window.setSize(CGSize(width: width, height: height))
if window.windowId != currentlyResizedWithMouseWindowId {
window.setTopLeftCorner(_point)
window.setSize(CGSize(width: width, height: height))
window.lastLayoutedRect = window.getRect()
}
case .tilingContainer(let container):
var point = _point
for child in container.children {
Expand Down
1 change: 1 addition & 0 deletions src/tree/Window.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ class Window: TreeNode, Hashable {
let windowId: UInt32
override var parent: TreeNode { super.parent ?? errorT("Windows always have parent") }
var parentOrNilForTests: TreeNode? { super.parent }
var lastLayoutedRect: Rect? = nil

init(id: UInt32, parent: TreeNode, adaptiveWeight: CGFloat) {
self.windowId = id
Expand Down
24 changes: 24 additions & 0 deletions src/util/AeroAny.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
protocol AeroAny {}

extension AeroAny {
@discardableResult
@inlinable
func apply(_ block: (Self) -> Void) -> Self {
block(self)
return self
}

@discardableResult
@inlinable
func also(_ block: (Self) -> Void) -> Self {
block(self)
return self
}

@inlinable func takeIf(_ predicate: (Self) -> Bool) -> Self? { predicate(self) ? self : nil }
}

extension TreeNode: AeroAny {}
extension Writer: AeroAny {}
extension Int: AeroAny {}
extension CGFloat: AeroAny {}
18 changes: 0 additions & 18 deletions src/util/ScopeFunctions.swift

This file was deleted.

2 changes: 2 additions & 0 deletions src/util/util.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
let EPS = 10e-5

func stringType(of some: Any) -> String {
let string = (some is Any.Type) ? String(describing: some) : String(describing: type(of: some))
return string
Expand Down

0 comments on commit 21e7cff

Please sign in to comment.