import SwiftUI import AppKit enum AppFontSize: String, Codable, CaseIterable, Identifiable, Sendable { case small = "Small" case standard = "Standard" case large = "Large" var id: String { rawValue } var dynamicTypeSize: DynamicTypeSize { switch self { case .small: return .xSmall case .standard: return .medium case .large: return .xxLarge } } var defaultFont: Font { switch self { case .small: return .system(size: 12) case .standard: return .system(size: 13) case .large: return .system(size: 15) } } var controlSize: ControlSize { switch self { case .small: return .small case .standard: return .regular case .large: return .large } } } enum ListRowDensity: String, Codable, CaseIterable, Identifiable, Sendable { case compact = "Compact" case standard = "Standard" case relaxed = "Relaxed" var id: String { rawValue } var verticalPadding: CGFloat { switch self { case .compact: return 4 case .standard: return 8 case .relaxed: return 14 } } var minRowHeight: CGFloat { switch self { case .compact: return 24 case .standard: return 32 case .relaxed: return 44 } } } enum AppTheme: String, Codable, CaseIterable, Identifiable, Sendable { case system = "System Default" case light = "Light" case dark = "Dark" case dracula = "Dracula" case nord = "Nord" var id: String { rawValue } var theme: Theme { switch self { case .system, .dark: return Theme.system case .light: return Theme.modernLight case .dracula: return Theme.dracula case .nord: return Theme.nord } } var appearance: NSAppearance? { switch self { case .system: return nil case .light: return NSAppearance(named: .aqua) case .dark, .dracula, .nord: return NSAppearance(named: .darkAqua) } } } struct Theme: Equatable, Sendable { var background: Color? var secondaryBackground: Color? var text: Color? var secondaryText: Color? var accent: Color? static let system = Theme( background: nil, secondaryBackground: nil, text: nil, secondaryText: nil, accent: nil ) static let modernLight = Theme( background: Color(nsColor: NSColor(calibratedRed: 0.98, green: 0.98, blue: 0.99, alpha: 1.0)), secondaryBackground: Color(nsColor: NSColor(calibratedRed: 0.94, green: 0.94, blue: 0.96, alpha: 1.0)), text: Color.primary, secondaryText: Color.secondary, accent: Color.accentColor ) static let dracula = Theme( background: Color(nsColor: NSColor(calibratedRed: 0.16, green: 0.16, blue: 0.21, alpha: 1.0)), secondaryBackground: Color(nsColor: NSColor(calibratedRed: 0.27, green: 0.28, blue: 0.35, alpha: 1.0)), text: Color(nsColor: NSColor(calibratedRed: 0.97, green: 0.97, blue: 0.95, alpha: 1.0)), secondaryText: Color(nsColor: NSColor(calibratedRed: 0.38, green: 0.44, blue: 0.58, alpha: 1.0)), accent: Color(nsColor: NSColor(calibratedRed: 1.00, green: 0.47, blue: 0.65, alpha: 1.0)) // Pink ) static let nord = Theme( background: Color(nsColor: NSColor(calibratedRed: 0.18, green: 0.20, blue: 0.25, alpha: 1.0)), // nord0 secondaryBackground: Color(nsColor: NSColor(calibratedRed: 0.23, green: 0.26, blue: 0.32, alpha: 1.0)), // nord1 text: Color(nsColor: NSColor(calibratedRed: 0.85, green: 0.87, blue: 0.91, alpha: 1.0)), // nord4 secondaryText: Color(nsColor: NSColor(calibratedRed: 0.57, green: 0.63, blue: 0.70, alpha: 1.0)), // nord3 accent: Color(nsColor: NSColor(calibratedRed: 0.53, green: 0.75, blue: 0.82, alpha: 1.0)) // nord8 ) } struct AppThemeModifier: ViewModifier { let theme: AppTheme func body(content: Content) -> some View { content .tint(theme.theme.accent) .onAppear { NSApp.appearance = theme.appearance } .onChange(of: theme) { _, newTheme in NSApp.appearance = newTheme.appearance } } } struct AppFontSizeModifier: ViewModifier { let fontSize: AppFontSize func body(content: Content) -> some View { content .font(fontSize.defaultFont) .controlSize(fontSize.controlSize) .dynamicTypeSize(fontSize.dynamicTypeSize) } } struct ThemeBackgroundModifier: ViewModifier { let color: Color? func body(content: Content) -> some View { if let color { content .scrollContentBackground(.hidden) .background(color) } else { content } } } extension View { func themeBackground(_ color: Color?) -> some View { modifier(ThemeBackgroundModifier(color: color)) } }