Sources/OrgEditorAppKit/Theme.swift
74 lines · 3592 bytes
1#if os(macOS)
2import AppKit
3import OrgPresentation
4
5/// Attributes for each style role. A fixed theme for now; the app's theme settings replace it.
6@MainActor
7struct Theme {
8 let font = NSFont.monospacedSystemFont(ofSize: 13, weight: .regular)
9
10 var base: [NSAttributedString.Key: Any] {
11 [.font: font, .foregroundColor: NSColor.textColor]
12 }
13
14 /// Width of one column of the monospaced font.
15 var column: CGFloat {
16 ("0" as NSString).size(withAttributes: [.font: font]).width
17 }
18
19 func paragraphStyle(firstLine: Int, wrapped: Int) -> NSParagraphStyle {
20 let style = NSMutableParagraphStyle()
21 style.firstLineHeadIndent = CGFloat(firstLine) * column
22 style.headIndent = CGFloat(wrapped) * column
23 return style
24 }
25
26 func apply(_ role: StyleRole, to storage: NSTextStorage, range: NSRange) {
27 switch role {
28 case .heading(let level):
29 let size = font.pointSize + CGFloat(max(0, 4 - level))
30 storage.addAttribute(.font, value: NSFont.monospacedSystemFont(ofSize: size, weight: .semibold), range: range)
31 case .leadingStars:
32 storage.addAttribute(.foregroundColor, value: NSColor.clear, range: range)
33 case .todo:
34 storage.addAttributes([.foregroundColor: NSColor.systemRed, .font: bold(storage, range)], range: range)
35 case .done:
36 storage.addAttributes([.foregroundColor: NSColor.systemGreen, .font: bold(storage, range)], range: range)
37 case .priority:
38 storage.addAttribute(.foregroundColor, value: NSColor.systemOrange, range: range)
39 case .tags, .comment, .keyword, .metadata:
40 storage.addAttribute(.foregroundColor, value: NSColor.secondaryLabelColor, range: range)
41 case .bold:
42 storage.addAttribute(.font, value: bold(storage, range), range: range)
43 case .italic:
44 storage.addAttribute(.font, value: NSFontManager.shared.convert(currentFont(storage, range), toHaveTrait: .italicFontMask), range: range)
45 case .underline:
46 storage.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range)
47 case .strikeThrough:
48 storage.addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue, range: range)
49 case .verbatim, .code, .inlineSource:
50 storage.addAttribute(.backgroundColor, value: NSColor.quaternarySystemFill, range: range)
51 case .markup, .linkMarkup, .blockDelimiter:
52 storage.addAttribute(.foregroundColor, value: NSColor.tertiaryLabelColor, range: range)
53 case .link:
54 storage.addAttributes([.foregroundColor: NSColor.linkColor, .underlineStyle: NSUnderlineStyle.single.rawValue], range: range)
55 case .timestamp:
56 storage.addAttribute(.foregroundColor, value: NSColor.systemPurple, range: range)
57 case .footnote, .cookie, .target, .macro, .latex:
58 storage.addAttribute(.foregroundColor, value: NSColor.systemTeal, range: range)
59 case .block:
60 storage.addAttribute(.backgroundColor, value: NSColor.quaternarySystemFill, range: range)
61 case .table, .rule:
62 break
63 }
64 }
65
66 private func currentFont(_ storage: NSTextStorage, _ range: NSRange) -> NSFont {
67 storage.attribute(.font, at: range.location, effectiveRange: nil) as? NSFont ?? font
68 }
69
70 private func bold(_ storage: NSTextStorage, _ range: NSRange) -> NSFont {
71 NSFontManager.shared.convert(currentFont(storage, range), toHaveTrait: .boldFontMask)
72 }
73}
74#endif