HuggingChat-Mac/ThemingEngine.swift (152 lines of code) (raw):

// // ThemingEngine.swift // HuggingChat-Mac // // Created by Cyril Zakka on 9/21/24. // import SwiftUI import MarkdownView struct Theme { var name: ThemeNames var previewImage: String var quickBarIcon: String var quickBarFont: Font var markdownFont: ThemedFontGroup? var animatedMeshMainColors: [Color] var animatedMeshHighlightColors: [Color] } enum ThemeNames: String, CaseIterable { case defaultTheme = "Default" case appleClassic = "McIntosh Classic" case chromeDino = "404" case pixelPals = "Pixel Pals" } enum FontType { case custom(String) } struct ThemedFontGroup: MarkdownFontGroup { private let fontType: FontType private let monospacedFontType: FontType private let serifFontType: FontType private let fontMultiplier: CGFloat init(fontType: FontType, monospacedFontType: FontType? = nil, serifFontType: FontType? = nil, fontMultiplier: CGFloat? = nil) { self.fontType = fontType self.monospacedFontType = monospacedFontType ?? fontType self.serifFontType = serifFontType ?? fontType self.fontMultiplier = fontMultiplier ?? 1 } private func getFont(for fontType: FontType, size: CGFloat) -> Font { switch fontType { case .custom(let name): return .custom(name, size: size) } } private func systemFontSize(for textStyle: Font.TextStyle) -> CGFloat { switch textStyle { case .largeTitle: return NSFont.preferredFont(forTextStyle: .headline).pointSize * fontMultiplier case .subheadline: return NSFont.preferredFont(forTextStyle: .subheadline).pointSize * fontMultiplier case .title: return NSFont.preferredFont(forTextStyle: .headline).pointSize * fontMultiplier case .title2: return NSFont.preferredFont(forTextStyle: .title2).pointSize * fontMultiplier case .title3: return NSFont.preferredFont(forTextStyle: .title3).pointSize * fontMultiplier case .headline: return NSFont.preferredFont(forTextStyle: .headline).pointSize * fontMultiplier case .body: return NSFont.preferredFont(forTextStyle: .body).pointSize * fontMultiplier case .callout: return NSFont.preferredFont(forTextStyle: .callout).pointSize * fontMultiplier default: return NSFont.preferredFont(forTextStyle: .body).pointSize * fontMultiplier } } var h1: Font { getFont(for: fontType, size: systemFontSize(for: .largeTitle)) } var h2: Font { getFont(for: fontType, size: systemFontSize(for: .title)) } var h3: Font { getFont(for: fontType, size: systemFontSize(for: .title2)) } var h4: Font { getFont(for: fontType, size: systemFontSize(for: .title3)) } var h5: Font { getFont(for: fontType, size: systemFontSize(for: .headline)) } var h6: Font { getFont(for: fontType, size: systemFontSize(for: .headline)) } var footnote: Font { getFont(for: fontType, size: systemFontSize(for: .footnote)) } var body: Font { getFont(for: fontType, size: systemFontSize(for: .body)) } var codeBlock: Font { getFont(for: monospacedFontType, size: systemFontSize(for: .callout)) } var blockQuote: Font { getFont(for: serifFontType, size: systemFontSize(for: .body)) } var tableHeader: Font { getFont(for: fontType, size: systemFontSize(for: .headline)) } var tableBody: Font { getFont(for: fontType, size: systemFontSize(for: .body)) } } @Observable class ThemingEngine { static let shared: ThemingEngine = ThemingEngine() private(set) var currentTheme: Theme init() { if let savedThemeName = UserDefaults.standard.string(forKey: "selectedTheme"), let savedTheme = ThemeNames(rawValue: savedThemeName) { self.currentTheme = Self.theme(for: savedTheme) } else { self.currentTheme = Self.defaultTheme } } static func theme(for name: ThemeNames) -> Theme { switch name { case .defaultTheme: return defaultTheme case .appleClassic: return appleClassicTheme case .chromeDino: return chromeDinoTheme case .pixelPals: return pixelPalsTheme } } func setTheme(_ themeName: ThemeNames) { switch themeName { case .defaultTheme: currentTheme = Self.defaultTheme case .appleClassic: currentTheme = Self.appleClassicTheme case .chromeDino: currentTheme = Self.chromeDinoTheme case .pixelPals: currentTheme = Self.pixelPalsTheme } UserDefaults.standard.set(themeName.rawValue, forKey: "selectedTheme") } static var defaultTheme: Theme { Theme( name: .defaultTheme, previewImage: "huggy.bp", quickBarIcon: "plus", quickBarFont: Font.system(.title3), markdownFont: nil, animatedMeshMainColors: [.blue, .purple, .indigo, .pink, .red, .mint, .teal, .cyan], animatedMeshHighlightColors: [] ) } static var appleClassicTheme: Theme { Theme( name: .appleClassic, previewImage: "huggy.classic", quickBarIcon: "plusApple", quickBarFont: Font.custom("ChicagoFLF", size: 15, relativeTo: .title3), markdownFont: ThemedFontGroup(fontType: .custom("ChicagoFLF"), fontMultiplier: 1), animatedMeshMainColors: [.green, .yellow, .orange, .red, .purple, .blue], animatedMeshHighlightColors: [] ) } static var chromeDinoTheme: Theme { Theme( name: .chromeDino, previewImage: "huggy.404", quickBarIcon: "chromeDino", quickBarFont: Font.custom("Silom", size: 15, relativeTo: .title3), markdownFont: ThemedFontGroup(fontType: .custom("Silom")), animatedMeshMainColors: [.gray, .black, .white], animatedMeshHighlightColors: [] ) } static var pixelPalsTheme: Theme { Theme( name: .pixelPals, previewImage: "huggy.pals", quickBarIcon: "plusPals", quickBarFont: Font.custom("PixeloidSans", size: 15, relativeTo: .title3), markdownFont: ThemedFontGroup(fontType: .custom("PixeloidSans")), animatedMeshMainColors: [.green, .yellow, .orange, .red, .purple, .blue], animatedMeshHighlightColors: [] ) } } #Preview { let themingEngine = ThemingEngine.shared themingEngine.setTheme(.chromeDino) return ConversationView(columnVisibility: .constant(.automatic)) .environment(ModelManager()) .environment(ConversationViewModel()) .environment(themingEngine) }