├── .gitignore
├── .jazzy.yaml
├── .swiftlint.yml
├── Demo
├── Bin
│ ├── Demo.zip
│ └── SampleApp.zip
├── Demo.xcodeproj
│ └── project.pbxproj
├── Demo
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── icon_128x128.png
│ │ │ ├── icon_128x128@2x.png
│ │ │ ├── icon_16x16.png
│ │ │ ├── icon_16x16@2x.png
│ │ │ ├── icon_256x256.png
│ │ │ ├── icon_256x256@2x.png
│ │ │ ├── icon_32x32.png
│ │ │ ├── icon_32x32@2x.png
│ │ │ ├── icon_512x512.png
│ │ │ └── icon_512x512@2x.png
│ │ ├── Contents.json
│ │ ├── apple.imageset
│ │ │ ├── Contents.json
│ │ │ └── apple.png
│ │ ├── file_doc.imageset
│ │ │ ├── Contents.json
│ │ │ └── file_doc.png
│ │ ├── icoWarning.imageset
│ │ │ ├── Contents.json
│ │ │ └── icoWarning.pdf
│ │ ├── ladyindark.imageset
│ │ │ ├── Contents.json
│ │ │ └── ladyindark.png
│ │ ├── moon.imageset
│ │ │ ├── Contents.json
│ │ │ ├── moon.png
│ │ │ └── moon@2x.png
│ │ ├── paper.imageset
│ │ │ ├── Contents.json
│ │ │ ├── paper.png
│ │ │ └── paper_@2X.png
│ │ ├── saturday_warmth.imageset
│ │ │ ├── Contents.json
│ │ │ └── saturday_warmth.png
│ │ └── sun.imageset
│ │ │ ├── Contents.json
│ │ │ ├── sun.png
│ │ │ └── sun@2x.png
│ ├── Base.lproj
│ │ └── Main.storyboard
│ ├── ContentViewController.swift
│ ├── DarkTheme+DemoSwift.swift
│ ├── DetailsView.swift
│ ├── DetailsViewController.swift
│ ├── Info.plist
│ ├── LightTheme+DemoSwift.swift
│ ├── Note.swift
│ ├── NotificationName+Note.swift
│ ├── PaperTheme.swift
│ ├── SidebarViewController+Actions.swift
│ ├── SidebarViewController.swift
│ ├── ThemeColor+DemoSwift.swift
│ ├── ThemeGradient+DemoSwift.swift
│ ├── ThemeImage+DemoSwift.swift
│ ├── TitleBarOverlayView.swift
│ ├── TitleView.swift
│ └── WindowController.swift
└── Themes
│ ├── LadyInDark.theme
│ ├── PurpleGreen.theme
│ └── SaturdayWarmth.theme
├── Docs
├── Classes
│ ├── DarkTheme.html
│ ├── LightTheme.html
│ ├── SystemTheme.html
│ ├── ThemeColor.html
│ ├── ThemeGradient.html
│ ├── ThemeImage.html
│ ├── ThemeManager.html
│ ├── ThemeManager
│ │ └── WindowThemePolicy.html
│ └── UserTheme.html
├── Extensions.html
├── Extensions
│ ├── NSColor.html
│ ├── NSWindow.html
│ ├── Notification.html
│ └── Notification
│ │ └── Name.html
├── Protocols
│ └── Theme.html
├── Theme Assets.html
├── ThemeKit.html
├── Themes.html
├── css
│ ├── highlight.css
│ └── jazzy.css
├── docsets
│ ├── ThemeKit.docset
│ │ └── Contents
│ │ │ ├── Info.plist
│ │ │ └── Resources
│ │ │ ├── Documents
│ │ │ ├── Classes
│ │ │ │ ├── DarkTheme.html
│ │ │ │ ├── LightTheme.html
│ │ │ │ ├── SystemTheme.html
│ │ │ │ ├── ThemeColor.html
│ │ │ │ ├── ThemeGradient.html
│ │ │ │ ├── ThemeImage.html
│ │ │ │ ├── ThemeManager.html
│ │ │ │ ├── ThemeManager
│ │ │ │ │ └── WindowThemePolicy.html
│ │ │ │ └── UserTheme.html
│ │ │ ├── Extensions.html
│ │ │ ├── Extensions
│ │ │ │ ├── NSColor.html
│ │ │ │ ├── NSWindow.html
│ │ │ │ ├── Notification.html
│ │ │ │ └── Notification
│ │ │ │ │ └── Name.html
│ │ │ ├── Protocols
│ │ │ │ └── Theme.html
│ │ │ ├── Theme Assets.html
│ │ │ ├── ThemeKit.html
│ │ │ ├── Themes.html
│ │ │ ├── css
│ │ │ │ ├── highlight.css
│ │ │ │ └── jazzy.css
│ │ │ ├── img
│ │ │ │ ├── carat.png
│ │ │ │ ├── dash.png
│ │ │ │ ├── gh.png
│ │ │ │ └── spinner.gif
│ │ │ ├── index.html
│ │ │ ├── js
│ │ │ │ ├── jazzy.js
│ │ │ │ ├── jazzy.search.js
│ │ │ │ ├── jquery.min.js
│ │ │ │ ├── lunr.min.js
│ │ │ │ └── typeahead.jquery.js
│ │ │ └── search.json
│ │ │ └── docSet.dsidx
│ ├── ThemeKit.tgz
│ └── ThemeKit.xml
├── img
│ ├── carat.png
│ ├── dash.png
│ ├── gh.png
│ └── spinner.gif
├── index.html
├── js
│ ├── jazzy.js
│ ├── jazzy.search.js
│ ├── jquery.min.js
│ ├── lunr.min.js
│ └── typeahead.jquery.js
├── search.json
└── undocumented.json
├── Imgs
├── ThemeKit.gif
├── ThemeKit.png
└── ThemeKit@2x.png
├── LICENSE
├── README.md
├── Sources
├── Common.swift
├── DarkTheme.swift
├── Info.plist
├── LightTheme.swift
├── NSColor+ThemeKit.swift
├── NSDictionary+UserTheme.swift
├── NSImage+ThemeKit.swift
├── NSObject+ThemeKit.swift
├── NSView+ThemeKit.swift
├── NSWindow+ThemeKit.swift
├── NotificationName+ThemeKit.swift
├── String+Substring.swift
├── SystemTheme.swift
├── Theme.swift
├── ThemeColor.swift
├── ThemeGradient.swift
├── ThemeImage.swift
├── ThemeKit.h
├── ThemeManager+ObjectiveC.swift
├── ThemeManager.swift
├── Thread+ThemeKit.swift
└── UserTheme.swift
├── ThemeKit.xcodeproj
├── project.pbxproj
└── xcshareddata
│ └── xcschemes
│ └── ThemeKit.xcscheme
└── macOSThemeKit.podspec
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac OS X
2 | .DS_Store
3 |
4 | # Xcode
5 | *.xcworkspace
6 | xcuserdata/
7 | project.xcworkspace/
8 | C.gitignore & Swift.gitignore
9 |
10 | # Cocoapods
11 | Pods/
12 |
13 | ## Build generated
14 | build/
15 | DerivedData/
16 |
17 | ## Various settings
18 | *.pbxuser
19 | !default.pbxuser
20 | *.mode1v3
21 | !default.mode1v3
22 | *.mode2v3
23 | !default.mode2v3
24 | *.perspectivev3
25 | !default.perspectivev3
26 | xcuserdata/
27 |
28 | ## Other
29 | *.moved-aside
30 | *.xcuserstate
31 |
32 | # Swift Package Manager
33 | Packages/
34 | .build/
35 |
36 | # Carthage
37 | Carthage/Checkouts
38 | Carthage/Build
39 |
40 | Demo/Index
41 |
--------------------------------------------------------------------------------
/.jazzy.yaml:
--------------------------------------------------------------------------------
1 | # About
2 | module: ThemeKit
3 | author: Paw & Nuno Grilo
4 | author_url: https://nunogrilo.com
5 | readme: README.md
6 | copyright: '© 2016-2017 [Paw](https://paw.cloud) & [Nuno Grilo](http://nunogrilo.com) under [MIT License](https://github.com/luckymarmot/ThemeKit/blob/master/LICENSE).'
7 |
8 | # URLs
9 | github_url: https://github.com/luckymarmot/ThemeKit
10 | root_url: http://themekit.nunogrilo.com
11 | github_file_prefix: https://github.com/luckymarmot/ThemeKit/blob/master
12 |
13 | # Generation
14 | clean: true
15 | output: Docs
16 | min_acl: public
17 | hide_documentation_coverage: true
18 | skip_undocumented: true
19 | exclude:
20 | - "Demo*/*.*"
21 | - "**/*ObjC.swift"
22 | - "**/*ObjectiveC.swift"
23 | - "**/NSDictionary+UserTheme.swift"
24 |
25 | # Navigation
26 | custom_categories:
27 | - name: ThemeKit
28 | children:
29 | - ThemeManager
30 | - Notification
31 | - name: Themes
32 | children:
33 | - Theme
34 | - LightTheme
35 | - DarkTheme
36 | - SystemTheme
37 | - UserTheme
38 | - name: Theme Assets
39 | children:
40 | - ThemeColor
41 | - ThemeGradient
42 | - ThemeImage
43 | - name: Extensions
44 | children:
45 | - NSWindow
46 | - NSColor
47 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - block_based_kvo
3 | - identifier_name
4 | - cyclomatic_complexity
5 | - function_body_length
6 | - file_length
7 | - type_body_length
8 | - function_parameter_count
9 | - discarded_notification_center_observer
10 | - line_length
11 | - large_tuple
12 |
--------------------------------------------------------------------------------
/Demo/Bin/Demo.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Bin/Demo.zip
--------------------------------------------------------------------------------
/Demo/Bin/SampleApp.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Bin/SampleApp.zip
--------------------------------------------------------------------------------
/Demo/Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 29/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | @NSApplicationMain
13 | class AppDelegate: NSObject, NSApplicationDelegate {
14 |
15 | // MARK: -
16 | // MARK: App Launch
17 |
18 | func applicationWillFinishLaunching(_ notification: Notification) {
19 |
20 | /* 0. Enable/disable theming capabilities. */
21 | // ThemeManager.shared.isEnabled = false
22 |
23 | /* 1. Simpler usage: switch between light and dark theme directly. */
24 |
25 | // ThemeManager.lightTheme.apply()
26 | // ThemeManager.darkTheme.apply()
27 | // ThemeManager.systemTheme.apply()
28 |
29 | /* 2. Advanced usage: define window theme policy & enable user themes.
30 | * Themes will be selected using popup bound to user defaults. */
31 |
32 | // 2.1 Setup window theme policy
33 | ThemeManager.shared.windowThemePolicy = .themeAllWindows
34 | // ThemeManager.shared.windowThemePolicy = .themeSomeWindows(windowClasses: [MyCustomWindow.self])
35 | // ThemeManager.shared.windowThemePolicy = .doNotThemeSomeWindows(windowClasses: [NSPanel.self])
36 | // ThemeManager.shared.windowThemePolicy = .doNotThemeWindows
37 |
38 | // 2.2 User themes folder
39 | if let bundleResourcePath = Bundle.main.resourcePath {
40 | ThemeManager.shared.userThemesFolderURL = URL(fileURLWithPath: bundleResourcePath)
41 | NSLog("ThemeManager.shared.userThemesFolderURL: %@", ThemeManager.shared.userThemesFolderURL?.path ?? "-")
42 | }
43 |
44 | // 2.3 You can define the default light and dark theme, used for `ThemeManager.systemTheme`
45 | // if let paperTheme = ThemeManager.shared.theme(withIdentifier: PaperTheme.identifier) {
46 | // ThemeManager.lightTheme = paperTheme
47 | // }
48 | // if let purpleGreenTheme = ThemeManager.shared.theme(withIdentifier: "com.luckymarmot.ThemeKit.PurpleGreen") {
49 | // ThemeManager.darkTheme = purpleGreenTheme
50 | // }
51 |
52 | // 2.4 Set default theme (default: macOS theme `ThemeManager.systemTheme`)
53 | ThemeManager.defaultTheme = ThemeManager.lightTheme
54 |
55 | // 2.5 Animate theme transitions? (default = true)
56 | // ThemeManager.shared.animateThemeTransitions = false
57 |
58 | // 2.6 Apply last applied theme, or the default one
59 | ThemeManager.shared.applyLastOrDefaultTheme()
60 |
61 | }
62 |
63 | func applicationDidFinishLaunching(_ aNotification: Notification) {
64 |
65 | // Watch for theme changes and update Theme menu
66 | NotificationCenter.default.addObserver(self, selector: #selector(updateThemeMenu(_:)), name: .didChangeTheme, object: nil)
67 |
68 | // Build theme menu
69 | updateThemeMenu()
70 | }
71 |
72 | // MARK: -
73 | // MARK: Theme related
74 |
75 | @objc private func updateThemeMenu(_ notification: Notification? = nil) {
76 | // Clean menu
77 | themeMenu.removeAllItems()
78 |
79 | // Add themes
80 | var counter = 1
81 | for theme in ThemeManager.shared.themes {
82 | let item = NSMenuItem(title: theme.shortDisplayName, action: #selector(switchTheme(_:)), keyEquivalent: String(counter))
83 | item.representedObject = theme
84 | item.state = (ThemeManager.shared.theme.identifier == theme.identifier) ? .on : .off
85 | themeMenu.addItem(item)
86 | counter += 1
87 | }
88 |
89 | // Add separator
90 | themeMenu.addItem(NSMenuItem.separator())
91 |
92 | // Add slideshow
93 | let slideshowItem = NSMenuItem(title: "Slideshow", action: #selector(startStopThemeSlideshow), keyEquivalent: "0")
94 | slideshowItem.state = slideshowTimerOn ? .on : .off
95 | themeMenu.addItem(slideshowItem)
96 | }
97 |
98 | @IBAction func switchTheme(_ menuItem: NSMenuItem) {
99 | guard menuItem.representedObject != nil else { return }
100 |
101 | if let theme = menuItem.representedObject as? Theme {
102 | ThemeManager.shared.theme = theme
103 | updateThemeMenu()
104 | }
105 | }
106 |
107 | // MARK: -
108 | // MARK: Theme Slideshow
109 |
110 | /// Slideshow timer.
111 | private var slideshowTimer: Timer?
112 |
113 | /// Slideshow state.
114 | private var slideshowTimerOn: Bool {
115 | return (slideshowTimer?.isValid ?? false)
116 | }
117 |
118 | /// Themes used on slideshow. Indices from `ThemeManager.shared.themes`.
119 | private static let slideshowAllThemeIndices = [0, 1, 3, 4, 5, 6]
120 | private static let slideshowAnimatedGIFThemeIndices = [0, 1, 3, 5]
121 | private static let slideshowThemeIndices = AppDelegate.slideshowAllThemeIndices
122 |
123 | /// Start/stop theme slideshow.
124 | @objc func startStopThemeSlideshow() {
125 | if slideshowTimerOn {
126 | slideshowTimer?.invalidate()
127 | slideshowTimer = nil
128 | } else {
129 | slideshowTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(nextSlideshowTheme), userInfo: nil, repeats: true)
130 | }
131 | }
132 |
133 | /// Apply next slideshow theme.
134 | @objc func nextSlideshowTheme() {
135 | let themes = ThemeManager.shared.themes
136 | guard themes.count > 0 else {
137 | startStopThemeSlideshow()
138 | return
139 | }
140 |
141 | // Determine next theme
142 | var nextIndex = AppDelegate.slideshowThemeIndices[0]
143 | if let currentThemeIndex = themes.index(where: { $0 === ThemeManager.shared.theme }),
144 | let currentSlideshowIndex = AppDelegate.slideshowThemeIndices.index(of: currentThemeIndex),
145 | currentSlideshowIndex + 1 < AppDelegate.slideshowThemeIndices.count {
146 | nextIndex = AppDelegate.slideshowThemeIndices[currentSlideshowIndex + 1]
147 | }
148 |
149 | // Apply theme
150 | if nextIndex >= themes.count {
151 | nextIndex = 0
152 | }
153 | ThemeManager.shared.theme = themes[nextIndex]
154 | updateThemeMenu()
155 | }
156 |
157 | // As a bonus, a way of theming window titlebar is shown on
158 | // `WindowController` for illustrative purposes.
159 |
160 | // MARK: -
161 | // MARK: App Termination
162 |
163 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
164 | return true
165 | }
166 |
167 | func applicationWillTerminate(_ aNotification: Notification) {
168 | // Insert code here to tear down your application
169 | }
170 |
171 | // MARK: -
172 | // MARK: Tabs
173 |
174 | private var tabs: [NSWindowController] = []
175 |
176 | @IBAction func newWindowForTab(_ sender: Any?) {
177 | let storyBoard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil)
178 | if let windowController = storyBoard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("WindowController")) as? NSWindowController {
179 | windowController.showWindow(self)
180 | }
181 | }
182 |
183 | // MARK: -
184 | // MARK: Notes actions
185 |
186 | @IBAction func addNote(_ sender: NSButton) {
187 | sidebarViewController?.addNote(sender)
188 | }
189 |
190 | @IBAction func deleteNote(_ sender: NSButton) {
191 | sidebarViewController?.deleteNote(sender)
192 | }
193 |
194 | @IBAction func resetNotes(_ sender: Any) {
195 | sidebarViewController?.resetNotes(sender)
196 | }
197 |
198 | // MARK: -
199 | // MARK: Others
200 |
201 | // Weak control references
202 | @IBOutlet weak var themeMenu: NSMenu!
203 |
204 | // Strong controllers references
205 | @objc weak var sidebarViewController: SidebarViewController?
206 |
207 | }
208 |
209 | extension NSApplication {
210 |
211 | @objc static var sidebarViewController: SidebarViewController? {
212 | if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
213 | return appDelegate.sidebarViewController
214 | }
215 | return nil
216 | }
217 |
218 | }
219 |
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "icon_16x16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "icon_16x16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "icon_32x32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "icon_128x128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "icon_128x128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "icon_256x256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "icon_512x512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "icon_512x512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/apple.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "apple.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/apple.imageset/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/apple.imageset/apple.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/file_doc.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "file_doc.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/file_doc.imageset/file_doc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/file_doc.imageset/file_doc.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/icoWarning.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "icoWarning.pdf",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/icoWarning.imageset/icoWarning.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/icoWarning.imageset/icoWarning.pdf
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/ladyindark.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ladyindark.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/ladyindark.imageset/ladyindark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/ladyindark.imageset/ladyindark.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/moon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "moon.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "moon@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/moon.imageset/moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/moon.imageset/moon.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/moon.imageset/moon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/moon.imageset/moon@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/paper.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "paper.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "paper_@2X.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/paper.imageset/paper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/paper.imageset/paper.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/paper.imageset/paper_@2X.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/paper.imageset/paper_@2X.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/saturday_warmth.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "saturday_warmth.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/saturday_warmth.imageset/saturday_warmth.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/saturday_warmth.imageset/saturday_warmth.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/sun.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "sun.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "sun@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "scale" : "3x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | },
22 | "properties" : {
23 | "template-rendering-intent" : "template"
24 | }
25 | }
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/sun.imageset/sun.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/sun.imageset/sun.png
--------------------------------------------------------------------------------
/Demo/Demo/Assets.xcassets/sun.imageset/sun@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Demo/Demo/Assets.xcassets/sun.imageset/sun@2x.png
--------------------------------------------------------------------------------
/Demo/Demo/ContentViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentViewController.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 29/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class ContentViewController: NSViewController, NSTextDelegate {
13 |
14 | /// Our content view
15 | @IBOutlet var contentView: NSView!
16 |
17 | /// Our text view
18 | @IBOutlet var contentTextView: NSTextView!
19 |
20 | /// Our placeholder view when no note is selected.
21 | @IBOutlet var noSelectionPlaceholder: NSView!
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | // Setup our Text View
27 | contentTextView.textColor = ThemeColor.contentTextColor
28 | contentTextView.backgroundColor = ThemeColor.contentBackgroundColor
29 | contentTextView.drawsBackground = true
30 | contentTextView.textContainer?.lineFragmentPadding = 16
31 | var font = NSFont(name: "GillSans-Light", size: 16)
32 | if font == nil {
33 | font = NSFont.systemFont(ofSize: 14)
34 | }
35 | contentTextView.font = font
36 | let paragraphStyle = NSMutableParagraphStyle()
37 | paragraphStyle.lineSpacing = 4.4
38 | contentTextView.defaultParagraphStyle = paragraphStyle
39 |
40 | // Setup our clipping view background color (text view parent)
41 | if let clipView = contentTextView.enclosingScrollView?.contentView {
42 | clipView.backgroundColor = ThemeColor.contentBackgroundColor
43 | clipView.drawsBackground = true
44 | }
45 |
46 | // Observe note selection change notifications
47 | NotificationCenter.default.addObserver(forName: .didChangeNoteSelection, object: nil, queue: nil) { (notification) in
48 | let obj = notification.object
49 | if let viewController = obj as? NSViewController,
50 | viewController.view.window == self.view.window {
51 | self.representedObject = notification.userInfo?["note"]
52 | }
53 | }
54 |
55 | // Fix white scrollbar view when in dark theme and scrollbars are set to
56 | // always be shown on *System Preferences > General*.
57 | if let scrollView = contentTextView.enclosingScrollView {
58 | scrollView.backgroundColor = ThemeColor.contentBackgroundColor
59 | scrollView.wantsLayer = true
60 | }
61 |
62 | // Observe theme changes
63 | NotificationCenter.default.addObserver(self, selector: #selector(didChangeTheme(_:)), name: .didChangeTheme, object: nil)
64 | }
65 |
66 | override var representedObject: Any? {
67 | didSet {
68 | var subview: NSView
69 |
70 | if let note = representedObject as? Note {
71 | contentTextView.string = note.text
72 | contentTextView.scrollToBeginningOfDocument(self)
73 | noSelectionPlaceholder.removeFromSuperview()
74 | subview = contentView
75 | } else {
76 | contentTextView.string = ""
77 | contentView.removeFromSuperview()
78 | subview = noSelectionPlaceholder
79 | }
80 |
81 | self.view.addSubview(subview)
82 | subview.translatesAutoresizingMaskIntoConstraints = true
83 | subview.autoresizingMask = [NSView.AutoresizingMask.width, NSView.AutoresizingMask.height]
84 | subview.frame = view.bounds
85 | }
86 | }
87 |
88 | // MARK: -
89 | // MARK: Theme related
90 |
91 | @objc private func didChangeTheme(_ notification: Notification? = nil) {
92 | // Update `NSScroller` background color based on current theme
93 | DispatchQueue.main.async {
94 | self.contentTextView.enclosingScrollView?.verticalScroller?.layer?.backgroundColor = ThemeColor.contentBackgroundColor.cgColor
95 | }
96 |
97 | // If in fullscreen, need to re-focus current window
98 | if let window = view.window {
99 | let isWindowInFullScreen = window.styleMask.contains(NSWindow.StyleMask.fullScreen) || window.className == "NSToolbarFullScreenWindow"
100 | if isWindowInFullScreen {
101 | DispatchQueue.main.async {
102 | window.makeKey()
103 | }
104 | }
105 | }
106 | }
107 |
108 | // MARK: -
109 | // MARK: NSTextDelegate
110 |
111 | public func textDidChange(_ notification: Notification) {
112 | if let note = representedObject as? Note {
113 | note.text = contentTextView.string
114 | note.lastModified = Date()
115 | NotificationCenter.default.post(name: .didEditNoteText, object: self, userInfo: ["note": note])
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/Demo/Demo/DarkTheme+DemoSwift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DarkTheme+DemoSwift.swift
3 | // DemoSwift
4 | //
5 | // Created by Nuno Grilo on 09/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ThemeKit
11 |
12 | extension DarkTheme {
13 |
14 | // MARK: CONTENT
15 |
16 | /// Notes content title text color
17 | @objc var contentTitleColor: NSColor {
18 | return NSColor(calibratedWhite: 0.95, alpha: 1.0)
19 | }
20 |
21 | /// Notes text foreground color
22 | @objc var contentTextColor: NSColor {
23 | return NSColor.lightGray
24 | }
25 |
26 | /// Notes text background color
27 | @objc var contentBackgroundColor: NSColor {
28 | return NSColor.black
29 | }
30 |
31 | /// Rainbow gradient (used between title and text)
32 | @objc var rainbowGradient: NSGradient? {
33 | return NSGradient(starting: NSColor.green, ending: NSColor.blue)
34 | }
35 |
36 | // MARK: DETAILS
37 |
38 | /// Notes details title text color
39 | @objc var detailsTitleColor: NSColor {
40 | return NSColor(red: 0.43, green: 0.43, blue: 0.43, alpha: 1.0)
41 | }
42 |
43 | /// Notes details background color
44 | @objc var detailsBackgroundColor: NSColor {
45 | return NSColor(calibratedWhite: 1.0, alpha: 0.0)
46 | }
47 |
48 | /// Notes details image
49 | @objc var detailsImage: NSImage? {
50 | return NSImage(named: NSImage.Name("moon"))
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Demo/Demo/DetailsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailsView.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 30/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class DetailsView: NSView {
13 |
14 | /// Drawing code
15 | override func draw(_ dirtyRect: NSRect) {
16 | // Fill with details background
17 | ThemeColor.detailsBackgroundColor.set()
18 | NSBezierPath(rect: bounds).fill()
19 |
20 | // Draw theme details image
21 | let imageSize = NSSize(width: 48, height: 48)
22 | ThemeImage.detailsImage.draw(in: NSRect(x: (bounds.width - imageSize.width) / 2, y: bounds.height - 56, width: imageSize.width, height: imageSize.height))
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/Demo/Demo/DetailsViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailsViewController.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 30/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class DetailsViewController: NSViewController {
13 |
14 | // IBOutlets
15 | @IBOutlet weak var containerView: NSView!
16 | @IBOutlet weak var detailsTitleTxt: NSTextField!
17 | @IBOutlet weak var fakeControlsTitleTxt: NSTextField!
18 | @IBOutlet weak var wordCountTxt: NSTextField!
19 | @IBOutlet weak var characterCount: NSTextField!
20 | @IBOutlet weak var lastModifiedTxt: NSTextField!
21 |
22 | @objc var dateFormatter: DateFormatter?
23 |
24 | override func viewDidLoad() {
25 | super.viewDidLoad()
26 |
27 | // Setup colors
28 | detailsTitleTxt.textColor = ThemeColor.detailsTitleColor
29 | fakeControlsTitleTxt.textColor = ThemeColor.detailsTitleColor
30 |
31 | // Setup date formatter
32 | dateFormatter = DateFormatter()
33 | dateFormatter?.dateStyle = .medium
34 | dateFormatter?.timeStyle = .none
35 |
36 | // Hide stuff until we have a selected note
37 | containerView.isHidden = true
38 |
39 | // Observe note selection change notifications
40 | NotificationCenter.default.addObserver(forName: .didChangeNoteSelection, object: nil, queue: nil) { (notification) in
41 | let obj = notification.object
42 | if let viewController = obj as? NSViewController,
43 | viewController.view.window == self.view.window {
44 | self.representedObject = notification.userInfo?["note"]
45 | }
46 | }
47 |
48 | // Observe note text edit notifications
49 | NotificationCenter.default.addObserver(forName: .didEditNoteText, object: nil, queue: nil) { (notification) in
50 | let obj = notification.object
51 | if let viewController = obj as? NSViewController,
52 | viewController.view.window == self.view.window {
53 | self.representedObject = notification.userInfo?["note"]
54 | }
55 | }
56 | }
57 |
58 | override var representedObject: Any? {
59 | didSet {
60 | if let note = representedObject as? Note {
61 | containerView.isHidden = false
62 | wordCountTxt.stringValue = String(note.textWordCount)
63 | characterCount.stringValue = String(note.textCharacterCount)
64 | lastModifiedTxt.stringValue = dateFormatter?.string(from: note.lastModified) ?? "-"
65 | } else {
66 | containerView.isHidden = true
67 | }
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/Demo/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.4.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2016-2018 Paw & Nuno Grilo. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Demo/Demo/LightTheme+DemoSwift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LightTheme+DemoSwift.swift
3 | // DemoSwift
4 | //
5 | // Created by Nuno Grilo on 09/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ThemeKit
11 |
12 | extension LightTheme {
13 |
14 | // MARK: CONTENT
15 |
16 | /// Notes content title text color
17 | @objc var contentTitleColor: NSColor {
18 | return NSColor(calibratedRed: 0.15, green: 0.15, blue: 0.15, alpha: 1.0)
19 | }
20 |
21 | /// Notes text foreground color
22 | @objc var contentTextColor: NSColor {
23 | return NSColor(calibratedRed: 0.1, green: 0.1, blue: 0.1, alpha: 1.0)
24 | }
25 |
26 | /// Notes text background color
27 | @objc var contentBackgroundColor: NSColor {
28 | return NSColor(red: 1.0, green: 0.99, blue: 0.98, alpha: 1.0)
29 | }
30 |
31 | /// Rainbow gradient (used between title and text)
32 | @objc var rainbowGradient: NSGradient? {
33 | return NSGradient(starting: NSColor(calibratedRed: 0.0, green: 0.66, blue: 1.0, alpha: 1.0), ending: ThemeColor.contentBackgroundColor)
34 | }
35 |
36 | // MARK: DETAILS
37 |
38 | /// Notes details title text color
39 | @objc var detailsTitleColor: NSColor {
40 | return NSColor(red: 0.43, green: 0.43, blue: 0.43, alpha: 1.0)
41 | }
42 |
43 | /// Notes details background color
44 | @objc var detailsBackgroundColor: NSColor {
45 | return NSColor(calibratedWhite: 1.0, alpha: 0.0)
46 | }
47 |
48 | /// Notes details image
49 | @objc var detailsImage: NSImage? {
50 | return NSImage(named: NSImage.Name("sun"))
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/Demo/Demo/Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Note.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 29/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Our basic Note class.
12 | class Note: NSObject, NSCoding {
13 |
14 | /// Note title.
15 | @objc var title: String = ""
16 |
17 | /// Note text.
18 | @objc var text: String = ""
19 |
20 | /// Last modified date.
21 | @objc var lastModified: Date = Date()
22 |
23 | // Character count.
24 | @objc var textCharacterCount: Int {
25 | return text.count
26 | }
27 |
28 | // Word count.
29 | @objc var textWordCount: Int {
30 | return text.components(separatedBy: " ").count
31 | }
32 |
33 | /// Initializer
34 | @objc init(title: String = "Untitled Note", text: String = "") {
35 | self.title = title
36 | self.text = text
37 | self.lastModified = Date()
38 | }
39 |
40 | // MARK: -
41 | // MARK: NSCoder
42 |
43 | public func encode(with aCoder: NSCoder) {
44 | aCoder.encode(title, forKey: "title")
45 | aCoder.encode(text, forKey: "text")
46 | aCoder.encode(lastModified, forKey: "lastModified")
47 | }
48 |
49 | public required init?(coder aDecoder: NSCoder) {
50 | if let title = aDecoder.decodeObject(forKey: "title") as? String {
51 | self.title = title
52 | }
53 | if let text = aDecoder.decodeObject(forKey: "text") as? String {
54 | self.text = text
55 | }
56 | if let lastModified = aDecoder.decodeObject(forKey: "lastModified") as? Date {
57 | self.lastModified = lastModified
58 | }
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/Demo/Demo/NotificationName+Note.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Notification+Demo.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 29/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension Notification.Name {
12 |
13 | /// Notification sent when note selection changed.
14 | public static let didChangeNoteSelection = Notification.Name("ThemeKitDemoDidChangeNoteSelectionNotification")
15 |
16 | /// Notification sent when note title did edit.
17 | public static let didEditNoteTitle = Notification.Name("ThemeKitDemoDidEditNoteTitleNotification")
18 |
19 | /// Notification sent when note text did edit.
20 | public static let didEditNoteText = Notification.Name("ThemeKitDemoDidEditNoteTextNotification")
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/Demo/PaperTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PaperTheme.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 01/10/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class PaperTheme: NSObject, Theme {
13 |
14 | /// Light theme identifier (static).
15 | @objc public static var identifier: String = "com.luckymarmot.ThemeKit.PaperTheme"
16 |
17 | /// Unique theme identifier.
18 | public var identifier: String = PaperTheme.identifier
19 |
20 | /// Theme display name.
21 | public var displayName: String = "Paper Theme"
22 |
23 | /// Theme short display name.
24 | public var shortDisplayName: String = "Paper"
25 |
26 | /// Is this a dark theme?
27 | public var isDarkTheme: Bool = false
28 |
29 | /// Description.
30 | public override var description: String {
31 | return "<\(PaperTheme.self): \(themeDescription(self))>"
32 | }
33 |
34 | // MARK: -
35 | // MARK: Theme Assets
36 |
37 | // MARK: WINDOW
38 |
39 | @objc var windowTitleBarActiveColor: NSColor {
40 | if _windowTitleBarActiveColor == nil,
41 | let paperImage = self.paperImage {
42 | // darken paper image
43 | paperImage.lockFocus()
44 | NSColor.init(white: 0.0, alpha: 0.05).setFill()
45 | NSBezierPath(rect: NSRect(x: 0, y: 0, width: paperImage.size.width, height: paperImage.size.height)).fill()
46 | paperImage.unlockFocus()
47 |
48 | _windowTitleBarActiveColor = NSColor(patternImage: paperImage)
49 | }
50 |
51 | return _windowTitleBarActiveColor ?? defaultFallbackBackgroundColor
52 | }
53 | private var _windowTitleBarActiveColor: NSColor?
54 |
55 | @objc var windowTitleBarInactiveColor: NSColor {
56 | guard let paperImage = self.paperImage else {
57 | return defaultFallbackBackgroundColor
58 | }
59 | return NSColor(patternImage: paperImage)
60 | }
61 |
62 | // MARK: CONTENT
63 |
64 | /// Notes content title text color
65 | @objc var contentTitleColor: NSColor {
66 | return NSColor(calibratedRed: 0.15, green: 0.15, blue: 0.35, alpha: 1.0)
67 | }
68 |
69 | /// Notes text foreground color
70 | @objc var contentTextColor: NSColor {
71 | return NSColor(calibratedRed: 0.1, green: 0.1, blue: 0.3, alpha: 1.0)
72 | }
73 |
74 | /// Notes text background color
75 | @objc var contentBackgroundColor: NSColor {
76 | if _contentBackgroundColor == nil,
77 | let paperImage = self.paperImage {
78 | // lighten paper image
79 | paperImage.lockFocus()
80 | NSColor.init(white: 1.0, alpha: 0.5).setFill()
81 | NSBezierPath(rect: NSRect(x: 0, y: 0, width: paperImage.size.width, height: paperImage.size.height)).fill()
82 | paperImage.unlockFocus()
83 |
84 | _contentBackgroundColor = NSColor(patternImage: paperImage)
85 | }
86 |
87 | return _contentBackgroundColor ?? defaultFallbackBackgroundColor
88 | }
89 | private var _contentBackgroundColor: NSColor?
90 |
91 | /// Rainbow gradient (used between title and text)
92 | @objc var rainbowGradient: NSGradient? {
93 | let blue = NSColor(calibratedRed: 0.18, green: 0.45, blue: 0.88, alpha: 1.0)
94 | let clear = NSColor(calibratedWhite: 1.0, alpha: 0.0)
95 | return NSGradient(colorsAndLocations: (blue, 0.0), (clear, 0.66))
96 | }
97 |
98 | // MARK: DETAILS
99 |
100 | /// Notes details title text color
101 | @objc var detailsTitleColor: NSColor {
102 | return contentTitleColor
103 | }
104 |
105 | /// Notes details background color
106 | @objc var detailsBackgroundColor: NSColor {
107 | guard let paperImage = self.paperImage else {
108 | return defaultFallbackBackgroundColor
109 | }
110 | return NSColor(patternImage: paperImage)
111 | }
112 |
113 | /// Notes details image
114 | @objc var detailsImage: NSImage? {
115 | return NSImage(named: NSImage.Name("file_doc"))
116 | }
117 |
118 | // MARK: SYSTEM OVERRIDE
119 |
120 | @objc var secondaryLabelColor: NSColor {
121 | return NSColor(calibratedRed: 0.18, green: 0.45, blue: 0.88, alpha: 1.0)
122 | }
123 |
124 | // MARK: SHARED
125 |
126 | @objc var paperImage: NSImage? {
127 | return Bundle.main.image(forResource: NSImage.Name("paper"))
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/Demo/Demo/SidebarViewController+Actions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSViewController+GlobalActions.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 01/10/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSViewController {
12 |
13 | @IBAction func addNote(_ sender: NSButton) {
14 | NSApplication.sidebarViewController?.addNote(sender)
15 | }
16 |
17 | @IBAction func deleteNote(_ sender: NSButton) {
18 | NSApplication.sidebarViewController?.deleteNote(sender)
19 | }
20 |
21 | @IBAction func resetNotes(_ sender: Any) {
22 | NSApplication.sidebarViewController?.resetNotes(sender)
23 | }
24 |
25 | }
26 |
27 | extension NSWindowController {
28 |
29 | @IBAction func addNote(_ sender: NSButton) {
30 | NSApplication.sidebarViewController?.addNote(sender)
31 | }
32 |
33 | @IBAction func deleteNote(_ sender: NSButton) {
34 | NSApplication.sidebarViewController?.deleteNote(sender)
35 | }
36 |
37 | @IBAction func resetNotes(_ sender: Any) {
38 | NSApplication.sidebarViewController?.resetNotes(sender)
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/Demo/Demo/ThemeColor+DemoSwift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeColor+DemoSwift.swift
3 | // DemoSwift
4 | //
5 | // Created by Nuno Grilo on 08/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ThemeKit
11 |
12 | extension ThemeColor {
13 |
14 | // MARK: WINDOW
15 |
16 | /// Active window title bar color
17 | @objc static var windowTitleBarActiveColor: ThemeColor {
18 | return ThemeColor.color(with: #function)
19 | }
20 |
21 | /// Inactive window title bar color
22 | @objc static var windowTitleBarInactiveColor: ThemeColor {
23 | return ThemeColor.color(with: #function)
24 | }
25 |
26 | // MARK: CONTENT
27 |
28 | /// Notes content title text color
29 | @objc static var contentTitleColor: ThemeColor {
30 | return ThemeColor.color(with: #function)
31 | }
32 |
33 | /// Notes text foreground color
34 | @objc static var contentTextColor: ThemeColor {
35 | return ThemeColor.color(with: #function)
36 | }
37 |
38 | /// Notes text background color
39 | @objc static var contentBackgroundColor: ThemeColor {
40 | return ThemeColor.color(with: #function)
41 | }
42 |
43 | // MARK: DETAILS
44 |
45 | /// Notes details title text color
46 | @objc static var detailsTitleColor: ThemeColor {
47 | return ThemeColor.color(with: #function)
48 | }
49 |
50 | /// Notes details background color
51 | @objc static var detailsBackgroundColor: ThemeColor {
52 | return ThemeColor.color(with: #function)
53 | }
54 |
55 | // MARK: SYSTEM OVERRIDE
56 |
57 | override open class var labelColor: ThemeColor {
58 | return ThemeColor.color(with: #function)
59 | }
60 |
61 | override open class var secondaryLabelColor: ThemeColor {
62 | return ThemeColor.color(with: #function)
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/Demo/Demo/ThemeGradient+DemoSwift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeGradient+DemoSwift.swift
3 | // DemoSwift
4 | //
5 | // Created by Nuno Grilo on 08/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ThemeKit
11 |
12 | extension ThemeGradient {
13 |
14 | // MARK: CONTENT
15 |
16 | /// Rainbow gradient (used between title and text)
17 | @objc static var rainbowGradient: ThemeGradient? {
18 | return ThemeGradient.gradient(with: #function)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/Demo/ThemeImage+DemoSwift.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeFont+DemoSwift.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 03/10/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import ThemeKit
11 |
12 | extension ThemeImage {
13 |
14 | // MARK: DETAILS
15 |
16 | /// Notes details image
17 | @objc static var detailsImage: ThemeImage {
18 | return ThemeImage.image(with: #function)
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Demo/Demo/TitleBarOverlayView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TitleBarOverlayView.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 11/07/2017.
6 | // Copyright © 2017 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class TitleBarOverlayView: NSView {
13 |
14 | /// Drawing code
15 | override func draw(_ dirtyRect: NSRect) {
16 | let theme = ThemeManager.shared.theme
17 | let windowIsActive = window?.isKeyWindow ?? false
18 | let isWindowInFullScreen = window?.styleMask.contains(NSWindow.StyleMask.fullScreen) ?? false
19 | || (window?.className ?? "") == "NSToolbarFullScreenWindow"
20 |
21 | if (windowIsActive || isWindowInFullScreen),
22 | let color = theme.themeAsset("windowTitleBarActiveColor") as? NSColor {
23 | // Fill with 'active' color
24 | color.set()
25 | NSBezierPath(rect: bounds).fill()
26 | } else if !windowIsActive,
27 | let color = theme.themeAsset("windowTitleBarInactiveColor") as? NSColor {
28 | // Fill with 'inactive' color
29 | color.set()
30 | NSBezierPath(rect: bounds).fill()
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Demo/Demo/TitleView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GradientView.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 30/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class TitleView: NSView, NSTextFieldDelegate {
13 |
14 | /// Our text field
15 | @IBOutlet weak var titleTextField: NSTextField!
16 |
17 | /// Weak reference to selected Note
18 | @objc weak var note: Note?
19 |
20 | override func awakeFromNib() {
21 | // Setup title text
22 | titleTextField.textColor = ThemeColor.contentTitleColor
23 | titleTextField.backgroundColor = ThemeColor.contentBackgroundColor
24 | titleTextField.drawsBackground = true
25 | titleTextField.delegate = self
26 | var font = NSFont(name: "GillSans-Light", size: 24)
27 | if font == nil {
28 | font = NSFont.systemFont(ofSize: 24)
29 | }
30 | titleTextField.font = font
31 |
32 | // Observe note selection change notifications
33 | NotificationCenter.default.addObserver(forName: .didChangeNoteSelection, object: nil, queue: nil) { (notification) in
34 | let obj = notification.object
35 | if let viewController = obj as? NSViewController,
36 | viewController.view.window == self.window,
37 | let note = notification.userInfo?["note"] as? Note {
38 |
39 | self.note = note
40 | self.titleTextField.stringValue = note.title
41 | }
42 | }
43 |
44 | // Observe note title change notifications
45 | NotificationCenter.default.addObserver(forName: .didEditNoteTitle, object: nil, queue: nil) { (_) in
46 | if let note = self.note {
47 | self.titleTextField.stringValue = note.title
48 | }
49 | }
50 | }
51 |
52 | /// Drawing code
53 | override func draw(_ dirtyRect: NSRect) {
54 | guard note?.title != nil else {
55 | return
56 | }
57 |
58 | // Fill with content background
59 | ThemeColor.contentBackgroundColor.set()
60 | NSBezierPath(rect: bounds).fill()
61 |
62 | // Draw gradient
63 | let gradientFrame = NSRect(x: 4, y: 0, width: bounds.width - 8, height: 4)
64 | if let gradient = ThemeGradient.rainbowGradient {
65 | gradient.draw(in: gradientFrame, angle: 0)
66 | }
67 |
68 | }
69 |
70 | // MARK: -
71 | // MARK: NSTextDelegate
72 |
73 | func controlTextDidChange(_ obj: Notification) {
74 | guard let note = self.note else {
75 | return
76 | }
77 | note.title = titleTextField.stringValue
78 | note.lastModified = Date()
79 | NotificationCenter.default.post(name: .didEditNoteTitle, object: self, userInfo: ["note": note])
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/Demo/Demo/WindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WindowController.swift
3 | // Demo
4 | //
5 | // Created by Nuno Grilo on 29/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ThemeKit
11 |
12 | class WindowController: NSWindowController {
13 |
14 | @objc public var themeKit: ThemeManager = ThemeManager.shared
15 |
16 | override func windowDidLoad() {
17 | // Observe note selection change notifications
18 | NotificationCenter.default.addObserver(forName: .didChangeNoteSelection, object: nil, queue: nil) { (notification) in
19 | let obj = notification.object
20 | if let note = notification.userInfo?["note"] as? Note,
21 | let viewController = obj as? NSViewController,
22 | viewController.view.window == self.window {
23 | self.updateTitle(note)
24 | }
25 | }
26 |
27 | // Observe note text edit notifications
28 | NotificationCenter.default.addObserver(forName: .didEditNoteText, object: nil, queue: nil) { (notification) in
29 | let obj = notification.object
30 | if let note = notification.userInfo?["note"] as? Note,
31 | let viewController = obj as? NSViewController,
32 | viewController.view.window == self.window {
33 | self.updateTitle(note)
34 | }
35 | }
36 |
37 | // Observe theme change notifications
38 | NotificationCenter.default.addObserver(forName: .didChangeTheme, object: nil, queue: nil) { (_) in
39 | // update KVO property
40 | self.willChangeValue(forKey: "canEditTheme")
41 | self.didChangeValue(forKey: "canEditTheme")
42 | }
43 | }
44 |
45 | /// Add titlebar overlay view.
46 | override func awakeFromNib() {
47 | super.awakeFromNib()
48 |
49 | if let titlebarView = self.titlebarView {
50 | // create titlebar background overlay view
51 | let overlayView = TitleBarOverlayView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
52 | overlayView.translatesAutoresizingMaskIntoConstraints = false
53 |
54 | // add overlay view below everything else
55 | titlebarView.addSubview(overlayView, positioned: .below, relativeTo: nil)
56 |
57 | // add constraints
58 | let constraintViews = ["view": overlayView]
59 | titlebarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view]-0-|", options: [NSLayoutConstraint.FormatOptions.directionLeadingToTrailing], metrics: nil, views: constraintViews))
60 | titlebarView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]-0-|", options: [NSLayoutConstraint.FormatOptions.directionLeadingToTrailing], metrics: nil, views: constraintViews))
61 |
62 | // refresh it when key flag changes
63 | NotificationCenter.default.addObserver(forName: NSWindow.didBecomeKeyNotification, object: window, queue: nil, using: { _ in
64 | overlayView.needsDisplay = true
65 | })
66 | NotificationCenter.default.addObserver(forName: NSWindow.didResignKeyNotification, object: window, queue: nil, using: { _ in
67 | overlayView.needsDisplay = true
68 | })
69 | }
70 | }
71 |
72 | /// Find `NSTitlebarContainerView` view.
73 | private var titlebarView: NSView? {
74 | if let themeFrame = self.window?.contentView?.superview {
75 | for subview in themeFrame.subviews where subview.className == "NSTitlebarContainerView" {
76 | return subview.subviews.first { $0.className == "NSTitlebarView" }
77 | }
78 | }
79 | return nil
80 | }
81 |
82 | /// Update window title with current note title.
83 | @objc func updateTitle(_ note: Note) {
84 | self.window?.title = "\(note.title) - ThemeKit Demo"
85 | }
86 |
87 | /// Can edit current theme (must be a `UserTheme`).
88 | @objc var canEditTheme: Bool {
89 | return ThemeManager.shared.theme.isUserTheme
90 | }
91 |
92 | /// Edit current (`UserTheme`) theme.
93 | @IBAction func editTheme(_ sender: Any) {
94 | if ThemeManager.shared.theme.isUserTheme,
95 | let userTheme = ThemeManager.shared.theme as? UserTheme,
96 | let userThemeURL = userTheme.fileURL {
97 |
98 | guard FileManager.default.isWritableFile(atPath: userThemeURL.path) else {
99 | let alert = NSAlert()
100 | alert.messageText = "Theme file is not writable."
101 | alert.informativeText = "If you're lunching Demo from the Downloads folder, move it to another place and try again."
102 | alert.alertStyle = NSAlert.Style.critical
103 | alert.addButton(withTitle: "OK")
104 | alert.runModal()
105 | return
106 | }
107 |
108 | // check if there is any app associted with `.theme` extension
109 | let userThemeCFURL: CFURL = userThemeURL as CFURL
110 | if LSCopyDefaultApplicationURLForURL(userThemeCFURL, .editor, nil) != nil {
111 | NSWorkspace.shared.open(userThemeURL)
112 | } else {
113 | // otherwise open with TextEdit
114 | NSWorkspace.shared.openFile(userThemeURL.path, withApplication: "TextEdit", andDeactivate: true)
115 | }
116 |
117 | }
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/Demo/Themes/LadyInDark.theme:
--------------------------------------------------------------------------------
1 | // ************************** Credits *************************** //
2 | // - "Lady in dark" pattern
3 | // Author: http://www.colourlovers.com/lover/Tenshiko/loveNote
4 | // URL: http://www.colourlovers.com/pattern/1758170/Lady_in_dark
5 |
6 | // ************************* Theme Info ************************* //
7 | displayName = Lady in Dark Theme
8 | shortDisplayName = Lady in Dark
9 | identifier = com.luckymarmot.ThemeKit.LadyInDark
10 | darkTheme = true
11 |
12 | // *************************** Window ************************** //
13 | windowTitleBarActiveColor = $sexy.latex
14 | windowTitleBarInactiveColor = $intellektuelle.black
15 |
16 | // *************************** Content ************************** //
17 | contentTitleColor = $text.lightgray
18 | contentTextColor = $text.gray
19 | contentBackgroundColor = $take.a.bow
20 | rainbowGradient = linear-gradient($text.darkgray, $sexy.latex)
21 |
22 | // *************************** Details ************************** //
23 | detailsTitleColor = $text.gray
24 | detailsBackgroundColor = pattern(named:ladyindark)
25 | detailsImage = image(named:moon)
26 |
27 | // *********************** System Override ********************** //
28 | secondaryLabelColor = $text.darkgray
29 |
30 | // ************** 'Lady in dark' Pattern Colors ***************** //
31 | sexy.latex = rgb(15, 15, 15)
32 | take.a.bow = rgb(23, 23, 23)
33 | dislike.black = rgb(28, 28, 28)
34 | intellektuelle.black = rgb(36, 36, 36)
35 | coal.mine = rgb(44, 44, 44)
36 |
37 | // *********************** Common Colors ************************ //
38 | text.darkgray = rgb(85, 85, 85)
39 | text.gray = rgb(107, 107, 107)
40 | text.lightgray = rgb(187, 187, 187)
41 |
42 | // ********************** Fallback Assets *********************** //
43 | fallbackForegroundColor = $text.lightgray
44 | fallbackGradient = $rainbowGradient
45 |
--------------------------------------------------------------------------------
/Demo/Themes/PurpleGreen.theme:
--------------------------------------------------------------------------------
1 | // ************************* Theme Info ************************* //
2 | displayName = Purple Green Theme
3 | shortDisplayName = Purple Green
4 | identifier = com.luckymarmot.ThemeKit.PurpleGreen
5 | darkTheme = true
6 |
7 | // *************************** Window ************************** //
8 | windowTitleBarActiveColor= $purple2
9 | windowTitleBarInactiveColor = $purple3
10 |
11 | // *************************** Content ************************** //
12 | contentTitleColor = $vivid.green
13 | contentTextColor = $dark.green
14 | contentBackgroundColor = $black1
15 | //contentBackgroundColor = pattern(named:paper)
16 | rainbowGradient = linear-gradient($purple, $vivid.green)
17 |
18 | // *************************** Details ************************** //
19 | detailsTitleColor = $dark.green
20 | detailsBackgroundColor = $purple2
21 | detailsImage = image(named:apple)
22 | //detailsImage2 = image(file:../some/path/some-file.jpg)
23 |
24 | // *********************** System Override ********************** //
25 | labelColor = $vivid.green
26 | secondaryLabelColor = $dark.green
27 |
28 | // *********************** Common Colors ************************ //
29 | blue = rgb(0, 170, 255)
30 | vivid.green = rgb(230, 255, 20)
31 | vivid.darker.green = rgb(200, 225, 0)
32 | dark.green = rgb(130, 155, 80)
33 | lime.green = rgb(250, 190, 45, .5)
34 | black1 = rgb(5, 25, 5)
35 | purple = rgb(175, 27, 230)
36 | purple2 = rgb(175, 27, 230, 0.25)
37 | purple3 = rgb(175, 27, 230, 0.15)
38 | lime.green2 = rgb(120, 233, 47)
39 | clear = rgb(0,0,0,0)
40 |
41 | // ********************** Fallback Assets *********************** //
42 | fallbackForegroundColor = rgb(255, 10, 90, 1.0)
43 | fallbackGradient = linear-gradient($blue, rgba(200, 140, 60, 1.0))
44 |
45 |
--------------------------------------------------------------------------------
/Demo/Themes/SaturdayWarmth.theme:
--------------------------------------------------------------------------------
1 | // ************************** Credits *************************** //
2 | // - "saturday warmth" pattern
3 | // Author: http://www.colourlovers.com/lover/logochic/loveNote
4 | // URL: http://www.colourlovers.com/pattern/582552/saturday_warmth
5 |
6 | // ************************* Theme Info ************************* //
7 | displayName = Saturday Warmth Theme
8 | shortDisplayName = Saturday Warmth
9 | identifier = com.luckymarmot.ThemeKit.SaturdayWarmth
10 | darkTheme = false
11 |
12 | // *************************** Window ************************** //
13 | windowTitleBarActiveColor = $black.tea
14 | windowTitleBarInactiveColor = rgba(252, 235, 182, 0.60)
15 |
16 | // *************************** Content ************************** //
17 | contentTitleColor = $history
18 | contentTextColor = $history
19 | contentBackgroundColor = $white
20 | rainbowGradient = linear-gradient($full.orange, $tweaked.turqouise)
21 |
22 | // *************************** Details ************************** //
23 | detailsTitleColor = $tweaked.turqouise
24 | detailsBackgroundColor = $black.tea
25 | detailsImage = image(named:saturday_warmth)
26 |
27 | // *********************** Colors ******************************* //
28 | black.tea = rgb(252, 235, 182)
29 | history = rgb(94, 65, 47)
30 | full.orange = rgb(240, 120, 24)
31 | tweaked.turqouise = rgb(120, 192, 168)
32 | joystiq.gold = rgb(240, 168, 48)
33 | clear = rgba(0,0,0,0)
34 | white = rgb(255,255,255)
35 |
36 | // ********************** Fallback Assets *********************** //
37 | fallbackForegroundColor = $history
38 | fallbackGradient = linear-gradient($history, $full.orange)
39 |
--------------------------------------------------------------------------------
/Docs/Extensions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Extensions Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | Extensions Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | Extensions
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
115 |
116 |
117 |
118 | NSWindow
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
NSWindow
ThemeKit extension.
127 |
128 |
See more
129 |
130 |
131 |
Declaration
132 |
133 |
Swift
134 |
public extension NSWindow
135 |
136 |
137 |
138 |
139 |
140 |
141 | -
142 |
143 |
144 |
145 |
146 | NSColor
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
NSColor
ThemeKit extension to help on when overriding colors in ThemeColor
extensions.
155 |
156 |
See more
157 |
158 |
159 |
Declaration
160 |
161 |
Swift
162 |
extension NSColor
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/Docs/Extensions/NSColor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NSColor Extension Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | NSColor Extension Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | NSColor
108 |
109 |
110 |
111 |
extension NSColor
112 |
113 |
114 |
115 | NSColor
ThemeKit extension to help on when overriding colors in ThemeColor
extensions.
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Color override
126 |
127 |
128 |
129 |
130 | -
131 |
138 |
139 |
140 |
141 |
142 |
143 |
Check if color is being overriden in a ThemeColor extension.
144 |
145 |
146 |
147 |
Declaration
148 |
149 |
Swift
150 |
@objc
151 | public var isThemeOverriden: Bool { get }
152 |
153 |
154 |
155 |
158 |
159 |
160 |
161 | -
162 |
169 |
170 |
171 |
172 |
173 |
174 |
Get all NSColor
color methods.
175 | Overridable class methods (can be overriden in ThemeColor
extension).
176 |
177 |
178 |
179 |
Declaration
180 |
181 |
Swift
182 |
@objc
183 | public class func colorMethodNames() -> [String]
184 |
185 |
186 |
187 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/Docs/Extensions/Notification.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Notification Extension Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | Notification Extension Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | Notification
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
115 |
116 |
117 |
118 | Name
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
Notifications defined by ThemeKit
.
127 |
128 |
See more
129 |
130 |
131 |
Declaration
132 |
133 |
Swift
134 |
public extension Notification.Name
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/Docs/ThemeKit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ThemeKit Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | ThemeKit Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | ThemeKit
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
121 |
122 |
123 |
124 |
125 |
126 |
Use ThemeManager
shared instance to perform app-wide theming related operations,
127 | such as:
128 |
129 |
130 | - Get information about current theme/appearance
131 | - Change current
theme
(can also be changed from NSUserDefaults
)
132 | - List available themes
133 | - Define
ThemeKit
behaviour
134 |
135 |
136 |
See more
137 |
138 |
139 |
Declaration
140 |
141 |
Swift
142 |
@objc(TKThemeManager)
143 | public class ThemeManager : NSObject
144 |
145 |
146 |
147 |
150 |
151 |
152 |
153 | -
154 |
161 |
171 |
172 |
173 |
174 |
175 |
176 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/Docs/css/highlight.css:
--------------------------------------------------------------------------------
1 | /* Credit to https://gist.github.com/wataru420/2048287 */
2 | .highlight {
3 | /* Comment */
4 | /* Error */
5 | /* Keyword */
6 | /* Operator */
7 | /* Comment.Multiline */
8 | /* Comment.Preproc */
9 | /* Comment.Single */
10 | /* Comment.Special */
11 | /* Generic.Deleted */
12 | /* Generic.Deleted.Specific */
13 | /* Generic.Emph */
14 | /* Generic.Error */
15 | /* Generic.Heading */
16 | /* Generic.Inserted */
17 | /* Generic.Inserted.Specific */
18 | /* Generic.Output */
19 | /* Generic.Prompt */
20 | /* Generic.Strong */
21 | /* Generic.Subheading */
22 | /* Generic.Traceback */
23 | /* Keyword.Constant */
24 | /* Keyword.Declaration */
25 | /* Keyword.Pseudo */
26 | /* Keyword.Reserved */
27 | /* Keyword.Type */
28 | /* Literal.Number */
29 | /* Literal.String */
30 | /* Name.Attribute */
31 | /* Name.Builtin */
32 | /* Name.Class */
33 | /* Name.Constant */
34 | /* Name.Entity */
35 | /* Name.Exception */
36 | /* Name.Function */
37 | /* Name.Namespace */
38 | /* Name.Tag */
39 | /* Name.Variable */
40 | /* Operator.Word */
41 | /* Text.Whitespace */
42 | /* Literal.Number.Float */
43 | /* Literal.Number.Hex */
44 | /* Literal.Number.Integer */
45 | /* Literal.Number.Oct */
46 | /* Literal.String.Backtick */
47 | /* Literal.String.Char */
48 | /* Literal.String.Doc */
49 | /* Literal.String.Double */
50 | /* Literal.String.Escape */
51 | /* Literal.String.Heredoc */
52 | /* Literal.String.Interpol */
53 | /* Literal.String.Other */
54 | /* Literal.String.Regex */
55 | /* Literal.String.Single */
56 | /* Literal.String.Symbol */
57 | /* Name.Builtin.Pseudo */
58 | /* Name.Variable.Class */
59 | /* Name.Variable.Global */
60 | /* Name.Variable.Instance */
61 | /* Literal.Number.Integer.Long */ }
62 | .highlight .c {
63 | color: #999988;
64 | font-style: italic; }
65 | .highlight .err {
66 | color: #a61717;
67 | background-color: #e3d2d2; }
68 | .highlight .k {
69 | color: #000000;
70 | font-weight: bold; }
71 | .highlight .o {
72 | color: #000000;
73 | font-weight: bold; }
74 | .highlight .cm {
75 | color: #999988;
76 | font-style: italic; }
77 | .highlight .cp {
78 | color: #999999;
79 | font-weight: bold; }
80 | .highlight .c1 {
81 | color: #999988;
82 | font-style: italic; }
83 | .highlight .cs {
84 | color: #999999;
85 | font-weight: bold;
86 | font-style: italic; }
87 | .highlight .gd {
88 | color: #000000;
89 | background-color: #ffdddd; }
90 | .highlight .gd .x {
91 | color: #000000;
92 | background-color: #ffaaaa; }
93 | .highlight .ge {
94 | color: #000000;
95 | font-style: italic; }
96 | .highlight .gr {
97 | color: #aa0000; }
98 | .highlight .gh {
99 | color: #999999; }
100 | .highlight .gi {
101 | color: #000000;
102 | background-color: #ddffdd; }
103 | .highlight .gi .x {
104 | color: #000000;
105 | background-color: #aaffaa; }
106 | .highlight .go {
107 | color: #888888; }
108 | .highlight .gp {
109 | color: #555555; }
110 | .highlight .gs {
111 | font-weight: bold; }
112 | .highlight .gu {
113 | color: #aaaaaa; }
114 | .highlight .gt {
115 | color: #aa0000; }
116 | .highlight .kc {
117 | color: #000000;
118 | font-weight: bold; }
119 | .highlight .kd {
120 | color: #000000;
121 | font-weight: bold; }
122 | .highlight .kp {
123 | color: #000000;
124 | font-weight: bold; }
125 | .highlight .kr {
126 | color: #000000;
127 | font-weight: bold; }
128 | .highlight .kt {
129 | color: #445588; }
130 | .highlight .m {
131 | color: #009999; }
132 | .highlight .s {
133 | color: #d14; }
134 | .highlight .na {
135 | color: #008080; }
136 | .highlight .nb {
137 | color: #0086B3; }
138 | .highlight .nc {
139 | color: #445588;
140 | font-weight: bold; }
141 | .highlight .no {
142 | color: #008080; }
143 | .highlight .ni {
144 | color: #800080; }
145 | .highlight .ne {
146 | color: #990000;
147 | font-weight: bold; }
148 | .highlight .nf {
149 | color: #990000; }
150 | .highlight .nn {
151 | color: #555555; }
152 | .highlight .nt {
153 | color: #000080; }
154 | .highlight .nv {
155 | color: #008080; }
156 | .highlight .ow {
157 | color: #000000;
158 | font-weight: bold; }
159 | .highlight .w {
160 | color: #bbbbbb; }
161 | .highlight .mf {
162 | color: #009999; }
163 | .highlight .mh {
164 | color: #009999; }
165 | .highlight .mi {
166 | color: #009999; }
167 | .highlight .mo {
168 | color: #009999; }
169 | .highlight .sb {
170 | color: #d14; }
171 | .highlight .sc {
172 | color: #d14; }
173 | .highlight .sd {
174 | color: #d14; }
175 | .highlight .s2 {
176 | color: #d14; }
177 | .highlight .se {
178 | color: #d14; }
179 | .highlight .sh {
180 | color: #d14; }
181 | .highlight .si {
182 | color: #d14; }
183 | .highlight .sx {
184 | color: #d14; }
185 | .highlight .sr {
186 | color: #009926; }
187 | .highlight .s1 {
188 | color: #d14; }
189 | .highlight .ss {
190 | color: #990073; }
191 | .highlight .bp {
192 | color: #999999; }
193 | .highlight .vc {
194 | color: #008080; }
195 | .highlight .vg {
196 | color: #008080; }
197 | .highlight .vi {
198 | color: #008080; }
199 | .highlight .il {
200 | color: #009999; }
201 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleIdentifier
6 | com.jazzy.themekit
7 | CFBundleName
8 | ThemeKit
9 | DocSetPlatformFamily
10 | themekit
11 | isDashDocset
12 |
13 | dashIndexFilePath
14 | index.html
15 | isJavaScriptEnabled
16 |
17 | DashDocSetFamily
18 | dashtoc
19 | DashDocSetFallbackURL
20 | http://themekit.nunogrilo.com/
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/Extensions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Extensions Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | Extensions Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | Extensions
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
115 |
116 |
117 |
118 | NSWindow
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
NSWindow
ThemeKit extension.
127 |
128 |
See more
129 |
130 |
131 |
Declaration
132 |
133 |
Swift
134 |
public extension NSWindow
135 |
136 |
137 |
138 |
139 |
140 |
141 | -
142 |
143 |
144 |
145 |
146 | NSColor
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
NSColor
ThemeKit extension to help on when overriding colors in ThemeColor
extensions.
155 |
156 |
See more
157 |
158 |
159 |
Declaration
160 |
161 |
Swift
162 |
extension NSColor
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
177 |
178 |
179 |
180 |
181 |
182 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/Extensions/NSColor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | NSColor Extension Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | NSColor Extension Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | NSColor
108 |
109 |
110 |
111 |
extension NSColor
112 |
113 |
114 |
115 | NSColor
ThemeKit extension to help on when overriding colors in ThemeColor
extensions.
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Color override
126 |
127 |
128 |
129 |
130 | -
131 |
138 |
139 |
140 |
141 |
142 |
143 |
Check if color is being overriden in a ThemeColor extension.
144 |
145 |
146 |
147 |
Declaration
148 |
149 |
Swift
150 |
@objc
151 | public var isThemeOverriden: Bool { get }
152 |
153 |
154 |
155 |
158 |
159 |
160 |
161 | -
162 |
169 |
170 |
171 |
172 |
173 |
174 |
Get all NSColor
color methods.
175 | Overridable class methods (can be overriden in ThemeColor
extension).
176 |
177 |
178 |
179 |
Declaration
180 |
181 |
Swift
182 |
@objc
183 | public class func colorMethodNames() -> [String]
184 |
185 |
186 |
187 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
201 |
202 |
203 |
204 |
205 |
206 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/Extensions/Notification.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Notification Extension Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | Notification Extension Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | Notification
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
115 |
116 |
117 |
118 | Name
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
Notifications defined by ThemeKit
.
127 |
128 |
See more
129 |
130 |
131 |
Declaration
132 |
133 |
Swift
134 |
public extension Notification.Name
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
149 |
150 |
151 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/ThemeKit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ThemeKit Reference
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
30 |
31 |
32 | ThemeKit Reference
33 |
34 | ThemeKit Reference
35 |
36 |
37 |
38 |
104 |
105 |
106 |
107 | ThemeKit
108 |
109 |
110 |
111 |
112 |
113 | -
114 |
121 |
122 |
123 |
124 |
125 |
126 |
Use ThemeManager
shared instance to perform app-wide theming related operations,
127 | such as:
128 |
129 |
130 | - Get information about current theme/appearance
131 | - Change current
theme
(can also be changed from NSUserDefaults
)
132 | - List available themes
133 | - Define
ThemeKit
behaviour
134 |
135 |
136 |
See more
137 |
138 |
139 |
Declaration
140 |
141 |
Swift
142 |
@objc(TKThemeManager)
143 | public class ThemeManager : NSObject
144 |
145 |
146 |
147 |
150 |
151 |
152 |
153 | -
154 |
161 |
171 |
172 |
173 |
174 |
175 |
176 |
180 |
181 |
182 |
183 |
184 |
185 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/css/highlight.css:
--------------------------------------------------------------------------------
1 | /* Credit to https://gist.github.com/wataru420/2048287 */
2 | .highlight {
3 | /* Comment */
4 | /* Error */
5 | /* Keyword */
6 | /* Operator */
7 | /* Comment.Multiline */
8 | /* Comment.Preproc */
9 | /* Comment.Single */
10 | /* Comment.Special */
11 | /* Generic.Deleted */
12 | /* Generic.Deleted.Specific */
13 | /* Generic.Emph */
14 | /* Generic.Error */
15 | /* Generic.Heading */
16 | /* Generic.Inserted */
17 | /* Generic.Inserted.Specific */
18 | /* Generic.Output */
19 | /* Generic.Prompt */
20 | /* Generic.Strong */
21 | /* Generic.Subheading */
22 | /* Generic.Traceback */
23 | /* Keyword.Constant */
24 | /* Keyword.Declaration */
25 | /* Keyword.Pseudo */
26 | /* Keyword.Reserved */
27 | /* Keyword.Type */
28 | /* Literal.Number */
29 | /* Literal.String */
30 | /* Name.Attribute */
31 | /* Name.Builtin */
32 | /* Name.Class */
33 | /* Name.Constant */
34 | /* Name.Entity */
35 | /* Name.Exception */
36 | /* Name.Function */
37 | /* Name.Namespace */
38 | /* Name.Tag */
39 | /* Name.Variable */
40 | /* Operator.Word */
41 | /* Text.Whitespace */
42 | /* Literal.Number.Float */
43 | /* Literal.Number.Hex */
44 | /* Literal.Number.Integer */
45 | /* Literal.Number.Oct */
46 | /* Literal.String.Backtick */
47 | /* Literal.String.Char */
48 | /* Literal.String.Doc */
49 | /* Literal.String.Double */
50 | /* Literal.String.Escape */
51 | /* Literal.String.Heredoc */
52 | /* Literal.String.Interpol */
53 | /* Literal.String.Other */
54 | /* Literal.String.Regex */
55 | /* Literal.String.Single */
56 | /* Literal.String.Symbol */
57 | /* Name.Builtin.Pseudo */
58 | /* Name.Variable.Class */
59 | /* Name.Variable.Global */
60 | /* Name.Variable.Instance */
61 | /* Literal.Number.Integer.Long */ }
62 | .highlight .c {
63 | color: #999988;
64 | font-style: italic; }
65 | .highlight .err {
66 | color: #a61717;
67 | background-color: #e3d2d2; }
68 | .highlight .k {
69 | color: #000000;
70 | font-weight: bold; }
71 | .highlight .o {
72 | color: #000000;
73 | font-weight: bold; }
74 | .highlight .cm {
75 | color: #999988;
76 | font-style: italic; }
77 | .highlight .cp {
78 | color: #999999;
79 | font-weight: bold; }
80 | .highlight .c1 {
81 | color: #999988;
82 | font-style: italic; }
83 | .highlight .cs {
84 | color: #999999;
85 | font-weight: bold;
86 | font-style: italic; }
87 | .highlight .gd {
88 | color: #000000;
89 | background-color: #ffdddd; }
90 | .highlight .gd .x {
91 | color: #000000;
92 | background-color: #ffaaaa; }
93 | .highlight .ge {
94 | color: #000000;
95 | font-style: italic; }
96 | .highlight .gr {
97 | color: #aa0000; }
98 | .highlight .gh {
99 | color: #999999; }
100 | .highlight .gi {
101 | color: #000000;
102 | background-color: #ddffdd; }
103 | .highlight .gi .x {
104 | color: #000000;
105 | background-color: #aaffaa; }
106 | .highlight .go {
107 | color: #888888; }
108 | .highlight .gp {
109 | color: #555555; }
110 | .highlight .gs {
111 | font-weight: bold; }
112 | .highlight .gu {
113 | color: #aaaaaa; }
114 | .highlight .gt {
115 | color: #aa0000; }
116 | .highlight .kc {
117 | color: #000000;
118 | font-weight: bold; }
119 | .highlight .kd {
120 | color: #000000;
121 | font-weight: bold; }
122 | .highlight .kp {
123 | color: #000000;
124 | font-weight: bold; }
125 | .highlight .kr {
126 | color: #000000;
127 | font-weight: bold; }
128 | .highlight .kt {
129 | color: #445588; }
130 | .highlight .m {
131 | color: #009999; }
132 | .highlight .s {
133 | color: #d14; }
134 | .highlight .na {
135 | color: #008080; }
136 | .highlight .nb {
137 | color: #0086B3; }
138 | .highlight .nc {
139 | color: #445588;
140 | font-weight: bold; }
141 | .highlight .no {
142 | color: #008080; }
143 | .highlight .ni {
144 | color: #800080; }
145 | .highlight .ne {
146 | color: #990000;
147 | font-weight: bold; }
148 | .highlight .nf {
149 | color: #990000; }
150 | .highlight .nn {
151 | color: #555555; }
152 | .highlight .nt {
153 | color: #000080; }
154 | .highlight .nv {
155 | color: #008080; }
156 | .highlight .ow {
157 | color: #000000;
158 | font-weight: bold; }
159 | .highlight .w {
160 | color: #bbbbbb; }
161 | .highlight .mf {
162 | color: #009999; }
163 | .highlight .mh {
164 | color: #009999; }
165 | .highlight .mi {
166 | color: #009999; }
167 | .highlight .mo {
168 | color: #009999; }
169 | .highlight .sb {
170 | color: #d14; }
171 | .highlight .sc {
172 | color: #d14; }
173 | .highlight .sd {
174 | color: #d14; }
175 | .highlight .s2 {
176 | color: #d14; }
177 | .highlight .se {
178 | color: #d14; }
179 | .highlight .sh {
180 | color: #d14; }
181 | .highlight .si {
182 | color: #d14; }
183 | .highlight .sx {
184 | color: #d14; }
185 | .highlight .sr {
186 | color: #009926; }
187 | .highlight .s1 {
188 | color: #d14; }
189 | .highlight .ss {
190 | color: #990073; }
191 | .highlight .bp {
192 | color: #999999; }
193 | .highlight .vc {
194 | color: #008080; }
195 | .highlight .vg {
196 | color: #008080; }
197 | .highlight .vi {
198 | color: #008080; }
199 | .highlight .il {
200 | color: #009999; }
201 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/carat.png
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/dash.png
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/gh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/gh.png
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/img/spinner.gif
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/js/jazzy.js:
--------------------------------------------------------------------------------
1 | window.jazzy = {'docset': false}
2 | if (typeof window.dash != 'undefined') {
3 | document.documentElement.className += ' dash'
4 | window.jazzy.docset = true
5 | }
6 | if (navigator.userAgent.match(/xcode/i)) {
7 | document.documentElement.className += ' xcode'
8 | window.jazzy.docset = true
9 | }
10 |
11 | function toggleItem($link, $content) {
12 | var animationDuration = 300;
13 | $link.toggleClass('token-open');
14 | $content.slideToggle(animationDuration);
15 | }
16 |
17 | function itemLinkToContent($link) {
18 | return $link.parent().parent().next();
19 | }
20 |
21 | // On doc load + hash-change, open any targetted item
22 | function openCurrentItemIfClosed() {
23 | if (window.jazzy.docset) {
24 | return;
25 | }
26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token');
27 | $content = itemLinkToContent($link);
28 | if ($content.is(':hidden')) {
29 | toggleItem($link, $content);
30 | }
31 | }
32 |
33 | $(openCurrentItemIfClosed);
34 | $(window).on('hashchange', openCurrentItemIfClosed);
35 |
36 | // On item link ('token') click, toggle its discussion
37 | $('.token').on('click', function(event) {
38 | if (window.jazzy.docset) {
39 | return;
40 | }
41 | var $link = $(this);
42 | toggleItem($link, itemLinkToContent($link));
43 |
44 | // Keeps the document from jumping to the hash.
45 | var href = $link.attr('href');
46 | if (history.pushState) {
47 | history.pushState({}, '', href);
48 | } else {
49 | location.hash = href;
50 | }
51 | event.preventDefault();
52 | });
53 |
54 | // Clicks on links to the current, closed, item need to open the item
55 | $("a:not('.token')").on('click', function() {
56 | if (location == this.href) {
57 | openCurrentItemIfClosed();
58 | }
59 | });
60 |
61 | // KaTeX rendering
62 | if ("katex" in window) {
63 | $($('.math').each( (_, element) => {
64 | katex.render(element.textContent, element, {
65 | displayMode: $(element).hasClass('m-block'),
66 | throwOnError: false,
67 | trust: true
68 | });
69 | }))
70 | }
71 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/Documents/js/jazzy.search.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | var $typeahead = $('[data-typeahead]');
3 | var $form = $typeahead.parents('form');
4 | var searchURL = $form.attr('action');
5 |
6 | function displayTemplate(result) {
7 | return result.name;
8 | }
9 |
10 | function suggestionTemplate(result) {
11 | var t = '';
12 | t += '' + result.name + '';
13 | if (result.parent_name) {
14 | t += '' + result.parent_name + '';
15 | }
16 | t += '
';
17 | return t;
18 | }
19 |
20 | $typeahead.one('focus', function() {
21 | $form.addClass('loading');
22 |
23 | $.getJSON(searchURL).then(function(searchData) {
24 | const searchIndex = lunr(function() {
25 | this.ref('url');
26 | this.field('name');
27 | this.field('abstract');
28 | for (const [url, doc] of Object.entries(searchData)) {
29 | this.add({url: url, name: doc.name, abstract: doc.abstract});
30 | }
31 | });
32 |
33 | $typeahead.typeahead(
34 | {
35 | highlight: true,
36 | minLength: 3,
37 | autoselect: true
38 | },
39 | {
40 | limit: 10,
41 | display: displayTemplate,
42 | templates: { suggestion: suggestionTemplate },
43 | source: function(query, sync) {
44 | const lcSearch = query.toLowerCase();
45 | const results = searchIndex.query(function(q) {
46 | q.term(lcSearch, { boost: 100 });
47 | q.term(lcSearch, {
48 | boost: 10,
49 | wildcard: lunr.Query.wildcard.TRAILING
50 | });
51 | }).map(function(result) {
52 | var doc = searchData[result.ref];
53 | doc.url = result.ref;
54 | return doc;
55 | });
56 | sync(results);
57 | }
58 | }
59 | );
60 | $form.removeClass('loading');
61 | $typeahead.trigger('focus');
62 | });
63 | });
64 |
65 | var baseURL = searchURL.slice(0, -"search.json".length);
66 |
67 | $typeahead.on('typeahead:select', function(e, result) {
68 | window.location = baseURL + result.url;
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.docset/Contents/Resources/docSet.dsidx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.docset/Contents/Resources/docSet.dsidx
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.tgz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/docsets/ThemeKit.tgz
--------------------------------------------------------------------------------
/Docs/docsets/ThemeKit.xml:
--------------------------------------------------------------------------------
1 | 1.4.0http://themekit.nunogrilo.com/docsets/ThemeKit.tgz
2 |
--------------------------------------------------------------------------------
/Docs/img/carat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/img/carat.png
--------------------------------------------------------------------------------
/Docs/img/dash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/img/dash.png
--------------------------------------------------------------------------------
/Docs/img/gh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/img/gh.png
--------------------------------------------------------------------------------
/Docs/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Docs/img/spinner.gif
--------------------------------------------------------------------------------
/Docs/js/jazzy.js:
--------------------------------------------------------------------------------
1 | window.jazzy = {'docset': false}
2 | if (typeof window.dash != 'undefined') {
3 | document.documentElement.className += ' dash'
4 | window.jazzy.docset = true
5 | }
6 | if (navigator.userAgent.match(/xcode/i)) {
7 | document.documentElement.className += ' xcode'
8 | window.jazzy.docset = true
9 | }
10 |
11 | function toggleItem($link, $content) {
12 | var animationDuration = 300;
13 | $link.toggleClass('token-open');
14 | $content.slideToggle(animationDuration);
15 | }
16 |
17 | function itemLinkToContent($link) {
18 | return $link.parent().parent().next();
19 | }
20 |
21 | // On doc load + hash-change, open any targetted item
22 | function openCurrentItemIfClosed() {
23 | if (window.jazzy.docset) {
24 | return;
25 | }
26 | var $link = $(`a[name="${location.hash.substring(1)}"]`).nextAll('.token');
27 | $content = itemLinkToContent($link);
28 | if ($content.is(':hidden')) {
29 | toggleItem($link, $content);
30 | }
31 | }
32 |
33 | $(openCurrentItemIfClosed);
34 | $(window).on('hashchange', openCurrentItemIfClosed);
35 |
36 | // On item link ('token') click, toggle its discussion
37 | $('.token').on('click', function(event) {
38 | if (window.jazzy.docset) {
39 | return;
40 | }
41 | var $link = $(this);
42 | toggleItem($link, itemLinkToContent($link));
43 |
44 | // Keeps the document from jumping to the hash.
45 | var href = $link.attr('href');
46 | if (history.pushState) {
47 | history.pushState({}, '', href);
48 | } else {
49 | location.hash = href;
50 | }
51 | event.preventDefault();
52 | });
53 |
54 | // Clicks on links to the current, closed, item need to open the item
55 | $("a:not('.token')").on('click', function() {
56 | if (location == this.href) {
57 | openCurrentItemIfClosed();
58 | }
59 | });
60 |
61 | // KaTeX rendering
62 | if ("katex" in window) {
63 | $($('.math').each( (_, element) => {
64 | katex.render(element.textContent, element, {
65 | displayMode: $(element).hasClass('m-block'),
66 | throwOnError: false,
67 | trust: true
68 | });
69 | }))
70 | }
71 |
--------------------------------------------------------------------------------
/Docs/js/jazzy.search.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | var $typeahead = $('[data-typeahead]');
3 | var $form = $typeahead.parents('form');
4 | var searchURL = $form.attr('action');
5 |
6 | function displayTemplate(result) {
7 | return result.name;
8 | }
9 |
10 | function suggestionTemplate(result) {
11 | var t = '';
12 | t += '' + result.name + '';
13 | if (result.parent_name) {
14 | t += '' + result.parent_name + '';
15 | }
16 | t += '
';
17 | return t;
18 | }
19 |
20 | $typeahead.one('focus', function() {
21 | $form.addClass('loading');
22 |
23 | $.getJSON(searchURL).then(function(searchData) {
24 | const searchIndex = lunr(function() {
25 | this.ref('url');
26 | this.field('name');
27 | this.field('abstract');
28 | for (const [url, doc] of Object.entries(searchData)) {
29 | this.add({url: url, name: doc.name, abstract: doc.abstract});
30 | }
31 | });
32 |
33 | $typeahead.typeahead(
34 | {
35 | highlight: true,
36 | minLength: 3,
37 | autoselect: true
38 | },
39 | {
40 | limit: 10,
41 | display: displayTemplate,
42 | templates: { suggestion: suggestionTemplate },
43 | source: function(query, sync) {
44 | const lcSearch = query.toLowerCase();
45 | const results = searchIndex.query(function(q) {
46 | q.term(lcSearch, { boost: 100 });
47 | q.term(lcSearch, {
48 | boost: 10,
49 | wildcard: lunr.Query.wildcard.TRAILING
50 | });
51 | }).map(function(result) {
52 | var doc = searchData[result.ref];
53 | doc.url = result.ref;
54 | return doc;
55 | });
56 | sync(results);
57 | }
58 | }
59 | );
60 | $form.removeClass('loading');
61 | $typeahead.trigger('focus');
62 | });
63 | });
64 |
65 | var baseURL = searchURL.slice(0, -"search.json".length);
66 |
67 | $typeahead.on('typeahead:select', function(e, result) {
68 | window.location = baseURL + result.url;
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/Imgs/ThemeKit.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Imgs/ThemeKit.gif
--------------------------------------------------------------------------------
/Imgs/ThemeKit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Imgs/ThemeKit.png
--------------------------------------------------------------------------------
/Imgs/ThemeKit@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luckymarmot/ThemeKit/36df64ff1d5d2ee33562a0cc8cb27c371deb79e3/Imgs/ThemeKit@2x.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Paw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Sources/Common.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Common.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 14/10/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | func CacheKey(selector: Selector) -> NSNumber {
10 | return CacheKey(selector: selector, colorSpace: nil, theme: nil)
11 | }
12 |
13 | func CacheKey(selector: Selector, colorSpace: NSColorSpace?) -> NSNumber {
14 | return CacheKey(selector: selector, colorSpace: colorSpace, theme: nil)
15 | }
16 |
17 | func CacheKey(selector: Selector, theme: Theme?) -> NSNumber {
18 | return CacheKey(selector: selector, colorSpace: nil, theme: theme)
19 | }
20 |
21 | func CacheKey(selector: Selector, colorSpace: NSColorSpace?, theme: Theme?) -> NSNumber {
22 | let hashValue = selector.hashValue ^ (colorSpace == nil ? 0 : (colorSpace!.hashValue << 4)) ^ (theme == nil ? 0 : (theme!.hash << 8))
23 | return NSNumber(value: hashValue)
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/DarkTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DarkTheme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Dark theme.
12 | @objc(TKDarkTheme)
13 | public class DarkTheme: NSObject, Theme {
14 |
15 | /// Dark theme identifier (static).
16 | @objc public static var identifier: String = "com.luckymarmot.ThemeKit.DarkTheme"
17 |
18 | /// Unique theme identifier.
19 | public var identifier: String = DarkTheme.identifier
20 |
21 | /// Theme display name.
22 | public var displayName: String = "Dark Theme"
23 |
24 | /// Theme short display name.
25 | public var shortDisplayName: String = "Dark"
26 |
27 | /// Is this a dark theme?
28 | public var isDarkTheme: Bool = true
29 |
30 | /// Calling `init()` is not allowed outside this library.
31 | /// Use `ThemeManager.darkTheme` instead.
32 | internal override init() {
33 | super.init()
34 | }
35 |
36 | override public var description: String {
37 | return "<\(DarkTheme.self): \(themeDescription(self))>"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.4.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2016-2018 Paw & Nuno Grilo. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Sources/LightTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LightTheme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Light theme (default).
12 | @objc(TKLightTheme)
13 | public class LightTheme: NSObject, Theme {
14 |
15 | /// Light theme identifier (static).
16 | @objc public static var identifier: String = "com.luckymarmot.ThemeKit.LightTheme"
17 |
18 | /// Unique theme identifier.
19 | public var identifier: String = LightTheme.identifier
20 |
21 | /// Theme display name.
22 | public var displayName: String = "Light Theme"
23 |
24 | /// Theme short display name.
25 | public var shortDisplayName: String = "Light"
26 |
27 | /// Is this a dark theme?
28 | public var isDarkTheme: Bool = false
29 |
30 | /// Calling `init()` is not allowed outside this library.
31 | /// Use `ThemeManager.lightTheme` instead.
32 | internal override init() {
33 | super.init()
34 | }
35 |
36 | override public var description: String {
37 | return "<\(LightTheme.self): \(themeDescription(self))>"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/NSColor+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSColor+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 24/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | `NSColor` ThemeKit extension to help on when overriding colors in `ThemeColor` extensions.
13 | */
14 | extension NSColor {
15 |
16 | // MARK: -
17 | // MARK: Color override
18 |
19 | /// Swizzle NSColor in case we are replacing system colors by themable colors.
20 | @objc static func swizzleNSColor() {
21 | swizzleNSColorOnce
22 | }
23 |
24 | /// Swizzle NSColor in case we are replacing system colors by themable colors.
25 | /// This code is executed *only once* (even if invoked multiple times).
26 | private static let swizzleNSColorOnce: Void = {
27 | // swizzle only if needed
28 | guard needsSwizzling else { return }
29 |
30 | // swizzle NSColor methods
31 | swizzleInstanceMethod(cls: NSClassFromString("NSDynamicSystemColor"), selector: #selector(set), withSelector: #selector(themeKitSet))
32 | swizzleInstanceMethod(cls: NSClassFromString("NSDynamicSystemColor"), selector: #selector(setFill), withSelector: #selector(themeKitSetFill))
33 | swizzleInstanceMethod(cls: NSClassFromString("NSDynamicSystemColor"), selector: #selector(setStroke), withSelector: #selector(themeKitSetStroke))
34 | }()
35 |
36 | /// Check if color is being overriden in a ThemeColor extension.
37 | @objc public var isThemeOverriden: Bool {
38 |
39 | // check if `NSColor` provides this color
40 | let selector = Selector(colorNameComponent)
41 | let nsColorMethod = class_getClassMethod(NSColor.classForCoder(), selector)
42 | guard nsColorMethod != nil else {
43 | return false
44 | }
45 |
46 | // get current theme
47 | let theme = ThemeManager.shared.effectiveTheme
48 |
49 | // `UserTheme`: check `hasThemeAsset(_:)` method
50 | if let userTheme = theme as? UserTheme {
51 | return userTheme.hasThemeAsset(colorNameComponent)
52 | }
53 |
54 | // native themes: look up for an instance method
55 | else {
56 | let themeClass: AnyClass = object_getClass(theme)!
57 | let themeColorMethod = class_getInstanceMethod(themeClass, selector)
58 | return themeColorMethod != nil && nsColorMethod != themeColorMethod
59 | }
60 | }
61 |
62 | /// Get all `NSColor` color methods.
63 | /// Overridable class methods (can be overriden in `ThemeColor` extension).
64 | @objc public class func colorMethodNames() -> [String] {
65 | let nsColorMethods = NSObject.classMethodNames(for: NSColor.classForCoder()).filter { (methodName) -> Bool in
66 | return methodName.hasSuffix("Color")
67 | }
68 | return nsColorMethods
69 | }
70 |
71 | // MARK: - Private
72 |
73 | /// Check if we need to swizzle NSDynamicSystemColor class.
74 | private class var needsSwizzling: Bool {
75 | let themeColorMethods = classMethodNames(for: ThemeColor.classForCoder()).filter { (methodName) -> Bool in
76 | return methodName.hasSuffix("Color")
77 | }
78 | let nsColorMethods = classMethodNames(for: NSColor.classForCoder()).filter { (methodName) -> Bool in
79 | return methodName.hasSuffix("Color")
80 | }
81 |
82 | // checks if NSColor `*Color` class methods are being overriden
83 | for colorMethod in themeColorMethods {
84 | if nsColorMethods.contains(colorMethod) {
85 | // theme color with `colorMethod` selector is overriding a `NSColor` method -> swizzling needed.
86 | return true
87 | }
88 | }
89 |
90 | return false
91 | }
92 |
93 | // ThemeKit.set() replacement to use theme-aware color
94 | @objc public func themeKitSet() {
95 | // call original .set() function
96 | themeKitSet()
97 |
98 | // check if the user provides an alternative color
99 | if ThemeManager.shared.isEnabled && isThemeOverriden {
100 | // call ThemeColor.set() function
101 | ThemeColor.color(with: Selector(colorNameComponent)).set()
102 | }
103 | }
104 |
105 | // ThemeKit.setFill() replacement to use theme-aware color
106 | @objc public func themeKitSetFill() {
107 | // call original .setFill() function
108 | themeKitSetFill()
109 |
110 | // check if the user provides an alternative color
111 | if ThemeManager.shared.isEnabled && isThemeOverriden {
112 | // call ThemeColor.setFill() function
113 | ThemeColor.color(with: Selector(colorNameComponent)).setFill()
114 | }
115 | }
116 |
117 | // ThemeKit.setStroke() replacement to use theme-aware color
118 | @objc public func themeKitSetStroke() {
119 | // call original .setStroke() function
120 | themeKitSetStroke()
121 |
122 | // check if the user provides an alternative color
123 | if ThemeManager.shared.isEnabled && isThemeOverriden {
124 | // call ThemeColor.setStroke() function
125 | ThemeColor.color(with: Selector(colorNameComponent)).setStroke()
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/Sources/NSDictionary+UserTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSDictionary+UserTheme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSDictionary {
12 |
13 | // MARK: `NSRegularExpression` Initialization
14 |
15 | /// Regular expression for user theme variables ($var)
16 | @objc static var varsRegExpr: NSRegularExpression? {
17 | do {
18 | return try NSRegularExpression(pattern: "(\\$[a-zA-Z0-9_\\-\\.]+)+", options: .caseInsensitive)
19 | } catch let error {
20 | print(error)
21 | return nil
22 | }
23 | }
24 |
25 | /// Regular expression for user theme colors
26 | @objc static var colorRegExpr: NSRegularExpression? {
27 | do {
28 | return try NSRegularExpression(pattern: "(?:rgba?)?[\\s]?[\\(]?[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?([0-1]?(?:\\.\\d+)?)", options: .caseInsensitive)
29 | } catch let error {
30 | print(error)
31 | return nil
32 | }
33 | }
34 |
35 | /// Regular expression for user theme gradients
36 | @objc static var linearGradRegExpr: NSRegularExpression? {
37 | do {
38 | return try NSRegularExpression(pattern: "linear-gradient\\(\\s*((?:rgba?)?[\\s]?[\\(]?[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]*[\\s+]?([0-1]?(?:\\.\\d+)?)\\))\\s*,\\s*((?:rgba?)?[\\s]?[\\(]?[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]+[\\s+]?(\\d+)[(\\s)|(,)]*[\\s+]?([0-1]?(?:\\.\\d+)?)\\))\\s*\\)", options: .caseInsensitive)
39 | } catch let error {
40 | print(error)
41 | return nil
42 | }
43 | }
44 |
45 | /// Regular expression for user theme pattern images (NSColor)
46 | @objc static var patternRegExpr: NSRegularExpression? {
47 | do {
48 | return try NSRegularExpression(pattern: "pattern\\(((named):[\\s]*([\\w-\\. ]+)|(file):[\\s]*([\\w-\\. \\/]+))\\)", options: .caseInsensitive)
49 | } catch let error {
50 | print(error)
51 | return nil
52 | }
53 | }
54 |
55 | /// Regular expression for user theme images
56 | @objc static var imageRegExpr: NSRegularExpression? {
57 | do {
58 | return try NSRegularExpression(pattern: "image\\(((named):[\\s]*([\\w-\\. ]+)|(file):[\\s]*([\\w-\\. \\/]+))\\)", options: .caseInsensitive)
59 | } catch let error {
60 | print(error)
61 | return nil
62 | }
63 | }
64 |
65 | // MARK: Evaluation
66 |
67 | /// Evaluate object for the specified key as theme asset (`NSColor`, `NSGradient`, `NSImage`, ...).
68 | @objc func evaluatedObject(key: String) -> AnyObject? {
69 | // Resolve any variables
70 | let stringValue = evaluatedString(key: key)
71 |
72 | // Evaluate as theme asset (NSColor, NSGradient, NSImage, ...)
73 | return evaluatedObjectAsThemeAsset(value: stringValue as AnyObject)
74 | }
75 |
76 | // MARK: Internal evaluation functions
77 |
78 | /// Evaluate object for the specified key as string.
79 | private func evaluatedString(key: String) -> String? {
80 | guard let stringValue = self[key] as? String else {
81 | return nil
82 | }
83 |
84 | // Resolve any variables
85 | var evaluatedStringValue = stringValue
86 | var rangeOffset = 0
87 | NSDictionary.varsRegExpr?.enumerateMatches(in: stringValue,
88 | options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)),
89 | range: NSRange(location: 0, length: stringValue.count),
90 | using: { (match, _, _) in
91 | if let matchRange = match?.range(at: 1) {
92 | var range = matchRange
93 | range.location += rangeOffset
94 |
95 | // Extract variable
96 | let start = range.location + 1
97 | let end = start + range.length - 2
98 | guard start < evaluatedStringValue.count && end < evaluatedStringValue.count else { return }
99 | let variable = evaluatedStringValue[start.. AnyObject? {
119 | guard let stringValue = value as? String else {
120 | // value is already evaluated as a non-string object
121 | return value
122 | }
123 |
124 | var evaluatedObject: AnyObject? = value
125 |
126 | // linear-gradient(color1, color2)
127 | if let match = NSDictionary.linearGradRegExpr?.firstMatch(in: stringValue, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSRange(location: 0, length: stringValue.count)),
128 | match.numberOfRanges == 11 {
129 |
130 | // Starting color
131 | let red1 = (Float(stringValue.substring(withNSRange: match.range(at: 2))) ?? 255) / 255
132 | let green1 = (Float(stringValue.substring(withNSRange: match.range(at: 3))) ?? 255) / 255
133 | let blue1 = (Float(stringValue.substring(withNSRange: match.range(at: 4))) ?? 255) / 255
134 | let alpha1 = Float(stringValue.substring(withNSRange: match.range(at: 5))) ?? 1.0
135 | let color1 = NSColor(red: CGFloat(red1), green: CGFloat(green1), blue: CGFloat(blue1), alpha: CGFloat(alpha1))
136 |
137 | // Ending color
138 | let red2 = (Float(stringValue.substring(withNSRange: match.range(at: 7))) ?? 255) / 255
139 | let green2 = (Float(stringValue.substring(withNSRange: match.range(at: 8))) ?? 255) / 255
140 | let blue2 = (Float(stringValue.substring(withNSRange: match.range(at: 9))) ?? 255) / 255
141 | let alpha2 = Float(stringValue.substring(withNSRange: match.range(at: 10))) ?? 1.0
142 | let color2 = NSColor(red: CGFloat(red2), green: CGFloat(green2), blue: CGFloat(blue2), alpha: CGFloat(alpha2))
143 |
144 | // Gradient
145 | evaluatedObject = NSGradient(starting: color1, ending: color2)
146 | }
147 |
148 | // rgb/rgba color
149 | if evaluatedObject is String,
150 | let match = NSDictionary.colorRegExpr?.firstMatch(in: stringValue, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSRange(location: 0, length: stringValue.count)),
151 | match.numberOfRanges == 5 {
152 |
153 | let red = (Float(stringValue.substring(withNSRange: match.range(at: 1))) ?? 255) / 255
154 | let green = (Float(stringValue.substring(withNSRange: match.range(at: 2))) ?? 255) / 255
155 | let blue = (Float(stringValue.substring(withNSRange: match.range(at: 3))) ?? 255) / 255
156 | let alpha = Float(stringValue.substring(withNSRange: match.range(at: 4))) ?? 1.0
157 |
158 | // Color
159 | evaluatedObject = NSColor(red: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha))
160 | }
161 |
162 | // pattern
163 | if evaluatedObject is String,
164 | let match = NSDictionary.patternRegExpr?.firstMatch(in: stringValue, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSRange(location: 0, length: stringValue.count)),
165 | match.numberOfRanges == 6 {
166 |
167 | let isNamedType = stringValue.substring(withNSRange: match.range(at: 2)) == "named"
168 | let imageName = stringValue.substring(withNSRange: match.range(at: 3))
169 | let isFileType = stringValue.substring(withNSRange: match.range(at: 4)) == "file"
170 | let imageFileName = stringValue.substring(withNSRange: match.range(at: 5))
171 |
172 | // Pattern image
173 | var pattern: NSImage
174 | if isNamedType {
175 | pattern = NSImage(named: NSImage.Name(imageName)) ?? NSImage(size: NSSize.zero)
176 | } else if isFileType, let imageURL = ThemeManager.shared.userThemesFolderURL?.appendingPathComponent(imageFileName) {
177 | pattern = NSImage(contentsOf: imageURL) ?? NSImage(size: NSSize.zero)
178 | } else {
179 | pattern = NSImage(size: NSSize.zero)
180 | }
181 | evaluatedObject = NSColor(patternImage: pattern)
182 | }
183 |
184 | // image
185 | if evaluatedObject is String,
186 | let match = NSDictionary.imageRegExpr?.firstMatch(in: stringValue, options: NSRegularExpression.MatchingOptions(rawValue: UInt(0)), range: NSRange(location: 0, length: stringValue.count)),
187 | match.numberOfRanges == 6 {
188 |
189 | let isNamedType = stringValue.substring(withNSRange: match.range(at: 2)) == "named"
190 | let imageName = stringValue.substring(withNSRange: match.range(at: 3))
191 | let isFileType = stringValue.substring(withNSRange: match.range(at: 4)) == "file"
192 | let imageFileName = stringValue.substring(withNSRange: match.range(at: 5))
193 |
194 | // Image
195 | if isNamedType {
196 | evaluatedObject = NSImage(named: NSImage.Name(imageName)) ?? NSImage(size: NSSize.zero)
197 | } else if isFileType, let imageURL = ThemeManager.shared.userThemesFolderURL?.appendingPathComponent(imageFileName) {
198 | evaluatedObject = NSImage(contentsOf: imageURL) ?? NSImage(size: NSSize.zero)
199 | } else {
200 | evaluatedObject = NSImage(size: NSSize.zero)
201 | }
202 | }
203 |
204 | return evaluatedObject
205 | }
206 |
207 | }
208 |
--------------------------------------------------------------------------------
/Sources/NSImage+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Converted to Swift 3 by Nuno Grilo on 02/10/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | /*
10 | Based on original work from Mircea "Bobby" Georgescu from:
11 | http://www.bobbygeorgescu.com/2011/08/finding-average-color-of-uiimage/
12 |
13 | UIImage+AverageColor.m
14 |
15 | Copyright (c) 2010, Mircea "Bobby" Georgescu
16 | All rights reserved.
17 |
18 | Redistribution and use in source and binary forms, with or without
19 | modification, are permitted provided that the following conditions are met:
20 | * Redistributions of source code must retain the above copyright
21 | notice, this list of conditions and the following disclaimer.
22 | * Redistributions in binary form must reproduce the above copyright
23 | notice, this list of conditions and the following disclaimer in the
24 | documentation and/or other materials provided with the distribution.
25 | * Neither the name of the Mircea "Bobby" Georgescu nor the
26 | names of its contributors may be used to endorse or promote products
27 | derived from this software without specific prior written permission.
28 |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
30 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
31 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
32 | DISCLAIMED. IN NO EVENT SHALL Mircea "Bobby" Georgescu BE LIABLE FOR ANY
33 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
35 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
36 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
38 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 | */
40 |
41 | import Cocoa
42 |
43 | extension NSImage {
44 |
45 | /// Find the average color in image.
46 | /// Not the most accurate algorithm, but probably got enough for the purpose.
47 | @objc internal func averageColor() -> NSColor {
48 | // setup a single-pixel image
49 | let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceRGB()
50 | let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
51 | guard let bitmapData = malloc(4),
52 | let context = CGContext(data: bitmapData, width: 1, height: 1, bitsPerComponent: 8, bytesPerRow: 4, space: colorSpace, bitmapInfo: bitmapInfo.rawValue),
53 | let cgImage = self.cgImage(forProposedRect: nil, context: NSGraphicsContext(cgContext: context, flipped: false), hints: nil) else {
54 | return NSColor.white
55 | }
56 |
57 | // draw the image into a 1x1 image
58 | context.draw(cgImage, in: CGRect(x: 0, y: 0, width: 1, height: 1))
59 |
60 | // extract byte colors from single-pixel image
61 | let red = bitmapData.load(fromByteOffset: 0, as: UInt8.self)
62 | let green = bitmapData.load(fromByteOffset: 1, as: UInt8.self)
63 | let blue = bitmapData.load(fromByteOffset: 2, as: UInt8.self)
64 | let alpha = bitmapData.load(fromByteOffset: 3, as: UInt8.self)
65 |
66 | // build "average color"
67 | let modifier = alpha > 0 ? CGFloat(alpha) / 255.0 : 1.0
68 | let redFloat: CGFloat = CGFloat(red) * modifier / 255.0
69 | let greenFloat: CGFloat = CGFloat(green) * modifier / 255.0
70 | let blueFloat: CGFloat = CGFloat(blue) * modifier / 255.0
71 | let alphaFloat: CGFloat = CGFloat(alpha) / 255.0
72 |
73 | return NSColor(red: redFloat, green: greenFloat, blue: blueFloat, alpha: alphaFloat)
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/Sources/NSObject+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSObject+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 24/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSObject {
12 |
13 | /// Swizzle instance methods.
14 | @objc internal class func swizzleInstanceMethod(cls: AnyClass?, selector originalSelector: Selector, withSelector swizzledSelector: Selector) {
15 | guard cls != nil else {
16 | print("Unable to swizzle \(originalSelector): dynamic system color override will not be available.")
17 | return
18 | }
19 |
20 | // methods
21 | let originalMethod = class_getInstanceMethod(cls, originalSelector)
22 | let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
23 |
24 | // add new method
25 | let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
26 |
27 | // switch implementations
28 | if didAddMethod {
29 | class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
30 | } else {
31 | method_exchangeImplementations(originalMethod!, swizzledMethod!)
32 | }
33 | }
34 |
35 | /// Returns class method names.
36 | @objc internal class func classMethodNames(for cls: AnyClass?) -> [String] {
37 | var results: [String] = []
38 |
39 | // retrieve class method list
40 | var count: UInt32 = 0
41 | if let methods: UnsafeMutablePointer = class_copyMethodList(object_getClass(cls), &count) {
42 |
43 | // iterate class methods
44 | for i in 0.. [AnyClass] {
58 | var results: [AnyClass] = []
59 |
60 | // class count
61 | let expectedCount: Int32 = objc_getClassList(nil, 0)
62 |
63 | // retrieve class list
64 | let buffer = UnsafeMutablePointer.allocate(capacity: Int(expectedCount))
65 | let realCount: Int32 = objc_getClassList(AutoreleasingUnsafeMutablePointer(buffer), expectedCount)
66 |
67 | // iterate classes
68 | for i in 0.. [AnyClass] {
82 | let classes = classList()
83 | var results = [AnyClass]()
84 |
85 | // iterate classes
86 | for cls in classes {
87 | if class_conformsToProtocol(cls, aProtocol) {
88 | results.append(cls)
89 | }
90 | }
91 |
92 | return results
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/Sources/NSView+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSView+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 22/09/2016.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension NSView {
12 |
13 | /// Returns the first subview that matches specified class.
14 | @objc func deepSubview(withClassName className: String) -> NSView? {
15 |
16 | // search level below (view subviews)
17 | for subview: NSView in self.subviews where subview.className == className {
18 | return subview
19 | }
20 |
21 | // search deeper
22 | for subview: NSView in self.subviews {
23 | if let foundView = subview.deepSubview(withClassName: className) {
24 | return foundView
25 | }
26 | }
27 |
28 | return nil
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/NotificationName+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationName+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 07/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | Notifications defined by `ThemeKit`.
13 | */
14 | public extension Notification.Name {
15 |
16 | /// ThemeKit notification sent when current theme is about to change.
17 | static let willChangeTheme = Notification.Name("ThemeKitWillChangeThemeNotification")
18 |
19 | /// ThemeKit notification sent when current theme did change.
20 | static let didChangeTheme = Notification.Name("ThemeKitDidChangeThemeNotification")
21 |
22 | /// ThemeKit notification sent when system theme did change (System Preference > General > Appearance).
23 | static let didChangeSystemTheme = Notification.Name("ThemeKitDidChangeSystemThemeNotification")
24 |
25 | /// Convenience property for the system notification sent when System Preference for dark mode changes.
26 | static let didChangeAppleInterfaceTheme = Notification.Name("AppleInterfaceThemeChangedNotification")
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/String+Substring.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Substring.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension String {
12 |
13 | /// Convenience subscript with Int to get character as Character.
14 | internal subscript (i: Int) -> Character {
15 | return self[self.index(self.startIndex, offsetBy: i)]
16 | }
17 |
18 | /// Convenience subscript with Int to get character as String.
19 | internal subscript (i: Int) -> String {
20 | return String(self[i] as Character)
21 | }
22 |
23 | /// Convenience function to get substring Range instead of Range.
24 | internal subscript (r: Range) -> String {
25 | let start = self.index(self.startIndex, offsetBy: r.lowerBound)
26 | let end = self.index(self.startIndex, offsetBy: r.upperBound)
27 |
28 | return String(self[start...end])
29 | }
30 |
31 | /// Convenience function to get substring with NSRange.
32 | internal func substring(withNSRange: NSRange) -> String {
33 | guard withNSRange.location < self.count else { return "" }
34 | let start = self.index(self.startIndex, offsetBy: withNSRange.location)
35 | let end = self.index(start, offsetBy: withNSRange.length)
36 | let range = Range(uncheckedBounds: (lower: start, upper: end))
37 | return String(self[range])
38 | }
39 |
40 | /// Convenience function to replace characters with NSRange.
41 | internal func replacingCharacters(inNSRange: NSRange, with: String) -> String {
42 | guard inNSRange.location < self.count else { return "" }
43 | let start = self.index(self.startIndex, offsetBy: inNSRange.location)
44 | let end = self.index(start, offsetBy: inNSRange.length)
45 | let range = Range(uncheckedBounds: (lower: start, upper: end))
46 | return self.replacingCharacters(in: range, with: with)
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/SystemTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SystemTheme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// System theme.
12 | ///
13 | /// Will dynamically resolve to either `ThemeManager.lightTheme` or `ThemeManager.darkTheme`,
14 | /// depending on the macOS preference at **System Preferences > General > Appearance**.
15 | @objc(TKSystemTheme)
16 | public class SystemTheme: NSObject, Theme {
17 |
18 | /// System theme identifier (static).
19 | @objc public static var identifier: String = "com.luckymarmot.ThemeKit.SystemTheme"
20 |
21 | /// Unique theme identifier.
22 | public var identifier: String = SystemTheme.identifier
23 |
24 | /// Theme display name.
25 | public var displayName: String {
26 | let systemVersion = OperatingSystemVersion(majorVersion: 10, minorVersion: 12, patchVersion: 0)
27 | return ProcessInfo.processInfo.isOperatingSystemAtLeast(systemVersion) ? "macOS Theme" : "OS X Theme"
28 | }
29 |
30 | /// Theme short display name.
31 | public var shortDisplayName: String {
32 | return displayName
33 | }
34 |
35 | /// Is this a dark theme?
36 | public var isDarkTheme: Bool = SystemTheme.isAppleInterfaceThemeDark
37 |
38 | /// Checks if Apple UI theme is set to dark, as set on **System Preferences > General > Appearance**.
39 | @objc public static var isAppleInterfaceThemeDark: Bool = SystemTheme.isAppleInterfaceThemeDarkOnUserDefaults()
40 |
41 | /// Calling `init()` is not allowed outside this library.
42 | /// Use `ThemeManager.systemTheme` instead.
43 | internal override init() {
44 | super.init()
45 |
46 | // Observe macOS Apple Interface Theme
47 | DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleInterfaceThemeDidChange(_:)), name: .didChangeAppleInterfaceTheme, object: nil)
48 | }
49 |
50 | /// Apple UI Theme has changed.
51 | @objc func appleInterfaceThemeDidChange(_ notification: Notification) {
52 | isDarkTheme = SystemTheme.isAppleInterfaceThemeDarkOnUserDefaults()
53 | SystemTheme.isAppleInterfaceThemeDark = isDarkTheme
54 | NotificationCenter.default.post(name: .didChangeSystemTheme, object: nil)
55 | }
56 |
57 | /// Read Apple Interface Theme preference from User Defaults.
58 | private static func isAppleInterfaceThemeDarkOnUserDefaults() -> Bool {
59 | return UserDefaults.standard.string(forKey: "AppleInterfaceStyle") != nil
60 | }
61 |
62 | override public var description: String {
63 | return "<\(SystemTheme.self): \(themeDescription(self))>"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Sources/Theme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Theme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | Theme protocol: the base of all themes.
13 |
14 | *ThemeKit* makes available, without any further coding:
15 |
16 | - a `LightTheme` (the default macOS theme)
17 | - a `DarkTheme` (the dark macOS theme, using `NSAppearanceNameVibrantDark`)
18 | - a `SystemTheme` (which dynamically resolve to either `LightTheme` or `DarkTheme
19 | depending on the macOS preference at **System Preferences > General > Appearance**)
20 |
21 | You can choose wheter or not to use these, and you can also implement your custom
22 | themes by:
23 |
24 | - implementing native `Theme` classes conforming to this protocol and `NSObject`
25 | - provide user themes (`UserTheme`) with `.theme` files
26 |
27 | Please check the provided *Demo.app* project for sample implementations of both.
28 |
29 | */
30 | @objc(TKTheme)
31 | public protocol Theme: NSObjectProtocol {
32 | // MARK: Required Properties
33 |
34 | /// Unique theme identifier.
35 | var identifier: String { get }
36 |
37 | /// Theme display name.
38 | var displayName: String { get }
39 |
40 | /// Theme short display name.
41 | var shortDisplayName: String { get }
42 |
43 | /// Is this a dark theme?
44 | var isDarkTheme: Bool { get }
45 |
46 | // MARK: Optional Methods/Properties
47 |
48 | /// Optional: foreground color to be used on when a foreground color is not provided
49 | /// by the theme.
50 | @objc optional var fallbackForegroundColor: NSColor? { get }
51 |
52 | /// Optional: background color to be used on when a background color (a color which
53 | /// contains `Background` in its name) is not provided by the theme.
54 | @objc optional var fallbackBackgroundColor: NSColor? { get }
55 |
56 | /// Optional: gradient to be used on when a gradient is not provided by the theme.
57 | @objc optional var fallbackGradient: NSGradient? { get }
58 |
59 | /// Optional: image to be used on when an image is not provided by the theme.
60 | @objc optional var fallbackImage: NSImage? { get }
61 | }
62 |
63 | /// Theme protocol extension.
64 | ///
65 | /// These functions are available for all `Theme`s.
66 | public extension Theme {
67 |
68 | // MARK: Convenient Methods/Properties (Swift only)
69 |
70 | /// Is this a light theme?
71 | ///
72 | /// This method is not available from Objective-C. Alternative code:
73 | ///
74 | /// ```objc
75 | /// !aTheme.isDarkTheme
76 | /// ```
77 | var isLightTheme: Bool {
78 | return !isDarkTheme
79 | }
80 |
81 | /// Is this the system theme? If true, theme automatically resolve to
82 | /// `ThemeManager.lightTheme` or `ThemeManager.darkTheme`, accordingly to
83 | /// **System Preferences > General > Appearance**.
84 | ///
85 | /// This method is not available from Objective-C. Alternative code:
86 | ///
87 | /// ```objc
88 | /// [aTheme.identifier isEqualToString:TKSystemTheme.identifier]
89 | /// ```
90 | var isSystemTheme: Bool {
91 | return identifier == SystemTheme.identifier
92 | }
93 |
94 | /// Is this a user theme?
95 | ///
96 | /// This method is not available from Objective-C. Alternative code:
97 | ///
98 | /// ```objc
99 | /// [aTheme isKindOfClass:[TKUserTheme class]]
100 | /// ```
101 | var isUserTheme: Bool {
102 | return self is UserTheme
103 | }
104 |
105 | /// Apply theme (make it the current one).
106 | ///
107 | /// This method is not available from Objective-C. Alternative code:
108 | ///
109 | /// ```objc
110 | /// [[TKThemeManager sharedManager] setTheme:aTheme]
111 | /// ```
112 | func apply() {
113 | ThemeManager.shared.theme = self
114 | }
115 |
116 | /// Theme asset for the specified key. Supported assets are `NSColor`, `NSGradient`, `NSImage` and `NSString`.
117 | ///
118 | /// This function is overriden by `UserTheme`.
119 | ///
120 | /// This method is not available from Objective-C.
121 | ///
122 | /// - parameter key: A color name, gradient name, image name or a theme string
123 | ///
124 | /// - returns: The theme value for the specified key.
125 | func themeAsset(_ key: String) -> Any? {
126 | // Because `Theme` is an @objc protocol, we cannot define this method on
127 | // the protocol and a provide a default implementation on this extension,
128 | // plus another on `UserTheme`. This is a workaround to accomplish it.
129 | if let userTheme = self as? UserTheme {
130 | return userTheme.themeAsset(key)
131 | }
132 |
133 | let selector = NSSelectorFromString(key)
134 | if let theme = self as? NSObject,
135 | theme.responds(to: selector) {
136 | return theme.perform(selector).takeUnretainedValue()
137 | }
138 |
139 | return nil
140 | }
141 |
142 | /// Checks if a theme asset is provided for the given key.
143 | ///
144 | /// This function is overriden by `UserTheme`.
145 | ///
146 | /// This method is not available from Objective-C.
147 | ///
148 | /// - parameter key: A color name, gradient name, image name or a theme string
149 | ///
150 | /// - returns: `true` if theme provides an asset for the given key; `false` otherwise.
151 | func hasThemeAsset(_ key: String) -> Bool {
152 | return themeAsset(key) != nil
153 | }
154 |
155 | /// Default foreground color to be used on fallback situations when
156 | /// no `fallbackForegroundColor` was specified by the theme.
157 | ///
158 | /// This method is not available from Objective-C. Alternative code:
159 | ///
160 | /// ```objc
161 | /// aTheme.isDarkTheme ? NSColor.whiteColor : NSColor.blackColor
162 | /// ```
163 | var defaultFallbackForegroundColor: NSColor {
164 | return isLightTheme ? NSColor.black : NSColor.white
165 | }
166 |
167 | /// Default background color to be used on fallback situations when
168 | /// no `fallbackBackgroundColor` was specified by the theme (background color
169 | /// is a color method that contains `Background` in its name).
170 | ///
171 | /// This method is not available from Objective-C. Alternative code:
172 | ///
173 | /// ```objc
174 | /// aTheme.isDarkTheme ? NSColor.blackColor : NSColor.whiteColor
175 | /// ```
176 | var defaultFallbackBackgroundColor: NSColor {
177 | return isLightTheme ? NSColor.white : NSColor.black
178 | }
179 |
180 | /// Default gradient to be used on fallback situations when
181 | /// no `fallbackForegroundColor` was specified by the theme.
182 | ///
183 | /// This method is not available from Objective-C. Alternative code:
184 | ///
185 | /// ```objc
186 | /// [[NSGradient alloc] initWithStartingColor:(aTheme.isDarkTheme ? NSColor.blackColor : NSColor.whiteColor) endingColor:(aTheme.isDarkTheme ? NSColor.whiteColor : NSColor.blackColor)]
187 | /// ```
188 | var defaultFallbackGradient: NSGradient? {
189 | return NSGradient(starting: defaultFallbackBackgroundColor, ending: defaultFallbackBackgroundColor)
190 | }
191 |
192 | /// Default image to be used on fallback situations when
193 | /// no image was specified by the theme.
194 | ///
195 | /// This method is not available from Objective-C. Alternative code:
196 | ///
197 | /// ```objc
198 | /// [[NSImage alloc] initWithSize:NSZeroSize]
199 | /// ```
200 | var defaultFallbackImage: NSImage {
201 | return NSImage(size: NSSize.zero)
202 | }
203 |
204 | /// Effective theme, which can be different from itself if it represents the
205 | /// system theme, respecting **System Preferences > General > Appearance**
206 | /// (in that case it will be either `ThemeManager.lightTheme` or `ThemeManager.darkTheme`).
207 | ///
208 | /// This method is not available from Objective-C. Alternative code:
209 | ///
210 | /// ```objc
211 | /// [aTheme.identifier isEqualToString:TKSystemTheme.identifier] ? (aTheme.isDarkTheme ? TKThemeManager.darkTheme : TKThemeManager.lightTheme) : aTheme;
212 | /// ```
213 | var effectiveTheme: Theme {
214 | if isSystemTheme {
215 | return isDarkTheme ? ThemeManager.darkTheme : ThemeManager.lightTheme
216 | } else {
217 | return self
218 | }
219 | }
220 |
221 | /// Theme description.
222 | func themeDescription(_ theme: Theme) -> String {
223 | return "\"\(displayName)\" [\(identifier)]\(isDarkTheme ? " (Dark)" : "")"
224 | }
225 | }
226 |
227 | /// Check if themes are the same.
228 | func == (lhs: Theme, rhs: Theme) -> Bool {
229 | return lhs.identifier == rhs.identifier
230 | }
231 |
232 | /// Check if themes are different.
233 | func != (lhs: Theme, rhs: Theme) -> Bool {
234 | return lhs.identifier != rhs.identifier
235 | }
236 |
--------------------------------------------------------------------------------
/Sources/ThemeKit.h:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeKit.h
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 21/09/2016.
6 | // Copyright © 2016-2017 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ThemeKit.
12 | FOUNDATION_EXPORT double ThemeKitVersionNumber;
13 |
14 | //! Project version string for ThemeKit.
15 | FOUNDATION_EXPORT const unsigned char ThemeKitVersionString[];
16 |
--------------------------------------------------------------------------------
/Sources/ThemeManager+ObjectiveC.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeManager+ObjectiveC.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 09/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension ThemeManager {
12 |
13 | /// Window theme policies that define which windows should be automatically themed, if any (Objective-C variant, only).
14 | @objc(TKThemeManagerWindowThemePolicy)
15 | enum TKThemeManagerWindowThemePolicy: Int {
16 | /// Theme all application windows (default).
17 | case themeAllWindows
18 | /// Only theme windows of the specified classes.
19 | case themeSomeWindows
20 | /// Do not theme windows of the specified classes.
21 | case doNotThemeSomeWindows
22 | /// Do not theme any window.
23 | case doNotThemeWindows
24 | }
25 |
26 | /// Current window theme policy.
27 | @objc(windowThemePolicy)
28 | var objc_windowThemePolicy: TKThemeManagerWindowThemePolicy {
29 | get {
30 | switch windowThemePolicy {
31 |
32 | case .themeAllWindows:
33 | return .themeAllWindows
34 |
35 | case .themeSomeWindows:
36 | return .themeSomeWindows
37 |
38 | case .doNotThemeSomeWindows:
39 | return .doNotThemeSomeWindows
40 |
41 | case .doNotThemeWindows:
42 | return .doNotThemeWindows
43 | }
44 | }
45 | set(value) {
46 | switch value {
47 | case .themeAllWindows:
48 | windowThemePolicy = .themeAllWindows
49 | case .themeSomeWindows:
50 | windowThemePolicy = .themeSomeWindows(windowClasses: themableWindowClasses ?? [])
51 | case .doNotThemeSomeWindows:
52 | windowThemePolicy = .doNotThemeSomeWindows(windowClasses: notThemableWindowClasses ?? [])
53 | case .doNotThemeWindows:
54 | windowThemePolicy = .doNotThemeWindows
55 | }
56 | }
57 | }
58 |
59 | /// Windows classes to be excluded from theming with the `TKThemeManagerWindowThemePolicyDoNotThemeSomeWindows`.
60 | @objc(notThemableWindowClasses)
61 | var notThemableWindowClasses: [AnyClass]? {
62 | get {
63 | switch windowThemePolicy {
64 |
65 | case .themeAllWindows:
66 | return nil
67 |
68 | case .themeSomeWindows:
69 | return []
70 |
71 | case .doNotThemeSomeWindows(let windowClasses):
72 | return windowClasses
73 |
74 | case .doNotThemeWindows:
75 | return []
76 | }
77 | }
78 | set(value) {
79 | if let newValue = value {
80 | if newValue.count > 0 {
81 | // theme some if value > 0
82 | windowThemePolicy = .doNotThemeSomeWindows(windowClasses: newValue)
83 | } else {
84 | // theme none if value is 0
85 | windowThemePolicy = .doNotThemeWindows
86 | }
87 | } else {
88 | // theme all windows if value is nil
89 | windowThemePolicy = .themeAllWindows
90 | }
91 | }
92 | }
93 |
94 | /// Windows classes to be themed with the `TKThemeManagerWindowThemePolicyThemeSomeWindows`.
95 | @objc(themableWindowClasses)
96 | var themableWindowClasses: [AnyClass]? {
97 | get {
98 | switch windowThemePolicy {
99 |
100 | case .themeAllWindows:
101 | return nil
102 |
103 | case .themeSomeWindows(let windowClasses):
104 | return windowClasses
105 |
106 | case .doNotThemeSomeWindows:
107 | return []
108 |
109 | case .doNotThemeWindows:
110 | return []
111 | }
112 | }
113 | set(value) {
114 | if let newValue = value {
115 | if newValue.count > 0 {
116 | // theme some if value > 0
117 | windowThemePolicy = .themeSomeWindows(windowClasses: newValue)
118 | } else {
119 | // theme none if value is 0
120 | windowThemePolicy = .doNotThemeWindows
121 | }
122 | } else {
123 | // theme all windows if value is nil
124 | windowThemePolicy = .themeAllWindows
125 | }
126 | }
127 | }
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/Sources/Thread+ThemeKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Thread+ThemeKit.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 08/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension Thread {
12 |
13 | /// Make sure code block is executed on main thread.
14 | @objc class func onMain(block: @escaping () -> Void) {
15 | if Thread.isMainThread {
16 | block()
17 | } else {
18 | DispatchQueue.main.async {
19 | block()
20 | }
21 | }
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/UserTheme.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserTheme.swift
3 | // ThemeKit
4 | //
5 | // Created by Nuno Grilo on 06/09/16.
6 | // Copyright © 2016 Paw & Nuno Grilo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /**
12 | A `Theme` class wrapping a user provided theme file (`.theme`).
13 |
14 | To enable user themes, set theme folder on `ThemeManager.userThemesFolderURL`.
15 |
16 | Notes about `.theme` files:
17 |
18 | - lines starting with `#` or `//` will be treated as comments, thus, ignored;
19 | - non-comment lines consists on simple variable/value assignments (eg, `variable = value`);
20 | - `variable` name can contain characters `[a-zA-Z0-9_-.]+`;
21 | - custom variables can be specified (eg, `myBackgroundColor = ...`);
22 | - theming properties match the class methods of `ThemeColor`, `ThemeGradient` and `ThemeImage` (eg, `labelColor`);
23 | - variables can be referenced by prefixing them with `$` (eg, `mainBorderColor = $commonBorderColor`);
24 | - colors are defined using `rgb(255, 255, 255)` or `rgba(255, 255, 255, 1.0)` (case insensitive);
25 | - gradients are defined using `linear-gradient(color1, color2)` (where colors are defined as above; case insensitive);
26 | - pattern images are defined using `pattern(named:xxxx)` (named images) or `pattern(file:../dddd/xxxx.yyy)` (filesystem images);
27 | - images are defined using `image(named:xxxx)` (named images) or `image(file:../dddd/xxxx.yyy)` (filesystem images);
28 | - `ThemeManager.themes` property is automatically updated when there are changes on the user themes folder;
29 | - file changes are applied on-the-fly, if it corresponds to the currently applied theme.
30 |
31 | Example `.theme` file:
32 |
33 | ```ruby
34 | // ************************* Theme Info ************************* //
35 | displayName = My Theme 1
36 | identifier = com.luckymarmot.ThemeKit.MyTheme1
37 | darkTheme = true
38 |
39 | // ********************* Colors & Gradients ********************* //
40 | # define color for `ThemeColor.brandColor`
41 | brandColor = $blue
42 | # define a new color for `NSColor.labelColor` (overriding)
43 | labelColor = rgb(11, 220, 111)
44 | # define gradient for `ThemeGradient.brandGradient`
45 | brandGradient = linear-gradient($orange.sky, rgba(200, 140, 60, 1.0))
46 |
47 | // ********************* Images & Patterns ********************** //
48 | # define pattern image from named image "paper" for color `ThemeColor.contentBackgroundColor`
49 | contentBackgroundColor = pattern(named:paper)
50 | # define pattern image from filesystem (relative to user themes folder) for color `ThemeColor.bottomBackgroundColor`
51 | bottomBackgroundColor = pattern(file:../some/path/some-file.png)
52 | # use named image "apple"
53 | namedImage = image(named:apple)
54 | # use image from filesystem (relative to user themes folder)
55 | fileImage = image(file:../some/path/some-file.jpg)
56 |
57 | // *********************** Common Colors ************************ //
58 | blue = rgb(0, 170, 255)
59 | orange.sky = rgb(160, 90, 45, .5)
60 |
61 | // ********************** Fallback Assets *********************** //
62 | fallbackForegroundColor = rgb(255, 10, 90, 1.0)
63 | fallbackBackgroundColor = rgb(255, 200, 190)
64 | fallbackGradient = linear-gradient($blue, rgba(200, 140, 60, 1.0))
65 | ```
66 |
67 | With the exception of system overrided named colors (e.g., `labelColor`), which
68 | defaults to the original system provided named color, unimplemented properties
69 | on theme file will default to `-fallbackForegroundColor`, `-fallbackBackgroundColor`,
70 | `-fallbackGradient` and `-fallbackImage`, for foreground color, background color,
71 | gradients and images, respectively.
72 |
73 |
74 | */
75 | @objc(TKUserTheme)
76 | public class UserTheme: NSObject, Theme {
77 | /// Unique theme identifier.
78 | public var identifier: String = "{Theme-Not-Loaded}"
79 |
80 | /// Theme display name.
81 | public var displayName: String = "Theme Not Loaded"
82 |
83 | /// Theme short display name.
84 | public var shortDisplayName: String = "Not Loaded"
85 |
86 | /// Is this a dark theme?
87 | public var isDarkTheme: Bool = false
88 |
89 | /// File URL.
90 | @objc public var fileURL: URL?
91 |
92 | /// Dictionary with key/values pairs read from the .theme file
93 | private var _keyValues: NSMutableDictionary = NSMutableDictionary()
94 |
95 | /// Dictionary with evaluated key/values pairs read from the .theme file
96 | private var _evaluatedKeyValues: NSMutableDictionary = NSMutableDictionary()
97 |
98 | // MARK: -
99 | // MARK: Initialization
100 |
101 | /// `init()` is disabled.
102 | private override init() {
103 | super.init()
104 | }
105 |
106 | /// Calling `init(_:)` is not allowed outside this library.
107 | /// Use `ThemeManager.shared.theme(:_)` instead.
108 | ///
109 | /// - parameter themeFileURL: A theme file (`.theme`) URL.
110 | ///
111 | /// - returns: An instance of `UserTheme`.
112 | @objc internal init(_ themeFileURL: URL) {
113 | super.init()
114 |
115 | // Load file
116 | fileURL = themeFileURL
117 | loadThemeFile(from: themeFileURL)
118 | }
119 |
120 | /// Reloads user theme from file.
121 | @objc public func reload() {
122 | if let url = fileURL {
123 | _keyValues.removeAllObjects()
124 | _evaluatedKeyValues.removeAllObjects()
125 | loadThemeFile(from: url)
126 | }
127 | }
128 |
129 | // MARK: -
130 | // MARK: Theme Assets
131 |
132 | /// Theme asset for the specified key. Supported assets are `NSColor`, `NSGradient`, `NSImage` and `NSString`.
133 | ///
134 | /// - parameter key: A color name, gradient name, image name or a theme string
135 | ///
136 | /// - returns: The theme value for the specified key.
137 | @objc public func themeAsset(_ key: String) -> Any? {
138 | var value = _evaluatedKeyValues[key]
139 | if value == nil,
140 | let evaluatedValue = _keyValues.evaluatedObject(key: key) {
141 | value = evaluatedValue
142 | _evaluatedKeyValues.setObject(evaluatedValue, forKey: key as NSString)
143 | }
144 | return value
145 | }
146 |
147 | /// Checks if a theme asset is provided for the given key.
148 | ///
149 | /// Do not check for theme asset availability with `themeAsset(_:)`, use
150 | /// this method instead, which is much faster.
151 | ///
152 | /// - parameter key: A color name, gradient name, image name or a theme string
153 | ///
154 | /// - returns: `true` if theme provides an asset for the given key; `false` otherwise.
155 | @objc public func hasThemeAsset(_ key: String) -> Bool {
156 | return _keyValues[key] != nil
157 | }
158 |
159 | // MARK: -
160 | // MARK: File
161 |
162 | /// Load theme file
163 | ///
164 | /// - parameter from: A theme file (`.theme`) URL.
165 | private func loadThemeFile(from: URL) {
166 | // Load contents from theme file
167 | if let themeContents = try? String(contentsOf: from, encoding: String.Encoding.utf8) {
168 |
169 | // Split content into lines
170 | var lineCharset = CharacterSet(charactersIn: ";")
171 | lineCharset.formUnion(CharacterSet.newlines)
172 | let lines: [String] = themeContents.components(separatedBy: lineCharset)
173 |
174 | // Parse lines
175 | for line in lines {
176 | // Trim
177 | let trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces)
178 |
179 | // Skip comments
180 | if trimmedLine.hasPrefix("#") || trimmedLine.hasPrefix("//") {
181 | continue
182 | }
183 |
184 | // Assign theme key-values (lazy evaluation)
185 | let assignment = trimmedLine.components(separatedBy: "=")
186 | if assignment.count == 2 {
187 | let key = assignment[0].trimmingCharacters(in: CharacterSet.whitespaces)
188 | let value = assignment[1].trimmingCharacters(in: CharacterSet.whitespaces)
189 | _keyValues.setObject(value, forKey: key as NSString)
190 | }
191 | }
192 |
193 | // Initialize properties with evaluated values from file
194 |
195 | // Identifier
196 | if let identifierString = themeAsset("identifier") as? String {
197 | identifier = identifierString
198 | } else {
199 | identifier = "{identifier: is mising}"
200 | }
201 |
202 | // Display Name
203 | if let displayNameString = themeAsset("displayName") as? String {
204 | displayName = displayNameString
205 | } else {
206 | displayName = "{displayName: is mising}"
207 | }
208 |
209 | // Short Display Name
210 | if let shortDisplayNameString = themeAsset("shortDisplayName") as? String {
211 | shortDisplayName = shortDisplayNameString
212 | } else {
213 | shortDisplayName = "{shortDisplayName: is mising}"
214 | }
215 |
216 | // Dark?
217 | if let isDarkThemeString = themeAsset("darkTheme") as? String {
218 | isDarkTheme = NSString(string: isDarkThemeString).boolValue
219 | } else {
220 | isDarkTheme = false
221 | }
222 | }
223 | }
224 |
225 | override public var description: String {
226 | return "<\(UserTheme.self): \(themeDescription(self))>"
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/ThemeKit.xcodeproj/xcshareddata/xcschemes/ThemeKit.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
44 |
50 |
51 |
52 |
53 |
59 |
60 |
66 |
67 |
68 |
69 |
71 |
72 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/macOSThemeKit.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'macOSThemeKit'
3 | s.version = '1.4.0'
4 | s.license = { :type => 'MIT', :file => 'LICENSE' }
5 | s.summary = 'macOS Theming Framework'
6 | s.homepage = 'https://github.com/luckymarmot/ThemeKit'
7 | s.screenshots = 'https://github.com/luckymarmot/ThemeKit/raw/master/Imgs/ThemeKit.gif'
8 | s.authors = { 'Paw' => 'https://paw.cloud',
9 | 'Nuno Grilo' => 'http://nunogrilo.com' }
10 | s.source = { :git => 'https://github.com/luckymarmot/ThemeKit.git',
11 | :tag => s.version }
12 | s.documentation_url = 'http://themekit.nunogrilo.com'
13 |
14 | s.platform = :osx, '10.10'
15 | s.requires_arc = true
16 | s.swift_version = '5.4.1'
17 |
18 | s.source_files = 'Sources/**/*.swift'
19 | end
20 |
--------------------------------------------------------------------------------