| 1 | #if os(macOS) |
| 2 | import AppKit |
| 3 | import SwiftUI |
| 4 | |
| 5 | struct SheetResizeHandle: NSViewRepresentable { |
| 6 | let vertical: Bool |
| 7 | let canBegin: () -> Bool |
| 8 | let change: (Double) -> Void |
| 9 | let finish: () -> Void |
| 10 | let cancel: () -> Void |
| 11 | var fit: (() -> Void)? = nil |
| 12 | func makeNSView(context: Context) -> Handle { Handle() } |
| 13 | func updateNSView(_ view: Handle, context: Context) { view.actions = self } |
| 14 | final class Handle: NSControl { |
| 15 | override func draw(_ dirtyRect: NSRect) { |
| 16 | NSColor.secondaryLabelColor.withAlphaComponent(0.15).setFill() |
| 17 | // Keep the 8-point mouse target, but paint only the grid boundary. |
| 18 | let pixel = 1 / (window?.backingScaleFactor ?? 2) |
| 19 | let rule = actions?.vertical == true |
| 20 | ? NSRect(x: bounds.minX, y: bounds.minY, width: bounds.width, height: pixel) |
| 21 | : NSRect(x: bounds.maxX - pixel, y: bounds.minY, width: pixel, height: bounds.height) |
| 22 | rule.fill() |
| 23 | } |
| 24 | var actions: SheetResizeHandle? |
| 25 | private var start: NSPoint? |
| 26 | private var moved = false |
| 27 | private weak var previousResponder: NSResponder? |
| 28 | override var acceptsFirstResponder: Bool { true } |
| 29 | override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } |
| 30 | override func resetCursorRects() { |
| 31 | addCursorRect(bounds, cursor: actions?.vertical == true ? .resizeUpDown : .resizeLeftRight) |
| 32 | } |
| 33 | override func mouseDown(with event: NSEvent) { |
| 34 | guard actions?.canBegin() == true else { return } |
| 35 | if event.clickCount == 2, let fit = actions?.fit { fit(); return } |
| 36 | previousResponder = window?.firstResponder |
| 37 | window?.makeFirstResponder(self) |
| 38 | start = event.locationInWindow; moved = false |
| 39 | } |
| 40 | override func mouseDragged(with event: NSEvent) { |
| 41 | guard let start else { return } |
| 42 | let amount = actions?.vertical == true ? start.y - event.locationInWindow.y : event.locationInWindow.x - start.x |
| 43 | guard moved || abs(amount) >= 2 else { return } |
| 44 | moved = true; actions?.change(amount) |
| 45 | } |
| 46 | override func mouseUp(with event: NSEvent) { |
| 47 | guard start != nil else { return } |
| 48 | if moved { actions?.finish() } |
| 49 | reset() |
| 50 | } |
| 51 | override func keyDown(with event: NSEvent) { |
| 52 | if event.keyCode == 53 { actions?.cancel(); reset() } else { super.keyDown(with: event) } |
| 53 | } |
| 54 | override func viewWillMove(toWindow newWindow: NSWindow?) { |
| 55 | if newWindow == nil, start != nil { actions?.cancel(); reset() } |
| 56 | super.viewWillMove(toWindow: newWindow) |
| 57 | } |
| 58 | private func reset() { |
| 59 | start = nil; moved = false |
| 60 | if window?.firstResponder === self { window?.makeFirstResponder(previousResponder) } |
| 61 | previousResponder = nil |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | #endif |
| 66 | |