diff --git a/Sources/ThemePark/Query.swift b/Sources/ThemePark/Query.swift new file mode 100644 index 0000000..0ff33ec --- /dev/null +++ b/Sources/ThemePark/Query.swift @@ -0,0 +1,58 @@ +import SwiftUI + +public enum ControlState: Hashable, Sendable { + case active + case inactive + case hover + +#if os(macOS) + init(controlActiveState: ControlActiveState) { + switch controlActiveState { + case .active, .key: + self = .active + case .inactive: + self = .inactive + @unknown default: + self = .active + } + } +#endif +} + +public struct Query: Hashable, Sendable { + public enum Key: Hashable, Sendable { + public enum Editor: Hashable, Sendable { + case background + case accessoryForeground + case accessoryBackground + case cursor + } + + public enum Gutter: Hashable, Sendable { + case background + case label + } + + case editor(Editor) + case gutter(Gutter) + case syntax(SyntaxSpecifier) + } + + public struct Context: Hashable, Sendable { + public var controlState: ControlState + public var variant: Variant + + public init(controlState: ControlState = .active, colorScheme: ColorScheme, colorSchemeContrast: ColorSchemeContrast = .standard) { + self.controlState = controlState + self.variant = Variant(colorScheme: colorScheme, colorSchemeContrast: colorSchemeContrast) + } + } + + public var key: Key + public var context: Context + + public init(key: Key, context: Context) { + self.key = key + self.context = context + } +} diff --git a/Sources/ThemePark/Style.swift b/Sources/ThemePark/Style.swift index 87d8fa0..479f9bd 100644 --- a/Sources/ThemePark/Style.swift +++ b/Sources/ThemePark/Style.swift @@ -9,25 +9,6 @@ public typealias PlatformColor = NSColor public typealias PlatformFont = NSFont #endif -public enum ControlState: Hashable, Sendable { - case active - case inactive - case hover - -#if os(macOS) - init(controlActiveState: ControlActiveState) { - switch controlActiveState { - case .active, .key: - self = .active - case .inactive: - self = .inactive - @unknown default: - self = .active - } - } -#endif -} - public struct Style: Hashable { public let color: PlatformColor public let font: PlatformFont? @@ -54,38 +35,38 @@ public struct Variant: Hashable, Sendable { self.colorScheme = colorScheme self.colorSchemeContrast = colorSchemeContrast } -} -public struct Query: Hashable, Sendable { - public enum Key: Hashable, Sendable { - public enum Editor: Hashable, Sendable { - case background - case accessoryForeground - case accessoryBackground - case cursor + #if canImport(AppKit) + public init (appearance: NSAppearance) { + switch appearance.name { + case .aqua: + self.init(colorScheme: .light, colorSchemeContrast: .standard) + case .accessibilityHighContrastAqua, .accessibilityHighContrastVibrantLight: + self.init(colorScheme: .light, colorSchemeContrast: .increased) + case .darkAqua: + self.init(colorScheme: .dark, colorSchemeContrast: .standard) + case .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark: + self.init(colorScheme: .light, colorSchemeContrast: .increased) + default: + self.init(colorScheme: .light, colorSchemeContrast: .standard) } - - case editor(Editor) - case syntax(SyntaxSpecifier) } - public struct Context: Hashable, Sendable { - public var controlState: ControlState - public var variant: Variant - - public init(controlState: ControlState = .active, colorScheme: ColorScheme, colorSchemeContrast: ColorSchemeContrast = .standard) { - self.controlState = controlState - self.variant = Variant(colorScheme: colorScheme, colorSchemeContrast: colorSchemeContrast) + public var appearance: NSAppearance? { + switch (colorScheme, colorSchemeContrast) { + case (.light, .standard): + NSAppearance(named: .aqua) + case (.light, .increased): + NSAppearance(named: .accessibilityHighContrastAqua) + case (.dark, .standard): + NSAppearance(named: .darkAqua) + case (.dark, .increased): + NSAppearance(named: .accessibilityHighContrastDarkAqua) + default: + NSAppearance(named: .aqua) } } - - public var key: Key - public var context: Context - - public init(key: Key, context: Context) { - self.key = key - self.context = context - } + #endif } public protocol Styling { @@ -94,6 +75,10 @@ public protocol Styling { } extension Styling { + public func style(for key: Query.Key, context: Query.Context = .init(colorScheme: .light)) -> Style { + style(for: Query(key: key, context: context)) + } + public func color(for query: Query) -> PlatformColor { style(for: query).color } diff --git a/Sources/ThemePark/SyntaxSpecifier.swift b/Sources/ThemePark/SyntaxSpecifier.swift index e66c358..7719cd7 100644 --- a/Sources/ThemePark/SyntaxSpecifier.swift +++ b/Sources/ThemePark/SyntaxSpecifier.swift @@ -63,6 +63,7 @@ public enum SyntaxSpecifier: Hashable, Sendable { } case text + case invisible case keyword(Keyword?) case literal(Literal?) case comment(Comment?) diff --git a/Sources/ThemePark/XcodeTheme.swift b/Sources/ThemePark/XcodeTheme.swift index 7224525..39a347b 100644 --- a/Sources/ThemePark/XcodeTheme.swift +++ b/Sources/ThemePark/XcodeTheme.swift @@ -5,6 +5,7 @@ public struct XcodeTheme: Codable, Hashable, Sendable { public let sourceTextBackground: String public let markupTextNormal: String public let insertionPoint: String + public let invisibles: String public let syntaxColors: [String: String] enum CodingKeys: String, CodingKey { @@ -13,6 +14,7 @@ public struct XcodeTheme: Codable, Hashable, Sendable { case syntaxColors = "DVTSourceTextSyntaxColors" case sourceTextBackground = "DVTSourceTextBackground" case insertionPoint = "DVTSourceTextInsertionPointColor" + case invisibles = "DVTSourceTextInvisiblesColor" } public init(with data: Data) throws { @@ -139,7 +141,7 @@ extension XcodeTheme: Styling { public func style(for query: Query) -> Style { switch query.key { - case .editor(.background): + case .editor(.background), .gutter(.background): let color = fallbackBackgroundColor return Style(color: color, font: nil) @@ -153,7 +155,7 @@ extension XcodeTheme: Styling { return syntaxStyle(for: "xcode.syntax.string") case .syntax(.keyword(_)): return syntaxStyle(for: "xcode.syntax.keyword") - case .syntax(.text): + case .syntax(.text), .gutter(.label): return syntaxStyle(for: "xcode.syntax.plain") case .syntax(.identifier(.variable)): return syntaxStyle(for: "xcode.syntax.identifier.variable") @@ -161,8 +163,11 @@ extension XcodeTheme: Styling { return syntaxStyle(for: "xcode.syntax.number") case .syntax(.identifier(.type)): return syntaxStyle(for: "xcode.syntax.identifier.type") + case .syntax(.invisible): + let color = PlatformColor(componentsString: invisibles) ?? fallbackForegroundColor + + return Style(color: color, font: nil) case .syntax(_): - print("asked for: ", query.key) return syntaxStyle(for: "xcode.syntax.plain") default: return Style(color: .red, font: nil) diff --git a/Tests/ThemeParkTests/XcodeThemeTests.swift b/Tests/ThemeParkTests/XcodeThemeTests.swift index 6ba7281..a34e857 100644 --- a/Tests/ThemeParkTests/XcodeThemeTests.swift +++ b/Tests/ThemeParkTests/XcodeThemeTests.swift @@ -7,6 +7,7 @@ final class XcodeThemeTests: XCTestCase { let theme = try XcodeTheme(contentsOf: url) XCTAssertEqual(theme.sourceTextBackground, "1 1 1 1") + XCTAssertEqual(theme.invisibles, "0.8 0.8 0.8 1") XCTAssertEqual(theme.syntaxColors.count, 28) XCTAssertEqual(theme.supportedVariants, [.init(colorScheme: .light)]) @@ -21,16 +22,24 @@ final class XcodeThemeTests: XCTestCase { let theme = try XcodeTheme(contentsOf: url) XCTAssertEqual( - theme.style(for: Query(key: .editor(.background), context: .init(colorScheme: .light))), + theme.style(for: .editor(.background)), Style(color: PlatformColor(hex: "#ffffff")!) ) XCTAssertEqual( - theme.style(for: Query(key: .syntax(.text), context: .init(colorScheme: .light))), + theme.style(for: .syntax(.text)), Style(color: PlatformColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.85)) ) XCTAssertEqual( - theme.style(for: Query(key: .syntax(.comment(nil)), context: .init(colorScheme: .light))), + theme.style(for: .syntax(.comment(nil))), Style(color: PlatformColor(red: 0.36526, green: 0.421879, blue: 0.475154, alpha: 1.0)) ) + XCTAssertEqual( + theme.style(for: .gutter(.background)), + theme.style(for: .editor(.background)) + ) + XCTAssertEqual( + theme.style(for: .gutter(.label)), + theme.style(for: .syntax(.text)) + ) } }