mirror of
https://github.com/jiayouzl/Bitcoin-Monitoring.git
synced 2025-11-26 03:44:57 +08:00
feat: 添加自定义币种图标生成器,支持基于首字母生成图标并更新相关视图
This commit is contained in:
@@ -146,9 +146,11 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
let symbolImage: NSImage?
|
let symbolImage: NSImage?
|
||||||
|
|
||||||
if self.appSettings.isUsingCustomSymbol() {
|
if self.appSettings.isUsingCustomSymbol() {
|
||||||
|
// 自定义币种:使用自定义图标
|
||||||
symbolImage = self.customSymbolImage()
|
symbolImage = self.customSymbolImage()
|
||||||
} else {
|
} else {
|
||||||
symbolImage = self.symbolImage(for: self.priceManager.selectedSymbol)
|
// 默认币种:直接从AppSettings获取当前选中的币种,避免依赖可能尚未更新的priceManager
|
||||||
|
symbolImage = self.symbolImage(for: self.appSettings.selectedSymbol)
|
||||||
}
|
}
|
||||||
symbolImage?.size = NSSize(width: 16, height: 16)
|
symbolImage?.size = NSSize(width: 16, height: 16)
|
||||||
|
|
||||||
@@ -178,11 +180,22 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "Crypto")
|
return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "Crypto")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取自定义币种的图标(统一使用BTC图标)
|
// 获取自定义币种的图标(基于首字母生成)
|
||||||
private func customSymbolImage() -> NSImage? {
|
private func customSymbolImage() -> NSImage? {
|
||||||
|
if appSettings.isUsingCustomSymbol(),
|
||||||
|
let index = appSettings.selectedCustomSymbolIndex,
|
||||||
|
index >= 0 && index < appSettings.customCryptoSymbols.count {
|
||||||
|
let customSymbol = appSettings.customCryptoSymbols[index]
|
||||||
|
return customSymbol.customIcon()
|
||||||
|
}
|
||||||
return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "自定义币种")
|
return NSImage(systemSymbolName: "bitcoinsign.circle.fill", accessibilityDescription: "自定义币种")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取指定自定义币种的图标
|
||||||
|
private func customSymbolImage(for customSymbol: CustomCryptoSymbol) -> NSImage? {
|
||||||
|
return customSymbol.customIcon()
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化价格为千分位分隔形式
|
// 格式化价格为千分位分隔形式
|
||||||
private func formatPriceWithCommas(_ price: Double) -> String {
|
private func formatPriceWithCommas(_ price: Double) -> String {
|
||||||
let formatter = NumberFormatter()
|
let formatter = NumberFormatter()
|
||||||
@@ -251,7 +264,7 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
let placeholderTitle = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..."
|
let placeholderTitle = isCurrent ? "✓ \(customSymbol.displayName) (自定义): 加载中..." : " \(customSymbol.displayName) (自定义): 加载中..."
|
||||||
let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "")
|
let item = NSMenuItem(title: placeholderTitle, action: #selector(self.selectOrCopySymbol(_:)), keyEquivalent: "")
|
||||||
item.target = self
|
item.target = self
|
||||||
if let icon = customSymbolImage() {
|
if let icon = customSymbolImage(for: customSymbol) {
|
||||||
icon.size = NSSize(width: 16, height: 16)
|
icon.size = NSSize(width: 16, height: 16)
|
||||||
item.image = icon
|
item.image = icon
|
||||||
}
|
}
|
||||||
@@ -508,18 +521,19 @@ class MenuBarManager: NSObject, ObservableObject {
|
|||||||
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
|
print("✅ 已切换到自定义币种: \(customSymbol.displayName)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 立即更新价格管理器
|
// 立即更新价格管理器和UI
|
||||||
self.priceManager.updateCryptoSymbolSettings()
|
self.priceManager.updateCryptoSymbolSettings()
|
||||||
self.updateMenuBarTitle(price: self.priceManager.currentPrice)
|
// 使用0.0价格强制更新显示状态,确保图标和文字都正确更新
|
||||||
|
self.updateMenuBarTitle(price: 0.0)
|
||||||
} else if let symbol = data["symbol"] as? CryptoSymbol {
|
} else if let symbol = data["symbol"] as? CryptoSymbol {
|
||||||
// 选择默认币种
|
// 选择默认币种
|
||||||
appSettings.saveSelectedSymbol(symbol)
|
appSettings.saveSelectedSymbol(symbol)
|
||||||
|
|
||||||
print("✅ 已切换到默认币种: \(symbol.displayName)")
|
print("✅ 已切换到默认币种: \(symbol.displayName)")
|
||||||
|
|
||||||
// 立即更新价格管理器
|
// 立即更新价格管理器和UI
|
||||||
self.priceManager.updateCryptoSymbolSettings()
|
self.priceManager.updateCryptoSymbolSettings()
|
||||||
self.updateMenuBarTitle(price: self.priceManager.currentPrice)
|
// 使用0.0价格强制更新显示状态,确保图标和文字都正确更新
|
||||||
|
self.updateMenuBarTitle(price: 0.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import AppKit
|
||||||
|
|
||||||
/// 自定义加密货币数据模型
|
/// 自定义加密货币数据模型
|
||||||
/// 支持用户定义的3-5字符币种符号,使用统一的BTC图标
|
/// 支持用户定义的3-5字符币种符号,使用统一的BTC图标
|
||||||
@@ -121,11 +122,18 @@ extension CustomCryptoSymbol {
|
|||||||
return "\(symbol)/USDT"
|
return "\(symbol)/USDT"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 对应的SF Symbols图标名称(统一使用BTC图标)
|
/// 对应的图标名称(基于首字母生成的自定义图标)
|
||||||
var systemImageName: String {
|
var systemImageName: String {
|
||||||
|
// 保留系统图标名称作为后备方案
|
||||||
return "bitcoinsign.circle.fill"
|
return "bitcoinsign.circle.fill"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取基于首字母的自定义图标
|
||||||
|
/// - Returns: 自定义生成的NSImage图标
|
||||||
|
func customIcon() -> NSImage {
|
||||||
|
return CryptoIconGenerator.generateSystemIcon(for: symbol)
|
||||||
|
}
|
||||||
|
|
||||||
/// 菜单标题(带勾选标记和自定义标识)
|
/// 菜单标题(带勾选标记和自定义标识)
|
||||||
/// - Parameter isCurrent: 是否为当前选中币种
|
/// - Parameter isCurrent: 是否为当前选中币种
|
||||||
/// - Returns: 菜单展示文本
|
/// - Returns: 菜单展示文本
|
||||||
|
|||||||
185
Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift
Normal file
185
Bitcoin-Monitoring/Utils/CryptoIconGenerator.swift
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
//
|
||||||
|
// CryptoIconGenerator.swift
|
||||||
|
// Bitcoin Monitoring
|
||||||
|
//
|
||||||
|
// Created by Mark on 2025/11/03.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密货币图标生成器
|
||||||
|
* 为自定义币种生成基于首字母的彩色图标
|
||||||
|
*/
|
||||||
|
class CryptoIconGenerator {
|
||||||
|
|
||||||
|
/// 图标缓存,避免重复生成相同币种的图标
|
||||||
|
private static var iconCache: [String: NSImage] = [:]
|
||||||
|
|
||||||
|
/// 预定义的颜色数组,用于生成不同币种的背景色
|
||||||
|
private static let backgroundColors: [NSColor] = [
|
||||||
|
NSColor(red: 0.2, green: 0.4, blue: 0.8, alpha: 1.0), // 蓝色
|
||||||
|
NSColor(red: 0.8, green: 0.3, blue: 0.3, alpha: 1.0), // 红色
|
||||||
|
NSColor(red: 0.3, green: 0.7, blue: 0.3, alpha: 1.0), // 绿色
|
||||||
|
NSColor(red: 0.9, green: 0.6, blue: 0.2, alpha: 1.0), // 橙色
|
||||||
|
NSColor(red: 0.7, green: 0.3, blue: 0.7, alpha: 1.0), // 紫色
|
||||||
|
NSColor(red: 0.2, green: 0.7, blue: 0.8, alpha: 1.0), // 青色
|
||||||
|
NSColor(red: 0.9, green: 0.4, blue: 0.6, alpha: 1.0), // 粉色
|
||||||
|
NSColor(red: 0.6, green: 0.6, blue: 0.2, alpha: 1.0), // 黄色
|
||||||
|
NSColor(red: 0.4, green: 0.8, blue: 0.6, alpha: 1.0), // 薄荷绿
|
||||||
|
NSColor(red: 0.7, green: 0.5, blue: 0.3, alpha: 1.0), // 棕色
|
||||||
|
]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为自定义币种生成基于首字母的图标
|
||||||
|
* - Parameter symbol: 币种符号(如 BTC、ETH、ADA)
|
||||||
|
* - Returns: 生成的NSImage图标
|
||||||
|
*/
|
||||||
|
static func generateIcon(for symbol: String) -> NSImage {
|
||||||
|
// 检查缓存
|
||||||
|
if let cachedIcon = iconCache[symbol] {
|
||||||
|
return cachedIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新图标
|
||||||
|
let icon = createLetterIcon(symbol: symbol)
|
||||||
|
|
||||||
|
// 缓存图标
|
||||||
|
iconCache[symbol] = icon
|
||||||
|
|
||||||
|
return icon
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建基于首字母的图标
|
||||||
|
* - Parameter symbol: 币种符号
|
||||||
|
* - Returns: 生成的NSImage图标
|
||||||
|
*/
|
||||||
|
private static func createLetterIcon(symbol: String) -> NSImage {
|
||||||
|
let size = NSSize(width: 64, height: 64) // 使用较大的尺寸生成,保证清晰度
|
||||||
|
|
||||||
|
let image = NSImage(size: size)
|
||||||
|
image.lockFocus()
|
||||||
|
|
||||||
|
// 创建圆形背景
|
||||||
|
let rect = NSRect(origin: .zero, size: size)
|
||||||
|
let circlePath = NSBezierPath(ovalIn: rect)
|
||||||
|
|
||||||
|
// 选择背景色
|
||||||
|
let backgroundColor = selectBackgroundColor(for: symbol)
|
||||||
|
backgroundColor.setFill()
|
||||||
|
circlePath.fill()
|
||||||
|
|
||||||
|
// 绘制首字母
|
||||||
|
let firstLetter = String(symbol.prefix(1)).uppercased()
|
||||||
|
let attributedString = NSAttributedString(
|
||||||
|
string: firstLetter,
|
||||||
|
attributes: [
|
||||||
|
.font: NSFont.boldSystemFont(ofSize: 28),
|
||||||
|
.foregroundColor: NSColor.white
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
// 计算文字位置,使其居中
|
||||||
|
let textSize = attributedString.size()
|
||||||
|
let textRect = NSRect(
|
||||||
|
x: (size.width - textSize.width) / 2,
|
||||||
|
y: (size.height - textSize.height) / 2,
|
||||||
|
width: textSize.width,
|
||||||
|
height: textSize.height
|
||||||
|
)
|
||||||
|
|
||||||
|
attributedString.draw(in: textRect)
|
||||||
|
|
||||||
|
image.unlockFocus()
|
||||||
|
|
||||||
|
// 设置图像属性,确保正确显示
|
||||||
|
image.isTemplate = false
|
||||||
|
|
||||||
|
return image
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据币种符号选择背景色
|
||||||
|
* 使用哈希算法确保相同币种总是得到相同的颜色
|
||||||
|
* - Parameter symbol: 币种符号
|
||||||
|
* - Returns: 背景色
|
||||||
|
*/
|
||||||
|
private static func selectBackgroundColor(for symbol: String) -> NSColor {
|
||||||
|
// 使用简单的哈希算法
|
||||||
|
var hash = 0
|
||||||
|
for char in symbol {
|
||||||
|
hash = hash * 31 + Int(char.unicodeScalars.first!.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取绝对值并使用颜色数组长度取模
|
||||||
|
let colorIndex = abs(hash) % backgroundColors.count
|
||||||
|
return backgroundColors[colorIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成系统符号样式的图标(兼容SF Symbols)
|
||||||
|
* - Parameter symbol: 币种符号
|
||||||
|
* - Returns: SF Symbols兼容的NSImage
|
||||||
|
*/
|
||||||
|
static func generateSystemIcon(for symbol: String) -> NSImage {
|
||||||
|
let icon = generateIcon(for: symbol)
|
||||||
|
|
||||||
|
// 调整大小为标准SF Symbols尺寸
|
||||||
|
let standardSize = NSSize(width: 16, height: 16)
|
||||||
|
let resizedIcon = resizeImage(icon, to: standardSize)
|
||||||
|
|
||||||
|
return resizedIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调整图像尺寸
|
||||||
|
* - Parameters:
|
||||||
|
* - image: 原始图像
|
||||||
|
* - size: 目标尺寸
|
||||||
|
* - Returns: 调整尺寸后的图像
|
||||||
|
*/
|
||||||
|
private static func resizeImage(_ image: NSImage, to size: NSSize) -> NSImage {
|
||||||
|
let resizedImage = NSImage(size: size)
|
||||||
|
resizedImage.lockFocus()
|
||||||
|
|
||||||
|
let drawingRect = NSRect(origin: .zero, size: size)
|
||||||
|
image.draw(in: drawingRect)
|
||||||
|
|
||||||
|
resizedImage.unlockFocus()
|
||||||
|
resizedImage.isTemplate = false
|
||||||
|
|
||||||
|
return resizedImage
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空图标缓存(用于测试或重置)
|
||||||
|
*/
|
||||||
|
static func clearCache() {
|
||||||
|
iconCache.removeAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取缓存中的图标数量
|
||||||
|
* - Returns: 缓存的图标数量
|
||||||
|
*/
|
||||||
|
static func cacheCount() -> Int {
|
||||||
|
return iconCache.count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - SwiftUI 兼容扩展
|
||||||
|
|
||||||
|
extension CryptoIconGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为SwiftUI生成Image
|
||||||
|
* - Parameter symbol: 币种符号
|
||||||
|
* - Returns: SwiftUI Image
|
||||||
|
*/
|
||||||
|
static func generateSwiftUIImage(for symbol: String) -> Image {
|
||||||
|
let nsImage = generateSystemIcon(for: symbol)
|
||||||
|
return Image(nsImage: nsImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -449,10 +449,16 @@ struct PreferencesWindowView: View {
|
|||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.foregroundColor(isSelected ? .blue : .secondary)
|
.foregroundColor(isSelected ? .blue : .secondary)
|
||||||
|
|
||||||
// 币种图标
|
// 币种图标(使用基于首字母的自定义图标)
|
||||||
Image(systemName: customSymbol.systemImageName)
|
Group {
|
||||||
|
let nsImage = customSymbol.customIcon()
|
||||||
|
Image(nsImage: nsImage)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
}
|
||||||
.foregroundColor(.orange)
|
.foregroundColor(.orange)
|
||||||
.font(.system(size: 16))
|
.font(.system(size: 16))
|
||||||
|
.frame(width: 16, height: 16)
|
||||||
|
|
||||||
// 币种信息
|
// 币种信息
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
|||||||
Reference in New Issue
Block a user