diff --git a/config-examples/default-config.toml b/config-examples/default-config.toml index c0eb23e3..999577b4 100644 --- a/config-examples/default-config.toml +++ b/config-examples/default-config.toml @@ -14,8 +14,8 @@ alt-enter = 'exec_and_forget open /System/Applications/Utilities/Terminal.app' alt-shift-quote = 'focus child' alt-quote = 'focus parent' -# alt-slash = 'layout h_list v_list' -# alt-comma = 'layout h_accordion v_accordion' +alt-slash = 'layout h_list v_list' +alt-comma = 'layout h_accordion v_accordion' # todo focus floating binding diff --git a/config-examples/i3-like-config-example.toml b/config-examples/i3-like-config-example.toml index a7b9ebe3..95287211 100644 --- a/config-examples/i3-like-config-example.toml +++ b/config-examples/i3-like-config-example.toml @@ -22,13 +22,11 @@ alt-shift-l = 'move_through right' # alt-f = 'fullscreen' # todo support fullscreen command? -# todo support layout parsing -#alt-s = 'layout v_accordion' # 'layout stacking' in i3 -#alt-w = 'layout h_accordion' # 'layout tabbed' in i3 -#alt-e = 'layout h_list v_list' # 'layout toggle list' in i3 +alt-s = 'layout v_accordion' # 'layout stacking' in i3 +alt-w = 'layout h_accordion' # 'layout tabbed' in i3 +alt-e = 'layout h_list v_list' # 'layout toggle list' in i3 -#todo support parsing -#alt-shift-space = 'layout floating tiling' # 'floating toggle' in i3 +alt-shift-space = 'layout floating tiling' # 'floating toggle' in i3 # Not supported, because this command is redundant in AeroSpace mental model. # Floating windows are part of the tree from the perspective of 'focus' command. diff --git a/src/command/LayoutCommand.swift b/src/command/LayoutCommand.swift index 82e3ae63..1e3f521b 100644 --- a/src/command/LayoutCommand.swift +++ b/src/command/LayoutCommand.swift @@ -1,18 +1,69 @@ /// Syntax: /// layout (main|h_accordion|v_accordion|h_list|v_list|floating|tiling)... struct LayoutCommand: Command { - let toggleTo: [Layout] - enum Layout { - case main - case h_accordion - case v_accordion - case h_list - case v_list - case floating + let toggleBetween: [Layout] + enum Layout: String { + //case main // todo drop? + case h_accordion, v_accordion, h_list, v_list + case floating, tiling + } + + init?(toggleBetween: [Layout]) { + if toggleBetween.isEmpty { + return nil + } + self.toggleBetween = toggleBetween } func run() async { precondition(Thread.current.isMainThread) - // todo + guard let window = focusedWindow ?? Workspace.focused.mruWindows.mostRecent else { return } + let targetLayout = toggleBetween.firstIndex(of: window.verboseLayout) + .flatMap { toggleBetween.getOrNil(atIndex: $0 + 1) } ?? toggleBetween.first + if let parent = window.parent as? TilingContainer { + parent.layout = targetLayout?.simpleLayout ?? errorT("TODO") + parent.orientation = targetLayout?.orientation ?? errorT("TODO") + refresh() + } else { + precondition(window.parent is Workspace) + // todo + } + } +} + +private extension LayoutCommand.Layout { + var simpleLayout: Layout? { + switch self { + case .h_accordion, .v_accordion: + return .Accordion + case .h_list, .v_list: + return .List + case .floating, .tiling: + return nil + } + } + + var orientation: Orientation? { + switch self { + case .h_accordion, .h_list: + return .H + case .v_accordion, .v_list: + return .V + case .floating, .tiling: + return nil + } + } +} + +private extension MacWindow { + var verboseLayout: LayoutCommand.Layout { + (parent as? TilingContainer).flatMap { + switch $0.layout { + case .List: + return $0.orientation == .H ? .h_list : .v_list + case .Accordion: + return $0.orientation == .H ? .h_accordion : .v_accordion + } + } ?? .floating } } diff --git a/src/command/parseCommand.swift b/src/command/parseCommand.swift index 89f2d796..abf8640a 100644 --- a/src/command/parseCommand.swift +++ b/src/command/parseCommand.swift @@ -37,6 +37,11 @@ private func parseSingleCommand(_ raw: String, _ backtrace: TomlBacktrace) -> Co let direction = MoveThroughCommand.Direction(rawValue: parseSingleArg(args, firstWord, backtrace)) ?? errorT("\(backtrace): Can't parse '\(firstWord)' direction") return MoveThroughCommand(direction: direction) + } else if firstWord == "layout" { + let layouts = args.map { + LayoutCommand.Layout(rawValue: String($0)) ?? errorT("Can't parse layout arg '\($0)'") + } + return LayoutCommand(toggleBetween: layouts) ?? errorT("Can't create layout command") } else if raw == "workspace_back_and_forth" { return WorkspaceBackAndForth() } else if raw == "reload_config" { diff --git a/src/tree/TreeNodeEx.swift b/src/tree/TreeNodeEx.swift index c33a61dd..d0170170 100644 --- a/src/tree/TreeNodeEx.swift +++ b/src/tree/TreeNodeEx.swift @@ -22,17 +22,17 @@ extension TreeNode { self as? Workspace ?? parent.workspace } - func allLeafWindowsRecursive(snappedTo: CardinalDirection) -> [MacWindow] { + func allLeafWindowsRecursive(snappedTo direction: CardinalDirection) -> [MacWindow] { if let workspace = self as? Workspace { - return workspace.rootTilingContainer.allLeafWindowsRecursive(snappedTo: snappedTo) + return workspace.rootTilingContainer.allLeafWindowsRecursive(snappedTo: direction) } else if let window = self as? MacWindow { return [window] } else if let container = self as? TilingContainer { - if snappedTo.orientation == container.orientation { - return (snappedTo.isPositive ? container.children.last : container.children.first)? - .allLeafWindowsRecursive(snappedTo: snappedTo) ?? [] + if direction.orientation == container.orientation { + return (direction.isPositive ? container.children.last : container.children.first)? + .allLeafWindowsRecursive(snappedTo: direction) ?? [] } else { - return children.flatMap { $0.allLeafWindowsRecursive(snappedTo: snappedTo) } + return children.flatMap { $0.allLeafWindowsRecursive(snappedTo: direction) } } } else { error("Not supported TreeNode type: \(Self.self)")