1import AppKit
2
3extension Notification.Name {
4 static let themeChanged = Notification.Name("themeChanged")
5}
6
7/// App-wide light/dark theme — follows the SYSTEM appearance (no setting).
8/// Custom views draw from these; system controls come along for free.
9enum Theme {
10 static var light: Bool {
11 NSApp.effectiveAppearance.bestMatch(from: [.aqua, .darkAqua]) == .aqua
12 }
13
14 private static var observation: NSKeyValueObservation?
15
16 /// Start following the system appearance; custom views redraw on change.
17 static func startObserving() {
18 observation = NSApp.observe(\.effectiveAppearance) { _, _ in
19 DispatchQueue.main.async {
20 NotificationCenter.default.post(name: .themeChanged, object: nil)
21 NotificationCenter.default.post(name: .viewOptionsChanged, object: nil)
22 }
23 }
24 }
25
26 private static func pick(_ dark: NSColor, _ lightC: NSColor) -> NSColor {
27 light ? lightC : dark
28 }
29
30 /// Selected-clip outline: white reads well on dark, the accent color on
31 /// light (white vanishes against light lanes).
32 static var selection: NSColor {
33 pick(.white, .controlAccentColor)
34 }
35
36 // Surfaces
37 static var timelineBg: NSColor {
38 pick(NSColor(calibratedWhite: 0.10, alpha: 1), NSColor(calibratedWhite: 0.91, alpha: 1))
39 }
40 static var laneBg: NSColor {
41 pick(NSColor(calibratedWhite: 0.145, alpha: 1), NSColor(calibratedWhite: 0.85, alpha: 1))
42 }
43 /// Storyboard track lane — a cool tint so it reads as its own strip apart
44 /// from the neutral video/audio lanes.
45 static var storyboardLaneBg: NSColor {
46 pick(NSColor(calibratedHue: 0.60, saturation: 0.28, brightness: 0.20, alpha: 1),
47 NSColor(calibratedHue: 0.60, saturation: 0.14, brightness: 0.78, alpha: 1))
48 }
49 static var rulerBg: NSColor {
50 pick(NSColor(calibratedWhite: 0.13, alpha: 1), NSColor(calibratedWhite: 0.88, alpha: 1))
51 }
52 static var barBg: NSColor {
53 pick(NSColor(calibratedWhite: 0.13, alpha: 1), NSColor(calibratedWhite: 0.93, alpha: 1))
54 }
55 static var viewerBg: NSColor {
56 pick(NSColor(calibratedWhite: 0.06, alpha: 1), NSColor(calibratedWhite: 0.80, alpha: 1))
57 }
58 static var canvasBg: NSColor {
59 pick(NSColor(calibratedWhite: 0.12, alpha: 1), NSColor(calibratedWhite: 0.82, alpha: 1))
60 }
61
62 // Lines & text
63 static var rulerLine: NSColor {
64 pick(NSColor(calibratedWhite: 0.22, alpha: 1), NSColor(calibratedWhite: 0.62, alpha: 1))
65 }
66 static var tickMajor: NSColor {
67 pick(NSColor(calibratedWhite: 0.35, alpha: 1), NSColor(calibratedWhite: 0.45, alpha: 1))
68 }
69 static var tickMinor: NSColor {
70 pick(NSColor(calibratedWhite: 0.24, alpha: 1), NSColor(calibratedWhite: 0.68, alpha: 1))
71 }
72 static var label: NSColor {
73 pick(NSColor(calibratedWhite: 0.9, alpha: 1), NSColor(calibratedWhite: 0.12, alpha: 1))
74 }
75 static var subtleLabel: NSColor {
76 pick(NSColor(calibratedWhite: 0.55, alpha: 1), NSColor(calibratedWhite: 0.40, alpha: 1))
77 }
78 static var faintLabel: NSColor {
79 pick(NSColor(calibratedWhite: 0.5, alpha: 1), NSColor(calibratedWhite: 0.45, alpha: 1))
80 }
81 static var clipTitle: NSColor {
82 NSColor(calibratedWhite: 0.92, alpha: 1) // titles sit on dark strips in both modes
83 }
84 static var dragHint: NSColor {
85 pick(NSColor(calibratedWhite: 0.3, alpha: 1), NSColor(calibratedWhite: 0.55, alpha: 1))
86 }
87
88 // Optimization strip (the thin band under the ruler). Muted so a mostly
89 // red/yellow strip informs rather than alarms.
90 /// Proxy built at the full preview-quality target.
91 static var stripFull: NSColor {
92 pick(NSColor(calibratedHue: 0.36, saturation: 0.70, brightness: 0.62, alpha: 1),
93 NSColor(calibratedHue: 0.36, saturation: 0.65, brightness: 0.60, alpha: 1))
94 }
95 /// Proxy built below target — usable now, an upgrade is still queued.
96 static var stripUsable: NSColor {
97 pick(NSColor(calibratedHue: 0.36, saturation: 0.50, brightness: 0.42, alpha: 1),
98 NSColor(calibratedHue: 0.36, saturation: 0.40, brightness: 0.72, alpha: 1))
99 }
100 /// Building right now (or a rescue slice standing in).
101 static var stripBuilding: NSColor {
102 pick(NSColor(calibratedHue: 0.13, saturation: 0.80, brightness: 0.72, alpha: 1),
103 NSColor(calibratedHue: 0.13, saturation: 0.85, brightness: 0.80, alpha: 1))
104 }
105 /// No proxy on disk (never built, evicted, or failed).
106 static var stripMissing: NSColor {
107 pick(NSColor(calibratedHue: 0.01, saturation: 0.65, brightness: 0.48, alpha: 1),
108 NSColor(calibratedHue: 0.01, saturation: 0.55, brightness: 0.75, alpha: 1))
109 }
110}