Files
wehub-resource-sync 1d1286fadb
Build / Build and test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:18:38 +08:00

140 lines
4.7 KiB
Swift

import AppKit
import MacToolsPluginKit
/// Pure model for the launcher's label appearance (design 2026-06-13). Three orthogonal
/// presets — color / weight / size — shared by grid labels (app names + collapsed folder
/// names, same `cell.label`) and the open-folder big title (which derives a slightly
/// heavier/larger style from the same selections).
///
/// Safe-default discipline (byte-compat): the defaults (`automatic` / `regular` /
/// `medium`) resolve to the historical hardcoded label rendering — `.labelColor`, the
/// system regular weight, 12pt at the 64pt icon — so an untouched preference renders
/// exactly like previous builds. Only non-default selections move the geometry.
/// Label text color preset. `automatic` resolves to `.labelColor` for zero migration.
enum LaunchpadLabelColor: String, CaseIterable, Identifiable {
case automatic
case light
case dark
case accent
var id: String { rawValue }
/// Preset → AppKit color. Pure; safe to call from any context.
/// `.automatic` resolves to `NSColor.labelColor` — the historical implicit color, so
/// the default selection is byte-identical to previous builds.
var nsColor: NSColor {
switch self {
case .automatic:
.labelColor
case .light:
.white
case .dark:
.black
case .accent:
.controlAccentColor
}
}
func label(localization: PluginLocalization) -> String {
switch self {
case .automatic:
localization.string("labelColor.automatic", defaultValue: "自动")
case .light:
localization.string("labelColor.light", defaultValue: "白色")
case .dark:
localization.string("labelColor.dark", defaultValue: "黑色")
case .accent:
localization.string("labelColor.accent", defaultValue: "强调色")
}
}
}
/// Label font-weight preset. `regular` matches the historical system regular weight.
enum LaunchpadLabelWeight: String, CaseIterable, Identifiable {
case regular
case medium
case semibold
case bold
var id: String { rawValue }
/// Preset → AppKit font weight. Pure.
var nsFontWeight: NSFont.Weight {
switch self {
case .regular:
.regular
case .medium:
.medium
case .semibold:
.semibold
case .bold:
.bold
}
}
/// The folder big title's weight: the user's selection floored at `.semibold` so the
/// title is never thinner than the historical baseline, satisfying "title ≥ app name".
var emphasized: NSFont.Weight {
switch self {
case .regular, .medium, .semibold:
.semibold
case .bold:
.bold
}
}
func label(localization: PluginLocalization) -> String {
switch self {
case .regular:
localization.string("labelWeight.regular", defaultValue: "常规")
case .medium:
localization.string("labelWeight.medium", defaultValue: "中等")
case .semibold:
localization.string("labelWeight.semibold", defaultValue: "半粗")
case .bold:
localization.string("labelWeight.bold", defaultValue: "加粗")
}
}
}
/// Label font-size preset. `medium` (the default) derives 12pt at the historical 64pt
/// icon, pinning the byte-compat baseline; the other tiers scale with the icon side.
enum LaunchpadLabelSize: String, CaseIterable, Identifiable {
case small
case medium
case large
var id: String { rawValue }
/// Preset → point size for a given icon side. Pure.
/// - `small`: a fixed 11pt, independent of icon size (compact density).
/// - `medium`: `clamp(round(iconSide * 0.18), 11, 15)` — 12pt @64pt pins the history.
/// - `large`: `clamp(round(iconSide * 0.21), 12, 17)` — coordinates with the icon.
func fontSize(iconSide: CGFloat) -> CGFloat {
switch self {
case .small:
11
case .medium:
Self.clamp((iconSide * 0.18).rounded(), lower: 11, upper: 15)
case .large:
Self.clamp((iconSide * 0.21).rounded(), lower: 12, upper: 17)
}
}
func label(localization: PluginLocalization) -> String {
switch self {
case .small:
localization.string("labelSize.small", defaultValue: "小")
case .medium:
localization.string("labelSize.medium", defaultValue: "中")
case .large:
localization.string("labelSize.large", defaultValue: "大")
}
}
private static func clamp(_ value: CGFloat, lower: CGFloat, upper: CGFloat) -> CGFloat {
min(max(value, lower), upper)
}
}