🚀 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.
236 lines
8.7 KiB
Swift
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
|
|
}
|
|
} |