From db778755ac461bbb1d961f7e107c028f714a5b31 Mon Sep 17 00:00:00 2001 From: Nikita Bobko Date: Sun, 1 Oct 2023 21:47:02 +0200 Subject: [PATCH] Draft moveOut --- src/command/FocusCommand.swift | 7 +-- src/command/MoveThroughCommand.swift | 43 ++++++++++++++-- src/tree/TilingContainer.swift | 1 + src/tree/TreeNode.swift | 9 +++- src/util/CardinalDirection.swift | 2 + test/command/MoveThroughCommandTest.swift | 60 +++++++++++++++++------ 6 files changed, 97 insertions(+), 25 deletions(-) diff --git a/src/command/FocusCommand.swift b/src/command/FocusCommand.swift index b7ccb881..1daf7fb9 100644 --- a/src/command/FocusCommand.swift +++ b/src/command/FocusCommand.swift @@ -14,15 +14,16 @@ struct FocusCommand: Command { // todo speed up. Now it's slightly slow (probabl precondition(Thread.current.isMainThread) guard let currentWindow = focusedWindowOrEffectivelyFocused else { return } if let direction = direction.cardinalOrNil { - guard let topMostChild = currentWindow.parentsWithSelf.first(where: { + let topMostChild = currentWindow.parentsWithSelf.first(where: { + // todo rewrite "is Workspace" part once "sticky" is introduced $0.parent is Workspace || ($0.parent as? TilingContainer)?.orientation == direction.orientation - }) else { return } + })! guard let parent = topMostChild.parent as? TilingContainer else { return } precondition(parent.orientation == direction.orientation) guard let index = topMostChild.ownIndexOrNil else { return } let mruIndexMap = currentWindow.workspace.mruWindows.mruIndexMap let windowToFocus: Window? = parent.children - .getOrNil(atIndex: direction.isPositive ? index + 1 : index - 1)? + .getOrNil(atIndex: index + direction.offset)? .allLeafWindowsRecursive(snappedTo: direction.opposite) .minBy { mruIndexMap[$0] ?? Int.max } windowToFocus?.focus() diff --git a/src/command/MoveThroughCommand.swift b/src/command/MoveThroughCommand.swift index 0a831fdf..14125d0e 100644 --- a/src/command/MoveThroughCommand.swift +++ b/src/command/MoveThroughCommand.swift @@ -6,19 +6,19 @@ struct MoveThroughCommand: Command { guard let currentWindow = focusedWindowOrEffectivelyFocused else { return } if let parent = currentWindow.parent as? TilingContainer { let indexOfCurrent = currentWindow.ownIndex - let indexOfSiblingTarget = direction.isPositive ? indexOfCurrent + 1 : indexOfCurrent - 1 + let indexOfSiblingTarget = indexOfCurrent + direction.offset if parent.orientation == direction.orientation && parent.children.indices.contains(indexOfSiblingTarget) { switch parent.children[indexOfSiblingTarget].kind { case .tilingContainer(let topLevelSiblingTargetContainer): - deepMove(window: currentWindow, into: topLevelSiblingTargetContainer, moveDirection: direction) + deepMoveIn(window: currentWindow, into: topLevelSiblingTargetContainer, moveDirection: direction) case .window: // "swap windows" let prevBinding = currentWindow.unbindFromParent() currentWindow.bindTo(parent: parent, adaptiveWeight: prevBinding.adaptiveWeight, index: indexOfSiblingTarget) case .workspace: error("Impossible") } - } else { // "move out" - + } else { + moveOut(window: currentWindow, direction: direction) } } else if let _ = currentWindow.parent as? Workspace { // floating window // todo @@ -26,7 +26,40 @@ struct MoveThroughCommand: Command { } } -private func deepMove(window: Window, into container: TilingContainer, moveDirection: CardinalDirection) { +private func moveOut(window: Window, direction: CardinalDirection) { + let topMostChild = 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 { + case .tilingContainer(let parent): + precondition(parent.orientation == direction.orientation) + bindTo = parent + bindToIndex = topMostChild.ownIndex + direction.insertionOffset + case .workspace(let parent): // create implicit container + let prevRoot = parent.rootTilingContainer + prevRoot.unbindFromParent() + precondition(prevRoot != parent.rootTilingContainer) + parent.rootTilingContainer.orientation = direction.orientation + prevRoot.bindTo(parent: parent.rootTilingContainer, adaptiveWeight: WEIGHT_AUTO) + + bindTo = parent.rootTilingContainer + bindToIndex = direction.insertionOffset + case .window: + error("Window can't contain children nodes") + } + + window.unbindFromParent() + window.bindTo( + parent: bindTo, + adaptiveWeight: WEIGHT_AUTO, + index: bindToIndex + ) +} + +private func deepMoveIn(window: Window, into container: TilingContainer, moveDirection: CardinalDirection) { let mruIndexMap = window.workspace.mruWindows.mruIndexMap let preferredPath: [TreeNode] = container.allLeafWindowsRecursive .minBy { mruIndexMap[$0] ?? Int.max }! diff --git a/src/tree/TilingContainer.swift b/src/tree/TilingContainer.swift index b3fd539b..83633d57 100644 --- a/src/tree/TilingContainer.swift +++ b/src/tree/TilingContainer.swift @@ -47,6 +47,7 @@ extension TilingContainer { } } + var ownIndex: Int { parent.children.firstIndex(of: self)! } var isRootContainer: Bool { parent is Workspace } } diff --git a/src/tree/TreeNode.swift b/src/tree/TreeNode.swift index 2ad496d7..21fd5b54 100644 --- a/src/tree/TreeNode.swift +++ b/src/tree/TreeNode.swift @@ -74,7 +74,14 @@ class TreeNode: Equatable { .div(newParent.children.count) ?? 1 case .workspace: - self.adaptiveWeight = WEIGHT_FLOATING + switch self.kind { + case .window: + self.adaptiveWeight = WEIGHT_FLOATING + case .tilingContainer: + self.adaptiveWeight = 1 + case .workspace: + error("Binding workspace to workspace is illegal") + } case .window: error("Windows can't have children") } diff --git a/src/util/CardinalDirection.swift b/src/util/CardinalDirection.swift index 8ac4e56c..215ba201 100644 --- a/src/util/CardinalDirection.swift +++ b/src/util/CardinalDirection.swift @@ -17,4 +17,6 @@ extension CardinalDirection { return .left } } + var offset: Int { isPositive ? 1 : -1 } + var insertionOffset: Int { isPositive ? 1 : 0 } } diff --git a/test/command/MoveThroughCommandTest.swift b/test/command/MoveThroughCommandTest.swift index 38d37de8..08aa03c0 100644 --- a/test/command/MoveThroughCommandTest.swift +++ b/test/command/MoveThroughCommandTest.swift @@ -103,22 +103,50 @@ final class MoveThroughCommandTest: XCTestCase { XCTAssertEqual(window1.hWeight, 1) } - //func testCreateImplicitContainer() async { - // let root = Workspace.get(byName: name).rootTilingContainer.apply { - // TestWindow(id: 1, parent: $0).focus() - // TestWindow(id: 2, parent: $0) - // TestWindow(id: 3, parent: $0) - // } - // - // await MoveThroughCommand(direction: .up).runWithoutRefresh() - // XCTAssertEqual( - // root.layoutDescription, - // .v_list([ - // .window(1), - // .h_list([.window(2), .window(3)]) - // ]) - // ) - //} + func testCreateImplicitContainer() async { + let workspace = Workspace.get(byName: name).apply { // Don't cache root + ($0 as! Workspace).rootTilingContainer.apply { + TestWindow(id: 1, parent: $0) + TestWindow(id: 2, parent: $0).focus() + TestWindow(id: 3, parent: $0) + } + } + + await MoveThroughCommand(direction: .up).runWithoutRefresh() + XCTAssertEqual( + workspace.layoutDescription, + .workspace([ + .v_list([ + .window(2), + .h_list([.window(1), .window(3)]) + ]) + ]) + ) + } + + func testMoveOut() async { + let root = Workspace.get(byName: name).rootTilingContainer.apply { + TestWindow(id: 1, parent: $0) + TilingContainer.newVList(parent: $0, adaptiveWeight: 1).apply { + TestWindow(id: 2, parent: $0).focus() + TestWindow(id: 3, parent: $0) + TestWindow(id: 4, parent: $0) + } + } + + await MoveThroughCommand(direction: .left).runWithoutRefresh() + XCTAssertEqual( + root.layoutDescription, + .h_list([ + .window(1), + .window(2), + .v_list([ + .window(3), + .window(4), + ]) + ]) + ) + } } extension TreeNode {