e0bff88225
The tree kept rendering as a ~628pt column centred in a wide window. That number was exactly the sum of the row's column minimums, which gave it away: VSplitView proposes its children their *ideal* width, so the List settled at the narrowest size its content allowed instead of filling. SplitPane hands each pane geo.size.width explicitly, removing the ambiguity, and its divider is draggable with the position remembered across launches. With the width no longer ambiguous the two name columns share it evenly, so each side gets half the window; their floor dropped to 120pt since a large minimum is what becomes the ideal width in the first place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UfhDYRLTpGJSKGP1m3LVJN
63 lines
2.1 KiB
Swift
63 lines
2.1 KiB
Swift
import SwiftUI
|
|
|
|
/// A vertical split with an explicit width for both panes.
|
|
///
|
|
/// `VSplitView` proposes its children their *ideal* width, so a `List` inside it
|
|
/// settles at the sum of its columns' minimums and sits centred with empty space
|
|
/// on both sides. Handing each pane `geo.size.width` removes the ambiguity, and
|
|
/// the divider stays draggable.
|
|
struct SplitPane<Top: View, Bottom: View>: View {
|
|
@Binding var fraction: CGFloat
|
|
let minimumFraction: CGFloat
|
|
@ViewBuilder var top: () -> Top
|
|
@ViewBuilder var bottom: () -> Bottom
|
|
|
|
private let dividerHeight: CGFloat = 6
|
|
|
|
init(fraction: Binding<CGFloat>,
|
|
minimumFraction: CGFloat = 0.15,
|
|
@ViewBuilder top: @escaping () -> Top,
|
|
@ViewBuilder bottom: @escaping () -> Bottom) {
|
|
self._fraction = fraction
|
|
self.minimumFraction = minimumFraction
|
|
self.top = top
|
|
self.bottom = bottom
|
|
}
|
|
|
|
var body: some View {
|
|
GeometryReader { geo in
|
|
let available = max(geo.size.height - dividerHeight, 1)
|
|
let topHeight = (available * fraction).rounded()
|
|
|
|
VStack(spacing: 0) {
|
|
top()
|
|
.frame(width: geo.size.width, height: topHeight)
|
|
divider(totalHeight: available)
|
|
bottom()
|
|
.frame(width: geo.size.width, height: available - topHeight)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func divider(totalHeight: CGFloat) -> some View {
|
|
ZStack {
|
|
Rectangle().fill(Color(nsColor: .separatorColor))
|
|
.frame(height: 1)
|
|
Rectangle().fill(Color.clear)
|
|
.frame(height: dividerHeight)
|
|
.contentShape(Rectangle())
|
|
}
|
|
.frame(height: dividerHeight)
|
|
.onHover { inside in
|
|
if inside { NSCursor.resizeUpDown.push() } else { NSCursor.pop() }
|
|
}
|
|
.gesture(
|
|
DragGesture()
|
|
.onChanged { value in
|
|
let delta = value.translation.height / totalHeight
|
|
fraction = min(max(fraction + delta * 0.35, minimumFraction), 1 - minimumFraction)
|
|
}
|
|
)
|
|
}
|
|
}
|