Add Kapture: menu-bar screenshot OCR app with Cmd+Shift+S hotkey
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
import AppKit
|
||||
|
||||
final class OverlayWindow: NSPanel {
|
||||
init(screen: NSScreen, onComplete: @escaping (NSRect) -> Void, onCancel: @escaping () -> Void) {
|
||||
let content = OverlayView(onComplete: onComplete, onCancel: onCancel)
|
||||
super.init(
|
||||
contentRect: screen.frame,
|
||||
styleMask: [.borderless, .nonactivatingPanel],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
contentView = content
|
||||
level = .screenSaver
|
||||
collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary, .transient]
|
||||
backgroundColor = .clear
|
||||
isOpaque = false
|
||||
hasShadow = false
|
||||
isReleasedWhenClosed = false
|
||||
acceptsMouseMovedEvents = true
|
||||
}
|
||||
|
||||
override var canBecomeKey: Bool { true }
|
||||
}
|
||||
|
||||
final class OverlayView: NSView {
|
||||
private let onComplete: (NSRect) -> Void
|
||||
private let onCancel: () -> Void
|
||||
|
||||
private var startPoint: NSPoint?
|
||||
private(set) var selection: NSRect?
|
||||
|
||||
init(onComplete: @escaping (NSRect) -> Void, onCancel: @escaping () -> Void) {
|
||||
self.onComplete = onComplete
|
||||
self.onCancel = onCancel
|
||||
super.init(frame: .zero)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override var acceptsFirstResponder: Bool { true }
|
||||
|
||||
override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true }
|
||||
|
||||
override func resetCursorRects() {
|
||||
addCursorRect(bounds, cursor: .crosshair)
|
||||
}
|
||||
|
||||
override func mouseDown(with event: NSEvent) {
|
||||
let p = convert(event.locationInWindow, from: nil)
|
||||
startPoint = p
|
||||
selection = NSRect(origin: p, size: .zero)
|
||||
needsDisplay = true
|
||||
}
|
||||
|
||||
override func mouseDragged(with event: NSEvent) {
|
||||
guard let start = startPoint else { return }
|
||||
let p = convert(event.locationInWindow, from: nil)
|
||||
selection = NSRect(
|
||||
x: min(start.x, p.x),
|
||||
y: min(start.y, p.y),
|
||||
width: abs(p.x - start.x),
|
||||
height: abs(p.y - start.y)
|
||||
)
|
||||
needsDisplay = true
|
||||
}
|
||||
|
||||
override func mouseUp(with event: NSEvent) {
|
||||
guard let sel = selection else { return }
|
||||
if sel.width >= 2, sel.height >= 2 {
|
||||
onComplete(window!.convertToScreen(sel))
|
||||
} else {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
override func keyDown(with event: NSEvent) {
|
||||
if event.keyCode == 53 {
|
||||
onCancel()
|
||||
} else {
|
||||
super.keyDown(with: event)
|
||||
}
|
||||
}
|
||||
|
||||
override func draw(_ dirtyRect: NSRect) {
|
||||
NSColor.black.withAlphaComponent(0.35).setFill()
|
||||
NSBezierPath(rect: bounds).fill()
|
||||
|
||||
guard let sel = selection, sel.width > 0, sel.height > 0 else { return }
|
||||
|
||||
NSGraphicsContext.current?.saveGraphicsState()
|
||||
NSGraphicsContext.current?.compositingOperation = .clear
|
||||
NSColor.black.setFill()
|
||||
NSBezierPath(rect: sel).fill()
|
||||
NSGraphicsContext.current?.restoreGraphicsState()
|
||||
|
||||
let border = NSBezierPath(rect: sel)
|
||||
border.lineWidth = 1.5
|
||||
NSColor.systemOrange.setStroke()
|
||||
border.stroke()
|
||||
|
||||
let label = "\(Int(sel.width)) × \(Int(sel.height))"
|
||||
let attrs: [NSAttributedString.Key: Any] = [
|
||||
.font: NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium),
|
||||
.foregroundColor: NSColor.white,
|
||||
]
|
||||
let labelSize = label.size(withAttributes: attrs)
|
||||
let labelRect = NSRect(
|
||||
x: sel.maxX - labelSize.width - 8,
|
||||
y: sel.minY - labelSize.height - 10,
|
||||
width: labelSize.width + 8,
|
||||
height: labelSize.height + 6
|
||||
)
|
||||
NSColor.black.withAlphaComponent(0.6).setFill()
|
||||
NSBezierPath(roundedRect: labelRect, xRadius: 4, yRadius: 4).fill()
|
||||
label.draw(
|
||||
at: NSPoint(x: labelRect.minX + 4, y: labelRect.minY + 3),
|
||||
withAttributes: attrs
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user