Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7e1b61d7 | ||
|
|
57da81bc29 | ||
|
|
86c4b29ed5 | ||
|
|
88f3465f27 | ||
|
|
27d4ca0d3b | ||
|
|
2802953130 | ||
|
|
5087a973cf | ||
|
|
a6e85ec913 | ||
|
|
27b3b9768c | ||
|
|
c26eb28536 | ||
|
|
9330644f9e | ||
|
|
e672053594 | ||
|
|
ef3ddda8e6 | ||
|
|
b734282b0d | ||
|
|
a343b50916 | ||
|
|
40866ce9f8 | ||
|
|
ffeedffda9 | ||
|
|
46159dd5d4 |
@@ -80,7 +80,7 @@
|
||||
B3B4C2C71E25894B009F8E4E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
BA22232620AFEC1B0001069C /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; };
|
||||
BA22232820B04D660001069C /* AppDelegate+Menu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Menu.swift"; sourceTree = "<group>"; };
|
||||
BA22232A20B0E3B50001069C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = "/Users/cosmotherly/dev/pixel-picker/README.md"; sourceTree = "<absolute>"; };
|
||||
BA22232A20B0E3B50001069C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
|
||||
BA71817120AE2DB400619700 /* PPMenuShortcutView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PPMenuShortcutView.m; sourceTree = "<group>"; };
|
||||
BA71817320AE2DCC00619700 /* PPMenuShortcutView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PPMenuShortcutView.h; sourceTree = "<group>"; };
|
||||
BA74C0F620A692D100306B27 /* PPOverlayController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PPOverlayController.swift; sourceTree = "<group>"; };
|
||||
@@ -332,18 +332,18 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BA74C14220AA409D00306B27 /* NSColor.swift in Sources */,
|
||||
BA71817220AE2DB400619700 /* PPMenuShortcutView.m in Sources */,
|
||||
15492103207B53AE00E45BBC /* Util.swift in Sources */,
|
||||
BA74C0F820A692D100306B27 /* PPOverlayController.swift in Sources */,
|
||||
BA74C14E20ADA33E00306B27 /* PPColor.swift in Sources */,
|
||||
BA22232920B04D660001069C /* AppDelegate+Menu.swift in Sources */,
|
||||
BA74C13120A6E95900306B27 /* PPOverlayPanel.swift in Sources */,
|
||||
B3B4C2C11E25894B009F8E4E /* AppDelegate.swift in Sources */,
|
||||
BA74C14420AA609500306B27 /* PPOverlayWrapper.swift in Sources */,
|
||||
BA74C13320A6F16C00306B27 /* ShowAndHideCursor.m in Sources */,
|
||||
BA74C14020AA390400306B27 /* PPOverlayPreview.swift in Sources */,
|
||||
BA71817220AE2DB400619700 /* PPMenuShortcutView.m in Sources */,
|
||||
B3B4C2C11E25894B009F8E4E /* AppDelegate.swift in Sources */,
|
||||
BA22232920B04D660001069C /* AppDelegate+Menu.swift in Sources */,
|
||||
BA74C14E20ADA33E00306B27 /* PPColor.swift in Sources */,
|
||||
15FC8CC8204E2E2C000B5E1E /* PPState.swift in Sources */,
|
||||
BA74C13120A6E95900306B27 /* PPOverlayPanel.swift in Sources */,
|
||||
BA74C14020AA390400306B27 /* PPOverlayPreview.swift in Sources */,
|
||||
BA74C14420AA609500306B27 /* PPOverlayWrapper.swift in Sources */,
|
||||
BA74C0F820A692D100306B27 /* PPOverlayController.swift in Sources */,
|
||||
BA74C14220AA409D00306B27 /* NSColor.swift in Sources */,
|
||||
15492103207B53AE00E45BBC /* Util.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -20,62 +20,145 @@ let concentrationModifiers: [(String, NSEvent.ModifierFlags)] = [
|
||||
]
|
||||
|
||||
extension AppDelegate: NSMenuDelegate {
|
||||
// Unregister/register the activating shortcut when the menu is opened/closed
|
||||
// so it can't be called when setting a new shortcut.
|
||||
func menuWillOpen(_ menu: NSMenu) { unregisterActivatingShortcut() }
|
||||
func menuDidClose(_ menu: NSMenu) { registerActivatingShortcut() }
|
||||
// Unregister the activating shortcut when the menu is opened/closed so it can't be called when
|
||||
// setting a new shortcut. Also start a run loop observer so we know when the modifierFlags have
|
||||
// changed (used to dynamically update the menu).
|
||||
func menuWillOpen(_ menu: NSMenu) {
|
||||
unregisterActivatingShortcut()
|
||||
if runLoopObserver == nil {
|
||||
let activites = CFRunLoopActivity.beforeWaiting.rawValue
|
||||
runLoopObserver = CFRunLoopObserverCreateWithHandler(nil, activites, true, 0, { [unowned self] (_, _) in
|
||||
self.updateMenuItems()
|
||||
})
|
||||
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runLoopObserver, CFRunLoopMode.commonModes)
|
||||
}
|
||||
}
|
||||
|
||||
// Re-register the activating shortcut, and remove the run loop observer.
|
||||
func menuDidClose(_ menu: NSMenu) {
|
||||
registerActivatingShortcut()
|
||||
if (runLoopObserver != nil) {
|
||||
CFRunLoopObserverInvalidate(runLoopObserver)
|
||||
runLoopObserver = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the titles of the recently picked colors - if the `option` key is pressed, then
|
||||
// the colors will be in the format they were *when* they were picked, otherwise they'll be
|
||||
// in the currently chosen format.
|
||||
private func updateMenuItems() {
|
||||
// Update recent picks list with correct titles.
|
||||
let alternate = NSEvent.modifierFlags.contains(.option)
|
||||
for item in contextMenu.items {
|
||||
if let pickedColor = item.representedObject as? PPPickedColor {
|
||||
item.title = alternate
|
||||
? pickedColor.asString
|
||||
: PPState.shared.chosenFormat.asString(withColor: pickedColor.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: look into only updating the menu rather than rebuilding it each time.
|
||||
// Might not be worth it - it doesn't seem expensive to build it every time it's opened...
|
||||
func rebuildContextMenu() {
|
||||
contextMenu.removeAllItems()
|
||||
|
||||
|
||||
let pickItem = contextMenu.addItem(withTitle: "Pick a pixel!", action: #selector(showPicker), keyEquivalent: "")
|
||||
pickItem.image = ICON
|
||||
|
||||
|
||||
buildRecentPicks()
|
||||
|
||||
|
||||
contextMenu.addItem(.separator())
|
||||
buildColorSpaceItem()
|
||||
buildColorFormatsMenu()
|
||||
buildMagnificationItem()
|
||||
buildConcentrationMenu()
|
||||
buildFloatPrecisionSlider()
|
||||
buildShortcutMenuItem()
|
||||
|
||||
let launchAtLoginItem = contextMenu.addItem(withTitle: "Launch \(APP_NAME) at Login", action: #selector(launchAtLogin(_:)), keyEquivalent: "")
|
||||
launchAtLoginItem.state = LaunchAtLogin.isEnabled ? .on : .off
|
||||
buildLaunchAtLoginItem()
|
||||
|
||||
contextMenu.addItem(.separator())
|
||||
contextMenu.addItem(withTitle: "About", action: #selector(showAboutPanel), keyEquivalent: "")
|
||||
contextMenu.addItem(withTitle: "Quit \(APP_NAME)", action: #selector(quitApplication), keyEquivalent: "")
|
||||
}
|
||||
|
||||
// A menu that allows choosing what color space the picker will use.
|
||||
private func buildColorSpaceItem() {
|
||||
let submenu = NSMenu()
|
||||
|
||||
let defaultItem = submenu.addItem(withTitle: "Default (infer from screen)", action: #selector(setColorSpace(_:)), keyEquivalent: "")
|
||||
defaultItem.state = PPState.shared.colorSpace == nil ? .on : .off
|
||||
submenu.addItem(.separator())
|
||||
for (title, name) in PPColor.colorSpaceNames {
|
||||
let item = submenu.addItem(withTitle: title, action: #selector(setColorSpace(_:)), keyEquivalent: "")
|
||||
item.representedObject = name
|
||||
item.state = PPState.shared.colorSpace == name ? .on : .off
|
||||
}
|
||||
|
||||
let item = contextMenu.addItem(withTitle: "Color Space", action: nil, keyEquivalent: "")
|
||||
item.submenu = submenu
|
||||
}
|
||||
|
||||
// If the selected color space is nil, then the preview will just infer the color space from
|
||||
// the screen the picker is currently on.
|
||||
@objc private func setColorSpace(_ sender: NSMenuItem) {
|
||||
PPState.shared.colorSpace = sender.representedObject as? String
|
||||
}
|
||||
|
||||
// A menu which allows the magnification level of the picker to be adjusted.
|
||||
private func buildMagnificationItem() {
|
||||
let submenu = NSMenu()
|
||||
for i in stride(from: 4, through: 24, by: 2) {
|
||||
let item = submenu.addItem(withTitle: "\(i)x", action: #selector(selectMagnification(_:)), keyEquivalent: "")
|
||||
item.representedObject = i
|
||||
item.state = PPState.shared.magnificationLevel == i ? .on : .off
|
||||
}
|
||||
let item = contextMenu.addItem(withTitle: "Magnification", action: nil, keyEquivalent: "")
|
||||
item.submenu = submenu
|
||||
}
|
||||
|
||||
@objc private func selectMagnification(_ sender: NSMenuItem) {
|
||||
if let level = sender.representedObject as? Int {
|
||||
PPState.shared.magnificationLevel = level
|
||||
}
|
||||
}
|
||||
|
||||
// Simple launch app at login menu item.
|
||||
private func buildLaunchAtLoginItem() {
|
||||
let item = contextMenu.addItem(withTitle: "Launch \(APP_NAME) at Login", action: #selector(launchAtLogin(_:)), keyEquivalent: "")
|
||||
item.state = LaunchAtLogin.isEnabled ? .on : .off
|
||||
}
|
||||
|
||||
@objc private func launchAtLogin(_ sender: NSMenuItem) {
|
||||
LaunchAtLogin.isEnabled = !LaunchAtLogin.isEnabled
|
||||
}
|
||||
|
||||
// Show the user's recent picks in the menu.
|
||||
private func buildRecentPicks() {
|
||||
// Recent picks.
|
||||
// TODO: it would be nice to hold the `option` key and have the recent picks show in the
|
||||
// currently chosen format, and when clicked copy in that format.
|
||||
// https://stackoverflow.com/q/11208632/5552584
|
||||
if PPState.shared.recentPicks.count > 0 {
|
||||
contextMenu.addItem(.separator())
|
||||
contextMenu.addItem(withTitle: "Recently Picked", action: nil, keyEquivalent: "")
|
||||
for pickedColor in PPState.shared.recentPicks {
|
||||
// TODO: copy to clipboard when clicked, if alt then in current format
|
||||
let item = contextMenu.addItem(withTitle: pickedColor.asString, action: #selector(copyRecentPick(_:)), keyEquivalent: "")
|
||||
let format = PPState.shared.chosenFormat
|
||||
for pickedColor in PPState.shared.recentPicks.reversed() {
|
||||
let item = contextMenu.addItem(withTitle: format.asString(withColor: pickedColor.color), action: #selector(copyRecentPick(_:)), keyEquivalent: "")
|
||||
item.representedObject = pickedColor
|
||||
item.image = circleImage(withSize: 12, color: pickedColor.color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Copies the recently picked color (associated with the menu item) to the clipboard.
|
||||
// If the `option` key is pressed, then it copies the color in the same format it was
|
||||
// when it was picked (otherwise, it copies it in the currently chosen format).
|
||||
@objc private func copyRecentPick(_ sender: NSMenuItem) {
|
||||
if let pickedColor = sender.representedObject as? PPPickedColor {
|
||||
copyToPasteboard(stringValue: pickedColor.asString)
|
||||
let value = NSEvent.modifierFlags.contains(.option)
|
||||
? pickedColor.asString
|
||||
: PPState.shared.chosenFormat.asString(withColor: pickedColor.color)
|
||||
copyToPasteboard(stringValue: value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Simply creates a circle NSImage with the given size and color.
|
||||
private func circleImage(withSize size: CGFloat, color: NSColor) -> NSImage {
|
||||
let image = NSImage(size: NSSize(width: size, height: size))
|
||||
@@ -85,7 +168,7 @@ extension AppDelegate: NSMenuDelegate {
|
||||
image.unlockFocus()
|
||||
return image
|
||||
}
|
||||
|
||||
|
||||
// A slider to change the float precision.
|
||||
private func buildFloatPrecisionSlider() {
|
||||
contextMenu.addItem(withTitle: "Float Precision (\(PPState.shared.floatPrecision))", action: nil, keyEquivalent: "")
|
||||
@@ -104,27 +187,23 @@ extension AppDelegate: NSMenuDelegate {
|
||||
item.view!.autoresizingMask = .width
|
||||
item.view!.addSubview(slider)
|
||||
}
|
||||
|
||||
|
||||
// Called when the slider is updated.
|
||||
@objc private func sliderUpdate(_ sender: NSSlider) {
|
||||
let newValue = UInt(sender.intValue)
|
||||
|
||||
|
||||
// Update slider title.
|
||||
if let item = contextMenu.item(withTitle: "Float Precision (\(PPState.shared.floatPrecision))") {
|
||||
item.title = "Float Precision (\(newValue))"
|
||||
}
|
||||
|
||||
|
||||
// Update state.
|
||||
PPState.shared.floatPrecision = newValue
|
||||
|
||||
|
||||
// Update recent picks list with new precision.
|
||||
for item in contextMenu.items {
|
||||
if let pickedColor = item.representedObject as? PPPickedColor {
|
||||
item.title = pickedColor.asString
|
||||
}
|
||||
}
|
||||
updateMenuItems()
|
||||
}
|
||||
|
||||
|
||||
// Build a submenu with each case in the PPColor enum.
|
||||
// TODO: with Swift 4.2, we shouldn't need to resort to the hacky "iterateEnum" approach.
|
||||
private func buildColorFormatsMenu() {
|
||||
@@ -138,16 +217,14 @@ extension AppDelegate: NSMenuDelegate {
|
||||
let item = contextMenu.addItem(withTitle: "Color Format", action: nil, keyEquivalent: "")
|
||||
item.submenu = submenu
|
||||
}
|
||||
|
||||
|
||||
// Set the selected format as the default.
|
||||
@objc private func selectFormat(_ sender: NSMenuItem) {
|
||||
if let format = sender.representedObject as? PPColor {
|
||||
PPState.shared.chosenFormat = format
|
||||
sender.menu?.items.forEach({ $0.state = .off })
|
||||
sender.state = .on
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Builds and adds the concentration modifier submenu.
|
||||
private func buildConcentrationMenu() {
|
||||
let submenu = NSMenu()
|
||||
@@ -160,16 +237,14 @@ extension AppDelegate: NSMenuDelegate {
|
||||
let item = contextMenu.addItem(withTitle: "Concentration Modifier", action: nil, keyEquivalent: "")
|
||||
item.submenu = submenu
|
||||
}
|
||||
|
||||
|
||||
// Set the chosen modifier to toggle "concentrationMode".
|
||||
@objc private func selectModifier(_ sender: NSMenuItem) {
|
||||
if let modifier = sender.representedObject as? NSEvent.ModifierFlags {
|
||||
PPState.shared.concentrationModeModifier = modifier
|
||||
sender.menu?.items.forEach({ $0.state = .off })
|
||||
sender.state = .on
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Builds and adds the MASShortcutView to be used in the menu.
|
||||
// Uses a custom view to handle events correctly (since it's inside a NSMenu).
|
||||
private func buildShortcutMenuItem() {
|
||||
@@ -178,7 +253,7 @@ extension AppDelegate: NSMenuDelegate {
|
||||
let shortcutView = MASShortcutView()
|
||||
shortcutView.shortcutValue = PPState.shared.activatingShortcut
|
||||
shortcutView.shortcutValueChange = { PPState.shared.activatingShortcut = $0?.shortcutValue }
|
||||
|
||||
|
||||
let item = contextMenu.addItem(withTitle: "Shortcut", action: nil, keyEquivalent: "")
|
||||
item.view = PPMenuShortcutView(shortcut: shortcutView)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
|
||||
|
||||
// This controller manages the pixel picker itself.
|
||||
@IBOutlet weak var overlayController: PPOverlayController!
|
||||
|
||||
var contextMenu: NSMenu = NSMenu()
|
||||
|
||||
// The actual menu bar item.
|
||||
var menuBarItem: NSStatusItem! = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
|
||||
|
||||
// The menu that drops down from the menu bar item.
|
||||
var contextMenu: NSMenu = NSMenu()
|
||||
// When the menu bar is opened, we observe the run loop for changes in modifierFlags.
|
||||
var runLoopObserver: CFRunLoopObserver? = nil
|
||||
|
||||
// Setup logging and load state.
|
||||
func applicationWillFinishLaunching(_ notification: Notification) {
|
||||
let minimumSeverity: LogSeverity = PPState.shared.defaults.bool(forKey: "debugMode") ? .debug : .info
|
||||
@@ -37,22 +41,28 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
|
||||
menuBarItem.image = ICON
|
||||
menuBarItem.action = #selector(onMenuClick)
|
||||
menuBarItem.sendAction(on: [.leftMouseUp, .rightMouseUp])
|
||||
|
||||
|
||||
registerActivatingShortcut()
|
||||
|
||||
// Set the CGEventSource.localEventsSuppressionInterval to a small interval (default: 250ms)
|
||||
// otherwise there'll be a delay when we re-associate the mouse input with the mouse cursor
|
||||
// (in the picker) that makes it feel laggy (the suppression interval controls how long
|
||||
// hardware events are suppressed after functions like CGWarpMouseCursorPosition are used.
|
||||
CGEventSource(stateID: CGEventSourceStateID.combinedSessionState)?.localEventsSuppressionInterval = 0.05
|
||||
|
||||
Log.info?.message("Sucessfully launched.")
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
PPState.shared.saveToDisk()
|
||||
}
|
||||
|
||||
|
||||
func registerActivatingShortcut() {
|
||||
if let shortcut = PPState.shared.activatingShortcut {
|
||||
MASShortcutMonitor.shared().register(shortcut, withAction: showPicker)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func unregisterActivatingShortcut() {
|
||||
MASShortcutMonitor.shared().unregisterShortcut(PPState.shared.activatingShortcut)
|
||||
}
|
||||
@@ -61,7 +71,7 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
|
||||
let leftClickToggles = PPState.shared.defaults.bool(forKey: "leftClickActivates")
|
||||
let pickerEvent: NSEvent.EventType = leftClickToggles ? .leftMouseUp : .rightMouseUp
|
||||
let dropdownEvent: NSEvent.EventType = leftClickToggles ? .rightMouseUp : .leftMouseUp
|
||||
|
||||
|
||||
let event = NSApp.currentEvent!
|
||||
if event.type == dropdownEvent {
|
||||
rebuildContextMenu()
|
||||
@@ -70,7 +80,7 @@ let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
|
||||
showPicker()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc func showPicker() {
|
||||
overlayController.showPicker()
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 994 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
@@ -3,7 +3,7 @@
|
||||
}
|
||||
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red230\green230\blue230;}
|
||||
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\csgray\c92143;}
|
||||
\paperw12240\paperh15840\margl1440\margr1440\vieww12800\viewh10340\viewkind0
|
||||
\margl1440\margr1440\vieww12800\viewh10340\viewkind0
|
||||
\deftab720
|
||||
\pard\pardeftab720\qc\partightenfactor0
|
||||
|
||||
@@ -27,8 +27,9 @@
|
||||
\pard\pardeftab720\qc\partightenfactor0
|
||||
{\field{\*\fldinst{HYPERLINK "https://github.com/SwiftyJSON/SwiftyJSON"}}{\fldrslt \cf2 SwiftyJSON}}\
|
||||
\
|
||||
\pard\pardeftab720\qc\partightenfactor0
|
||||
|
||||
\b\fs36 Experimental Overrides
|
||||
\b\fs36 \cf2 Experimental Overrides
|
||||
\b0 \
|
||||
\pard\pardeftab720\qc\partightenfactor0
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>4</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -26,6 +26,16 @@ extension NSColor {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates an NSImage with the given size and color.
|
||||
func image(withSize size: NSSize) -> NSImage {
|
||||
let image = NSImage(size: size)
|
||||
image.lockFocus()
|
||||
self.set()
|
||||
NSRect(origin: NSPoint.zero, size: size).fill()
|
||||
image.unlockFocus()
|
||||
return image
|
||||
}
|
||||
|
||||
// Returns either black or white, whichever will contrast the most with the given color.
|
||||
// See https://stackoverflow.com/a/3943023/5552584
|
||||
func bestContrastingColor() -> NSColor {
|
||||
|
||||
@@ -15,29 +15,28 @@ class PPOverlayController: NSWindowController {
|
||||
|
||||
// Outlets relating to the picker's info box.
|
||||
@IBOutlet weak var infoPanel: PPOverlayPanel!
|
||||
@IBOutlet weak var infoBox: NSBox!
|
||||
@IBOutlet weak var infoWrapper: NSView!
|
||||
@IBOutlet weak var infoFormatField: NSTextField!
|
||||
@IBOutlet weak var infoDetailField: NSTextField!
|
||||
|
||||
// The current magnification level of the preview.
|
||||
// TODO: make this somewhat configurable?
|
||||
var magnification: CGFloat = 8.0
|
||||
|
||||
// This mode increases the picker's size, increases the magnification and also
|
||||
// slows down mouseMove events to make it easier to pick the right pixel.
|
||||
// This mode increases the picker's size, increases the magnification and also slows down move
|
||||
// events to make it easier to pick the right pixel. We dissociate the mouse (input) from the
|
||||
// mouse cursor while the concentrationMode is active. This is so we can slow it down.
|
||||
var concentrationMode: Bool = false {
|
||||
didSet {
|
||||
if isEnabled {
|
||||
panelSize = concentrationMode ? 300 : 150
|
||||
panelSize = concentrationMode ? PPOverlayController.panelSizeLarge : PPOverlayController.panelSizeNormal
|
||||
overlayPanel.activate(withSize: panelSize, infoPanel: infoPanel)
|
||||
wrapper.layer?.cornerRadius = PPState.shared.paschaModeEnabled ? 0 : panelSize / 2
|
||||
CGAssociateMouseAndMouseCursorPosition(boolean_t(truncating: concentrationMode ? 0 : 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The size of the pixel picker.
|
||||
// TODO: use constants rather than hard-coded values.
|
||||
private var panelSize: CGFloat = 150
|
||||
// The current and different sizes of the pixel picker.
|
||||
private static let panelSizeNormal: CGFloat = 150
|
||||
private static let panelSizeLarge: CGFloat = 300
|
||||
private var panelSize: CGFloat = PPOverlayController.panelSizeNormal
|
||||
|
||||
// The app that was last active before the picker was activated. We keep track
|
||||
// of this in order to fully restore first responder status after picking a pixel.
|
||||
@@ -50,23 +49,24 @@ class PPOverlayController: NSWindowController {
|
||||
private var lastHighlightedColor: NSColor = NSColor.black
|
||||
|
||||
// Whether or not the picker should be actively updating its preview.
|
||||
// We dissociate the mouse (input) from the mouse cursor while the picker is active.
|
||||
// This is so we can slow down the mouse movement during concentration mode.
|
||||
private var isEnabled: Bool = false {
|
||||
didSet {
|
||||
if isEnabled {
|
||||
lastMouseLocation = NSEvent.mouseLocation
|
||||
startMonitoringEvents()
|
||||
CGAssociateMouseAndMouseCursorPosition(boolean_t(truncating: 0))
|
||||
} else {
|
||||
stopMonitoringEvents()
|
||||
concentrationMode = false
|
||||
CGAssociateMouseAndMouseCursorPosition(boolean_t(truncating: 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func awakeFromNib() {
|
||||
// Setup the info window.
|
||||
infoWrapper.wantsLayer = true
|
||||
infoWrapper.layer?.cornerRadius = 8
|
||||
infoWrapper.layer?.backgroundColor = NSColor.black.cgColor
|
||||
|
||||
// For some reason if we don't listen for events this way we miss the escape key.
|
||||
NSEvent.addLocalMonitorForEvents(matching: [.keyDown]) {
|
||||
if self.isEnabled { self.keyDown(with: $0) }
|
||||
@@ -118,24 +118,30 @@ class PPOverlayController: NSWindowController {
|
||||
// slow it down to make it easier to pick the correct pixel.
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
if isEnabled {
|
||||
let speed: CGFloat = concentrationMode ? 0.1 : 0.5
|
||||
let currentMouseLocation = NSEvent.mouseLocation
|
||||
var nextMouseLocation = currentMouseLocation
|
||||
|
||||
let x = lastMouseLocation.x + (event.deltaX * speed)
|
||||
let y = lastMouseLocation.y - (event.deltaY * speed)
|
||||
// Slow down tracking speed when concentration mode is active.
|
||||
if concentrationMode {
|
||||
let speed: CGFloat = concentrationMode ? 0.1 : 0.5
|
||||
let x = lastMouseLocation.x + (event.deltaX * speed)
|
||||
let y = lastMouseLocation.y - (event.deltaY * speed)
|
||||
|
||||
// Ensure the picker doesn't travel off screen.
|
||||
var nextMouseLocation = NSPoint(x: x, y: y)
|
||||
for screen in NSScreen.screens {
|
||||
let outsideCoordinate = coordinateOutsideRect(nextMouseLocation, screen.frame)
|
||||
if NSMouseInRect(currentMouseLocation, screen.frame, false) && outsideCoordinate != .none {
|
||||
if outsideCoordinate == .x { nextMouseLocation.x = currentMouseLocation.x }
|
||||
if outsideCoordinate == .y { nextMouseLocation.y = currentMouseLocation.y }
|
||||
if outsideCoordinate == .both { nextMouseLocation = currentMouseLocation }
|
||||
// Ensure the picker doesn't travel off screen.
|
||||
nextMouseLocation = NSPoint(x: x, y: y)
|
||||
for screen in NSScreen.screens {
|
||||
let outlier = Coordinate.isOutsideRect(nextMouseLocation, screen.frame)
|
||||
if NSMouseInRect(currentMouseLocation, screen.frame, false) && outlier != .none {
|
||||
if outlier == .x { nextMouseLocation.x = currentMouseLocation.x }
|
||||
if outlier == .y { nextMouseLocation.y = currentMouseLocation.y }
|
||||
if outlier == .both { nextMouseLocation = currentMouseLocation }
|
||||
}
|
||||
}
|
||||
|
||||
// Since we've disassociated the mouse input with the cursor, we manually move it.
|
||||
CGWarpMouseCursorPosition(convertToCGCoordinateSystem(nextMouseLocation))
|
||||
}
|
||||
|
||||
CGWarpMouseCursorPosition(convertToCGCoordinateSystem(nextMouseLocation))
|
||||
updatePreview(aroundPoint: nextMouseLocation)
|
||||
lastMouseLocation = nextMouseLocation
|
||||
}
|
||||
@@ -209,7 +215,7 @@ class PPOverlayController: NSWindowController {
|
||||
|
||||
// Updates the info panel with the correct colors and text.
|
||||
func updateInfoPanel(_ color: NSColor, _ contrastingColor: NSColor) {
|
||||
infoBox.fillColor = color.alphaComponent == 0 ? NSColor.black : color
|
||||
infoWrapper.layer?.backgroundColor = color.cgColor
|
||||
infoFormatField.textColor = contrastingColor
|
||||
infoDetailField.textColor = contrastingColor
|
||||
infoFormatField.stringValue = PPState.shared.chosenFormat.rawValue
|
||||
@@ -232,9 +238,13 @@ class PPOverlayController: NSWindowController {
|
||||
overlayPanel.activate(withSize: panelSize, infoPanel: infoPanel)
|
||||
}
|
||||
|
||||
// Center the mouse in the picker window.
|
||||
overlayPanel.setFrameOrigin(NSPoint(x: point.x - (panelSize / 2), y: point.y - (panelSize / 2)))
|
||||
let normalisedPoint = NSPoint(x: round(point.x * 2) / 2, y: round(point.y * 2) / 2)
|
||||
if let screenShot = getScreenShot(aroundPoint: normalisedPoint) {
|
||||
|
||||
// Calculate a zoomed rect which will crop the screenshot we took.
|
||||
let magnification = CGFloat(PPState.shared.magnificationLevel)
|
||||
let zoomReciprocal: CGFloat = 1.0 / (concentrationMode ? magnification * 2.5 : magnification)
|
||||
let currentSize = CGFloat(screenShot.width) + 1
|
||||
let origin = floor(currentSize * ((1 - zoomReciprocal) / 2))
|
||||
@@ -243,28 +253,115 @@ class PPOverlayController: NSWindowController {
|
||||
|
||||
// Ensure preview size is an odd number (so there's a middle pixel).
|
||||
let zoomedSize = floor(ensureOdd(currentSize * zoomReciprocal))
|
||||
let middle = zoomedSize / 2
|
||||
|
||||
let croppedRect = CGRect(x: x, y: y, width: zoomedSize, height: zoomedSize)
|
||||
let zoomedImage: CGImage = screenShot.cropping(to: croppedRect)!
|
||||
let pixelSize = panelSize / zoomedSize
|
||||
let middlePosition = zoomedSize / 2
|
||||
|
||||
// Extract the middle pixel color from the zoomed image.
|
||||
let bitmap = NSBitmapImageRep(cgImage: zoomedImage)
|
||||
bitmap.colorSpaceName = .calibratedRGB
|
||||
if let colorAtPixel = bitmap.colorAt(x: Int(middle), y: Int(middle)) {
|
||||
let contrastingColor = colorAtPixel.bestContrastingColor()
|
||||
// Convert the preview to the correct color space, and update the overlay.
|
||||
|
||||
preview.layer?.contents = NSImage(cgImage: zoomedImage, size: NSSize(width: panelSize, height: panelSize))
|
||||
preview.updateCrosshair(panelSize / zoomedSize, middle, contrastingColor.cgColor)
|
||||
wrapper.update(contrastingColor.cgColor)
|
||||
updateInfoPanel(colorAtPixel, contrastingColor)
|
||||
|
||||
// Save color under pixel (used when copied).
|
||||
lastHighlightedColor = colorAtPixel
|
||||
// User has chosen a custom color space.
|
||||
if let colorSpace = getChosenColorSpace(point) {
|
||||
if colorSpace == zoomedImage.colorSpace {
|
||||
return updatePreview(zoomedImage, pixelSize, middlePosition)
|
||||
} else if let image = zoomedImage.copy(colorSpace: colorSpace) {
|
||||
return updatePreview(image, pixelSize, middlePosition)
|
||||
}
|
||||
}
|
||||
|
||||
// No custom color space chosen, so use the screen's one.
|
||||
if let colorSpace = getScreenColorSpace(point) {
|
||||
if colorSpace == zoomedImage.colorSpace {
|
||||
return updatePreview(zoomedImage, pixelSize, middlePosition)
|
||||
} else if let image = zoomedImage.copy(colorSpace: colorSpace) {
|
||||
return updatePreview(image, pixelSize, middlePosition)
|
||||
}
|
||||
}
|
||||
|
||||
// If that also didn't work, then just return the image in its default color space.
|
||||
return updatePreview(zoomedImage, pixelSize, middlePosition)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePreview(_ image: CGImage, _ pixelSize: CGFloat, _ middlePosition: CGFloat) {
|
||||
// Extract the middle pixel color from the prepared image.
|
||||
let colorAtPixel = image.colorAt(x: Int(middlePosition), y: Int(middlePosition))
|
||||
let contrastingColor = colorAtPixel.bestContrastingColor()
|
||||
|
||||
preview.layer?.contents = image
|
||||
preview.updateCrosshair(pixelSize, middlePosition, contrastingColor.cgColor)
|
||||
wrapper.update(contrastingColor.cgColor)
|
||||
updateInfoPanel(colorAtPixel, contrastingColor)
|
||||
|
||||
// Save color under pixel (used when copied).
|
||||
lastHighlightedColor = colorAtPixel
|
||||
}
|
||||
|
||||
private func getScreenColorSpace(_ point: NSPoint) -> CGColorSpace? {
|
||||
return getScreenFromPoint(point)?.colorSpace?.cgColorSpace
|
||||
}
|
||||
|
||||
// Gets the colorspace in which to display the preview and retreive color values.
|
||||
private func getChosenColorSpace(_ point: NSPoint) -> CGColorSpace? {
|
||||
if let name = PPState.shared.colorSpace, let colorSpace = CGColorSpace(name: name as CFString) {
|
||||
return colorSpace
|
||||
}
|
||||
|
||||
// Center the mouse in the picker window.
|
||||
overlayPanel.setFrameOrigin(NSPoint(x: normalisedPoint.x - (panelSize / 2), y: normalisedPoint.y - (panelSize / 2)))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension CGImage {
|
||||
// In order to get the color at a given pixel from a CGImage, we need to convert the CGImage's
|
||||
// data to a bitmap and draw it. We do so via a CGContext, and then manually extract the data at
|
||||
// the desired pixel.
|
||||
func colorAt(x: Int, y: Int) -> NSColor {
|
||||
assert(0 <= x && x < self.width)
|
||||
assert(0 <= y && y < self.height)
|
||||
|
||||
let bitmapBytesPerRow = width * 4
|
||||
let bitmapByteCount = bitmapBytesPerRow * Int(self.height)
|
||||
|
||||
// Allocate memory for image data. This is the destination in memory where any drawing to
|
||||
// the bitmap context will be rendered.
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
let bitmapData = malloc(bitmapByteCount)
|
||||
|
||||
// Since we manually allocate memeory for the data, we must ensure that the same memory is
|
||||
// freed after we've used it.
|
||||
defer { free(bitmapData) }
|
||||
|
||||
// Create the bitmap context.
|
||||
let context = CGContext(
|
||||
data: bitmapData,
|
||||
width: self.width,
|
||||
height: self.height,
|
||||
bitsPerComponent: 8,
|
||||
bytesPerRow: bitmapBytesPerRow,
|
||||
space: CGColorSpaceCreateDeviceRGB(),
|
||||
bitmapInfo: bitmapInfo.rawValue
|
||||
)
|
||||
|
||||
// Extract the pixel data from the right offset.
|
||||
if context != nil {
|
||||
// First, we draw the image data onto the context we created.
|
||||
let rect = CGRect(x: 0, y: 0, width: self.width, height: self.height)
|
||||
context!.draw(self, in: rect)
|
||||
|
||||
// Then we extract the data at the right spot.
|
||||
let data = context!.data!
|
||||
let offset = 4 * (y * width + x)
|
||||
|
||||
let a = CGFloat(data.load(fromByteOffset: offset, as: UInt8.self)) / 255.0
|
||||
let r = CGFloat(data.load(fromByteOffset: offset + 1, as: UInt8.self)) / 255.0
|
||||
let g = CGFloat(data.load(fromByteOffset: offset + 2, as: UInt8.self)) / 255.0
|
||||
let b = CGFloat(data.load(fromByteOffset: offset + 3, as: UInt8.self)) / 255.0
|
||||
return NSColor(red: r, green: g, blue: b, alpha: a)
|
||||
}
|
||||
|
||||
// Creating the context failed, so return a default color instead.
|
||||
// Hopefully, this should never happen.
|
||||
Log.error?.message("Failed to create CGContext!")
|
||||
return NSColor.black
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14109"/>
|
||||
<capability name="box content view" minToolsVersion="7.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -21,10 +20,10 @@
|
||||
</customObject>
|
||||
<customObject id="ECQ-9M-Pkg" customClass="PPOverlayController" customModule="PixelPicker" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="infoBox" destination="rpe-Xh-gaO" id="BYW-qP-HLd"/>
|
||||
<outlet property="infoDetailField" destination="Re2-GH-fyd" id="K03-75-cf9"/>
|
||||
<outlet property="infoFormatField" destination="1fa-UE-yMM" id="Nan-fO-kbu"/>
|
||||
<outlet property="infoPanel" destination="OMO-LB-w57" id="GGj-Y6-LfU"/>
|
||||
<outlet property="infoWrapper" destination="HED-rR-FDP" id="21H-Tc-c4W"/>
|
||||
<outlet property="overlayPanel" destination="gZA-A3-IZ5" id="DUd-MU-oD4"/>
|
||||
<outlet property="preview" destination="oj3-ml-2qo" id="xLc-pu-U90"/>
|
||||
<outlet property="window" destination="gZA-A3-IZ5" id="GLv-sO-WxM"/>
|
||||
@@ -35,7 +34,7 @@
|
||||
<windowStyleMask key="styleMask" utility="YES" nonactivatingPanel="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="132" width="101" height="101"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
|
||||
<view key="contentView" id="acW-WA-gq2" customClass="PPOverlayWrapper" customModule="PixelPicker" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="101" height="101"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
@@ -56,50 +55,35 @@
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" restorable="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="OMO-LB-w57" customClass="PPOverlayPanel" customModule="PixelPicker" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" utility="YES" nonactivatingPanel="YES"/>
|
||||
<rect key="contentRect" x="926" y="538" width="100" height="50"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1178"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1028"/>
|
||||
<view key="contentView" id="HED-rR-FDP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<box boxType="custom" borderType="none" cornerRadius="4" title="Box" translatesAutoresizingMaskIntoConstraints="NO" id="rpe-Xh-gaO">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
|
||||
<view key="contentView" id="g1f-dN-SYl">
|
||||
<rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1fa-UE-yMM">
|
||||
<rect key="frame" x="-2" y="28" width="104" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="RkG-Wy-Shq">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Re2-GH-fyd">
|
||||
<rect key="frame" x="-2" y="5" width="104" height="14"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="IYd-1Y-7cd">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="1fa-UE-yMM" firstAttribute="top" secondItem="g1f-dN-SYl" secondAttribute="top" constant="5" id="OMX-Uf-gxc"/>
|
||||
<constraint firstAttribute="trailing" secondItem="1fa-UE-yMM" secondAttribute="trailing" id="YWA-wF-wWD"/>
|
||||
<constraint firstItem="Re2-GH-fyd" firstAttribute="leading" secondItem="g1f-dN-SYl" secondAttribute="leading" id="def-Ev-ONx"/>
|
||||
<constraint firstItem="1fa-UE-yMM" firstAttribute="leading" secondItem="g1f-dN-SYl" secondAttribute="leading" id="j0J-F5-ipt"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Re2-GH-fyd" secondAttribute="bottom" constant="5" id="ktF-UW-gVI"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Re2-GH-fyd" secondAttribute="trailing" id="t9s-4R-bFb"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1fa-UE-yMM">
|
||||
<rect key="frame" x="-2" y="25" width="104" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="RkG-Wy-Shq">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Re2-GH-fyd">
|
||||
<rect key="frame" x="-2" y="8" width="104" height="14"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="IYd-1Y-7cd">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="rpe-Xh-gaO" firstAttribute="leading" secondItem="HED-rR-FDP" secondAttribute="leading" id="Buc-en-Way"/>
|
||||
<constraint firstItem="rpe-Xh-gaO" firstAttribute="top" secondItem="HED-rR-FDP" secondAttribute="top" id="NrK-e2-suw"/>
|
||||
<constraint firstAttribute="bottom" secondItem="rpe-Xh-gaO" secondAttribute="bottom" id="WSu-Ro-a4K"/>
|
||||
<constraint firstAttribute="trailing" secondItem="rpe-Xh-gaO" secondAttribute="trailing" id="myM-UI-QfA"/>
|
||||
<constraint firstItem="1fa-UE-yMM" firstAttribute="leading" secondItem="HED-rR-FDP" secondAttribute="leading" id="23z-pW-nAx"/>
|
||||
<constraint firstItem="1fa-UE-yMM" firstAttribute="top" secondItem="HED-rR-FDP" secondAttribute="top" constant="8" id="G1j-j5-iWY"/>
|
||||
<constraint firstItem="Re2-GH-fyd" firstAttribute="leading" secondItem="HED-rR-FDP" secondAttribute="leading" id="aZf-QY-Lpd"/>
|
||||
<constraint firstAttribute="bottom" secondItem="Re2-GH-fyd" secondAttribute="bottom" constant="8" id="amm-jV-oWa"/>
|
||||
<constraint firstAttribute="trailing" secondItem="1fa-UE-yMM" secondAttribute="trailing" id="flM-Te-2qj"/>
|
||||
<constraint firstAttribute="trailing" secondItem="Re2-GH-fyd" secondAttribute="trailing" id="jWO-5f-uxW"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="236" y="230"/>
|
||||
|
||||
@@ -13,28 +13,27 @@ class PPOverlayPreview: NSView, CALayerDelegate {
|
||||
override var wantsUpdateLayer: Bool {
|
||||
get { return true }
|
||||
}
|
||||
|
||||
|
||||
override func awakeFromNib() {
|
||||
// Make layers contents resize to fill, and disable antialiasing.
|
||||
wantsLayer = true
|
||||
layer?.magnificationFilter = kCAFilterNearest
|
||||
layer?.contentsGravity = kCAGravityResizeAspectFill
|
||||
layer?.delegate = self
|
||||
|
||||
|
||||
// Prepare crosshair shape layers.
|
||||
crosshair.strokeColor = NSColor.black.cgColor
|
||||
crosshair.fillColor = nil
|
||||
layer?.addSublayer(crosshair)
|
||||
}
|
||||
|
||||
|
||||
// Update the crosshair with the correct color, position and size.
|
||||
func updateCrosshair(_ pixelSize: CGFloat, _ middle: CGFloat, _ color: CGColor) {
|
||||
let pos: CGFloat = (pixelSize * middle) - (pixelSize / 2)
|
||||
let pixelRect = NSMakeRect(pos, pos, pixelSize, pixelSize)
|
||||
let outerRect = pixelRect.insetBy(dx: -1, dy: -1)
|
||||
|
||||
crosshair.path = CGPath(rect: outerRect, transform: nil)
|
||||
crosshair.path = CGPath(rect: pixelRect, transform: nil)
|
||||
crosshair.strokeColor = color
|
||||
setNeedsDisplay(outerRect)
|
||||
setNeedsDisplay(pixelRect)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,42 +5,30 @@
|
||||
|
||||
import SwiftyJSON
|
||||
|
||||
// This allows us to iterate over the raw values of an enum.
|
||||
// TODO: use `CaseIterable` when Swift 4.2 comes out
|
||||
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
|
||||
var i = 0
|
||||
return AnyIterator {
|
||||
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
|
||||
if next.hashValue != i { return nil }
|
||||
i += 1
|
||||
return next
|
||||
}
|
||||
}
|
||||
|
||||
// Each time the user picks a pixel we save it as the format and the color.
|
||||
struct PPPickedColor {
|
||||
let color: NSColor
|
||||
let format: PPColor
|
||||
|
||||
|
||||
init(color: NSColor, format: PPColor) {
|
||||
self.color = color
|
||||
self.format = format
|
||||
}
|
||||
|
||||
|
||||
init?(fromJSON json: JSON) {
|
||||
guard
|
||||
let formatString = json["format"].string,
|
||||
let format = PPColor(rawValue: formatString),
|
||||
let color = NSColor.deserialize(fromJson: json["color"])
|
||||
else { return nil }
|
||||
|
||||
|
||||
self.init(color: color, format: format)
|
||||
}
|
||||
|
||||
|
||||
var asString: String {
|
||||
return format.asString(withColor: color)
|
||||
}
|
||||
|
||||
|
||||
var asJSON: JSON {
|
||||
return [
|
||||
"color": NSColor.serialize(self.color),
|
||||
@@ -91,10 +79,10 @@ enum PPColor: String {
|
||||
let f = self.insertFloatPrecisionFormatter
|
||||
let c = colorInCorrectColorSpace(passedColor)
|
||||
switch self {
|
||||
case .genericHex: return self.formatAsHex(c, "%2X")
|
||||
case .genericHex: return self.formatAsHex(c, "%06x")
|
||||
case .generic8Bit: return self.formatAs8Bit(c, "%u, %u, %u")
|
||||
case .genericDecimal: return self.formatAsDecimal(c, f("%f, %f, %f"), .rgb)
|
||||
case .cssHex: return self.formatAsHex(c, "#%2X")
|
||||
case .cssHex: return self.formatAsHex(c, "#%06x")
|
||||
case .cssRgb: return self.formatAs8Bit(c, "rgb(%u, %u, %u)")
|
||||
case .cssRgba: return self.formatAs8Bit(c, "rgba(%u, %u, %u, 1)")
|
||||
case .cssHsl: return self.formatAsHSL(c, "hsl(%u, %u%%, %u%%)")
|
||||
@@ -119,8 +107,8 @@ enum PPColor: String {
|
||||
case .javaRgba: return self.formatAs8Bit(c, "new Color(%u, %u, %u, 255)")
|
||||
case .androidRgb: return self.formatAs8Bit(c, "Color.rgb(%u, %u, %u)")
|
||||
case .androidArgb: return self.formatAs8Bit(c, "Color.argb(255, %u, %u, %u)")
|
||||
case .androidXmlRgb: return self.formatAsHex(c, "<color name=\"color_name\">#%2X</color>")
|
||||
case .androidXmlArgb: return self.formatAsHex(c, "<color name=\"color_name\">#ff%2X</color>")
|
||||
case .androidXmlRgb: return self.formatAsHex(c, "<color name=\"color_name\">#%06x</color>")
|
||||
case .androidXmlArgb: return self.formatAsHex(c, "<color name=\"color_name\">#ff%06x</color>")
|
||||
case .cgColorRgb: return self.formatAsDecimal(c, f("CGColorCreateGenericRGB(%f, %f, %f, 1.000)"), .rgb)
|
||||
case .openGlRgb: return self.formatAsDecimal(c, f("glColor3f(%f, %f, %f)"), .rgb)
|
||||
case .openGlRgba: return self.formatAsDecimal(c, f("glColor4f(%f, %f, %f, 1.000)"), .rgb)
|
||||
@@ -136,7 +124,7 @@ enum PPColor: String {
|
||||
case .androidXmlRgb: fallthrough
|
||||
case .androidXmlArgb: fallthrough
|
||||
case .cgColorRgb: fallthrough
|
||||
case .genericHex: return self.formatAsHex(color, "%2X")
|
||||
case .genericHex: return self.formatAsHex(color, "%06x")
|
||||
// CSS HSL is a unique format.
|
||||
case .cssHsl: fallthrough
|
||||
case .cssHsla: return self.formatAsHSL(color, "%u, %u%%, %u%%")
|
||||
@@ -170,17 +158,17 @@ enum PPColor: String {
|
||||
case .objCUIColorHsb: return self.formatAsDecimal(color, self.insertFloatPrecisionFormatter("%f, %f, %f"), .rgb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns the PPColor that sits after this one.
|
||||
func next() -> PPColor {
|
||||
return next(withArray: iterateEnum(PPColor.self).map({ $0 }))
|
||||
}
|
||||
|
||||
|
||||
// Same as next() but backwards.
|
||||
func previous() -> PPColor {
|
||||
return next(withArray: iterateEnum(PPColor.self).reversed())
|
||||
}
|
||||
|
||||
|
||||
// Finds the next element after this element in the given array.
|
||||
// This method should only be passed the result of iterateEnum(PPColor.self).
|
||||
private func next(withArray array: [PPColor]) -> PPColor {
|
||||
@@ -192,26 +180,26 @@ enum PPColor: String {
|
||||
|
||||
return array.first!
|
||||
}
|
||||
|
||||
|
||||
// A tiny enum that describes which components should be used when formatting.
|
||||
private enum Components {
|
||||
case rgb
|
||||
case hsb
|
||||
}
|
||||
|
||||
|
||||
// Replaces "%f" with "%.3f" (if "3" is the current precision level).
|
||||
private func insertFloatPrecisionFormatter(_ input: String) -> String {
|
||||
return input.replacingOccurrences(of: "%f", with: String(format: "%%.%uf", PPState.shared.floatPrecision))
|
||||
}
|
||||
|
||||
|
||||
// Formats the colors as a hex value, eg: "D3504E".
|
||||
private func formatAsHex(_ color: NSColor, _ template: String) -> String {
|
||||
let a = Int(color.redComponent * 255) << 16
|
||||
let b = Int(color.greenComponent * 255) << 8
|
||||
let c = Int(color.blueComponent * 255) << 0
|
||||
return String(format: template, a | b | c)
|
||||
let r = min(Int(color.redComponent * 0x100), 0x0ff) << 16
|
||||
let g = min(Int(color.greenComponent * 0x100), 0x0ff) << 8
|
||||
let b = min(Int(color.blueComponent * 0x100), 0x0ff) << 0
|
||||
return String(format: template, (r | g | b))
|
||||
}
|
||||
|
||||
|
||||
// Formats the color as decimal values, eg: "0.145, 0.361, 0.722".
|
||||
private func formatAsDecimal(_ color: NSColor, _ template: String, _ cmp: Components) -> String {
|
||||
let a = cmp == .rgb ? color.redComponent : color.hueComponent
|
||||
@@ -219,23 +207,23 @@ enum PPColor: String {
|
||||
let c = cmp == .rgb ? color.blueComponent : color.brightnessComponent
|
||||
return String(format: template, a, b, c)
|
||||
}
|
||||
|
||||
|
||||
// Formats the color as 8-bit values, eg: "158, 198, 117".
|
||||
private func formatAs8Bit(_ color: NSColor, _ template: String) -> String {
|
||||
let a = Int(color.redComponent * 255)
|
||||
let b = Int(color.greenComponent * 255)
|
||||
let c = Int(color.blueComponent * 255)
|
||||
return String(format: template, a, b, c)
|
||||
let r = min(Int(color.redComponent * 0x100), 0x0ff)
|
||||
let g = min(Int(color.greenComponent * 0x100), 0x0ff)
|
||||
let b = min(Int(color.blueComponent * 0x100), 0x0ff)
|
||||
return String(format: template, r, g, b)
|
||||
}
|
||||
|
||||
|
||||
// Special formatter since CSS uses a unique style here, eg: "35, 79%, 47%".
|
||||
private func formatAsHSL(_ color: NSColor, _ template: String) -> String {
|
||||
let a = Int(color.hueComponent * 360)
|
||||
let b = Int(color.saturationComponent * 100)
|
||||
let c = Int(color.brightnessComponent * 100)
|
||||
return String(format: template, a, b, c)
|
||||
let h = min(Int(color.hueComponent * 360), 359)
|
||||
let s = Int(color.saturationComponent * 100)
|
||||
let b = Int(color.brightnessComponent * 100)
|
||||
return String(format: template, h, s, b)
|
||||
}
|
||||
|
||||
|
||||
// Returns a new color in the correct colorspace for the format.
|
||||
private func colorInCorrectColorSpace(_ color: NSColor) -> NSColor {
|
||||
switch self {
|
||||
@@ -253,4 +241,29 @@ enum PPColor: String {
|
||||
return color
|
||||
}
|
||||
}
|
||||
|
||||
// Available CGColorSpaces that can be chosen.
|
||||
// NOTE: the commented out color spaces don't work at all on my screens, they may work for other
|
||||
// screens, but here they're left out since they're probably not common/necessary. If you're
|
||||
// reading this and you want to use them, make an issue on GitHub and then we can test them.
|
||||
static let colorSpaceNames = [
|
||||
// ("Generic CMYK", CGColorSpace.genericCMYK as String),
|
||||
("Generic XYZ", CGColorSpace.genericXYZ as String),
|
||||
("Generic RGB Linear", CGColorSpace.genericRGBLinear as String),
|
||||
// ("Generic Gray Gamma 2.2", CGColorSpace.genericGrayGamma2_2 as String),
|
||||
("ACESCG Linear", CGColorSpace.acescgLinear as String),
|
||||
("Adobe RGB 1998", CGColorSpace.adobeRGB1998 as String),
|
||||
("DCIP3", CGColorSpace.dcip3 as String),
|
||||
("Display P3", CGColorSpace.displayP3 as String),
|
||||
// ("Linear Gray", CGColorSpace.linearGray as String),
|
||||
("Linear sRGB", CGColorSpace.linearSRGB as String),
|
||||
// ("Extended Linear Gray", CGColorSpace.extendedLinearGray as String),
|
||||
("Extended Linear sRGB", CGColorSpace.extendedLinearSRGB as String),
|
||||
// ("Extended Gray", CGColorSpace.extendedGray as String),
|
||||
("Extended sRGB", CGColorSpace.extendedSRGB as String),
|
||||
("ITUR 2020", CGColorSpace.itur_2020 as String),
|
||||
("ITUR 709", CGColorSpace.itur_709 as String),
|
||||
("ROMM RGB", CGColorSpace.rommrgb as String),
|
||||
("sRGB", CGColorSpace.sRGB as String)
|
||||
]
|
||||
}
|
||||
|
||||
@@ -24,17 +24,24 @@ import CleanroomLogger
|
||||
let defaults: UserDefaults = UserDefaults.standard
|
||||
|
||||
// Whether the picker should be square or not.
|
||||
// TODO: implement shortcut
|
||||
var paschaModeEnabled: Bool = false
|
||||
|
||||
// The shortcut that activates the pixel picker.
|
||||
var activatingShortcut: MASShortcut?
|
||||
|
||||
// Magnification level of the picker.
|
||||
var magnificationLevel: Int = 8
|
||||
|
||||
// Hold this down to enter concentration mode.
|
||||
var concentrationModeModifier: NSEvent.ModifierFlags = .control
|
||||
|
||||
// The currently chosen format.
|
||||
var chosenFormat: PPColor = .genericHex
|
||||
|
||||
// The name of the chosen color space. If this is nil, then the picker will attempt to get the
|
||||
// color space of the screen the picker is currently on (and if that fails, fall back to the
|
||||
// default color space of the screenshot).
|
||||
var colorSpace: String? = nil
|
||||
|
||||
// How precise floats should be when copied.
|
||||
var floatPrecision: UInt = 3
|
||||
@@ -98,8 +105,16 @@ import CleanroomLogger
|
||||
activatingShortcut = shortcut
|
||||
}
|
||||
}
|
||||
case "colorSpace":
|
||||
// Check for the presence of the color space in our list. If it's found, then we
|
||||
// can use it (safeguard against bogus input).
|
||||
if let _ = PPColor.colorSpaceNames.index(where: { $0.1 == value.string }) {
|
||||
colorSpace = value.string
|
||||
}
|
||||
case "chosenFormat":
|
||||
chosenFormat = PPColor(rawValue: value.stringValue) ?? .genericHex
|
||||
case "magnificationLevel":
|
||||
magnificationLevel = value.int ?? 8
|
||||
case "floatPrecision":
|
||||
let n = value.uInt ?? 3
|
||||
floatPrecision = (n > 0 && n < PPState.maxFloatPrecision) ? n : 3
|
||||
@@ -137,6 +152,8 @@ import CleanroomLogger
|
||||
"paschaModeEnabled": paschaModeEnabled,
|
||||
"concentrationModeModifier": concentrationModeModifier.rawValue,
|
||||
"activatingShortcut": shortcutData,
|
||||
"magnificationLevel": magnificationLevel,
|
||||
"colorSpace": colorSpace ?? "",
|
||||
"chosenFormat": chosenFormat.rawValue,
|
||||
"floatPrecision": floatPrecision,
|
||||
"recentPicks": recentPicks.map({ $0.asJSON })
|
||||
|
||||
@@ -8,6 +8,18 @@ import CleanroomLogger
|
||||
let APP_NAME = Bundle.main.infoDictionary![kCFBundleNameKey as String] as! String
|
||||
let APPLE_INTERFACE_STYLE = "AppleInterfaceStyle"
|
||||
|
||||
// This allows us to iterate over the raw values of an enum.
|
||||
// TODO: use `CaseIterable` when Swift 4.2 comes out
|
||||
func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
|
||||
var i = 0
|
||||
return AnyIterator {
|
||||
let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
|
||||
if next.hashValue != i { return nil }
|
||||
i += 1
|
||||
return next
|
||||
}
|
||||
}
|
||||
|
||||
// Copies the given string to the clipboard.
|
||||
func copyToPasteboard(stringValue value: String) {
|
||||
NSPasteboard.general.declareTypes([.string], owner: nil)
|
||||
@@ -42,14 +54,14 @@ func getScreenFromPoint(_ point: NSPoint) -> NSScreen? {
|
||||
// Returns which coordinates are outside, if any.
|
||||
enum Coordinate {
|
||||
case x, y, both, none
|
||||
}
|
||||
func coordinateOutsideRect(_ point: NSPoint, _ rect: NSRect) -> Coordinate {
|
||||
let x = point.x <= rect.origin.x || point.x >= (rect.origin.x + rect.width)
|
||||
let y = point.y <= rect.origin.y || point.y >= (rect.origin.y + rect.height)
|
||||
if x && y { return .both }
|
||||
if x { return .x }
|
||||
if y { return .y }
|
||||
return .none
|
||||
static func isOutsideRect(_ point: NSPoint, _ rect: NSRect) -> Coordinate {
|
||||
let x = point.x < rect.origin.x || point.x > (rect.origin.x + rect.width)
|
||||
let y = point.y < rect.origin.y || point.y > (rect.origin.y + rect.height)
|
||||
if x && y { return .both }
|
||||
if x { return .x }
|
||||
if y { return .y }
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
// A simple helper to run animations with the same context configration.
|
||||
|
||||
@@ -15,11 +15,7 @@ PixelPicker is like Digital Color Meter, but lives in your menu bar and lets you
|
||||
|
||||
## Installation
|
||||
|
||||
Coming soon.
|
||||
|
||||
<!--
|
||||
Simply download the dmg from the [releases](https://github.com/acheronfail/pixel-picker/releases) tab and drag "Pixel Picker.app" into your `/Applications` folder.
|
||||
-->
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 333 KiB After Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 774 B After Width: | Height: | Size: 994 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 54 KiB |
@@ -1 +0,0 @@
|
||||
Versions/Current/Headers
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/Modules
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/Resources
|
||||
@@ -1 +0,0 @@
|
||||
Versions/Current/SwiftyJSON
|
||||
@@ -1,189 +0,0 @@
|
||||
// Generated by Apple Swift version 4.1 effective-3.3 (swiftlang-902.0.48 clang-902.0.37.1)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgcc-compat"
|
||||
|
||||
#if !defined(__has_include)
|
||||
# define __has_include(x) 0
|
||||
#endif
|
||||
#if !defined(__has_attribute)
|
||||
# define __has_attribute(x) 0
|
||||
#endif
|
||||
#if !defined(__has_feature)
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
#if !defined(__has_warning)
|
||||
# define __has_warning(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_include(<swift/objc-prologue.h>)
|
||||
# include <swift/objc-prologue.h>
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic ignored "-Wauto-import"
|
||||
#include <objc/NSObject.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#if !defined(SWIFT_TYPEDEFS)
|
||||
# define SWIFT_TYPEDEFS 1
|
||||
# if __has_include(<uchar.h>)
|
||||
# include <uchar.h>
|
||||
# elif !defined(__cplusplus)
|
||||
typedef uint_least16_t char16_t;
|
||||
typedef uint_least32_t char32_t;
|
||||
# endif
|
||||
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
|
||||
#endif
|
||||
|
||||
#if !defined(SWIFT_PASTE)
|
||||
# define SWIFT_PASTE_HELPER(x, y) x##y
|
||||
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
|
||||
#endif
|
||||
#if !defined(SWIFT_METATYPE)
|
||||
# define SWIFT_METATYPE(X) Class
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS_PROPERTY)
|
||||
# if __has_feature(objc_class_property)
|
||||
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
|
||||
# else
|
||||
# define SWIFT_CLASS_PROPERTY(...)
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if __has_attribute(objc_runtime_name)
|
||||
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
|
||||
#else
|
||||
# define SWIFT_RUNTIME_NAME(X)
|
||||
#endif
|
||||
#if __has_attribute(swift_name)
|
||||
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
|
||||
#else
|
||||
# define SWIFT_COMPILE_NAME(X)
|
||||
#endif
|
||||
#if __has_attribute(objc_method_family)
|
||||
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
|
||||
#else
|
||||
# define SWIFT_METHOD_FAMILY(X)
|
||||
#endif
|
||||
#if __has_attribute(noescape)
|
||||
# define SWIFT_NOESCAPE __attribute__((noescape))
|
||||
#else
|
||||
# define SWIFT_NOESCAPE
|
||||
#endif
|
||||
#if __has_attribute(warn_unused_result)
|
||||
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#else
|
||||
# define SWIFT_WARN_UNUSED_RESULT
|
||||
#endif
|
||||
#if __has_attribute(noreturn)
|
||||
# define SWIFT_NORETURN __attribute__((noreturn))
|
||||
#else
|
||||
# define SWIFT_NORETURN
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS_EXTRA)
|
||||
# define SWIFT_CLASS_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_PROTOCOL_EXTRA)
|
||||
# define SWIFT_PROTOCOL_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM_EXTRA)
|
||||
# define SWIFT_ENUM_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS)
|
||||
# if __has_attribute(objc_subclassing_restricted)
|
||||
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
|
||||
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# else
|
||||
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if !defined(SWIFT_PROTOCOL)
|
||||
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
|
||||
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
|
||||
#endif
|
||||
|
||||
#if !defined(SWIFT_EXTENSION)
|
||||
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
|
||||
#endif
|
||||
|
||||
#if !defined(OBJC_DESIGNATED_INITIALIZER)
|
||||
# if __has_attribute(objc_designated_initializer)
|
||||
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
|
||||
# else
|
||||
# define OBJC_DESIGNATED_INITIALIZER
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM_ATTR)
|
||||
# if defined(__has_attribute) && __has_attribute(enum_extensibility)
|
||||
# define SWIFT_ENUM_ATTR __attribute__((enum_extensibility(open)))
|
||||
# else
|
||||
# define SWIFT_ENUM_ATTR
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM)
|
||||
# define SWIFT_ENUM(_type, _name) enum _name : _type _name; enum SWIFT_ENUM_ATTR SWIFT_ENUM_EXTRA _name : _type
|
||||
# if __has_feature(generalized_swift_name)
|
||||
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR SWIFT_ENUM_EXTRA _name : _type
|
||||
# else
|
||||
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME) SWIFT_ENUM(_type, _name)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_UNAVAILABLE)
|
||||
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
|
||||
#endif
|
||||
#if !defined(SWIFT_UNAVAILABLE_MSG)
|
||||
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
|
||||
#endif
|
||||
#if !defined(SWIFT_AVAILABILITY)
|
||||
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
|
||||
#endif
|
||||
#if !defined(SWIFT_DEPRECATED)
|
||||
# define SWIFT_DEPRECATED __attribute__((deprecated))
|
||||
#endif
|
||||
#if !defined(SWIFT_DEPRECATED_MSG)
|
||||
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
|
||||
#endif
|
||||
#if __has_feature(attribute_diagnose_if_objc)
|
||||
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
|
||||
#else
|
||||
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
|
||||
#endif
|
||||
#if __has_feature(modules)
|
||||
@import Foundation;
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
|
||||
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
|
||||
#if __has_warning("-Wpragma-clang-attribute")
|
||||
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
|
||||
#endif
|
||||
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma clang diagnostic ignored "-Wnullability"
|
||||
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma push_macro("any")
|
||||
# undef any
|
||||
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="SwiftyJSON",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
|
||||
# pragma pop_macro("any")
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma clang attribute pop
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
@@ -1,31 +0,0 @@
|
||||
// SwiftyJSON.h
|
||||
//
|
||||
// Copyright (c) 2014 - 2017 Ruoyu Fu, Pinglin Tang
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
@import Foundation;
|
||||
|
||||
//! Project version number for SwiftyJSON.
|
||||
FOUNDATION_EXPORT double SwiftyJSONVersionNumber;
|
||||
|
||||
//! Project version string for SwiftyJSON.
|
||||
FOUNDATION_EXPORT const unsigned char SwiftyJSONVersionString[];
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
framework module SwiftyJSON {
|
||||
umbrella header "SwiftyJSON.h"
|
||||
|
||||
export *
|
||||
module * { export * }
|
||||
}
|
||||
|
||||
module SwiftyJSON.Swift {
|
||||
header "SwiftyJSON-Swift.h"
|
||||
requires objc
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>17E202</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SwiftyJSON</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.swiftyjson.SwiftyJSON</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>SwiftyJSON</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>9E501</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>GM</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>17E189</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>macosx10.13</string>
|
||||
<key>DTXcode</key>
|
||||
<string>0931</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>9E501</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1 +0,0 @@
|
||||
A
|
||||