• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

luckymarmot/ThemeKit: macOS theming library

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称(OpenSource Name):

luckymarmot/ThemeKit

开源软件地址(OpenSource Url):

https://github.com/luckymarmot/ThemeKit

开源编程语言(OpenSource Language):

Swift 99.2%

开源软件介绍(OpenSource Introduction):

ThemeKit

macOS Swift4 Release MIT CocoaPods Carthage

Summary

ThemeKit is a lightweight theming library completely written in Swift that provides theming capabilities to both Swift and Objective-C macOS applications.

ThemeKit is brought to you with ❤️ by Nuno Grilo and the Paw team.

ThemeKit Animated Demo

QuickStart

Table of Contents

Features

  • Written in Swift 4.2
  • Optional configuration, none required
  • Neglected performance impact
  • Automatically theme windows (configurable)
  • Themes:
    • LightTheme (default macOS appearance)
    • DarkTheme
    • SystemTheme (default theme). Dynamically resolves to ThemeManager.lightTheme or ThemeManager.darkTheme, depending on the "System Preferences > General > Appearance".
    • Support for custom themes (Theme)
    • Support for user-defined themes (UserTheme)
  • Theme-aware assets:
    • ThemeColor: colors that dynamically change with the theme
    • ThemeGradient: gradients that dynamically change with the theme
    • ThemeImage: images that dynamically change with the theme
    • Optional override of NSColor named colors (e.g., labelColor) to dynamically change with the theme

Installation

ThemeKit Version Swift Version
1.0.0 3.0
1.1.0 4.0
1.2.0 4.1
1.2.3 4.2

There are multiple options to include ThemeKit on your project:

  • CocoaPods

    Add to your Podfile:

    use_frameworks!
    target '[YOUR APP TARGET]' do
        pod 'macOSThemeKit', '~> 1.2.0'
    end
    

    When using CocoaPods, the ThemeKit module is named macOSThemeKit:

    import macOSThemeKit
    
  • Carthage

    github "luckymarmot/ThemeKit"
    

    Then import ThemeKit module with:

    import ThemeKit
    
  • Manually

    • Either add ThemeKit.framework on your project, or, manually add source files from the ThemeKit\ folder to your project
    • If importing into a Objective-C project, you will need to include all the Swift related frameworks as well (as reported here)

    Then import ThemeKit module with:

    import ThemeKit
    

Usage

Simple Usage

At its simpler usage, applications can be themed with a single line command:

In Swift:
func applicationWillFinishLaunching(_ notification: Notification) {
	
	/// Apply the dark theme
	ThemeManager.darkTheme.apply()
	
	/// or, the light theme
	//ThemeManager.lightTheme.apply()
	
	/// or, the 'system' theme, which dynamically changes to light or dark, 
	/// respecting *System Preferences > General > Appearance* setting.
	//ThemeManager.systemTheme.apply()
	
}
In Objective-C:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {
	
	// Apply the dark theme
	TKDarkTheme *darkTheme = TKThemeManager.darkTheme;
	[[TKThemeManager sharedManager] setTheme:darkTheme];
	
}

Advanced Usage

The following code will define which windows should be automatically themed (WindowThemePolicy) and add support for user themes (UserTheme):

