140 lines
4.7 KiB
Swift
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)
|
|
}
|
|
}
|