WIP: add collection-colored card headers
This commit is contained in:
@@ -40,6 +40,56 @@ private struct ClipboardItemCardLayout: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
private enum ClipboardCollectionVisuals {
|
||||
private static let customPalette: [NSColor] = [
|
||||
NSColor(calibratedRed: 0.98, green: 0.30, blue: 0.32, alpha: 1),
|
||||
NSColor(calibratedRed: 0.96, green: 0.64, blue: 0.00, alpha: 1),
|
||||
NSColor(calibratedRed: 0.04, green: 0.47, blue: 0.95, alpha: 1),
|
||||
NSColor(calibratedRed: 0.18, green: 0.72, blue: 0.34, alpha: 1),
|
||||
NSColor(calibratedRed: 0.55, green: 0.35, blue: 0.88, alpha: 1),
|
||||
NSColor(calibratedRed: 0.93, green: 0.12, blue: 0.34, alpha: 1),
|
||||
NSColor(calibratedRed: 0.10, green: 0.62, blue: 0.72, alpha: 1),
|
||||
NSColor(calibratedRed: 0.74, green: 0.42, blue: 0.16, alpha: 1)
|
||||
]
|
||||
|
||||
static func color(for mode: ClipboardSortMode) -> NSColor {
|
||||
switch mode {
|
||||
case .mostRecent: return .secondaryLabelColor
|
||||
case .mostUsed: return NSColor(calibratedRed: 0.58, green: 0.42, blue: 0.92, alpha: 1)
|
||||
case .text: return NSColor(calibratedRed: 0.96, green: 0.64, blue: 0.00, alpha: 1)
|
||||
case .links: return NSColor(calibratedRed: 0.02, green: 0.47, blue: 0.98, alpha: 1)
|
||||
case .images: return NSColor(calibratedRed: 1.00, green: 0.22, blue: 0.25, alpha: 1)
|
||||
case .audio: return NSColor(calibratedRed: 0.93, green: 0.12, blue: 0.34, alpha: 1)
|
||||
case .files: return NSColor(calibratedRed: 0.11, green: 0.68, blue: 0.36, alpha: 1)
|
||||
case .pinned: return NSColor(calibratedRed: 0.94, green: 0.12, blue: 0.48, alpha: 1)
|
||||
}
|
||||
}
|
||||
|
||||
static func color(forCollectionNamed name: String) -> NSColor {
|
||||
switch name {
|
||||
case "Useful Links":
|
||||
return customPalette[0]
|
||||
case "Important Notes":
|
||||
return customPalette[1]
|
||||
case "Code Snippets":
|
||||
return customPalette[2]
|
||||
case "Read Later":
|
||||
return customPalette[3]
|
||||
default:
|
||||
return customPalette[stablePaletteIndex(for: name)]
|
||||
}
|
||||
}
|
||||
|
||||
private static func stablePaletteIndex(for name: String) -> Int {
|
||||
var hash: UInt64 = 1_469_598_103_934_665_603
|
||||
for scalar in name.lowercased().unicodeScalars {
|
||||
hash ^= UInt64(scalar.value)
|
||||
hash &*= 1_099_511_628_211
|
||||
}
|
||||
return Int(hash % UInt64(customPalette.count))
|
||||
}
|
||||
}
|
||||
|
||||
final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
private enum Metrics {
|
||||
static let actionButtonSize: CGFloat = 30
|
||||
@@ -522,31 +572,11 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
}
|
||||
|
||||
private func collectionColor(for mode: ClipboardSortMode) -> NSColor {
|
||||
switch mode {
|
||||
case .mostRecent: return .secondaryLabelColor
|
||||
case .mostUsed: return NSColor(calibratedRed: 0.58, green: 0.42, blue: 0.92, alpha: 1)
|
||||
case .text: return NSColor(calibratedRed: 0.96, green: 0.64, blue: 0.00, alpha: 1)
|
||||
case .links: return NSColor(calibratedRed: 0.02, green: 0.47, blue: 0.98, alpha: 1)
|
||||
case .images: return NSColor(calibratedRed: 1.00, green: 0.22, blue: 0.25, alpha: 1)
|
||||
case .audio: return NSColor(calibratedRed: 0.93, green: 0.12, blue: 0.34, alpha: 1)
|
||||
case .files: return NSColor(calibratedRed: 0.11, green: 0.68, blue: 0.36, alpha: 1)
|
||||
case .pinned: return NSColor(calibratedRed: 0.94, green: 0.12, blue: 0.48, alpha: 1)
|
||||
}
|
||||
ClipboardCollectionVisuals.color(for: mode)
|
||||
}
|
||||
|
||||
private func collectionColor(forCollectionNamed name: String) -> NSColor {
|
||||
switch name {
|
||||
case "Useful Links":
|
||||
return NSColor(calibratedRed: 0.98, green: 0.30, blue: 0.32, alpha: 1)
|
||||
case "Important Notes":
|
||||
return NSColor(calibratedRed: 0.96, green: 0.64, blue: 0.00, alpha: 1)
|
||||
case "Code Snippets":
|
||||
return NSColor(calibratedRed: 0.04, green: 0.47, blue: 0.95, alpha: 1)
|
||||
case "Read Later":
|
||||
return NSColor(calibratedRed: 0.18, green: 0.72, blue: 0.34, alpha: 1)
|
||||
default:
|
||||
return NSColor(calibratedRed: 0.52, green: 0.42, blue: 0.86, alpha: 1)
|
||||
}
|
||||
ClipboardCollectionVisuals.color(forCollectionNamed: name)
|
||||
}
|
||||
|
||||
private func applyCardDensity() {
|
||||
@@ -639,7 +669,8 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
collectionNames: collectionNames,
|
||||
isStacked: viewModel.isItemStacked(at: index),
|
||||
stackCount: viewModel.stackCount,
|
||||
canShowInClipboard: viewModel.canShowVisibleItemsInClipboard
|
||||
canShowInClipboard: viewModel.canShowVisibleItemsInClipboard,
|
||||
selectedCollectionName: viewModel.selectedCollectionName
|
||||
)
|
||||
card.onSelect = { [weak self] selected in
|
||||
self?.viewModel.selectItem(at: selected)
|
||||
@@ -1114,6 +1145,22 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
cardViews.map(\.debugHeaderBadgeSymbol)
|
||||
}
|
||||
|
||||
var debugFirstCardHeaderTitle: String {
|
||||
cardViews.first?.debugHeaderTitle ?? ""
|
||||
}
|
||||
|
||||
var debugFirstCardHeaderSubtitle: String {
|
||||
cardViews.first?.debugHeaderSubtitle ?? ""
|
||||
}
|
||||
|
||||
var debugFirstCardHeaderColorHex: String {
|
||||
cardViews.first?.debugHeaderColorHex ?? ""
|
||||
}
|
||||
|
||||
var debugFirstCardFooterDetailText: String {
|
||||
cardViews.first?.debugFooterDetailText ?? ""
|
||||
}
|
||||
|
||||
var debugQuickPasteBadgeTexts: [String] {
|
||||
cardViews.compactMap(\.debugQuickPasteBadgeText)
|
||||
}
|
||||
@@ -1199,6 +1246,12 @@ final class ClipboardPanelView: NSVisualEffectView, NSSearchFieldDelegate {
|
||||
return viewModel.collectionNames.compactMap { customCollectionButtons[$0]?.count }
|
||||
}
|
||||
|
||||
var debugCustomCollectionColorHexes: [String: String] {
|
||||
Dictionary(uniqueKeysWithValues: viewModel.collectionNames.map { name in
|
||||
(name, ClipboardItemCardView.debugHex(ClipboardCollectionVisuals.color(forCollectionNamed: name)))
|
||||
})
|
||||
}
|
||||
|
||||
var debugStackChipIsVisible: Bool {
|
||||
collectionStack.arrangedSubviews.contains(stackChip)
|
||||
}
|
||||
@@ -1643,6 +1696,8 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
private let itemSourceAppName: String?
|
||||
private let itemSourceAppBundleID: String?
|
||||
private let itemCollectionName: String?
|
||||
private let activeCollectionName: String?
|
||||
private let activeCollectionColor: NSColor?
|
||||
private let collectionNames: [String]
|
||||
private let contentView = NSView()
|
||||
private let footerDetailLabel = NSTextField(labelWithString: "")
|
||||
@@ -1664,8 +1719,12 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
collectionNames: [String] = [],
|
||||
isStacked: Bool = false,
|
||||
stackCount: Int = 0,
|
||||
canShowInClipboard: Bool = false
|
||||
canShowInClipboard: Bool = false,
|
||||
selectedCollectionName: String? = nil
|
||||
) {
|
||||
let normalizedItemCollection = ClipboardCollectionDefaults.normalizedName(item.collectionName)
|
||||
let normalizedSelectedCollection = ClipboardCollectionDefaults.normalizedName(selectedCollectionName)
|
||||
let activeCollection = normalizedSelectedCollection == normalizedItemCollection ? normalizedSelectedCollection : nil
|
||||
self.index = index
|
||||
self.itemID = item.id
|
||||
self.layout = layout
|
||||
@@ -1676,7 +1735,9 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
self.canShowInClipboard = canShowInClipboard
|
||||
self.itemSourceAppName = Self.presentSourceText(item.sourceApp)
|
||||
self.itemSourceAppBundleID = Self.presentSourceText(item.sourceAppBundleId)
|
||||
self.itemCollectionName = ClipboardCollectionDefaults.normalizedName(item.collectionName)
|
||||
self.itemCollectionName = normalizedItemCollection
|
||||
self.activeCollectionName = activeCollection
|
||||
self.activeCollectionColor = activeCollection.map(ClipboardCollectionVisuals.color(forCollectionNamed:))
|
||||
self.collectionNames = collectionNames.compactMap { ClipboardCollectionDefaults.normalizedName($0) }
|
||||
super.init(frame: .zero)
|
||||
configure(item: item, thumbnail: thumbnail)
|
||||
@@ -1806,6 +1867,9 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
private(set) var debugPreviewSummary = ""
|
||||
private(set) var debugPreviewStyle = ""
|
||||
private(set) var debugHeaderBadgeSymbol = ""
|
||||
private(set) var debugHeaderTitle = ""
|
||||
private(set) var debugHeaderSubtitle = ""
|
||||
private(set) var debugHeaderColorHex = ""
|
||||
|
||||
var debugMenuTitles: [String] {
|
||||
contextMenu().items.map { $0.isSeparatorItem ? "-" : $0.title }
|
||||
@@ -1852,9 +1916,21 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
quickPasteBadgeLabel?.stringValue
|
||||
}
|
||||
|
||||
var debugFooterDetailText: String {
|
||||
footerDetailLabel.stringValue
|
||||
}
|
||||
|
||||
var debugItemID: UUID {
|
||||
itemID
|
||||
}
|
||||
|
||||
static func debugHex(_ color: NSColor) -> String {
|
||||
let rgb = color.usingColorSpace(.deviceRGB) ?? color
|
||||
let red = Int((rgb.redComponent * 255).rounded())
|
||||
let green = Int((rgb.greenComponent * 255).rounded())
|
||||
let blue = Int((rgb.blueComponent * 255).rounded())
|
||||
return String(format: "#%02X%02X%02X", red, green, blue)
|
||||
}
|
||||
#endif
|
||||
|
||||
private func contextMenu() -> NSMenu {
|
||||
@@ -2242,6 +2318,9 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
debugPreviewSummary = "\(titleText(for: item))|\(previewText(for: item))|\(detailMetricText(for: item))"
|
||||
debugPreviewStyle = previewStyle(for: item, thumbnail: thumbnail)
|
||||
debugHeaderBadgeSymbol = headerBadgeSymbol(for: item.kind)
|
||||
debugHeaderTitle = headerTitle(for: item)
|
||||
debugHeaderSubtitle = headerSubtitle(for: item)
|
||||
debugHeaderColorHex = Self.debugHex(headerColor(for: item))
|
||||
#endif
|
||||
|
||||
wantsLayer = true
|
||||
@@ -2302,17 +2381,17 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
private func headerView(for item: ClipboardItem) -> NSView {
|
||||
let header = NSView()
|
||||
header.wantsLayer = true
|
||||
header.layer?.backgroundColor = accentColor(for: item.kind).cgColor
|
||||
header.layer?.backgroundColor = headerColor(for: item).cgColor
|
||||
header.heightAnchor.constraint(equalToConstant: layout.headerHeight).isActive = true
|
||||
|
||||
let kind = NSTextField(labelWithString: kindLabel(for: item.kind))
|
||||
let kind = NSTextField(labelWithString: headerTitle(for: item))
|
||||
kind.font = .systemFont(ofSize: layout.isCompact ? 15 : 16, weight: .bold)
|
||||
kind.textColor = .white
|
||||
kind.lineBreakMode = .byTruncatingTail
|
||||
kind.maximumNumberOfLines = 1
|
||||
kind.toolTip = kind.stringValue
|
||||
|
||||
let source = NSTextField(labelWithString: Self.relativeDateText(for: item.createdAt))
|
||||
let source = NSTextField(labelWithString: headerSubtitle(for: item))
|
||||
source.font = .systemFont(ofSize: layout.isCompact ? 10 : 11, weight: .regular)
|
||||
source.textColor = NSColor.white.withAlphaComponent(0.72)
|
||||
source.lineBreakMode = .byTruncatingTail
|
||||
@@ -2382,6 +2461,20 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
return header
|
||||
}
|
||||
|
||||
private func headerTitle(for item: ClipboardItem) -> String {
|
||||
activeCollectionName ?? kindLabel(for: item.kind)
|
||||
}
|
||||
|
||||
private func headerSubtitle(for item: ClipboardItem) -> String {
|
||||
let relativeDate = Self.relativeDateText(for: item.createdAt)
|
||||
guard activeCollectionName != nil else { return relativeDate }
|
||||
return "\(kindLabel(for: item.kind)) - \(relativeDate)"
|
||||
}
|
||||
|
||||
private func headerColor(for item: ClipboardItem) -> NSColor {
|
||||
activeCollectionColor ?? accentColor(for: item.kind)
|
||||
}
|
||||
|
||||
private func quickPasteBadge() -> NSTextField? {
|
||||
guard index < 9 else { return nil }
|
||||
let label = NSTextField(labelWithString: "\(index + 1)")
|
||||
@@ -2839,7 +2932,9 @@ private final class ClipboardItemCardView: NSView, NSDraggingSource {
|
||||
source.toolTip = source.stringValue
|
||||
|
||||
let detailText = detailMetricText(for: item)
|
||||
if let collectionName = item.collectionName?.clipboardTrimmed, !collectionName.isEmpty {
|
||||
if activeCollectionName == nil,
|
||||
let collectionName = item.collectionName?.clipboardTrimmed,
|
||||
!collectionName.isEmpty {
|
||||
footerDetailLabel.stringValue = "\(collectionName) - \(detailText)"
|
||||
} else {
|
||||
footerDetailLabel.stringValue = detailText
|
||||
|
||||
Reference in New Issue
Block a user