In Swift:
func applicationWillFinishLaunching(_ notification: Notification) {

	/// Define default theme.
	/// Used on first run. Default: `SystemTheme`.
	/// Note: `SystemTheme` is a special theme that resolves to `ThemeManager.lightTheme` or `ThemeManager.darkTheme`,
	/// depending on the macOS preference at 'System Preferences > General > Appearance'.
	ThemeManager.defaultTheme = ThemeManager.lightTheme
	
	/// Define window theme policy.
	ThemeManager.shared.windowThemePolicy = .themeAllWindows
	//ThemeManager.shared.windowThemePolicy = .themeSomeWindows(windowClasses: [MyWindow.self])
	//ThemeManager.shared.windowThemePolicy = .doNotThemeSomeWindows(windowClasses: [NSPanel.self])
	//ThemeManager.shared.windowThemePolicy = .doNotThemeWindows
	    
	/// Enable & configure user themes.
	/// Will use folder `(...)/Application Support/{your_app_bundle_id}/Themes`.
	let applicationSupportURLs = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
	let thisAppSupportURL = URL.init(fileURLWithPath: applicationSupportURLs.first!).appendingPathComponent(Bundle.main.bundleIdentifier!)
	let userThemesFolderURL = thisAppSupportURL.appendingPathComponent("Themes")
	ThemeManager.shared.userThemesFolderURL = userThemesFolderURL
	
	/// Change the default light and dark theme, used when `SystemTheme` is selected.
	//ThemeManager.lightTheme = ThemeManager.shared.theme(withIdentifier: PaperTheme.identifier)!
	//ThemeManager.darkTheme = ThemeManager.shared.theme(withIdentifier: "com.luckymarmot.ThemeKit.PurpleGreen")!
	
	/// Apply last applied theme (or the default theme, if no previous one)
	ThemeManager.shared.applyLastOrDefaultTheme()
	 
}    
In Objective-C:
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification {

    /// Define default theme.
    /// Used on first run. Default: `SystemTheme`.
    /// Note: `SystemTheme` is a special theme that resolves to `ThemeManager.lightTheme` or `ThemeManager.darkTheme`,
    /// depending on the macOS preference at 'System Preferences > General > Appearance'.
    [TKThemeManager setDefaultTheme:TKThemeManager.lightTheme];
    
    /// Define window theme policy.
    [TKThemeManager sharedManager].windowThemePolicy = TKThemeManagerWindowThemePolicyThemeAllWindows;
    //[TKThemeManager sharedManager].windowThemePolicy = TKThemeManagerWindowThemePolicyThemeSomeWindows;
    //[TKThemeManager sharedManager].themableWindowClasses = @[[MyWindow class]];
    //[TKThemeManager sharedManager].windowThemePolicy = TKThemeManagerWindowThemePolicyDoNotThemeSomeWindows;
    //[TKThemeManager sharedManager].notThemableWindowClasses = @[[NSPanel class]];
    //[TKThemeManager sharedManager].windowThemePolicy = TKThemeManagerWindowThemePolicyDoNotThemeWindows;
    
    /// Enable & configure user themes.
    /// Will use folder `(...)/Application Support/{your_app_bundle_id}/Themes`.
    NSArray<NSString*>* applicationSupportURLs = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
    NSURL* thisAppSupportURL = [[NSURL fileURLWithPath:applicationSupportURLs.firstObject] URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier];
    NSURL* userThemesFolderURL = [thisAppSupportURL URLByAppendingPathComponent:@"Themes"];
    [TKThemeManager sharedManager].userThemesFolderURL = userThemesFolderURL;
    
    /// Change the default light and dark theme, used when `SystemTheme` is selected.
    //TKThemeManager.lightTheme = [[TKThemeManager sharedManager] themeWithIdentifier:PaperTheme.identifier];
    //TKThemeManager.darkTheme = [[TKThemeManager sharedManager] themeWithIdentifier:@"com.luckymarmot.ThemeKit.PurpleGreen"];
    
    /// Apply last applied theme (or the default theme, if no previous one)
    [[TKThemeManager sharedManager] applyLastOrDefaultTheme];
    
}

Please check the Demo application source code for a more complete usage example of ThemeKit.

Observing theme changes

ThemeKit provides the following notifications:

  • Notification.Name.willChangeTheme is sent when current theme is about to change
  • Notification.Name.didChangeTheme is sent when current theme did change
  • Notification.Name.didChangeSystemTheme is sent when system theme did change (System Preference > General)

Example:

// Register to be notified of theme changes
NotificationCenter.default.addObserver(self, selector: #selector(changedTheme(_:)), name: .didChangeTheme, object: nil)

@objc private func changedTheme(_ notification: Notification) {
	// ...
}

Additionally, the following properties are KVO compliant:

Example:

// Register for KVO changes on ThemeManager.shared.effectiveTheme
ThemeManager.shared.addObserver(self, forKeyPath: "effectiveTheme", options: NSKeyValueObservingOptions.init(rawValue: 0), context: nil)

public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
	if keyPath == "effectiveTheme" {
		// ...
   }
}

Manually theming windows

In case (WindowThemePolicy) was NOT set to .themeAllWindows, you may need to manually theme a window. You can use our NSWindow extension for that:

