native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203import AppKitimport SwiftUI
struct SplitView<L: View, R: View>: View { let direction: Direction let dividerColor: Color let resizeIncrements: CGSize let left: L let right: R let onEqualize: () -> Void let minSize: CGFloat = 10 @Binding var split: CGFloat static var defaultVisibleSize: CGFloat { 1 } // Visible thickness of the divider bar. The invisible hitbox stays constant // so resize ergonomics don't depend on the visible thickness. let splitterVisibleSize: CGFloat private let splitterInvisibleSize: CGFloat = 6
var body: some View { GeometryReader { geo in let leftRect = leftRect(for: geo.size) let rightRect = rightRect(for: geo.size, leftRect: leftRect) let splitterPoint = splitterPoint(for: geo.size, leftRect: leftRect)
ZStack(alignment: .topLeading) { left .frame(width: leftRect.size.width, height: leftRect.size.height) .offset(x: leftRect.origin.x, y: leftRect.origin.y) right .frame(width: rightRect.size.width, height: rightRect.size.height) .offset(x: rightRect.origin.x, y: rightRect.origin.y) SplitDivider( direction: direction, visibleSize: splitterVisibleSize, invisibleSize: splitterInvisibleSize, color: dividerColor ) .position(splitterPoint) .gesture(dragGesture(geo.size)) .onTapGesture(count: 2) { onEqualize() } } } }
init( _ direction: Direction, _ split: Binding<CGFloat>, dividerColor: Color, dividerVisibleSize: CGFloat = Self.defaultVisibleSize, resizeIncrements: CGSize = .init(width: 1, height: 1), @ViewBuilder left: (() -> L), @ViewBuilder right: (() -> R), onEqualize: @escaping () -> Void ) { self.direction = direction self._split = split self.dividerColor = dividerColor self.splitterVisibleSize = dividerVisibleSize self.resizeIncrements = resizeIncrements self.left = left() self.right = right() self.onEqualize = onEqualize }
private func dragGesture(_ size: CGSize) -> some Gesture { DragGesture() .onChanged { gesture in switch direction { case .horizontal: let new = min(max(minSize, gesture.location.x), size.width - minSize) split = new / size.width case .vertical: let new = min(max(minSize, gesture.location.y), size.height - minSize) split = new / size.height } } }
private func leftRect(for size: CGSize) -> CGRect { var result = CGRect(x: 0, y: 0, width: size.width, height: size.height) switch direction { case .horizontal: result.size.width *= split result.size.width -= splitterVisibleSize / 2 result.size.width -= result.size.width.truncatingRemainder(dividingBy: resizeIncrements.width) case .vertical: result.size.height *= split result.size.height -= splitterVisibleSize / 2 result.size.height -= result.size.height.truncatingRemainder( dividingBy: resizeIncrements.height) } return result }
private func rightRect(for size: CGSize, leftRect: CGRect) -> CGRect { var result = CGRect(x: 0, y: 0, width: size.width, height: size.height) switch direction { case .horizontal: result.origin.x += leftRect.size.width result.origin.x += splitterVisibleSize / 2 result.size.width -= result.origin.x case .vertical: result.origin.y += leftRect.size.height result.origin.y += splitterVisibleSize / 2 result.size.height -= result.origin.y } return result }
private func splitterPoint(for size: CGSize, leftRect: CGRect) -> CGPoint { switch direction { case .horizontal: return CGPoint(x: leftRect.size.width, y: size.height / 2) case .vertical: return CGPoint(x: size.width / 2, y: leftRect.size.height) } }
enum Direction: Codable { case horizontal case vertical }
private struct SplitDivider: View { let direction: Direction let visibleSize: CGFloat let invisibleSize: CGFloat let color: Color @State private var isHovered = false var body: some View { ZStack { Rectangle() .fill(color) .frame(width: visibleWidth, height: visibleHeight) } .frame(width: hitboxWidth, height: hitboxHeight) .contentShape(.rect) .onHover { hovering in guard hovering != isHovered else { return } isHovered = hovering if hovering { hoverCursor.push() } else { NSCursor.pop() } } .onDisappear { if isHovered { isHovered = false NSCursor.pop() } } }
private var hoverCursor: NSCursor { switch direction { case .horizontal: return .resizeLeftRight case .vertical: return .resizeUpDown } }
private var visibleWidth: CGFloat? { switch direction { case .horizontal: return visibleSize case .vertical: return nil } }
private var visibleHeight: CGFloat? { switch direction { case .horizontal: return nil case .vertical: return visibleSize } }
private var hitboxWidth: CGFloat? { switch direction { case .horizontal: return visibleSize + invisibleSize case .vertical: return nil } }
private var hitboxHeight: CGFloat? { switch direction { case .horizontal: return nil case .vertical: return visibleSize + invisibleSize } } }}