├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcbaselines
│ └── ChameleonTests.xcbaseline
│ ├── 8341A37E-AEA5-4935-9652-39C679C43F11.plist
│ └── Info.plist
├── Chameleon.podspec
├── Demos
├── Demos.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── Public
│ ├── TestColors.swift
│ └── ThemeInfo.swift
├── ios
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
└── macos
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
│ ├── Base.lproj
│ └── Main.storyboard
│ ├── NSBox+.swift
│ ├── TestAppearanceManager.swift
│ ├── ViewController.swift
│ └── macos.entitlements
├── LICENSE
├── Package.swift
├── README.md
├── Resources
├── Docs.md
├── Images
│ ├── callable.png
│ ├── classes.png
│ └── leak.png
└── graph.graffle
├── Sources
└── Chameleon
│ ├── Chameleon.swift
│ ├── Core
│ ├── APPError.swift
│ ├── AppearanceManager.swift
│ ├── Appearanced
│ │ ├── AppearancedProtocol.swift
│ │ ├── Callable+Appearanced.swift
│ │ ├── Callable+Attributed.swift
│ │ ├── Callable+Collection.swift
│ │ ├── Callable+Customized.swift
│ │ └── Callable+Original.swift
│ ├── Callable.swift
│ ├── NSObject+Appearance.swift
│ ├── NSObject+Cache.swift
│ └── Typealias.swift
│ ├── Helper
│ ├── AttributedString+.swift
│ ├── Chameleon+Notifier.swift
│ ├── Dictionary+Value.swift
│ ├── NSObject+Swizzing.swift
│ ├── NSUIColor.swift
│ ├── NSUIImage.swift
│ ├── Numeric.swift
│ ├── SaftyCheck.swift
│ └── String+.swift
│ └── NSUIKits
│ ├── APPKit
│ ├── NSBox+.swift
│ ├── NSButton+.swift
│ ├── NSButtonTouchBarItem+.swift
│ ├── NSClipView+.swift
│ ├── NSCollectionView+.swift
│ ├── NSDatePicker+.swift
│ ├── NSImageView+.swift
│ ├── NSLevelIndicator+.swift
│ ├── NSPathControl+.swift
│ ├── NSPathControlItem+.swift
│ ├── NSScrollView+.swift
│ ├── NSSegmentedControl+.swift
│ ├── NSSlider+.swift
│ ├── NSTableRowView+.swift
│ ├── NSTableView+.swift
│ ├── NSText+.swift
│ ├── NSTextField+.swift
│ ├── NSTextView+.swift
│ ├── NSView+.swift
│ └── NSWindow+.swift
│ ├── Implementation.swift
│ └── UIKit
│ ├── UIActivityIndicatorView+.swift
│ ├── UIBarItem+.swift
│ ├── UIButton+.swift
│ ├── UIImageView+.swift
│ ├── UILabel+.swift
│ ├── UINavigationBar+.swift
│ ├── UIPageControl+.swift
│ ├── UIProgressView+.swift
│ ├── UIRefreshControl+.swift
│ ├── UISearchBar+.swift
│ ├── UISearchTextField+.swift
│ ├── UISegmentedControl+.swift
│ ├── UISlider+.swift
│ ├── UIStepper+.swift
│ ├── UISwitch+.swift
│ ├── UITabBar+.swift
│ ├── UITableView+.swift
│ ├── UITextField+.swift
│ ├── UITextView+.swift
│ ├── UIToolbar+.swift
│ └── UIView+.swift
├── Tests
└── ChameleonTests
│ ├── AppearancedTests.swift
│ ├── AttributedStringTests.swift
│ ├── AttributesTests.swift
│ ├── ChameleonTests.swift
│ ├── CheckTests.swift
│ ├── ColorImages.swift
│ ├── ComplexTests.swift
│ ├── DictionaryTests.swift
│ ├── Recycle.swift
│ ├── Resources
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── theme0
│ │ │ ├── Contents.json
│ │ │ ├── bullseye_icon_252137.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── bullseye_icon_252137.png
│ │ │ ├── dinosaur_icon_252012.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── dinosaur_icon_252012.png
│ │ │ └── heart_love_icon_251954.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── heart_love_icon_251954.png
│ │ ├── theme1
│ │ │ ├── Contents.json
│ │ │ ├── home_house_icon_251952.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── home_house_icon_251952.png
│ │ │ ├── human_handsup_icon_251948.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── human_handsup_icon_251948.png
│ │ │ └── mood_happy_icon_251870.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── mood_happy_icon_251870.png
│ │ └── theme2
│ │ │ ├── Contents.json
│ │ │ ├── trash_delete_remove_icon_251766.imageset
│ │ │ ├── Contents.json
│ │ │ └── trash_delete_remove_icon_251766.png
│ │ │ ├── visible_eye_icon_251745.imageset
│ │ │ ├── Contents.json
│ │ │ └── visible_eye_icon_251745.png
│ │ │ └── zap_icon_251733.imageset
│ │ │ ├── Contents.json
│ │ │ └── zap_icon_251733.png
│ └── bullseye_icon_252137.png
│ ├── SwizzingTests.swift
│ └── ThemeInfo.swift
└── Tools
├── Breeder
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── Breeder.entitlements
├── DataCenter.swift
├── DetailViewController.swift
├── FilesViewController.swift
├── InfosViewController.swift
└── MainWindowController.swift
└── Tools.xcodeproj
├── project.pbxproj
└── project.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
├── IDEWorkspaceChecks.plist
└── swiftpm
└── Package.resolved
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcbaselines/ChameleonTests.xcbaseline/8341A37E-AEA5-4935-9652-39C679C43F11.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | AppearancedTests
8 |
9 | testLargeKits()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 3.667114
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcbaselines/ChameleonTests.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | 8341A37E-AEA5-4935-9652-39C679C43F11
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 0
13 | cpuCount
14 | 1
15 | cpuKind
16 | Apple M1
17 | cpuSpeedInMHz
18 | 0
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro17,1
23 | physicalCPUCoresPerPackage
24 | 8
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | arm64
30 | targetDevice
31 |
32 | modelCode
33 | iPhone15,2
34 | platformIdentifier
35 | com.apple.platform.iphonesimulator
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Chameleon.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "Chameleon"
3 | s.version = "0.1.0"
4 | s.summary = "This is an easy framework for change your APP theme."
5 | s.description = <<-DESC
6 | Chameleon is a skin-changing (theme) framework for iOS/macOS that supports dynamic skin changing and custom attributes, and also supports non-invasive skin changing (swizzling).
7 | DESC
8 | s.homepage = "https://github.com/JyHu/Chameleon.git"
9 | s.license = "MIT"
10 | s.authors = {
11 | "JyHu" => "auu.aug@gmail.com",
12 | }
13 |
14 | s.ios.deployment_target = '11.0'
15 | s.osx.deployment_target = '10.13'
16 |
17 | s.source = { :git => "https://github.com/JyHu/Chameleon.git", :tag => s.version }
18 | s.requires_arc = true
19 |
20 | s.source_files = 'Sources/Chameleon/**/*.swift'
21 | end
22 |
--------------------------------------------------------------------------------
/Demos/Demos.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demos/Demos.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Demos/Public/TestColors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestColors.swift
3 | // macos
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | #if os(macOS)
9 | import Cocoa
10 | #else
11 | import UIKit
12 | #endif
13 |
14 | import Chameleon
15 |
16 | extension NSUIAppearanceColor {
17 | static let C001 = NSUIAppearanceColor.colorWith(hexAString: "F7ACBC")
18 | .withAppearanceIdentifier("color/text/C001")
19 | static let C002 = NSUIAppearanceColor.colorWith(hexAString: "DEAB8A")
20 | .withAppearanceIdentifier("color/text/C002")
21 | static let C003 = NSUIAppearanceColor.colorWith(hexAString: "B17936")
22 | .withAppearanceIdentifier("color/text/C003")
23 | static let C004 = NSUIAppearanceColor.colorWith(hexAString: "444693")
24 | .withAppearanceIdentifier("color/text/C004")
25 | static let C005 = NSUIAppearanceColor.colorWith(hexAString: "EF5B9C")
26 | .withAppearanceIdentifier("color/text/C005")
27 | static let C006 = NSUIAppearanceColor.colorWith(hexAString: "FEDCBD")
28 | .withAppearanceIdentifier("color/text/C006")
29 | static let C007 = NSUIAppearanceColor.colorWith(hexAString: "7F7522")
30 | .withAppearanceIdentifier("color/text/C007")
31 | static let C008 = NSUIAppearanceColor.colorWith(hexAString: "2B4490")
32 | .withAppearanceIdentifier("color/text/C008")
33 |
34 |
35 | static let C101 = NSUIAppearanceColor.colorWith(hexAString: "FEEEED")
36 | .withAppearanceIdentifier("color/background/C101")
37 | static let C102 = NSUIAppearanceColor.colorWith(hexAString: "747920")
38 | .withAppearanceIdentifier("color/background/C102")
39 | static let C103 = NSUIAppearanceColor.colorWith(hexAString: "80752C")
40 | .withAppearanceIdentifier("color/background/C103")
41 | static let C104 = NSUIAppearanceColor.colorWith(hexAString: "2A5CAA")
42 | .withAppearanceIdentifier("color/background/C104")
43 | static let C105 = NSUIAppearanceColor.colorWith(hexAString: "F05B72")
44 | .withAppearanceIdentifier("color/background/C105")
45 | static let C106 = NSUIAppearanceColor.colorWith(hexAString: "905A3D")
46 | .withAppearanceIdentifier("color/background/C106")
47 | static let C107 = NSUIAppearanceColor.colorWith(hexAString: "87843B")
48 | .withAppearanceIdentifier("color/background/C107")
49 | static let C108 = NSUIAppearanceColor.colorWith(hexAString: "224B8F")
50 | .withAppearanceIdentifier("color/background/C108")
51 |
52 |
53 |
54 |
55 |
56 | static func colorWith(hexAString: String) -> NSUIAppearanceColor {
57 | let components = hexAString.components(separatedBy: " ")
58 | if components.count <= 1 {
59 | return colorWith(hexString: hexAString)
60 | }
61 |
62 | guard let hexString = components.first,
63 | let alphaString = components.last,
64 | let alpha = Double(alphaString) else {
65 | return .clear
66 | }
67 |
68 | let color = colorWith(hexString: hexString)
69 |
70 | if alpha == 1 {
71 | return color
72 | }
73 |
74 | if alpha == 0 {
75 | return .clear
76 | }
77 |
78 | return color.withAlphaComponent(alpha)
79 | }
80 |
81 | private static func colorWith(hexString: String) -> NSUIAppearanceColor {
82 | var hexValue: UInt = 0
83 | let scanner = Scanner(string: hexString)
84 | scanner.scanInt(&hexValue)
85 |
86 | return NSUIAppearanceColor(red: CGFloat((hexValue & 0xFF0000) >> 16) / 255, green: CGFloat((hexValue & 0xFF00) >> 8) / 255, blue: CGFloat(hexValue & 0xFF) / 255, alpha: 1)
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Demos/Public/ThemeInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeInfo.swift
3 | // Demos
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ThemeInfo {
11 | static let theme0: [String: Any] = [
12 | "color": [
13 | "text": [
14 | "C001": "F7ACBC",
15 | "C002": "DEAB8A",
16 | "C003": "B17936",
17 | "C004": "444693",
18 | "C005": "EF5B9C",
19 | "C006": "FEDCBD",
20 | "C007": "7F7522",
21 | "C008": "2B4490"
22 | ],
23 | "background": [
24 | "C101": "FEEEED",
25 | "C102": "747920",
26 | "C103": "80752C",
27 | "C104": "2A5CAA",
28 | "C105": "F05B72",
29 | "C106": "905A3D",
30 | "C107": "87843B",
31 | "C108": "224B8F",
32 | ]
33 | ],
34 | "others": [
35 | "border": [
36 | "width": 5
37 | ]
38 | ]
39 | ]
40 |
41 | static let theme1: [String: Any] = [
42 | "color": [
43 | "text": [
44 | "C001": "F69C9F 0.9",
45 | "C002": "5F3C23 0.9",
46 | "C003": "2E3A1F 0.9",
47 | "C004": "426AB3 0.9",
48 | "C005": "F58F98 0.9",
49 | "C006": "6B473C 0.9",
50 | "C007": "4D4F36 0.9",
51 | "C008": "46485F 0.9"
52 | ],
53 | "background": [
54 | "C101": "F15B6C 0.9",
55 | "C102": "8F4B2E 0.9",
56 | "C103": "726930 0.9",
57 | "C104": "003A6C 0.9",
58 | "C105": "F8ABA6 0.9",
59 | "C106": "87481F 0.9",
60 | "C107": "454926 0.9",
61 | "C108": "102B6A 0.9",
62 | ]
63 | ],
64 | "others": [
65 | "border": [
66 | "width": 2
67 | ]
68 | ]
69 | ]
70 |
71 | static let theme2: [String: Any] = [
72 | "color": [
73 | "text": [
74 | "C001": "CA8687 0.8",
75 | "C002": "FAA755 0.8",
76 | "C003": "B7BA6B 0.8",
77 | "C004": "4E72B8 0.8",
78 | "C005": "F391A6 0.8",
79 | "C006": "FAB27B 0.8",
80 | "C007": "B2D235 0.8",
81 | "C008": "181D4B 0.8"
82 | ],
83 | "background": [
84 | "C101": "BD675B 0.8",
85 | "C102": "F5B220 0.8",
86 | "C103": "5C7A29 0.8",
87 | "C104": "1A2933 0.8",
88 | "C105": "D71345 0.8",
89 | "C106": "843900 0.8",
90 | "C107": "BED742 0.8",
91 | "C108": "6A6DA9 0.8",
92 | ]
93 | ],
94 | "others": [
95 | "border": [
96 | "width": 10
97 | ]
98 | ]
99 | ]
100 | }
101 |
--------------------------------------------------------------------------------
/Demos/ios/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ios
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/Demos/ios/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Demos/ios/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Demos/ios/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demos/ios/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Demos/ios/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Demos/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 | UISceneStoryboardFile
19 | Main
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Demos/ios/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // ios
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Demos/ios/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ios
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import UIKit
9 |
10 | class ViewController: UIViewController {
11 |
12 | override func viewDidLoad() {
13 | super.viewDidLoad()
14 | // Do any additional setup after loading the view.
15 | }
16 |
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/Demos/macos/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // macos
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ aNotification: Notification) {
14 | // Insert code here to initialize your application
15 |
16 | }
17 |
18 | func applicationWillTerminate(_ aNotification: Notification) {
19 | // Insert code here to tear down your application
20 | }
21 |
22 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
23 | return true
24 | }
25 |
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/Demos/macos/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Demos/macos/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Demos/macos/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Demos/macos/NSBox+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSBox+.swift
3 | // macos
4 | //
5 | // Created by Jo on 2023/4/5.
6 | //
7 |
8 | import Cocoa
9 | import Chameleon
10 |
11 | ///
12 | ///
13 | ///
14 | /// 测试自定义换肤属性
15 | ///
16 | ///
17 | ///
18 | extension NSBox {
19 | var app_borderWidth: CGFloat {
20 | set {
21 | cache(appearanceCallable: Callable.One(
22 | firstParam: Callable.Appearanced(newValue, identifier: "others/border/width"),
23 | identifier: "NSBox.__setBorderWidth(_:)",
24 | action: { [weak self] va in self?.__setBorderWidth(va) })
25 | )
26 | }
27 | get {
28 | borderWidth
29 | }
30 | }
31 |
32 | // var optionalAttributedString: NSAttributedString? {
33 | // set {
34 | // cache(
35 | // firstParam: Callable.Attributed(newValue),
36 | // identifier: "NSBox.__setOptionalAttributedString(_:)") { val in
37 | // print("--> \(val)")
38 | // }
39 | // }
40 | // get {
41 | // return nil
42 | // }
43 | // }
44 |
45 | func customized(color1: NSColor, color2: NSColor?) {
46 | cache(appearanceCallable:
47 | Callable.Two(
48 | firstParam: Callable.Customized(color1, identifier: color1.appearanceIdentifier, converter: __colorConverter(_:)),
49 | secondParam: Callable.Customized(color2, identifier: color2?.appearanceIdentifier, converter: __colorConverter(_:)),
50 | identifier: "NSBox.__customized(color1:color2:)",
51 | action: { [weak self] va, vb in { self?.__customized(color1: va, color2: vb) }}
52 | )
53 | )
54 | }
55 | }
56 |
57 | private extension NSBox {
58 | func __setBorderWidth(_ borderWidth: CGFloat) {
59 | self.borderWidth = borderWidth
60 | }
61 |
62 | func __customized(color1: NSColor, color2: NSColor?) {
63 | print("--> \(color1), \(String(describing: color2))")
64 | }
65 |
66 | func __colorConverter(_ appearanceInfo: Any?) -> NSColor? {
67 | let callable = findCallable(with: "NSBox.__customized(color1:color2:)") as? Callable.Two, Callable.Customized>
68 | let p1 = callable?.firstParam.original
69 | let p2 = callable?.secondParam.original
70 | print("--> \(String(describing: appearanceInfo)) \(String(describing: p1)) \(String(describing: p2))")
71 | return nil
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Demos/macos/TestAppearanceManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TestAppearanceManager.swift
3 | // macos
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | import Foundation
9 | import Chameleon
10 |
11 | class MyTheme {
12 | var identifier: String = UUID().uuidString
13 | var name: String = "My Theme"
14 | var folder: URL? = nil
15 |
16 | fileprivate var appearanceInfo: [String: Any]?
17 | }
18 |
19 | class TestAppearanceManager: NSObject {
20 | static var shared = TestAppearanceManager()
21 |
22 | private(set) var themes: [MyTheme] = []
23 | private var themesMap: [String: MyTheme] = [:]
24 |
25 | override init() {
26 | super.init()
27 | /// 这两个协议可用可不用,主要是为了开放更多的能力给使用者,这里仅作示例
28 | AppearanceManager.shared.delegate = self
29 | AppearanceManager.shared.customizedDelegate = self
30 |
31 | loadLocalThemes()
32 | downloadThemes()
33 | }
34 |
35 | func switchTheme(_ identifier: String) {
36 | guard let theme = themesMap[identifier] else { return }
37 | if theme.appearanceInfo == nil {
38 | // let themeInfo = ....
39 | // theme.appearanceInfo = ....
40 | }
41 |
42 | guard let themeInfo = theme.appearanceInfo else { return }
43 | AppearanceManager.shared.changeThemeWith(themeInfo: themeInfo)
44 | }
45 | }
46 |
47 | private extension TestAppearanceManager {
48 | func loadLocalThemes() {
49 | /// 加载本地的皮肤资源文件
50 | /// 然后缓存到themes中
51 | }
52 |
53 | func downloadThemes() {
54 | /// 下载云端的换肤资源
55 | }
56 |
57 | func transferToMap() {
58 | /// themes -> themesMap
59 | /// 方便查找
60 | for theme in themes {
61 | themesMap[theme.identifier] = theme
62 | }
63 | }
64 | }
65 |
66 | /// 自己管理换肤资源,需要向框架内提供
67 | extension TestAppearanceManager: AppearanceManagerProtocol {
68 | func appearanceInfo(with identifier: Chameleon.AppearanceCallableIdentifier) throws -> Any? {
69 | return nil
70 | }
71 |
72 | var currentThemePath: String? {
73 | return nil
74 | }
75 | }
76 |
77 | /// 开放给使用者自己来处理颜色、图片的转换和查找
78 | extension TestAppearanceManager: CustomizedAppearanceProtocol {
79 | func customizedAppearancedColor(of appearanceInfo: Any, identifier: Chameleon.AppearanceCallableIdentifier) -> Chameleon.CustomizedAppearanceResult {
80 | return .result(.red, true)
81 | }
82 |
83 | func customizedAppearancedImage(of appearanceInfo: Any, identifier: Chameleon.AppearanceCallableIdentifier) -> Chameleon.CustomizedAppearanceResult {
84 | return .ignored
85 | }
86 |
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/Demos/macos/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // macos
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import Cocoa
9 | import Chameleon
10 |
11 | class ViewController: NSViewController {
12 |
13 | @IBOutlet weak var testView: NSView!
14 |
15 | @IBOutlet weak var testBox: NSBox!
16 |
17 | @IBOutlet var testTextView: NSTextView!
18 |
19 | @IBOutlet weak var testTextField: NSTextField!
20 |
21 | @IBOutlet weak var multilineLabel: NSTextField!
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | AppearanceManager.exchangeImplementations()
27 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
28 |
29 | testBox.customized(color1: .C101, color2: .C103)
30 |
31 | testBox.borderWidth = 5
32 |
33 | testView.app_layerBackgroundColor = .C101
34 | testView.app_layerBorderColor = .C108
35 | testView.layer?.borderWidth = 5
36 |
37 | testBox.app_borderColor = .C001
38 | testBox.app_fillColor = .C002
39 | testBox.app_borderWidth = 5
40 | testTextView.app_backgroundColor = .C003
41 | testTextView.app_textColor = .C004
42 | testTextField.app_backgroundColor = .C107
43 | testTextField.app_textColor = .C005
44 |
45 | let attributedString = NSMutableAttributedString(string: "Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.")
46 | attributedString.addAttributes([.foregroundColor: NSColor.C002], range: NSMakeRange(0, 4))
47 | attributedString.addAttributes([.foregroundColor: NSColor.C006], range: NSMakeRange(8, 5))
48 | attributedString.addAttributes([.font: NSFont.systemFont(ofSize: 18)], range: NSMakeRange(2, 10))
49 | multilineLabel.app_attributedStringValue = attributedString
50 |
51 | // testBox.borderColor = .c002
52 | // testBox.fillColor = .priceColor
53 | // testTextView.backgroundColor = .link
54 | // testTextView.textColor = .priceColor
55 | }
56 |
57 | @IBAction func switchThemeAction(_ sender: NSSegmentedControl) {
58 | if sender.indexOfSelectedItem == 0 {
59 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
60 | } else if sender.indexOfSelectedItem == 1 {
61 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme1)
62 | } else if sender.indexOfSelectedItem == 2 {
63 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme2)
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Demos/macos/macos.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 胡金友
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 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Chameleon",
8 | platforms: [ .macOS(.v10_13), .iOS(.v11) ],
9 | products: [
10 | .library(
11 | name: "Chameleon",
12 | targets: ["Chameleon"]
13 | ),
14 | ],
15 | targets: [
16 | .target(
17 | name: "Chameleon",
18 | dependencies: []
19 | ),
20 | .testTarget(
21 | name: "ChameleonTests",
22 | dependencies: ["Chameleon"],
23 | resources: [.process("Resources")]
24 | )
25 | ]
26 | )
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Chameleon
2 |
3 | Chameleon is a theme framework designed for iOS/macOS, which allows for dynamic theme changes and customization of attributes. It also supports non-intrusive theme changes (swizzling) to alter the interface style. In other words, Chameleon provides a convenient way for developers to implement theme changes and custom attribute functionality in their applications without requiring extensive modifications to the existing codebase.
4 |
5 | ## Requirements
6 |
7 | - iOS 11.0+/ macOS 10.13+
8 | - Xcode 10.0+
9 | - Swift 5.0+
10 |
11 | ## Installation
12 |
13 | ### Swift Package Manager
14 |
15 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies.
16 |
17 | ```
18 | dependencies: [
19 | .package(url: "https://github.com/JyHu/Chameleon.git", .upToNextMajor(from: "0.1.0"))
20 | ]
21 | ```
22 |
23 | ### Cocoapods
24 |
25 | [CocoaPods](http://cocoapods.org/) is a dependency manager for Cocoa projects. You can install it with the following command:
26 |
27 | ```
28 | $ gem install cocoapods
29 | ```
30 |
31 | To integrate Chameleon into your Xcode project using CocoaPods, specify it in your `Podfile`:
32 |
33 | ```
34 | source 'https://github.com/CocoaPods/Specs.git'
35 | platform :ios, '11.0'
36 | # platform: macos, '10.13'
37 | use_frameworks!
38 |
39 | target '' do
40 | pod 'Chameleon', :git => 'https://github.com/JyHu/Chameleon.git'
41 | end
42 | ```
43 |
44 | Then, run the following command:
45 |
46 | ```
47 | $ pod install
48 | ```
49 |
50 | ## Resources
51 |
52 | - [Documentation](Resources/Docs.md)
53 |
54 | ## License
55 |
56 | Chameleon is released under the MIT license. See LICENSE for details.
57 |
--------------------------------------------------------------------------------
/Resources/Images/callable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Resources/Images/callable.png
--------------------------------------------------------------------------------
/Resources/Images/classes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Resources/Images/classes.png
--------------------------------------------------------------------------------
/Resources/Images/leak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Resources/Images/leak.png
--------------------------------------------------------------------------------
/Resources/graph.graffle:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Resources/graph.graffle
--------------------------------------------------------------------------------
/Sources/Chameleon/Chameleon.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/2.
6 | //
7 |
8 | import Foundation
9 |
10 | let version = "1.0"
11 |
12 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/APPError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/2.
6 | //
7 |
8 | import Foundation
9 |
10 | enum APPError: Error {
11 | /// 解析换肤属性索引错误
12 | /// - 当前换肤数据的key
13 | /// - 当前遍历到的索引
14 | case parseIndexError(String, Int)
15 |
16 | /// 根据换肤标识获取对应的换肤属性的时候失败,找不到有效的字典数据
17 | case unknownAppearanceInfo(Any)
18 |
19 | /// 解析换肤文件中图片资源的时候遇到未知的图片数据
20 | case unknowImageAppearance(Any)
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/AppearancedProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | import Foundation
9 |
10 | ///
11 | /// 每一个换肤属性持有者都必须要遵守的协议
12 | ///
13 | public protocol AppearancedProtocol {
14 |
15 | /// 缓存当前入参的数据类型
16 | associatedtype InputType
17 |
18 | /// 缓存的原始参数值
19 | var original: InputType { get }
20 |
21 | /// 是否是支持换肤的属性
22 | var isAppearanced: Bool { get }
23 |
24 | /// 根据当前参数数据从换肤资源中获取对应有效的换肤数据
25 | var correct: InputType { get }
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/Callable+Appearanced.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Callable {
11 | /// 数值类型
12 | enum NumType {
13 | case int
14 | case int8
15 | case int16
16 | case int32
17 | case int64
18 |
19 | case uint
20 | case uint8
21 | case uint16
22 | case uint32
23 | case uing64
24 |
25 | case double
26 | case cgfloat
27 |
28 | case number
29 | }
30 |
31 | /// 当前换肤参数的类型
32 | enum ClsType {
33 | /// 颜色类型参数
34 | case color
35 | /// 图片类型参数
36 | case image
37 | /// 数值类型
38 | case numeric(NumType)
39 | /// 其他类型参数
40 | case other
41 |
42 | init(obj: Any) {
43 | if obj is NSUIAppearanceColor { self = .color }
44 | else if obj is NSUIAppearanceImage { self = .image }
45 | else if obj is Int { self = .numeric(.int) }
46 | else if obj is Int8 { self = .numeric(.int8) }
47 | else if obj is Int16 { self = .numeric(.int16) }
48 | else if obj is Int32 { self = .numeric(.int32) }
49 | else if obj is Int64 { self = .numeric(.int64) }
50 | else if obj is UInt { self = .numeric(.uint) }
51 | else if obj is UInt8 { self = .numeric(.uint8) }
52 | else if obj is UInt16 { self = .numeric(.uint16) }
53 | else if obj is UInt32 { self = .numeric(.uint32) }
54 | else if obj is UInt64 { self = .numeric(.uing64) }
55 | else if obj is Double { self = .numeric(.double) }
56 | else if obj is CGFloat { self = .numeric(.cgfloat) }
57 | else if obj is NSNumber { self = .numeric(.number) }
58 | else { self = .other }
59 | }
60 | }
61 | }
62 |
63 | public extension Callable {
64 | /// 对单个换肤参数的更高一层的包装,用于记录当前入参的一些通用属性
65 | ///
66 | ///
67 | /// 该类支持Color、Image、常用数值类型的换肤属性支持
68 | ///
69 | ///
70 | struct Appearanced: AppearancedProtocol {
71 | public typealias InputType = T
72 |
73 | /// 缓存的原始参数值
74 | public let original: T
75 | /// 换肤参数的标识符,用于从换肤资源中找到对应的换肤属性
76 | public let identifier: AppearanceCallableIdentifier?
77 | /// 参数类型
78 | public let clsType: ClsType
79 | /// 是否是支持换肤的属性
80 | public var isAppearanced: Bool {
81 | identifier != nil
82 | }
83 |
84 | /// 初始化方法
85 | /// - Parameter original: 原始的参数值
86 | public init(_ original: T) {
87 | self.original = original
88 | self.identifier = (original as? AppearancedParamProtocol)?.appearanceIdentifier
89 | self.clsType = ClsType(obj: original)
90 | }
91 |
92 | /// 初始化方法
93 | /// - Parameters:
94 | /// - original: 传入进来的原始值
95 | /// - identifier: 换肤标识
96 | /// - clsType: 原始值的类型,如果没有设定,框架内部会根据原始值自己判断
97 | public init(_ original: T, identifier: AppearanceCallableIdentifier?, clsType: ClsType? = nil) {
98 | self.original = original
99 | self.identifier = identifier
100 | if let clsType = clsType {
101 | self.clsType = clsType
102 | } else {
103 | self.clsType = ClsType(obj: original)
104 | }
105 | }
106 |
107 | /// 根据当前参数数据从换肤资源中获取对应有效的换肤数据
108 | public var correct: T {
109 | /// 如果不支持换肤,返回原始值
110 | guard let identifier = identifier else { return original }
111 |
112 | func pickup(_ object: Any?) -> T {
113 | guard let object = object else { return original }
114 | return (object as? T) ?? original
115 | }
116 |
117 | switch clsType {
118 | case .color: return pickup(AppearanceManager.shared.color(with: identifier))
119 | case .image: return pickup(AppearanceManager.shared.image(with: identifier))
120 | case .numeric(let numType):
121 | let numVal = (try? AppearanceManager.shared.appearanceInfo(with: identifier))
122 | let matchedRes: T? = matchedAppearancedNumValue(from: numVal, of: numType)
123 | return matchedRes ?? original
124 | case .other: return pickup(try? AppearanceManager.shared.appearanceInfo(with: identifier))
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/Callable+Attributed.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Callable {
11 |
12 | ///
13 | ///
14 | ///
15 | /// 该类支持富文本换肤属性的换肤支持
16 | ///
17 | /// 对于富文本换肤,暂时只支持颜色属性的换肤
18 | ///
19 | ///
20 | struct Attributed: AppearancedProtocol {
21 | public typealias InputType = T
22 |
23 | public let original: T
24 |
25 | /// 缓存的当前富文本中所有颜色属性的位置信息
26 | private let elements: [NSAttributedString.ColorElement]
27 |
28 | public var isAppearanced: Bool { elements.count > 0 }
29 |
30 | public var correct: T {
31 | ((original as? NSAttributedString)?.updateAppearancedValues(elements) as? T) ?? original
32 | }
33 | }
34 | }
35 |
36 | public extension Callable.Attributed where T == NSAttributedString {
37 | init(_ original: T) {
38 | self.original = original
39 | self.elements = original.colorAppearancedElements()
40 | }
41 | }
42 |
43 | public extension Callable.Attributed where T == NSAttributedString? {
44 | init(_ original: T) {
45 | self.original = original
46 | self.elements = original?.colorAppearancedElements() ?? []
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/Callable+Collection.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - 实际的数组、字典换肤数据的持有及转换者
11 |
12 | ///
13 | ///
14 | /// 转换协议,因为数组、字典的换肤支持逻辑不一样,需要区别去处理,
15 | /// 但是又需要统一类型,所以用协议来抹平数据类型
16 | ///
17 | ///
18 | private protocol __CollectionAppearanceConvert {
19 | associatedtype OutPut
20 | var correct: OutPut { get }
21 | var isAppearanced: Bool { get }
22 | }
23 |
24 | private extension Callable {
25 | /// 数组类换肤数据的持有、转换者
26 | struct __ArrayHolder: __CollectionAppearanceConvert {
27 | /// 输出类型,即原范型T
28 | typealias OutPut = [V]
29 |
30 | /// 缓存的数组中的各换肤属性,用于后续的还原和查找对应的换肤属性值
31 | private let arrayAppearanced: [Appearanced]
32 |
33 | var correct: [V] {
34 | arrayAppearanced.map { $0.correct }
35 | }
36 |
37 | var isAppearanced: Bool { arrayAppearanced.contains { $0.isAppearanced } }
38 |
39 | init(original: [V]) {
40 | self.arrayAppearanced = original.map { Appearanced($0) }
41 | }
42 | }
43 |
44 | /// 字典类型换肤数据的持有、转换者
45 | struct __DictionaryHolder: __CollectionAppearanceConvert where K: Hashable {
46 | /// 输出类型,即原范型T
47 | typealias OutPut = [K: V]
48 |
49 | /// 缓存的数组中的各换肤属性,用于后续的还原和查找对应的换肤属性值
50 | private let dictionaryAppearanced: [K: Appearanced]
51 |
52 | var correct: [K : V] {
53 | var result: [K: V] = [:]
54 | for (key, value) in dictionaryAppearanced {
55 | result[key] = value.correct
56 | }
57 |
58 | return result
59 | }
60 |
61 | var isAppearanced: Bool { dictionaryAppearanced.contains { $0.value.isAppearanced }}
62 |
63 | init(original: [K: V]) {
64 | var appearances: [K: Callable.Appearanced] = [:]
65 |
66 | for (key, value) in original {
67 | appearances[key] = Callable.Appearanced(value)
68 | }
69 |
70 | self.dictionaryAppearanced = appearances
71 | }
72 | }
73 | }
74 |
75 | // MARK: - 集合属性换肤支持
76 |
77 | public extension Callable {
78 | ///
79 | ///
80 | ///
81 | ///
82 | ///
83 | /// 支持数组、字典类型的换肤属性
84 | ///
85 | /// T:原始的入参类型,比如数组、字典类型
86 | /// E:根据入参类型不同会有不同的定义,可以见后面的4个extension
87 | ///
88 | ///
89 | ///
90 | struct Collection: AppearancedProtocol {
91 | public typealias InputType = T
92 |
93 | public let original: T
94 |
95 | public let isAppearanced: Bool
96 |
97 | public var correct: T {
98 | (converter?.correct as? T) ?? original
99 | }
100 |
101 | private var converter: (any __CollectionAppearanceConvert)?
102 | }
103 | }
104 |
105 | // MARK: - 字典属性
106 |
107 | ///
108 | ///
109 | /// 对于字典类型的集合来说,此处的E表示的是字典的Key,
110 | /// 此处还额外的多了一个范型V,表示字典中的Value,
111 | /// 在这里将入参范型T和入参范型V、E做下关联,即 T == [E: V]
112 | ///
113 | ///
114 |
115 | extension Callable.Collection where E: Hashable {
116 | public init(_ original: T) where T == [E: V] {
117 | self.original = original
118 | self.converter = Callable.__DictionaryHolder(original: original)
119 | self.isAppearanced = self.converter?.isAppearanced ?? false
120 | }
121 | }
122 |
123 | extension Callable.Collection where E: Hashable {
124 | public init(_ original: T) where T == [E: V]? {
125 | self.original = original
126 |
127 | guard let original = original else {
128 | self.isAppearanced = false
129 | return
130 | }
131 |
132 | self.converter = Callable.__DictionaryHolder(original: original)
133 | self.isAppearanced = self.converter?.isAppearanced ?? false
134 | }
135 | }
136 |
137 | // MARK: - 数组属性
138 |
139 | ///
140 | ///
141 | ///
142 | /// 对于数组来说,此处的E表示数组中的Element,即每个元素的类型
143 | /// 在这里将入参范性T和E做下关联,即 T == [E]
144 | ///
145 | ///
146 |
147 | extension Callable.Collection where T == [E] {
148 | public init(_ original: T) {
149 | self.original = original
150 | self.converter = Callable.__ArrayHolder(original: original)
151 | self.isAppearanced = self.converter?.isAppearanced ?? false
152 | }
153 | }
154 |
155 | extension Callable.Collection where T == [E]? {
156 | public init(_ original: T) {
157 | self.original = original
158 |
159 | guard let original = original else {
160 | self.isAppearanced = false
161 | return
162 | }
163 |
164 | self.converter = Callable.__ArrayHolder(original: original)
165 | self.isAppearanced = self.converter?.isAppearanced ?? false
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/Callable+Customized.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Callable {
11 | ///
12 | ///
13 | ///
14 | ///
15 | /// 用于处理自定义的数据类型
16 | ///
17 | ///
18 | ///
19 | ///
20 | struct Customized: AppearancedProtocol {
21 |
22 | public typealias InputType = T
23 |
24 | public let original: T
25 | public let identifier: AppearanceCallableIdentifier?
26 | public let isAppearanced: Bool = true
27 |
28 | /// 获取有效的值
29 | public var correct: T {
30 | guard let identifier = identifier else { return original }
31 | return converter(try? AppearanceManager.shared.appearanceInfo(with: identifier)) ?? original
32 | }
33 |
34 | private var converter: (Any?) -> T?
35 |
36 | /// 初始化方法
37 | /// - Parameters:
38 | /// - original: 原始值
39 | /// - identifier: 当前自定义富文本的换肤资源ID
40 | /// - converter: 转换方法,将主题资源中的数据转换为对应的有效皮肤信息
41 | public init(_ original: T, identifier: AppearanceCallableIdentifier?, converter: @escaping (Any?) -> T?) {
42 | self.original = original
43 | self.identifier = identifier
44 | self.converter = converter
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Appearanced/Callable+Original.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Callable {
11 | ///
12 | ///
13 | ///
14 | /// 过渡的中间类,不需要执行换肤操作的
15 | ///
16 | ///
17 | ///
18 | struct Original: AppearancedProtocol {
19 | public typealias InputType = T
20 |
21 | public var original: InputType
22 |
23 | public var isAppearanced: Bool { false }
24 |
25 | public var correct: InputType { original }
26 |
27 | public init(_ original: InputType) {
28 | self.original = original
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/NSObject+Appearance.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | #if os(macOS)
9 | import Cocoa
10 | #else
11 | import UIKit
12 | #endif
13 |
14 | /// 换肤资源附加标识符的协议,用于唯一的标识这个属性,并用于从换肤资源中找到对应的源数据
15 | public protocol AppearancedParamProtocol {
16 | var appearanceIdentifier: AppearanceCallableIdentifier? { get }
17 | }
18 |
19 | extension NSUIAppearanceColor: AppearancedParamProtocol {
20 | private struct AssociationKey {
21 | static var appearanceIdentifierKey = "com.auu.chameleon.association.appearanceIdentifier"
22 | }
23 |
24 | /// 当前对象的唯一标识符,用于在主题文件中找到对应的唯一属性值
25 | public private(set) var appearanceIdentifier: AppearanceCallableIdentifier? {
26 | set {
27 | objc_setAssociatedObject(self, &AssociationKey.appearanceIdentifierKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
28 | }
29 | get {
30 | return objc_getAssociatedObject(self, &AssociationKey.appearanceIdentifierKey) as? AppearanceCallableIdentifier
31 | }
32 | }
33 |
34 | /// 一个中转方法,用于给一个对象添加一个标识符
35 | /// - Parameter identifier: 换肤的标识符
36 | /// - Returns: 原对象
37 | public func withAppearanceIdentifier(_ identifier: AppearanceCallableIdentifier) -> NSUIAppearanceColor {
38 | appearanceIdentifier = identifier
39 | return self
40 | }
41 | }
42 |
43 | extension NSUIAppearanceImage: AppearancedParamProtocol {
44 | private struct AssociationKey {
45 | static var appearanceIdentifierKey = "com.auu.chameleon.association.appearanceIdentifier"
46 | }
47 |
48 | /// 当前对象的唯一标识符,用于在主题文件中找到对应的唯一属性值
49 | public private(set) var appearanceIdentifier: AppearanceCallableIdentifier? {
50 | set {
51 | objc_setAssociatedObject(self, &AssociationKey.appearanceIdentifierKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
52 | }
53 | get {
54 | return objc_getAssociatedObject(self, &AssociationKey.appearanceIdentifierKey) as? AppearanceCallableIdentifier
55 | }
56 | }
57 |
58 | /// 一个中转方法,用于给一个对象添加一个标识符
59 | /// - Parameter identifier: 换肤的标识符
60 | /// - Returns: 原对象
61 | public func withAppearanceIdentifier(_ identifier: AppearanceCallableIdentifier) -> Self {
62 | if let obj = NSUIAppearanceImage.imageWith(appearanceIdentifier: identifier) {
63 | obj.appearanceIdentifier = identifier
64 | return obj as! Self
65 | }
66 |
67 | appearanceIdentifier = identifier
68 | return self
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Core/Typealias.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/5.
6 | //
7 |
8 | #if canImport(Cocoa)
9 | import Cocoa
10 | public typealias NSUIAppearanceColor = NSColor
11 | public typealias NSUIAppearanceImage = NSImage
12 | #elseif canImport(UIKit)
13 | import UIKit
14 | public typealias NSUIAppearanceColor = UIColor
15 | public typealias NSUIAppearanceImage = UIImage
16 | #endif
17 |
18 | public typealias AppearanceCallableCategory = String
19 | public typealias AppearanceCallableIdentifier = String
20 |
21 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/AttributedString+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension NSAttributedString {
11 | /// 缓存颜色属性的位置信息
12 | struct ColorElement {
13 | /// 颜色值
14 | let color: NSUIAppearanceColor
15 | /// 换肤的颜色属性标识
16 | let identifier: AppearanceCallableIdentifier
17 | /// 颜色属性的位置
18 | let range: NSRange
19 | }
20 |
21 | /// 获取当前富文本的所有颜色属性的位置信息
22 | func colorAppearancedElements() -> [ColorElement] {
23 | var elements: [ColorElement] = []
24 |
25 | func enumerate(_ key: NSAttributedString.Key) {
26 | enumerateAttribute(key, in: NSMakeRange(0, length)) { color, range, stop in
27 | guard let color = color as? NSUIAppearanceColor else { return }
28 | guard let appearanceIdentifier = color.appearanceIdentifier else { return }
29 | elements.append(ColorElement(color: color, identifier: appearanceIdentifier, range: range))
30 | }
31 | }
32 |
33 | enumerate(.foregroundColor)
34 | enumerate(.backgroundColor)
35 | enumerate(.strokeColor)
36 | enumerate(.underlineColor)
37 | enumerate(.strikethroughColor)
38 |
39 | return elements
40 | }
41 |
42 | /// 使用缓存的颜色属性位置信息来更新富文本
43 | func updateAppearancedValues(_ elements: [ColorElement]) -> NSAttributedString {
44 | func getMutableAttributedString() -> NSMutableAttributedString {
45 | if let target = self as? NSMutableAttributedString {
46 | return target
47 | }
48 |
49 | return NSMutableAttributedString(attributedString: self)
50 | }
51 |
52 | let mutableAttributedString = getMutableAttributedString()
53 |
54 | /// 遍历所有的缓存信息,用换肤资源中有效的值来替换
55 | for element in elements {
56 | guard element.range.upperBound <= self.length else { continue }
57 | let color = NSUIAppearanceColor.colorWith(appearanceIdentifier: element.identifier) ?? element.color
58 | mutableAttributedString.addAttributes([.foregroundColor: color], range: element.range)
59 | }
60 |
61 | return mutableAttributedString
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/Chameleon+Notifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/2.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension AppearanceManager {
11 | /// 换肤后发送的通知
12 | static let appearanceChanged = Notification.Name("com.auu.chameleon.notification.appearanceChanged")
13 | }
14 |
15 | internal extension AppearanceManager {
16 | /// 发送主题改变的通知,通知所有注册了主题监听的组件刷新
17 | func postThemeChangeNotification() {
18 | notificationCenter.post(name: AppearanceManager.appearanceChanged, object: nil)
19 | }
20 |
21 | /// 注册主题改变的通知
22 | /// - Parameters:
23 | /// - observer: 要接收通知的对象
24 | /// - action: 接收通知后的操作方法
25 | func registerAppearanceObserver(_ observer: NSObject, action: Selector) {
26 | notificationCenter.addObserver(observer, selector: action, name: AppearanceManager.appearanceChanged, object: nil)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/Dictionary+Value.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/2.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Dictionary where Key == String {
11 |
12 | /// 根据给定的属性标识符,获取对应的属性信息
13 | /// 遍历方式是类似于文件路径,一级一级的查找,如:
14 | /// image/background/xxxxxx
15 | /// 会从当前字典中一层一层的查找
16 | ///
17 | /// - Parameter identifier: 属性标识符
18 | /// - Returns: 换肤资源信息
19 | func appearanceInfo(with identifier: AppearanceCallableIdentifier) throws -> Any? {
20 | if !identifier.contains("/") {
21 | return self[identifier]
22 | }
23 |
24 | let pathComponents = identifier.components(separatedBy: "/")
25 | return try appearanceInfo(pathComponents: pathComponents, originalIdentifier: identifier)
26 | }
27 |
28 | private func appearanceInfo(at index: Int = 0, pathComponents: [String], originalIdentifier: AppearanceCallableIdentifier) throws -> Any? {
29 | guard index < pathComponents.count else {
30 | throw APPError.parseIndexError(originalIdentifier, index)
31 | }
32 |
33 | let key = pathComponents[index]
34 |
35 | guard let curObj = self[key] else {
36 | return nil
37 | }
38 |
39 | if index == pathComponents.count - 1 {
40 | return curObj
41 | }
42 |
43 | guard let curObj = curObj as? [String: Any] else {
44 | throw APPError.unknownAppearanceInfo(curObj)
45 | }
46 |
47 |
48 | return try curObj.appearanceInfo(at: index + 1, pathComponents: pathComponents, originalIdentifier: originalIdentifier)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/NSObject+Swizzing.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension NSObject {
11 | ///
12 | /// Runtime method swizzling
13 | ///
14 | static func app_swizzling(originalSelector: Selector, newSelector: Selector) {
15 | if let originalMethod = class_getInstanceMethod(self, originalSelector),
16 | let swizedMethod = class_getInstanceMethod(self, newSelector) {
17 | method_exchangeImplementations(swizedMethod, originalMethod)
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/NSUIColor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | #if canImport(Cocoa)
9 | import Cocoa
10 | #elseif canImport(UIKit)
11 | import UIKit
12 | #endif
13 |
14 |
15 | public extension NSUIAppearanceColor {
16 | /// 使用颜色属性标识符,从换肤资源中找到对应的有效的颜色值
17 | static func colorWith(appearanceIdentifier: AppearanceCallableIdentifier) -> NSUIAppearanceColor? {
18 | return AppearanceManager.shared.color(with: appearanceIdentifier)
19 | }
20 |
21 | /// 将颜色资源信息转换为有效的颜色值
22 | static func colorWith(appearancedValue: String) -> NSUIAppearanceColor? {
23 | if appearancedValue == "random" {
24 | return app_random()
25 | }
26 |
27 | if appearancedValue.hasPrefix("rgba") {
28 | guard let rgbaStr = appearancedValue.app_substringWithPattern("(?<=<).*?(?=>)"), rgbaStr.count > 0 else { return nil }
29 | let components = rgbaStr.replacingOccurrences(of: " ", with: "").components(separatedBy: ",")
30 | guard components.count == 4 else { return nil }
31 | guard let red = Double(components[0]),
32 | let green = Double(components[1]),
33 | let blue = Double(components[2]),
34 | let alpha = Double(components[3]) else { return nil }
35 |
36 | return app_colorWith(red: red, green: green, blue: blue, alpha: alpha)
37 | }
38 |
39 | if appearancedValue.hasPrefix("hex") {
40 | guard let hexString = appearancedValue.app_substringWithPattern("(?<=<).*?(?=>)"), hexString.count > 0 else { return nil }
41 | return app_colorWith(hexAString: hexString)
42 | }
43 |
44 | return app_colorWith(hexAString: appearancedValue)
45 | }
46 | }
47 |
48 | internal extension NSUIAppearanceColor {
49 | /// 生成随机颜色
50 | static func app_random() -> NSUIAppearanceColor {
51 | return app_colorWith(
52 | red: CGFloat(arc4random_uniform(256)),
53 | green: CGFloat(arc4random_uniform(256)),
54 | blue: CGFloat(arc4random_uniform(256))
55 | )
56 | }
57 |
58 | /// RGBA 颜色
59 | static func app_colorWith(hexAString: String) -> NSUIAppearanceColor? {
60 | let components = hexAString.components(separatedBy: " ")
61 | if components.count <= 1 {
62 | return app_colorWith(hexString: hexAString)
63 | }
64 |
65 | guard let hexString = components.first,
66 | let alphaString = components.last,
67 | let color = app_colorWith(hexString: hexString),
68 | let alpha = Double(alphaString) else {
69 | return nil
70 | }
71 |
72 | if alpha == 1 {
73 | return color
74 | }
75 |
76 | if alpha == 0 {
77 | return .clear
78 | }
79 |
80 | return color.withAlphaComponent(alpha)
81 | }
82 |
83 | /// 16进制色值字符串转换为颜色对象
84 | static func app_colorWith(hexString: String) -> NSUIAppearanceColor? {
85 | if hexString.isEmpty { return nil }
86 | var hexValue: UInt64 = 0
87 | let scanner = Scanner(string: hexString)
88 | scanner.scanHexInt64(&hexValue)
89 | return app_colorWith(hexValue: hexValue)
90 | }
91 |
92 | /// 16进制颜色值转换为颜色对象
93 | static func app_colorWith(hexValue: UInt64) -> NSUIAppearanceColor {
94 | return app_colorWith(
95 | red: CGFloat((hexValue & 0xFF0000) >> 16),
96 | green: CGFloat((hexValue & 0xFF00) >> 8),
97 | blue: CGFloat(hexValue & 0xFF)
98 | )
99 | }
100 |
101 | /// 将RGBA转换为颜色对象
102 | static func app_colorWith(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1) -> NSUIAppearanceColor {
103 | return NSUIAppearanceColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/NSUIImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | #if canImport(Cocoa)
9 | import Cocoa
10 | #elseif canImport(UIKit)
11 | import UIKit
12 | #endif
13 |
14 | public extension NSUIAppearanceImage {
15 | static func imageWith(appearanceIdentifier: AppearanceCallableIdentifier) -> NSUIAppearanceImage? {
16 | return AppearanceManager.shared.image(with: appearanceIdentifier)
17 | }
18 |
19 | static func imageWith(appearancedValue: Any) throws -> NSUIAppearanceImage? {
20 | if let imageInfo = appearancedValue as? String {
21 | if imageInfo.hasPrefix("file://") {
22 | return app_imageWith(fileName: imageInfo.app_substringFrom(7))
23 | }
24 |
25 | return NSUIAppearanceImage(named: imageInfo)
26 | }
27 |
28 | guard let imageInfo = appearancedValue as? [String: Any] else {
29 | throw APPError.unknowImageAppearance(appearancedValue)
30 | }
31 |
32 | if let name = imageInfo["name"] as? String {
33 | return NSUIAppearanceImage(named: name)
34 | }
35 |
36 | if let file = imageInfo["file"] as? String {
37 | return app_imageWith(fileName: file)
38 | }
39 |
40 | if let colorInfo = imageInfo["color"] as? String,
41 | let color = NSUIAppearanceColor.colorWith(appearancedValue: colorInfo) {
42 | return NSUIAppearanceImage.app_imageWith(color: color)
43 | }
44 |
45 | return nil
46 | }
47 | }
48 |
49 | private extension NSUIAppearanceImage {
50 | static func app_imageWith(color: NSUIAppearanceColor, size: CGSize = CGSize(width: 1, height: 1)) -> NSUIAppearanceImage? {
51 | #if os(macOS)
52 | return NSImage(size: size, flipped: true) { dstRect in
53 | color.setFill()
54 | dstRect.fill()
55 | return true
56 | }
57 | #else
58 | let rect = CGRect(origin: .zero, size: size)
59 | UIGraphicsBeginImageContext(size)
60 | let context = UIGraphicsGetCurrentContext()
61 |
62 | context?.setFillColor(color.cgColor)
63 | context?.fill([rect])
64 |
65 | let image = UIGraphicsGetImageFromCurrentImageContext()
66 | UIGraphicsEndImageContext()
67 |
68 | return image
69 | #endif
70 | }
71 |
72 | static func app_imageWith(fileName: String?) -> NSUIAppearanceImage? {
73 | guard let themePath = AppearanceManager.shared.currentThemePath, let fileName = fileName else { return nil }
74 | let imagePath = NSString(string: themePath).appendingPathComponent(fileName)
75 |
76 | guard FileManager.default.fileExists(atPath: imagePath) else {
77 | return nil
78 | }
79 |
80 | return NSUIAppearanceImage(contentsOfFile: imagePath)
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/SaftyCheck.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2024/1/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// 用于比较的资源信息结构体
11 | public struct CheckableAppearanceInfo {
12 | /// 资源的标识符,用于在检查出错时确定是哪个文件
13 | public let identifier: String
14 |
15 | /// 需要比较的资源内容
16 | public let appearanceInfos: [String: Any]
17 |
18 | /// 初始化方法,用于创建一个资源比较的对象
19 | public init(identifier: String, appearanceInfos: [String : Any]) {
20 | self.identifier = identifier
21 | self.appearanceInfos = appearanceInfos
22 | }
23 |
24 | /// 从文件URL初始化,读取文件内容并转换为字典
25 | /// - Parameters:
26 | /// - identifier: 资源的标识符
27 | /// - fileURL: 文件URL
28 | public init(identifier: String, fileURL: URL) {
29 | self.identifier = identifier
30 |
31 | // 检查文件是否存在
32 | guard FileManager.default.fileExists(atPath: fileURL.path) else {
33 | self.appearanceInfos = [:]
34 | return
35 | }
36 |
37 | // 读取文件数据并尝试解析为字典
38 | guard let data = try? Data(contentsOf: fileURL),
39 | let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
40 | self.appearanceInfos = [:]
41 | return
42 | }
43 |
44 | self.appearanceInfos = json
45 | }
46 | }
47 |
48 | /// 表示可能的比较错误的枚举
49 | public enum CheckedAppearanceError: Error, CustomStringConvertible {
50 | /// 传入的资源为空
51 | case emptyFile(_ identifier: String)
52 |
53 | /// 缺少对应路径下的键
54 | case missingKey(_ identifier: String, _ path: String)
55 |
56 | /// 资源类型不匹配
57 | case notMatched(_ path: String)
58 |
59 | /// 描述输出内容
60 | public var description: String {
61 | switch self {
62 | case .emptyFile(let identifier):
63 | return "Empty file of \(identifier)"
64 | case .missingKey(let identifier, let path):
65 | return "Missing key of \(identifier) at \(path)"
66 | case .notMatched(let path):
67 | return "Appearance info not matched at \(path)"
68 | }
69 | }
70 | }
71 |
72 | /// 对外暴露的比较函数,接受一个资源信息数组,返回比较结果的错误数组
73 | /// - Parameter infos: 资源信息数组
74 | /// - Returns: 比较结果的错误数组
75 | public func checkAppearanceInfos(_ infos: [CheckableAppearanceInfo]) -> [CheckedAppearanceError] {
76 | // 至少需要两个资源进行比较
77 | guard infos.count > 1 else { return [] }
78 |
79 | for info in infos {
80 | // 检查每个资源是否为空文件
81 | if info.appearanceInfos.isEmpty {
82 | return [.emptyFile(info.identifier)]
83 | }
84 | }
85 |
86 | // 调用辅助函数进行比较
87 | return _checkAppearanceInfos(infos, path: "")
88 | }
89 |
90 | /// 辅助函数,用于递归比较资源信息
91 | /// - Parameters:
92 | /// - infos: 资源信息数组
93 | /// - path: 当前比较的路径
94 | /// - Returns: 比较结果的错误数组
95 | private func _checkAppearanceInfos(_ infos: [CheckableAppearanceInfo], path: String) -> [CheckedAppearanceError] {
96 | // 初始化所有键的集合
97 | var allKeys: Set = Set(infos[0].appearanceInfos.keys)
98 | var errors: [CheckedAppearanceError] = []
99 |
100 | // 计算所有资源的键的并集
101 | for checkedInfo in infos[1...] {
102 | allKeys.formUnion(checkedInfo.appearanceInfos.keys)
103 | }
104 |
105 | // 逐个比较键
106 | for key in allKeys {
107 | let curPath = path.isEmpty ? key : (path + "/" + key)
108 | var isValue: Bool = false
109 | var containsError: Bool = false
110 | var nextLevleInfos: [CheckableAppearanceInfo] = []
111 |
112 | // 遍历每个资源
113 | for (index, checkedInfo) in infos.enumerated() {
114 | // 检查键是否存在
115 | guard let checkedValue = checkedInfo.appearanceInfos[key] else {
116 | containsError = true
117 | errors.append(.missingKey(checkedInfo.identifier, curPath))
118 | continue
119 | }
120 |
121 | // 如果已经包含错误,跳过后续逻辑,这样的做法只会记录检查键值存在的问题,而不会再匹配数据的一致性
122 | guard !containsError else { continue }
123 |
124 | // 根据值的类型进行不同处理
125 | if let checkedChildren = checkedValue as? [String: Any] {
126 | nextLevleInfos.append(CheckableAppearanceInfo(identifier: checkedInfo.identifier, appearanceInfos: checkedChildren))
127 |
128 | // 判断是否是第一个资源
129 | if index == 0 {
130 | isValue = false
131 | } else if isValue {
132 | containsError = true
133 | errors.append(.notMatched(curPath))
134 | }
135 | } else {
136 | // 判断是否是第一个资源
137 | if index == 0 {
138 | isValue = true
139 | } else if !isValue {
140 | containsError = true
141 | errors.append(.notMatched(curPath))
142 | }
143 | }
144 | }
145 |
146 | // 如果没有包含错误且不是值类型,递归调用进行下一层比较
147 | if !containsError && !isValue {
148 | errors.append(contentsOf: _checkAppearanceInfos(nextLevleInfos, path: curPath))
149 | }
150 | }
151 |
152 | return errors
153 | }
154 |
--------------------------------------------------------------------------------
/Sources/Chameleon/Helper/String+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | import Foundation
9 |
10 | internal extension String {
11 | /// 使用正则截取需要的字符串部分
12 | func app_substringWithPattern(_ pattern: String) -> String? {
13 | let nsstring = NSString(string: self)
14 | let range = nsstring.range(of: pattern, options: .regularExpression)
15 | if range.location == NSNotFound {
16 | return nil
17 | }
18 |
19 | return nsstring.substring(with: range)
20 | }
21 |
22 | /// 获取从指定位置起的字符串
23 | func app_substringFrom(_ index: Int) -> String? {
24 | if index >= count || index < 0 {
25 | return nil
26 | }
27 |
28 | return NSString(string: self).substring(from: index)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSBox+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/3.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let fillColor = "NSBox.__setFillColor(_:)"
14 | static let borderColor = "NSBox.__setBorderColor(_:)"
15 | }
16 |
17 | public extension NSBox {
18 | var app_fillColor: NSColor {
19 | get { fillColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | fillColor = newValue
23 | } else {
24 | swizzled_setFillColor(newValue)
25 | }
26 | }
27 | }
28 |
29 | var app_borderColor: NSColor {
30 | get { borderColor }
31 | set {
32 | if __USING_APPEARANCED_SWIZZLING__ {
33 | borderColor = newValue
34 | } else {
35 | swizzled_setBorderColor(newValue)
36 | }
37 | }
38 | }
39 | }
40 |
41 | internal extension NSBox {
42 | static func silenceExchangeBoxImplementation() {
43 | app_swizzling(
44 | originalSelector: #selector(setter: fillColor),
45 | newSelector: #selector(swizzled_setFillColor(_:))
46 | )
47 |
48 | app_swizzling(
49 | originalSelector: #selector(setter: borderColor),
50 | newSelector: #selector(swizzled_setBorderColor(_:))
51 | )
52 | }
53 | }
54 |
55 | private extension NSBox {
56 | func __setFillColor(_ fillColor: NSColor) {
57 | if __USING_APPEARANCED_SWIZZLING__ {
58 | swizzled_setFillColor(fillColor)
59 | } else {
60 | self.fillColor = fillColor
61 | }
62 | }
63 |
64 | func __setBorderColor(_ borderColor: NSColor) {
65 | if __USING_APPEARANCED_SWIZZLING__ {
66 | swizzled_setBorderColor(borderColor)
67 | } else {
68 | self.borderColor = borderColor
69 | }
70 | }
71 |
72 | @objc func swizzled_setFillColor(_ fillColor: NSColor) {
73 | cache(
74 | firstParam: Callable.Appearanced(fillColor),
75 | identifier: .fillColor,
76 | action: { [weak self] va in self?.__setFillColor(va) }
77 | )
78 | }
79 |
80 | @objc func swizzled_setBorderColor(_ borderColor: NSColor) {
81 | cache(
82 | firstParam: Callable.Appearanced(borderColor),
83 | identifier: .borderColor,
84 | action: { [weak self] va in self?.__setBorderColor(va) }
85 | )
86 | }
87 | }
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSButtonTouchBarItem+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 |
9 | #if os(macOS)
10 | import Cocoa
11 |
12 | @available(macOS 10.15, *)
13 | private extension AppearanceCallableIdentifier {
14 | static let image = "NSButtonTouchBarItem.__setImage(_:)"
15 | static let bezelColor = "NSButtonTouchBarItem.__setBezelColor(_:)"
16 | }
17 |
18 | @available(macOS 10.15, *)
19 | public extension NSButtonTouchBarItem {
20 | var app_image: NSUIAppearanceImage? {
21 | get { image }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | image = newValue
25 | } else {
26 | swizzled_setImage(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_bezelColor: NSUIAppearanceColor? {
32 | get { bezelColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | bezelColor = newValue
36 | } else {
37 | swizzled_setBezelColor(newValue)
38 | }
39 | }
40 | }
41 | }
42 |
43 | @available(macOS 10.15, *)
44 | internal extension NSButtonTouchBarItem {
45 | static func silenceExchangeButtonTouchBarItemImplementation() {
46 | app_swizzling(
47 | originalSelector: #selector(setter: image),
48 | newSelector: #selector(swizzled_setImage(_:))
49 | )
50 |
51 | app_swizzling(
52 | originalSelector: #selector(setter: bezelColor),
53 | newSelector: #selector(swizzled_setBezelColor(_:))
54 | )
55 | }
56 | }
57 |
58 | @available(macOS 10.15, *)
59 | private extension NSButtonTouchBarItem {
60 | func __setImage(_ image: NSUIAppearanceImage?) {
61 | if __USING_APPEARANCED_SWIZZLING__ {
62 | swizzled_setImage(image)
63 | } else {
64 | self.image = image
65 | }
66 | }
67 |
68 | func __setBezelColor(_ bezelColor: NSUIAppearanceColor?) {
69 | if __USING_APPEARANCED_SWIZZLING__ {
70 | swizzled_setBezelColor(bezelColor)
71 | } else {
72 | self.bezelColor = bezelColor
73 | }
74 | }
75 |
76 | @objc func swizzled_setImage(_ image: NSUIAppearanceImage?) {
77 | cache(
78 | firstParam: Callable.Appearanced(image),
79 | identifier: .image,
80 | action: { [weak self] va in self?.__setImage(va) }
81 | )
82 | }
83 |
84 | @objc func swizzled_setBezelColor(_ bezelColor: NSUIAppearanceColor?) {
85 | cache(
86 | firstParam: Callable.Appearanced(bezelColor),
87 | identifier: .bezelColor,
88 | action: { [weak self] va in self?.__setBezelColor(va) }
89 | )
90 | }
91 | }
92 | #endif
93 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSClipView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "NSClipView.__setBackgroundColor(_:)"
14 | }
15 |
16 | public extension NSClipView {
17 | var app_backgroundColor: NSColor {
18 | get { backgroundColor }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | self.backgroundColor = backgroundColor
22 | } else {
23 | swizzled_setBackgroundColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSClipView {
30 | static func silenceExchangeClipViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: backgroundColor),
33 | newSelector: #selector(swizzled_setBackgroundColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSClipView {
39 | func __setBackgroundColor(_ backgroundColor: NSColor) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setBackgroundColor(backgroundColor)
42 | } else {
43 | self.backgroundColor = backgroundColor
44 | }
45 | }
46 |
47 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
48 | cache(
49 | firstParam: Callable.Appearanced(backgroundColor),
50 | identifier: .backgroundColor,
51 | action: { [weak self] va in self?.__setBackgroundColor(va) }
52 | )
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSCollectionView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/9.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColors = "NSCollectionView.__setBackgroundColors(_:)"
14 | }
15 |
16 | public extension NSCollectionView {
17 | var app_backgroundColors: [NSColor] {
18 | get { backgroundColors }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | backgroundColors = newValue
22 | } else {
23 | swizzled_setBackgroundColors(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSCollectionView {
30 | static func silenceExchangeCollectionViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: backgroundColors),
33 | newSelector: #selector(swizzled_setBackgroundColors(_:)))
34 | }
35 | }
36 |
37 | private extension NSCollectionView {
38 | func __setBackgroundColors(_ backgroundColors: [NSColor]) {
39 | if __USING_APPEARANCED_SWIZZLING__ {
40 | swizzled_setBackgroundColors(backgroundColors)
41 | } else {
42 | self.backgroundColors = backgroundColors
43 | }
44 | }
45 |
46 | @objc func swizzled_setBackgroundColors(_ backgroundColors: [NSColor]) {
47 | cache(
48 | firstParam: Callable.Collection(backgroundColors),
49 | identifier: .backgroundColors,
50 | action: { [weak self] va in self?.__setBackgroundColors(va) }
51 | )
52 | }
53 | }
54 |
55 | #endif
56 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSDatePicker+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let textColor = "NSDatePicker.__setTextColor(_:)"
14 | static let backgroundColor = "NSDatePicker.__setBackgroundColor(_:)"
15 | }
16 |
17 | public extension NSDatePicker {
18 | var app_textColor: NSColor {
19 | get { textColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | textColor = newValue
23 | } else {
24 | swizzled_setTextColor(newValue)
25 | }
26 | }
27 | }
28 |
29 | var app_backgroundColor: NSColor {
30 | get { backgroundColor }
31 | set {
32 | if __USING_APPEARANCED_SWIZZLING__ {
33 | backgroundColor = newValue
34 | } else {
35 | swizzled_setBackgroundColor(newValue)
36 | }
37 | }
38 | }
39 | }
40 |
41 | internal extension NSDatePicker {
42 | static func silenceExchangeDatePickerViewImplementation() {
43 | app_swizzling(
44 | originalSelector: #selector(setter: textColor),
45 | newSelector: #selector(swizzled_setTextColor(_:))
46 | )
47 |
48 | app_swizzling(
49 | originalSelector: #selector(setter: backgroundColor),
50 | newSelector: #selector(swizzled_setBackgroundColor(_:))
51 | )
52 | }
53 | }
54 |
55 | private extension NSDatePicker {
56 | func __setTextColor(_ textColor: NSColor) {
57 | if __USING_APPEARANCED_SWIZZLING__ {
58 | swizzled_setTextColor(textColor)
59 | } else {
60 | self.textColor = textColor
61 | }
62 | }
63 |
64 | func __setBackgroundColor(_ backgroundColor: NSColor) {
65 | if __USING_APPEARANCED_SWIZZLING__ {
66 | swizzled_setBackgroundColor(backgroundColor)
67 | } else {
68 | self.backgroundColor = backgroundColor
69 | }
70 | }
71 |
72 | @objc func swizzled_setTextColor(_ textColor: NSColor) {
73 | cache(
74 | firstParam: Callable.Appearanced(textColor),
75 | identifier: .textColor,
76 | action: { [weak self] va in self?.__setTextColor(va) }
77 | )
78 | }
79 |
80 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
81 | cache(
82 | firstParam: Callable.Appearanced(backgroundColor),
83 | identifier: .backgroundColor,
84 | action: { [weak self] va in self?.__setBackgroundColor(va) }
85 | )
86 | }
87 | }
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSImageView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let image = "NSImageView.__setImage(_:)"
14 | }
15 |
16 | public extension NSImageView {
17 | var app_image: NSImage? {
18 | get { image }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | image = newValue
22 | } else {
23 | swizzled_setImage(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSImageView {
30 | static func silenceExchangeImageViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: image),
33 | newSelector: #selector(swizzled_setImage(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSImageView {
39 | func __setImage(_ image: NSImage?) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setImage(image)
42 | } else {
43 | self.image = image
44 | }
45 | }
46 |
47 |
48 | @objc func swizzled_setImage(_ image: NSImage?) {
49 | cache(
50 | firstParam: Callable.Appearanced(image),
51 | identifier: .image,
52 | action: { [weak self] va in self?.__setImage(va) }
53 | )
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSPathControl+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 |
9 | #if os(macOS)
10 |
11 | import Cocoa
12 |
13 | private extension AppearanceCallableIdentifier {
14 | static let placeholderAttributedString = "NSPathControl.__setPlaceholderAttributedString(_:)"
15 | static let backgroundColor = "NSPathControl.__setBackgroundColor(_:)"
16 | }
17 |
18 | public extension NSPathControl {
19 | var app_placeholderAttributedString: NSAttributedString? {
20 | get { placeholderAttributedString }
21 | set {
22 | if __USING_APPEARANCED_SWIZZLING__ {
23 | placeholderAttributedString = newValue
24 | } else {
25 | swizzled_setPlaceholderAttributedString(newValue)
26 | }
27 | }
28 | }
29 |
30 | var app_backgroundColor: NSColor? {
31 | get { backgroundColor }
32 | set {
33 | if __USING_APPEARANCED_SWIZZLING__ {
34 | backgroundColor = newValue
35 | } else {
36 | swizzled_setBackgroundColor(newValue)
37 | }
38 | }
39 | }
40 | }
41 |
42 | internal extension NSPathControl {
43 | static func silenceExchangePathControlImplementation() {
44 | app_swizzling(
45 | originalSelector: #selector(setter: placeholderAttributedString),
46 | newSelector: #selector(swizzled_setPlaceholderAttributedString(_:))
47 | )
48 |
49 | app_swizzling(
50 | originalSelector: #selector(setter: backgroundColor),
51 | newSelector: #selector(swizzled_setBackgroundColor(_:))
52 | )
53 | }
54 | }
55 |
56 | private extension NSPathControl {
57 | func __setBackgroundColor(_ backgroundColor: NSColor?) {
58 | if __USING_APPEARANCED_SWIZZLING__ {
59 | swizzled_setBackgroundColor(backgroundColor)
60 | } else {
61 | self.backgroundColor = backgroundColor
62 | }
63 | }
64 |
65 | func __setPlaceholderAttributedString(_ placeholderAttributedString: NSAttributedString?) {
66 | if __USING_APPEARANCED_SWIZZLING__ {
67 | swizzled_setPlaceholderAttributedString(placeholderAttributedString)
68 | } else {
69 | self.placeholderAttributedString = placeholderAttributedString
70 | }
71 | }
72 |
73 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor?) {
74 | cache(
75 | firstParam: Callable.Appearanced(backgroundColor),
76 | identifier: .backgroundColor,
77 | action: { [weak self] va in self?.__setBackgroundColor(va) }
78 | )
79 | }
80 |
81 | @objc func swizzled_setPlaceholderAttributedString(_ placeholderAttributedString: NSAttributedString?) {
82 | cache(
83 | firstParam: Callable.Attributed(placeholderAttributedString),
84 | identifier: .placeholderAttributedString,
85 | action: { [weak self] va in self?.__setPlaceholderAttributedString(va) }
86 | )
87 | }
88 | }
89 |
90 | #endif
91 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSPathControlItem+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 |
9 | #if os(macOS)
10 |
11 | import Cocoa
12 |
13 | private extension AppearanceCallableIdentifier {
14 | static let attributedTitle = "NSPathControlItem.__setAttributedTitle(_:)"
15 | static let image = "NSPathControlItem.__setImage(_:)"
16 | }
17 |
18 | public extension NSPathControlItem {
19 | var app_attributedTitle: NSAttributedString {
20 | get { attributedTitle }
21 | set {
22 | if __USING_APPEARANCED_SWIZZLING__ {
23 | attributedTitle = newValue
24 | } else {
25 | swizzled_setAttributedTitle(newValue)
26 | }
27 | }
28 | }
29 |
30 | var app_image: NSImage? {
31 | get { image }
32 | set {
33 | if __USING_APPEARANCED_SWIZZLING__ {
34 | image = newValue
35 | } else {
36 | swizzled_setImage(newValue)
37 | }
38 | }
39 | }
40 | }
41 |
42 | internal extension NSPathControlItem {
43 | static func silenceExchangePathControlItemImplementation() {
44 | app_swizzling(
45 | originalSelector: #selector(setter: attributedTitle),
46 | newSelector: #selector(swizzled_setAttributedTitle(_:))
47 | )
48 |
49 | app_swizzling(
50 | originalSelector: #selector(setter: image),
51 | newSelector: #selector(swizzled_setImage(_:))
52 | )
53 | }
54 | }
55 |
56 | private extension NSPathControlItem {
57 | func __setAttributedTitle(_ attributedTitle: NSAttributedString) {
58 | if __USING_APPEARANCED_SWIZZLING__ {
59 | swizzled_setAttributedTitle(attributedTitle)
60 | } else {
61 | self.attributedTitle = attributedTitle
62 | }
63 | }
64 |
65 | func __setImage(_ image: NSImage?) {
66 | if __USING_APPEARANCED_SWIZZLING__ {
67 | swizzled_setImage(image)
68 | } else {
69 | self.image = image
70 | }
71 | }
72 |
73 | @objc func swizzled_setAttributedTitle(_ attributedTitle: NSAttributedString) {
74 | cache(
75 | firstParam: Callable.Attributed(attributedTitle),
76 | identifier: .attributedTitle,
77 | action: { [weak self] va in self?.__setAttributedTitle(va) }
78 | )
79 | }
80 |
81 | @objc func swizzled_setImage(_ image: NSImage?) {
82 | cache(
83 | firstParam: Callable.Appearanced(image),
84 | identifier: .image,
85 | action: { [weak self] va in self?.__setImage(va) }
86 | )
87 | }
88 | }
89 |
90 | #endif
91 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSScrollView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "NSScrollView.__setBackgroundColor(_:)"
14 | }
15 |
16 | public extension NSScrollView {
17 | var app_backgroundColor: NSColor {
18 | get { backgroundColor }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | backgroundColor = newValue
22 | } else {
23 | swizzled_setBackgroundColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSScrollView {
30 | static func silenceExchangeScrollViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: backgroundColor),
33 | newSelector: #selector(swizzled_setBackgroundColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSScrollView {
39 | func __setBackgroundColor(_ backgroundColor: NSColor) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setBackgroundColor(backgroundColor)
42 | } else {
43 | self.backgroundColor = backgroundColor
44 | }
45 | }
46 |
47 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
48 | cache(
49 | firstParam: Callable.Appearanced(backgroundColor),
50 | identifier: .backgroundColor,
51 | action: { [weak self] va in self?.__setBackgroundColor(va) }
52 | )
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSSegmentedControl+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let selectedSegmentBezelColor = "NSSegmentedControl.__setSelectedSegmentBezelColor(_:)"
14 | static let setImageForSegment = "NSSegmentedControl.__setImage(_:forSegment:)"
15 | }
16 |
17 | public extension NSSegmentedControl {
18 | var app_selectedSegmentBezelColor: NSColor? {
19 | get { selectedSegmentBezelColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | self.selectedSegmentBezelColor = newValue
23 | } else {
24 | swizzled_setSelectedSegmentBezelColor(newValue)
25 | }
26 | }
27 | }
28 |
29 | func app_setImage(_ image: NSImage?, forSegment segment: Int) {
30 | if __USING_APPEARANCED_SWIZZLING__ {
31 | setImage(image, forSegment: segment)
32 | } else {
33 | swizzled_setImage(image, forSegment: segment)
34 | }
35 | }
36 | }
37 |
38 | internal extension NSSegmentedControl {
39 | static func silenceExchangeSegmentedControlImplementation() {
40 | app_swizzling(
41 | originalSelector: #selector(setter: selectedSegmentBezelColor),
42 | newSelector: #selector(swizzled_setSelectedSegmentBezelColor(_:))
43 | )
44 |
45 | app_swizzling(
46 | originalSelector: #selector(setImage(_:forSegment:)),
47 | newSelector: #selector(swizzled_setImage(_:forSegment:))
48 | )
49 | }
50 | }
51 |
52 | private extension NSSegmentedControl {
53 | func __setSelectedSegmentBezelColor(_ selectedSegmentBezelColor: NSColor?) {
54 | if __USING_APPEARANCED_SWIZZLING__ {
55 | swizzled_setSelectedSegmentBezelColor(selectedSegmentBezelColor)
56 | } else {
57 | self.selectedSegmentBezelColor = selectedSegmentBezelColor
58 | }
59 | }
60 |
61 | func __setImage(_ image: NSImage?, forSegment segment: Int) {
62 | if __USING_APPEARANCED_SWIZZLING__ {
63 | swizzled_setImage(image, forSegment: segment)
64 | } else {
65 | setImage(image, forSegment: segment)
66 | }
67 | }
68 |
69 | @objc func swizzled_setSelectedSegmentBezelColor(_ selectedSegmentBezelColor: NSColor?) {
70 | cache(
71 | firstParam: Callable.Appearanced(selectedSegmentBezelColor),
72 | identifier: .selectedSegmentBezelColor,
73 | action: { [weak self] va in self?.__setSelectedSegmentBezelColor(va) }
74 | )
75 | }
76 |
77 | @objc func swizzled_setImage(_ image: NSImage?, forSegment segment: Int) {
78 | cache(
79 | firstParam: Callable.Appearanced(image),
80 | secondParam: Callable.Original(segment),
81 | identifier: .setImageForSegment,
82 | action: { [weak self] va, vb in self?.__setImage(va, forSegment: vb) }
83 | )
84 | }
85 | }
86 |
87 | #endif
88 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSSlider+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let trackFillColor = "NSSlider."
14 | }
15 |
16 | public extension NSSlider {
17 | var app_trackFillColor: NSColor? {
18 | get { trackFillColor }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | trackFillColor = newValue
22 | } else {
23 | swizzled_setTrackFillColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSSlider {
30 | static func silenceExchangeSliderImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: trackFillColor),
33 | newSelector: #selector(swizzled_setTrackFillColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSSlider {
39 | func __setTrackFillColor(_ trackFillColor: NSColor?) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setTrackFillColor(trackFillColor)
42 | } else {
43 | self.trackFillColor = trackFillColor
44 | }
45 | }
46 |
47 |
48 | @objc func swizzled_setTrackFillColor(_ trackFillColor: NSColor?) {
49 | cache(
50 | firstParam: Callable.Appearanced(trackFillColor),
51 | identifier: .trackFillColor,
52 | action: { [weak self] va in self?.__setTrackFillColor(va) }
53 | )
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSTableRowView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "NSTableRowView.__setBackgroundColor(_:)"
14 | }
15 |
16 | public extension NSTableRowView {
17 | var app_backgroundColor: NSColor {
18 | get { backgroundColor }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | backgroundColor = newValue
22 | } else {
23 | swizzled_setBackgroundColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSTableRowView {
30 | static func silenceExchangeTableRowViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: backgroundColor),
33 | newSelector: #selector(swizzled_setBackgroundColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSTableRowView {
39 | func __setBackgroundColor(_ backgroundColor: NSColor) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setBackgroundColor(backgroundColor)
42 | } else {
43 | self.backgroundColor = backgroundColor
44 | }
45 | }
46 |
47 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
48 | cache(
49 | firstParam: Callable.Appearanced(backgroundColor),
50 | identifier: .backgroundColor,
51 | action: { [weak self] va in self?.__setBackgroundColor(va) }
52 | )
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSTableView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "NSTableView.__setBackgroundColor(_:)"
14 | static let gridColor = "NSTableView.__setGridColor(_:)"
15 | static let setIndicatorImageInTableColumn = "NSTableView.__setIndicatorImage(_:in:)"
16 | }
17 |
18 | public extension NSTableView {
19 | var app_backgroundColor: NSColor {
20 | get { backgroundColor }
21 | set {
22 | if __USING_APPEARANCED_SWIZZLING__ {
23 | backgroundColor = newValue
24 | } else {
25 | swizzled_setBackgroundColor(newValue)
26 | }
27 | }
28 | }
29 |
30 | var app_gridColor: NSColor {
31 | get { gridColor }
32 | set {
33 | if __USING_APPEARANCED_SWIZZLING__ {
34 | gridColor = newValue
35 | } else {
36 | swizzled_setGridColor(newValue)
37 | }
38 | }
39 | }
40 |
41 | func app_setIndicatorImage(_ image: NSImage, in tableColumn: NSTableColumn) {
42 | if __USING_APPEARANCED_SWIZZLING__ {
43 | setIndicatorImage(image, in: tableColumn)
44 | } else {
45 | swizzled_setIndicatorImage(image, in: tableColumn)
46 | }
47 | }
48 | }
49 |
50 | internal extension NSTableView {
51 | static func silenceExchangeTableViewImplementation() {
52 | app_swizzling(
53 | originalSelector: #selector(setter: backgroundColor),
54 | newSelector: #selector(swizzled_setBackgroundColor(_:))
55 | )
56 |
57 | app_swizzling(
58 | originalSelector: #selector(setter: gridColor),
59 | newSelector: #selector(swizzled_setGridColor(_:))
60 | )
61 |
62 | app_swizzling(
63 | originalSelector: #selector(setIndicatorImage(_:in:)),
64 | newSelector: #selector(swizzled_setIndicatorImage(_:in:))
65 | )
66 | }
67 | }
68 |
69 | private extension NSTableView {
70 | func __setBackgroundColor(_ backgroundColor: NSColor) {
71 | if __USING_APPEARANCED_SWIZZLING__ {
72 | swizzled_setBackgroundColor(backgroundColor)
73 | } else {
74 | self.backgroundColor = backgroundColor
75 | }
76 | }
77 |
78 | func __setGridColor(_ gridColor: NSColor) {
79 | if __USING_APPEARANCED_SWIZZLING__ {
80 | swizzled_setGridColor(gridColor)
81 | } else {
82 | self.gridColor = gridColor
83 | }
84 | }
85 |
86 | func __setIndicatorImage(_ image: NSImage, in tableColumn: NSTableColumn) {
87 | if __USING_APPEARANCED_SWIZZLING__ {
88 | swizzled_setIndicatorImage(image, in: tableColumn)
89 | } else {
90 | setIndicatorImage(image, in: tableColumn)
91 | }
92 | }
93 |
94 |
95 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
96 | cache(
97 | firstParam: Callable.Appearanced(backgroundColor),
98 | identifier: .backgroundColor,
99 | action: { [weak self] va in self?.__setBackgroundColor(va) }
100 | )
101 | }
102 |
103 | @objc func swizzled_setGridColor(_ gridColor: NSColor) {
104 | cache(
105 | firstParam: Callable.Appearanced(gridColor),
106 | identifier: .gridColor,
107 | action: { [weak self] va in self?.__setGridColor(va) }
108 | )
109 | }
110 |
111 | @objc func swizzled_setIndicatorImage(_ image: NSImage, in tableColumn: NSTableColumn) {
112 | cache(
113 | firstParam: Callable.Appearanced(image),
114 | secondParam: Callable.Original(tableColumn),
115 | identifier: .setIndicatorImageInTableColumn,
116 | action: { [weak self] va, vb in self?.__setIndicatorImage(va, in: vb) }
117 | )
118 | }
119 | }
120 |
121 | #endif
122 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSText+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/2.
6 | //
7 |
8 | #if os(macOS)
9 | import Cocoa
10 |
11 | private extension AppearanceCallableIdentifier {
12 | static let textColor = "NSText.__text_setTextColor(_:)"
13 | static let backgroundColor = "NSText.__text_setBackgroundColor(_:)"
14 | static let textColorRange = "NSText.__setTextColor(_:range:)"
15 | }
16 |
17 | public extension NSText {
18 | var app_text_textColor: NSColor? {
19 | get { textColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | textColor = newValue
23 | } else {
24 | swizzled_text_setTextColor(newValue)
25 | }
26 | }
27 | }
28 |
29 | var app_text_backgroundColor: NSColor? {
30 | get { backgroundColor }
31 | set {
32 | if __USING_APPEARANCED_SWIZZLING__ {
33 | backgroundColor = newValue
34 | } else {
35 | swizzled_text_setBackgroundColor(newValue)
36 | }
37 | }
38 | }
39 |
40 | func app_setTextColor(_ color: NSColor?, range: NSRange) {
41 | if __USING_APPEARANCED_SWIZZLING__ {
42 | setTextColor(color, range: range)
43 | } else {
44 | swizzled_setTextColor(color, range: range)
45 | }
46 | }
47 |
48 | }
49 |
50 | internal extension NSText {
51 | static func silenceExchangeTextImplementation() {
52 | app_swizzling(
53 | originalSelector: #selector(setter: textColor),
54 | newSelector: #selector(swizzled_text_setTextColor(_:))
55 | )
56 |
57 | app_swizzling(
58 | originalSelector: #selector(setter: backgroundColor),
59 | newSelector: #selector(swizzled_text_setBackgroundColor(_:))
60 | )
61 |
62 | app_swizzling(
63 | originalSelector: #selector(setTextColor(_:range:)),
64 | newSelector: #selector(swizzled_setTextColor(_:range:))
65 | )
66 | }
67 | }
68 |
69 | private extension NSText {
70 |
71 | func __text_setTextColor(_ textColor: NSColor?) {
72 | if __USING_APPEARANCED_SWIZZLING__ {
73 | swizzled_text_setTextColor(textColor)
74 | } else {
75 | self.textColor = textColor
76 | }
77 | }
78 |
79 | func __text_setBackgroundColor(_ backgroundColor: NSColor?) {
80 | if __USING_APPEARANCED_SWIZZLING__ {
81 | swizzled_text_setBackgroundColor(backgroundColor)
82 | } else {
83 | self.backgroundColor = backgroundColor
84 | }
85 | }
86 |
87 | func __setTextColor(_ color: NSColor?, range: NSRange) {
88 | if __USING_APPEARANCED_SWIZZLING__ {
89 | swizzled_setTextColor(color, range: range)
90 | } else {
91 | setTextColor(color, range: range)
92 | }
93 | }
94 |
95 |
96 | @objc func swizzled_text_setTextColor(_ textColor: NSColor?) {
97 | cache(
98 | firstParam: Callable.Appearanced(textColor),
99 | identifier: .textColor,
100 | action: { [weak self] va in self?.__text_setTextColor(va) }
101 | )
102 | }
103 |
104 | @objc func swizzled_text_setBackgroundColor(_ backgroundColor: NSColor?) {
105 | cache(
106 | firstParam: Callable.Appearanced(backgroundColor),
107 | identifier: .backgroundColor,
108 | action: { [weak self] va in self?.__text_setBackgroundColor(va) }
109 | )
110 | }
111 |
112 | @objc func swizzled_setTextColor(_ color: NSColor?, range: NSRange) {
113 | cache(
114 | firstParam: Callable.Appearanced(color),
115 | secondParam: Callable.Original(range),
116 | identifier: .textColorRange,
117 | action: { [weak self] va, vb in self?.__setTextColor(va, range: vb) }
118 | )
119 | }
120 | }
121 |
122 | #endif
123 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSTextField+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 |
9 | #if os(macOS)
10 |
11 | import Cocoa
12 |
13 | private extension AppearanceCallableIdentifier {
14 | static let backgroundColor = "NSTextField.__setBackGroundColor(_:)"
15 | static let textColor = "NSTextField.__setTextColor(_:)"
16 | static let attributedStringValue = "NSTextField.__setAttributedStringValue(_:)"
17 | static let placeholderAttributedString = "NSTextField.__setPlaceholderAttributedString(_:)"
18 | }
19 |
20 | public extension NSTextField {
21 | var app_backgroundColor: NSColor? {
22 | get { backgroundColor }
23 | set {
24 | if __USING_APPEARANCED_SWIZZLING__ {
25 | backgroundColor = newValue
26 | } else {
27 | swizzled_setBackGroundColor(newValue)
28 | }
29 | }
30 | }
31 |
32 | var app_textColor: NSColor? {
33 | get { textColor }
34 | set {
35 | if __USING_APPEARANCED_SWIZZLING__ {
36 | textColor = newValue
37 | } else {
38 | swizzled_setTextColor(newValue)
39 | }
40 | }
41 | }
42 |
43 | var app_attributedStringValue: NSAttributedString {
44 | get { attributedStringValue }
45 | set {
46 | if __USING_APPEARANCED_SWIZZLING__ {
47 | attributedStringValue = newValue
48 | } else {
49 | swizzled_setAttributedStringValue(newValue)
50 | }
51 | }
52 | }
53 |
54 | var app_placeholderAttributedString: NSAttributedString? {
55 | get { placeholderAttributedString }
56 | set {
57 | if __USING_APPEARANCED_SWIZZLING__ {
58 | placeholderAttributedString = newValue
59 | } else {
60 | swizzled_setPlaceholderAttributedString(newValue)
61 | }
62 | }
63 | }
64 | }
65 |
66 | internal extension NSTextField {
67 | static func silenceExchangeTextFieldImplementation() {
68 | app_swizzling(
69 | originalSelector: #selector(setter: backgroundColor),
70 | newSelector: #selector(swizzled_setBackGroundColor(_:))
71 | )
72 |
73 | app_swizzling(
74 | originalSelector: #selector(setter: textColor),
75 | newSelector: #selector(swizzled_setTextColor(_:))
76 | )
77 |
78 | app_swizzling(
79 | originalSelector: #selector(setter: attributedStringValue),
80 | newSelector: #selector(swizzled_setAttributedStringValue(_:))
81 | )
82 |
83 | app_swizzling(
84 | originalSelector: #selector(setter: placeholderAttributedString),
85 | newSelector: #selector(swizzled_setPlaceholderAttributedString(_:))
86 | )
87 | }
88 | }
89 |
90 | private extension NSTextField {
91 | func __setBackGroundColor(_ backgroundColor: NSColor?) {
92 | if __USING_APPEARANCED_SWIZZLING__ {
93 | swizzled_setBackGroundColor(backgroundColor)
94 | } else {
95 | self.backgroundColor = backgroundColor
96 | }
97 | }
98 |
99 | func __setTextColor(_ textColor: NSColor?) {
100 | if __USING_APPEARANCED_SWIZZLING__ {
101 | swizzled_setTextColor(textColor)
102 | } else {
103 | self.textColor = textColor
104 | }
105 | }
106 |
107 | func __setAttributedStringValue(_ attributedStringValue: NSAttributedString) {
108 | if __USING_APPEARANCED_SWIZZLING__ {
109 | swizzled_setAttributedStringValue(attributedStringValue)
110 | } else {
111 | self.attributedStringValue = attributedStringValue
112 | }
113 | }
114 |
115 | func __setPlaceholderAttributedString(_ placeholderAttributedString: NSAttributedString?) {
116 | if __USING_APPEARANCED_SWIZZLING__ {
117 | swizzled_setPlaceholderAttributedString(placeholderAttributedString)
118 | } else {
119 | self.placeholderAttributedString = placeholderAttributedString
120 | }
121 | }
122 |
123 | @objc func swizzled_setBackGroundColor(_ backgroundColor: NSColor?) {
124 | cache(
125 | firstParam: Callable.Appearanced(backgroundColor),
126 | identifier: .backgroundColor,
127 | action: { [weak self] va in self?.__setBackGroundColor(va) }
128 | )
129 | }
130 |
131 | @objc func swizzled_setTextColor(_ textColor: NSColor?) {
132 | cache(
133 | firstParam: Callable.Appearanced(textColor),
134 | identifier: .textColor,
135 | action: { [weak self] va in self?.__setTextColor(va) }
136 | )
137 | }
138 |
139 | @objc func swizzled_setAttributedStringValue(_ attributedStringValue: NSAttributedString) {
140 | cache(
141 | firstParam: Callable.Attributed(attributedStringValue),
142 | identifier: .attributedStringValue,
143 | action: { [weak self] va in self?.__setAttributedStringValue(va) }
144 | )
145 | }
146 |
147 | @objc func swizzled_setPlaceholderAttributedString(_ placeholderAttributedString: NSAttributedString?) {
148 | cache(
149 | firstParam: Callable.Attributed(placeholderAttributedString),
150 | identifier: .placeholderAttributedString,
151 | action: { [weak self] va in self?.__setPlaceholderAttributedString(va) }
152 | )
153 | }
154 | }
155 |
156 | #endif
157 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSTextView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 |
9 | #if os(macOS)
10 |
11 | import Cocoa
12 |
13 | private extension AppearanceCallableIdentifier {
14 | static let insertionPointColor = "NSTextView.__setInsertionPointColor(_:)"
15 | static let backgroundColor = "NSTextView.__setBackgroundColor(_:)"
16 | static let textColor = "NSTextView.__setTextColor(_:)"
17 | }
18 |
19 | public extension NSTextView {
20 | var app_insertionPointColor: NSColor {
21 | get { insertionPointColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | insertionPointColor = newValue
25 | } else {
26 | swizzled_setInsertionPointColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_backgroundColor: NSColor {
32 | get { backgroundColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | backgroundColor = newValue
36 | } else {
37 | swizzled_setBackgroundColor(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_textColor: NSColor? {
43 | get { textColor }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | textColor = newValue
47 | } else {
48 | swizzled_setTextColor(newValue)
49 | }
50 | }
51 | }
52 | }
53 |
54 | internal extension NSTextView {
55 | static func silenceExchangeTextViewImplementation() {
56 |
57 | app_swizzling(
58 | originalSelector: #selector(setter: insertionPointColor),
59 | newSelector: #selector(swizzled_setInsertionPointColor(_:))
60 | )
61 |
62 | app_swizzling(
63 | originalSelector: #selector(setter: backgroundColor),
64 | newSelector: #selector(swizzled_setBackgroundColor(_:))
65 | )
66 |
67 | app_swizzling(
68 | originalSelector: #selector(setter: textColor),
69 | newSelector: #selector(swizzled_setTextColor(_:))
70 | )
71 |
72 | }
73 | }
74 |
75 | private extension NSTextView {
76 | func __setInsertionPointColor(_ insertionPointColor: NSColor) {
77 | if __USING_APPEARANCED_SWIZZLING__ {
78 | swizzled_setInsertionPointColor(insertionPointColor)
79 | } else {
80 | self.insertionPointColor = insertionPointColor
81 | }
82 | }
83 |
84 | func __setBackgroundColor(_ backgroundColor: NSColor) {
85 | if __USING_APPEARANCED_SWIZZLING__ {
86 | swizzled_setBackgroundColor(backgroundColor)
87 | } else {
88 | self.backgroundColor = backgroundColor
89 | }
90 | }
91 |
92 | func __setTextColor(_ textColor: NSColor?) {
93 | if __USING_APPEARANCED_SWIZZLING__ {
94 | swizzled_setTextColor(textColor)
95 | } else {
96 | self.textColor = textColor
97 | }
98 | }
99 |
100 | @objc func swizzled_setInsertionPointColor(_ insertionPointColor: NSColor) {
101 | cache(
102 | firstParam: Callable.Appearanced(insertionPointColor),
103 | identifier: .insertionPointColor,
104 | action: { [weak self] va in self?.__setInsertionPointColor(va) }
105 | )
106 | }
107 |
108 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
109 | cache(
110 | firstParam: Callable.Appearanced(backgroundColor),
111 | identifier: .backgroundColor,
112 | action: { [weak self] va in self?.__setBackgroundColor(va) }
113 | )
114 | }
115 |
116 | @objc func swizzled_setTextColor(_ textColor: NSColor?) {
117 | cache(
118 | firstParam: Callable.Appearanced(textColor),
119 | identifier: .textColor,
120 | action: { [weak self] va in self?.__setTextColor(va) }
121 | )
122 | }
123 | }
124 |
125 | #endif
126 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let layerBackgroundColor = "NSView.__setLayerBackgroundColor(_:)"
14 | static let layerBorderColor = "NSView.__setLayerBorderColor(_:)"
15 | }
16 |
17 | public extension NSView {
18 | var app_layerBackgroundColor: NSColor? {
19 | set {
20 | cache(
21 | firstParam: Callable.Appearanced(newValue),
22 | identifier: .layerBackgroundColor,
23 | action: { [weak self] va in self?.__setLayerBackgroundColor(va) }
24 | )
25 | }
26 | get {
27 | guard let cgColor = layer?.backgroundColor else { return nil }
28 | return NSColor(cgColor: cgColor)
29 | }
30 | }
31 |
32 | var app_layerBorderColor: NSColor? {
33 | set {
34 | cache(
35 | firstParam: Callable.Appearanced(newValue),
36 | identifier: .layerBorderColor,
37 | action: { [weak self] va in self?.__setLayerBorderColor(va) }
38 | )
39 | }
40 | get {
41 | if let cgColor = layer?.borderColor {
42 | return NSColor(cgColor: cgColor)
43 | }
44 |
45 | return nil
46 | }
47 | }
48 | }
49 |
50 | private extension NSView {
51 | func __setLayerBackgroundColor(_ layerBackgroundColor: NSColor?) {
52 | if !self.wantsLayer {
53 | self.wantsLayer = true
54 | }
55 |
56 | self.layer?.backgroundColor = layerBackgroundColor?.cgColor
57 | }
58 |
59 | func __setLayerBorderColor(_ layerBorderColor: NSColor?) {
60 | if !self.wantsLayer {
61 | self.wantsLayer = true
62 | }
63 |
64 | self.layer?.borderColor = layerBorderColor?.cgColor
65 | }
66 | }
67 |
68 | #endif
69 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/APPKit/NSWindow+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if os(macOS)
9 |
10 | import Cocoa
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "NSWindow.__setBackgroundColor(_:)"
14 | }
15 |
16 | public extension NSWindow {
17 | var app_backgroundColor: NSColor {
18 | get { backgroundColor }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | backgroundColor = newValue
22 | } else {
23 | swizzled_setBackgroundColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension NSWindow {
30 | static func silenceExchangeWindowImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: backgroundColor),
33 | newSelector: #selector(swizzled_setBackgroundColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension NSWindow {
39 | func __setBackgroundColor(_ backgroundColor: NSColor) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setBackgroundColor(backgroundColor)
42 | } else {
43 | self.backgroundColor = backgroundColor
44 | }
45 | }
46 |
47 | @objc func swizzled_setBackgroundColor(_ backgroundColor: NSColor) {
48 | cache(
49 | firstParam: Callable.Appearanced(backgroundColor),
50 | identifier: .backgroundColor,
51 | action: { [weak self] va in self?.__setBackgroundColor(va) }
52 | )
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/Implementation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | #if os(macOS)
9 | import Cocoa
10 | #elseif canImport(UIKit)
11 | import UIKit
12 | #endif
13 |
14 | /// 内部调用
15 | internal extension AppearanceManager {
16 | static func exchangeImplementations() {
17 | #if os(macOS)
18 | NSBox.silenceExchangeBoxImplementation()
19 | NSButton.silenceExchangeButtonImplementation()
20 | NSDatePicker.silenceExchangeDatePickerViewImplementation()
21 | NSImageView.silenceExchangeImageViewImplementation()
22 | NSScrollView.silenceExchangeScrollViewImplementation()
23 | NSSlider.silenceExchangeSliderImplementation()
24 | NSTableView.silenceExchangeTableViewImplementation()
25 | NSText.silenceExchangeTextImplementation()
26 | NSTextField.silenceExchangeTextFieldImplementation()
27 | NSTextView.silenceExchangeTextViewImplementation()
28 | /// NSView
29 | NSWindow.silenceExchangeWindowImplementation()
30 | NSClipView.silenceExchangeClipViewImplementation()
31 | NSPathControl.silenceExchangePathControlImplementation()
32 | NSPathControlItem.silenceExchangePathControlItemImplementation()
33 | NSTableRowView.silenceExchangeTableRowViewImplementation()
34 | NSSegmentedControl.silenceExchangeSegmentedControlImplementation()
35 | NSLevelIndicator.silenceExchangeLevelIndicatorImplementation()
36 | NSCollectionView.silenceExchangeCollectionViewImplementation()
37 |
38 | if #available(macOS 10.15, *) {
39 | NSButtonTouchBarItem.silenceExchangeButtonTouchBarItemImplementation()
40 | }
41 | #elseif canImport(UIKit)
42 | UIButton.silenceExchangeButtonImplementation()
43 | UILabel.silenceExchangeLabelImplementation()
44 | UIView.silenceExchangeViewImplementation()
45 | UIActivityIndicatorView.silenceExchangeActivityIndicatorViewImplementation()
46 | UIBarItem.silenceExchangeBarItemImplementation()
47 | UIImageView.silenceExchangeImageViewImplementation()
48 | UINavigationBar.silenceExchangeNavigationBarImplementation()
49 | UIPageControl.silenceExchangePageControlImplementation()
50 | UIProgressView.silenceExchangeProgressViewImplementation()
51 | UIRefreshControl.silenceExchangeRefreshControlImplementation()
52 | UISearchBar.silenceExchangeSearchBarImplementation()
53 | UISegmentedControl.silenceExchangeSegmentedControlImplementation()
54 | UISlider.silenceExchangeSliderImplementation()
55 | UIStepper.silenceExchangeStepperImplementation()
56 | UISwitch.silenceExchangeSwitchImplementation()
57 | UITabBar.silenceExchangeTabBarImplementation()
58 | UITableView.silenceExchangeTableViewImplementation()
59 | UITextField.silenceExchangeTextFieldImplementation()
60 | UITextView.silenceExchangeTextViewImplementation()
61 | UIToolbar.silenceExchangeToolbarImplementation()
62 |
63 | if #available(iOS 13.0, *) {
64 | UISearchTextField.silenceExchangeSearchTextFieldImplementation()
65 | }
66 | #endif
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIActivityIndicatorView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let color = "UIActivityIndicatorView.__setColor(_:)"
14 | }
15 |
16 | public extension UIActivityIndicatorView {
17 | var app_color: UIColor {
18 | get { color }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | color = newValue
22 | } else {
23 | swizzled_setColor(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension UIActivityIndicatorView {
30 | static func silenceExchangeActivityIndicatorViewImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: color),
33 | newSelector: #selector(swizzled_setColor(_:))
34 | )
35 | }
36 | }
37 |
38 | private extension UIActivityIndicatorView {
39 | func __setColor(_ color: UIColor) {
40 | if __USING_APPEARANCED_SWIZZLING__ {
41 | swizzled_setColor(color)
42 | } else {
43 | self.color = color
44 | }
45 | }
46 |
47 | @objc func swizzled_setColor(_ color: UIColor) {
48 | cache(
49 | firstParam: Callable.Appearanced(color),
50 | identifier: .color,
51 | action: { [weak self] va in self?.__setColor(va) }
52 | )
53 | }
54 | }
55 |
56 | #endif
57 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIBarItem+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/10.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let image = "UIBarItem.__setImage(_:)"
14 | static let landscapeImagePhone = "UIBarItem.__setLandscapeImagePhone(_:)"
15 | static let largeContentSizeImage = "UIBarItem.__setLargeContentSizeImage(_:)"
16 | }
17 |
18 | public extension UIBarItem {
19 | var app_image: UIImage? {
20 | get { image }
21 | set {
22 | if __USING_APPEARANCED_SWIZZLING__ {
23 | self.image = newValue
24 | } else {
25 | swizzled_setImage(newValue)
26 | }
27 | }
28 | }
29 |
30 | var app_landscapeImagePhone: UIImage? {
31 | get { landscapeImagePhone }
32 | set {
33 | if __USING_APPEARANCED_SWIZZLING__ {
34 | self.landscapeImagePhone = newValue
35 | } else {
36 | swizzled_setLandscapeImagePhone(newValue)
37 | }
38 | }
39 | }
40 |
41 | var app_largeContentSizeImage: UIImage? {
42 | get { largeContentSizeImage }
43 | set {
44 | if __USING_APPEARANCED_SWIZZLING__ {
45 | self.largeContentSizeImage = newValue
46 | } else {
47 | swizzled_setLargeContentSizeImage(newValue)
48 | }
49 | }
50 | }
51 | }
52 |
53 | internal extension UIBarItem {
54 | static func silenceExchangeBarItemImplementation() {
55 | app_swizzling(
56 | originalSelector: #selector(setter: image),
57 | newSelector: #selector(swizzled_setImage(_:))
58 | )
59 |
60 | app_swizzling(
61 | originalSelector: #selector(setter: landscapeImagePhone),
62 | newSelector: #selector(swizzled_setLandscapeImagePhone(_:))
63 | )
64 |
65 | app_swizzling(
66 | originalSelector: #selector(setter: largeContentSizeImage),
67 | newSelector: #selector(swizzled_setLargeContentSizeImage(_:))
68 | )
69 | }
70 | }
71 |
72 | private extension UIBarItem {
73 | func __setImage(_ image: UIImage?) {
74 | if __USING_APPEARANCED_SWIZZLING__ {
75 | swizzled_setImage(image)
76 | } else {
77 | self.image = image
78 | }
79 | }
80 |
81 | func __setLandscapeImagePhone(_ landscapeImagePhone: UIImage?) {
82 | if __USING_APPEARANCED_SWIZZLING__ {
83 | swizzled_setLandscapeImagePhone(landscapeImagePhone)
84 | } else {
85 | self.landscapeImagePhone = landscapeImagePhone
86 | }
87 | }
88 |
89 | func __setLargeContentSizeImage(_ largeContentSizeImage: UIImage?) {
90 | if __USING_APPEARANCED_SWIZZLING__ {
91 | swizzled_setLargeContentSizeImage(largeContentSizeImage)
92 | } else {
93 | self.largeContentSizeImage = largeContentSizeImage
94 | }
95 | }
96 |
97 | @objc func swizzled_setImage(_ image: UIImage?) {
98 | cache(
99 | firstParam: Callable.Appearanced(image),
100 | identifier: .image,
101 | action: { [weak self] va in self?.__setImage(va) }
102 | )
103 | }
104 |
105 | @objc func swizzled_setLandscapeImagePhone(_ landscapeImagePhone: UIImage?) {
106 | cache(
107 | firstParam: Callable.Appearanced(landscapeImagePhone),
108 | identifier: .landscapeImagePhone,
109 | action: { [weak self] va in self?.__setLandscapeImagePhone(va) }
110 | )
111 | }
112 |
113 | @objc func swizzled_setLargeContentSizeImage(_ largeContentSizeImage: UIImage?) {
114 | cache(
115 | firstParam: Callable.Appearanced(largeContentSizeImage),
116 | identifier: .largeContentSizeImage,
117 | action: { [weak self] va in self?.__setLargeContentSizeImage(va) }
118 | )
119 | }
120 | }
121 |
122 | #endif
123 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIButton+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if canImport(UIKit)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let setTitleColorForState = "UIButton.__setTitleColor(_:for:)"
14 | static let setImageForState = "UIButton.__setImage(_:for:)"
15 | static let setBackgroundImageForState = "UIButton.__setBackgroundImage(_:for:)"
16 | static let setAttributedTitleForState = "UIButton.__setAttributedTitle(_:for:)"
17 | }
18 |
19 | public extension UIButton {
20 | func app_setTitleColor(_ titleColor: UIColor?, for state: State) {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | setTitleColor(titleColor, for: state)
23 | } else {
24 | swizzled_setTitleColor(titleColor, for: state)
25 | }
26 | }
27 |
28 | func app_setImage(_ image: UIImage?, for state: State) {
29 | if __USING_APPEARANCED_SWIZZLING__ {
30 | setImage(image, for: state)
31 | } else {
32 | swizzled_setImage(image, for: state)
33 | }
34 | }
35 |
36 | func app_setBackgroundImage(_ backgroundImage: UIImage?, for state: State) {
37 | if __USING_APPEARANCED_SWIZZLING__ {
38 | setBackgroundImage(backgroundImage, for: state)
39 | }
40 | }
41 |
42 | func app_setAttributedTitle(_ attributedTitle: NSAttributedString?, for state: State) {
43 | if __USING_APPEARANCED_SWIZZLING__ {
44 | setAttributedTitle(attributedTitle, for: state)
45 | } else {
46 | swizzled_setAttributedTitle(attributedTitle, for: state)
47 | }
48 | }
49 | }
50 |
51 | internal extension UIButton {
52 | static func silenceExchangeButtonImplementation() {
53 | app_swizzling(
54 | originalSelector: #selector(setTitleColor(_:for:)),
55 | newSelector: #selector(swizzled_setTitleColor(_:for:))
56 | )
57 |
58 | app_swizzling(
59 | originalSelector: #selector(setImage(_:for:)),
60 | newSelector: #selector(swizzled_setImage(_:for:))
61 | )
62 |
63 | app_swizzling(
64 | originalSelector: #selector(setBackgroundImage(_:for:)),
65 | newSelector: #selector(swizzled_setBackgroundImage(_:for:))
66 | )
67 |
68 | app_swizzling(
69 | originalSelector: #selector(setAttributedTitle(_:for:)),
70 | newSelector: #selector(swizzled_setAttributedTitle(_:for:))
71 | )
72 | }
73 | }
74 |
75 | private extension UIButton {
76 | func __setTitleColor(_ titleColor: UIColor?, for state: State) {
77 | if __USING_APPEARANCED_SWIZZLING__ {
78 | swizzled_setTitleColor(titleColor, for: state)
79 | } else {
80 | setTitleColor(titleColor, for: state)
81 | }
82 | }
83 |
84 | func __setImage(_ image: UIImage?, for state: State) {
85 | if __USING_APPEARANCED_SWIZZLING__ {
86 | swizzled_setImage(image, for: state)
87 | } else {
88 | setImage(image, for: state)
89 | }
90 | }
91 |
92 | func __setBackgroundImage(_ backgroundImage: UIImage?, for state: State) {
93 | if __USING_APPEARANCED_SWIZZLING__ {
94 | swizzled_setBackgroundImage(backgroundImage, for: state)
95 | } else {
96 | setBackgroundImage(backgroundImage, for: state)
97 | }
98 | }
99 |
100 | func __setAttributedTitle(_ attributedTitle: NSAttributedString?, for state: State) {
101 | if __USING_APPEARANCED_SWIZZLING__ {
102 | swizzled_setAttributedTitle(attributedTitle, for: state)
103 | } else {
104 | setAttributedTitle(attributedTitle, for: state)
105 | }
106 | }
107 |
108 | @objc func swizzled_setTitleColor(_ titleColor: UIColor?, for state: State) {
109 | cache(
110 | firstParam: Callable.Appearanced(titleColor),
111 | secondParam: Callable.Original(state),
112 | identifier: .setTitleColorForState,
113 | action: { [weak self] va, vb in self?.__setTitleColor(va, for: vb) },
114 | category: "UIButton.state:\(state.rawValue)"
115 | )
116 | }
117 |
118 | @objc func swizzled_setImage(_ image: UIImage?, for state: State) {
119 | cache(
120 | firstParam: Callable.Appearanced(image),
121 | secondParam: Callable.Original(state),
122 | identifier: .setImageForState,
123 | action: { [weak self] va, vb in self?.__setImage(va, for: vb) },
124 | category: "UIButton.state:\(state.rawValue)"
125 | )
126 | }
127 |
128 | @objc func swizzled_setBackgroundImage(_ backgroundImage: UIImage?, for state: State) {
129 | cache(
130 | firstParam: Callable.Appearanced(backgroundImage),
131 | secondParam: Callable.Original(state),
132 | identifier: .setBackgroundImageForState,
133 | action: { [weak self] va, vb in self?.__setBackgroundImage(va, for: vb) },
134 | category: "UIButton.state:\(state.rawValue)"
135 | )
136 | }
137 |
138 | @objc func swizzled_setAttributedTitle(_ attributedTitle: NSAttributedString?, for state: State) {
139 | cache(
140 | firstParam: Callable.Attributed(attributedTitle),
141 | secondParam: Callable.Original(state),
142 | identifier: .setAttributedTitleForState,
143 | action: { [weak self] va, vb in self?.__setAttributedTitle(va, for: vb) },
144 | category: "UIButton.state:\(state.rawValue)"
145 | )
146 | }
147 | }
148 |
149 | #endif
150 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UILabel+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if canImport(UIKit)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let textColor = "UILabel.__setTextColor(_:)"
14 | static let shadowColor = "UILabel.__setShadowColor(_:)"
15 | static let attributedText = "UILabel.__setAttributedText(_:)"
16 | static let highlightedTextColor = "UILabel.__setHighlightedTextColor(_:)"
17 | }
18 |
19 | public extension UILabel {
20 | var app_textColor: UIColor {
21 | get { textColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | self.textColor = newValue
25 | } else {
26 | swizzled_setTextColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_shadowColor: UIColor? {
32 | get { shadowColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | self.shadowColor = newValue
36 | } else {
37 | swizzled_setShadowColor(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_attributedText: NSAttributedString? {
43 | get { attributedText }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | self.attributedText = newValue
47 | } else {
48 | swizzled_setAttributedText(newValue)
49 | }
50 | }
51 | }
52 |
53 | var app_highlightedTextColor: UIColor? {
54 | get { highlightedTextColor }
55 | set {
56 | if __USING_APPEARANCED_SWIZZLING__ {
57 | self.highlightedTextColor = newValue
58 | } else {
59 | swizzled_setHighlightedTextColor(newValue)
60 | }
61 | }
62 | }
63 | }
64 |
65 | internal extension UILabel {
66 | static func silenceExchangeLabelImplementation() {
67 | app_swizzling(
68 | originalSelector: #selector(setter: textColor),
69 | newSelector: #selector(swizzled_setTextColor(_:))
70 | )
71 |
72 | app_swizzling(
73 | originalSelector: #selector(setter: shadowColor),
74 | newSelector: #selector(swizzled_setShadowColor(_:))
75 | )
76 |
77 | app_swizzling(
78 | originalSelector: #selector(setter: attributedText),
79 | newSelector: #selector(swizzled_setAttributedText(_:))
80 | )
81 |
82 | app_swizzling(
83 | originalSelector: #selector(setter: highlightedTextColor),
84 | newSelector: #selector(swizzled_setHighlightedTextColor(_:))
85 | )
86 | }
87 | }
88 |
89 | private extension UILabel {
90 | func __setTextColor(_ textColor: UIColor) {
91 | if __USING_APPEARANCED_SWIZZLING__ {
92 | swizzled_setTextColor(textColor)
93 | } else {
94 | self.textColor = textColor
95 | }
96 | }
97 |
98 | func __setShadowColor(_ shadowColor: UIColor?) {
99 | if __USING_APPEARANCED_SWIZZLING__ {
100 | swizzled_setShadowColor(shadowColor)
101 | } else {
102 | self.shadowColor = shadowColor
103 | }
104 | }
105 |
106 | func __setAttributedText(_ attributedText: NSAttributedString?) {
107 | if __USING_APPEARANCED_SWIZZLING__ {
108 | swizzled_setAttributedText(attributedText)
109 | } else {
110 | self.attributedText = attributedText
111 | }
112 | }
113 |
114 | func __setHighlightedTextColor(_ highlightedTextColor: UIColor?) {
115 | if __USING_APPEARANCED_SWIZZLING__ {
116 | swizzled_setHighlightedTextColor(highlightedTextColor)
117 | } else {
118 | self.highlightedTextColor = highlightedTextColor
119 | }
120 | }
121 |
122 | @objc func swizzled_setTextColor(_ textColor: UIColor) {
123 | cache(
124 | firstParam: Callable.Appearanced(textColor),
125 | identifier: .textColor,
126 | action: { [weak self] va in self?.__setTextColor(va) }
127 | )
128 | }
129 |
130 | @objc func swizzled_setShadowColor(_ shadowColor: UIColor?) {
131 | cache(
132 | firstParam: Callable.Appearanced(shadowColor),
133 | identifier: .shadowColor,
134 | action: { [weak self] va in self?.__setShadowColor(va) }
135 | )
136 | }
137 |
138 | @objc func swizzled_setAttributedText(_ attributedText: NSAttributedString?) {
139 | cache(
140 | firstParam: Callable.Appearanced(attributedText),
141 | identifier: .attributedText,
142 | action: { [weak self] va in self?.__setAttributedText(va) }
143 | )
144 | }
145 |
146 | @objc func swizzled_setHighlightedTextColor(_ highlightedTextColor: UIColor?) {
147 | cache(
148 | firstParam: Callable.Appearanced(highlightedTextColor),
149 | identifier: .highlightedTextColor,
150 | action: { [weak self] va in self?.__setHighlightedTextColor(va) }
151 | )
152 | }
153 | }
154 |
155 | #endif
156 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIProgressView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let progressTintColor = "UIProgressView.__setProgressTintColor(_:)"
14 | static let trackTintColor = "UIProgressView.__setTrackTintColor(_:)"
15 | static let progressImage = "UIProgressView.__setProgressImage(_:)"
16 | static let trackImage = "UIProgressView.__setTrackImage(_:)"
17 | }
18 |
19 | public extension UIProgressView {
20 | var app_progressTintColor: UIColor? {
21 | get { progressTintColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | self.progressTintColor = newValue
25 | } else {
26 | swizzled_setProgressTintColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_trackTintColor: UIColor? {
32 | get { trackTintColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | self.trackTintColor = newValue
36 | } else {
37 | swizzled_setTrackTintColor(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_progressImage: UIImage? {
43 | get { progressImage }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | self.progressImage = newValue
47 | } else {
48 | swizzled_setProgressImage(newValue)
49 | }
50 | }
51 | }
52 |
53 | var app_trackImage: UIImage? {
54 | get { trackImage }
55 | set {
56 | if __USING_APPEARANCED_SWIZZLING__ {
57 | self.trackImage = newValue
58 | } else {
59 | swizzled_setTrackImage(newValue)
60 | }
61 | }
62 | }
63 | }
64 |
65 | internal extension UIProgressView {
66 | static func silenceExchangeProgressViewImplementation() {
67 | app_swizzling(
68 | originalSelector: #selector(setter: progressTintColor),
69 | newSelector: #selector(swizzled_setProgressTintColor(_:))
70 | )
71 |
72 | app_swizzling(
73 | originalSelector: #selector(setter: trackTintColor),
74 | newSelector: #selector(swizzled_setTrackTintColor(_:))
75 | )
76 |
77 | app_swizzling(
78 | originalSelector: #selector(setter: progressImage),
79 | newSelector: #selector(swizzled_setProgressImage(_:))
80 | )
81 |
82 | app_swizzling(
83 | originalSelector: #selector(setter: trackImage),
84 | newSelector: #selector(swizzled_setTrackImage(_:))
85 | )
86 | }
87 | }
88 |
89 | private extension UIProgressView {
90 | func __setProgressTintColor(_ progressTintColor: UIColor?) {
91 | if __USING_APPEARANCED_SWIZZLING__ {
92 | swizzled_setProgressTintColor(progressTintColor)
93 | } else {
94 | self.progressTintColor = progressTintColor
95 | }
96 | }
97 |
98 | func __setTrackTintColor(_ trackTintColor: UIColor?) {
99 | if __USING_APPEARANCED_SWIZZLING__ {
100 | swizzled_setTrackTintColor(trackTintColor)
101 | } else {
102 | self.trackTintColor = trackTintColor
103 | }
104 | }
105 |
106 | func __setProgressImage(_ progressImage: UIImage?) {
107 | if __USING_APPEARANCED_SWIZZLING__ {
108 | swizzled_setProgressImage(progressImage)
109 | } else {
110 | self.progressImage = progressImage
111 | }
112 | }
113 |
114 | func __setTrackImage(_ trackImage: UIImage?) {
115 | if __USING_APPEARANCED_SWIZZLING__ {
116 | swizzled_setTrackImage(trackImage)
117 | } else {
118 | self.trackImage = trackImage
119 | }
120 | }
121 |
122 |
123 | @objc func swizzled_setProgressTintColor(_ progressTintColor: UIColor?) {
124 | cache(
125 | firstParam: Callable.Appearanced(progressTintColor),
126 | identifier: .progressTintColor,
127 | action: { [weak self] va in self?.__setProgressTintColor(va) }
128 | )
129 | }
130 |
131 | @objc func swizzled_setTrackTintColor(_ trackTintColor: UIColor?) {
132 | cache(
133 | firstParam: Callable.Appearanced(trackTintColor),
134 | identifier: .trackTintColor,
135 | action: { [weak self] va in self?.__setTrackTintColor(va) }
136 | )
137 | }
138 |
139 | @objc func swizzled_setProgressImage(_ progressImage: UIImage?) {
140 | cache(
141 | firstParam: Callable.Appearanced(progressImage),
142 | identifier: .progressImage,
143 | action: { [weak self] va in self?.__setProgressImage(va) }
144 | )
145 | }
146 |
147 | @objc func swizzled_setTrackImage(_ trackImage: UIImage?) {
148 | cache(
149 | firstParam: Callable.Appearanced(trackImage),
150 | identifier: .trackImage,
151 | action: { [weak self] va in self?.__setTrackImage(va) }
152 | )
153 | }
154 | }
155 |
156 | #endif
157 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIRefreshControl+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let attributedTitle = "UIRefreshControl.__setAttributedTitle(_:)"
14 | }
15 |
16 | public extension UIRefreshControl {
17 | var app_attributedTitle: NSAttributedString? {
18 | get { attributedTitle }
19 | set {
20 | if __USING_APPEARANCED_SWIZZLING__ {
21 | self.attributedTitle = newValue
22 | } else {
23 | swizzled_setAttributedTitle(newValue)
24 | }
25 | }
26 | }
27 | }
28 |
29 | internal extension UIRefreshControl {
30 | static func silenceExchangeRefreshControlImplementation() {
31 | app_swizzling(
32 | originalSelector: #selector(setter: attributedTitle),
33 | newSelector: #selector(swizzled_setAttributedTitle(_:))
34 | )
35 | }
36 | }
37 |
38 |
39 | private extension UIRefreshControl {
40 | func __setAttributedTitle(_ attributedTitle: NSAttributedString?) {
41 | if __USING_APPEARANCED_SWIZZLING__ {
42 | swizzled_setAttributedTitle(attributedTitle)
43 | } else {
44 | self.attributedTitle = attributedTitle
45 | }
46 | }
47 |
48 | @objc func swizzled_setAttributedTitle(_ attributedTitle: NSAttributedString?) {
49 | cache(
50 | firstParam: Callable.Attributed(attributedTitle),
51 | identifier: .attributedTitle,
52 | action: { [weak self] va in self?.__setAttributedTitle(va) }
53 | )
54 | }
55 | }
56 |
57 | #endif
58 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UISearchTextField+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let tokenBackgroundColor = "UISearchTextField.__setTokenBackgroundColor(_:)"
14 | }
15 |
16 | @available(iOS 13.0, *)
17 | public extension UISearchTextField {
18 | var app_tokenBackgroundColor: UIColor {
19 | get { tokenBackgroundColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | self.tokenBackgroundColor = newValue
23 | } else {
24 | swizzled_setTokenBackgroundColor(newValue)
25 | }
26 | }
27 | }
28 | }
29 |
30 | @available(iOS 13.0, *)
31 | internal extension UISearchTextField {
32 | static func silenceExchangeSearchTextFieldImplementation() {
33 | app_swizzling(
34 | originalSelector: #selector(setter: tokenBackgroundColor),
35 | newSelector: #selector(swizzled_setTokenBackgroundColor(_:))
36 | )
37 | }
38 | }
39 |
40 | @available(iOS 13.0, *)
41 | private extension UISearchTextField {
42 | func __setTokenBackgroundColor(_ tokenBackgroundColor: UIColor) {
43 | if __USING_APPEARANCED_SWIZZLING__ {
44 | swizzled_setTokenBackgroundColor(tokenBackgroundColor)
45 | } else {
46 | self.tokenBackgroundColor = tokenBackgroundColor
47 | }
48 | }
49 |
50 | @objc func swizzled_setTokenBackgroundColor(_ tokenBackgroundColor: UIColor) {
51 | cache(
52 | firstParam: Callable.Appearanced(tokenBackgroundColor),
53 | identifier: .tokenBackgroundColor,
54 | action: { [weak self] va in self?.__setTokenBackgroundColor(va) }
55 | )
56 | }
57 | }
58 |
59 | #endif
60 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UISwitch+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let onTintColor = "UISwitch.__setOnTintColor(_:)"
14 | static let thumbTintColor = "UISwitch.__setThumbTintColor(_:)"
15 | static let onImage = "UISwitch.__setOnImage(_:)"
16 | static let offImage = "UISwitch.__setOffImage(_:)"
17 | }
18 |
19 | public extension UISwitch {
20 | var app_onTintColor: UIColor? {
21 | get { onTintColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | self.onTintColor = newValue
25 | } else {
26 | swizzled_setOnTintColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_thumbTintColor: UIColor? {
32 | get { thumbTintColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | self.thumbTintColor = newValue
36 | } else {
37 | swizzled_setThumbTintColor(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_onImage: UIImage? {
43 | get { onImage }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | self.onImage = newValue
47 | } else {
48 | swizzled_setOnImage(newValue)
49 | }
50 | }
51 | }
52 |
53 | var app_offImage: UIImage? {
54 | get { offImage }
55 | set {
56 | if __USING_APPEARANCED_SWIZZLING__ {
57 | self.offImage = newValue
58 | } else {
59 | swizzled_setOffImage(newValue)
60 | }
61 | }
62 | }
63 | }
64 |
65 | internal extension UISwitch {
66 | static func silenceExchangeSwitchImplementation() {
67 | app_swizzling(
68 | originalSelector: #selector(setter: onTintColor),
69 | newSelector: #selector(swizzled_setOnTintColor(_:))
70 | )
71 | app_swizzling(
72 | originalSelector: #selector(setter: thumbTintColor),
73 | newSelector: #selector(swizzled_setThumbTintColor(_:))
74 | )
75 | app_swizzling(
76 | originalSelector: #selector(setter: onImage),
77 | newSelector: #selector(swizzled_setOnImage(_:))
78 | )
79 | app_swizzling(
80 | originalSelector: #selector(setter: offImage),
81 | newSelector: #selector(swizzled_setOffImage(_:))
82 | )
83 | }
84 | }
85 |
86 | private extension UISwitch {
87 | func __setOnTintColor(_ onTintColor: UIColor?) {
88 | if __USING_APPEARANCED_SWIZZLING__ {
89 | swizzled_setOnTintColor(onTintColor)
90 | } else {
91 | self.onTintColor = onTintColor
92 | }
93 | }
94 |
95 | func __setThumbTintColor(_ thumbTintColor: UIColor?) {
96 | if __USING_APPEARANCED_SWIZZLING__ {
97 | swizzled_setThumbTintColor(thumbTintColor)
98 | } else {
99 | self.thumbTintColor = thumbTintColor
100 | }
101 | }
102 |
103 | func __setOnImage(_ onImage: UIImage?) {
104 | if __USING_APPEARANCED_SWIZZLING__ {
105 | swizzled_setOnImage(onImage)
106 | } else {
107 | self.onImage = onImage
108 | }
109 | }
110 |
111 | func __setOffImage(_ offImage: UIImage?) {
112 | if __USING_APPEARANCED_SWIZZLING__ {
113 | swizzled_setOffImage(offImage)
114 | } else {
115 | self.offImage = offImage
116 | }
117 | }
118 |
119 |
120 | @objc func swizzled_setOnTintColor(_ onTintColor: UIColor?) {
121 | cache(
122 | firstParam: Callable.Appearanced(onTintColor),
123 | identifier: .onTintColor,
124 | action: { [weak self] va in self?.__setOnTintColor(va) }
125 | )
126 | }
127 |
128 | @objc func swizzled_setThumbTintColor(_ thumbTintColor: UIColor?) {
129 | cache(
130 | firstParam: Callable.Appearanced(thumbTintColor),
131 | identifier: .thumbTintColor,
132 | action: { [weak self] va in self?.__setThumbTintColor(va) }
133 | )
134 | }
135 |
136 | @objc func swizzled_setOnImage(_ onImage: UIImage?) {
137 | cache(
138 | firstParam: Callable.Appearanced(onImage),
139 | identifier: .onImage,
140 | action: { [weak self] va in self?.__setOnImage(va) }
141 | )
142 | }
143 |
144 | @objc func swizzled_setOffImage(_ offImage: UIImage?) {
145 | cache(
146 | firstParam: Callable.Appearanced(offImage),
147 | identifier: .offImage,
148 | action: { [weak self] va in self?.__setOffImage(va) }
149 | )
150 | }
151 | }
152 |
153 | #endif
154 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UITableView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let sectionIndexColor = "UITableView.__setSectionIndexColor(_:)"
14 | static let sectionIndexBackgroundColor = "UITableView.__setSectionIndexBackgroundColor(_:)"
15 | static let sectionIndexTrackingBackgroundColor = "UITableView.__setSectionIndexTrackingBackgroundColor(_:)"
16 | static let separatorColor = "UITableView.__setSeparatorColor(_:)"
17 | }
18 |
19 | public extension UITableView {
20 | var app_sectionIndexColor: UIColor? {
21 | get { sectionIndexColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | self.sectionIndexColor = newValue
25 | } else {
26 | swizzled_setSectionIndexColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_sectionIndexBackgroundColor: UIColor? {
32 | get { sectionIndexBackgroundColor }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | self.sectionIndexBackgroundColor = newValue
36 | } else {
37 | swizzled_setSectionIndexBackgroundColor(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_sectionIndexTrackingBackgroundColor: UIColor? {
43 | get { sectionIndexTrackingBackgroundColor }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | self.sectionIndexTrackingBackgroundColor = newValue
47 | } else {
48 | swizzled_setSectionIndexTrackingBackgroundColor(newValue)
49 | }
50 | }
51 | }
52 |
53 | var app_separatorColor: UIColor? {
54 | get { separatorColor }
55 | set {
56 | if __USING_APPEARANCED_SWIZZLING__ {
57 | self.separatorColor = newValue
58 | } else {
59 | swizzled_setSeparatorColor(newValue)
60 | }
61 | }
62 | }
63 | }
64 |
65 | internal extension UITableView {
66 | static func silenceExchangeTableViewImplementation() {
67 | app_swizzling(
68 | originalSelector: #selector(setter: sectionIndexColor),
69 | newSelector: #selector(swizzled_setSectionIndexColor)
70 | )
71 |
72 | app_swizzling(
73 | originalSelector: #selector(setter: sectionIndexBackgroundColor),
74 | newSelector: #selector(swizzled_setSectionIndexBackgroundColor)
75 | )
76 |
77 | app_swizzling(
78 | originalSelector: #selector(setter: sectionIndexTrackingBackgroundColor),
79 | newSelector: #selector(swizzled_setSectionIndexTrackingBackgroundColor)
80 | )
81 |
82 | app_swizzling(
83 | originalSelector: #selector(setter: separatorColor),
84 | newSelector: #selector(swizzled_setSeparatorColor)
85 | )
86 | }
87 | }
88 |
89 | private extension UITableView {
90 | func __setSectionIndexColor(_ sectionIndexColor: UIColor?) {
91 | if __USING_APPEARANCED_SWIZZLING__ {
92 | swizzled_setSectionIndexColor(sectionIndexColor)
93 | } else {
94 | self.sectionIndexColor = sectionIndexColor
95 | }
96 | }
97 |
98 | func __setSectionIndexBackgroundColor(_ sectionIndexBackgroundColor: UIColor?) {
99 | if __USING_APPEARANCED_SWIZZLING__ {
100 | swizzled_setSectionIndexBackgroundColor(sectionIndexBackgroundColor)
101 | } else {
102 | self.sectionIndexBackgroundColor = sectionIndexBackgroundColor
103 | }
104 | }
105 |
106 | func __setSectionIndexTrackingBackgroundColor(_ sectionIndexTrackingBackgroundColor: UIColor?) {
107 | if __USING_APPEARANCED_SWIZZLING__ {
108 | swizzled_setSectionIndexTrackingBackgroundColor(sectionIndexTrackingBackgroundColor)
109 | } else {
110 | self.sectionIndexTrackingBackgroundColor = sectionIndexTrackingBackgroundColor
111 | }
112 | }
113 |
114 | func __setSeparatorColor(_ separatorColor: UIColor?) {
115 | if __USING_APPEARANCED_SWIZZLING__ {
116 | swizzled_setSeparatorColor(separatorColor)
117 | } else {
118 | self.separatorColor = separatorColor
119 | }
120 | }
121 |
122 |
123 | @objc func swizzled_setSectionIndexColor(_ sectionIndexColor: UIColor?) {
124 | cache(
125 | firstParam: Callable.Appearanced(sectionIndexColor),
126 | identifier: .sectionIndexColor,
127 | action: { [weak self] va in self?.__setSectionIndexColor(va) }
128 | )
129 | }
130 |
131 | @objc func swizzled_setSectionIndexBackgroundColor(_ sectionIndexBackgroundColor: UIColor?) {
132 | cache(
133 | firstParam: Callable.Appearanced(sectionIndexBackgroundColor),
134 | identifier: .sectionIndexBackgroundColor,
135 | action: { [weak self] va in self?.__setSectionIndexBackgroundColor(va) }
136 | )
137 | }
138 |
139 | @objc func swizzled_setSectionIndexTrackingBackgroundColor(_ sectionIndexTrackingBackgroundColor: UIColor?) {
140 | cache(
141 | firstParam: Callable.Appearanced(sectionIndexTrackingBackgroundColor),
142 | identifier: .sectionIndexTrackingBackgroundColor,
143 | action: { [weak self] va in self?.__setSectionIndexTrackingBackgroundColor(va) }
144 | )
145 | }
146 |
147 | @objc func swizzled_setSeparatorColor(_ separatorColor: UIColor?) {
148 | cache(
149 | firstParam: Callable.Appearanced(separatorColor),
150 | identifier: .separatorColor,
151 | action: { [weak self] va in self?.__setSeparatorColor(va) }
152 | )
153 | }
154 |
155 |
156 | }
157 |
158 | #endif
159 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UITextView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let textColor = "UITextView.__setTextColor(:)"
14 | static let attributedText = "UITextView.__setAttributedText(:)"
15 | static let typingAttributes = "UITextView.__setTypingAttributes(:)"
16 | static let linkTextAttributes = "UITextView.__setLinkTextAttributes(:)"
17 | }
18 |
19 | public extension UITextView {
20 | var app_textColor: UIColor? {
21 | get { textColor }
22 | set {
23 | if __USING_APPEARANCED_SWIZZLING__ {
24 | self.textColor = newValue
25 | } else {
26 | swizzled_setTextColor(newValue)
27 | }
28 | }
29 | }
30 |
31 | var app_attributedText: NSAttributedString {
32 | get { attributedText }
33 | set {
34 | if __USING_APPEARANCED_SWIZZLING__ {
35 | self.attributedText = newValue
36 | } else {
37 | swizzled_setAttributedText(newValue)
38 | }
39 | }
40 | }
41 |
42 | var app_typingAttributes: [NSAttributedString.Key: Any] {
43 | get { typingAttributes }
44 | set {
45 | if __USING_APPEARANCED_SWIZZLING__ {
46 | self.typingAttributes = newValue
47 | } else {
48 | swizzled_setTypingAttributes(newValue)
49 | }
50 | }
51 | }
52 |
53 | var app_linkTextAttributes: [NSAttributedString.Key: Any] {
54 | get { linkTextAttributes }
55 | set {
56 | if __USING_APPEARANCED_SWIZZLING__ {
57 | self.linkTextAttributes = newValue
58 | } else {
59 | swizzled_setLinkTextAttributes(newValue)
60 | }
61 | }
62 | }
63 | }
64 |
65 | internal extension UITextView {
66 | static func silenceExchangeTextViewImplementation() {
67 | app_swizzling(
68 | originalSelector: #selector(setter: textColor),
69 | newSelector: #selector(swizzled_setTextColor(_:))
70 | )
71 |
72 | app_swizzling(
73 | originalSelector: #selector(setter: attributedText),
74 | newSelector: #selector(swizzled_setAttributedText(_:))
75 | )
76 |
77 | app_swizzling(
78 | originalSelector: #selector(setter: typingAttributes),
79 | newSelector: #selector(swizzled_setTypingAttributes(_:))
80 | )
81 |
82 | app_swizzling(
83 | originalSelector: #selector(setter: linkTextAttributes),
84 | newSelector: #selector(swizzled_setLinkTextAttributes(_:))
85 | )
86 | }
87 | }
88 |
89 | private extension UITextView {
90 | func __setTextColor(_ textColor: UIColor?) {
91 | if __USING_APPEARANCED_SWIZZLING__ {
92 | swizzled_setTextColor(textColor)
93 | } else {
94 | self.textColor = textColor
95 | }
96 | }
97 |
98 | func __setAttributedText(_ attributedText: NSAttributedString) {
99 | if __USING_APPEARANCED_SWIZZLING__ {
100 | swizzled_setAttributedText(attributedText)
101 | } else {
102 | self.attributedText = attributedText
103 | }
104 | }
105 |
106 | func __setTypingAttributes(_ typingAttributes: [NSAttributedString.Key: Any]) {
107 | if __USING_APPEARANCED_SWIZZLING__ {
108 | swizzled_setTypingAttributes(typingAttributes)
109 | } else {
110 | self.typingAttributes = typingAttributes
111 | }
112 | }
113 |
114 | func __setLinkTextAttributes(_ linkTextAttributes: [NSAttributedString.Key: Any]) {
115 | if __USING_APPEARANCED_SWIZZLING__ {
116 | swizzled_setLinkTextAttributes(linkTextAttributes)
117 | } else {
118 | self.linkTextAttributes = linkTextAttributes
119 | }
120 | }
121 |
122 |
123 | @objc func swizzled_setTextColor(_ textColor: UIColor?) {
124 | cache(
125 | firstParam: Callable.Appearanced(textColor),
126 | identifier: .textColor,
127 | action: { [weak self] va in self?.__setTextColor(va) }
128 | )
129 | }
130 |
131 | @objc func swizzled_setAttributedText(_ attributedText: NSAttributedString) {
132 | cache(
133 | firstParam: Callable.Attributed(attributedText),
134 | identifier: .attributedText,
135 | action: { [weak self] va in self?.__setAttributedText(va) }
136 | )
137 | }
138 |
139 | @objc func swizzled_setTypingAttributes(_ typingAttributes: [NSAttributedString.Key: Any]) {
140 | cache(
141 | firstParam: Callable.Collection(typingAttributes),
142 | identifier: .typingAttributes,
143 | action: { [weak self] va in self?.__setTypingAttributes(va) }
144 | )
145 | }
146 |
147 | @objc func swizzled_setLinkTextAttributes(_ linkTextAttributes: [NSAttributedString.Key: Any]) {
148 | cache(
149 | firstParam: Callable.Collection(linkTextAttributes),
150 | identifier: .linkTextAttributes,
151 | action: { [weak self] va in self?.__setLinkTextAttributes(va) }
152 | )
153 | }
154 | }
155 |
156 | #endif
157 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIToolbar+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/11.
6 | //
7 |
8 | #if os(iOS)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let barTintColor = "__setBarTintColor(_:)"
14 | static let setBackgroundImageForPositionMetrics = "__setBackgroundImage(_:forToolbarPosition:barMetrics:)"
15 | static let setShadowImageFor = "__setShadowImage(_:forToolbarPosition:)"
16 | }
17 |
18 | public extension UIToolbar {
19 | var app_barTintColor: UIColor? {
20 | get { barTintColor }
21 | set {
22 | if __USING_APPEARANCED_SWIZZLING__ {
23 | self.barTintColor = newValue
24 | } else {
25 | swizzled_setBarTintColor(newValue)
26 | }
27 | }
28 | }
29 |
30 | func app_setBackgroundImage(_ backgroundImage: UIImage?, forToolbarPosition position: UIBarPosition, barMetrics: UIBarMetrics) {
31 | if __USING_APPEARANCED_SWIZZLING__ {
32 | setBackgroundImage(backgroundImage, forToolbarPosition: position, barMetrics: barMetrics)
33 | } else {
34 | swizzled_setBackgroundImage(backgroundImage, forToolbarPosition: position, barMetrics: barMetrics)
35 | }
36 | }
37 |
38 | func app_setShadowImage(_ shadowImage: UIImage?, forToolbarPosition position: UIBarPosition) {
39 | if __USING_APPEARANCED_SWIZZLING__ {
40 | setShadowImage(shadowImage, forToolbarPosition: position)
41 | } else {
42 | swizzled_setShadowImage(shadowImage, forToolbarPosition: position)
43 | }
44 | }
45 | }
46 |
47 | internal extension UIToolbar {
48 | static func silenceExchangeToolbarImplementation() {
49 | app_swizzling(
50 | originalSelector: #selector(setter: barTintColor),
51 | newSelector: #selector(swizzled_setBarTintColor(_:))
52 | )
53 |
54 | app_swizzling(
55 | originalSelector: #selector(setBackgroundImage(_:forToolbarPosition:barMetrics:)),
56 | newSelector: #selector(swizzled_setBackgroundImage(_:forToolbarPosition:barMetrics:))
57 | )
58 |
59 | app_swizzling(
60 | originalSelector: #selector(setShadowImage(_:forToolbarPosition:)),
61 | newSelector: #selector(swizzled_setShadowImage(_:forToolbarPosition:))
62 | )
63 | }
64 | }
65 |
66 | private extension UIToolbar {
67 | func __setBarTintColor(_ barTintColor: UIColor?) {
68 | if __USING_APPEARANCED_SWIZZLING__ {
69 | swizzled_setBarTintColor(barTintColor)
70 | } else {
71 | self.barTintColor = barTintColor
72 | }
73 | }
74 |
75 | func __setBackgroundImage(_ backgroundImage: UIImage?, forToolbarPosition position: UIBarPosition, barMetrics: UIBarMetrics) {
76 | if __USING_APPEARANCED_SWIZZLING__ {
77 | swizzled_setBackgroundImage(backgroundImage, forToolbarPosition: position, barMetrics: barMetrics)
78 | } else {
79 | setBackgroundImage(backgroundImage, forToolbarPosition: position, barMetrics: barMetrics)
80 | }
81 | }
82 |
83 | func __setShadowImage(_ shadowImage: UIImage?, forToolbarPosition position: UIBarPosition) {
84 | if __USING_APPEARANCED_SWIZZLING__ {
85 | swizzled_setShadowImage(shadowImage, forToolbarPosition: position)
86 | } else {
87 | setShadowImage(shadowImage, forToolbarPosition: position)
88 | }
89 | }
90 |
91 | @objc func swizzled_setBarTintColor(_ barTintColor: UIColor?) {
92 | cache(
93 | firstParam: Callable.Appearanced(barTintColor),
94 | identifier: .barTintColor,
95 | action: { [weak self] va in self?.__setBarTintColor(va) }
96 | )
97 | }
98 |
99 | @objc func swizzled_setBackgroundImage(_ backgroundImage: UIImage?, forToolbarPosition position: UIBarPosition, barMetrics: UIBarMetrics) {
100 | cache(
101 | firstParam: Callable.Appearanced(backgroundImage),
102 | secondParam: Callable.Original(position),
103 | thirdParam: Callable.Original(barMetrics),
104 | identifier: .setBackgroundImageForPositionMetrics,
105 | action: { [weak self] va, vb, vc in self?.__setBackgroundImage(va, forToolbarPosition: vb, barMetrics: vc) },
106 | category: "UIToolBar.position:\(position.rawValue).metrics:\(barMetrics.rawValue)"
107 | )
108 | }
109 |
110 | @objc func swizzled_setShadowImage(_ shadowImage: UIImage?, forToolbarPosition position: UIBarPosition) {
111 | cache(
112 | firstParam: Callable.Appearanced(shadowImage),
113 | secondParam: Callable.Original(position),
114 | identifier: .setShadowImageFor,
115 | action: { [weak self] va, vb in self?.__setShadowImage(va, forToolbarPosition: vb) },
116 | category: "UIToolBar.position:\(position.rawValue)"
117 | )
118 | }
119 | }
120 |
121 | #endif
122 |
--------------------------------------------------------------------------------
/Sources/Chameleon/NSUIKits/UIKit/UIView+.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | #if canImport(UIKit)
9 |
10 | import UIKit
11 |
12 | private extension AppearanceCallableIdentifier {
13 | static let backgroundColor = "UIView.__setBackgroundColor(_:)"
14 | static let tintColor = "UIView.__setTintColor(_:)"
15 | }
16 |
17 | public extension UIView {
18 | var app_backgroundColor: UIColor? {
19 | get { backgroundColor }
20 | set {
21 | if __USING_APPEARANCED_SWIZZLING__ {
22 | backgroundColor = newValue
23 | } else {
24 | swizzled_setBackgroundColor(newValue)
25 | }
26 | }
27 | }
28 |
29 | var app_tintColor: UIColor {
30 | get { tintColor }
31 | set {
32 | if __USING_APPEARANCED_SWIZZLING__ {
33 | self.tintColor = newValue
34 | } else {
35 | swizzled_setTintColor(newValue)
36 | }
37 | }
38 | }
39 | }
40 |
41 | internal extension UIView {
42 | static func silenceExchangeViewImplementation() {
43 | app_swizzling(
44 | originalSelector: #selector(setter: backgroundColor),
45 | newSelector: #selector(swizzled_setBackgroundColor(_:))
46 | )
47 |
48 | app_swizzling(
49 | originalSelector: #selector(setter: tintColor),
50 | newSelector: #selector(swizzled_setTintColor(_:))
51 | )
52 | }
53 | }
54 |
55 | private extension UIView {
56 | func __setBackgroundColor(_ backgroundColor: UIColor?) {
57 | if __USING_APPEARANCED_SWIZZLING__ {
58 | swizzled_setBackgroundColor(backgroundColor)
59 | } else {
60 | self.backgroundColor = backgroundColor
61 | }
62 | }
63 |
64 | func __setTintColor(_ tintColor: UIColor) {
65 | if __USING_APPEARANCED_SWIZZLING__ {
66 | swizzled_setTintColor(tintColor)
67 | } else {
68 | self.tintColor = tintColor
69 | }
70 | }
71 |
72 | @objc func swizzled_setBackgroundColor(_ backgroundColor: UIColor?) {
73 | cache(
74 | firstParam: Callable.Appearanced(backgroundColor),
75 | identifier: .backgroundColor,
76 | action: { [weak self] va in self?.__setBackgroundColor(va) }
77 | )
78 | }
79 |
80 | @objc func swizzled_setTintColor(_ tintColor: UIColor) {
81 | cache(
82 | firstParam: Callable.Appearanced(tintColor),
83 | identifier: .tintColor,
84 | action: { [weak self] va in self?.__setTintColor(va) }
85 | )
86 | }
87 | }
88 |
89 | #endif
90 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/AttributedStringTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryTests.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | import XCTest
9 | import Chameleon
10 |
11 | final class AttributedStringTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 |
28 | #if os(macOS)
29 | let attributedString = NSMutableAttributedString(string: "Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.")
30 | attributedString.addAttributes([.foregroundColor: NSColor.red], range: NSMakeRange(0, 4))
31 | attributedString.addAttributes([.foregroundColor: NSColor.blue], range: NSMakeRange(8, 5))
32 | attributedString.addAttributes([.font: NSFont.systemFont(ofSize: 18)], range: NSMakeRange(2, 8))
33 |
34 | func enumerated() {
35 | attributedString.enumerateAttributes(in: NSMakeRange(0, attributedString.length)) { attributes, range, stop in
36 | print("---> \(range.location)->\(range.location + range.length): \(attributes.keys)")
37 | }
38 | attributedString.enumerateAttribute(.foregroundColor, in: NSMakeRange(0, attributedString.length)) { value, range, stop in
39 | print("===> \(range.location)->\(range.location + range.length): \(String(describing: value))")
40 | }
41 | }
42 |
43 | enumerated()
44 |
45 | print("\n\n\n")
46 | attributedString.addAttributes([.foregroundColor: NSColor.cyan], range: NSMakeRange(8, 5))
47 |
48 | enumerated()
49 | #endif
50 | }
51 |
52 | func testPerformanceExample() throws {
53 | // This is an example of a performance test case.
54 | self.measure {
55 | // Put the code you want to measure the time of here.
56 | }
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/AttributesTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/16.
6 | //
7 |
8 | import Foundation
9 | import XCTest
10 | #if os(macOS)
11 | import Cocoa
12 | #else
13 | import UIKit
14 | #endif
15 | import Chameleon
16 |
17 | extension Dictionary where Key == NSAttributedString.Key {
18 | func attributes(equalTo dict2Block: @autoclosure () -> [Key: Any]) -> Bool {
19 | let dict2 = dict2Block()
20 |
21 | guard self.count == dict2.count else { return false }
22 |
23 | for (key, value) in self {
24 | guard let value2 = dict2[key] else { return false }
25 |
26 | if key == .foregroundColor || key == .backgroundColor {
27 | guard let color1 = value as? NSUIAppearanceColor,
28 | let color2 = value2 as? NSUIAppearanceColor else { return false }
29 | guard color1 == color2 else { return false }
30 | }
31 | }
32 |
33 | return true
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/ChameleonTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if os(macOS)
4 | import Cocoa
5 | #else
6 | import UIKit
7 | #endif
8 |
9 | @testable import Chameleon
10 |
11 | final class ChameleonTests: XCTestCase {
12 | func testExample() throws {
13 | // This is an example of a functional test case.
14 | // Use XCTAssert and related functions to verify your tests produce the correct
15 | // results.
16 |
17 | #if os(macOS)
18 | // let text = NSText()
19 | // text.app_setTextColor(.red, range: NSMakeRange(0, 10))
20 | // text.app_setTextColor(.red, range: NSMakeRange(0, 20))
21 |
22 | // RunTimeKit.changeExchangeImplementations()
23 |
24 | AppearanceManager.activeSwizzling()
25 |
26 | let color = NSColor.red
27 | print(color)
28 | let box = NSBox()
29 | box.fillColor = color
30 | box.fillColor = .blue
31 | box.fillColor = .yellow
32 | let _ = box.fillColor
33 | print(box.fillColor)
34 |
35 | #endif
36 | }
37 |
38 | func testRepeatSet() {
39 | #if os(macOS)
40 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
41 |
42 | let label = NSTextField(labelWithString: "hello world")
43 |
44 | label.app_textColor = ThemeInfos.Theme0.C001
45 |
46 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme1)
47 |
48 | XCTAssertEqual(label.textColor, ThemeInfos.Theme1.C001)
49 |
50 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme2)
51 |
52 | XCTAssertEqual(label.textColor, ThemeInfos.Theme2.C001)
53 |
54 | label.app_textColor = ThemeInfos.Theme0.C002
55 | label.app_textColor = ThemeInfos.Theme0.C003
56 |
57 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
58 |
59 | XCTAssertEqual(label.textColor, ThemeInfos.Theme0.C003)
60 | XCTAssertNotEqual(label.textColor, ThemeInfos.Theme0.C001)
61 |
62 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme1)
63 |
64 | XCTAssertEqual(label.textColor, ThemeInfos.Theme1.C003)
65 | XCTAssertNotEqual(label.textColor, ThemeInfos.Theme1.C001)
66 |
67 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme2)
68 |
69 | XCTAssertEqual(label.textColor, ThemeInfos.Theme2.C003)
70 | XCTAssertNotEqual(label.textColor, ThemeInfos.Theme2.C001)
71 |
72 | #endif
73 | }
74 |
75 | func testExecution() {
76 | let color1 = NSUIAppearanceColor.red
77 | let color2 = NSUIAppearanceColor.red
78 |
79 | testFunc1(color1)
80 | testFunc2(color2)
81 | }
82 |
83 | func testFunc1(_ color: NSUIAppearanceColor) {
84 | Cacher(param: color, callback: callback1(_:)).execute()
85 | }
86 |
87 | func testFunc2(_ color: NSUIAppearanceColor?) {
88 | Cacher(param: color, callback: callback2(_:)).execute()
89 | }
90 |
91 | func callback1(_ color: NSUIAppearanceColor) {
92 | print(String(describing: color))
93 | }
94 |
95 | func callback2(_ color: NSUIAppearanceColor?) {
96 | print(String(describing: color))
97 | }
98 | }
99 |
100 | struct Cacher {
101 | let param: T
102 | let callback: ((T) -> Void)
103 |
104 | func execute() {
105 | callback(param)
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/CheckTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CheckTests.swift
3 | //
4 | //
5 | // Created by Jo on 2024/1/26.
6 | //
7 |
8 | import XCTest
9 | @testable import Chameleon
10 |
11 | final class CheckTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | let d1 = [
23 | "a": [
24 | "b": [
25 | "k": 1,
26 | "d": 2
27 | ],
28 | "e": [
29 | "f": 3,
30 | "g": 4
31 | ],
32 | "h": 5,
33 | "i": 6
34 | ]
35 | ]
36 |
37 | let d2 = [
38 | "a": [
39 | "b": [
40 | "k": 7,
41 | "d": 8
42 | ],
43 | "e": [
44 | "f": 9,
45 | "g": 10
46 | ],
47 | "h": 11,
48 | "i": 12
49 | ]
50 | ]
51 |
52 | let d3 = [
53 | "a": [
54 | "b": [
55 | "c": 13,
56 | "d": 14
57 | ],
58 | "e": [
59 | "f": 15,
60 | "g": 16
61 | ],
62 | "h": 17,
63 | "i": 18
64 | ]
65 | ]
66 |
67 | let d4 = [
68 | "a": [
69 | "b": [
70 | "c": 19,
71 | "d": 20
72 | ],
73 | "e": [
74 | "l": 21,
75 | "g": 22
76 | ],
77 | "h": 23,
78 | "i": ["m": 44]
79 | ]
80 | ]
81 |
82 | let result = checkAppearanceInfos([
83 | CheckableAppearanceInfo(identifier: "d1", appearanceInfos: d1),
84 | CheckableAppearanceInfo(identifier: "d2", appearanceInfos: d2),
85 | CheckableAppearanceInfo(identifier: "d3", appearanceInfos: d3),
86 | CheckableAppearanceInfo(identifier: "d4", appearanceInfos: d4)
87 | ])
88 |
89 | print(result.map { "\($0)" }.joined(separator: "\n"))
90 |
91 | XCTAssertTrue(result.count > 0)
92 | }
93 |
94 | func testPerformanceExample() throws {
95 | // This is an example of a performance test case.
96 | self.measure {
97 | // Put the code you want to measure the time of here.
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/ComplexTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComplexTests.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/17.
6 | //
7 |
8 | import XCTest
9 | import Chameleon
10 |
11 | final class ComplexTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | #if os(iOS)
23 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
24 |
25 | let theme0 = ThemeInfos.Theme0.self
26 |
27 | let view = UIView()
28 |
29 | view.backgroundColor = theme0.C001
30 |
31 | XCTAssertEqual(view.backgroundColor, ThemeInfos.Theme0.C001)
32 |
33 | view.app_backgroundColor = theme0.C001
34 |
35 | XCTAssertEqual(view.backgroundColor, ThemeInfos.Theme0.C001)
36 |
37 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme1)
38 |
39 | XCTAssertEqual(view.backgroundColor, ThemeInfos.Theme1.C001)
40 |
41 | AppearanceManager.activeSwizzling()
42 |
43 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme2)
44 |
45 | XCTAssertEqual(view.backgroundColor, ThemeInfos.Theme2.C001)
46 | #endif
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/DictionaryTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DictionaryTests.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/6.
6 | //
7 |
8 | import XCTest
9 | import Chameleon
10 |
11 | final class DictionaryTests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 | }
16 |
17 | override func tearDownWithError() throws {
18 | // Put teardown code here. This method is called after the invocation of each test method in the class.
19 | }
20 |
21 | func testExample() throws {
22 | // This is an example of a functional test case.
23 | // Use XCTAssert and related functions to verify your tests produce the correct results.
24 | // Any test you write for XCTest can be annotated as throws and async.
25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
27 |
28 | let id0 = "color/text/C001"
29 | let id1 = "color/background/C102"
30 | let id2 = "image/bg/tips"
31 |
32 | XCTAssertEqual((try? ThemeInfo.theme0.appearanceInfo(with: id0)) as! String, "F7ACBC")
33 | XCTAssertEqual((try? ThemeInfo.theme1.appearanceInfo(with: id1)) as! String, "525F42 0.9")
34 | XCTAssertNil((try? ThemeInfo.theme2.appearanceInfo(with: id2)))
35 | XCTAssertNil((try? ThemeInfo.theme0.appearanceInfo(with: "color/textc000")))
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Recycle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Recycle.swift
3 | //
4 | //
5 | // Created by Jo on 2023/4/13.
6 | //
7 |
8 | import XCTest
9 | import Chameleon
10 |
11 | #if os(iOS)
12 | import UIKit
13 |
14 |
15 | private class MyLabel: UILabel {
16 | var key: String = ""
17 | deinit {
18 | print("deinit \(key)")
19 | }
20 |
21 | override var description: String {
22 | return "\(key) deinit \(self)"
23 | }
24 | }
25 |
26 | private class MyView: UIView {
27 | var key: String = ""
28 | deinit {
29 | print("deinit \(key)")
30 | }
31 |
32 | override var description: String {
33 | return "\(key) deinit \(self)"
34 | }
35 | }
36 |
37 | final class Recycle: XCTestCase {
38 |
39 | override func setUpWithError() throws {
40 | // Put setup code here. This method is called before the invocation of each test method in the class.
41 | }
42 |
43 | override func tearDownWithError() throws {
44 | // Put teardown code here. This method is called after the invocation of each test method in the class.
45 | }
46 |
47 | func testExample() throws {
48 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme0)
49 |
50 | func test1() {
51 | let label = MyLabel()
52 | label.app_textColor = ThemeInfos.Theme0.C004
53 | label.key = "appearnced Label1"
54 |
55 | let label2 = MyLabel()
56 | label2.key = "not appearanced label2"
57 | }
58 |
59 | func test2() {
60 | let view = MyView()
61 | view.app_backgroundColor = ThemeInfos.Theme0.C001
62 | view.key = "appearanced view1"
63 |
64 | let view2 = MyView()
65 | view2.key = "not appearanced view2"
66 | }
67 |
68 | let label3 = MyLabel()
69 | label3.app_textColor = ThemeInfos.Theme0.C004
70 | label3.key = "appearnced Label3"
71 |
72 | let view3 = MyView()
73 | view3.app_backgroundColor = ThemeInfos.Theme0.C001
74 | view3.key = "appearanced view3"
75 |
76 | test1()
77 | test2()
78 |
79 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme1)
80 |
81 | AppearanceManager.shared.changeThemeWith(themeInfo: ThemeInfo.theme2)
82 | }
83 | }
84 |
85 | #endif
86 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/bullseye_icon_252137.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "bullseye_icon_252137.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/bullseye_icon_252137.imageset/bullseye_icon_252137.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/bullseye_icon_252137.imageset/bullseye_icon_252137.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/dinosaur_icon_252012.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "dinosaur_icon_252012.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/dinosaur_icon_252012.imageset/dinosaur_icon_252012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/dinosaur_icon_252012.imageset/dinosaur_icon_252012.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/heart_love_icon_251954.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "heart_love_icon_251954.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/heart_love_icon_251954.imageset/heart_love_icon_251954.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme0/heart_love_icon_251954.imageset/heart_love_icon_251954.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/home_house_icon_251952.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "home_house_icon_251952.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/home_house_icon_251952.imageset/home_house_icon_251952.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/home_house_icon_251952.imageset/home_house_icon_251952.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/human_handsup_icon_251948.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "human_handsup_icon_251948.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/human_handsup_icon_251948.imageset/human_handsup_icon_251948.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/human_handsup_icon_251948.imageset/human_handsup_icon_251948.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/mood_happy_icon_251870.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "mood_happy_icon_251870.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/mood_happy_icon_251870.imageset/mood_happy_icon_251870.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme1/mood_happy_icon_251870.imageset/mood_happy_icon_251870.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/trash_delete_remove_icon_251766.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "trash_delete_remove_icon_251766.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/trash_delete_remove_icon_251766.imageset/trash_delete_remove_icon_251766.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/trash_delete_remove_icon_251766.imageset/trash_delete_remove_icon_251766.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/visible_eye_icon_251745.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "visible_eye_icon_251745.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/visible_eye_icon_251745.imageset/visible_eye_icon_251745.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/visible_eye_icon_251745.imageset/visible_eye_icon_251745.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/zap_icon_251733.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "zap_icon_251733.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/zap_icon_251733.imageset/zap_icon_251733.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/Assets.xcassets/theme2/zap_icon_251733.imageset/zap_icon_251733.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/Resources/bullseye_icon_252137.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JyHu/Chameleon/9118e990720748fa2f14c4b75aeb21df0eb4f32c/Tests/ChameleonTests/Resources/bullseye_icon_252137.png
--------------------------------------------------------------------------------
/Tests/ChameleonTests/ThemeInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ThemeInfo.swift
3 | // Demos
4 | //
5 | // Created by Jo on 2023/4/4.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ThemeInfo {
11 | static let theme0: [String: Any] = [
12 | "color": [
13 | "text": [
14 | "C001": "F7ACBC",
15 | "C002": "EF5B9C",
16 | "C003": "FEEEED",
17 | "C004": "F05B72",
18 | "C005": "FAA755",
19 | "C006": "D71345",
20 | "C007": "905D1D",
21 | "C008": "1D953F"
22 | ],
23 | "background": [
24 | "C101": "C99979",
25 | "C102": "DE773F",
26 | "C103": "B7704F",
27 | "C104": "B4532A",
28 | "C105": "F36C21",
29 | "C106": "8E3E1F",
30 | "C107": "6A3427",
31 | "C108": "C85D44",
32 | ]
33 | ],
34 | "image": [
35 | "no01": "bullseye_icon_252137",
36 | "no02": "dinosaur_icon_252012",
37 | "no03": "heart_love_icon_251954"
38 | ],
39 | "others": [
40 | "border": [
41 | "width": 5
42 | ],
43 | "imageView": [
44 | "animationDuration": 1,
45 | "animationRepeatCount": 3
46 | ]
47 | ]
48 | ]
49 |
50 | static let theme1: [String: Any] = [
51 | "color": [
52 | "text": [
53 | "C001": "B17936 0.9",
54 | "C002": "rgba<254,220,189,0.9>",
55 | "C003": "rgba<244,121,32,0.9>",
56 | "C004": "hex<905A3D 0.9>",
57 | "C005": "hex",
58 | "C006": "5C7A29 0.9",
59 | "C007": "A3CF62 0.9",
60 | "C008": "694D9F"
61 | ],
62 | "background": [
63 | "C101": "5F5D46 0.9",
64 | "C102": "525F42 0.9",
65 | "C103": "596032 0.9",
66 | "C104": "6E6B41 0.9",
67 | "C105": "CBC547 0.9",
68 | "C106": "DECB00 0.9",
69 | "C107": "FCF16E 0.9",
70 | "C108": "FFE600 0.9",
71 | ]
72 | ],
73 | "image": [
74 | "no01": "home_house_icon_251952",
75 | "no02": "human_handsup_icon_251948",
76 | "no03": "mood_happy_icon_251870"
77 | ],
78 | "others": [
79 | "border": [
80 | "width": 2
81 | ],
82 | "imageView": [
83 | "animationDuration": 3,
84 | "animationRepeatCount": 5
85 | ]
86 | ]
87 | ]
88 |
89 | static let theme2: [String: Any] = [
90 | "color": [
91 | "text": [
92 | "C001": "444693 0.8",
93 | "C002": "2B4490 0.8",
94 | "C003": "2A5CAA 0.8",
95 | "C004": "102B6A 0.8",
96 | "C005": "1A2933 0.8",
97 | "C006": "6A6DA9 0.8",
98 | "C007": "494E8F 0.8",
99 | "C008": "FCAF17"
100 | ],
101 | "background": [
102 | "C101": "1B315E 0.8",
103 | "C102": "2585A6 0.8",
104 | "C103": "2570A1 0.8",
105 | "C104": "2468A2 0.8",
106 | "C105": "228F8D 0.8",
107 | "C106": "7BBFEA 0.8",
108 | "C107": "11264F 0.8",
109 | "C108": "6C4C49 0.8",
110 | ]
111 | ],
112 | "image": [
113 | "no01": "trash_delete_remove_icon_251766",
114 | "no02": "visible_eye_icon_251745",
115 | "no03": "zap_icon_251733"
116 | ],
117 | "others": [
118 | "border": [
119 | "width": 10
120 | ],
121 | "imageView": [
122 | "animationDuration": 5,
123 | "animationRepeatCount": 7
124 | ]
125 | ]
126 | ]
127 | }
128 |
--------------------------------------------------------------------------------
/Tools/Breeder/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | private var mainWindowController = MainWindowController()
14 |
15 | func applicationDidFinishLaunching(_ aNotification: Notification) {
16 | mainWindowController.window?.makeKeyAndOrderFront(nil)
17 | }
18 |
19 | func applicationWillTerminate(_ aNotification: Notification) {
20 | // Insert code here to tear down your application
21 | }
22 |
23 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
24 | return false
25 | }
26 |
27 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
28 | if !flag {
29 | mainWindowController.window?.makeKeyAndOrderFront(nil)
30 | }
31 | return true
32 | }
33 |
34 | func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
35 | return true
36 | }
37 |
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/Tools/Breeder/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tools/Breeder/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tools/Breeder/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tools/Breeder/Breeder.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Tools/Breeder/DataCenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DataManager.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Foundation
9 | import Combine
10 |
11 | class DataCenter {
12 | static var shared = DataCenter()
13 |
14 | @Published var files: [FileInfo] = []
15 | }
16 |
17 | class FileInfo {
18 | let identifier = UUID().uuidString
19 | var name: String?
20 | var url: URL?
21 | }
22 |
--------------------------------------------------------------------------------
/Tools/Breeder/DetailViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DetailViewController.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Cocoa
9 |
10 | class DetailViewController: NSViewController {
11 |
12 | override func loadView() {
13 | view = NSView(frame: NSMakeRect(0, 0, 100, 100))
14 | }
15 |
16 | override func viewDidLoad() {
17 | super.viewDidLoad()
18 | // Do view setup here.
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Tools/Breeder/FilesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FilesViewController.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Cocoa
9 | import SwiftExtensions
10 | import Combine
11 |
12 | private extension NSUserInterfaceItemIdentifier {
13 | static let index = NSUserInterfaceItemIdentifier("com.auu.chameleon.files.index")
14 | static let name = NSUserInterfaceItemIdentifier("com.auu.chameleon.files.name")
15 | static let path = NSUserInterfaceItemIdentifier("com.auu.chameleon.files.path")
16 | static let delete = NSUserInterfaceItemIdentifier("com.auu.chameleon.files.delete")
17 | }
18 |
19 | class FilesViewController: NSViewController {
20 |
21 | private var tableView = NSTableView(frame: NSMakeRect(0, 0, 100, 100))
22 | private var subscription: AnyCancellable?
23 |
24 | override func loadView() {
25 | view = NSScrollView(documentView: tableView, isHorizontal: false)
26 | }
27 |
28 | override func viewDidLoad() {
29 | super.viewDidLoad()
30 |
31 | tableView.delegate = self
32 | tableView.dataSource = self
33 | tableView.focusRingType = .none
34 | tableView.allowsColumnResizing = true
35 | tableView.usesAlternatingRowBackgroundColors = true
36 | tableView.addTableColumn(identifier: .index, title: "Index", width: 36)
37 | tableView.addTableColumn(identifier: .name, title: "Name", minWidth: 48, maxWidth: 120)
38 | tableView.addTableColumn(identifier: .path, title: "Path", minWidth: 150)
39 | tableView.addTableColumn(identifier: .delete, title: "", width: 64)
40 |
41 | subscription = DataCenter.shared.$files.sink { [weak self] _ in
42 | self?.tableView.reloadData()
43 | }
44 | }
45 | }
46 |
47 | extension FilesViewController: NSTableViewDelegate, NSTableViewDataSource {
48 | func numberOfRows(in tableView: NSTableView) -> Int {
49 | return DataCenter.shared.files.count
50 | }
51 |
52 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
53 | guard let identifier = tableColumn?.identifier else { return nil }
54 | if identifier == .index {
55 | return "\(row)"
56 | }
57 |
58 | let fileInfo = DataCenter.shared.files[row]
59 |
60 | if identifier == .name {
61 | return fileInfo.name
62 | }
63 |
64 | if identifier == .path {
65 | return fileInfo.url?.getPath()
66 | }
67 |
68 | return nil
69 | }
70 |
71 | func tableView(_ tableView: NSTableView, dataCellFor tableColumn: NSTableColumn?, row: Int) -> NSCell? {
72 | guard let identifier = tableColumn?.identifier else { return nil }
73 | if identifier == .index {
74 | return NSTextFieldCell()
75 | } else if identifier == .path {
76 | return NSTextFieldCell()
77 | } else if identifier == .delete {
78 | let cell = NSButtonCell(textCell: "Delete")
79 | cell.bezelStyle = .rounded
80 | cell.target = self
81 | cell.action = #selector(deleteFilesAction)
82 | return cell
83 | }
84 |
85 | return nil
86 | }
87 |
88 | func controlTextDidChange(_ obj: Notification) {
89 |
90 | }
91 | }
92 |
93 | private extension FilesViewController {
94 | @objc func deleteFilesAction() {
95 |
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Tools/Breeder/InfosViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InfosViewController.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Cocoa
9 | import SwiftExtensions
10 |
11 | class InfosViewController: NSViewController {
12 |
13 | private var outlineView = NSOutlineView(frame: NSMakeRect(0, 0, 100, 100))
14 |
15 | override func loadView() {
16 | view = NSScrollView(documentView: outlineView)
17 | }
18 |
19 | override func viewDidLoad() {
20 | super.viewDidLoad()
21 | // Do view setup here.
22 |
23 | outlineView.focusRingType = .none
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Tools/Breeder/MainWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWindowController.swift
3 | // Breeder
4 | //
5 | // Created by Jo on 2023/4/7.
6 | //
7 |
8 | import Cocoa
9 | import SFSymbols
10 | import SwiftExtensions
11 |
12 | private extension NSToolbar.Identifier {
13 | static let mainToolbar = NSToolbar.Identifier("com.auu.chameleon.toolbar")
14 | }
15 |
16 | private extension NSToolbarItem.Identifier {
17 | static let newItem = NSToolbarItem.Identifier("com.auu.chameleon.new")
18 | static let expandGroup = NSToolbarItem.Identifier("com.auu.chameleon.expand")
19 | static let filesGroup = NSToolbarItem.Identifier("com.auu.chameleon.files")
20 | }
21 |
22 | class MainWindowController: NSWindowController {
23 | init() {
24 | super.init(window: MainWindow())
25 | }
26 |
27 | required init?(coder: NSCoder) {
28 | fatalError("init(coder:) has not been implemented")
29 | }
30 | }
31 |
32 | class MainWindow: NSWindow, NSToolbarDelegate {
33 |
34 | private var mainSplit = NSSplitViewController()
35 | private var fileSplit = NSSplitViewController()
36 |
37 | private var filesPage = FilesViewController()
38 | private var infosPage = InfosViewController()
39 | private var detailPage = DetailViewController()
40 |
41 | init() {
42 | let styleMask: NSWindow.StyleMask = [ .closable, .titled, .resizable, .miniaturizable ]
43 | super.init(contentRect: NSMakeRect(0, 0, 720, 540), styleMask: styleMask, backing: .buffered, defer: true)
44 |
45 | fileSplit.isVertical = false
46 | fileSplit.add(filesPage, minThickness: 200)
47 | fileSplit.add(infosPage, minThickness: 200)
48 | mainSplit.add(fileSplit, minThickness: 300)
49 | mainSplit.add(detailPage, minThickness: 200, maxThickness: 500)
50 |
51 | contentViewController = mainSplit
52 |
53 | let bar = NSToolbar(identifier: .mainToolbar)
54 | bar.delegate = self
55 | bar.displayMode = .iconOnly
56 | bar.sizeMode = .default
57 | bar.showsBaselineSeparator = true
58 | toolbar = bar
59 |
60 | title = "Breeder"
61 | contentViewController = mainSplit
62 | minSize = NSMakeSize(720, 540)
63 | setFrame(NSMakeRect(0, 0, 720, 540), display: true)
64 | titlebarAppearsTransparent = false
65 | titlebarSeparatorStyle = .line
66 | toolbarStyle = .unifiedCompact
67 | center()
68 | }
69 |
70 | func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
71 | return [ .filesGroup, .expandGroup ]
72 | }
73 |
74 | func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
75 | return [ .filesGroup, .expandGroup ]
76 | }
77 |
78 | func toolbarSelectableItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
79 | return []
80 | }
81 |
82 | func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
83 |
84 | if itemIdentifier == .filesGroup {
85 | return NSToolbarItemGroup(itemIdentifier: .filesGroup, sfnames: [ .plus, .squareAndArrowDown, .squareAndArrowUp ], selectionModel: .momentary, labels: nil, target: self, action: #selector(filesGroupAction))
86 | }
87 |
88 | if itemIdentifier == .expandGroup {
89 | return NSToolbarItemGroup(itemIdentifier: .expandGroup, sfnames: [ .rectangleTophalfFilled, .rectangleRighthalfFilled ], selectionModel: .momentary, labels: nil, target: self, action: #selector(expandsAction))
90 | }
91 |
92 | return NSToolbarItem(itemIdentifier: itemIdentifier)
93 | }
94 | }
95 |
96 | private extension MainWindow {
97 | @objc func filesGroupAction(_ itemGroup: NSToolbarItemGroup) {
98 | if itemGroup.selectedIndex == 0 {
99 | DataCenter.shared.files.append(FileInfo())
100 | }
101 | }
102 |
103 | @objc func expandsAction(_ itemGroup: NSToolbarItemGroup) {
104 | let firstItem = (itemGroup.selectedIndex == 0 ? fileSplit : mainSplit).splitViewItems[itemGroup.selectedIndex]
105 | firstItem.isCollapsed = !firstItem.isCollapsed
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/Tools/Tools.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tools/Tools.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Tools/Tools.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "sfsymbols",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/JyHu/SFSymbols.git",
7 | "state" : {
8 | "revision" : "f8e464d8e49d4a3e726d47f82e50bf48ceaefdb9"
9 | }
10 | },
11 | {
12 | "identity" : "swiftextensions",
13 | "kind" : "remoteSourceControl",
14 | "location" : "https://github.com/JyHu/SwiftExtensions",
15 | "state" : {
16 | "branch" : "main",
17 | "revision" : "1483220781095b6bcfca41bd65f417be62a92f31"
18 | }
19 | }
20 | ],
21 | "version" : 2
22 | }
23 |
--------------------------------------------------------------------------------