NSWindow Extension
  • NSWindow.theme()

    Theme window if appearance needs update. Doesn't check for policy compliance.

  • NSWindow.themeIfCompliantWithWindowThemePolicy()

    Theme window if compliant to ThemeManager.shared.windowThemePolicy (and if appearance needs update).

  • NSWindow.themeAllWindows()

    Theme all windows compliant to ThemeManager.shared.windowThemePolicy (and if appearance needs update).

  • NSWindow.windowTheme

    Any window specific theme. This is, usually, nil, which means the current global theme will be used. Please note that when using window specific themes, only the associated NSAppearance will be automatically set. All theme aware assets (ThemeColor, ThemeGradient and ThemeImage) should call methods that returns a resolved color instead (which means they don't change with the theme change, you need to observe theme changes manually, and set colors afterwards):

    • ThemeColor.color(for view:, selector:)
    • ThemeGradient.gradient(for view:, selector:)
    • ThemeImage.image(for view:, selector:)

    Additionally, please note that system overridden colors (NSColor.*) will always use the global theme.

  • NSWindow.windowEffectiveTheme

    Returns the current effective theme (read-only).

  • NSWindow.windowEffectiveThemeAppearance

    Returns the current effective appearance (read-only).

Theme-aware Assets

ThemeColor, ThemeGradient and ThemeImage provides colors, gradients and images, respectively, that dynamically change with the current theme.

Additionally, named colors from the NSColor class defined on the ThemeColor subclass extension will override the system ones, providing theme-aware colors.

For example, a project defines a ThemeColor.brandColor color. This will resolve to different colors at runtime, depending on the selected theme:

  • ThemeColor.brandColor will resolve to NSColor.blue if the light theme is selected
  • ThemeColor.brandColor will resolve to NSColor.white if the dark theme is selected
  • ThemeColor.brandColor will resolve to rgba(100, 50, 0, 0.5) for some user-defined theme (UserTheme)

Similarly, defining a ThemeColor.labelColor will override NSColor.labelColor (ThemeColor is a subclass of NSColor), and ThemeKit will allow labelColor to be customized on a per-theme basis as well.

The NSColor Extension may be useful when overriding colors in ThemeColor extensions.

Fallback Assets

ThemeKit provides a simple fallback mechanism when looking up assets in the current theme. It will search for assets, in order:

  • the asset name, defined in theme (e.g., myBackgroundColor)
  • fallbackForegroundColor, fallbackBackgroundColor, fallbackGradient or fallbackImage defined in theme, depending if asset is a foreground/background color, gradient or image, respectively
  • defaultFallbackForegroundColor, defaultFallbackBackgroundColor, fallbackGradient or defaultFallbackImage defined internally, depending if asset is a foreground/background color, gradient or image, respectively

However, for overridden system named colors, the fallback mechanism is different and simpler:

  • the asset name, defined in theme (e.g., labelColor)
  • original asset defined in the system (e.g., NSColor.labelColor)

Please refer to ThemeColor, ThemeGradient and ThemeImage for more information.

Creating Themes

Native Themes

For creating additional themes, you only need to create a class that conforms to the Theme protocol and extends NSObject.

Sample theme:

import Cocoa
import ThemeKit
	
class MyOwnTheme: NSObject, Theme {
    
    /// Light theme identifier (static).
    public static var identifier: String = "com.luckymarmot.ThemeKit.MyOwnTheme"
    
    /// Unique theme identifier.
    public var identifier: String = MyOwnTheme.identifier
    
    /// Theme display name.
    public var displayName: String = "My Own Theme"
    
    /// Theme short display name.
    public var shortDisplayName: String = "My Own"
    
    /// Is this a dark theme?
    public var isDarkTheme: Bool = false
    
    /// Description (optional).
    public override var description : String {
        return "<\(MyOwnTheme.self): \(themeDescription(self))>"
    }
    
    // MARK: -
    // MARK: Theme Assets
    
    // Here you can define the instance methods for the class methods defined 
    // on `ThemeColor`, `ThemeGradient` and `ThemeImage`, if any. Check
    // documentation of these classes for more details.
}

User Themes

ThemeKit also supports definition of additional themes with simple text files (.theme files). Example of a very basic .theme file:

// ************************* Theme Info ************************* //
displayName = My Theme 1
identifier = com.luckymarmot.ThemeKit.MyTheme1
darkTheme = true

// ********************* Colors & Gradients ********************* //
# define color for `ThemeColor.brandColor`
brandColor = $blue
# define a new color for `NSColor.labelColor` (overriding)
labelColor = rgb(11, 220, 111)
# define gradient for `ThemeGradient.brandGradient`
brandGradient = linear-gradient($orange.sky, rgba(200, 140, 60, 1.0))
 
// ********************* Images & Patterns ********************** //
# define pattern image from named image "paper" for color `ThemeColor.contentBackgroundColor`
contentBackgroundColor = pattern(named:paper)
# define pattern image from filesystem (relative to user themes folder) for color `ThemeColor.bottomBackgroundColor`
bottomBackgroundColor = pattern(file:../some/path/some-file.png)
# define image using named image "apple"
namedImage = image(named:apple)
# define image using from filesystem (relative to user themes folder)
fileImage = image(file:../some/path/some-file.jpg)

// *********************** Common Colors ************************ //
blue = rgb(0, 170, 255)
orange.sky = rgb(160, 90, 45, .5)

// ********************** Fallback Assets *********************** //
fallbackForegroundColor = rgb(255, 10, 90, 1.0)
fallbackBackgroundColor = rgb(255, 200, 190)
fallbackGradient = linear-gradient($blue, rgba(200, 140, 60, 1.0))

To enable support for user themes, just need to set the location for them:

// Setup ThemeKit user themes folder
ThemeManager.shared.userThemesFolderURL = //...

Please refer to UserTheme for more information.

FAQ

Where can I find the API documentation?

Documentation can be found here. You can also install it on Dash.

热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap