🎉 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:
194
ShotScreen/Sources/GridCellView.swift
Normal file
194
ShotScreen/Sources/GridCellView.swift
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user