diff options
Diffstat (limited to 'Sources/NorgUI/Theme/NorgTheme.swift')
| -rw-r--r-- | Sources/NorgUI/Theme/NorgTheme.swift | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/Sources/NorgUI/Theme/NorgTheme.swift b/Sources/NorgUI/Theme/NorgTheme.swift new file mode 100644 index 0000000..47dd161 --- /dev/null +++ b/Sources/NorgUI/Theme/NorgTheme.swift @@ -0,0 +1,128 @@ +import NorgKit +import SwiftUI + +/// Typography and colours used when rendering a Norg document. +public struct NorgTheme: Sendable { + + /// Font for a heading at the given (1-based) level. + public var heading: @Sendable (Int) -> Font + /// Base font for body text, list items and paragraphs. + public var body: Font + /// Font for the title of a definition, footnote or table cell. + public var termFont: Font + /// Foreground colour for `verbatim` inline spans. + public var verbatimColor: Color + /// Foreground colour for `spoiler` inline spans. + public var spoilerForeground: Color + /// Background colour for `spoiler` inline spans. + public var spoilerBackground: Color + /// Foreground colour applied to link spans. + public var linkColor: Color + /// Baseline offset applied to `superscript` spans. + public var superscriptOffset: CGFloat + /// Baseline offset applied to `subscript` spans. + public var subscriptOffset: CGFloat + /// Foreground colour for block quotes. + public var quoteColor: Color + /// Fill colour for the vertical bar drawn alongside a quote. + public var quoteBarColor: Color + /// Background fill for fenced code blocks and ranged tags. + public var codeBackground: AnyShapeStyle + /// Font for code block and ranged-tag contents. + public var codeFont: Font + /// Horizontal indentation applied per nesting level. + public var indentWidth: CGFloat + /// Tint for a task's status symbol. + public var statusTint: @Sendable (TaskStatus) -> Color + /// SF Symbol name for a task's status symbol. + public var statusSymbol: @Sendable (TaskStatus) -> String + /// Localized label for a task status, shown in the status menu and used + /// for accessibility. Defaults to ``TaskStatus/displayName``; override to + /// supply wording from your own string catalog. + public var statusLabel: @Sendable (TaskStatus) -> LocalizedStringResource + + public init( + heading: @escaping @Sendable (Int) -> Font, + body: Font, + termFont: Font, + verbatimColor: Color, + spoilerForeground: Color, + spoilerBackground: Color, + linkColor: Color, + superscriptOffset: CGFloat, + subscriptOffset: CGFloat, + quoteColor: Color, + quoteBarColor: Color, + codeBackground: AnyShapeStyle, + codeFont: Font, + indentWidth: CGFloat, + statusTint: @escaping @Sendable (TaskStatus) -> Color, + statusSymbol: @escaping @Sendable (TaskStatus) -> String, + statusLabel: @escaping @Sendable (TaskStatus) -> LocalizedStringResource = { $0.displayName } + ) { + self.heading = heading + self.body = body + self.termFont = termFont + self.verbatimColor = verbatimColor + self.spoilerForeground = spoilerForeground + self.spoilerBackground = spoilerBackground + self.linkColor = linkColor + self.superscriptOffset = superscriptOffset + self.subscriptOffset = subscriptOffset + self.quoteColor = quoteColor + self.quoteBarColor = quoteBarColor + self.codeBackground = codeBackground + self.codeFont = codeFont + self.indentWidth = indentWidth + self.statusTint = statusTint + self.statusSymbol = statusSymbol + self.statusLabel = statusLabel + } + + /// The default theme. + public static let `default` = NorgTheme( + heading: { level in + switch level { + case 1: return .title.bold() + case 2: return .title2.bold() + case 3: return .title3.bold() + case 4: return .headline + case 5: return .subheadline.bold() + default: return .subheadline + } + }, + body: .body, + termFont: .body.bold(), + verbatimColor: .pink, + spoilerForeground: .secondary, + spoilerBackground: .secondary.opacity(0.25), + linkColor: .accentColor, + superscriptOffset: 5, + subscriptOffset: -3, + quoteColor: .secondary, + quoteBarColor: .secondary, + codeBackground: AnyShapeStyle(.quaternary), + codeFont: .system(.callout, design: .monospaced), + indentWidth: 18, + statusTint: { status in + switch status { + case .done: return .green + case .urgent: return .red + case .pending: return .blue + default: return .secondary + } + }, + statusSymbol: { status in + switch status { + case .undone: return "circle" + case .done: return "checkmark.circle.fill" + case .needsInput: return "questionmark.circle" + case .urgent: return "exclamationmark.circle" + case .recurring: return "repeat.circle" + case .pending: return "ellipsis.circle" + case .onHold: return "pause.circle" + case .cancelled: return "xmark.circle" + } + } + ) +} |