ghostty/macos/Sources/Features/Splits/TerminalSplitTreeView.swift
Mitchell Hashimoto e4cd90b8a0 macos: set explicit identity for split tree view based on structure
Fixes #7546

SwiftUI uses type and structure to identify views, which can lead
to issues with tree like structures where the shape and type is the same
but the content changes. This was causing #7546.

To fix this, we need to add explicit identity to the split tree view
so that SwiftUI can differentiate when it needs to redraw the view.

We don't want to blindly add Hashable to SplitTree because we don't want
to take into account all the fields. Instead, we add an explicit
"structural identity" to the SplitTreeView that can be used by SwiftUI.
2025-06-08 20:11:58 -07:00

61 lines
2.0 KiB
Swift

import SwiftUI
struct TerminalSplitTreeView: View {
let tree: SplitTree<Ghostty.SurfaceView>
let onResize: (SplitTree<Ghostty.SurfaceView>.Node, Double) -> Void
var body: some View {
if let node = tree.zoomed ?? tree.root {
TerminalSplitSubtreeView(
node: node,
isRoot: node == tree.root,
onResize: onResize)
// This is necessary because we can't rely on SwiftUI's implicit
// structural identity to detect changes to this view. Due to
// the tree structure of splits it could result in bad beaviors.
// See: https://github.com/ghostty-org/ghostty/issues/7546
.id(node.structuralIdentity)
}
}
}
struct TerminalSplitSubtreeView: View {
@EnvironmentObject var ghostty: Ghostty.App
let node: SplitTree<Ghostty.SurfaceView>.Node
var isRoot: Bool = false
let onResize: (SplitTree<Ghostty.SurfaceView>.Node, Double) -> Void
var body: some View {
switch (node) {
case .leaf(let leafView):
Ghostty.InspectableSurface(
surfaceView: leafView,
isSplit: !isRoot)
case .split(let split):
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),
left: {
TerminalSplitSubtreeView(node: split.left, onResize: onResize)
},
right: {
TerminalSplitSubtreeView(node: split.right, onResize: onResize)
}
)
}
}
}