macos: handle split resizing
parent
1707159441
commit
e3bc3422dc
|
|
@ -93,6 +93,24 @@ extension SplitTree {
|
|||
|
||||
return .init(root: newRoot, zoomed: newZoomed)
|
||||
}
|
||||
|
||||
/// Replace a node in the tree with a new node.
|
||||
func replace(node: Node, with newNode: Node) throws -> Self {
|
||||
guard let root else { throw SplitError.viewNotFound }
|
||||
|
||||
// Get the path to the node we want to replace
|
||||
guard let path = root.path(to: node) else {
|
||||
throw SplitError.viewNotFound
|
||||
}
|
||||
|
||||
// Replace the node
|
||||
let newRoot = try root.replaceNode(at: path, with: newNode)
|
||||
|
||||
// Update zoomed if it was the replaced node
|
||||
let newZoomed = (zoomed == node) ? newNode : zoomed
|
||||
|
||||
return .init(root: newRoot, zoomed: newZoomed)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SplitTree.Node
|
||||
|
|
@ -210,7 +228,7 @@ extension SplitTree.Node {
|
|||
}
|
||||
|
||||
/// Helper function to replace a node at the given path from the root
|
||||
private func replaceNode(at path: Path, with newNode: Self) throws -> Self {
|
||||
func replaceNode(at path: Path, with newNode: Self) throws -> Self {
|
||||
// If path is empty, replace the root
|
||||
if path.isEmpty {
|
||||
return newNode
|
||||
|
|
@ -293,6 +311,26 @@ extension SplitTree.Node {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Resize a split node to the specified ratio.
|
||||
/// For leaf nodes, this returns the node unchanged.
|
||||
/// For split nodes, this creates a new split with the updated ratio.
|
||||
func resize(to ratio: Double) -> Self {
|
||||
switch self {
|
||||
case .leaf:
|
||||
// Leaf nodes don't have a ratio to resize
|
||||
return self
|
||||
|
||||
case .split(let split):
|
||||
// Create a new split with the updated ratio
|
||||
return .split(.init(
|
||||
direction: split.direction,
|
||||
ratio: ratio,
|
||||
left: split.left,
|
||||
right: split.right
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: SplitTree.Node Protocols
|
||||
|
|
|
|||
|
|
@ -2,17 +2,21 @@ import SwiftUI
|
|||
|
||||
struct TerminalSplitTreeView: View {
|
||||
let tree: SplitTree
|
||||
let onResize: (SplitTree.Node, Double) -> Void
|
||||
|
||||
var body: some View {
|
||||
if let node = tree.root {
|
||||
TerminalSplitSubtreeView(node: node, isRoot: true)
|
||||
TerminalSplitSubtreeView(node: node, isRoot: true, onResize: onResize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TerminalSplitSubtreeView: View {
|
||||
@EnvironmentObject var ghostty: Ghostty.App
|
||||
|
||||
let node: SplitTree.Node
|
||||
var isRoot: Bool = false
|
||||
let onResize: (SplitTree.Node, Double) -> Void
|
||||
|
||||
var body: some View {
|
||||
switch (node) {
|
||||
|
|
@ -23,40 +27,28 @@ struct TerminalSplitSubtreeView: View {
|
|||
isSplit: !isRoot)
|
||||
|
||||
case .split(let split):
|
||||
TerminalSplitSplitView(split: split)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TerminalSplitSplitView: View {
|
||||
@EnvironmentObject var ghostty: Ghostty.App
|
||||
|
||||
let split: SplitTree.Node.Split
|
||||
|
||||
private var splitViewDirection: SplitViewDirection {
|
||||
switch (split.direction) {
|
||||
case .horizontal: .horizontal
|
||||
case .vertical: .vertical
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
SplitView(
|
||||
splitViewDirection,
|
||||
.init(get: {
|
||||
CGFloat(split.ratio)
|
||||
}, set: { _ in
|
||||
// TODO
|
||||
}),
|
||||
dividerColor: ghostty.config.splitDividerColor,
|
||||
resizeIncrements: .init(width: 1, height: 1),
|
||||
resizePublisher: .init(),
|
||||
left: {
|
||||
TerminalSplitSubtreeView(node: split.left)
|
||||
},
|
||||
right: {
|
||||
TerminalSplitSubtreeView(node: split.right)
|
||||
let splitViewDirection: SplitViewDirection = switch (split.direction) {
|
||||
case .horizontal: .horizontal
|
||||
case .vertical: .vertical
|
||||
}
|
||||
)
|
||||
|
||||
SplitView(
|
||||
splitViewDirection,
|
||||
.init(get: {
|
||||
CGFloat(split.ratio)
|
||||
}, set: {
|
||||
onResize(node, $0)
|
||||
}),
|
||||
dividerColor: ghostty.config.splitDividerColor,
|
||||
resizeIncrements: .init(width: 1, height: 1),
|
||||
resizePublisher: .init(),
|
||||
left: {
|
||||
TerminalSplitSubtreeView(node: split.left, onResize: onResize)
|
||||
},
|
||||
right: {
|
||||
TerminalSplitSubtreeView(node: split.right, onResize: onResize)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -366,11 +366,6 @@ class BaseTerminalController: NSWindowController,
|
|||
|
||||
// MARK: TerminalViewDelegate
|
||||
|
||||
// Note: this is different from surfaceDidTreeChange(from:,to:) because this is called
|
||||
// when the currently set value changed in place and the from:to: variant is called
|
||||
// when the variable was set.
|
||||
func surfaceTreeDidChange() {}
|
||||
|
||||
func focusedSurfaceDidChange(to: Ghostty.SurfaceView?) {
|
||||
let lastFocusedSurface = focusedSurface
|
||||
focusedSurface = to
|
||||
|
|
@ -420,6 +415,16 @@ class BaseTerminalController: NSWindowController,
|
|||
|
||||
func zoomStateDidChange(to: Bool) {}
|
||||
|
||||
func splitDidResize(node: SplitTree.Node, to newRatio: Double) {
|
||||
let resizedNode = node.resize(to: newRatio)
|
||||
do {
|
||||
surfaceTree2 = try surfaceTree2.replace(node: node, with: resizedNode)
|
||||
} catch {
|
||||
// TODO: log
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func performAction(_ action: String, on surfaceView: Ghostty.SurfaceView) {
|
||||
guard let surface = surfaceView.surface else { return }
|
||||
let len = action.utf8CString.count
|
||||
|
|
|
|||
|
|
@ -107,6 +107,10 @@ class TerminalController: BaseTerminalController {
|
|||
|
||||
override func surfaceTreeDidChange(from: Ghostty.SplitNode?, to: Ghostty.SplitNode?) {
|
||||
super.surfaceTreeDidChange(from: from, to: to)
|
||||
|
||||
// Whenever our surface tree changes in any way (new split, close split, etc.)
|
||||
// we want to invalidate our state.
|
||||
invalidateRestorableState()
|
||||
|
||||
// If our surface tree is now nil then we close our window.
|
||||
if (to == nil) {
|
||||
|
|
@ -696,12 +700,6 @@ class TerminalController: BaseTerminalController {
|
|||
}
|
||||
}
|
||||
|
||||
override func surfaceTreeDidChange() {
|
||||
// Whenever our surface tree changes in any way (new split, close split, etc.)
|
||||
// we want to invalidate our state.
|
||||
invalidateRestorableState()
|
||||
}
|
||||
|
||||
override func zoomStateDidChange(to: Bool) {
|
||||
guard let window = window as? TerminalWindow else { return }
|
||||
window.surfaceIsZoomed = to
|
||||
|
|
|
|||
|
|
@ -14,15 +14,14 @@ protocol TerminalViewDelegate: AnyObject {
|
|||
/// The cell size changed.
|
||||
func cellSizeDidChange(to: NSSize)
|
||||
|
||||
/// The surface tree did change in some way, i.e. a split was added, removed, etc. This is
|
||||
/// not called initially.
|
||||
func surfaceTreeDidChange()
|
||||
|
||||
/// This is called when a split is zoomed.
|
||||
func zoomStateDidChange(to: Bool)
|
||||
|
||||
/// Perform an action. At the time of writing this is only triggered by the command palette.
|
||||
func performAction(_ action: String, on: Ghostty.SurfaceView)
|
||||
|
||||
/// A split is resizing to a given value.
|
||||
func splitDidResize(node: SplitTree.Node, to newRatio: Double)
|
||||
}
|
||||
|
||||
/// The view model is a required implementation for TerminalView callers. This contains
|
||||
|
|
@ -81,7 +80,9 @@ struct TerminalView<ViewModel: TerminalViewModel>: View {
|
|||
DebugBuildWarningView()
|
||||
}
|
||||
|
||||
TerminalSplitTreeView(tree: viewModel.surfaceTree2)
|
||||
TerminalSplitTreeView(
|
||||
tree: viewModel.surfaceTree2,
|
||||
onResize: { delegate?.splitDidResize(node: $0, to: $1) })
|
||||
.environmentObject(ghostty)
|
||||
.focused($focused)
|
||||
.onAppear { self.focused = true }
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ class TerminalWindow: NSWindow {
|
|||
observe(\.surfaceIsZoomed, options: [.initial, .new]) { [weak self] window, _ in
|
||||
guard let tabGroup = self?.tabGroup else { return }
|
||||
|
||||
Ghostty.logger.warning("WOW \(window.surfaceIsZoomed)")
|
||||
self?.resetZoomTabButton.isHidden = !window.surfaceIsZoomed
|
||||
self?.updateResetZoomTitlebarButtonVisibility()
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue