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

300 lines
8.6 KiB
Swift

import AppKit
import SwiftUI
public enum PluginSettingsTheme {
public enum Typography {
public static var pageTitle: Font {
.title2.weight(.semibold)
}
public static var pageDescription: Font {
.subheadline
}
public static var sectionTitle: Font {
.body.weight(.semibold)
}
public static var rowTitle: Font {
.body.weight(.medium)
}
public static var emphasizedRowTitle: Font {
.body.weight(.semibold)
}
public static var rowDescription: Font {
.subheadline
}
public static var secondaryLabel: Font {
.subheadline.weight(.medium)
}
public static var statusBadge: Font {
.caption2.weight(.medium)
}
public static var rowIcon: Font {
.caption.weight(.semibold)
}
public static var controlLabel: Font {
.callout
}
public static var monospacedValue: Font {
.system(size: 12, design: .monospaced)
}
}
public enum Spacing {
public static let pagePadding: CGFloat = 24
public static let section: CGFloat = 18
public static let sectionHeaderContent: CGFloat = 10
public static let cardContent: CGFloat = 16
public static let rowHorizontal: CGFloat = 16
public static let rowVertical: CGFloat = 10
public static let interactiveRowVertical: CGFloat = 12
public static let rowTitleDescription: CGFloat = 3
public static let rowContentControl: CGFloat = 12
public static let controlCluster: CGFloat = 8
}
public enum Radius {
public static let card: CGFloat = 10
public static let hostCard: CGFloat = 12
public static let control: CGFloat = 8
public static let field: CGFloat = 6
}
public enum Stroke {
public static let hairline: CGFloat = 0.5
public static let standard: CGFloat = 1
}
public enum Size {
public static let pageIcon: CGFloat = 42
public static let rowIcon: CGFloat = 18
public static let controlHeight: CGFloat = 30
public static let metricIcon: CGFloat = 36
public static let emptyStateIcon: CGFloat = 28
}
public enum Palette {
public static var windowBackground: Color {
dynamic(light: 0xF4F5F7, dark: 0x1E1F22)
}
public static var sidebarBackground: Color {
dynamic(light: 0xEEF0F3, dark: 0x25262A)
}
public static var contentBackground: Color {
dynamic(light: 0xF7F8FA, dark: 0x1F2023)
}
public static var cardBackground: Color {
dynamic(light: 0xFFFFFF, dark: 0x2A2B2F)
}
public static var recessedControlBackground: Color {
dynamic(light: 0xF1F3F6, dark: 0x222327)
}
public static var fieldBackground: Color {
dynamic(light: 0xFFFFFF, dark: 0x1C1D20)
}
public static var keycapBackground: Color {
dynamic(light: 0xF8F9FB, dark: 0x2F3035)
}
public static var separator: Color {
dynamic(light: 0xD9DDE4, dark: 0x3A3B40)
}
public static var cardBorder: Color {
dynamic(light: 0xDDE1E7, dark: 0x3B3C42)
}
public static var sidebarHoverBackground: Color {
Color.primary.opacity(0.05)
}
public static var sidebarSelectionBackground: Color {
Color.accentColor.opacity(0.12)
}
public static var activeControlBackground: Color {
Color.accentColor.opacity(0.12)
}
public static var recordingBackground: Color {
Color.accentColor.opacity(0.08)
}
public static var nativeCardBackground: Color {
Color(nsColor: .controlBackgroundColor)
}
public static var nativeFieldBackground: Color {
Color(nsColor: .textBackgroundColor)
}
public static var nativeSeparator: Color {
Color(nsColor: .separatorColor)
}
private static func dynamic(light lightHex: UInt32, dark darkHex: UInt32) -> Color {
Color(
nsColor: NSColor(
name: nil,
dynamicProvider: { appearance in
appearance.pluginSettingsThemeIsDark
? .pluginSettingsThemeRGB(darkHex)
: .pluginSettingsThemeRGB(lightHex)
}
)
)
}
}
}
public enum PluginSettingsCardBackgroundStyle {
case host
case plugin
case recessed
}
public enum PluginSettingsListDividerStyle {
case horizontal
case vertical
}
public struct PluginSettingsCardBackground: ViewModifier {
private let style: PluginSettingsCardBackgroundStyle
public init(_ style: PluginSettingsCardBackgroundStyle = .host) {
self.style = style
}
public func body(content: Content) -> some View {
content
.background(
RoundedRectangle(cornerRadius: radius, style: .continuous)
.fill(background)
)
}
private var radius: CGFloat {
switch style {
case .host:
return PluginSettingsTheme.Radius.hostCard
case .plugin, .recessed:
return PluginSettingsTheme.Radius.card
}
}
private var background: Color {
switch style {
case .host:
return PluginSettingsTheme.Palette.cardBackground
case .plugin:
return PluginSettingsTheme.Palette.nativeCardBackground
case .recessed:
return PluginSettingsTheme.Palette.recessedControlBackground
}
}
}
public struct PluginSettingsListDivider: View {
private let style: PluginSettingsListDividerStyle
private let leadingInset: CGFloat
private let trailingInset: CGFloat
public init(
_ style: PluginSettingsListDividerStyle = .horizontal,
leadingInset: CGFloat = PluginSettingsTheme.Spacing.rowHorizontal,
trailingInset: CGFloat = PluginSettingsTheme.Spacing.rowHorizontal
) {
self.style = style
self.leadingInset = leadingInset
self.trailingInset = trailingInset
}
public var body: some View {
switch style {
case .horizontal:
Rectangle()
.fill(PluginSettingsTheme.Palette.separator)
.frame(height: PluginSettingsTheme.Stroke.standard)
.padding(.leading, leadingInset)
.padding(.trailing, trailingInset)
case .vertical:
Rectangle()
.fill(PluginSettingsTheme.Palette.separator)
.frame(width: PluginSettingsTheme.Stroke.standard)
}
}
}
public extension View {
func pluginSettingsCardBackground(
_ style: PluginSettingsCardBackgroundStyle = .host
) -> some View {
modifier(PluginSettingsCardBackground(style))
}
func pluginSettingsRowIconStyle(visualScale: CGFloat = 1) -> some View {
pluginSettingsRowIconStyle(
HierarchicalShapeStyle.secondary,
visualScale: visualScale
)
}
func pluginSettingsRowIconStyle<S: ShapeStyle>(
_ foregroundStyle: S,
visualScale: CGFloat = 1
) -> some View {
self
.font(PluginSettingsTheme.Typography.rowIcon)
.foregroundStyle(foregroundStyle)
.symbolRenderingMode(.monochrome)
.scaleEffect(visualScale)
.frame(
width: PluginSettingsTheme.Size.rowIcon,
height: PluginSettingsTheme.Size.rowIcon
)
}
func pluginSettingsListRowPadding(interactive: Bool = false) -> some View {
self
.padding(.horizontal, PluginSettingsTheme.Spacing.rowHorizontal)
.padding(
.vertical,
interactive
? PluginSettingsTheme.Spacing.interactiveRowVertical
: PluginSettingsTheme.Spacing.rowVertical
)
}
}
private extension NSAppearance {
var pluginSettingsThemeIsDark: Bool {
bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
}
private extension NSColor {
static func pluginSettingsThemeRGB(_ hex: UInt32, alpha: CGFloat = 1) -> NSColor {
NSColor(
srgbRed: CGFloat((hex >> 16) & 0xff) / 255,
green: CGFloat((hex >> 8) & 0xff) / 255,
blue: CGFloat(hex & 0xff) / 255,
alpha: alpha
)
}
}