Sources/Orgstar/ThemeColors.swift
51 lines · 2089 bytes
1import AppKit
2import OrgPresentation
3import SwiftUI
4
5extension EnvironmentValues {
6 /// The theme's colors, for the views around the editor.
7 @Entry var orgTheme: ThemeSpec = .default
8}
9
10extension ThemeSpec {
11 /// A color that follows the appearance, or nil when the theme leaves `key` to the system.
12 /// A key set for one appearance only leaves the other to the system.
13 func color(_ key: String) -> Color? {
14 guard light[key] != nil || dark[key] != nil else { return nil }
15 let (light, dark) = (light[key], dark[key])
16 let fallback = Self.systemColor(key)
17 return Color(nsColor: NSColor(name: nil) { appearance in
18 let isDark = appearance.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
19 guard let c = isDark ? dark : light else { return fallback }
20 return NSColor(srgbRed: c.red, green: c.green, blue: c.blue, alpha: c.alpha)
21 })
22 }
23
24 /// What the system shows for a key the theme leaves unset in one appearance.
25 static func systemColor(_ key: String) -> NSColor {
26 switch key {
27 case "sidebar-background", "modeline-background": .windowBackgroundColor
28 case "agenda-background", "background": .textBackgroundColor
29 case "sidebar-header", "modeline-foreground", "agenda-time", "agenda-category", "agenda-scheduled", "tags": .secondaryLabelColor
30 default: .labelColor
31 }
32 }
33
34 /// A TODO keyword's color: its own from `[theme.todo]`, else the theme's TODO or DONE.
35 func keywordColor(_ keyword: String, done: Bool) -> Color {
36 if let c = todo[keyword] { return Color(.sRGB, red: c.red, green: c.green, blue: c.blue, opacity: c.alpha) }
37 return color(done ? "done" : "todo") ?? (done ? .green : .red)
38 }
39}
40
41extension View {
42 /// A list's background from the theme, or the system's when the theme has none.
43 @ViewBuilder
44 func themedListBackground(_ color: Color?) -> some View {
45 if let color {
46 scrollContentBackground(.hidden).background(color)
47 } else {
48 self
49 }
50 }
51}