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:
parent
c8691636a6
commit
837bc32abd
Binary file not shown.
2
Makefile
2
Makefile
|
|
@ -83,6 +83,8 @@ dmg: bundle
|
|||
@mkdir -p $(BUILD_DIR)/dmg-staging
|
||||
@cp -R $(APP_BUNDLE) $(BUILD_DIR)/dmg-staging/
|
||||
@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)" \
|
||||
-srcfolder $(BUILD_DIR)/dmg-staging \
|
||||
-ov -format UDZO \
|
||||
|
|
|
|||
|
|
@ -48,38 +48,98 @@ class SliderMenuItemView: NSView {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Color Menu Item View
|
||||
class ColorMenuItemView: NSView {
|
||||
let colorWell: NSColorWell
|
||||
// MARK: - Color Swatch Menu Item View
|
||||
class ColorSwatchMenuItemView: NSView {
|
||||
private let titleLabel: NSTextField
|
||||
private var swatchButtons: [NSButton] = []
|
||||
private var selectedIndex: Int = -1
|
||||
var onChanged: ((NSColor) -> Void)?
|
||||
|
||||
init(title: String, color: NSColor, width: CGFloat = 280) {
|
||||
titleLabel = NSTextField(labelWithString: title)
|
||||
colorWell = NSColorWell(frame: .zero)
|
||||
static let presetColors: [(String, NSColor)] = [
|
||||
("Teal", .systemTeal),
|
||||
("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.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)
|
||||
|
||||
colorWell.frame = NSRect(x: 140, y: 4, width: 40, height: 26)
|
||||
colorWell.color = color
|
||||
colorWell.target = self
|
||||
colorWell.action = #selector(colorPicked)
|
||||
addSubview(colorWell)
|
||||
let swatchSize: CGFloat = 22
|
||||
let gap: CGFloat = 4
|
||||
let count = Self.presetColors.count
|
||||
let totalW = CGFloat(count) * swatchSize + CGFloat(count - 1) * gap
|
||||
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() }
|
||||
|
||||
@objc private func colorPicked() {
|
||||
onChanged?(colorWell.color)
|
||||
@objc private func swatchClicked(_ sender: NSButton) {
|
||||
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) {
|
||||
colorWell.color = color
|
||||
highlightClosest(to: color)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -87,14 +147,13 @@ class ColorMenuItemView: NSView {
|
|||
class SettingsMenuBuilder {
|
||||
private var intensityView: 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 {
|
||||
let submenu = NSMenu(title: "Settings")
|
||||
|
||||
// Color
|
||||
let cv = ColorMenuItemView(title: "Gradient Color:", color: Settings.shared.gradientColor)
|
||||
// Color swatches
|
||||
let cv = ColorSwatchMenuItemView(title: "Gradient Color:", currentColor: Settings.shared.gradientColor)
|
||||
cv.onChanged = { color in Settings.shared.gradientColor = color }
|
||||
colorView = cv
|
||||
let colorItem = NSMenuItem()
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue