Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some fixes for binary space partitioning #922

Merged
merged 3 commits into from
Dec 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Amethyst/Layout/Layout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extension Layout {

- Note: This does not necessarily correspond to the final position of the window as windows do not necessarily take the exact frame the layout provides.
*/
func windowAtPoint(_ point: CGPoint, of windowSet: WindowSet<Window>, on screen: Screen) -> LayoutWindow? {
func windowAtPoint(_ point: CGPoint, of windowSet: WindowSet<Window>, on screen: Screen) -> LayoutWindow<Window>? {
return frameAssignments(windowSet, on: screen)?.first(where: { $0.frame.contains(point) })?.window
}

Expand All @@ -85,7 +85,7 @@ extension Layout {
- Note: This does not necessarily correspond to the final frame of the window as windows do not necessarily take the exact frame the layout provides.
*/
func assignedFrame(_ window: Window, of windowSet: WindowSet<Window>, on screen: Screen) -> FrameAssignment<Window>? {
return frameAssignments(windowSet, on: screen)?.first { $0.window.id == window.windowID() }
return frameAssignments(windowSet, on: screen)?.first { $0.window.id == window.id() }
}
}

Expand Down Expand Up @@ -192,7 +192,7 @@ class StatefulLayout<Window: WindowType>: Layout<Window> {
- Returns:
The ID of the window before the current window.
*/
func nextWindowIDCounterClockwise() -> CGWindowID? {
func nextWindowIDCounterClockwise() -> Window.WindowID? {
fatalError("Must be implemented by subclass")
}

Expand All @@ -202,7 +202,7 @@ class StatefulLayout<Window: WindowType>: Layout<Window> {
- Returns:
The ID of the window after the current window.
*/
func nextWindowIDClockwise() -> CGWindowID? {
func nextWindowIDClockwise() -> Window.WindowID? {
fatalError("Must be implemented by subclass")
}
}
81 changes: 48 additions & 33 deletions Amethyst/Layout/Layouts/BinarySpacePartitioningLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@

import Silica

class TreeNode {
class TreeNode<Window: WindowType> {
typealias WindowID = Window.WindowID

weak var parent: TreeNode?
var left: TreeNode?
var right: TreeNode?
var windowID: CGWindowID?
var windowID: WindowID?

var valid: Bool {
return (left != nil && right != nil && windowID == nil) || (left == nil && right == nil && windowID != nil)
}

func findWindowID(_ windowID: CGWindowID) -> TreeNode? {
func findWindowID(_ windowID: WindowID) -> TreeNode? {
guard self.windowID == windowID else {
return left?.findWindowID(windowID) ?? right?.findWindowID(windowID)
}

return self
}

func orderedWindowIDs() -> [CGWindowID] {
func orderedWindowIDs() -> [WindowID] {
guard let windowID = windowID else {
let leftWindowIDs = left?.orderedWindowIDs() ?? []
let rightWindowIDs = right?.orderedWindowIDs() ?? []
Expand All @@ -36,7 +38,7 @@ class TreeNode {
return [windowID]
}

func insertWindowIDAtEnd(_ windowID: CGWindowID) {
func insertWindowIDAtEnd(_ windowID: WindowID) {
guard left == nil && right == nil else {
right?.insertWindowIDAtEnd(windowID)
return
Expand All @@ -45,7 +47,7 @@ class TreeNode {
insertWindowID(windowID)
}

func insertWindowID(_ windowID: CGWindowID, atPoint insertionPoint: CGWindowID) {
func insertWindowID(_ windowID: WindowID, atPoint insertionPoint: WindowID) {
guard self.windowID == insertionPoint else {
left?.insertWindowID(windowID, atPoint: insertionPoint)
right?.insertWindowID(windowID, atPoint: insertionPoint)
Expand All @@ -55,7 +57,7 @@ class TreeNode {
insertWindowID(windowID)
}

func removeWindowID(_ windowID: CGWindowID) {
func removeWindowID(_ windowID: WindowID) {
guard let node = findWindowID(windowID) else {
log.error("Trying to remove window not in tree")
return
Expand Down Expand Up @@ -93,7 +95,7 @@ class TreeNode {
}
}

func insertWindowID(_ windowID: CGWindowID) {
func insertWindowID(_ windowID: WindowID) {
guard parent != nil || self.windowID != nil else {
self.windowID = windowID
return
Expand Down Expand Up @@ -134,56 +136,66 @@ class TreeNode {
}
}

extension TreeNode: Equatable {}

func == (lhs: TreeNode, rhs: TreeNode) -> Bool {
return lhs.windowID == rhs.windowID
extension TreeNode: Equatable {
static func == (lhs: TreeNode, rhs: TreeNode) -> Bool {
return lhs.windowID == rhs.windowID
}
}

class BinarySpacePartitioningLayout<Window: WindowType>: StatefulLayout<Window> {
private typealias TraversalNode = (node: TreeNode, frame: CGRect)
typealias WindowID = Window.WindowID

private typealias TraversalNode = (node: TreeNode<Window>, frame: CGRect)

override static var layoutName: String { return "Binary Space Partitioning" }
override static var layoutKey: String { return "bsp" }

override var layoutDescription: String { return "\(lastKnownFocusedWindowID.debugDescription)" }

private var rootNode = TreeNode()
private var lastKnownFocusedWindowID: CGWindowID?
private var rootNode = TreeNode<Window>()
private var lastKnownFocusedWindowID: WindowID?

private func constructInitialTreeWithWindows(_ windows: [LayoutWindow]) {
private func constructInitialTreeWithWindows(_ windows: [LayoutWindow<Window>]) {
for window in windows {
guard rootNode.findWindowID(window.id) == nil else {
continue
}

rootNode.insertWindowIDAtEnd(window.id)

if window.isFocused {
lastKnownFocusedWindowID = window.id
}
}
}

override func updateWithChange(_ windowChange: Change<Window>) {
switch windowChange {
case let .add(window):
guard rootNode.findWindowID(window.windowID()) == nil else {
guard rootNode.findWindowID(window.id()) == nil else {
log.warning("Trying to add a window already in the tree")
return
}

if let insertionPoint = lastKnownFocusedWindowID, window.windowID() != insertionPoint {
log.info("insert \(window) - \(window.windowID()) at point: \(insertionPoint)")
rootNode.insertWindowID(window.windowID(), atPoint: insertionPoint)
if let insertionPoint = lastKnownFocusedWindowID, window.id() != insertionPoint {
log.info("insert \(window) - \(window.id()) at point: \(insertionPoint)")
rootNode.insertWindowID(window.id(), atPoint: insertionPoint)
} else {
log.info("insert \(window) - \(window.windowID()) at end")
rootNode.insertWindowIDAtEnd(window.windowID())
log.info("insert \(window) - \(window.id()) at end")
rootNode.insertWindowIDAtEnd(window.id())
}

if window.isFocused() {
lastKnownFocusedWindowID = window.id()
}
case let .remove(window):
log.info("remove: \(window) - \(window.windowID())")
rootNode.removeWindowID(window.windowID())
log.info("remove: \(window) - \(window.id())")
rootNode.removeWindowID(window.id())
case let .focusChanged(window):
lastKnownFocusedWindowID = window.windowID()
lastKnownFocusedWindowID = window.id()
case let .windowSwap(window, otherWindow):
let windowID = window.windowID()
let otherWindowID = otherWindow.windowID()
let windowID = window.id()
let otherWindowID = otherWindow.id()

guard let windowNode = rootNode.findWindowID(windowID), let otherWindowNode = rootNode.findWindowID(otherWindowID) else {
log.error("Tried to perform an unbalanced window swap: \(windowID) <-> \(otherWindowID)")
Expand All @@ -197,14 +209,14 @@ class BinarySpacePartitioningLayout<Window: WindowType>: StatefulLayout<Window>
}
}

override func nextWindowIDCounterClockwise() -> CGWindowID? {
override func nextWindowIDCounterClockwise() -> WindowID? {
guard let focusedWindow = Window.currentlyFocused() else {
return nil
}

let orderedIDs = rootNode.orderedWindowIDs()

guard let focusedWindowIndex = orderedIDs.index(of: focusedWindow.windowID()) else {
guard let focusedWindowIndex = orderedIDs.index(of: focusedWindow.id()) else {
return nil
}

Expand All @@ -213,14 +225,14 @@ class BinarySpacePartitioningLayout<Window: WindowType>: StatefulLayout<Window>
return orderedIDs[nextWindowIndex]
}

override func nextWindowIDClockwise() -> CGWindowID? {
override func nextWindowIDClockwise() -> WindowID? {
guard let focusedWindow = Window.currentlyFocused() else {
return nil
}

let orderedIDs = rootNode.orderedWindowIDs()

guard let focusedWindowIndex = orderedIDs.index(of: focusedWindow.windowID()) else {
guard let focusedWindowIndex = orderedIDs.index(of: focusedWindow.id()) else {
return nil
}

Expand All @@ -236,13 +248,16 @@ class BinarySpacePartitioningLayout<Window: WindowType>: StatefulLayout<Window>
return []
}

let windowIDMap: [CGWindowID: LayoutWindow] = windows.reduce([:]) { (windowMap, window) -> [CGWindowID: LayoutWindow] in
if rootNode.left == nil && rootNode.right == nil {
constructInitialTreeWithWindows(windows)
}

let windowIDMap: [WindowID: LayoutWindow<Window>] = windows.reduce([:]) { (windowMap, window) -> [WindowID: LayoutWindow<Window>] in
var mutableWindowMap = windowMap
mutableWindowMap[window.id] = window
return mutableWindowMap
}

let focusedWindow = Window.currentlyFocused()
let baseFrame = screen.adjustedFrame()
var ret: [FrameAssignment<Window>] = []
var traversalNodes: [TraversalNode] = [(node: rootNode, frame: baseFrame)]
Expand Down
38 changes: 24 additions & 14 deletions Amethyst/Layout/ReflowOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,35 +57,45 @@ struct ResizeRules {
}
}

struct LayoutWindow {
let id: CGWindowID
struct LayoutWindow<Window: WindowType>: Equatable {
let id: Window.WindowID
let frame: CGRect
let isFocused: Bool

static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}

init(id: Window.WindowID, frame: CGRect, isFocused: Bool) {
self.id = id
self.frame = frame
self.isFocused = isFocused
}
}

struct WindowSet<Window: WindowType> {
let windows: [LayoutWindow]
private let isWindowWithIDActive: (CGWindowID) -> Bool
private let isWindowWithIDFloating: (CGWindowID) -> Bool
private let windowForID: (CGWindowID) -> Window?
let windows: [LayoutWindow<Window>]
private let isWindowWithIDActive: (Window.WindowID) -> Bool
private let isWindowWithIDFloating: (Window.WindowID) -> Bool
private let windowForID: (Window.WindowID) -> Window?

init(
windows: [LayoutWindow],
isWindowWithIDActive: @escaping (CGWindowID) -> Bool,
isWindowWithIDFloating: @escaping (CGWindowID) -> Bool,
windowForID: @escaping (CGWindowID) -> Window?
windows: [LayoutWindow<Window>],
isWindowWithIDActive: @escaping (Window.WindowID) -> Bool,
isWindowWithIDFloating: @escaping (Window.WindowID) -> Bool,
windowForID: @escaping (Window.WindowID) -> Window?
) {
self.windows = windows
self.isWindowWithIDActive = isWindowWithIDActive
self.isWindowWithIDFloating = isWindowWithIDFloating
self.windowForID = windowForID
}

func isWindowActive(_ window: LayoutWindow) -> Bool {
func isWindowActive(_ window: LayoutWindow<Window>) -> Bool {
return isWindowWithIDActive(window.id)
}

func isWindowFloating(_ window: LayoutWindow) -> Bool {
func isWindowFloating(_ window: LayoutWindow<Window>) -> Bool {
return isWindowWithIDFloating(window.id)
}

Expand All @@ -111,7 +121,7 @@ struct FrameAssignment<Window: WindowType> {
let frame: CGRect

/// The window that will be moved and sized.
let window: LayoutWindow
let window: LayoutWindow<Window>

/// The frame of the screen being occupied.
let screenFrame: CGRect
Expand Down Expand Up @@ -213,7 +223,7 @@ class ReflowOperation<Window: WindowType>: Operation {

let layout: Layout<Window>

var windows: [LayoutWindow] { return windowSet.windows }
var windows: [LayoutWindow<Window>] { return windowSet.windows }

/**
- Parameters:
Expand Down
8 changes: 4 additions & 4 deletions Amethyst/Managers/FocusTransitionCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ protocol FocusTransitionTarget: class {
func lastFocusedWindow(on screen: Screen) -> Window?
func screen(at index: Int) -> Screen?
func windows(onScreen screen: Screen) -> [Window]
func nextWindowIDClockwise(on screen: Screen) -> CGWindowID?
func nextWindowIDCounterClockwise(on screen: Screen) -> CGWindowID?
func nextWindowIDClockwise(on screen: Screen) -> Window.WindowID?
func nextWindowIDCounterClockwise(on screen: Screen) -> Window.WindowID?
func nextScreenIndexClockwise(from screen: Screen) -> Int
func nextScreenIndexCounterClockwise(from screen: Screen) -> Int
}
Expand Down Expand Up @@ -67,7 +67,7 @@ class FocusTransitionCoordinator<Target: FocusTransitionTarget> {

let windowToFocus = { () -> Window in
if let nextWindowID = self.target.nextWindowIDCounterClockwise(on: screen) {
let windowToFocusIndex = windows.index { $0.windowID() == nextWindowID } ?? 0
let windowToFocusIndex = windows.index { $0.id() == nextWindowID } ?? 0
return windows[windowToFocusIndex]
} else {
let windowIndex = windows.index(of: focusedWindow) ?? 0
Expand Down Expand Up @@ -97,7 +97,7 @@ class FocusTransitionCoordinator<Target: FocusTransitionTarget> {

let windowToFocus = { () -> Window in
if let nextWindowID = target.nextWindowIDClockwise(on: screen) {
let windowToFocusIndex = windows.index { $0.windowID() == nextWindowID } ?? 0
let windowToFocusIndex = windows.index { $0.id() == nextWindowID } ?? 0
return windows[windowToFocusIndex]
} else {
let windowIndex = windows.index(of: focusedWindow) ?? windows.count - 1
Expand Down
4 changes: 2 additions & 2 deletions Amethyst/Managers/ScreenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,15 @@ final class ScreenManager<Delegate: ScreenManagerDelegate>: NSObject {
panedLayout.expandMainPane()
}

func nextWindowIDCounterClockwise() -> CGWindowID? {
func nextWindowIDCounterClockwise() -> Window.WindowID? {
guard let layout = currentLayout as? StatefulLayout else {
return nil
}

return layout.nextWindowIDCounterClockwise()
}

func nextWindowIDClockwise() -> CGWindowID? {
func nextWindowIDClockwise() -> Window.WindowID? {
guard let statefulLayout = currentLayout as? StatefulLayout else {
return nil
}
Expand Down
8 changes: 4 additions & 4 deletions Amethyst/Managers/WindowManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ final class WindowManager<Application: ApplicationType>: NSObject {
}

let didLeaveScreen = windows.isWindowActive(existingWindow) && !existingWindow.isOnScreen()
let isInvalid = existingWindow.windowID() == kCGNullWindowID
let isInvalid = existingWindow.cgID() == kCGNullWindowID

// The window needs to have either left the screen and therefore is being replaced
// or be invalid and therefore being removed and can be replaced.
Expand Down Expand Up @@ -415,7 +415,7 @@ extension WindowManager: MouseStateKeeperDelegate {

if let screenManager: ScreenManager<WindowManager<Application>> = focusedScreenManager(), let layout = screenManager.currentLayout {
let windowSet = self.windows.windowSet(forWindowsOnScreen: screen)
if let layoutWindow = layout.windowAtPoint(pointerLocation, of: windowSet, on: screen), let framedWindow = self.windows.window(withWindowID: layoutWindow.id) {
if let layoutWindow = layout.windowAtPoint(pointerLocation, of: windowSet, on: screen), let framedWindow = self.windows.window(withID: layoutWindow.id) {
executeTransition(.switchWindows(draggedWindow, framedWindow))
return
}
Expand Down Expand Up @@ -649,11 +649,11 @@ extension WindowManager: FocusTransitionTarget {
return screens.screenManagers.first { $0.screen.screenID() == screen.screenID() }?.lastFocusedWindow
}

func nextWindowIDClockwise(on screen: Screen) -> CGWindowID? {
func nextWindowIDClockwise(on screen: Screen) -> Window.WindowID? {
return screenManager(for: screen)?.nextWindowIDClockwise()
}

func nextWindowIDCounterClockwise(on screen: Screen) -> CGWindowID? {
func nextWindowIDCounterClockwise(on screen: Screen) -> Window.WindowID? {
return screenManager(for: screen)?.nextWindowIDCounterClockwise()
}
}
Loading