├── .github
└── FUNDING.yml
├── screenshot.png
├── quicklook_thumb.png
├── screenshot_themestore.png
├── Asset Catalog Tinkerer
├── Assets.xcassets
│ ├── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── AppIcon.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── squircle-dark.svg
│ │ ├── squircle-light.svg
│ │ └── Sausages.svg
│ └── icon.json
├── Asset Catalog Tinkerer.entitlements
├── Asset Catalog Tinkerer-Bridging-Header.h
├── NSImage+BrightnessDetection.h
├── NSPasteboard+Filenames.h
├── MainWindowController.swift
├── NSPasteboard+Filenames.m
├── Preferences.swift
├── AppDelegate.swift
├── DocumentController.swift
├── NSImage+BrightnessDetection.m
├── ImageCollectionViewItem.xib
├── GridLayout.swift
├── AssetCatalogDocument.swift
├── ProgressBar.swift
├── Info.plist
├── QuickLookableCollectionView.swift
├── ImageCollectionViewItem.swift
└── ImagesCollectionViewDataProvider.swift
├── releases
├── AssetCatalogTinkerer_latest.zip
├── releasenotes.md
└── appcast.xml
├── Resources
├── AssetCatalogTinkerer Icon.sketch
├── AppIconV2.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── Boxes.svg
│ │ ├── squircle-dark.svg
│ │ └── squircle-light.svg
│ └── icon.json
├── AppIconV3.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── squircle-dark.svg
│ │ ├── squircle-light.svg
│ │ └── Sausages.svg
│ └── icon.json
├── AppIconV4.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── squircle-dark.svg
│ │ ├── squircle-light.svg
│ │ └── SausagesV2.svg
│ └── icon.json
├── AppIconV5.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── squircle-dark.svg
│ │ ├── squircle-light.svg
│ │ ├── SausagesV3-FG.svg
│ │ └── SausagesV3-BG.svg
│ └── icon.json
├── AppIconV6.icon
│ ├── Assets
│ │ ├── background-dark.png
│ │ ├── background-light.png
│ │ ├── circle-dark.svg
│ │ ├── circle-light.svg
│ │ ├── squircle-dark.svg
│ │ ├── squircle-light.svg
│ │ ├── SausagesV4-BG.svg
│ │ └── SausagesV4-FG.svg
│ └── icon.json
└── AppIconV7.icon
│ ├── Assets
│ ├── background-dark.png
│ ├── background-light.png
│ ├── SausagesV4-BG.svg
│ └── SausagesV4-FG.svg
│ └── icon.json
├── Config
└── Main.xcconfig
├── .gitignore
├── ACS
├── Private Headers
│ ├── CoreSVG.h
│ ├── CoreUI+TV.h
│ └── CoreUI.h
├── ACS.h
├── Info.plist
└── Source
│ ├── AssetCatalogReader.h
│ └── ImageExporter.swift
├── act
├── Info.plist
├── Helpers.swift
└── AssetCatalogTinkererCLI.swift
├── AssetCatalog
├── QuickLookHelper.h
├── GeneratePreviewForURL.m
├── GenerateThumbnailForURL.m
├── Info.plist
├── QuickLookHelper.m
└── main.c
├── LICENSE
├── README.md
└── Asset Catalog Tinkerer.xcodeproj
└── xcshareddata
└── xcschemes
├── Asset Catalog Tinkerer.xcscheme
└── act.xcscheme
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: insidegui
2 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/screenshot.png
--------------------------------------------------------------------------------
/quicklook_thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/quicklook_thumb.png
--------------------------------------------------------------------------------
/screenshot_themestore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/screenshot_themestore.png
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/releases/AssetCatalogTinkerer_latest.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/releases/AssetCatalogTinkerer_latest.zip
--------------------------------------------------------------------------------
/Resources/AssetCatalogTinkerer Icon.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AssetCatalogTinkerer Icon.sketch
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV2.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV3.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV4.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV5.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV6.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV7.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV7.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV2.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV3.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV4.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV5.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV6.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Resources/AppIconV7.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Resources/AppIconV7.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/background-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Asset Catalog Tinkerer/AppIcon.icon/Assets/background-dark.png
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/background-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/HEAD/Asset Catalog Tinkerer/AppIcon.icon/Assets/background-light.png
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Asset Catalog Tinkerer.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Config/Main.xcconfig:
--------------------------------------------------------------------------------
1 | MARKETING_VERSION = 2.9
2 | CURRENT_PROJECT_VERSION = 290
3 |
4 | VERSIONING_SYSTEM = apple-generic
5 | DYLIB_CURRENT_VERSION = $(CURRENT_PROJECT_VERSION)
6 |
7 | ACT_SPARKLE_PUBLIC_ED_KEY=dj8ljUPnwoLj/dLs6HyJg5Ayw+t8zWtgjQUfQsH56ww=
8 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Asset Catalog Tinkerer-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | //
2 | // Use this file to import your target's public headers that you would like to expose to Swift.
3 | //
4 |
5 | #import "AssetCatalogReader.h"
6 | #import "NSImage+BrightnessDetection.h"
7 | #import "NSPasteboard+Filenames.h"
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | __MACOSX
3 | *.pbxuser
4 | !default.pbxuser
5 | *.mode1v3
6 | !default.mode1v3
7 | *.mode2v3
8 | !default.mode2v3
9 | *.perspectivev3
10 | !default.perspectivev3
11 | *.xcworkspace
12 | !default.xcworkspace
13 | xcuserdata
14 | profile
15 | *.moved-aside
16 | DerivedData
17 | .idea/
18 | build
19 | Carthage/
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemBlueColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/NSImage+BrightnessDetection.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+BrightnessDetection.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 03/02/18.
6 | // Copyright © 2018 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | @interface NSImage (BrightnessDetection)
12 |
13 | - (CGFloat)averageBrightness;
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/ACS/Private Headers/CoreSVG.h:
--------------------------------------------------------------------------------
1 | //
2 |
3 | #ifndef CoreSVG_h
4 | #define CoreSVG_h
5 |
6 | struct CGSVGDocument;
7 |
8 | typedef struct CGSVGDocument *CGSVGDocumentRef;
9 |
10 | int CGSVGDocumentWriteToData(CGSVGDocumentRef, CFDataRef, CFDictionaryRef) __attribute__((weak_import));;
11 | void CGContextDrawSVGDocument(CGContextRef, CGSVGDocumentRef) __attribute__((weak_import));;
12 | CGSize CGSVGDocumentGetCanvasSize(CGSVGDocumentRef) __attribute__((weak_import));;
13 | #endif /* CoreSVG_h */
14 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/NSPasteboard+Filenames.h:
--------------------------------------------------------------------------------
1 | //
2 | // NSPasteboard+Filenames.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 02/07/19.
6 | // Copyright © 2019 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface NSPasteboard (Filenames)
14 |
15 | - (void)setFilenamesPropertyListWithFilenames:(NSArray *)filenames;
16 | - (NSArray *__nullable)filenamesPropertyList;
17 |
18 | @end
19 |
20 | NS_ASSUME_NONNULL_END
21 |
--------------------------------------------------------------------------------
/act/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleExecutable
6 | srdchimed
7 | CFBundleIdentifier
8 | $(PRODUCT_BUNDLE_IDENTIFIER)
9 | CFBundleShortVersionString
10 | $(MARKETING_VERSION)
11 | CFBundleVersion
12 | $(CURRENT_PRODUCT_VERSION)
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ACS/ACS.h:
--------------------------------------------------------------------------------
1 | //
2 | // ACS.h
3 | // ACS
4 | //
5 | // Created by Guilherme Rambo on 02/01/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ACS.
12 | FOUNDATION_EXPORT double ACSVersionNumber;
13 |
14 | //! Project version string for ACS.
15 | FOUNDATION_EXPORT const unsigned char ACSVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 | #import
21 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/MainWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainWindowController.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 28/05/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class MainWindowController: NSWindowController {
12 |
13 | @IBOutlet weak var searchField: NSSearchField!
14 |
15 | @IBAction func enableSearchField(_ sender: AnyObject?) {
16 | searchField.isEnabled = true
17 | }
18 |
19 | @IBAction func disableSearchField(_ sender: AnyObject?) {
20 | searchField.isEnabled = false
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/NSPasteboard+Filenames.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSPasteboard+Filenames.m
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 02/07/19.
6 | // Copyright © 2019 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import "NSPasteboard+Filenames.h"
10 |
11 | @implementation NSPasteboard (Filenames)
12 |
13 | - (void)setFilenamesPropertyListWithFilenames:(NSArray *)filenames
14 | {
15 | [self setPropertyList:filenames forType:NSPasteboardTypeString];
16 | }
17 |
18 | - (NSArray *)filenamesPropertyList
19 | {
20 | return [self propertyListForType:NSPasteboardTypeString];
21 | }
22 |
23 | @end
24 |
--------------------------------------------------------------------------------
/AssetCatalog/QuickLookHelper.h:
--------------------------------------------------------------------------------
1 | //
2 | // QuickLookHelper.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 02/01/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | #import
12 |
13 | @interface QuickLookHelper : NSObject
14 |
15 | + (OSStatus)handleQuickLookRequestWithThumbnail:(QLThumbnailRequestRef)thumbnailRequest
16 | preview:(QLPreviewRequestRef)previewRequest
17 | url:(CFURLRef)url
18 | maxSize:(CGSize)maxSize;
19 |
20 | - (instancetype)initWithURL:(NSURL *)url size:(CGSize)size;
21 |
22 | - (void)generatePreview;
23 | - (void)cancel;
24 |
25 | @property (nonatomic, readonly) CGSize size;
26 | @property (nonatomic, readonly, getter=isFinished) BOOL isFinished;
27 |
28 | - (void)drawPreview;
29 |
30 | @end
31 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Preferences.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Preferences.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 22/02/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class Preferences {
12 |
13 | enum Key: String {
14 | case distinguishCatalogsAndThemeStores
15 | case ignorePackedAssets
16 | case debugImageBrightness = "ACTDebugImageBrightness"
17 | }
18 |
19 | static let shared = Preferences()
20 |
21 | private let defaults: UserDefaults
22 |
23 | init(defaults: UserDefaults = UserDefaults.standard) {
24 | self.defaults = defaults
25 | }
26 |
27 | subscript(key: Key) -> Bool {
28 | get {
29 | return defaults.bool(forKey: key.rawValue)
30 | }
31 | set {
32 | defaults.set(newValue, forKey: key.rawValue)
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/ACS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2017 Guilherme Rambo. All rights reserved.
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/circle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/circle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/releases/releasenotes.md:
--------------------------------------------------------------------------------
1 | ### Support for SVG Assets
2 |
3 | This version introduces support for reading and exporting SVG assets, including SF Symbols embedded in asset catalogs. Thanks a bunch to [@zats](https://github.com/zats) for making this happen!
4 |
5 | ### Command-Line Tool
6 |
7 | The new ‘act’ command line tool is here to help you get information and extract asset catalogs right from your command line.
8 |
9 | You can find it in the ‘Contents/MacOS’ directory of Asset Catalog Tinkerer.app.
10 |
11 | To make it even easier to use, you can set an alias for it in your favorite shell, like this for zsh:
12 |
13 | `echo "\nalias act='/Applications/Asset\ Catalog\ Tinkerer.app/Contents/MacOS/act'" >> ~/.zshrc`
14 |
15 | ### Raycast Integration
16 |
17 | There is a new [Raycast extension](https://www.raycast.com/chrismessina/asset-catalog-extractor) that leverages the `act` tool, allowing for easy extraction of asset catalogs in Raycast. Thanks [@chrismessina](https://github.com/chrismessina) for making this happen!
18 |
--------------------------------------------------------------------------------
/ACS/Private Headers/CoreUI+TV.h:
--------------------------------------------------------------------------------
1 | //
2 | // CoreUI+TV.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 22/11/15.
6 | // Copyright © 2015 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #ifndef CoreUI_TV_h
10 | #define CoreUI_TV_h
11 |
12 | #import "CoreUI.h"
13 |
14 | @interface CUINamedLayerStack : CUINamedImage
15 |
16 | @property(retain, nonatomic) NSArray *layers; // @synthesize layers=_layers;
17 |
18 | @property(readonly, nonatomic) struct CGImage *radiosityImage;
19 | @property(readonly, nonatomic) struct CGImage *flattenedImage;
20 | - (id)layerImageAtIndex:(unsigned long long)arg1;
21 | @property(readonly, nonatomic) struct CGSize size;
22 |
23 | @end
24 |
25 | @interface CUINamedLayerImage : CUINamedImage
26 |
27 | @property(nonatomic) int blendMode; // @synthesize blendMode=_blendMode;
28 | @property(nonatomic) double opacity; // @synthesize opacity=_opacity;
29 | @property(nonatomic) struct CGRect frame; // @synthesize frame=_frame;
30 |
31 | @end
32 |
33 | #endif /* CoreUI_TV_h */
34 |
--------------------------------------------------------------------------------
/releases/appcast.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Asset Catalog Tinkerer
5 | -
6 | 2.9
7 | Sat, 10 Aug 2024 11:58:51 -0300
8 | https://github.com/insidegui/AssetCatalogTinkerer
9 | 290
10 | 2.9
11 | 11.0
12 |
13 | https://assetcatalogtinkerer.rambo.workers.dev/releasenotes.html
14 |
15 |
16 |
--------------------------------------------------------------------------------
/AssetCatalog/GeneratePreviewForURL.m:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #import "QuickLookHelper.h"
6 |
7 | #import
8 |
9 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options);
10 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview);
11 |
12 | /* -----------------------------------------------------------------------------
13 | Generate a preview for file
14 |
15 | This function's job is to create preview for designated file
16 | ----------------------------------------------------------------------------- */
17 |
18 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
19 | {
20 | return [QuickLookHelper handleQuickLookRequestWithThumbnail:NULL preview:preview url:url maxSize:NSMakeSize(500, 700)];
21 | }
22 |
23 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview)
24 | {
25 | }
26 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 | fileprivate var documentController: DocumentController!
15 |
16 | func applicationWillFinishLaunching(_ notification: Notification) {
17 | // The first NSDocumentController initialized becomes the sharedDocumentController
18 | documentController = DocumentController()
19 | }
20 |
21 | func applicationDidFinishLaunching(_ aNotification: Notification) {
22 | // Insert code here to initialize your application
23 | }
24 |
25 | func applicationWillTerminate(_ aNotification: Notification) {
26 | // Insert code here to tear down your application
27 | }
28 |
29 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
30 | UserDefaults.standard.bool(forKey: "TerminateAfterLastWindowClosed")
31 | }
32 |
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/Boxes.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/AssetCatalog/GenerateThumbnailForURL.m:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 |
5 | #import "QuickLookHelper.h"
6 | #import
7 |
8 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize);
9 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail);
10 |
11 | /* -----------------------------------------------------------------------------
12 | Generate a thumbnail for file
13 |
14 | This function's job is to create thumbnail for designated file as fast as possible
15 | ----------------------------------------------------------------------------- */
16 |
17 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize)
18 | {
19 | return [QuickLookHelper handleQuickLookRequestWithThumbnail:thumbnail preview:NULL url:url maxSize:maxSize];
20 | }
21 |
22 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail)
23 | {
24 | }
25 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/squircle-dark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/squircle-light.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/DocumentController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DocumentController.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 28/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class DocumentController: NSDocumentController {
12 |
13 | override func newDocument(_ sender: Any?) {
14 | // the app doesn't support creating new documents
15 | }
16 |
17 | override func saveAllDocuments(_ sender: Any?) {
18 | // the app doesn't support saving documents
19 | }
20 |
21 | private func makeSettingsView() -> NSView? {
22 | let storyboard = NSStoryboard(name: "Main", bundle: nil)
23 |
24 | guard let viewController = storyboard.instantiateController(withIdentifier: "prefs") as? NSViewController else {
25 | return nil
26 | }
27 |
28 | return viewController.view
29 | }
30 |
31 | override func runModalOpenPanel(_ openPanel: NSOpenPanel, forTypes types: [String]?) -> Int {
32 | openPanel.allowedFileTypes = ["car", "app", "framework", "bundle", "plugin"]
33 | openPanel.treatsFilePackagesAsDirectories = true
34 | openPanel.accessoryView = makeSettingsView()
35 |
36 | return openPanel.runModal().rawValue
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Guilherme Rambo
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | - Redistributions of source code must retain the above copyright notice, this
7 | list of conditions and the following disclaimer.
8 |
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 |
13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
17 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/NSImage+BrightnessDetection.m:
--------------------------------------------------------------------------------
1 | //
2 | // NSImage+BrightnessDetection.m
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 03/02/18.
6 | // Copyright © 2018 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import "NSImage+BrightnessDetection.h"
10 |
11 | #define D 299.0
12 | #define K 587.0
13 | #define Z 114.0
14 | #define Y 1000.0
15 |
16 | @implementation NSImage (BrightnessDetection)
17 |
18 | - (CGColorSpaceRef)_actRGBSpace
19 | {
20 | static CGColorSpaceRef _space;
21 | static dispatch_once_t onceToken;
22 | dispatch_once(&onceToken, ^{
23 | _space = CGColorSpaceCreateDeviceRGB();
24 | });
25 |
26 | return _space;
27 | }
28 |
29 | - (CGFloat)averageBrightness
30 | {
31 | CGColorSpaceRef colorSpace = [self _actRGBSpace];
32 |
33 | unsigned char rgba[4];
34 | CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
35 |
36 | if (!context) return 0;
37 |
38 | CGImageRef image = [self CGImageForProposedRect:nil context:nil hints:nil];
39 | if (!image) return 0;
40 |
41 | CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image);
42 |
43 | CGContextRelease(context);
44 |
45 | CGFloat red = rgba[0];
46 | CGFloat green = rgba[1];
47 | CGFloat blue = rgba[2];
48 |
49 | return (((red * D) + (green * K) + (blue * Z)) / Y) / 255.0;
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/ImageCollectionViewItem.xib:
--------------------------------------------------------------------------------
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 |
27 |
--------------------------------------------------------------------------------
/act/Helpers.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | extension String: @retroactive LocalizedError {
4 | public var errorDescription: String? { self }
5 | }
6 |
7 | extension String {
8 | var resolvedPath: String { (self as NSString).expandingTildeInPath }
9 | var resolvedURL: URL { URL(filePath: resolvedPath) }
10 | }
11 |
12 | struct PathValidationFlags: OptionSet {
13 | let rawValue: Int
14 |
15 | static let allowDirectory = PathValidationFlags(rawValue: 1 << 0)
16 | static let requireDirectory = PathValidationFlags(rawValue: 1 << 1)
17 | }
18 |
19 | extension String {
20 | func resolvedExistingFileURL(options: PathValidationFlags = []) throws -> URL {
21 | let url = self.resolvedURL
22 |
23 | var isDir = ObjCBool(false)
24 | guard FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) else {
25 | throw "File doesn't exist at \(url.path)"
26 | }
27 |
28 | if options.contains(.allowDirectory) {
29 | if options.contains(.requireDirectory) {
30 | guard isDir.boolValue else {
31 | throw "Input must be a directory, not a file: \(url.path)"
32 | }
33 | }
34 | } else {
35 | guard !isDir.boolValue else {
36 | throw "Input must be a file, not a directory: \(url.path)"
37 | }
38 | }
39 |
40 | return url
41 | }
42 | }
43 |
44 | extension URL {
45 | var exists: Bool { FileManager.default.fileExists(atPath: path) }
46 | var isExistingDirectory: Bool {
47 | var isDir = ObjCBool(false)
48 | guard FileManager.default.fileExists(atPath: path, isDirectory: &isDir) else { return false }
49 | return isDir.boolValue
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/GridLayout.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridLayout.swift
3 | // MacTube
4 | //
5 | // Created by Guilherme Rambo on 20/01/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class GridLayout: NSCollectionViewFlowLayout {
12 |
13 | fileprivate struct Constants {
14 | static let itemWidth = CGFloat(150.0)
15 | static let itemHeight = CGFloat(150.0)
16 | static let itemPadding = CGFloat(10.0)
17 | }
18 |
19 | override init() {
20 | super.init()
21 |
22 | self.itemSize = NSMakeSize(Constants.itemWidth, Constants.itemHeight)
23 | self.minimumInteritemSpacing = Constants.itemPadding
24 | self.minimumLineSpacing = Constants.itemPadding
25 | self.sectionInset = NSEdgeInsetsMake(Constants.itemPadding, Constants.itemPadding, Constants.itemPadding, Constants.itemPadding)
26 | }
27 |
28 | required init?(coder aDecoder: NSCoder) {
29 | super.init(coder: aDecoder)
30 | }
31 |
32 | override func layoutAttributesForItem(at indexPath: IndexPath) -> NSCollectionViewLayoutAttributes? {
33 | let attributes = super.layoutAttributesForItem(at: indexPath)
34 |
35 | attributes?.zIndex = (indexPath as NSIndexPath).item
36 |
37 |
38 | return attributes
39 | }
40 |
41 | override func layoutAttributesForElements(in rect: NSRect) -> [NSCollectionViewLayoutAttributes] {
42 | let layoutAttributesArray = super.layoutAttributesForElements(in: rect)
43 |
44 | for attr in layoutAttributesArray {
45 | guard let path = attr.indexPath else { continue }
46 | attr.zIndex = (path as NSIndexPath).item
47 | }
48 |
49 | return layoutAttributesArray
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Asset Catalog Tinkerer
2 |
3 | Asset Catalog Tinkerer lets you open asset catalog files (`.car`) and view the images that they contain. You can also copy and export individual images out or export all images from an asset catalog.
4 |
5 | [⬇ Download and contribute with any amount on Gumroad](https://insidegui.gumroad.com/l/AssetCatalogTinkerer)
6 |
7 | [⬇ Download Latest Release](https://github.com/insidegui/AssetCatalogTinkerer/raw/main/releases/AssetCatalogTinkerer_latest.zip)
8 |
9 | You can also install it with [Homebrew](https://brew.sh) by running `brew install asset-catalog-tinkerer`!
10 |
11 | 
12 |
13 | ### Unsupported Asset Types
14 |
15 | Asset Catalog Tinkerer was designed with images in mind, so it doesn't support some of the more modern asset catalog features, such as colors. It may or may not be updated in the future to support those.
16 |
17 | ### QuickLook PlugIn
18 |
19 | The app also includes a QuickLook PlugIn so you can see previews of asset catalogs in QuickLook.
20 |
21 | 
22 |
23 | ---
24 |
25 | ### How to use
26 |
27 | The app can open any `.car` file, usually located within an app's `Resources` directory.
28 |
29 | Once you have an asset catalog opened, you can drag individual assets out or export the entire catalog / selected images to a directory.
30 |
31 | ### Supported file types
32 |
33 | Since version 2.2, Asset Catalog Tinkerer can now read theme store files, not only catalog files.
34 |
35 | Theme store files contain assets for UI components, you can find examples of them in `/System/Library/CoreServices/SystemAppearance.bundle`. The app also supports ProKit's theme stores found inside `ProKit.framework`, `LunaKit.framework` and other folders within pro apps.
36 |
37 | 
38 |
--------------------------------------------------------------------------------
/ACS/Source/AssetCatalogReader.h:
--------------------------------------------------------------------------------
1 | //
2 | // AssetCatalogReader.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | /// The name of the asset
12 | extern NSString *__nonnull const kACSNameKey;
13 | /// An NSImage representing the image for the asset
14 | extern NSString *__nonnull const kACSImageKey;
15 | /// An NSImage representing a smaller version of the asset's image (suitable for thumbnails)
16 | extern NSString *__nonnull const kACSThumbnailKey;
17 | /// An NSString with the suggested filename for the asset
18 | extern NSString *__nonnull const kACSFilenameKey;
19 | /// An NSData containing "image" data for the asset (PNG, SVG, etc)
20 | extern NSString *__nonnull const kACSContentsDataKey;
21 | /// An NSBitmapImageRep containing a bitmap representation of the asset
22 | extern NSString *__nonnull const kACSImageRepKey;
23 |
24 | @interface AssetCatalogReader : NSObject
25 |
26 | @property (nonatomic, assign) NSSize thumbnailSize;
27 |
28 | @property (nonatomic, strong) NSArray *> *__nonnull images;
29 | @property (nonatomic, copy) NSString *__nullable catalogName;
30 | @property (nonatomic, copy) NSError *__nullable error;
31 |
32 | // After reading, contains the total number of assets contained within the asset catalog
33 | @property (readonly) NSUInteger totalNumberOfAssets;
34 |
35 | - (instancetype __nonnull)initWithFileURL:(NSURL * __nonnull)URL;
36 | - (void)readWithCompletionHandler:(void (^__nonnull)(void))callback progressHandler:(void (^__nullable)(double progress))progressCallback;
37 |
38 | // Performs a more lightweight read (used by the QuickLook PlugIn)
39 | - (void)resourceConstrainedReadWithMaxCount:(int)max completionHandler:(void (^__nonnull)(void))callback;
40 |
41 | - (void)cancelReading;
42 |
43 | @property (nonatomic, assign) BOOL cancelled;
44 |
45 | @property (nonatomic, assign) BOOL distinguishCatalogsFromThemeStores;
46 | @property (nonatomic, assign) BOOL ignorePackedAssets;
47 |
48 | @end
49 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/SausagesV3-FG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ACS/Source/ImageExporter.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 |
3 | public struct ImageExporter {
4 | public let images: [[String: NSObject]]
5 |
6 | public init(images: [[String : NSObject]]) {
7 | self.images = images
8 | }
9 |
10 | @available(macOS 10.15, *)
11 | public func export(toDirectoryAt url: URL) async {
12 | await withCheckedContinuation { continuation in
13 | export(toDirectoryAt: url) {
14 | continuation.resume()
15 | }
16 | }
17 | }
18 |
19 | public func export(toDirectoryAt url: URL, completionHandler: (() -> Void)? = nil) {
20 | DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
21 | images.forEach { image in
22 | guard let filename = image[kACSFilenameKey] as? String else { return }
23 |
24 | var pathComponents = url.pathComponents
25 |
26 | pathComponents.append(filename)
27 |
28 | guard let pngData = image[kACSContentsDataKey] as? Data else { return }
29 |
30 | let path = self.nextAvailablePath(filePath: NSString.path(withComponents: pathComponents) as String)
31 | do {
32 | try pngData.write(to: URL(fileURLWithPath: path), options: .atomic)
33 | } catch {
34 | NSLog("ERROR: Unable to write \(filename) to \(path); \(error)")
35 | }
36 | }
37 |
38 | DispatchQueue.main.async {
39 | completionHandler?()
40 | }
41 | }
42 | }
43 |
44 | private func nextAvailablePath(filePath: String) -> String {
45 | let fileManager = FileManager.default
46 | let originalURL = URL(fileURLWithPath: filePath)
47 | let directory = originalURL.deletingLastPathComponent()
48 | let baseFilename = originalURL.deletingPathExtension().lastPathComponent
49 | let fileExtension = originalURL.pathExtension
50 |
51 | var counter = 1
52 | var newFilename = baseFilename
53 |
54 | while fileManager.fileExists(atPath: directory.appendingPathComponent(newFilename).appendingPathExtension(fileExtension).path) && counter < 100 {
55 | newFilename = "\(baseFilename)_\(counter)"
56 | counter += 1
57 | }
58 |
59 | return directory.appendingPathComponent(newFilename).appendingPathExtension(fileExtension).path
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AssetCatalogDocument.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Document.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import ACS
11 |
12 | class AssetCatalogDocument: NSDocument {
13 |
14 | fileprivate var reader: AssetCatalogReader!
15 |
16 | override init() {
17 | super.init()
18 | // Add your subclass-specific initialization here.
19 | }
20 |
21 | override func makeWindowControllers() {
22 | let storyboard = NSStoryboard(name: "Main", bundle: nil)
23 | let windowController = storyboard.instantiateController(withIdentifier: "Document Window Controller") as! NSWindowController
24 | self.addWindowController(windowController)
25 |
26 | if #available(OSX 10.12, *) {
27 | windowController.window?.tabbingIdentifier = "ACTWindow"
28 | windowController.window?.tabbingMode = .preferred
29 | }
30 |
31 | NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: windowController.window, queue: OperationQueue.main) { _ in
32 | if self.reader != nil { self.reader.cancelReading() }
33 | }
34 | }
35 |
36 | fileprivate var imagesViewController: ImagesViewController? {
37 | return windowControllers.first?.contentViewController as? ImagesViewController
38 | }
39 |
40 | override func read(from url: URL, ofType typeName: String) throws {
41 | reader = AssetCatalogReader(fileURL: url)
42 | reader.thumbnailSize = NSSize(width: 138.0, height: 138.0)
43 |
44 | reader.distinguishCatalogsFromThemeStores = Preferences.shared[.distinguishCatalogsAndThemeStores]
45 | reader.ignorePackedAssets = Preferences.shared[.ignorePackedAssets]
46 |
47 | reader.read(completionHandler: didFinishReading, progressHandler: updateProgress)
48 | }
49 |
50 | fileprivate func updateProgress(_ progress: Double) {
51 | imagesViewController?.loadProgress = progress
52 | }
53 |
54 | fileprivate func didFinishReading() {
55 | guard !reader.cancelled else { return }
56 |
57 | if let error = reader.error {
58 | imagesViewController?.error = error as NSError?
59 | } else {
60 | imagesViewController?.images = reader.images
61 | }
62 | }
63 |
64 | }
65 |
66 |
--------------------------------------------------------------------------------
/AssetCatalog/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDocumentTypes
8 |
9 |
10 | CFBundleTypeRole
11 | QLGenerator
12 | LSItemContentTypes
13 |
14 | com.apple.assetcatalog
15 |
16 |
17 |
18 | CFBundleExecutable
19 | $(EXECUTABLE_NAME)
20 | CFBundleIdentifier
21 | $(PRODUCT_BUNDLE_IDENTIFIER)
22 | CFBundleInfoDictionaryVersion
23 | 6.0
24 | CFBundleName
25 | $(PRODUCT_NAME)
26 | CFBundleShortVersionString
27 | $(MARKETING_VERSION)
28 | CFBundleVersion
29 | $(CURRENT_PROJECT_VERSION)
30 | CFPlugInDynamicRegisterFunction
31 |
32 | CFPlugInDynamicRegistration
33 | NO
34 | CFPlugInFactories
35 |
36 | 6E610ECE-9B7E-4ED8-89D1-EA191DD2EA00
37 | QuickLookGeneratorPluginFactory
38 |
39 | CFPlugInTypes
40 |
41 | 5E2D9680-5022-40FA-B806-43349622E5B9
42 |
43 | 6E610ECE-9B7E-4ED8-89D1-EA191DD2EA00
44 |
45 |
46 | CFPlugInUnloadFunction
47 |
48 | NSHumanReadableCopyright
49 | Copyright © 2017 Guilherme Rambo. All rights reserved.
50 | QLNeedsToBeRunInMainThread
51 |
52 | QLPreviewHeight
53 | 600
54 | QLPreviewWidth
55 | 800
56 | QLSupportsConcurrentRequests
57 |
58 | QLThumbnailMinimumSize
59 | 17
60 | UTImportedTypeDeclarations
61 |
62 |
63 | UTTypeConformsTo
64 |
65 | public.data
66 |
67 | UTTypeDescription
68 | Asset Catalog
69 | UTTypeIdentifier
70 | com.apple.assetcatalog
71 | UTTypeTagSpecification
72 |
73 | public.filename-extension
74 |
75 | car
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/ProgressBar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProgressBar.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | open class ProgressBar: NSView {
12 |
13 | override public init(frame frameRect: NSRect) {
14 | super.init(frame: frameRect)
15 |
16 | commonInit()
17 | }
18 |
19 | required public init?(coder: NSCoder) {
20 | super.init(coder: coder)
21 |
22 | commonInit()
23 | }
24 |
25 | override open func awakeFromNib() {
26 | super.awakeFromNib()
27 |
28 | commonInit()
29 | }
30 |
31 | open var tintColor: NSColor? {
32 | didSet {
33 | CATransaction.begin()
34 | CATransaction.setAnimationDuration(0.0)
35 | progressLayer.backgroundColor = tintColor?.cgColor
36 | CATransaction.commit()
37 | }
38 | }
39 |
40 | open var progress: Double = 0.0 {
41 | didSet {
42 | let animated = oldValue < progress
43 |
44 | DispatchQueue.main.async { self.updateProgressLayer(animated) }
45 | }
46 | }
47 |
48 | fileprivate var progressLayer: CALayer!
49 |
50 | fileprivate func commonInit() {
51 | guard progressLayer == nil else { return }
52 |
53 | wantsLayer = true
54 | layer = CALayer()
55 |
56 | progressLayer = CALayer()
57 | progressLayer.backgroundColor = tintColor?.cgColor
58 | progressLayer.frame = NSRect(x: 0.0, y: 0.0, width: 0.0, height: bounds.height)
59 | layer!.addSublayer(progressLayer)
60 | progressLayer.autoresizingMask = [.layerWidthSizable, .layerHeightSizable]
61 |
62 | updateProgressLayer()
63 | }
64 |
65 | fileprivate var widthForProgressLayer: CGFloat {
66 | return bounds.width * CGFloat(progress)
67 | }
68 |
69 | fileprivate func updateProgressLayer(_ animated: Bool = true) {
70 | CATransaction.begin()
71 | CATransaction.setAnimationDuration(animated ? 0.4 : 0.0)
72 | var frame = progressLayer.frame
73 | frame.size.width = widthForProgressLayer
74 | progressLayer.frame = frame
75 |
76 | if progress >= 0.99 {
77 | progressLayer.opacity = 0.0
78 | } else {
79 | progressLayer.opacity = 1.0
80 | }
81 |
82 | CATransaction.commit()
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/act/AssetCatalogTinkererCLI.swift:
--------------------------------------------------------------------------------
1 | import Cocoa
2 | import ACS
3 | import ArgumentParser
4 |
5 | struct ACTOptions: ParsableArguments {
6 | @Option(name: [.short, .long], help: "Path to input asset catalog.")
7 | var input: String
8 | }
9 |
10 | @main
11 | struct AssetCatalogTinkererCLI: AsyncParsableCommand {
12 | static let configuration = CommandConfiguration(
13 | commandName: "act",
14 | abstract: "Command-line interface for interacting with asset catalog (.car) files.",
15 | subcommands: [
16 | InfoCommand.self,
17 | ExtractCommand.self
18 | ]
19 | )
20 |
21 | @OptionGroup
22 | var options: ACTOptions
23 | }
24 |
25 | private extension AssetCatalogReader {
26 | static func images(from url: URL) async throws -> [[String: NSObject]] {
27 | let reader = AssetCatalogReader(fileURL: url)
28 |
29 | await withCheckedContinuation { continuation in
30 | reader.read {
31 | continuation.resume()
32 | } progressHandler: { progress in
33 | fputs("Reading... \(String(format: "%02.0f%%", progress * 100))\n", stderr)
34 | }
35 | }
36 |
37 | return reader.images
38 | }
39 | }
40 |
41 | struct InfoCommand: AsyncParsableCommand {
42 | static let configuration = CommandConfiguration(
43 | commandName: "info",
44 | abstract: "Retrieves information about assets in an asset catalog file,"
45 | )
46 |
47 | @OptionGroup
48 | var options: ACTOptions
49 |
50 | func run() async throws {
51 | let inputURL = try options.input.resolvedExistingFileURL()
52 |
53 | let images = try await AssetCatalogReader.images(from: inputURL)
54 |
55 | for image in images {
56 | guard let filename = image[kACSFilenameKey] as? String else { continue }
57 | print(filename)
58 | }
59 | }
60 | }
61 |
62 | struct ExtractCommand: AsyncParsableCommand {
63 | static let configuration = CommandConfiguration(
64 | commandName: "extract",
65 | abstract: "Extracts assets from an asset catalog file."
66 | )
67 |
68 | @OptionGroup
69 | var options: ACTOptions
70 |
71 | @Option(name: [.short, .long], help: "Path to directory where to save the extracted assets. Will be created if it doesn't exist yet.")
72 | var output: String
73 |
74 | func run() async throws {
75 | let inputURL = try options.input.resolvedExistingFileURL()
76 | let outputURL = output.resolvedURL
77 |
78 | if !FileManager.default.fileExists(atPath: outputURL.path) {
79 | try FileManager.default.createDirectory(at: outputURL, withIntermediateDirectories: false)
80 | }
81 |
82 | guard outputURL.isExistingDirectory else {
83 | throw "Output must be a directory: \(outputURL.path)"
84 | }
85 |
86 | let images = try await AssetCatalogReader.images(from: inputURL)
87 |
88 | guard !images.isEmpty else {
89 | throw "Asset catalog had no images at \(inputURL.path)"
90 | }
91 |
92 | let exporter = ImageExporter(images: images)
93 |
94 | await exporter.export(toDirectoryAt: outputURL)
95 |
96 | print("Exported \(images.count) images to \(outputURL.path)\n")
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer.xcodeproj/xcshareddata/xcschemes/Asset Catalog Tinkerer.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
43 |
45 |
51 |
52 |
53 |
54 |
60 |
62 |
68 |
69 |
70 |
71 |
73 |
74 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDocumentTypes
8 |
9 |
10 | CFBundleTypeExtensions
11 |
12 | car
13 |
14 | CFBundleTypeIconFile
15 |
16 | CFBundleTypeName
17 | Asset Catalog
18 | CFBundleTypeRole
19 | Viewer
20 | LSItemContentTypes
21 |
22 | com.apple.assetcatalog
23 |
24 | LSTypeIsPackage
25 | 0
26 | NSDocumentClass
27 | $(PRODUCT_MODULE_NAME).AssetCatalogDocument
28 |
29 |
30 | CFBundleTypeExtensions
31 |
32 | app
33 | framework
34 | bundle
35 | plugin
36 |
37 | CFBundleTypeName
38 | Bundle
39 | CFBundleTypeRole
40 | Viewer
41 | LSItemContentTypes
42 |
43 | com.apple.bundle
44 |
45 | LSTypeIsPackage
46 | 1
47 | NSDocumentClass
48 | $(PRODUCT_MODULE_NAME).AssetCatalogDocument
49 |
50 |
51 | CFBundleExecutable
52 | $(EXECUTABLE_NAME)
53 | CFBundleIconFile
54 |
55 | CFBundleIdentifier
56 | $(PRODUCT_BUNDLE_IDENTIFIER)
57 | CFBundleInfoDictionaryVersion
58 | 6.0
59 | CFBundleName
60 | $(PRODUCT_NAME)
61 | CFBundlePackageType
62 | APPL
63 | CFBundleShortVersionString
64 | $(MARKETING_VERSION)
65 | CFBundleSignature
66 | ????
67 | CFBundleVersion
68 | $(CURRENT_PROJECT_VERSION)
69 | LSMinimumSystemVersion
70 | $(MACOSX_DEPLOYMENT_TARGET)
71 | NSHumanReadableCopyright
72 | Copyright © 2016–2021 Guilherme Rambo. All rights reserved.
73 | NSMainStoryboardFile
74 | Main
75 | NSPrincipalClass
76 | NSApplication
77 | SUEnableAutomaticChecks
78 |
79 | SUFeedURL
80 | https://raw.githubusercontent.com/insidegui/AssetCatalogTinkerer/main/releases/appcast.xml
81 | SUPublicEDKey
82 | $(ACT_SPARKLE_PUBLIC_ED_KEY)
83 | SUScheduledCheckInterval
84 | 86400
85 | UTExportedTypeDeclarations
86 |
87 |
88 | UTTypeConformsTo
89 |
90 | public.data
91 |
92 | UTTypeDescription
93 | Asset Catalog
94 | UTTypeIdentifier
95 | com.apple.assetcatalog
96 | UTTypeTagSpecification
97 |
98 | public.filename-extension
99 |
100 | car
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/Assets/SausagesV3-BG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/SausagesV4-BG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV7.icon/Assets/SausagesV4-BG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/Assets/SausagesV4-FG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Resources/AppIconV7.icon/Assets/SausagesV4-FG.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer.xcodeproj/xcshareddata/xcschemes/act.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
16 |
22 |
23 |
24 |
25 |
26 |
32 |
33 |
44 |
46 |
52 |
53 |
54 |
55 |
58 |
59 |
62 |
63 |
66 |
67 |
70 |
71 |
74 |
75 |
76 |
77 |
83 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/Assets/SausagesV2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ACS/Private Headers/CoreUI.h:
--------------------------------------------------------------------------------
1 | //
2 | // CoreUI.h
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/09/15.
6 | // Copyright (c) 2014 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "CoreSVG.h"
11 |
12 | @interface CUINamedData : NSObject
13 |
14 | @end
15 |
16 | @interface CUINamedImage : NSObject
17 |
18 | @property (copy, nonatomic) NSString *name;
19 | @property (readonly, nonatomic) int exifOrientation;
20 | @property (readonly, nonatomic) BOOL isStructured;
21 | @property (readonly, nonatomic) NSInteger templateRenderingMode;
22 | @property (readonly, nonatomic) BOOL isTemplate;
23 | @property (readonly, nonatomic) BOOL isVectorBased;
24 | @property (readonly, nonatomic) BOOL hasSliceInformation;
25 | @property (readonly, nonatomic) NSInteger resizingMode;
26 | @property (readonly, nonatomic) int blendMode;
27 | @property (readonly, nonatomic) CGFloat opacity;
28 | @property (readonly, nonatomic) NSInteger imageType;
29 | @property (readonly, nonatomic) CGFloat scale;
30 | @property (readonly, nonatomic) NSSize size;
31 | @property (readonly, nonatomic) CGImageRef image;
32 |
33 | @end
34 |
35 | #define kCoreThemeStateNone -1
36 |
37 | NSString *themeStateNameForThemeState(long long state) {
38 | switch (state) {
39 | case 0:
40 | return @"Normal";
41 | break;
42 | case 1:
43 | return @"Rollover";
44 | break;
45 | case 2:
46 | return @"Pressed";
47 | break;
48 | case 3:
49 | return @"Inactive";
50 | break;
51 | case 4:
52 | return @"Disabled";
53 | break;
54 | case 5:
55 | return @"DeeplyPressed";
56 | break;
57 | }
58 |
59 | return nil;
60 | }
61 |
62 | struct _renditionkeytoken {
63 | unsigned short identifier;
64 | unsigned short value;
65 | };
66 |
67 | @interface CUIRenditionKey: NSObject
68 |
69 | @property (readonly) struct _renditionkeytoken *keyList;
70 |
71 | @property (readonly) long long themeScale;
72 | @property (readonly) long long themeState;
73 | @property (readonly) long long themeDirection;
74 | @property (readonly) long long themeSize;
75 | @property (readonly) long long themeElement;
76 | @property (readonly) long long themePart;
77 | @property (readonly) long long themeGlyphWeight;
78 | @property (readonly) long long themeGlyphSize;
79 |
80 | @end
81 |
82 | @interface CUIThemeRendition: NSObject
83 |
84 | @property (nonatomic, readonly) CGFloat scale;
85 | @property (nonatomic, readonly) NSString *name;
86 | @property (nonatomic, readonly) NSData *data;
87 | @property (nonatomic, readonly) CGImageRef unslicedImage;
88 | @property (nonatomic, readonly) BOOL isVectorBased;
89 | @property (nonatomic, readonly) struct CGSVGDocument *svgDocument;
90 | @property (nonatomic, readonly) long long vectorGlyphRenderingMode;
91 |
92 | @end
93 |
94 | @interface CUICommonAssetStorage: NSObject
95 | {
96 | struct _carheader {
97 | unsigned int _field1;
98 | unsigned int _field2;
99 | unsigned int _field3;
100 | unsigned int _field4;
101 | unsigned int _field5;
102 | char _field6[128];
103 | char _field7[256];
104 | unsigned char _field8[16];
105 | unsigned int _field9;
106 | unsigned int _field10;
107 | unsigned int _field11;
108 | unsigned int _field12;
109 | } *_header;
110 | }
111 |
112 | @property (readonly) NSArray *allAssetKeys;
113 |
114 | @end
115 |
116 | @interface CUIStructuredThemeStore : NSObject
117 |
118 | - (instancetype)initWithURL:(NSURL *)URL;
119 | - (instancetype)initWithPath:(NSString *)path;
120 | - (NSData *)lookupAssetForKey:(struct _renditionkeytoken *)key;
121 | - (CUIThemeRendition *)renditionWithKey:(const struct _renditionkeytoken *)key;
122 |
123 | @property (readonly) CUICommonAssetStorage *themeStore;
124 |
125 | @end
126 |
127 | @interface CUICatalog : NSObject
128 |
129 | + (instancetype)systemUICatalog;
130 | + (instancetype)defaultUICatalog;
131 |
132 | - (instancetype)initWithURL:(NSURL *)url error:(NSError **)outError;
133 |
134 | @property (nonatomic, readonly) NSArray *allImageNames;
135 | - (CUINamedImage *)imageWithName:(NSString *)name scaleFactor:(CGFloat)scaleFactor;
136 |
137 |
138 | - (CUIStructuredThemeStore *)_themeStore;
139 |
140 | @end
141 |
142 | typedef NS_ENUM(NSInteger, UIImageSymbolScale) {
143 | UIImageSymbolScaleDefault = -1, // use the system default size
144 | UIImageSymbolScaleUnspecified = 0, // allow the system to pick a size based on the context
145 | UIImageSymbolScaleSmall = 1,
146 | UIImageSymbolScaleMedium,
147 | UIImageSymbolScaleLarge,
148 | };
149 |
150 | typedef NS_ENUM(NSInteger, UIImageSymbolWeight) {
151 | UIImageSymbolWeightUnspecified = 0,
152 | UIImageSymbolWeightUltraLight = 1,
153 | UIImageSymbolWeightThin,
154 | UIImageSymbolWeightLight,
155 | UIImageSymbolWeightRegular,
156 | UIImageSymbolWeightMedium,
157 | UIImageSymbolWeightSemibold,
158 | UIImageSymbolWeightBold,
159 | UIImageSymbolWeightHeavy,
160 | UIImageSymbolWeightBlack
161 | };
162 |
163 | // This is a made up enum
164 | typedef NS_ENUM(NSInteger, UIImageSymbolRenderingMode) {
165 | UIImageSymbolRenderingModeAutomatic,
166 | UIImageSymbolRenderingModeTemplate,
167 | UIImageSymbolRenderingModeMulticolor,
168 | UIImageSymbolRenderingModeHierarchical,
169 | };
170 |
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/Assets/Sausages.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/Assets/Sausages.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/QuickLookableCollectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // QuickLookableCollectionView.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 28/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import Quartz
11 |
12 | class QuickLookableCollectionView: NSCollectionView {
13 |
14 | typealias PasteboardWriterProvidingBlock = (Set) -> [NSPasteboardWriting]
15 |
16 | var provideQuickLookPasteboardWriters: PasteboardWriterProvidingBlock?
17 |
18 | override func keyDown(with theEvent: NSEvent) {
19 | // spacebar
20 | if theEvent.keyCode == 49 {
21 | showQuickLookPreview(self);
22 | return;
23 | }
24 |
25 | super.keyDown(with: theEvent)
26 | }
27 |
28 | fileprivate lazy var quickLookHandler = QuickLookableCollectionViewPreviewHandler()
29 |
30 | @IBAction func showQuickLookPreview(_ sender: AnyObject) {
31 | guard selectionIndexPaths.count > 0 else { return }
32 |
33 | quickLookHandler.pasteboard = NSPasteboard(name: NSPasteboard.Name(rawValue: "CollectionViewQuickLook"))
34 | quickLookHandler.collectionView = self
35 |
36 | let panel = QLPreviewPanel.shared()
37 |
38 | if (QLPreviewPanel.sharedPreviewPanelExists() && (panel?.isVisible)!) {
39 | panel?.orderOut(self)
40 | } else {
41 | panel?.makeKeyAndOrderFront(self)
42 | panel?.reloadData()
43 | }
44 | }
45 |
46 | override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
47 | return true
48 | }
49 |
50 | override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
51 | writeSelectionToQuickLookPasteboard()
52 |
53 | panel.delegate = quickLookHandler
54 | panel.dataSource = quickLookHandler
55 | }
56 |
57 | override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
58 | panel.delegate = nil
59 | panel.dataSource = nil
60 | }
61 |
62 | fileprivate func writeSelectionToQuickLookPasteboard() {
63 | guard let items = provideQuickLookPasteboardWriters?(selectionIndexPaths) else { return }
64 |
65 | quickLookHandler.pasteboard.clearContents()
66 | quickLookHandler.pasteboard.writeObjects(items)
67 | }
68 |
69 | private var selectionObservation: NSKeyValueObservation?
70 |
71 | fileprivate var isWindowClosing = false
72 |
73 | override func viewWillMove(toWindow newWindow: NSWindow?) {
74 | super.viewWillMove(toWindow: newWindow)
75 |
76 | guard newWindow != nil else { return }
77 |
78 | NotificationCenter.default.addObserver(forName: NSWindow.willCloseNotification, object: newWindow, queue: .main) { [weak self] _ in
79 | self?.isWindowClosing = true
80 |
81 | QLPreviewPanel.shared().close()
82 | }
83 |
84 | // Handles updating the QuickLook panel when selection changes.
85 | selectionObservation = observe(\.selectionIndexPaths) { [weak self] _, _ in
86 | guard let self = self else { return }
87 |
88 | self.updateQuickLookWithNewSelectionIfNeeded()
89 | }
90 | }
91 |
92 | private func updateQuickLookWithNewSelectionIfNeeded() {
93 | guard QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible else { return }
94 |
95 | writeSelectionToQuickLookPasteboard()
96 | QLPreviewPanel.shared().reloadData()
97 | }
98 |
99 | }
100 |
101 | @objc private class QuickLookableCollectionViewPreviewHandler: NSObject, QLPreviewPanelDataSource, QLPreviewPanelDelegate {
102 |
103 | var pasteboard: NSPasteboard!
104 | var collectionView: QuickLookableCollectionView!
105 |
106 | var previewItems: [URL] {
107 | pasteboard.readObjects(forClasses: [NSURL.self], options: nil)?.compactMap { $0 as? URL } ?? []
108 | }
109 |
110 | @objc fileprivate func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
111 | return previewItems.count
112 | }
113 |
114 | @objc fileprivate func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
115 | guard previewItems.count > 0 else { return nil }
116 |
117 | return previewItems[index] as QLPreviewItem?
118 | }
119 |
120 | @objc fileprivate func previewPanel(_ panel: QLPreviewPanel!, handle event: NSEvent!) -> Bool {
121 | if event.type == .keyDown {
122 | collectionView.keyDown(with: event)
123 | return true
124 | }
125 |
126 | return false
127 | }
128 |
129 | @objc fileprivate func previewPanel(_ panel: QLPreviewPanel!, sourceFrameOnScreenFor item: QLPreviewItem!) -> NSRect {
130 | guard let window = collectionView.window else { return .zero }
131 | guard !collectionView.isWindowClosing else { return .zero }
132 |
133 | let combinedRect = collectionView.selectionIndexes.map { return collectionView.frameForItem(at: $0) }.reduce(NSZeroRect) { NSUnionRect($0, $1) }
134 |
135 | var preliminaryRect = collectionView.enclosingScrollView!.convert(combinedRect, to: nil)
136 | preliminaryRect.origin.y += collectionView.enclosingScrollView!.contentView.bounds.origin.y
137 | let rect = window.convertToScreen(preliminaryRect)
138 |
139 | // Prevent ludicrously tall rect from causing glitchy panel open,
140 | // should probably fix the rect calculation instead but I'm lazy.
141 | guard rect.height < window.frame.height else { return .zero }
142 |
143 | return rect
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/ImageCollectionViewItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCollectionViewItem.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension NSTextField {
12 |
13 | class func makeLabel() -> NSTextField {
14 | let l = NSTextField(frame: .zero)
15 |
16 | l.translatesAutoresizingMaskIntoConstraints = false
17 | l.isBordered = false
18 | l.isBezeled = false
19 | l.isEditable = false
20 | l.isSelectable = false
21 | l.drawsBackground = false
22 | l.textColor = .labelColor
23 |
24 | return l
25 | }
26 |
27 | }
28 |
29 | class ImageCollectionViewItem: NSCollectionViewItem {
30 |
31 | private struct Constants {
32 | static let brightImageThreshold: CGFloat = 0.8
33 | }
34 |
35 | var image: [String: NSObject]? {
36 | didSet {
37 | updateUI()
38 | }
39 | }
40 |
41 | fileprivate struct Colors {
42 | static let background = NSColor.white
43 | static let brightImageBackground = NSColor(calibratedWhite: 0.9, alpha: 1)
44 | static let border = NSColor(calibratedWhite: 0.9, alpha: 1.0)
45 | static let selectedBackground = NSColor(calibratedRed:0, green:0.496, blue:1, alpha:1)
46 | static let selectedBorder = NSColor(calibratedRed:0.019, green:0.316, blue:0.687, alpha:1)
47 | static let text = NSColor(calibratedRed:0, green:0.496, blue:1, alpha:1)
48 | static let selectedText = NSColor.white
49 | }
50 |
51 | private var optimalBackgroundColor: NSColor = Colors.background
52 |
53 | override var isSelected: Bool {
54 | didSet {
55 | view.layer?.backgroundColor = isSelected ? Colors.selectedBackground.cgColor : optimalBackgroundColor.cgColor
56 | view.layer?.borderColor = isSelected ? Colors.selectedBorder.cgColor : Colors.border.cgColor
57 | nameLabel.textColor = isSelected ? Colors.selectedText : Colors.text
58 | }
59 | }
60 |
61 | override func viewDidLoad() {
62 | super.viewDidLoad()
63 |
64 | buildUI()
65 | updateUI()
66 | }
67 |
68 | fileprivate lazy var catalogImageView: NSImageView = {
69 | let iv = NSImageView(frame: NSZeroRect)
70 |
71 | iv.translatesAutoresizingMaskIntoConstraints = false
72 | iv.imageFrameStyle = .none
73 | iv.imageScaling = .scaleProportionallyDown
74 | iv.imageAlignment = .alignCenter
75 |
76 | return iv
77 | }()
78 |
79 | fileprivate lazy var nameLabel: NSTextField = {
80 | let l = NSTextField.makeLabel()
81 |
82 | l.font = NSFont.systemFont(ofSize: 11.0, weight: .medium)
83 | l.textColor = Colors.text
84 | l.lineBreakMode = .byTruncatingMiddle
85 |
86 | return l
87 | }()
88 |
89 | fileprivate lazy var brightnessDebugLabel: NSTextField = {
90 | let l = NSTextField.makeLabel()
91 |
92 | l.font = NSFont.systemFont(ofSize: 11.0, weight: .medium)
93 | l.isHidden = !Preferences.shared[.debugImageBrightness]
94 |
95 | return l
96 | }()
97 |
98 | fileprivate func buildUI() {
99 | defer { installBrightnessDebugLabelIfNeeded() }
100 |
101 | guard catalogImageView.superview == nil else { return }
102 |
103 | view.wantsLayer = true
104 | view.layer = CALayer()
105 |
106 | catalogImageView.frame = view.bounds
107 | view.addSubview(catalogImageView)
108 |
109 | catalogImageView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
110 | catalogImageView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
111 | catalogImageView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
112 | catalogImageView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
113 |
114 | view.layer!.borderWidth = 1.0
115 | view.layer!.borderColor = Colors.border.cgColor
116 | view.layer!.cornerRadius = 4.0
117 |
118 | view.addSubview(nameLabel)
119 | nameLabel.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -2.0).isActive = true
120 | nameLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
121 | nameLabel.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, multiplier: 1.0, constant: -12.0).isActive = true
122 | }
123 |
124 | private func installBrightnessDebugLabelIfNeeded() {
125 | guard Preferences.shared[.debugImageBrightness], brightnessDebugLabel.superview == nil else { return }
126 |
127 | view.addSubview(brightnessDebugLabel)
128 | brightnessDebugLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2.0).isActive = true
129 | brightnessDebugLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 4.0).isActive = true
130 | }
131 |
132 | fileprivate func updateUI() {
133 | guard let imageData = image , isViewLoaded else { return }
134 | guard let image = imageData[kACSThumbnailKey] as? NSImage else { return }
135 | let name = imageData[kACSNameKey] as! String
136 | let filename = imageData[kACSFilenameKey] as! String
137 |
138 | let brightness = image.averageBrightness()
139 |
140 | catalogImageView.image = image
141 | nameLabel.stringValue = name
142 | view.toolTip = filename
143 |
144 | if Preferences.shared[.debugImageBrightness] {
145 | brightnessDebugLabel.stringValue = String(format: "B: %.1f", brightness)
146 | }
147 |
148 | if brightness >= Constants.brightImageThreshold {
149 | optimalBackgroundColor = Colors.brightImageBackground
150 | } else {
151 | optimalBackgroundColor = Colors.background
152 | }
153 |
154 | view.layer?.backgroundColor = optimalBackgroundColor.cgColor
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/ImagesCollectionViewDataProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImagesCollectionViewDataProvider.swift
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 27/03/16.
6 | // Copyright © 2016 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 | import UniformTypeIdentifiers
11 | import ACS
12 |
13 | extension NSUserInterfaceItemIdentifier {
14 | static let imageItemIdentifier = NSUserInterfaceItemIdentifier("ImageItemIdentifier")
15 | }
16 |
17 | class ImagesCollectionViewDataProvider: NSObject, NSCollectionViewDataSource, NSCollectionViewDelegate {
18 |
19 | fileprivate struct Constants {
20 | static let nibName = "ImageCollectionViewItem"
21 |
22 | }
23 |
24 | var collectionView: NSCollectionView! {
25 | didSet {
26 | collectionView.setDraggingSourceOperationMask(.copy, forLocal: false)
27 |
28 | collectionView.delegate = self
29 | collectionView.dataSource = self
30 |
31 | collectionView.collectionViewLayout = GridLayout()
32 |
33 | let nib = NSNib(nibNamed: Constants.nibName, bundle: nil)
34 | collectionView.register(nib, forItemWithIdentifier: .imageItemIdentifier)
35 | }
36 | }
37 |
38 | var images = [[String: NSObject]]() {
39 | didSet {
40 | filteredImages = filterImagesWithCurrentSearchTerm()
41 | collectionView.reloadData()
42 | }
43 | }
44 |
45 | var searchTerm = "" {
46 | didSet {
47 | filteredImages = filterImagesWithCurrentSearchTerm()
48 | collectionView.reloadData()
49 | }
50 | }
51 |
52 | var filteredImages = [[String: NSObject]]()
53 |
54 | fileprivate func filterImagesWithCurrentSearchTerm() -> [[String: NSObject]] {
55 | guard !searchTerm.isEmpty else { return images }
56 |
57 | let predicate = NSPredicate(format: "name contains[cd] %@", searchTerm)
58 | return (images as NSArray).filtered(using: predicate) as! [[String: NSObject]]
59 | }
60 |
61 | func numberOfSections(in collectionView: NSCollectionView) -> Int {
62 | return 1
63 | }
64 |
65 | func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
66 | let item = collectionView.makeItem(withIdentifier: .imageItemIdentifier, for: indexPath) as! ImageCollectionViewItem
67 |
68 | item.image = filteredImages[(indexPath as NSIndexPath).item]
69 |
70 | return item
71 | }
72 |
73 | func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
74 | return filteredImages.count
75 | }
76 |
77 | private func canPerformPasteboardOperation(at indexPath: IndexPath) -> Bool {
78 | assert(indexPath.section == 0, "Only a single section is supported for now")
79 | assert(indexPath.item < filteredImages.count, "Invalid item index")
80 |
81 | return indexPath.item < filteredImages.count
82 | }
83 |
84 | func collectionView(_ collectionView: NSCollectionView, pasteboardWriterForItemAt indexPath: IndexPath) -> NSPasteboardWriting? {
85 | guard canPerformPasteboardOperation(at: indexPath) else { return nil }
86 |
87 | // TODO: Use correct file type/extension instead of hardcoding png.
88 | let fileExtension = "png"
89 |
90 | let provider: NSFilePromiseProvider
91 |
92 | if #available(macOS 11.0, *) {
93 | let typeIdentifier = UTType(filenameExtension: fileExtension)
94 | provider = NSFilePromiseProvider(fileType: typeIdentifier!.identifier, delegate: self)
95 | } else {
96 | let typeIdentifier =
97 | UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension as CFString, nil)
98 | provider = NSFilePromiseProvider(fileType: typeIdentifier!.takeRetainedValue() as String, delegate: self)
99 | }
100 |
101 | provider.userInfo = self.filteredImages[indexPath.item]
102 |
103 | return provider
104 | }
105 |
106 | private lazy var filePromiseQueue = OperationQueue()
107 |
108 | private let copyQueue = DispatchQueue(label: "Copy", qos: .userInteractive)
109 |
110 | func generalPasteboardWriter(at indexPath: IndexPath) -> NSPasteboardWriting? {
111 | guard canPerformPasteboardOperation(at: indexPath) else { return nil }
112 |
113 | let image = filteredImages[indexPath.item]
114 |
115 | guard let filename = image[kACSFilenameKey] as? String else { return nil }
116 | guard let data = image[kACSContentsDataKey] as? Data else { return nil }
117 |
118 | let tempURL = URL(fileURLWithPath: NSTemporaryDirectory())
119 | .appendingPathComponent(filename)
120 |
121 | do {
122 | try data.write(to: tempURL, options: .atomic)
123 |
124 | return tempURL as NSURL
125 | } catch {
126 | assertionFailure("Failed to write temporary URL for pasteboard: \(String(describing: error))")
127 | return nil
128 | }
129 | }
130 |
131 | }
132 |
133 | extension ImagesCollectionViewDataProvider: NSFilePromiseProviderDelegate {
134 |
135 | func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
136 | guard let image = filePromiseProvider.userInfo as? [String: NSObject] else {
137 | return ""
138 | }
139 |
140 | guard let filename = image[kACSFilenameKey] as? String else { return "" }
141 |
142 | return filename
143 | }
144 |
145 | func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue { filePromiseQueue }
146 |
147 | func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: @escaping (Error?) -> Void) {
148 | guard let image = filePromiseProvider.userInfo as? [String: NSObject] else {
149 | completionHandler(nil)
150 | return
151 | }
152 |
153 | guard let data = image[kACSContentsDataKey] as? Data else {
154 | completionHandler(nil)
155 | return
156 | }
157 |
158 | do {
159 | try data.write(to: url)
160 |
161 | completionHandler(nil)
162 | } catch let error {
163 | completionHandler(error)
164 | }
165 | }
166 |
167 | }
168 |
--------------------------------------------------------------------------------
/Resources/AppIconV2.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blur-material" : 0.9,
6 | "hidden" : false,
7 | "layers" : [
8 | {
9 | "fill-specializations" : [
10 | {
11 | "value" : "automatic"
12 | },
13 | {
14 | "appearance" : "dark",
15 | "value" : {
16 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
17 | }
18 | }
19 | ],
20 | "glass" : true,
21 | "image-name" : "Boxes.svg",
22 | "name" : "Boxes",
23 | "position" : {
24 | "scale" : 1.24,
25 | "translation-in-points" : [
26 | 0,
27 | 0
28 | ]
29 | }
30 | }
31 | ],
32 | "name" : "Layer4",
33 | "shadow" : {
34 | "kind" : "layer-color",
35 | "opacity" : 0.5
36 | },
37 | "specular" : true,
38 | "translucency-specializations" : [
39 | {
40 | "value" : {
41 | "enabled" : true,
42 | "value" : 0.7
43 | }
44 | },
45 | {
46 | "appearance" : "dark",
47 | "value" : {
48 | "enabled" : true,
49 | "value" : 0.7
50 | }
51 | }
52 | ]
53 | },
54 | {
55 | "blend-mode-specializations" : [
56 | {
57 | "appearance" : "dark",
58 | "value" : "plus-lighter"
59 | }
60 | ],
61 | "blur-material-specializations" : [
62 | {
63 | "appearance" : "dark",
64 | "value" : null
65 | }
66 | ],
67 | "layers" : [
68 | {
69 | "fill" : "none",
70 | "glass" : true,
71 | "hidden" : false,
72 | "image-name-specializations" : [
73 | {
74 | "value" : "squircle-light.svg"
75 | },
76 | {
77 | "appearance" : "dark",
78 | "value" : "squircle-dark.svg"
79 | }
80 | ],
81 | "name" : "squircle",
82 | "opacity-specializations" : [
83 | {
84 | "value" : 1
85 | },
86 | {
87 | "appearance" : "dark",
88 | "value" : 1
89 | }
90 | ],
91 | "position" : {
92 | "scale" : 1.24,
93 | "translation-in-points" : [
94 | 0,
95 | 0
96 | ]
97 | }
98 | }
99 | ],
100 | "name" : "Layer3",
101 | "opacity-specializations" : [
102 | {
103 | "value" : 0.5
104 | },
105 | {
106 | "appearance" : "dark",
107 | "value" : 0.5
108 | },
109 | {
110 | "appearance" : "tinted",
111 | "value" : 0.7
112 | }
113 | ],
114 | "shadow-specializations" : [
115 | {
116 | "appearance" : "dark",
117 | "value" : {
118 | "kind" : "none",
119 | "opacity" : 0.5
120 | }
121 | }
122 | ],
123 | "specular-specializations" : [
124 | {
125 | "appearance" : "dark",
126 | "value" : false
127 | }
128 | ],
129 | "translucency-specializations" : [
130 | {
131 | "appearance" : "dark",
132 | "value" : {
133 | "enabled" : true,
134 | "value" : 0.5
135 | }
136 | }
137 | ]
138 | },
139 | {
140 | "hidden" : false,
141 | "layers" : [
142 | {
143 | "blend-mode-specializations" : [
144 | {
145 | "appearance" : "dark",
146 | "value" : "normal"
147 | }
148 | ],
149 | "fill-specializations" : [
150 | {
151 | "value" : "none"
152 | },
153 | {
154 | "appearance" : "dark",
155 | "value" : "none"
156 | }
157 | ],
158 | "glass" : true,
159 | "hidden" : false,
160 | "image-name-specializations" : [
161 | {
162 | "value" : "circle-light.svg"
163 | },
164 | {
165 | "appearance" : "dark",
166 | "value" : "circle-dark.svg"
167 | }
168 | ],
169 | "name" : "circle",
170 | "opacity-specializations" : [
171 | {
172 | "appearance" : "dark",
173 | "value" : 1
174 | }
175 | ],
176 | "position" : {
177 | "scale" : 1.24,
178 | "translation-in-points" : [
179 | 0,
180 | 0
181 | ]
182 | }
183 | }
184 | ],
185 | "name" : "Layer2",
186 | "shadow" : {
187 | "kind" : "neutral",
188 | "opacity" : 0.5
189 | },
190 | "specular-specializations" : [
191 | {
192 | "appearance" : "dark",
193 | "value" : false
194 | }
195 | ],
196 | "translucency" : {
197 | "enabled" : true,
198 | "value" : 0.5
199 | }
200 | },
201 | {
202 | "hidden-specializations" : [
203 | {
204 | "appearance" : "tinted",
205 | "value" : true
206 | }
207 | ],
208 | "layers" : [
209 | {
210 | "blend-mode-specializations" : [
211 | {
212 | "appearance" : "dark",
213 | "value" : "normal"
214 | }
215 | ],
216 | "glass-specializations" : [
217 | {
218 | "value" : false
219 | },
220 | {
221 | "appearance" : "dark",
222 | "value" : false
223 | }
224 | ],
225 | "image-name-specializations" : [
226 | {
227 | "value" : "background-light.png"
228 | },
229 | {
230 | "appearance" : "dark",
231 | "value" : "background-dark.png"
232 | }
233 | ],
234 | "name" : "background",
235 | "opacity-specializations" : [
236 | {
237 | "appearance" : "dark",
238 | "value" : 1
239 | },
240 | {
241 | "appearance" : "tinted",
242 | "value" : 1
243 | }
244 | ],
245 | "position" : {
246 | "scale" : 1.3,
247 | "translation-in-points" : [
248 | 0,
249 | 0
250 | ]
251 | }
252 | }
253 | ],
254 | "name" : "Layer1",
255 | "opacity-specializations" : [
256 | {
257 | "appearance" : "tinted",
258 | "value" : 1
259 | }
260 | ],
261 | "shadow" : {
262 | "kind" : "neutral",
263 | "opacity" : 0.5
264 | },
265 | "specular-specializations" : [
266 | {
267 | "value" : false
268 | },
269 | {
270 | "appearance" : "dark",
271 | "value" : false
272 | }
273 | ],
274 | "translucency" : {
275 | "enabled" : false,
276 | "value" : 0.5
277 | }
278 | }
279 | ],
280 | "supported-platforms" : {
281 | "circles" : [
282 | "watchOS"
283 | ],
284 | "squares" : "shared"
285 | }
286 | }
--------------------------------------------------------------------------------
/Resources/AppIconV3.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blur-material" : 0.9,
6 | "hidden" : false,
7 | "layers" : [
8 | {
9 | "fill-specializations" : [
10 | {
11 | "value" : "automatic"
12 | },
13 | {
14 | "appearance" : "dark",
15 | "value" : {
16 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
17 | }
18 | }
19 | ],
20 | "glass" : true,
21 | "image-name" : "Sausages.svg",
22 | "name" : "Sausages",
23 | "position" : {
24 | "scale" : 1.24,
25 | "translation-in-points" : [
26 | 0,
27 | 0
28 | ]
29 | }
30 | }
31 | ],
32 | "name" : "Layer4",
33 | "shadow" : {
34 | "kind" : "layer-color",
35 | "opacity" : 0.5
36 | },
37 | "specular" : true,
38 | "translucency-specializations" : [
39 | {
40 | "value" : {
41 | "enabled" : true,
42 | "value" : 0.7
43 | }
44 | },
45 | {
46 | "appearance" : "dark",
47 | "value" : {
48 | "enabled" : true,
49 | "value" : 0.7
50 | }
51 | }
52 | ]
53 | },
54 | {
55 | "blend-mode-specializations" : [
56 | {
57 | "appearance" : "dark",
58 | "value" : "plus-lighter"
59 | }
60 | ],
61 | "blur-material-specializations" : [
62 | {
63 | "appearance" : "dark",
64 | "value" : null
65 | }
66 | ],
67 | "layers" : [
68 | {
69 | "fill" : "none",
70 | "glass" : true,
71 | "hidden" : false,
72 | "image-name-specializations" : [
73 | {
74 | "value" : "squircle-light.svg"
75 | },
76 | {
77 | "appearance" : "dark",
78 | "value" : "squircle-dark.svg"
79 | }
80 | ],
81 | "name" : "squircle",
82 | "opacity-specializations" : [
83 | {
84 | "value" : 1
85 | },
86 | {
87 | "appearance" : "dark",
88 | "value" : 1
89 | }
90 | ],
91 | "position" : {
92 | "scale" : 1.24,
93 | "translation-in-points" : [
94 | 0,
95 | 0
96 | ]
97 | }
98 | }
99 | ],
100 | "name" : "Layer3",
101 | "opacity-specializations" : [
102 | {
103 | "value" : 0.5
104 | },
105 | {
106 | "appearance" : "dark",
107 | "value" : 0.5
108 | },
109 | {
110 | "appearance" : "tinted",
111 | "value" : 0.7
112 | }
113 | ],
114 | "shadow-specializations" : [
115 | {
116 | "appearance" : "dark",
117 | "value" : {
118 | "kind" : "none",
119 | "opacity" : 0.5
120 | }
121 | }
122 | ],
123 | "specular-specializations" : [
124 | {
125 | "appearance" : "dark",
126 | "value" : false
127 | }
128 | ],
129 | "translucency-specializations" : [
130 | {
131 | "appearance" : "dark",
132 | "value" : {
133 | "enabled" : true,
134 | "value" : 0.5
135 | }
136 | }
137 | ]
138 | },
139 | {
140 | "hidden" : false,
141 | "layers" : [
142 | {
143 | "blend-mode-specializations" : [
144 | {
145 | "appearance" : "dark",
146 | "value" : "normal"
147 | }
148 | ],
149 | "fill-specializations" : [
150 | {
151 | "value" : "none"
152 | },
153 | {
154 | "appearance" : "dark",
155 | "value" : "none"
156 | }
157 | ],
158 | "glass" : true,
159 | "hidden" : false,
160 | "image-name-specializations" : [
161 | {
162 | "value" : "circle-light.svg"
163 | },
164 | {
165 | "appearance" : "dark",
166 | "value" : "circle-dark.svg"
167 | }
168 | ],
169 | "name" : "circle",
170 | "opacity-specializations" : [
171 | {
172 | "appearance" : "dark",
173 | "value" : 1
174 | }
175 | ],
176 | "position" : {
177 | "scale" : 1.24,
178 | "translation-in-points" : [
179 | 0,
180 | 0
181 | ]
182 | }
183 | }
184 | ],
185 | "name" : "Layer2",
186 | "shadow" : {
187 | "kind" : "neutral",
188 | "opacity" : 0.5
189 | },
190 | "specular-specializations" : [
191 | {
192 | "appearance" : "dark",
193 | "value" : false
194 | }
195 | ],
196 | "translucency" : {
197 | "enabled" : true,
198 | "value" : 0.5
199 | }
200 | },
201 | {
202 | "hidden-specializations" : [
203 | {
204 | "appearance" : "tinted",
205 | "value" : true
206 | }
207 | ],
208 | "layers" : [
209 | {
210 | "blend-mode-specializations" : [
211 | {
212 | "appearance" : "dark",
213 | "value" : "normal"
214 | }
215 | ],
216 | "glass-specializations" : [
217 | {
218 | "value" : false
219 | },
220 | {
221 | "appearance" : "dark",
222 | "value" : false
223 | }
224 | ],
225 | "image-name-specializations" : [
226 | {
227 | "value" : "background-light.png"
228 | },
229 | {
230 | "appearance" : "dark",
231 | "value" : "background-dark.png"
232 | }
233 | ],
234 | "name" : "background",
235 | "opacity-specializations" : [
236 | {
237 | "appearance" : "dark",
238 | "value" : 1
239 | },
240 | {
241 | "appearance" : "tinted",
242 | "value" : 1
243 | }
244 | ],
245 | "position" : {
246 | "scale" : 1.3,
247 | "translation-in-points" : [
248 | 0,
249 | 0
250 | ]
251 | }
252 | }
253 | ],
254 | "name" : "Layer1",
255 | "opacity-specializations" : [
256 | {
257 | "appearance" : "tinted",
258 | "value" : 1
259 | }
260 | ],
261 | "shadow" : {
262 | "kind" : "neutral",
263 | "opacity" : 0.5
264 | },
265 | "specular-specializations" : [
266 | {
267 | "value" : false
268 | },
269 | {
270 | "appearance" : "dark",
271 | "value" : false
272 | }
273 | ],
274 | "translucency" : {
275 | "enabled" : false,
276 | "value" : 0.5
277 | }
278 | }
279 | ],
280 | "supported-platforms" : {
281 | "circles" : [
282 | "watchOS"
283 | ],
284 | "squares" : "shared"
285 | }
286 | }
--------------------------------------------------------------------------------
/Resources/AppIconV4.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blur-material" : 0.9,
6 | "hidden" : false,
7 | "layers" : [
8 | {
9 | "fill-specializations" : [
10 | {
11 | "value" : "automatic"
12 | },
13 | {
14 | "appearance" : "dark",
15 | "value" : {
16 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
17 | }
18 | }
19 | ],
20 | "glass" : true,
21 | "image-name" : "SausagesV2.svg",
22 | "name" : "SausagesV2",
23 | "position" : {
24 | "scale" : 1.24,
25 | "translation-in-points" : [
26 | 0,
27 | 0
28 | ]
29 | }
30 | }
31 | ],
32 | "name" : "Layer4",
33 | "shadow" : {
34 | "kind" : "layer-color",
35 | "opacity" : 0.5
36 | },
37 | "specular" : true,
38 | "translucency-specializations" : [
39 | {
40 | "value" : {
41 | "enabled" : true,
42 | "value" : 0.7
43 | }
44 | },
45 | {
46 | "appearance" : "dark",
47 | "value" : {
48 | "enabled" : true,
49 | "value" : 0.7
50 | }
51 | }
52 | ]
53 | },
54 | {
55 | "blend-mode-specializations" : [
56 | {
57 | "appearance" : "dark",
58 | "value" : "plus-lighter"
59 | }
60 | ],
61 | "blur-material-specializations" : [
62 | {
63 | "appearance" : "dark",
64 | "value" : null
65 | }
66 | ],
67 | "layers" : [
68 | {
69 | "fill" : "none",
70 | "glass" : true,
71 | "hidden" : false,
72 | "image-name-specializations" : [
73 | {
74 | "value" : "squircle-light.svg"
75 | },
76 | {
77 | "appearance" : "dark",
78 | "value" : "squircle-dark.svg"
79 | }
80 | ],
81 | "name" : "squircle",
82 | "opacity-specializations" : [
83 | {
84 | "value" : 1
85 | },
86 | {
87 | "appearance" : "dark",
88 | "value" : 1
89 | }
90 | ],
91 | "position" : {
92 | "scale" : 1.24,
93 | "translation-in-points" : [
94 | 0,
95 | 0
96 | ]
97 | }
98 | }
99 | ],
100 | "name" : "Layer3",
101 | "opacity-specializations" : [
102 | {
103 | "value" : 0.5
104 | },
105 | {
106 | "appearance" : "dark",
107 | "value" : 0.5
108 | },
109 | {
110 | "appearance" : "tinted",
111 | "value" : 0.7
112 | }
113 | ],
114 | "shadow-specializations" : [
115 | {
116 | "appearance" : "dark",
117 | "value" : {
118 | "kind" : "none",
119 | "opacity" : 0.5
120 | }
121 | }
122 | ],
123 | "specular-specializations" : [
124 | {
125 | "appearance" : "dark",
126 | "value" : false
127 | }
128 | ],
129 | "translucency-specializations" : [
130 | {
131 | "appearance" : "dark",
132 | "value" : {
133 | "enabled" : true,
134 | "value" : 0.5
135 | }
136 | }
137 | ]
138 | },
139 | {
140 | "hidden" : false,
141 | "layers" : [
142 | {
143 | "blend-mode-specializations" : [
144 | {
145 | "appearance" : "dark",
146 | "value" : "normal"
147 | }
148 | ],
149 | "fill-specializations" : [
150 | {
151 | "value" : "none"
152 | },
153 | {
154 | "appearance" : "dark",
155 | "value" : "none"
156 | }
157 | ],
158 | "glass" : true,
159 | "hidden" : false,
160 | "image-name-specializations" : [
161 | {
162 | "value" : "circle-light.svg"
163 | },
164 | {
165 | "appearance" : "dark",
166 | "value" : "circle-dark.svg"
167 | }
168 | ],
169 | "name" : "circle",
170 | "opacity-specializations" : [
171 | {
172 | "appearance" : "dark",
173 | "value" : 1
174 | }
175 | ],
176 | "position" : {
177 | "scale" : 1.24,
178 | "translation-in-points" : [
179 | 0,
180 | 0
181 | ]
182 | }
183 | }
184 | ],
185 | "name" : "Layer2",
186 | "shadow" : {
187 | "kind" : "neutral",
188 | "opacity" : 0.5
189 | },
190 | "specular-specializations" : [
191 | {
192 | "appearance" : "dark",
193 | "value" : false
194 | }
195 | ],
196 | "translucency" : {
197 | "enabled" : true,
198 | "value" : 0.5
199 | }
200 | },
201 | {
202 | "hidden-specializations" : [
203 | {
204 | "appearance" : "tinted",
205 | "value" : true
206 | }
207 | ],
208 | "layers" : [
209 | {
210 | "blend-mode-specializations" : [
211 | {
212 | "appearance" : "dark",
213 | "value" : "normal"
214 | }
215 | ],
216 | "glass-specializations" : [
217 | {
218 | "value" : false
219 | },
220 | {
221 | "appearance" : "dark",
222 | "value" : false
223 | }
224 | ],
225 | "image-name-specializations" : [
226 | {
227 | "value" : "background-light.png"
228 | },
229 | {
230 | "appearance" : "dark",
231 | "value" : "background-dark.png"
232 | }
233 | ],
234 | "name" : "background",
235 | "opacity-specializations" : [
236 | {
237 | "appearance" : "dark",
238 | "value" : 1
239 | },
240 | {
241 | "appearance" : "tinted",
242 | "value" : 1
243 | }
244 | ],
245 | "position" : {
246 | "scale" : 1.3,
247 | "translation-in-points" : [
248 | 0,
249 | 0
250 | ]
251 | }
252 | }
253 | ],
254 | "name" : "Layer1",
255 | "opacity-specializations" : [
256 | {
257 | "appearance" : "tinted",
258 | "value" : 1
259 | }
260 | ],
261 | "shadow" : {
262 | "kind" : "neutral",
263 | "opacity" : 0.5
264 | },
265 | "specular-specializations" : [
266 | {
267 | "value" : false
268 | },
269 | {
270 | "appearance" : "dark",
271 | "value" : false
272 | }
273 | ],
274 | "translucency" : {
275 | "enabled" : false,
276 | "value" : 0.5
277 | }
278 | }
279 | ],
280 | "supported-platforms" : {
281 | "circles" : [
282 | "watchOS"
283 | ],
284 | "squares" : "shared"
285 | }
286 | }
--------------------------------------------------------------------------------
/Asset Catalog Tinkerer/AppIcon.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blur-material" : 0.9,
6 | "hidden" : false,
7 | "layers" : [
8 | {
9 | "fill-specializations" : [
10 | {
11 | "value" : "automatic"
12 | },
13 | {
14 | "appearance" : "dark",
15 | "value" : {
16 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
17 | }
18 | }
19 | ],
20 | "glass" : true,
21 | "image-name" : "Sausages.svg",
22 | "name" : "Sausages",
23 | "position" : {
24 | "scale" : 1.24,
25 | "translation-in-points" : [
26 | 0,
27 | 0
28 | ]
29 | }
30 | }
31 | ],
32 | "name" : "Layer4",
33 | "shadow" : {
34 | "kind" : "layer-color",
35 | "opacity" : 0.5
36 | },
37 | "specular" : true,
38 | "translucency-specializations" : [
39 | {
40 | "value" : {
41 | "enabled" : true,
42 | "value" : 0.7
43 | }
44 | },
45 | {
46 | "appearance" : "dark",
47 | "value" : {
48 | "enabled" : true,
49 | "value" : 0.7
50 | }
51 | }
52 | ]
53 | },
54 | {
55 | "blend-mode-specializations" : [
56 | {
57 | "appearance" : "dark",
58 | "value" : "plus-lighter"
59 | }
60 | ],
61 | "blur-material-specializations" : [
62 | {
63 | "appearance" : "dark",
64 | "value" : null
65 | }
66 | ],
67 | "layers" : [
68 | {
69 | "fill" : "none",
70 | "glass" : true,
71 | "hidden" : false,
72 | "image-name-specializations" : [
73 | {
74 | "value" : "squircle-light.svg"
75 | },
76 | {
77 | "appearance" : "dark",
78 | "value" : "squircle-dark.svg"
79 | }
80 | ],
81 | "name" : "squircle",
82 | "opacity-specializations" : [
83 | {
84 | "value" : 1
85 | },
86 | {
87 | "appearance" : "dark",
88 | "value" : 1
89 | }
90 | ],
91 | "position" : {
92 | "scale" : 1.24,
93 | "translation-in-points" : [
94 | 0,
95 | 0
96 | ]
97 | }
98 | }
99 | ],
100 | "name" : "Layer3",
101 | "opacity-specializations" : [
102 | {
103 | "value" : 0.5
104 | },
105 | {
106 | "appearance" : "dark",
107 | "value" : 0.5
108 | },
109 | {
110 | "appearance" : "tinted",
111 | "value" : 0.7
112 | }
113 | ],
114 | "shadow-specializations" : [
115 | {
116 | "appearance" : "dark",
117 | "value" : {
118 | "kind" : "none",
119 | "opacity" : 0.5
120 | }
121 | }
122 | ],
123 | "specular-specializations" : [
124 | {
125 | "appearance" : "dark",
126 | "value" : false
127 | }
128 | ],
129 | "translucency-specializations" : [
130 | {
131 | "appearance" : "dark",
132 | "value" : {
133 | "enabled" : true,
134 | "value" : 0.5
135 | }
136 | }
137 | ]
138 | },
139 | {
140 | "hidden" : false,
141 | "layers" : [
142 | {
143 | "blend-mode-specializations" : [
144 | {
145 | "appearance" : "dark",
146 | "value" : "normal"
147 | }
148 | ],
149 | "fill-specializations" : [
150 | {
151 | "value" : "none"
152 | },
153 | {
154 | "appearance" : "dark",
155 | "value" : "none"
156 | }
157 | ],
158 | "glass" : true,
159 | "hidden" : false,
160 | "image-name-specializations" : [
161 | {
162 | "value" : "circle-light.svg"
163 | },
164 | {
165 | "appearance" : "dark",
166 | "value" : "circle-dark.svg"
167 | }
168 | ],
169 | "name" : "circle",
170 | "opacity-specializations" : [
171 | {
172 | "appearance" : "dark",
173 | "value" : 1
174 | }
175 | ],
176 | "position" : {
177 | "scale" : 1.24,
178 | "translation-in-points" : [
179 | 0,
180 | 0
181 | ]
182 | }
183 | }
184 | ],
185 | "name" : "Layer2",
186 | "shadow" : {
187 | "kind" : "neutral",
188 | "opacity" : 0.5
189 | },
190 | "specular-specializations" : [
191 | {
192 | "appearance" : "dark",
193 | "value" : false
194 | }
195 | ],
196 | "translucency" : {
197 | "enabled" : true,
198 | "value" : 0.5
199 | }
200 | },
201 | {
202 | "hidden-specializations" : [
203 | {
204 | "appearance" : "tinted",
205 | "value" : true
206 | }
207 | ],
208 | "layers" : [
209 | {
210 | "blend-mode-specializations" : [
211 | {
212 | "appearance" : "dark",
213 | "value" : "normal"
214 | }
215 | ],
216 | "glass-specializations" : [
217 | {
218 | "value" : false
219 | },
220 | {
221 | "appearance" : "dark",
222 | "value" : false
223 | }
224 | ],
225 | "image-name-specializations" : [
226 | {
227 | "value" : "background-light.png"
228 | },
229 | {
230 | "appearance" : "dark",
231 | "value" : "background-dark.png"
232 | }
233 | ],
234 | "name" : "background",
235 | "opacity-specializations" : [
236 | {
237 | "appearance" : "dark",
238 | "value" : 0.5
239 | },
240 | {
241 | "appearance" : "tinted",
242 | "value" : 1
243 | }
244 | ],
245 | "position" : {
246 | "scale" : 1.3,
247 | "translation-in-points" : [
248 | 0,
249 | 0
250 | ]
251 | }
252 | }
253 | ],
254 | "name" : "Layer1",
255 | "opacity-specializations" : [
256 | {
257 | "appearance" : "tinted",
258 | "value" : 1
259 | }
260 | ],
261 | "shadow" : {
262 | "kind" : "neutral",
263 | "opacity" : 0.5
264 | },
265 | "specular-specializations" : [
266 | {
267 | "value" : false
268 | },
269 | {
270 | "appearance" : "dark",
271 | "value" : false
272 | }
273 | ],
274 | "translucency" : {
275 | "enabled" : false,
276 | "value" : 0.5
277 | }
278 | }
279 | ],
280 | "supported-platforms" : {
281 | "circles" : [
282 | "watchOS"
283 | ],
284 | "squares" : "shared"
285 | }
286 | }
--------------------------------------------------------------------------------
/Resources/AppIconV7.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blend-mode-specializations" : [
6 | {
7 | "value" : "overlay"
8 | },
9 | {
10 | "appearance" : "dark",
11 | "value" : "plus-lighter"
12 | },
13 | {
14 | "appearance" : "tinted",
15 | "value" : "plus-lighter"
16 | }
17 | ],
18 | "blur-material" : 0.3,
19 | "hidden" : false,
20 | "layers" : [
21 | {
22 | "fill-specializations" : [
23 | {
24 | "value" : "automatic"
25 | },
26 | {
27 | "appearance" : "dark",
28 | "value" : {
29 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
30 | }
31 | }
32 | ],
33 | "glass" : true,
34 | "image-name" : "SausagesV4-FG.svg",
35 | "name" : "SausagesV4-FG",
36 | "position" : {
37 | "scale" : 1.24,
38 | "translation-in-points" : [
39 | 0,
40 | 0
41 | ]
42 | }
43 | }
44 | ],
45 | "lighting" : "individual",
46 | "name" : "Layer4",
47 | "opacity" : 0.9,
48 | "shadow-specializations" : [
49 | {
50 | "value" : {
51 | "kind" : "neutral",
52 | "opacity" : 1
53 | }
54 | },
55 | {
56 | "appearance" : "dark",
57 | "value" : {
58 | "kind" : "layer-color",
59 | "opacity" : 1
60 | }
61 | }
62 | ],
63 | "specular" : true,
64 | "translucency-specializations" : [
65 | {
66 | "value" : {
67 | "enabled" : true,
68 | "value" : 0.7
69 | }
70 | },
71 | {
72 | "appearance" : "dark",
73 | "value" : {
74 | "enabled" : true,
75 | "value" : 0.7
76 | }
77 | }
78 | ]
79 | },
80 | {
81 | "blend-mode-specializations" : [
82 | {
83 | "value" : "overlay"
84 | },
85 | {
86 | "appearance" : "dark",
87 | "value" : "plus-lighter"
88 | },
89 | {
90 | "appearance" : "tinted",
91 | "value" : "plus-lighter"
92 | }
93 | ],
94 | "blur-material" : 0.1,
95 | "hidden" : false,
96 | "layers" : [
97 | {
98 | "fill-specializations" : [
99 | {
100 | "value" : "automatic"
101 | },
102 | {
103 | "appearance" : "dark",
104 | "value" : {
105 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
106 | }
107 | }
108 | ],
109 | "glass" : true,
110 | "image-name" : "SausagesV4-BG.svg",
111 | "name" : "SausagesV4-BG",
112 | "position" : {
113 | "scale" : 1.24,
114 | "translation-in-points" : [
115 | 0,
116 | 0
117 | ]
118 | }
119 | }
120 | ],
121 | "lighting" : "individual",
122 | "name" : "Layer3",
123 | "shadow" : {
124 | "kind" : "layer-color",
125 | "opacity" : 0.5
126 | },
127 | "specular" : true,
128 | "translucency-specializations" : [
129 | {
130 | "value" : {
131 | "enabled" : true,
132 | "value" : 0.5
133 | }
134 | },
135 | {
136 | "appearance" : "dark",
137 | "value" : {
138 | "enabled" : true,
139 | "value" : 0.7
140 | }
141 | }
142 | ]
143 | },
144 | {
145 | "blend-mode-specializations" : [
146 | {
147 | "value" : "soft-light"
148 | },
149 | {
150 | "appearance" : "dark",
151 | "value" : "hard-light"
152 | },
153 | {
154 | "appearance" : "tinted",
155 | "value" : "hard-light"
156 | }
157 | ],
158 | "hidden" : false,
159 | "layers" : [
160 | {
161 | "image-name" : "grid.svg",
162 | "name" : "grid",
163 | "position" : {
164 | "scale" : 1.24,
165 | "translation-in-points" : [
166 | 0,
167 | 0
168 | ]
169 | }
170 | }
171 | ],
172 | "lighting" : "individual",
173 | "name" : "Layer2",
174 | "opacity-specializations" : [
175 | {
176 | "value" : 0.5
177 | },
178 | {
179 | "appearance" : "dark",
180 | "value" : 0.15
181 | },
182 | {
183 | "appearance" : "tinted",
184 | "value" : 0.15
185 | }
186 | ],
187 | "shadow" : {
188 | "kind" : "neutral",
189 | "opacity" : 0.5
190 | },
191 | "specular-specializations" : [
192 | {
193 | "value" : false
194 | },
195 | {
196 | "appearance" : "dark",
197 | "value" : false
198 | }
199 | ],
200 | "translucency" : {
201 | "enabled" : true,
202 | "value" : 0.5
203 | }
204 | },
205 | {
206 | "hidden-specializations" : [
207 | {
208 | "appearance" : "tinted",
209 | "value" : true
210 | }
211 | ],
212 | "layers" : [
213 | {
214 | "blend-mode-specializations" : [
215 | {
216 | "appearance" : "dark",
217 | "value" : "normal"
218 | }
219 | ],
220 | "glass-specializations" : [
221 | {
222 | "value" : false
223 | },
224 | {
225 | "appearance" : "dark",
226 | "value" : false
227 | }
228 | ],
229 | "image-name-specializations" : [
230 | {
231 | "value" : "background-light.png"
232 | },
233 | {
234 | "appearance" : "dark",
235 | "value" : "background-dark.png"
236 | }
237 | ],
238 | "name" : "background",
239 | "opacity-specializations" : [
240 | {
241 | "appearance" : "dark",
242 | "value" : 1
243 | },
244 | {
245 | "appearance" : "tinted",
246 | "value" : 1
247 | }
248 | ],
249 | "position" : {
250 | "scale" : 1.3,
251 | "translation-in-points" : [
252 | 0,
253 | 0
254 | ]
255 | }
256 | }
257 | ],
258 | "name" : "Layer1",
259 | "opacity-specializations" : [
260 | {
261 | "appearance" : "tinted",
262 | "value" : 1
263 | }
264 | ],
265 | "shadow" : {
266 | "kind" : "neutral",
267 | "opacity" : 0.5
268 | },
269 | "specular-specializations" : [
270 | {
271 | "value" : false
272 | },
273 | {
274 | "appearance" : "dark",
275 | "value" : false
276 | }
277 | ],
278 | "translucency" : {
279 | "enabled" : false,
280 | "value" : 0.5
281 | }
282 | }
283 | ],
284 | "supported-platforms" : {
285 | "circles" : [
286 | "watchOS"
287 | ],
288 | "squares" : "shared"
289 | }
290 | }
--------------------------------------------------------------------------------
/Resources/AppIconV5.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blur-material-specializations" : [
6 | {
7 | "value" : 0.3
8 | },
9 | {
10 | "appearance" : "dark",
11 | "value" : 0.3
12 | }
13 | ],
14 | "hidden" : false,
15 | "layers" : [
16 | {
17 | "fill-specializations" : [
18 | {
19 | "value" : "automatic"
20 | },
21 | {
22 | "appearance" : "dark",
23 | "value" : {
24 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
25 | }
26 | }
27 | ],
28 | "glass" : true,
29 | "image-name" : "SausagesV3-FG.svg",
30 | "name" : "SausagesV3-FG",
31 | "position" : {
32 | "scale" : 1.24,
33 | "translation-in-points" : [
34 | 0,
35 | 0
36 | ]
37 | }
38 | }
39 | ],
40 | "lighting" : "combined",
41 | "name" : "Layer4",
42 | "shadow" : {
43 | "kind" : "layer-color",
44 | "opacity" : 0.5
45 | },
46 | "specular" : true,
47 | "translucency-specializations" : [
48 | {
49 | "value" : {
50 | "enabled" : true,
51 | "value" : 1
52 | }
53 | },
54 | {
55 | "appearance" : "dark",
56 | "value" : {
57 | "enabled" : true,
58 | "value" : 0.7
59 | }
60 | }
61 | ]
62 | },
63 | {
64 | "blur-material" : 0.1,
65 | "hidden" : false,
66 | "layers" : [
67 | {
68 | "fill-specializations" : [
69 | {
70 | "value" : "automatic"
71 | },
72 | {
73 | "appearance" : "dark",
74 | "value" : {
75 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
76 | }
77 | }
78 | ],
79 | "glass" : true,
80 | "image-name" : "SausagesV3-BG.svg",
81 | "name" : "SausagesV3-BG",
82 | "position" : {
83 | "scale" : 1.24,
84 | "translation-in-points" : [
85 | 0,
86 | 0
87 | ]
88 | }
89 | }
90 | ],
91 | "lighting" : "combined",
92 | "name" : "Layer3",
93 | "shadow" : {
94 | "kind" : "layer-color",
95 | "opacity" : 0.5
96 | },
97 | "specular" : true,
98 | "translucency-specializations" : [
99 | {
100 | "value" : {
101 | "enabled" : true,
102 | "value" : 0.5
103 | }
104 | },
105 | {
106 | "appearance" : "dark",
107 | "value" : {
108 | "enabled" : true,
109 | "value" : 0.7
110 | }
111 | }
112 | ]
113 | },
114 | {
115 | "hidden" : false,
116 | "layers" : [
117 | {
118 | "fill" : "none",
119 | "glass" : true,
120 | "hidden" : false,
121 | "image-name-specializations" : [
122 | {
123 | "value" : "squircle-light.svg"
124 | },
125 | {
126 | "appearance" : "dark",
127 | "value" : "squircle-dark.svg"
128 | }
129 | ],
130 | "name" : "squircle",
131 | "opacity-specializations" : [
132 | {
133 | "value" : 1
134 | },
135 | {
136 | "appearance" : "dark",
137 | "value" : 1
138 | }
139 | ],
140 | "position" : {
141 | "scale" : 1.24,
142 | "translation-in-points" : [
143 | 0,
144 | 0
145 | ]
146 | }
147 | },
148 | {
149 | "blend-mode-specializations" : [
150 | {
151 | "appearance" : "dark",
152 | "value" : "normal"
153 | }
154 | ],
155 | "fill-specializations" : [
156 | {
157 | "value" : "none"
158 | },
159 | {
160 | "appearance" : "dark",
161 | "value" : "none"
162 | }
163 | ],
164 | "glass" : true,
165 | "hidden" : false,
166 | "image-name-specializations" : [
167 | {
168 | "value" : "circle-light.svg"
169 | },
170 | {
171 | "appearance" : "dark",
172 | "value" : "circle-dark.svg"
173 | }
174 | ],
175 | "name" : "circle",
176 | "opacity-specializations" : [
177 | {
178 | "appearance" : "dark",
179 | "value" : 1
180 | }
181 | ],
182 | "position" : {
183 | "scale" : 1.24,
184 | "translation-in-points" : [
185 | 0,
186 | 0
187 | ]
188 | }
189 | }
190 | ],
191 | "lighting" : "individual",
192 | "name" : "Layer2",
193 | "shadow" : {
194 | "kind" : "neutral",
195 | "opacity" : 0.5
196 | },
197 | "specular-specializations" : [
198 | {
199 | "appearance" : "dark",
200 | "value" : false
201 | }
202 | ],
203 | "translucency" : {
204 | "enabled" : true,
205 | "value" : 0.5
206 | }
207 | },
208 | {
209 | "hidden-specializations" : [
210 | {
211 | "appearance" : "tinted",
212 | "value" : true
213 | }
214 | ],
215 | "layers" : [
216 | {
217 | "blend-mode-specializations" : [
218 | {
219 | "appearance" : "dark",
220 | "value" : "normal"
221 | }
222 | ],
223 | "glass-specializations" : [
224 | {
225 | "value" : false
226 | },
227 | {
228 | "appearance" : "dark",
229 | "value" : false
230 | }
231 | ],
232 | "image-name-specializations" : [
233 | {
234 | "value" : "background-light.png"
235 | },
236 | {
237 | "appearance" : "dark",
238 | "value" : "background-dark.png"
239 | }
240 | ],
241 | "name" : "background",
242 | "opacity-specializations" : [
243 | {
244 | "appearance" : "dark",
245 | "value" : 1
246 | },
247 | {
248 | "appearance" : "tinted",
249 | "value" : 1
250 | }
251 | ],
252 | "position" : {
253 | "scale" : 1.3,
254 | "translation-in-points" : [
255 | 0,
256 | 0
257 | ]
258 | }
259 | }
260 | ],
261 | "name" : "Layer1",
262 | "opacity-specializations" : [
263 | {
264 | "appearance" : "tinted",
265 | "value" : 1
266 | }
267 | ],
268 | "shadow" : {
269 | "kind" : "neutral",
270 | "opacity" : 0.5
271 | },
272 | "specular-specializations" : [
273 | {
274 | "value" : false
275 | },
276 | {
277 | "appearance" : "dark",
278 | "value" : false
279 | }
280 | ],
281 | "translucency" : {
282 | "enabled" : false,
283 | "value" : 0.5
284 | }
285 | }
286 | ],
287 | "supported-platforms" : {
288 | "circles" : [
289 | "watchOS"
290 | ],
291 | "squares" : "shared"
292 | }
293 | }
--------------------------------------------------------------------------------
/Resources/AppIconV6.icon/icon.json:
--------------------------------------------------------------------------------
1 | {
2 | "fill" : "system-light",
3 | "groups" : [
4 | {
5 | "blend-mode-specializations" : [
6 | {
7 | "value" : "overlay"
8 | },
9 | {
10 | "appearance" : "dark",
11 | "value" : "plus-lighter"
12 | },
13 | {
14 | "appearance" : "tinted",
15 | "value" : "plus-lighter"
16 | }
17 | ],
18 | "blur-material" : 0.3,
19 | "hidden" : false,
20 | "layers" : [
21 | {
22 | "fill-specializations" : [
23 | {
24 | "value" : "automatic"
25 | },
26 | {
27 | "appearance" : "dark",
28 | "value" : {
29 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
30 | }
31 | }
32 | ],
33 | "glass" : true,
34 | "image-name" : "SausagesV4-FG.svg",
35 | "name" : "SausagesV4-FG",
36 | "position" : {
37 | "scale" : 1.24,
38 | "translation-in-points" : [
39 | 0,
40 | 0
41 | ]
42 | }
43 | }
44 | ],
45 | "lighting" : "individual",
46 | "name" : "Layer4",
47 | "opacity" : 0.9,
48 | "shadow-specializations" : [
49 | {
50 | "value" : {
51 | "kind" : "neutral",
52 | "opacity" : 1
53 | }
54 | },
55 | {
56 | "appearance" : "dark",
57 | "value" : {
58 | "kind" : "layer-color",
59 | "opacity" : 1
60 | }
61 | }
62 | ],
63 | "specular" : true,
64 | "translucency-specializations" : [
65 | {
66 | "value" : {
67 | "enabled" : true,
68 | "value" : 0.7
69 | }
70 | },
71 | {
72 | "appearance" : "dark",
73 | "value" : {
74 | "enabled" : true,
75 | "value" : 0.7
76 | }
77 | }
78 | ]
79 | },
80 | {
81 | "blend-mode-specializations" : [
82 | {
83 | "value" : "overlay"
84 | },
85 | {
86 | "appearance" : "dark",
87 | "value" : "plus-lighter"
88 | },
89 | {
90 | "appearance" : "tinted",
91 | "value" : "plus-lighter"
92 | }
93 | ],
94 | "blur-material" : 0.1,
95 | "hidden" : false,
96 | "layers" : [
97 | {
98 | "fill-specializations" : [
99 | {
100 | "value" : "automatic"
101 | },
102 | {
103 | "appearance" : "dark",
104 | "value" : {
105 | "automatic-gradient" : "display-p3:0.00000,0.50588,1.00000,1.00000"
106 | }
107 | }
108 | ],
109 | "glass" : true,
110 | "image-name" : "SausagesV4-BG.svg",
111 | "name" : "SausagesV4-BG",
112 | "position" : {
113 | "scale" : 1.24,
114 | "translation-in-points" : [
115 | 0,
116 | 0
117 | ]
118 | }
119 | }
120 | ],
121 | "lighting" : "individual",
122 | "name" : "Layer3",
123 | "shadow" : {
124 | "kind" : "layer-color",
125 | "opacity" : 0.5
126 | },
127 | "specular" : true,
128 | "translucency-specializations" : [
129 | {
130 | "value" : {
131 | "enabled" : true,
132 | "value" : 0.5
133 | }
134 | },
135 | {
136 | "appearance" : "dark",
137 | "value" : {
138 | "enabled" : true,
139 | "value" : 0.7
140 | }
141 | }
142 | ]
143 | },
144 | {
145 | "hidden" : false,
146 | "layers" : [
147 | {
148 | "fill" : "none",
149 | "glass" : true,
150 | "hidden" : false,
151 | "image-name-specializations" : [
152 | {
153 | "value" : "squircle-light.svg"
154 | },
155 | {
156 | "appearance" : "dark",
157 | "value" : "squircle-dark.svg"
158 | }
159 | ],
160 | "name" : "squircle",
161 | "opacity-specializations" : [
162 | {
163 | "value" : 0.2
164 | },
165 | {
166 | "appearance" : "dark",
167 | "value" : 0.2
168 | }
169 | ],
170 | "position" : {
171 | "scale" : 1.24,
172 | "translation-in-points" : [
173 | 0,
174 | 0
175 | ]
176 | }
177 | },
178 | {
179 | "blend-mode-specializations" : [
180 | {
181 | "appearance" : "dark",
182 | "value" : "normal"
183 | }
184 | ],
185 | "fill-specializations" : [
186 | {
187 | "value" : "none"
188 | },
189 | {
190 | "appearance" : "dark",
191 | "value" : "none"
192 | }
193 | ],
194 | "glass" : true,
195 | "hidden" : false,
196 | "image-name-specializations" : [
197 | {
198 | "value" : "circle-light.svg"
199 | },
200 | {
201 | "appearance" : "dark",
202 | "value" : "circle-dark.svg"
203 | }
204 | ],
205 | "name" : "circle",
206 | "opacity-specializations" : [
207 | {
208 | "appearance" : "dark",
209 | "value" : 0.8
210 | }
211 | ],
212 | "position" : {
213 | "scale" : 1.24,
214 | "translation-in-points" : [
215 | 0,
216 | 0
217 | ]
218 | }
219 | }
220 | ],
221 | "lighting" : "individual",
222 | "name" : "Layer2",
223 | "shadow" : {
224 | "kind" : "neutral",
225 | "opacity" : 0.5
226 | },
227 | "specular-specializations" : [
228 | {
229 | "value" : false
230 | },
231 | {
232 | "appearance" : "dark",
233 | "value" : false
234 | }
235 | ],
236 | "translucency" : {
237 | "enabled" : true,
238 | "value" : 0.5
239 | }
240 | },
241 | {
242 | "hidden-specializations" : [
243 | {
244 | "appearance" : "tinted",
245 | "value" : true
246 | }
247 | ],
248 | "layers" : [
249 | {
250 | "blend-mode-specializations" : [
251 | {
252 | "appearance" : "dark",
253 | "value" : "normal"
254 | }
255 | ],
256 | "glass-specializations" : [
257 | {
258 | "value" : false
259 | },
260 | {
261 | "appearance" : "dark",
262 | "value" : false
263 | }
264 | ],
265 | "image-name-specializations" : [
266 | {
267 | "value" : "background-light.png"
268 | },
269 | {
270 | "appearance" : "dark",
271 | "value" : "background-dark.png"
272 | }
273 | ],
274 | "name" : "background",
275 | "opacity-specializations" : [
276 | {
277 | "appearance" : "dark",
278 | "value" : 1
279 | },
280 | {
281 | "appearance" : "tinted",
282 | "value" : 1
283 | }
284 | ],
285 | "position" : {
286 | "scale" : 1.3,
287 | "translation-in-points" : [
288 | 0,
289 | 0
290 | ]
291 | }
292 | }
293 | ],
294 | "name" : "Layer1",
295 | "opacity-specializations" : [
296 | {
297 | "appearance" : "tinted",
298 | "value" : 1
299 | }
300 | ],
301 | "shadow" : {
302 | "kind" : "neutral",
303 | "opacity" : 0.5
304 | },
305 | "specular-specializations" : [
306 | {
307 | "value" : false
308 | },
309 | {
310 | "appearance" : "dark",
311 | "value" : false
312 | }
313 | ],
314 | "translucency" : {
315 | "enabled" : false,
316 | "value" : 0.5
317 | }
318 | }
319 | ],
320 | "supported-platforms" : {
321 | "circles" : [
322 | "watchOS"
323 | ],
324 | "squares" : "shared"
325 | }
326 | }
--------------------------------------------------------------------------------
/AssetCatalog/QuickLookHelper.m:
--------------------------------------------------------------------------------
1 | //
2 | // QuickLookHelper.m
3 | // Asset Catalog Tinkerer
4 | //
5 | // Created by Guilherme Rambo on 02/01/17.
6 | // Copyright © 2017 Guilherme Rambo. All rights reserved.
7 | //
8 |
9 | #import "QuickLookHelper.h"
10 |
11 | #import
12 |
13 | #define kMaxNumberOfAssets 100
14 |
15 | @interface QuickLookHelper ()
16 |
17 | @property (nonatomic, assign, getter=isFinished) BOOL isFinished;
18 |
19 | @property (nonatomic, strong) AssetCatalogReader *reader;
20 | @property (nonatomic, assign) CGSize size;
21 |
22 | @end
23 |
24 | @implementation QuickLookHelper
25 |
26 | + (OSStatus)handleQuickLookRequestWithThumbnail:(QLThumbnailRequestRef)thumbnailRequest
27 | preview:(QLPreviewRequestRef)previewRequest
28 | url:(CFURLRef)url
29 | maxSize:(CGSize)maxSize
30 | {
31 | QuickLookHelper *generator = [[QuickLookHelper alloc] initWithURL:(__bridge NSURL *)url size:NSMakeSize(500, 700)];
32 |
33 | [generator generatePreview];
34 |
35 | while (!generator.isFinished) {
36 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true);
37 | }
38 |
39 | CGContextRef ctx;
40 | if (previewRequest) {
41 | ctx = QLPreviewRequestCreateContext(previewRequest, generator.size, false, NULL);
42 | } else {
43 | ctx = QLThumbnailRequestCreateContext(thumbnailRequest, generator.size, false, NULL);
44 | }
45 |
46 | if (!ctx) return -1;
47 |
48 | NSGraphicsContext *graphicsContext = [NSGraphicsContext graphicsContextWithCGContext:(void *)ctx flipped:NO];
49 | [NSGraphicsContext setCurrentContext:graphicsContext];
50 |
51 | [generator drawPreview];
52 |
53 | if (previewRequest) {
54 | QLPreviewRequestFlushContext(previewRequest, ctx);
55 | } else {
56 | QLThumbnailRequestFlushContext(thumbnailRequest, ctx);
57 | }
58 |
59 | return noErr;
60 | }
61 |
62 | - (instancetype)initWithURL:(NSURL *)url size:(CGSize)size
63 | {
64 | if (!(self = [super init])) return nil;
65 |
66 | _reader = [[AssetCatalogReader alloc] initWithFileURL:url];
67 | _size = size;
68 |
69 | return self;
70 | }
71 |
72 | - (void)generatePreview
73 | {
74 | __weak typeof(self) weakSelf = self;
75 | [self.reader resourceConstrainedReadWithMaxCount:kMaxNumberOfAssets completionHandler:^{
76 | weakSelf.isFinished = YES;
77 | }];
78 | }
79 |
80 | - (void)cancel
81 | {
82 | [self.reader cancelReading];
83 | }
84 |
85 | #define kCellMargin 28.0f
86 | #define kTextPadding 8.0f
87 | #define kTextDrawingRelativeSizeThreshold 2.5f
88 |
89 | - (void)drawPreview
90 | {
91 | // the number of assets actually previewed (the drawing can be aborted if there's not enough space to draw everyting)
92 | NSUInteger totalAssetsDrawn = 0;
93 |
94 | // text attributes for per-asset info
95 |
96 | NSMutableParagraphStyle *pStyle = [[NSMutableParagraphStyle alloc] init];
97 | pStyle.alignment = NSTextAlignmentCenter;
98 | pStyle.lineBreakMode = NSLineBreakByTruncatingTail;
99 |
100 | NSDictionary *assetTextAttrs = @{
101 | NSFontAttributeName: [NSFont systemFontOfSize:8.0],
102 | NSForegroundColorAttributeName: [NSColor grayColor],
103 | NSParagraphStyleAttributeName: pStyle
104 | };
105 |
106 | // fill background
107 |
108 | [[NSColor whiteColor] setFill];
109 | NSRectFill(NSMakeRect(0, 0, _size.width, _size.height));
110 |
111 | // draw asset grid
112 |
113 | // calculate median size which will be used to draw the assets
114 | NSSize referenceSize = [self medianImageSize];
115 |
116 | // sometimes this median can be greater than the size of the viewport, so resize accordingly
117 | if (referenceSize.width > self.size.width - kCellMargin * 2) {
118 | referenceSize.width = self.size.width - kCellMargin * 2;
119 | }
120 | if (referenceSize.height > self.size.height - kCellMargin * 2) {
121 | referenceSize.height = self.size.height - kCellMargin * 2;
122 | }
123 |
124 | CGFloat x = kCellMargin;
125 | CGFloat y = 0;
126 | CGFloat lastRowHeight = 0;
127 |
128 | for (NSDictionary *asset in self.reader.images) {
129 | NSImageRep *rep = asset[kACSImageRepKey];
130 | NSSize size = [self fitSize:rep.size inSize:referenceSize];
131 |
132 | if (size.height > lastRowHeight) {
133 | lastRowHeight = size.height;
134 | }
135 |
136 | if (y == 0) {
137 | y = _size.height - size.height - kCellMargin;
138 | }
139 |
140 | if ((x + size.width + kCellMargin) > (self.size.width - kCellMargin)) {
141 | x = kCellMargin;
142 | y -= (lastRowHeight + kCellMargin);
143 | lastRowHeight = 0;
144 | }
145 |
146 | if ((y - size.height) < kCellMargin) break;
147 |
148 | NSRect rect = NSMakeRect(x, y, size.width, size.height);
149 |
150 | [rep drawInRect:rect];
151 |
152 | NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(rect, -4, -4) xRadius:4 yRadius:4];
153 | [[NSColor colorWithCalibratedWhite:0.9 alpha:1.0] setStroke];
154 | [border stroke];
155 |
156 | x += size.width + kCellMargin;
157 |
158 | // draw per-asset info (if enough space is available)
159 |
160 | NSString *assetName = [asset[kACSNameKey] stringByDeletingPathExtension];
161 | NSAttributedString *assetInfo = [[NSAttributedString alloc] initWithString:assetName attributes:assetTextAttrs];
162 | CGFloat textAreaWidth = rect.size.width + kCellMargin - kTextPadding * 2;
163 |
164 | // draw text only if the area available for it is not too small
165 | if (textAreaWidth > floor(assetInfo.size.width / kTextDrawingRelativeSizeThreshold)) {
166 | NSRect textRect = NSMakeRect(rect.origin.x + round(rect.size.width / 2.0 - textAreaWidth / 2.0),
167 | rect.origin.y - assetInfo.size.height - kTextPadding,
168 | textAreaWidth,
169 | assetInfo.size.height);
170 | [assetInfo drawInRect:textRect];
171 | }
172 |
173 | totalAssetsDrawn++;
174 | }
175 |
176 | // draw summary text
177 |
178 | unsigned long readCount = MIN(self.reader.totalNumberOfAssets, totalAssetsDrawn);
179 |
180 | NSDictionary *attrs = @{
181 | NSFontAttributeName: [NSFont systemFontOfSize:12.0 weight:NSFontWeightMedium],
182 | NSForegroundColorAttributeName: [NSColor grayColor]
183 | };
184 | NSString *info = [NSString stringWithFormat:@"Previewing %lu of %lu assets", readCount, (unsigned long)self.reader.totalNumberOfAssets];
185 | NSAttributedString *summary = [[NSAttributedString alloc] initWithString:info attributes:attrs];
186 |
187 | CGFloat tw = summary.size.width;
188 | CGFloat th = summary.size.height;
189 | NSRect summaryRect = NSMakeRect(round(_size.width / 2.0 - tw / 2.0), kCellMargin, tw, th);
190 |
191 | [summary drawInRect:summaryRect];
192 | }
193 |
194 | - (NSSize)fitSize:(NSSize)originalSize inSize:(NSSize)maxSize
195 | {
196 | if (originalSize.width <= maxSize.width && originalSize.height <= maxSize.height) return originalSize;
197 |
198 | CGFloat newWidth, newHeight = 0;
199 | double rw = originalSize.width / maxSize.width;
200 | double rh = originalSize.height / maxSize.height;
201 |
202 | if (rw > rh)
203 | {
204 | newHeight = MAX(roundl(originalSize.height / rw), 1);
205 | newWidth = maxSize.width;
206 | }
207 | else
208 | {
209 | newWidth = MAX(roundl(originalSize.width / rh), 1);
210 | newHeight = maxSize.height;
211 | }
212 |
213 | return NSMakeSize(newWidth, newHeight);
214 | }
215 |
216 | - (NSNumber *)medianValueInArray:(NSArray *)input
217 | {
218 | NSArray *sorted = [input sortedArrayUsingSelector:@selector(compare:)];
219 | NSUInteger c = input.count;
220 |
221 | if (c % 2 == 1) {
222 | return sorted[c / 2];
223 | } else {
224 | NSNumber *m1 = sorted[c / 2];
225 | NSNumber *m2 = sorted[c / 2 - 1];
226 |
227 | return @((m1.doubleValue + m2.doubleValue) / 2);
228 | }
229 | }
230 |
231 | - (NSSize)medianImageSize
232 | {
233 | NSMutableArray *widths = [[NSMutableArray alloc] initWithCapacity:self.reader.images.count];
234 | NSMutableArray *heights = [[NSMutableArray alloc] initWithCapacity:self.reader.images.count];
235 |
236 | [self.reader.images enumerateObjectsUsingBlock:^(NSDictionary* asset, NSUInteger idx, BOOL *stop) {
237 | NSImageRep *rep = asset[kACSImageRepKey];
238 | [widths addObject:@(rep.size.width)];
239 | [heights addObject:@(rep.size.height)];
240 | }];
241 |
242 | NSNumber *medianWidth = [self medianValueInArray:widths];
243 | NSNumber *medianHeight = [self medianValueInArray:heights];
244 |
245 | return NSMakeSize(round(medianWidth.doubleValue), round(medianHeight.doubleValue));
246 | }
247 |
248 | @end
249 |
--------------------------------------------------------------------------------
/AssetCatalog/main.c:
--------------------------------------------------------------------------------
1 | //==============================================================================
2 | //
3 | // DO NO MODIFY THE CONTENT OF THIS FILE
4 | //
5 | // This file contains the generic CFPlug-in code necessary for your generator
6 | // To complete your generator implement the function in GenerateThumbnailForURL/GeneratePreviewForURL.c
7 | //
8 | //==============================================================================
9 |
10 |
11 |
12 |
13 |
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | // -----------------------------------------------------------------------------
21 | // constants
22 | // -----------------------------------------------------------------------------
23 |
24 | // Don't modify this line
25 | #define PLUGIN_ID "6E610ECE-9B7E-4ED8-89D1-EA191DD2EA00"
26 |
27 | //
28 | // Below is the generic glue code for all plug-ins.
29 | //
30 | // You should not have to modify this code aside from changing
31 | // names if you decide to change the names defined in the Info.plist
32 | //
33 |
34 |
35 | // -----------------------------------------------------------------------------
36 | // typedefs
37 | // -----------------------------------------------------------------------------
38 |
39 | // The thumbnail generation function to be implemented in GenerateThumbnailForURL.c
40 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize);
41 | void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail);
42 |
43 | // The preview generation function to be implemented in GeneratePreviewForURL.c
44 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options);
45 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview);
46 |
47 | // The layout for an instance of QuickLookGeneratorPlugIn
48 | typedef struct __QuickLookGeneratorPluginType
49 | {
50 | void *conduitInterface;
51 | CFUUIDRef factoryID;
52 | UInt32 refCount;
53 | } QuickLookGeneratorPluginType;
54 |
55 | // -----------------------------------------------------------------------------
56 | // prototypes
57 | // -----------------------------------------------------------------------------
58 | // Forward declaration for the IUnknown implementation.
59 | //
60 |
61 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID);
62 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance);
63 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv);
64 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID);
65 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance);
66 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance);
67 |
68 | // -----------------------------------------------------------------------------
69 | // myInterfaceFtbl definition
70 | // -----------------------------------------------------------------------------
71 | // The QLGeneratorInterfaceStruct function table.
72 | //
73 | static QLGeneratorInterfaceStruct myInterfaceFtbl = {
74 | NULL,
75 | QuickLookGeneratorQueryInterface,
76 | QuickLookGeneratorPluginAddRef,
77 | QuickLookGeneratorPluginRelease,
78 | NULL,
79 | NULL,
80 | NULL,
81 | NULL
82 | };
83 |
84 |
85 | // -----------------------------------------------------------------------------
86 | // AllocQuickLookGeneratorPluginType
87 | // -----------------------------------------------------------------------------
88 | // Utility function that allocates a new instance.
89 | // You can do some initial setup for the generator here if you wish
90 | // like allocating globals etc...
91 | //
92 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID)
93 | {
94 | QuickLookGeneratorPluginType *theNewInstance;
95 |
96 | theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType));
97 | memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType));
98 |
99 | /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */
100 | theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct));
101 | memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct));
102 |
103 | /* Retain and keep an open instance refcount for each factory. */
104 | theNewInstance->factoryID = CFRetain(inFactoryID);
105 | CFPlugInAddInstanceForFactory(inFactoryID);
106 |
107 | /* This function returns the IUnknown interface so set the refCount to one. */
108 | theNewInstance->refCount = 1;
109 | return theNewInstance;
110 | }
111 |
112 | // -----------------------------------------------------------------------------
113 | // DeallocQuickLookGeneratorPluginType
114 | // -----------------------------------------------------------------------------
115 | // Utility function that deallocates the instance when
116 | // the refCount goes to zero.
117 | // In the current implementation generator interfaces are never deallocated
118 | // but implement this as this might change in the future
119 | //
120 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance)
121 | {
122 | CFUUIDRef theFactoryID;
123 |
124 | theFactoryID = thisInstance->factoryID;
125 | /* Free the conduitInterface table up */
126 | free(thisInstance->conduitInterface);
127 |
128 | /* Free the instance structure */
129 | free(thisInstance);
130 | if (theFactoryID){
131 | CFPlugInRemoveInstanceForFactory(theFactoryID);
132 | CFRelease(theFactoryID);
133 | }
134 | }
135 |
136 | // -----------------------------------------------------------------------------
137 | // QuickLookGeneratorQueryInterface
138 | // -----------------------------------------------------------------------------
139 | // Implementation of the IUnknown QueryInterface function.
140 | //
141 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv)
142 | {
143 | CFUUIDRef interfaceID;
144 |
145 | interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid);
146 |
147 | if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){
148 | /* If the Right interface was requested, bump the ref count,
149 | * set the ppv parameter equal to the instance, and
150 | * return good status.
151 | */
152 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL;
153 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration;
154 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL;
155 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration;
156 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance);
157 | *ppv = thisInstance;
158 | CFRelease(interfaceID);
159 | return S_OK;
160 | }else{
161 | /* Requested interface unknown, bail with error. */
162 | *ppv = NULL;
163 | CFRelease(interfaceID);
164 | return E_NOINTERFACE;
165 | }
166 | }
167 |
168 | // -----------------------------------------------------------------------------
169 | // QuickLookGeneratorPluginAddRef
170 | // -----------------------------------------------------------------------------
171 | // Implementation of reference counting for this type. Whenever an interface
172 | // is requested, bump the refCount for the instance. NOTE: returning the
173 | // refcount is a convention but is not required so don't rely on it.
174 | //
175 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance)
176 | {
177 | ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1;
178 | return ((QuickLookGeneratorPluginType*) thisInstance)->refCount;
179 | }
180 |
181 | // -----------------------------------------------------------------------------
182 | // QuickLookGeneratorPluginRelease
183 | // -----------------------------------------------------------------------------
184 | // When an interface is released, decrement the refCount.
185 | // If the refCount goes to zero, deallocate the instance.
186 | //
187 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance)
188 | {
189 | ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1;
190 | if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){
191 | DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance );
192 | return 0;
193 | }else{
194 | return ((QuickLookGeneratorPluginType*) thisInstance )->refCount;
195 | }
196 | }
197 |
198 | // -----------------------------------------------------------------------------
199 | // QuickLookGeneratorPluginFactory
200 | // -----------------------------------------------------------------------------
201 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID)
202 | {
203 | QuickLookGeneratorPluginType *result;
204 | CFUUIDRef uuid;
205 |
206 | /* If correct type is being requested, allocate an
207 | * instance of kQLGeneratorTypeID and return the IUnknown interface.
208 | */
209 | if (CFEqual(typeID,kQLGeneratorTypeID)){
210 | uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID));
211 | result = AllocQuickLookGeneratorPluginType(uuid);
212 | CFRelease(uuid);
213 | return result;
214 | }
215 | /* If the requested type is incorrect, return NULL. */
216 | return NULL;
217 | }
218 |
219 |
--------------------------------------------------------------------------------