Files
Termind/Frameworks/TermindPresentation/Terminal/TerminalAppearance.swift
T

135 lines
5.4 KiB
Swift

import Foundation
import GhosttyTerminal
import GhosttyTheme
import SwiftUI
import TermindDomain
nonisolated extension TermindTerminalPalette.RGB {
public var color: Color { Color(.sRGB, red: r, green: g, blue: b, opacity: 1) }
}
nonisolated extension TermindTerminalPalette {
/// The same palette expressed in the format consumed by libghostty. The
/// SwiftUI chrome and the terminal grid therefore share one definition
/// instead of merely using similarly named colors.
var ghosttyDefinition: GhosttyThemeDefinition {
GhosttyThemeDefinition(
name: name,
background: background.hex,
foreground: foreground.hex,
cursorColor: cursor.hex,
cursorText: background.hex,
selectionBackground: selection.hex,
selectionForeground: foreground.hex,
palette: Dictionary(uniqueKeysWithValues: ansi.enumerated().map { ($0, $1.hex) })
)
}
}
nonisolated extension TerminalCustomConfiguration {
func applying(to base: GhosttyTerminal.TerminalConfiguration)
-> GhosttyTerminal.TerminalConfiguration
{
GhosttyTerminal.TerminalConfiguration(startingFrom: base) { builder in
for entry in entries {
builder.withCustom(entry.key, entry.value)
}
}
}
}
public nonisolated enum TermindTerminalThemeCatalog {
public static let allThemes: [GhosttyThemeDefinition] = {
let termindThemes = TermindTerminalPalette.all.map(\.ghosttyDefinition)
let termindNames = Set(termindThemes.map(\.name))
return termindThemes + GhosttyThemeCatalog.allThemes.filter { !termindNames.contains($0.name) }
}()
public static func theme(named name: String) -> GhosttyThemeDefinition? {
allThemes.first { $0.name == name }
}
public static func darkTheme(named name: String) -> GhosttyThemeDefinition? {
theme(named: name).flatMap { $0.isDark ? $0 : nil }
}
public static func lightTheme(named name: String) -> GhosttyThemeDefinition? {
theme(named: name).flatMap { $0.isDark ? nil : $0 }
}
public static let defaultDark = TermindTerminalPalette.termindDark.ghosttyDefinition
public static let defaultLight = TermindTerminalPalette.termindLight.ghosttyDefinition
public static let darkThemes = allThemes.filter(\.isDark).sorted { $0.name < $1.name }
public static let lightThemes = allThemes.filter { !$0.isDark }.sorted { $0.name < $1.name }
public static func themes(isDark: Bool) -> [GhosttyThemeDefinition] {
isDark ? darkThemes : lightThemes
}
public static func fallbackTheme(isDark: Bool) -> GhosttyThemeDefinition {
isDark ? defaultDark : defaultLight
}
public static func resolvedTheme(named name: String, isDark: Bool) -> GhosttyThemeDefinition {
theme(named: name).flatMap { $0.isDark == isDark ? $0 : nil } ?? fallbackTheme(isDark: isDark)
}
public static func terminalConfiguration(
themeName: String,
customEnabled: Bool,
customText: String,
isDark: Bool,
font: TerminalFontPreferences = .init()
) -> GhosttyTerminal.TerminalConfiguration {
let selected = resolvedTheme(named: themeName, isDark: isDark)
let base = selected.toTerminalConfiguration()
let themed = customEnabled ? TerminalCustomConfiguration(customText).applying(to: base) : base
// Ghostty renders theme commands after session overrides. Typography must
// come last here, and font-family is a list that needs an explicit reset.
return GhosttyTerminal.TerminalConfiguration(startingFrom: themed) { builder in
if !font.family.isEmpty {
builder.withFontFamily("")
builder.withFontFamily(font.family)
}
if !font.sizeFollowsTheme { builder.withFontSize(Float(font.size)) }
}
}
public static func background(
themeName: String,
customEnabled: Bool,
customText: String,
isDark: Bool
) -> TermindTerminalPalette.RGB {
let selected = resolvedTheme(named: themeName, isDark: isDark)
if customEnabled,
let value = TerminalCustomConfiguration(customText).lastValue(for: "background"),
let customBackground = TermindTerminalPalette.RGB(hex: value)
{
return customBackground
}
return TermindTerminalPalette.RGB(hex: selected.background)
?? (isDark ? TermindTerminalPalette.termindDark.background : TermindTerminalPalette.termindLight.background)
}
}
@MainActor
extension TerminalViewState {
public func applyPreferences(_ value: TerminalPreferences, font: TerminalFontPreferences = .init()) {
let light = TermindTerminalThemeCatalog.terminalConfiguration(
themeName: value.lightTheme,
customEnabled: value.lightCustomEnabled, customText: value.lightCustomConfiguration, isDark: false,
font: font)
let dark = TermindTerminalThemeCatalog.terminalConfiguration(
themeName: value.darkTheme,
customEnabled: value.darkCustomEnabled, customText: value.darkCustomConfiguration, isDark: true,
font: font)
setTheme(GhosttyTerminal.TerminalTheme(light: light, dark: dark))
setTerminalConfiguration(
GhosttyTerminal.TerminalConfiguration { builder in
builder.withWindowPaddingX(8)
builder.withWindowPaddingY(8)
})
}
}