diff --git a/Package.swift b/Package.swift index 46627a4..7e7ea9c 100644 --- a/Package.swift +++ b/Package.swift @@ -33,11 +33,7 @@ let package = Package( ], resources: [ .process("config.toml"), - .process("Themes/voltage.toml"), - // .process("Themes/catppuccin-mocha.toml"), - .process("Themes/dracula.toml"), - // .process("Themes/gruvbox.toml"), - // .process("Themes/tokyo-night.toml"), + .process("Models/Theme/theme.toml"), ], ), .testTarget( diff --git a/Sources/phbar/Controllers/PHController.swift b/Sources/phbar/Controllers/PHController.swift index 54aa4e3..1647f6d 100644 --- a/Sources/phbar/Controllers/PHController.swift +++ b/Sources/phbar/Controllers/PHController.swift @@ -37,10 +37,10 @@ final class PHController: ObservableObject { // Style extension PHController { - func style(for block: PHBlock) -> PHTheme.Style { + func style(for block: PHBlock) -> PHThemeStyle { guard let style = theme.styles.first(where: { $0.name == block.styleName }) else { guard let defaultStyle = theme.styles.first(where: { $0.name == "default" }) else { - return PHTheme.Style.default + return PHThemeStyle.default } return defaultStyle } diff --git a/Sources/phbar/Models/PHBlock.swift b/Sources/phbar/Models/PHBlock.swift index 1c693e3..331c003 100644 --- a/Sources/phbar/Models/PHBlock.swift +++ b/Sources/phbar/Models/PHBlock.swift @@ -9,7 +9,7 @@ final class PHBlock: ObservableObject, Decodable, Identifiable { let command: String let name: String? let styleName: String? - @Published var style: PHTheme.Style = .default + @Published var style: PHThemeStyle = .default let refresh: Double? let centered: Bool? var debug: Bool = false diff --git a/Sources/phbar/Models/PHTheme.swift b/Sources/phbar/Models/PHTheme.swift deleted file mode 100644 index 2f83b58..0000000 --- a/Sources/phbar/Models/PHTheme.swift +++ /dev/null @@ -1,53 +0,0 @@ -struct PHTheme: Decodable { - static let `default` = "dracula" - - static let bundled = [ - "voltage", - "catppuccin-mocha", - "dracula", - "gruvbox", - "tokyo-night", - ] - - let window: Window - let styles: [Style] - - enum CodingKeys: String, CodingKey { - case window - case styles = "style" - } -} - -// Window - -extension PHTheme { - struct Window: Decodable { - let hasShadow: Bool - let blur: Double - - enum CodingKeys: String, CodingKey { - case hasShadow = "shadow" - case blur - } - } -} - -// Style - -extension PHTheme { - struct Style: Decodable { - let name: String - let foreground: PHTheme.Color - let background: PHTheme.Color - let padding: PHTheme.Padding - - static let `default`: Self = { - Style( - name: "_default", - foreground: .init(color: "#000000", alpha: nil), - background: .init(color: "#ffffff", alpha: nil), - padding: .init(leading: 10, trailing: 10) - ) - }() - } -} diff --git a/Sources/phbar/Models/PHTheme/PHTheme+Color.swift b/Sources/phbar/Models/PHTheme/PHTheme+Color.swift deleted file mode 100644 index ed37b04..0000000 --- a/Sources/phbar/Models/PHTheme/PHTheme+Color.swift +++ /dev/null @@ -1,27 +0,0 @@ -import SwiftUI - -extension PHTheme { - struct Color: Decodable, ShapeStyle { - typealias Resolved = SwiftUI.Color - - let color: String - let alpha: Double? - - var uiColor: SwiftUI.Color { - SwiftUI.Color(hex: color, opacity: alpha) - } - - var nsColor: NSColor? { - guard let cgColor else { return nil } - return NSColor(cgColor: cgColor) - } - - var cgColor: CGColor? { - uiColor.cgColor - } - - func resolve(in environment: EnvironmentValues) -> SwiftUI.Color { - uiColor - } - } -} diff --git a/Sources/phbar/Models/PHTheme/PHTheme+Padding.swift b/Sources/phbar/Models/PHTheme/PHTheme+Padding.swift deleted file mode 100644 index f4fe549..0000000 --- a/Sources/phbar/Models/PHTheme/PHTheme+Padding.swift +++ /dev/null @@ -1,12 +0,0 @@ -extension PHTheme { - struct Padding: Decodable { - let leading: Double - let trailing: Double - - var total: Double { leading + trailing } - - static var zero: Padding { - Self.init(leading: 0, trailing: 0) - } - } -} diff --git a/Sources/phbar/Models/PHTheme/PHTheme+Loading.swift b/Sources/phbar/Models/Theme/PHTheme.swift similarity index 71% rename from Sources/phbar/Models/PHTheme/PHTheme+Loading.swift rename to Sources/phbar/Models/Theme/PHTheme.swift index a307e08..b2bbb36 100644 --- a/Sources/phbar/Models/PHTheme/PHTheme+Loading.swift +++ b/Sources/phbar/Models/Theme/PHTheme.swift @@ -1,11 +1,24 @@ import Foundation import TOML +struct PHTheme: Decodable { + let window: PHThemeWindow + let styles: [PHThemeStyle] + + enum CodingKeys: String, CodingKey { + case window + case styles = "style" + } +} + +// Loading + extension PHTheme { - /// Load theme from the default path (~/.config/phbar/theme.toml). + /// Load theme from the config directory (~/.config/phbar/themes/). /// If the file doesn't exist or fails to parse, defaults are used (non-fatal). static func load(_ theme: String?) throws -> PHTheme { - let theme = theme ?? Self.default + guard let theme else { return try loadFromBundle() } + let url = FileManager.default.homeDirectoryForCurrentUser.appending( path: ".config/phbar/themes/\(theme).toml" ) @@ -13,16 +26,16 @@ extension PHTheme { if FileManager.default.fileExists(atPath: url.relativePath) { return try load(from: url) } else { - return try load(bundled: theme) + return try loadFromBundle() } } - /// Load a bundled theme. + /// Load the bundled theme. /// If the theme doesn't exist or fails to parse, the execution is interrupted. - static func load(bundled theme: String) throws -> PHTheme { + static private func loadFromBundle() throws -> PHTheme { guard let defaultConfig = Bundle.module.url( - forResource: theme, + forResource: "theme", withExtension: "toml" )?.resolvingSymlinksInPath() else { @@ -33,7 +46,7 @@ extension PHTheme { /// Load theme from a file at URL. /// If the file doesn't exist or fails to parse, the execution is interrupted. - static func load(from url: URL) throws -> PHTheme { + static private func load(from url: URL) throws -> PHTheme { do { let data = try Data(contentsOf: url) guard let contents = String(data: data, encoding: .utf8), !contents.isEmpty else { @@ -47,7 +60,7 @@ extension PHTheme { /// Load theme from a TOML string. /// If the content fails to parse, the execution is interrupted. - static func load(from contents: String) throws -> PHTheme { + static private func load(from contents: String) throws -> PHTheme { do { let decoder = TOMLDecoder() return try decoder.decode(PHTheme.self, from: contents) diff --git a/Sources/phbar/Models/Theme/PHThemeColor.swift b/Sources/phbar/Models/Theme/PHThemeColor.swift new file mode 100644 index 0000000..ffb3715 --- /dev/null +++ b/Sources/phbar/Models/Theme/PHThemeColor.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct PHThemeColor: Decodable, ShapeStyle { + typealias Resolved = SwiftUI.Color + + let color: String + let alpha: Double? + + var uiColor: SwiftUI.Color { + SwiftUI.Color(hex: color, opacity: alpha) + } + + var nsColor: NSColor? { + guard let cgColor else { return nil } + return NSColor(cgColor: cgColor) + } + + var cgColor: CGColor? { + uiColor.cgColor + } + + func resolve(in environment: EnvironmentValues) -> SwiftUI.Color { + uiColor + } +} diff --git a/Sources/phbar/Models/Theme/PHThemePadding.swift b/Sources/phbar/Models/Theme/PHThemePadding.swift new file mode 100644 index 0000000..27866ff --- /dev/null +++ b/Sources/phbar/Models/Theme/PHThemePadding.swift @@ -0,0 +1,10 @@ +struct PHThemePadding: Decodable { + let leading: Double + let trailing: Double + + var total: Double { leading + trailing } + + static var zero: PHThemePadding { + Self.init(leading: 0, trailing: 0) + } +} diff --git a/Sources/phbar/Models/Theme/PHThemeStyle.swift b/Sources/phbar/Models/Theme/PHThemeStyle.swift new file mode 100644 index 0000000..2350e4a --- /dev/null +++ b/Sources/phbar/Models/Theme/PHThemeStyle.swift @@ -0,0 +1,15 @@ +struct PHThemeStyle: Decodable { + let name: String + let foreground: PHThemeColor + let background: PHThemeColor + let padding: PHThemePadding + + static let `default`: Self = { + PHThemeStyle( + name: "_default", + foreground: .init(color: "#000000", alpha: nil), + background: .init(color: "#ffffff", alpha: nil), + padding: .init(leading: 10, trailing: 10) + ) + }() +} diff --git a/Sources/phbar/Models/Theme/PHThemeWindow.swift b/Sources/phbar/Models/Theme/PHThemeWindow.swift new file mode 100644 index 0000000..83080f9 --- /dev/null +++ b/Sources/phbar/Models/Theme/PHThemeWindow.swift @@ -0,0 +1,9 @@ +struct PHThemeWindow: Decodable { + let hasShadow: Bool + let blur: Double + + enum CodingKeys: String, CodingKey { + case hasShadow = "shadow" + case blur + } +} diff --git a/Sources/phbar/Themes/voltage.toml b/Sources/phbar/Models/Theme/theme.toml similarity index 100% rename from Sources/phbar/Themes/voltage.toml rename to Sources/phbar/Models/Theme/theme.toml diff --git a/Sources/phbar/Themes/dracula.toml b/Sources/phbar/Themes/dracula.toml deleted file mode 100644 index 1c38ea7..0000000 --- a/Sources/phbar/Themes/dracula.toml +++ /dev/null @@ -1,19 +0,0 @@ -# phbar theme file ~ Dracula -# -# Place at ~/.config/phbar/themes/dracula.toml - -[window] -shadow = false -blur = 10.0 - -[[style]] -name = "default" -foreground = { color = "#f8f8f2" } -background = { color = "#000000", alpha = 0.5 } -padding = { leading = 0.0, trailing = 0.0 } - -[[style]] -name = "tinted" -foreground = { color = "#282a36" } -background = { color = "#bd93f9" } -padding = { leading = 10.0, trailing = 10.0 }