- Selecting a row in the Profiles tab shows its Claude Desktop and Claude Code dirs inline and selectable, instead of requiring a right-click "Reveal in Finder" to find out where a profile actually lives. - build.sh stamps Contents/Resources/COMMIT with the short HEAD SHA, left empty when HEAD sits exactly on a tag (how a real release is built), since the version number alone is unambiguous there. About appends it when present, so two dev builds off the same VERSION are distinguishable. The update checker still compares plain semver. - Profile dot colours are picked per NSApp.effectiveAppearance at render time: same hue either way, but full saturation with brightness split per background (1.0 dark, 0.55 light), so they read as bold hues rather than washing out on white or muddying against a dark menu.
49 lines
2.1 KiB
Swift
49 lines
2.1 KiB
Swift
// A stable, well-distributed color per profile name, shared by the menu
|
|
// bar dots and the Manage window's profile list so the same name always
|
|
// reads as the same color everywhere.
|
|
import AppKit
|
|
|
|
enum ProfileColor {
|
|
// FNV-1a: a proper avalanching hash, not a preset-palette lookup — the
|
|
// full hue wheel is available, and single-character name edits (e.g.
|
|
// "personal" vs "personal2") land on unrelated hues instead of an
|
|
// adjacent bucket the way a plain sum-of-scalars hash mod'd into a
|
|
// short color list would.
|
|
private static func fnv1aHash(_ s: String) -> UInt32 {
|
|
var hash: UInt32 = 0x811c_9dc5
|
|
for byte in s.utf8 {
|
|
hash ^= UInt32(byte)
|
|
hash = hash &* 0x0100_0193
|
|
}
|
|
return hash
|
|
}
|
|
|
|
// A brightness/saturation pair that reads clearly on its own
|
|
// background — a color bright enough to pop on a dark menu would wash
|
|
// out on a light one, and vice versa, so this picks per current
|
|
// appearance rather than using one fixed value for both.
|
|
private static func isDarkAppearance() -> Bool {
|
|
NSApp.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
|
|
}
|
|
|
|
static func dotColor(for name: String) -> NSColor {
|
|
let hue = CGFloat(fnv1aHash(name) % 360) / 360.0
|
|
let (saturation, brightness): (CGFloat, CGFloat) = isDarkAppearance() ? (0.90, 1.0) : (1.0, 0.55)
|
|
return NSColor(calibratedHue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
|
|
}
|
|
|
|
// `dimmed` marks a profile that isn't actually running right now — the
|
|
// colored dot still identifies it, but faded, so "open" vs "known but
|
|
// closed" is visible without reading a tooltip.
|
|
static func dotImage(for name: String, dimmed: Bool = false) -> NSImage {
|
|
let size = NSSize(width: 10, height: 10)
|
|
let image = NSImage(size: size)
|
|
image.lockFocus()
|
|
dotColor(for: name).withAlphaComponent(dimmed ? 0.3 : 1.0).setFill()
|
|
NSBezierPath(ovalIn: NSRect(origin: .zero, size: size)).fill()
|
|
image.unlockFocus()
|
|
image.isTemplate = false
|
|
return image
|
|
}
|
|
}
|