import AppKit // MARK: - Slider Menu Item View class SliderMenuItemView: NSView { let slider: NSSlider let valueLabel: NSTextField private let titleLabel: NSTextField var onChanged: ((Double) -> Void)? init(title: String, value: Double, minValue: Double, maxValue: Double, width: CGFloat = 280) { titleLabel = NSTextField(labelWithString: title) slider = NSSlider(value: value, minValue: minValue, maxValue: maxValue, target: nil, action: nil) valueLabel = NSTextField(labelWithString: String(format: "%.2fx", value)) super.init(frame: NSRect(x: 0, y: 0, width: width, height: 50)) titleLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium) titleLabel.textColor = .labelColor titleLabel.frame = NSRect(x: 16, y: 28, width: width - 32, height: 16) addSubview(titleLabel) let sliderW = width - 80 slider.frame = NSRect(x: 16, y: 4, width: sliderW, height: 20) slider.target = self slider.action = #selector(sliderMoved) slider.isContinuous = true addSubview(slider) valueLabel.font = NSFont.monospacedSystemFont(ofSize: 11, weight: .regular) valueLabel.textColor = .secondaryLabelColor valueLabel.alignment = .right valueLabel.frame = NSRect(x: sliderW + 20, y: 4, width: 44, height: 20) addSubview(valueLabel) } required init?(coder: NSCoder) { fatalError() } @objc private func sliderMoved() { let v = slider.doubleValue valueLabel.stringValue = String(format: "%.2fx", v) onChanged?(v) } func update(value: Double) { slider.doubleValue = value valueLabel.stringValue = String(format: "%.2fx", value) } } // MARK: - Color Menu Item View class ColorMenuItemView: NSView { let colorWell: NSColorWell private let titleLabel: NSTextField var onChanged: ((NSColor) -> Void)? init(title: String, color: NSColor, width: CGFloat = 280) { titleLabel = NSTextField(labelWithString: title) colorWell = NSColorWell(frame: .zero) super.init(frame: NSRect(x: 0, y: 0, width: width, height: 34)) titleLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium) titleLabel.textColor = .labelColor titleLabel.frame = NSRect(x: 16, y: 7, width: 120, height: 16) addSubview(titleLabel) colorWell.frame = NSRect(x: 140, y: 4, width: 40, height: 26) colorWell.color = color colorWell.target = self colorWell.action = #selector(colorPicked) addSubview(colorWell) } required init?(coder: NSCoder) { fatalError() } @objc private func colorPicked() { onChanged?(colorWell.color) } func update(color: NSColor) { colorWell.color = color } } // MARK: - Settings Menu Builder class SettingsMenuBuilder { private var intensityView: SliderMenuItemView? private var durationView: SliderMenuItemView? private var colorView: ColorMenuItemView? /// Build settings items and append them to the given menu as a submenu func buildSettingsSubmenu() -> NSMenuItem { let submenu = NSMenu(title: "Settings") // Color let cv = ColorMenuItemView(title: "Gradient Color:", color: Settings.shared.gradientColor) cv.onChanged = { color in Settings.shared.gradientColor = color } colorView = cv let colorItem = NSMenuItem() colorItem.view = cv submenu.addItem(colorItem) submenu.addItem(NSMenuItem.separator()) // Intensity let iv = SliderMenuItemView(title: "Intensity", value: Settings.shared.intensityScale, minValue: 0.25, maxValue: 2.0) iv.onChanged = { val in Settings.shared.intensityScale = val } intensityView = iv let intensityItem = NSMenuItem() intensityItem.view = iv submenu.addItem(intensityItem) submenu.addItem(NSMenuItem.separator()) // Blink Duration let dv = SliderMenuItemView(title: "Blink Duration", value: Settings.shared.blinkDurationScale, minValue: 0.25, maxValue: 3.0) dv.onChanged = { val in Settings.shared.blinkDurationScale = val } durationView = dv let durationItem = NSMenuItem() durationItem.view = dv submenu.addItem(durationItem) submenu.addItem(NSMenuItem.separator()) // Reset let resetItem = NSMenuItem(title: "Reset to Defaults", action: #selector(resetDefaults), keyEquivalent: "") resetItem.target = self submenu.addItem(resetItem) let parentItem = NSMenuItem(title: "Settings", action: nil, keyEquivalent: ",") parentItem.submenu = submenu return parentItem } func refreshValues() { let s = Settings.shared intensityView?.update(value: s.intensityScale) durationView?.update(value: s.blinkDurationScale) colorView?.update(color: s.gradientColor) } @objc private func resetDefaults() { Settings.shared.resetToDefaults() refreshValues() } }