Fix color picker with preset swatches and add DMG volume icon

Replace NSColorWell (breaks inside NSMenu submenu) with a row of 9
clickable color swatch circles that work reliably in the dropdown.
Set the boot-logo .icns as the DMG volume icon via SetFile -a C.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-06 16:03:19 -05:00
parent c8691636a6
commit 837bc32abd
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
4 changed files with 81 additions and 20 deletions

Binary file not shown.

View File

@ -83,6 +83,8 @@ dmg: bundle
@mkdir -p $(BUILD_DIR)/dmg-staging @mkdir -p $(BUILD_DIR)/dmg-staging
@cp -R $(APP_BUNDLE) $(BUILD_DIR)/dmg-staging/ @cp -R $(APP_BUNDLE) $(BUILD_DIR)/dmg-staging/
@ln -s /Applications $(BUILD_DIR)/dmg-staging/Applications @ln -s /Applications $(BUILD_DIR)/dmg-staging/Applications
@cp Resources/$(APP_NAME).icns $(BUILD_DIR)/dmg-staging/.VolumeIcon.icns
@SetFile -a C $(BUILD_DIR)/dmg-staging
@hdiutil create -volname "$(APP_NAME)" \ @hdiutil create -volname "$(APP_NAME)" \
-srcfolder $(BUILD_DIR)/dmg-staging \ -srcfolder $(BUILD_DIR)/dmg-staging \
-ov -format UDZO \ -ov -format UDZO \

View File

@ -48,38 +48,98 @@ class SliderMenuItemView: NSView {
} }
} }
// MARK: - Color Menu Item View // MARK: - Color Swatch Menu Item View
class ColorMenuItemView: NSView { class ColorSwatchMenuItemView: NSView {
let colorWell: NSColorWell
private let titleLabel: NSTextField private let titleLabel: NSTextField
private var swatchButtons: [NSButton] = []
private var selectedIndex: Int = -1
var onChanged: ((NSColor) -> Void)? var onChanged: ((NSColor) -> Void)?
init(title: String, color: NSColor, width: CGFloat = 280) { static let presetColors: [(String, NSColor)] = [
titleLabel = NSTextField(labelWithString: title) ("Teal", .systemTeal),
colorWell = NSColorWell(frame: .zero) ("Blue", .systemBlue),
("Purple", .systemPurple),
("Pink", .systemPink),
("Red", .systemRed),
("Orange", .systemOrange),
("Yellow", .systemYellow),
("Green", .systemGreen),
("Indigo", .systemIndigo),
]
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 34)) init(title: String, currentColor: NSColor, width: CGFloat = 280) {
titleLabel = NSTextField(labelWithString: title)
super.init(frame: NSRect(x: 0, y: 0, width: width, height: 56))
titleLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium) titleLabel.font = NSFont.systemFont(ofSize: 12, weight: .medium)
titleLabel.textColor = .labelColor titleLabel.textColor = .labelColor
titleLabel.frame = NSRect(x: 16, y: 7, width: 120, height: 16) titleLabel.frame = NSRect(x: 16, y: 34, width: width - 32, height: 16)
addSubview(titleLabel) addSubview(titleLabel)
colorWell.frame = NSRect(x: 140, y: 4, width: 40, height: 26) let swatchSize: CGFloat = 22
colorWell.color = color let gap: CGFloat = 4
colorWell.target = self let count = Self.presetColors.count
colorWell.action = #selector(colorPicked) let totalW = CGFloat(count) * swatchSize + CGFloat(count - 1) * gap
addSubview(colorWell) var x = (width - totalW) / 2
for (i, (_, color)) in Self.presetColors.enumerated() {
let btn = NSButton(frame: NSRect(x: x, y: 6, width: swatchSize, height: swatchSize))
btn.wantsLayer = true
btn.isBordered = false
btn.title = ""
btn.layer?.backgroundColor = color.cgColor
btn.layer?.cornerRadius = swatchSize / 2
btn.layer?.borderWidth = 2
btn.layer?.borderColor = NSColor.clear.cgColor
btn.tag = i
btn.target = self
btn.action = #selector(swatchClicked(_:))
addSubview(btn)
swatchButtons.append(btn)
x += swatchSize + gap
}
highlightClosest(to: currentColor)
} }
required init?(coder: NSCoder) { fatalError() } required init?(coder: NSCoder) { fatalError() }
@objc private func colorPicked() { @objc private func swatchClicked(_ sender: NSButton) {
onChanged?(colorWell.color) let idx = sender.tag
guard idx >= 0 && idx < Self.presetColors.count else { return }
setSelected(idx)
onChanged?(Self.presetColors[idx].1)
}
private func setSelected(_ index: Int) {
// Clear previous
for btn in swatchButtons {
btn.layer?.borderColor = NSColor.clear.cgColor
}
// Highlight new
if index >= 0 && index < swatchButtons.count {
swatchButtons[index].layer?.borderColor = NSColor.white.cgColor
selectedIndex = index
}
}
func highlightClosest(to target: NSColor) {
let t = target.usingColorSpace(.sRGB) ?? target
var bestIdx = 0
var bestDist: CGFloat = .greatestFiniteMagnitude
for (i, (_, color)) in Self.presetColors.enumerated() {
let c = color.usingColorSpace(.sRGB) ?? color
let dr = t.redComponent - c.redComponent
let dg = t.greenComponent - c.greenComponent
let db = t.blueComponent - c.blueComponent
let dist = dr*dr + dg*dg + db*db
if dist < bestDist { bestDist = dist; bestIdx = i }
}
setSelected(bestIdx)
} }
func update(color: NSColor) { func update(color: NSColor) {
colorWell.color = color highlightClosest(to: color)
} }
} }
@ -87,14 +147,13 @@ class ColorMenuItemView: NSView {
class SettingsMenuBuilder { class SettingsMenuBuilder {
private var intensityView: SliderMenuItemView? private var intensityView: SliderMenuItemView?
private var durationView: SliderMenuItemView? private var durationView: SliderMenuItemView?
private var colorView: ColorMenuItemView? private var colorView: ColorSwatchMenuItemView?
/// Build settings items and append them to the given menu as a submenu
func buildSettingsSubmenu() -> NSMenuItem { func buildSettingsSubmenu() -> NSMenuItem {
let submenu = NSMenu(title: "Settings") let submenu = NSMenu(title: "Settings")
// Color // Color swatches
let cv = ColorMenuItemView(title: "Gradient Color:", color: Settings.shared.gradientColor) let cv = ColorSwatchMenuItemView(title: "Gradient Color:", currentColor: Settings.shared.gradientColor)
cv.onChanged = { color in Settings.shared.gradientColor = color } cv.onChanged = { color in Settings.shared.gradientColor = color }
colorView = cv colorView = cv
let colorItem = NSMenuItem() let colorItem = NSMenuItem()

Binary file not shown.