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 } }