feat: show outline of windows hidden for privacy reasons (closes #5172)
This commit is contained in:
1 parent
b8ba0f0888
commit
1090acf3a5
2 files changed
+86
No files matched your search
@@ -198,6 +198,15 @@ extension NSImage {
|
||||
static func initCopy(_ name: String) -> NSImage {
|
||||
return NSImage(named: name)!.copy() as! NSImage
|
||||
}
|
||||
|
||||
func tinted(_ color: NSColor) -> NSImage {
|
||||
NSImage(size: size, flipped: false) { rect in
|
||||
color.set()
|
||||
rect.fill()
|
||||
self.draw(in: rect, from: NSRect(origin: .zero, size: self.size), operation: .destinationIn, fraction: 1.0)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CGImage {
|
||||
@@ -214,6 +223,23 @@ extension CGImage {
|
||||
func size() -> NSSize {
|
||||
return NSSize(width: width, height: height)
|
||||
}
|
||||
|
||||
func iFullyTransparent() -> Bool {
|
||||
guard ![.none, .noneSkipFirst, .noneSkipLast].contains(alphaInfo),
|
||||
let provider = dataProvider, let data = provider.data, let ptr = CFDataGetBytePtr(data)
|
||||
else { return false }
|
||||
// Assumes: kCGImageAlphaPremultipliedFirst | kCGImageByteOrder32Little
|
||||
// Layout: [B, G, R, A]
|
||||
let length = CFDataGetLength(data)
|
||||
var i = 3
|
||||
while i < length {
|
||||
if ptr[i] != 0 {
|
||||
return false
|
||||
}
|
||||
i += 4
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
extension CVPixelBuffer {
|
||||
|
||||
@@ -3,6 +3,9 @@ import Cocoa
|
||||
/// this is a lightweight view which displays an image using its CALayer
|
||||
/// it is an alternative to NSImageView, which doesn't have internal complexities and performance costs
|
||||
class LightImageView: NSView {
|
||||
private var outlineLayer: CAShapeLayer!
|
||||
private var handRaisedLayer: CALayer!
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("Class only supports programmatic initialization")
|
||||
}
|
||||
@@ -25,12 +28,67 @@ class LightImageView: NSView {
|
||||
"contentsScale": NSNull()
|
||||
]
|
||||
layerContentsRedrawPolicy = .never
|
||||
setupOutlineLayer()
|
||||
setupHandRaisedLayer()
|
||||
}
|
||||
|
||||
private func setupOutlineLayer() {
|
||||
outlineLayer = CAShapeLayer()
|
||||
outlineLayer.name = "outline"
|
||||
outlineLayer.fillColor = nil
|
||||
outlineLayer.lineWidth = 1.0
|
||||
outlineLayer.lineDashPattern = [4, 3]
|
||||
outlineLayer.lineCap = .round
|
||||
outlineLayer.lineJoin = .round
|
||||
outlineLayer.isHidden = true
|
||||
layer!.addSublayer(outlineLayer)
|
||||
}
|
||||
|
||||
private func setupHandRaisedLayer() {
|
||||
guard #available(macOS 11.0, *) else { return }
|
||||
handRaisedLayer = CALayer()
|
||||
handRaisedLayer.contentsGravity = .resizeAspect
|
||||
handRaisedLayer.isHidden = true
|
||||
layer!.addSublayer(handRaisedLayer)
|
||||
}
|
||||
|
||||
|
||||
private func updateOutlineLayer(_ fullyTransparent: Bool) {
|
||||
outlineLayer.isHidden = !fullyTransparent
|
||||
guard fullyTransparent else { return }
|
||||
let inset: CGFloat = 0.5
|
||||
let radius: CGFloat = 8.0
|
||||
let bounds = outlineLayer.superlayer!.bounds
|
||||
outlineLayer.frame = bounds
|
||||
let rect = bounds.insetBy(dx: inset, dy: inset)
|
||||
let path = CGPath(roundedRect: rect, cornerWidth: radius, cornerHeight: radius, transform: nil)
|
||||
outlineLayer.path = path
|
||||
outlineLayer.strokeColor = Appearance.fontColor.cgColor
|
||||
}
|
||||
|
||||
private func updateHandRaisedLayer(_ fullyTransparent: Bool) {
|
||||
guard #available(macOS 11.0, *) else { return }
|
||||
handRaisedLayer.isHidden = !fullyTransparent
|
||||
guard fullyTransparent else { return }
|
||||
// we want the icon to be large but not too large, and readable at small sizes
|
||||
let minSize = min(Appearance.iconSize, bounds.width * 0.9, bounds.height * 0.9)
|
||||
let maxSize = Appearance.iconSize * 3
|
||||
let ratio = min(bounds.width / ThumbnailView.maxThumbnailWidth(), bounds.height / ThumbnailView.maxThumbnailHeight())
|
||||
let pointSize = max(minSize, maxSize * ratio)
|
||||
let config = NSImage.SymbolConfiguration(pointSize: pointSize, weight: .regular)
|
||||
guard let image = NSImage(systemSymbolName: "hand.raised.fill", accessibilityDescription: nil)?.withSymbolConfiguration(config) else { return }
|
||||
handRaisedLayer.contents = image.tinted(Appearance.fontColor)
|
||||
handRaisedLayer.bounds = CGRect(x: 0, y: 0, width: pointSize, height: pointSize)
|
||||
handRaisedLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
}
|
||||
|
||||
|
||||
func updateContents(_ caLayerContents: CALayerContents, _ size: NSSize) {
|
||||
var fullyTransparent = false
|
||||
switch caLayerContents {
|
||||
case .cgImage(let image?):
|
||||
layer!.contents = image
|
||||
fullyTransparent = image.iFullyTransparent()
|
||||
case .pixelBuffer(let pixelBuffer?):
|
||||
layer!.contents = CVPixelBufferGetIOSurface(pixelBuffer)?.takeUnretainedValue()
|
||||
default: break
|
||||
@@ -38,6 +96,8 @@ class LightImageView: NSView {
|
||||
if frame.size != size {
|
||||
frame.size = size
|
||||
}
|
||||
updateOutlineLayer(fullyTransparent)
|
||||
updateHandRaisedLayer(fullyTransparent)
|
||||
}
|
||||
|
||||
func releaseImage() {
|
||||
|
||||
Reference in new issue
Block a user