153 lines
6.2 KiB
Swift
153 lines
6.2 KiB
Swift
import AppKit
|
|
|
|
@MainActor
|
|
enum ThemePalette {
|
|
struct State {
|
|
let scheme: ColorScheme
|
|
var error: String?
|
|
var usesFallback: Bool
|
|
let background: NSColor
|
|
let selectionFill: NSColor
|
|
let selectionBorder: NSColor
|
|
let keyBackground: NSColor
|
|
|
|
init(scheme: ColorScheme, error: String? = nil, usesFallback: Bool = false) {
|
|
self.scheme = scheme
|
|
self.error = error
|
|
self.usesFallback = usesFallback
|
|
background = scheme.background.withAlphaComponent(0.55)
|
|
selectionFill = scheme.accent.withAlphaComponent(0.12)
|
|
selectionBorder = scheme.accent.withAlphaComponent(0.30)
|
|
keyBackground = scheme.keyBackground.withAlphaComponent(0.5)
|
|
}
|
|
}
|
|
|
|
static var loaded: State = {
|
|
do {
|
|
return State(scheme: try ColorScheme.load(from: ColorScheme.configurationDirectory))
|
|
} catch {
|
|
return State(scheme: .sunset,
|
|
error: "Color configuration: \(error.localizedDescription)\nUsing the built-in sunset palette. Fix the file to apply changes.",
|
|
usesFallback: true)
|
|
}
|
|
}()
|
|
private static var observation: ColorSchemeObservation?
|
|
|
|
static let didChangeNotification = Notification.Name("ConjureThemePaletteDidChange")
|
|
static var configurationError: String? { loaded.error }
|
|
static var background: NSColor { loaded.background }
|
|
static var text: NSColor { loaded.scheme.text }
|
|
static var secondaryText: NSColor { loaded.scheme.secondaryText }
|
|
static var accent: NSColor { loaded.scheme.accent }
|
|
static var selectionFill: NSColor { loaded.selectionFill }
|
|
static var selectionBorder: NSColor { loaded.selectionBorder }
|
|
static var glow: NSColor { loaded.scheme.glow }
|
|
static var keyBackground: NSColor { loaded.keyBackground }
|
|
static var error: NSColor { loaded.scheme.error }
|
|
|
|
static func startObserving() {
|
|
guard observation == nil else { return }
|
|
let directory = ColorScheme.configurationDirectory
|
|
observation = ColorSchemeObservation(directory: directory) {
|
|
let previousError = configurationError
|
|
reload(from: directory)
|
|
if let diagnostic = configurationError, diagnostic != previousError {
|
|
NSLog("%@", diagnostic)
|
|
}
|
|
}
|
|
reload(from: directory)
|
|
}
|
|
|
|
@discardableResult
|
|
static func reload(from directory: URL = ColorScheme.configurationDirectory) -> Bool {
|
|
do {
|
|
let scheme = try ColorScheme.load(from: directory)
|
|
let previous = loaded.scheme
|
|
guard scheme.background != previous.background || scheme.text != previous.text
|
|
|| scheme.secondaryText != previous.secondaryText || scheme.accent != previous.accent
|
|
|| scheme.glow != previous.glow || scheme.keyBackground != previous.keyBackground
|
|
|| scheme.error != previous.error else {
|
|
loaded.error = nil
|
|
loaded.usesFallback = false
|
|
return false
|
|
}
|
|
loaded = State(scheme: scheme)
|
|
NotificationCenter.default.post(name: didChangeNotification, object: nil)
|
|
return true
|
|
} catch {
|
|
let recovery = loaded.usesFallback
|
|
? "Using the built-in sunset palette."
|
|
: "Keeping the last valid palette."
|
|
loaded.error = "Color configuration: \(error.localizedDescription)\n\(recovery) Fix the file to apply changes."
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
final class LauncherBackground: NSView {
|
|
weak var canvas: NSView?
|
|
private var amber = NSGradient(colors: [
|
|
ThemePalette.accent.withAlphaComponent(0.18),
|
|
ThemePalette.accent.withAlphaComponent(0.065),
|
|
ThemePalette.accent.withAlphaComponent(0),
|
|
])!
|
|
private var ember = NSGradient(colors: [
|
|
ThemePalette.glow.withAlphaComponent(0.26),
|
|
ThemePalette.glow.withAlphaComponent(0.10),
|
|
ThemePalette.glow.withAlphaComponent(0),
|
|
])!
|
|
|
|
override init(frame frameRect: NSRect) {
|
|
super.init(frame: frameRect)
|
|
observePalette()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
observePalette()
|
|
}
|
|
|
|
private func observePalette() {
|
|
NotificationCenter.default.addObserver(self, selector: #selector(paletteChanged),
|
|
name: ThemePalette.didChangeNotification, object: nil)
|
|
}
|
|
|
|
@objc private func paletteChanged() {
|
|
amber = NSGradient(colors: [
|
|
ThemePalette.accent.withAlphaComponent(0.18),
|
|
ThemePalette.accent.withAlphaComponent(0.065),
|
|
ThemePalette.accent.withAlphaComponent(0),
|
|
])!
|
|
ember = NSGradient(colors: [
|
|
ThemePalette.glow.withAlphaComponent(0.26),
|
|
ThemePalette.glow.withAlphaComponent(0.10),
|
|
ThemePalette.glow.withAlphaComponent(0),
|
|
])!
|
|
needsDisplay = true
|
|
}
|
|
|
|
override func hitTest(_ point: NSPoint) -> NSView? { nil }
|
|
|
|
override func draw(_ dirtyRect: NSRect) {
|
|
ThemePalette.background.setFill()
|
|
dirtyRect.fill()
|
|
let canvasBounds = canvas.map { convert($0.bounds, from: $0) } ?? bounds
|
|
let upperGlow = NSPoint(x: canvasBounds.minX + canvasBounds.width * 0.58,
|
|
y: canvasBounds.minY + canvasBounds.height * 0.94)
|
|
ember.draw(fromCenter: upperGlow, radius: 0, toCenter: upperGlow,
|
|
radius: canvasBounds.width * 0.40, options: [])
|
|
let lowerGlow = NSPoint(x: canvasBounds.minX + canvasBounds.width * 0.88,
|
|
y: canvasBounds.minY + canvasBounds.height * 0.10)
|
|
amber.draw(fromCenter: lowerGlow, radius: 0, toCenter: lowerGlow,
|
|
radius: canvasBounds.width * 0.55, options: [])
|
|
let leftGlow = NSPoint(x: canvasBounds.minX + canvasBounds.width * 0.08,
|
|
y: canvasBounds.minY + canvasBounds.height * 0.65)
|
|
NSGraphicsContext.saveGraphicsState()
|
|
NSGraphicsContext.current?.cgContext.setAlpha(0.45)
|
|
amber.draw(fromCenter: leftGlow, radius: 0, toCenter: leftGlow,
|
|
radius: canvasBounds.width * 0.32, options: [])
|
|
NSGraphicsContext.restoreGraphicsState()
|
|
}
|
|
}
|