├── .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 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV3.icon/Assets/circle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV4.icon/Assets/circle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV5.icon/Assets/circle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/circle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV2.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV3.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV4.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV5.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Asset Catalog Tinkerer/AppIcon.icon/Assets/circle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Asset Catalog Tinkerer/AppIcon.icon/Assets/circle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | circle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | 3 | Boxes 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV3.icon/Assets/squircle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV4.icon/Assets/squircle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV5.icon/Assets/squircle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/squircle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV2.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV3.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV4.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV5.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Asset Catalog Tinkerer/AppIcon.icon/Assets/squircle-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-dark 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Asset Catalog Tinkerer/AppIcon.icon/Assets/squircle-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | squircle-light 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | ![screenshot](https://raw.github.com/insidegui/AssetCatalogTinkerer/master/screenshot.png) 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 | ![quicklook thumbnail](./quicklook_thumb.png) 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 | ![screenshot2](https://raw.github.com/insidegui/AssetCatalogTinkerer/master/screenshot_themestore.png) 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 | 3 | SausagesV3-FG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 | 3 | SausagesV3-BG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/SausagesV4-BG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | SausagesV4-BG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/AppIconV7.icon/Assets/SausagesV4-BG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | SausagesV4-BG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/AppIconV6.icon/Assets/SausagesV4-FG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | SausagesV4-FG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Resources/AppIconV7.icon/Assets/SausagesV4-FG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | SausagesV4-FG 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | 3 | SausagesV2 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | 3 | Sausages 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Asset Catalog Tinkerer/AppIcon.icon/Assets/Sausages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sausages 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------