]> git.r.bdr.sh - rbdr/captura/blobdiff - Captura/Presentation/Windows/RecordingWindow.swift
Use tinted PDF for Icon
[rbdr/captura] / Captura / Presentation / Windows / RecordingWindow.swift
index 7a64576acd161a97cfafbda235c0466c6edd073f..71e3a5577605ffd75735886b562b9f1518315ac9 100644 (file)
@@ -1,27 +1,42 @@
+/*
+ Copyright (C) 2024 Rubén Beltrán del Río
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see https://captura.tranquil.systems.
+ */
 import Cocoa
 import Combine
 
 class RecordingWindow: NSWindow {
-  
+
   var pixelDensity: CGFloat {
     self.screen?.backingScaleFactor ?? 1.0
   }
-  
+
   var recordingContentView: RecordingContentView {
     self.contentView as! RecordingContentView
   }
-  
+
   init(_ configuration: CaptureSessionConfiguration, _ button: NSRect?) {
-    
+
     let boundingBox = NSScreen.screenWithMouse?.frame ?? NSZeroRect
-    
+
     super.init(
       contentRect: boundingBox,
       styleMask: [.borderless],
       backing: .buffered,
       defer: false)
 
-
     self.isReleasedWhenClosed = false
     self.collectionBehavior = [.canJoinAllSpaces]
     self.isMovableByWindowBackground = false
@@ -30,7 +45,8 @@ class RecordingWindow: NSWindow {
     self.titlebarAppearsTransparent = true
     self.setFrame(boundingBox, display: true)
     self.titleVisibility = .hidden
-    let recordingView = RecordingContentView(configuration, frame: boundingBox, button: button ?? NSZeroRect)
+    let recordingView = RecordingContentView(
+      configuration, frame: boundingBox, button: button ?? NSZeroRect)
     recordingView.frame = boundingBox
     self.contentView = recordingView
     self.backgroundColor = NSColor(white: 1.0, alpha: 0.001)
@@ -40,30 +56,30 @@ class RecordingWindow: NSWindow {
     self.isOpaque = false
     self.hasShadow = false
   }
-  
+
   // MARK: - Window Behavior Overrides
-  
+
   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
   }
-  
+
   override func resignMain() {
     super.resignMain()
     if (self.contentView as? RecordingContentView)?.state != .recording {
       self.ignoresMouseEvents = false
     }
   }
-  
+
   override func becomeMain() {
     super.becomeMain()
     if (self.contentView as? RecordingContentView)?.state != .recording {
@@ -73,11 +89,11 @@ class RecordingWindow: NSWindow {
 }
 
 enum RecordingWindowState {
-  case passthrough, idle, drawing, moving, resizing, recording;
+  case passthrough, idle, drawing, moving, resizing, recording
 }
 
 class RecordingContentView: NSView {
-  
+
   init(_ configuration: CaptureSessionConfiguration, frame: NSRect, button: NSRect) {
     self.buttonSize = button.size
     var buttonOffset = NSPoint()
@@ -93,11 +109,15 @@ class RecordingContentView: NSView {
     preventResize = configuration.preventResize
     preventMove = configuration.preventMove
     autoStart = configuration.autoStart
-    
+
     self.bounds = frame
-    self.button = NSRect(x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width, height: buttonSize.height)
-    
-    if configuration.x != nil || configuration.y != nil || configuration.width != nil || configuration.height != nil {
+    self.button = NSRect(
+      x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width,
+      height: buttonSize.height)
+
+    if configuration.x != nil || configuration.y != nil || configuration.width != nil
+      || configuration.height != nil
+    {
       box = NSRect(
         x: configuration.x ?? Int(frame.width / 2.0),
         y: configuration.y ?? Int(frame.height / 2.0),
@@ -105,18 +125,18 @@ class RecordingContentView: NSView {
         height: configuration.height ?? 400
       )
     }
-    
+
     if autoStart {
       DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
         NotificationCenter.default.post(name: .startRecording, object: nil, userInfo: nil)
       }
     }
   }
-  
+
   required init?(coder: NSCoder) {
     fatalError("init(coder:) has not been implemented")
   }
-  
+
   private let buttonSize: NSSize
   private let buttonOffset: NSPoint
   public var button: NSRect? = nil
@@ -128,68 +148,71 @@ class RecordingContentView: NSView {
   private var preventResize = false
   private var preventMove = false
   private var autoStart = false
-  
+
   private var resizeBox: NSRect? {
     if let box {
       return NSRect(x: box.maxX - 5, y: box.minY - 5, width: 10, height: 10)
     }
     return nil
   }
-  
+
   private var shouldPassthrough: Bool {
     state == .recording || state == .passthrough
   }
-  
+
   // MARK: - State changing API
-  
+
   public func startRecording() {
     state = .recording
     window?.ignoresMouseEvents = true
   }
-  
+
   public func stopRecording() {
-    
+
   }
-  
+
   public func reset() {
     state = .idle
     window?.ignoresMouseEvents = false
   }
-  
+
   public func startPassthrough() {
     state = .passthrough
     window?.ignoresMouseEvents = true
   }
-  
+
   public func stopPassthrough() {
     state = .idle
     window?.ignoresMouseEvents = false
   }
-  
+
   // MARK: - View Behavior Overrides
-  
+
   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)
+    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 mouseExited(with event: NSEvent) {
     if state == .idle && box == nil {
       self.moveWindow()
     }
   }
-  
+
   override func mouseMoved(with event: NSEvent) {
-    
+
     self.mouseLocation = self.convert(event.locationInWindow, from: nil)
-    
+
     if shouldPassthrough {
       NSCursor.arrow.set()
     } else {
@@ -215,7 +238,7 @@ class RecordingContentView: NSView {
 
     self.setNeedsDisplay(self.bounds)
   }
-  
+
   override func mouseDragged(with event: NSEvent) {
     self.mouseLocation = self.convert(event.locationInWindow, from: nil)
     if state == .drawing {
@@ -226,7 +249,7 @@ class RecordingContentView: NSView {
         height: round(abs(mouseLocation.y - origin.y))
       )
     }
-    
+
     if box != nil {
       if state == .moving {
         NSCursor.closedHand.set()
@@ -234,7 +257,7 @@ class RecordingContentView: NSView {
           x: self.boxOrigin.x - self.origin.x + self.mouseLocation.x,
           y: self.boxOrigin.y - self.origin.y + self.mouseLocation.y)
       }
-      
+
       if state == .resizing {
         box = NSRect(
           x: round(min(origin.x, mouseLocation.x)),
@@ -246,7 +269,7 @@ class RecordingContentView: NSView {
     }
     self.setNeedsDisplay(self.bounds)
   }
-  
+
   override func cursorUpdate(with event: NSEvent) {
     NSCursor.crosshair.set()
   }
@@ -254,7 +277,7 @@ class RecordingContentView: NSView {
   override func hitTest(_ point: NSPoint) -> NSView? {
     return shouldPassthrough ? nil : self
   }
-      
+
   override var acceptsFirstResponder: Bool {
     return true
   }
@@ -262,14 +285,14 @@ class RecordingContentView: NSView {
   override func mouseDown(with event: NSEvent) {
     self.origin = self.convert(event.locationInWindow, from: nil)
     if let box {
-      
+
       if let button {
         if button.contains(origin) {
           NotificationCenter.default.post(name: .startRecording, object: nil, userInfo: nil)
           return
         }
       }
-      
+
       if resizeBox!.contains(origin) && !preventResize {
         self.origin = NSPoint(x: box.minX, y: box.maxY)
         state = .resizing
@@ -281,11 +304,11 @@ class RecordingContentView: NSView {
         return
       }
     }
-    
+
     if preventResize || preventMove {
       return
     }
-    
+
     state = .drawing
   }
 
@@ -294,16 +317,16 @@ class RecordingContentView: NSView {
       state = .idle
     }
   }
-  
+
   override func keyDown(with event: NSEvent) {
     switch event.keyCode {
-      case 53:  // Escape key
-        NotificationCenter.default.post(name: .reset, object: nil, userInfo: nil)
-      default:
-        super.keyDown(with: event)
+    case 53:  // Escape key
+      NotificationCenter.default.post(name: .reset, object: nil, userInfo: nil)
+    default:
+      super.keyDown(with: event)
     }
   }
-  
+
   override func flagsChanged(with event: NSEvent) {
     if state == .idle {
       if event.modifierFlags.contains(.shift) {
@@ -313,7 +336,7 @@ class RecordingContentView: NSView {
       }
     }
   }
-  
+
   override func draw(_ dirtyRect: NSRect) {
     if shouldPassthrough {
       NSColor.clear.setFill()
@@ -321,54 +344,54 @@ class RecordingContentView: NSView {
       NSColor(white: 1.0, alpha: 0.001).setFill()
     }
     dirtyRect.fill()
-    
+
     let dashLength: CGFloat = 5.0
     let lineWidth = 0.5
 
-// Uncomment below to debug button placement visually
-//    if let button {
-//      let buttonPath = NSBezierPath()
-//      buttonPath.move(to: NSPoint(x: button.minX, y: button.minY))
-//      buttonPath.line(to: NSPoint(x: button.maxX, y: button.minY))
-//      buttonPath.line(to: NSPoint(x: button.maxX, y: button.maxY))
-//      buttonPath.line(to: NSPoint(x: button.minX, y: button.maxY))
-//      buttonPath.line(to: NSPoint(x: button.minX, y: button.minY))
-//      NSColor(red: 1, green: 0, blue: 1, alpha: 1).setFill()
-//      buttonPath.fill()
-//    }
+    // Uncomment below to debug button placement visually
+    //    if let button {
+    //      let buttonPath = NSBezierPath()
+    //      buttonPath.move(to: NSPoint(x: button.minX, y: button.minY))
+    //      buttonPath.line(to: NSPoint(x: button.maxX, y: button.minY))
+    //      buttonPath.line(to: NSPoint(x: button.maxX, y: button.maxY))
+    //      buttonPath.line(to: NSPoint(x: button.minX, y: button.maxY))
+    //      buttonPath.line(to: NSPoint(x: button.minX, y: button.minY))
+    //      NSColor(red: 1, green: 0, blue: 1, alpha: 1).setFill()
+    //      buttonPath.fill()
+    //    }
 
     if state == .idle && 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
@@ -380,7 +403,7 @@ class RecordingContentView: NSView {
       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)
@@ -391,11 +414,11 @@ class RecordingContentView: NSView {
       whiteBox.line(to: NSPoint(x: box.minX, y: box.minY))
       NSColor.white.setStroke()
       whiteBox.stroke()
-      
+
       if state == .recording {
         return
       }
-      
+
       if let resizeBox {
         let clearBox = NSBezierPath()
         clearBox.move(to: NSPoint(x: resizeBox.minX, y: resizeBox.minY))
@@ -406,46 +429,53 @@ class RecordingContentView: NSView {
         NSColor(white: 0, alpha: 0.2).setFill()
         clearBox.fill()
       }
-      
+
       if state == .moving {
         let string = "\(Int(box.minX)), \(Int(box.maxY))" as NSString
-        drawText(string, NSPoint(
-          x: box.minX,
-          y: box.maxY
-        ), true)
+        drawText(
+          string,
+          NSPoint(
+            x: box.minX,
+            y: box.maxY
+          ), true)
       }
-      
+
       if state == .resizing {
         let string = "\(Int(mouseLocation.x)), \(Int(mouseLocation.y))" as NSString
         drawText(string, mouseLocation)
       }
-      
+
       if box.contains(mouseLocation) && state != .resizing {
-        return;
+        return
       }
     }
-    
+
     // Draw text
 
     let string = "\(Int(mouseLocation.x)), \(Int(mouseLocation.y))" as NSString
     drawText(string, mouseLocation)
   }
-  
+
   // MARK: - Utilities
-  
+
   private func drawText(_ text: NSString, _ location: NSPoint, _ isBottomRight: Bool = false) {
-    
+
     let textAttributes = [
-      NSAttributedString.Key.font: NSFont(name: "Hiragino Mincho ProN", size: 12) ?? NSFont.systemFont(ofSize: 12),
+      NSAttributedString.Key.font: NSFont(name: "Hiragino Mincho ProN", size: 12)
+        ?? NSFont.systemFont(ofSize: 12),
       NSAttributedString.Key.foregroundColor: NSColor.white,
     ]
     let offset = NSPoint(x: 10, y: 10)
     let padding = NSPoint(x: 5, y: 2)
     let size = text.size(withAttributes: textAttributes)
-    var rect = NSRect(x: location.x + offset.x, y: location.y + offset.y, width: size.width + 2 * padding.x, height: size.height + 2 * padding.y)
-    var textRect = NSRect(x: location.x + offset.x + padding.x, y: location.y + offset.y + padding.y, width: size.width, height: size.height)
-    
-    if (isBottomRight) {
+    var rect = NSRect(
+      x: location.x + offset.x, y: location.y + offset.y, width: size.width + 2 * padding.x,
+      height: size.height + 2 * padding.y)
+    var textRect = NSRect(
+      x: location.x + offset.x + padding.x, y: location.y + offset.y + padding.y, width: size.width,
+      height: size.height)
+
+    if isBottomRight {
       rect = rect.offsetBy(dx: -size.width - 2 * offset.x, dy: 0)
       textRect = textRect.offsetBy(dx: -size.width - 2 * offset.x, dy: 0)
     }
@@ -455,7 +485,7 @@ class RecordingContentView: NSView {
 
     text.draw(in: textRect, withAttributes: textAttributes)
   }
-  
+
   private func moveWindow() {
     let screen = NSScreen.screenWithMouse
     if let currentScreen = self.window?.screen {
@@ -464,10 +494,12 @@ class RecordingContentView: NSView {
         self.frame = CGRect(origin: NSZeroPoint, size: frame.size)
         self.bounds = CGRect(origin: NSZeroPoint, size: frame.size)
         self.updateTrackingAreas()
-        
+
         if let window = self.window {
           self.bounds = frame
-          self.button = NSRect(x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width, height: buttonSize.height)
+          self.button = NSRect(
+            x: frame.maxX - buttonOffset.x, y: frame.maxY - buttonOffset.y, width: buttonSize.width,
+            height: buttonSize.height)
           window.setFrame(frame, display: true, animate: false)
           window.makeKeyAndOrderFront(nil)
           window.orderFrontRegardless()