Files
Termind/TFM/DesignSystem/LinuxDistributionPresentation.swift
T
Kaede Akino c2958a05b3 Add iOS workspaces and shared feature services
Share Vault, persistence, SSH/SFTP, forwarding, and SwiftUI components across platforms. Add mobile editors, terminal input, standalone Assistant, Metrics navigation, transfers, and a unified Live Activity with host system icons.

Validation: 35 iOS tests and 100 focused macOS tests pass; formatting and zh-Hans checks pass. The full macOS run reports 14 issues in the documented timing, Keychain, and popover suites. Remaining Assistant tool and CloudKit work is tracked in Memory/MOBILE_ROLLOUT.md.
2026-09-11 13:47:16 +08:00

92 lines
3.5 KiB
Swift

import AppKit
import Foundation
import SwiftUI
import TermindDomain
import TermindPresentation
typealias LinuxDistributionIconEntry = TermindPresentation.LinuxDistributionIconEntry
typealias LinuxDistributionIconCatalog = TermindPresentation.LinuxDistributionIconCatalog
/// Converts a Simple Icons brand hex into a foreground that remains legible on
/// CardIcon's 16%-opacity tint surface. Only luminance moves, toward the current
/// semantic label color, and only when the original brand color misses 3:1.
@MainActor
enum LinuxDistributionBrandColor {
static func color(hex: String, colorScheme: ColorScheme) -> Color? {
guard let original = nsColor(hex: hex) else { return nil }
let appearanceName: NSAppearance.Name = colorScheme == .dark ? .darkAqua : .aqua
guard let appearance = NSAppearance(named: appearanceName) else {
return Color(nsColor: original)
}
var surface = NSColor.controlBackgroundColor
var target = NSColor.labelColor
appearance.performAsCurrentDrawingAppearance {
surface = NSColor.controlBackgroundColor.usingColorSpace(.deviceRGB) ?? surface
target = NSColor.labelColor.usingColorSpace(.deviceRGB) ?? target
}
guard let foreground = original.usingColorSpace(.deviceRGB),
let background = surface.usingColorSpace(.deviceRGB),
let label = target.usingColorSpace(.deviceRGB)
else { return Color(nsColor: original) }
var candidate = foreground
for step in 0...20 {
let amount = CGFloat(step) / 20
candidate = mixed(foreground, label, amount: amount)
let tintedSurface = mixed(background, candidate, amount: 0.16)
if contrastRatio(candidate, tintedSurface) >= 3 {
return Color(nsColor: candidate)
}
}
return Color(nsColor: candidate)
}
static func nsColor(hex: String) -> NSColor? {
guard hex.count == 6, let value = UInt32(hex, radix: 16) else { return nil }
return NSColor(
srgbRed: CGFloat((value >> 16) & 0xFF) / 255,
green: CGFloat((value >> 8) & 0xFF) / 255,
blue: CGFloat(value & 0xFF) / 255,
alpha: 1
)
}
private static func mixed(_ lhs: NSColor, _ rhs: NSColor, amount: CGFloat) -> NSColor {
let left = components(lhs)
let right = components(rhs)
return NSColor(
srgbRed: left.0 + (right.0 - left.0) * amount,
green: left.1 + (right.1 - left.1) * amount,
blue: left.2 + (right.2 - left.2) * amount,
alpha: 1
)
}
private static func contrastRatio(_ lhs: NSColor, _ rhs: NSColor) -> CGFloat {
let lighter = max(luminance(lhs), luminance(rhs))
let darker = min(luminance(lhs), luminance(rhs))
return (lighter + 0.05) / (darker + 0.05)
}
private static func luminance(_ color: NSColor) -> CGFloat {
func linear(_ value: CGFloat) -> CGFloat {
value <= 0.04045
? value / 12.92
: pow((value + 0.055) / 1.055, 2.4)
}
let value = components(color)
return 0.2126 * linear(value.0)
+ 0.7152 * linear(value.1)
+ 0.0722 * linear(value.2)
}
private static func components(_ color: NSColor) -> (CGFloat, CGFloat, CGFloat) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: nil)
return (red, green, blue)
}
}