🎉 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.
This commit is contained in:
2025-06-28 16:15:15 +02:00
commit 0dabed11d2
63 changed files with 25727 additions and 0 deletions

View File

@@ -0,0 +1,194 @@
import AppKit
// MARK: - Grid Cell View for Action Grid
class GridCellView: NSView {
let index: Int
private let label: NSTextField = NSTextField(labelWithString: "")
private let iconImageView: NSImageView = NSImageView()
private var iconCenterConstraint: NSLayoutConstraint!
private let originalBackgroundColor = NSColor.clear
private let highlightBackgroundColor = NSColor.clear // geen highlight
private var isHovered: Bool = false
private let iconStartOffset: CGFloat // <-- maak property
init(frame frameRect: NSRect, index: Int, text: String) {
self.index = index
self.iconStartOffset = 30 // <-- property initialiseren
super.init(frame: frameRect)
wantsLayer = true
// Setup theme change observer
ThemeManager.shared.observeThemeChanges { [weak self] in
DispatchQueue.main.async {
self?.updateThemeColors()
}
}
// Voeg mouse tracking toe
let trackingArea = NSTrackingArea(rect: bounds,
options: [.mouseEnteredAndExited, .activeAlways],
owner: self,
userInfo: nil)
addTrackingArea(trackingArea)
// Glas-achtergrond (dubbele blur)
#if false
let blur1 = NSVisualEffectView()
blur1.blendingMode = .behindWindow
blur1.material = .hudWindow
blur1.state = .active
blur1.frame = bounds
blur1.autoresizingMask = [.width, .height]
let blur2 = NSVisualEffectView()
blur2.blendingMode = .behindWindow
blur2.material = .hudWindow
blur2.state = .active
blur2.alphaValue = 0.6
blur2.frame = bounds
blur2.autoresizingMask = [.width, .height]
addSubview(blur1, positioned: .below, relativeTo: nil)
addSubview(blur2, positioned: .below, relativeTo: nil)
#endif
layer?.cornerRadius = 0
layer?.masksToBounds = false
label.stringValue = text
label.font = NSFont.systemFont(ofSize: 12, weight: .semibold)
label.textColor = ThemeManager.shared.gridCellTextColor(isHovered: false)
label.alignment = .left
label.alphaValue = 1.0 // Use theme opacity directly
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
// Cirkel-achtergrond voor het icoon
let iconBackground = NSView()
iconBackground.wantsLayer = true
iconBackground.translatesAutoresizingMaskIntoConstraints = false
iconBackground.layer?.cornerRadius = 18 // voor 36×36 cirkel
iconBackground.layer?.backgroundColor = ThemeManager.shared.gridCellIconBackground.cgColor
addSubview(iconBackground)
// Configuratie icon based on text content
let symbolName: String
if text.contains("Rename") {
symbolName = "pencil"
} else if text.contains("Stash") {
symbolName = "archivebox"
} else if text.contains("Text") {
symbolName = "text.viewfinder"
} else if text.contains("Clipboard") {
symbolName = "doc.on.clipboard"
} else if text.contains("Remove BG") {
symbolName = "person.and.background.dotted"
} else if text.contains("Cancel") {
symbolName = "xmark"
} else if text.contains("Remove") {
symbolName = "trash"
} else {
symbolName = "square"
}
let baseImage = NSImage(systemSymbolName: symbolName, accessibilityDescription: nil)
let configuredImage = baseImage?.withSymbolConfiguration(NSImage.SymbolConfiguration(pointSize: 18, weight: .semibold))
iconImageView.image = configuredImage
iconImageView.contentTintColor = ThemeManager.shared.primaryTextColor
iconImageView.translatesAutoresizingMaskIntoConstraints = false
iconBackground.addSubview(iconImageView)
// AutoLayout (start meer naar rechts)
let iconStartOffset: CGFloat = 30 // Pas dit getal aan voor meer/minder ruimte links
iconCenterConstraint = iconBackground.centerXAnchor.constraint(equalTo: centerXAnchor, constant: iconStartOffset)
NSLayoutConstraint.activate([
iconCenterConstraint,
iconBackground.centerYAnchor.constraint(equalTo: centerYAnchor),
iconBackground.widthAnchor.constraint(equalToConstant: 36),
iconBackground.heightAnchor.constraint(equalTo: iconBackground.widthAnchor),
iconImageView.centerXAnchor.constraint(equalTo: iconBackground.centerXAnchor),
iconImageView.centerYAnchor.constraint(equalTo: iconBackground.centerYAnchor),
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 12),
label.trailingAnchor.constraint(equalTo: iconBackground.leadingAnchor, constant: -8),
label.centerYAnchor.constraint(equalTo: centerYAnchor)
])
// Zet initiële transform
layer?.anchorPoint = CGPoint(x: 0.5, y: 0.5)
layer?.transform = CATransform3DIdentity
// Voeg subtiele gloed toe aan het label
let shadow = NSShadow()
shadow.shadowColor = ThemeManager.shared.primaryTextColor.withAlphaComponent(0.4)
shadow.shadowBlurRadius = 4
shadow.shadowOffset = NSSize(width: 0, height: -1)
label.shadow = shadow
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func mouseEntered(with event: NSEvent) {
let shift = (self.bounds.width / 2) - 18 - 2
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = 0.15
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
self.iconCenterConstraint.animator().constant = shift
self.label.animator().textColor = ThemeManager.shared.gridCellTextColor(isHovered: true)
}
}
override func mouseExited(with event: NSEvent) {
NSAnimationContext.runAnimationGroup { context in
context.duration = 0.15
context.timingFunction = CAMediaTimingFunction(name: .easeOut)
// Icoon terug naar startpositie
self.iconCenterConstraint.animator().constant = self.iconStartOffset
// Text color terug naar normal
self.label.animator().textColor = ThemeManager.shared.gridCellTextColor(isHovered: false)
}
}
func setHovered(_ hovered: Bool) {
guard hovered != isHovered else { return }
isHovered = hovered
let shift = (self.bounds.width / 2) - 18 - 5
NSAnimationContext.runAnimationGroup { ctx in
ctx.duration = 0.15
ctx.timingFunction = CAMediaTimingFunction(name: .easeOut)
if hovered {
self.iconCenterConstraint.animator().constant = shift
self.label.animator().textColor = ThemeManager.shared.gridCellTextColor(isHovered: true)
} else {
self.iconCenterConstraint.animator().constant = self.iconStartOffset // Terug naar startpositie
self.label.animator().textColor = ThemeManager.shared.gridCellTextColor(isHovered: false)
}
}
}
func setHighlighted(_ highlighted: Bool) {
// Do nothing (no color change)
}
// MARK: - Theme Management
private func updateThemeColors() {
// Update text color
label.textColor = ThemeManager.shared.gridCellTextColor(isHovered: isHovered)
// Update icon background
if let iconBackground = iconImageView.superview {
iconBackground.layer?.backgroundColor = ThemeManager.shared.gridCellIconBackground.cgColor
}
// Update icon tint color
iconImageView.contentTintColor = ThemeManager.shared.primaryTextColor
// Update shadow color
let shadow = NSShadow()
shadow.shadowColor = ThemeManager.shared.primaryTextColor.withAlphaComponent(0.4)
shadow.shadowBlurRadius = 4
shadow.shadowOffset = NSSize(width: 0, height: -1)
label.shadow = shadow
}
}