From 5d8c9357c096b7e907437acdb8dd1efe1eab2b54 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 27 Dec 2025 14:26:55 -0800 Subject: [PATCH] macos: move around some functions --- .../Splits/TerminalSplitTreeView.swift | 151 +++++++++--------- 1 file changed, 75 insertions(+), 76 deletions(-) diff --git a/macos/Sources/Features/Splits/TerminalSplitTreeView.swift b/macos/Sources/Features/Splits/TerminalSplitTreeView.swift index 9a09ed743..bcb425908 100644 --- a/macos/Sources/Features/Splits/TerminalSplitTreeView.swift +++ b/macos/Sources/Features/Splits/TerminalSplitTreeView.swift @@ -4,7 +4,7 @@ import os struct TerminalSplitTreeView: View { let tree: SplitTree let onResize: (SplitTree.Node, Double) -> Void - let onDrop: (Ghostty.SurfaceView, TerminalSplitLeaf.DropZone) -> Void + let onDrop: (Ghostty.SurfaceView, TerminalSplitDropZone) -> Void var body: some View { if let node = tree.zoomed ?? tree.root { @@ -28,7 +28,7 @@ struct TerminalSplitSubtreeView: View { let node: SplitTree.Node var isRoot: Bool = false let onResize: (SplitTree.Node, Double) -> Void - let onDrop: (Ghostty.SurfaceView, TerminalSplitLeaf.DropZone) -> Void + let onDrop: (Ghostty.SurfaceView, TerminalSplitDropZone) -> Void var body: some View { switch (node) { @@ -68,7 +68,7 @@ struct TerminalSplitSubtreeView: View { struct TerminalSplitLeaf: View { let surfaceView: Ghostty.SurfaceView let isSplit: Bool - let onDrop: (Ghostty.SurfaceView, DropZone) -> Void + let onDrop: (Ghostty.SurfaceView, TerminalSplitDropZone) -> Void @State private var dropState: DropState = .idle @@ -93,7 +93,7 @@ struct TerminalSplitLeaf: View { .overlay { if case .dropping(let zone) = dropState { GeometryReader { geometry in - dropZoneOverlay(for: zone, in: geometry) + zone.overlay(in: geometry) } .allowsHitTesting(false) } @@ -102,11 +102,78 @@ struct TerminalSplitLeaf: View { .accessibilityLabel("Terminal pane") } - @ViewBuilder - private func dropZoneOverlay(for zone: DropZone, in geometry: GeometryProxy) -> some View { - let overlayColor = Color.accentColor.opacity(0.3) + private enum DropState: Equatable { + case idle + case dropping(TerminalSplitDropZone) + } + + private struct SplitDropDelegate: DropDelegate { + @Binding var dropState: DropState + let viewSize: CGSize + let onDrop: (TerminalSplitDropZone) -> Void - switch zone { + func validateDrop(info: DropInfo) -> Bool { + info.hasItemsConforming(to: [.ghosttySurfaceId]) + } + + func dropEntered(info: DropInfo) { + dropState = .dropping(.calculate(at: info.location, in: viewSize)) + } + + func dropUpdated(info: DropInfo) -> DropProposal? { + // For some reason dropUpdated is sent after performDrop is called + // and we don't want to reset our drop zone to show it so we have + // to guard on the state here. + guard case .dropping = dropState else { return DropProposal(operation: .forbidden) } + dropState = .dropping(.calculate(at: info.location, in: viewSize)) + return DropProposal(operation: .move) + } + + func dropExited(info: DropInfo) { + dropState = .idle + } + + func performDrop(info: DropInfo) -> Bool { + dropState = .idle + onDrop(.calculate(at: info.location, in: viewSize)) + return true + } + } +} + +enum TerminalSplitDropZone: String, Equatable { + case top + case bottom + case left + case right + + /// Determines which drop zone the cursor is in based on proximity to edges. + /// + /// Divides the view into four triangular regions by drawing diagonals from + /// corner to corner. The drop zone is determined by which edge the cursor + /// is closest to, creating natural triangular hit regions for each side. + static func calculate(at point: CGPoint, in size: CGSize) -> TerminalSplitDropZone { + let relX = point.x / size.width + let relY = point.y / size.height + + let distToLeft = relX + let distToRight = 1 - relX + let distToTop = relY + let distToBottom = 1 - relY + + let minDist = min(distToLeft, distToRight, distToTop, distToBottom) + + if minDist == distToLeft { return .left } + if minDist == distToRight { return .right } + if minDist == distToTop { return .top } + return .bottom + } + + @ViewBuilder + func overlay(in geometry: GeometryProxy) -> some View { + let overlayColor = Color.accentColor.opacity(0.3) + + switch self { case .top: VStack(spacing: 0) { Rectangle() @@ -137,72 +204,4 @@ struct TerminalSplitLeaf: View { } } } - - enum DropZone: String, Equatable { - case top - case bottom - case left - case right - } - - enum DropState: Equatable { - case idle - case dropping(DropZone) - } - - struct SplitDropDelegate: DropDelegate { - @Binding var dropState: DropState - let viewSize: CGSize - let onDrop: (DropZone) -> Void - - func validateDrop(info: DropInfo) -> Bool { - info.hasItemsConforming(to: [.ghosttySurfaceId]) - } - - func dropEntered(info: DropInfo) { - dropState = .dropping(calculateDropZone(at: info.location)) - } - - func dropUpdated(info: DropInfo) -> DropProposal? { - // For some reason dropUpdated is sent after performDrop is called - // and we don't want to reset our drop zone to show it so we have - // to guard on the state here. - guard case .dropping = dropState else { return DropProposal(operation: .forbidden) } - dropState = .dropping(calculateDropZone(at: info.location)) - return DropProposal(operation: .move) - } - - func dropExited(info: DropInfo) { - dropState = .idle - } - - func performDrop(info: DropInfo) -> Bool { - let zone = calculateDropZone(at: info.location) - dropState = .idle - onDrop(zone) - return true - } - - /// Determines which drop zone the cursor is in based on proximity to edges. - /// - /// Divides the view into four triangular regions by drawing diagonals from - /// corner to corner. The drop zone is determined by which edge the cursor - /// is closest to, creating natural triangular hit regions for each side. - private func calculateDropZone(at point: CGPoint) -> DropZone { - let relX = point.x / viewSize.width - let relY = point.y / viewSize.height - - let distToLeft = relX - let distToRight = 1 - relX - let distToTop = relY - let distToBottom = 1 - relY - - let minDist = min(distToLeft, distToRight, distToTop, distToBottom) - - if minDist == distToLeft { return .left } - if minDist == distToRight { return .right } - if minDist == distToTop { return .top } - return .bottom - } - } }