Files
shotscreen/ShotScreen/Sources/ButtonHoverExtension.swift
Nick Roodenrijs 0dabed11d2 🎉 ShotScreen v1.0 - Initial Release
🚀 First official release of ShotScreen with complete feature set:

 Core Features:
- Advanced screenshot capture system
- Multi-monitor support
- Professional UI/UX design
- Automated update system with Sparkle
- Apple notarized & code signed

🛠 Technical Excellence:
- Native Swift macOS application
- Professional build & deployment pipeline
- Comprehensive error handling
- Memory optimized performance

📦 Distribution Ready:
- Professional DMG packaging
- Apple notarization complete
- No security warnings for users
- Ready for public distribution

This is the foundation release that establishes ShotScreen as a premium screenshot tool for macOS.
2025-06-28 16:15:15 +02:00

236 lines
8.7 KiB
Swift

import AppKit
// Extension voor NSButton hover effecten
extension NSButton {
private struct AssociatedKeys {
static var hoverEffectScale: UInt8 = 0
static var originalTransform: UInt8 = 1
static var originalAnchorPoint: UInt8 = 2
static var originalPosition: UInt8 = 3
static var trackingArea: UInt8 = 4
}
private var hoverEffectScale: CGFloat? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.hoverEffectScale) as? CGFloat
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.hoverEffectScale, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var originalTransform: CATransform3D? {
get {
guard let value = objc_getAssociatedObject(self, &AssociatedKeys.originalTransform) as? NSValue else { return nil }
var transform = CATransform3DIdentity
value.getValue(&transform)
return transform
}
set {
var valueToSet: NSValue? = nil
if let transform = newValue {
valueToSet = NSValue(caTransform3D: transform)
}
objc_setAssociatedObject(self, &AssociatedKeys.originalTransform, valueToSet, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var originalAnchorPointForHover: CGPoint? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.originalAnchorPoint) as? CGPoint
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.originalAnchorPoint, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var originalPositionForHover: CGPoint? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.originalPosition) as? CGPoint
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.originalPosition, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private var hoverTrackingArea: NSTrackingArea? {
get {
objc_getAssociatedObject(self, &AssociatedKeys.trackingArea) as? NSTrackingArea
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.trackingArea, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
func addHoverEffect(scale: CGFloat = 1.2) {
self.wantsLayer = true
self.hoverEffectScale = scale
if self.originalTransform == nil, let layer = self.layer {
self.originalTransform = layer.transform
}
if self.window != nil {
self.updateTrackingAreas()
}
}
override open func mouseEntered(with event: NSEvent) {
super.mouseEntered(with: event)
// Check if this button uses the color change effect (our new simple hover)
if objc_getAssociatedObject(self, "useZoomColorEffect") != nil {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
context.allowsImplicitAnimation = true
self.animator().contentTintColor = NSColor.white // Bright white on hover
})
return
}
// Check if this button uses the new hover effect
if objc_getAssociatedObject(self, "useNewHoverEffect") != nil {
// Use new hover effect with zoom and color change
if let hoverHandler = objc_getAssociatedObject(self, "hoverHandler") as? ButtonHoverHandler {
hoverHandler.mouseEntered(with: event)
}
return
}
// ONLY process buttons that explicitly have hover effects enabled (old zoom system)
guard let scale = self.hoverEffectScale else {
return
}
guard let layer = self.layer else {
return
}
// Extra safety: Check if window is still valid
guard self.window != nil else {
return
}
if self.originalTransform == nil {
self.originalTransform = layer.transform
}
if self.originalAnchorPointForHover == nil {
self.originalAnchorPointForHover = layer.anchorPoint
}
if self.originalPositionForHover == nil {
self.originalPositionForHover = layer.position
}
layer.anchorPoint = CGPoint(x: 0.5, y: 0.5)
if let oPos = self.originalPositionForHover, let oAP = self.originalAnchorPointForHover {
layer.position = CGPoint(x: oPos.x + (layer.anchorPoint.x - oAP.x) * layer.bounds.width,
y: oPos.y + (layer.anchorPoint.y - oAP.y) * layer.bounds.height)
}
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.2
context.allowsImplicitAnimation = true
layer.transform = CATransform3DScale(self.originalTransform ?? CATransform3DIdentity, scale, scale, 1)
}, completionHandler: nil)
}
override open func mouseExited(with event: NSEvent) {
super.mouseExited(with: event)
// Check if this button uses the color change effect (our new simple hover)
if objc_getAssociatedObject(self, "useZoomColorEffect") != nil {
let originalColor = objc_getAssociatedObject(self, "originalColor") as? NSColor ?? NSColor(white: 0.8, alpha: 1.0)
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.25
context.allowsImplicitAnimation = true
self.animator().contentTintColor = originalColor // Restore original color
})
return
}
// Check if this button uses the new hover effect
if objc_getAssociatedObject(self, "useNewHoverEffect") != nil {
// Use new hover effect with zoom and color change
if let hoverHandler = objc_getAssociatedObject(self, "hoverHandler") as? ButtonHoverHandler {
hoverHandler.mouseExited(with: event)
}
return
}
// ONLY process buttons that explicitly have hover effects enabled (old zoom system)
guard self.hoverEffectScale != nil else {
return
}
guard let layer = self.layer else {
return
}
// Extra safety: Check if window is still valid
guard self.window != nil else {
return
}
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.25
context.allowsImplicitAnimation = true
layer.transform = self.originalTransform ?? CATransform3DIdentity
}, completionHandler: { [weak self] in
// Extra safety in completion handler
guard let self = self,
let safeLayer = self.layer,
self.window != nil else {
return
}
if let oAP = self.originalAnchorPointForHover {
safeLayer.anchorPoint = oAP
}
if let oPos = self.originalPositionForHover {
safeLayer.position = oPos
}
})
}
override open func viewWillMove(toSuperview newSuperview: NSView?) {
super.viewWillMove(toSuperview: newSuperview)
if newSuperview == nil, let trackingArea = self.hoverTrackingArea {
self.removeTrackingArea(trackingArea)
self.hoverTrackingArea = nil
}
}
override open func updateTrackingAreas() {
super.updateTrackingAreas()
// Check if this button uses the new hover effect
let hasNewHoverEffect = objc_getAssociatedObject(self, "useNewHoverEffect")
if hasNewHoverEffect != nil {
// Don't interfere with custom tracking areas for new hover effect
return
}
// Check if this button uses the color change effect
let hasColorEffect = objc_getAssociatedObject(self, "useZoomColorEffect")
// Process buttons that have ANY hover effect (old system OR new color effect)
guard self.hoverEffectScale != nil || hasColorEffect != nil else {
return
}
if let existingTrackingArea = self.hoverTrackingArea {
self.removeTrackingArea(existingTrackingArea)
}
guard self.bounds != .zero else {
return
}
let trackingArea = NSTrackingArea(rect: self.bounds,
options: [.mouseEnteredAndExited, .activeAlways, .inVisibleRect],
owner: self,
userInfo: nil)
self.addTrackingArea(trackingArea)
self.hoverTrackingArea = trackingArea
}
}