macos: allow searching sessions by color too
parent
842583b628
commit
e1d0b22029
|
|
@ -157,6 +157,7 @@
|
|||
"Helpers/Extensions/KeyboardShortcut+Extension.swift",
|
||||
"Helpers/Extensions/NSAppearance+Extension.swift",
|
||||
"Helpers/Extensions/NSApplication+Extension.swift",
|
||||
"Helpers/Extensions/NSColor+Extension.swift",
|
||||
"Helpers/Extensions/NSImage+Extension.swift",
|
||||
"Helpers/Extensions/NSMenu+Extension.swift",
|
||||
"Helpers/Extensions/NSMenuItem+Extension.swift",
|
||||
|
|
|
|||
|
|
@ -67,14 +67,23 @@ struct CommandPaletteView: View {
|
|||
@FocusState private var isTextFieldFocused: Bool
|
||||
|
||||
// The options that we should show, taking into account any filtering from
|
||||
// the query.
|
||||
// the query. Options with matching leadingColor are ranked higher.
|
||||
var filteredOptions: [CommandOption] {
|
||||
if query.isEmpty {
|
||||
return options
|
||||
} else {
|
||||
return options.filter {
|
||||
// Filter by title/subtitle match OR color match
|
||||
let filtered = options.filter {
|
||||
$0.title.localizedCaseInsensitiveContains(query) ||
|
||||
($0.subtitle?.localizedCaseInsensitiveContains(query) ?? false)
|
||||
($0.subtitle?.localizedCaseInsensitiveContains(query) ?? false) ||
|
||||
colorMatchScore(for: $0.leadingColor, query: query) > 0
|
||||
}
|
||||
|
||||
// Sort by color match score (higher scores first), then maintain original order
|
||||
return filtered.sorted { a, b in
|
||||
let scoreA = colorMatchScore(for: a.leadingColor, query: query)
|
||||
let scoreB = colorMatchScore(for: b.leadingColor, query: query)
|
||||
return scoreA > scoreB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -191,6 +200,32 @@ struct CommandPaletteView: View {
|
|||
isTextFieldFocused = isPresented
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a score (0.0 to 1.0) indicating how well a color matches a search query color name.
|
||||
/// Returns 0 if no color name in the query matches, or if the color is nil.
|
||||
private func colorMatchScore(for color: Color?, query: String) -> Double {
|
||||
guard let color = color else { return 0 }
|
||||
|
||||
let queryLower = query.lowercased()
|
||||
let nsColor = NSColor(color)
|
||||
|
||||
var bestScore: Double = 0
|
||||
for name in NSColor.colorNames {
|
||||
guard queryLower.contains(name),
|
||||
let systemColor = NSColor(named: name) else { continue }
|
||||
|
||||
let distance = nsColor.distance(to: systemColor)
|
||||
// Max distance in weighted RGB space is ~3.0, so normalize and invert
|
||||
// Use a threshold to determine "close enough" matches
|
||||
let maxDistance: Double = 1.5
|
||||
if distance < maxDistance {
|
||||
let score = 1.0 - (distance / maxDistance)
|
||||
bestScore = max(bestScore, score)
|
||||
}
|
||||
}
|
||||
|
||||
return bestScore
|
||||
}
|
||||
}
|
||||
|
||||
/// The text field for building the query for the command palette.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import AppKit
|
||||
|
||||
extension NSColor {
|
||||
/// Using a color list let's us get localized names.
|
||||
private static let appleColorList: NSColorList? = NSColorList(named: "Apple")
|
||||
|
||||
convenience init?(named name: String) {
|
||||
guard let colorList = Self.appleColorList,
|
||||
let color = colorList.color(withKey: name.capitalized) else {
|
||||
return nil
|
||||
}
|
||||
guard let components = color.usingColorSpace(.sRGB) else {
|
||||
return nil
|
||||
}
|
||||
self.init(
|
||||
red: components.redComponent,
|
||||
green: components.greenComponent,
|
||||
blue: components.blueComponent,
|
||||
alpha: components.alphaComponent
|
||||
)
|
||||
}
|
||||
|
||||
static var colorNames: [String] {
|
||||
appleColorList?.allKeys.map { $0.lowercased() } ?? []
|
||||
}
|
||||
|
||||
/// Calculates the perceptual distance to another color in RGB space.
|
||||
func distance(to other: NSColor) -> Double {
|
||||
guard let a = self.usingColorSpace(.sRGB),
|
||||
let b = other.usingColorSpace(.sRGB) else { return .infinity }
|
||||
|
||||
let dr = a.redComponent - b.redComponent
|
||||
let dg = a.greenComponent - b.greenComponent
|
||||
let db = a.blueComponent - b.blueComponent
|
||||
|
||||
// Weighted Euclidean distance (human eye is more sensitive to green)
|
||||
return sqrt(2 * dr * dr + 4 * dg * dg + 3 * db * db)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue