import Cocoa class RecordingWindow: NSWindow { init() { let screens = NSScreen.screens var boundingBox = NSZeroRect for screen in screens { boundingBox = NSUnionRect(boundingBox, screen.frame) } super.init( contentRect: boundingBox, styleMask: [.borderless], backing: .buffered, defer: false) self.center() self.isMovableByWindowBackground = false self.isMovable = false self.titlebarAppearsTransparent = true self.setFrame(boundingBox, display: true) self.titleVisibility = .hidden self.contentView = RecordingContentView() self.backgroundColor = NSColor(white: 1.0, alpha: 0.001) self.level = .screenSaver self.isOpaque = false self.hasShadow = false self.makeKeyAndOrderFront(nil) self.makeFirstResponder(nil) } override func resetCursorRects() { super.resetCursorRects() let cursor = NSCursor.crosshair self.contentView?.addCursorRect(self.contentView!.bounds, cursor: cursor) } override var canBecomeKey: Bool { return true } override var canBecomeMain: Bool { return true } } class RecordingContentView: NSView { var isDrawing = false var isMoving = false var isResizing = false var box: NSRect? = nil var mouseLocation: NSPoint = NSPoint() var origin: NSPoint = NSPoint() var boxOrigin: NSPoint = NSPoint() var resizeBox: NSRect? { if let box { return NSRect(x: box.maxX - 5, y: box.minY - 5, width: 10, height: 10) } return nil } override func updateTrackingAreas() { super.updateTrackingAreas() for trackingArea in self.trackingAreas { self.removeTrackingArea(trackingArea) } let options: NSTrackingArea.Options = [.mouseEnteredAndExited, .activeInKeyWindow, .cursorUpdate, .mouseMoved] let trackingArea = NSTrackingArea(rect: self.bounds, options: options, owner: self, userInfo: nil) self.addTrackingArea(trackingArea) } override func mouseMoved(with event: NSEvent) { self.mouseLocation = self.convert(event.locationInWindow, from: nil) if let box { if resizeBox!.contains(mouseLocation) { NSCursor.arrow.set() } else { if box.contains(mouseLocation) { NSCursor.openHand.set() } else { NSCursor.crosshair.set() } } } else { NSCursor.crosshair.set() } self.setNeedsDisplay(self.bounds) } override func mouseDragged(with event: NSEvent) { self.mouseLocation = self.convert(event.locationInWindow, from: nil) if isDrawing { box = NSRect( x: round(min(origin.x, mouseLocation.x)), y: round(min(origin.y, mouseLocation.y)), width: round(abs(mouseLocation.x - origin.x)), height: round(abs(mouseLocation.y - origin.y)) ) } if isMoving && box != nil { NSCursor.closedHand.set() box!.origin = NSPoint( x: self.boxOrigin.x - self.origin.x + self.mouseLocation.x, y: self.boxOrigin.y - self.origin.y + self.mouseLocation.y) } self.setNeedsDisplay(self.bounds) } override func cursorUpdate(with event: NSEvent) { NSCursor.crosshair.set() } override func hitTest(_ point: NSPoint) -> NSView? { return self } override var acceptsFirstResponder: Bool { return true } override func mouseDown(with event: NSEvent) { self.origin = self.convert(event.locationInWindow, from: nil) if let box { if box.contains(origin) { isMoving = true self.boxOrigin = NSPoint(x: box.origin.x, y: box.origin.y) return } } isDrawing = true } override func mouseUp(with event: NSEvent) { isDrawing = false isMoving = false } override func draw(_ dirtyRect: NSRect) { NSColor(white: 1.0, alpha: 0.001).setFill() dirtyRect.fill() let dashLength: CGFloat = 5.0 let lineWidth = 0.5 if !isDrawing && box == nil { let blackLine = NSBezierPath() blackLine.lineWidth = lineWidth blackLine.setLineDash([dashLength, dashLength], count: 2, phase: 0) // Vertical line (Black) blackLine.move(to: NSPoint(x: self.mouseLocation.x, y: NSMinY(self.bounds))) blackLine.line(to: NSPoint(x: self.mouseLocation.x, y: NSMaxY(self.bounds))) // Horizontal line (Black) blackLine.move(to: NSPoint(x: NSMinX(self.bounds), y: self.mouseLocation.y)) blackLine.line(to: NSPoint(x: NSMaxX(self.bounds), y: self.mouseLocation.y)) NSColor.black.setStroke() blackLine.stroke() let whiteLine = NSBezierPath() whiteLine.lineWidth = lineWidth whiteLine.setLineDash([dashLength, dashLength], count: 2, phase: dashLength) // Vertical line (White) whiteLine.move(to: NSPoint(x: self.mouseLocation.x, y: NSMinY(self.bounds))) whiteLine.line(to: NSPoint(x: self.mouseLocation.x, y: NSMaxY(self.bounds))) // Horizontal line (White) whiteLine.move(to: NSPoint(x: NSMinX(self.bounds), y: self.mouseLocation.y)) whiteLine.line(to: NSPoint(x: NSMaxX(self.bounds), y: self.mouseLocation.y)) NSColor.white.setStroke() whiteLine.stroke() } if let box { let blackBox = NSBezierPath() blackBox.lineWidth = lineWidth blackBox.setLineDash([dashLength, dashLength], count: 2, phase: 0) blackBox.move(to: NSPoint(x: box.minX, y: box.minY)) blackBox.line(to: NSPoint(x: box.maxX, y: box.minY)) blackBox.line(to: NSPoint(x: box.maxX, y: box.maxY)) blackBox.line(to: NSPoint(x: box.minX, y: box.maxY)) blackBox.line(to: NSPoint(x: box.minX, y: box.minY)) NSColor.black.setStroke() blackBox.stroke() let whiteBox = NSBezierPath() whiteBox.lineWidth = lineWidth whiteBox.setLineDash([dashLength, dashLength], count: 2, phase: dashLength) whiteBox.move(to: NSPoint(x: box.minX, y: box.minY)) whiteBox.line(to: NSPoint(x: box.maxX, y: box.minY)) whiteBox.line(to: NSPoint(x: box.maxX, y: box.maxY)) whiteBox.line(to: NSPoint(x: box.minX, y: box.maxY)) whiteBox.line(to: NSPoint(x: box.minX, y: box.minY)) NSColor.white.setStroke() whiteBox.stroke() if let resizeBox { let clearBox = NSBezierPath() clearBox.move(to: NSPoint(x: resizeBox.minX, y: resizeBox.minY)) clearBox.line(to: NSPoint(x: resizeBox.maxX, y: resizeBox.minY)) clearBox.line(to: NSPoint(x: resizeBox.maxX, y: resizeBox.maxY)) clearBox.line(to: NSPoint(x: resizeBox.minX, y: resizeBox.maxY)) clearBox.line(to: NSPoint(x: resizeBox.minX, y: resizeBox.minY)) NSColor(white: 0, alpha: 0.2).setFill() clearBox.fill() } if box.contains(mouseLocation) && !isResizing { return; } } // Draw text let offset = NSPoint(x: 10, y: 10) let padding = NSPoint(x: 5, y: 2) let textAttributes = [ NSAttributedString.Key.font: NSFont(name: "Hiragino Mincho ProN", size: 12) ?? NSFont.systemFont(ofSize: 12), NSAttributedString.Key.foregroundColor: NSColor.white, ] let string = "\(Int(mouseLocation.x)), \(Int(mouseLocation.y))" as NSString let size = string.size(withAttributes: textAttributes) let rect = NSRect(x: mouseLocation.x + offset.x, y: mouseLocation.y + offset.y, width: size.width + 2 * padding.x, height: size.height + 2 * padding.y) let textRect = NSRect(x: mouseLocation.x + offset.x + padding.x, y: mouseLocation.y + offset.y + padding.y, width: size.width, height: size.height) NSColor.black.set() rect.fill() string.draw(in: textRect, withAttributes: textAttributes) } }