7 Commits
15 changed files with 71 additions and 55 deletions
+6 -7
View File
@@ -256,13 +256,14 @@
TargetAttributes = {
15B4E86F207994AF0024FDE4 = {
CreatedOnToolsVersion = 9.3;
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
TestTargetID = B3B4C2BC1E25894B009F8E4E;
};
B3B4C2BC1E25894B009F8E4E = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = UNR5C68RPX;
LastSwiftMigration = 0920;
LastSwiftMigration = 1000;
ProvisioningStyle = Automatic;
};
};
@@ -389,7 +390,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.acheronfail.PixelPickerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PixelPicker.app/Contents/MacOS/PixelPicker";
};
name = Debug;
@@ -417,7 +418,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.acheronfail.PixelPickerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/PixelPicker.app/Contents/MacOS/PixelPicker";
};
name = Release;
@@ -559,8 +560,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "PixelPicker/Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Debug;
};
@@ -585,8 +585,7 @@
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "PixelPicker/Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
SWIFT_VERSION = 4.2;
};
name = Release;
};
+2 -2
View File
@@ -236,10 +236,9 @@ extension AppDelegate: NSMenuDelegate {
}
// 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() {
let submenu = NSMenu()
for format in iterateEnum(PPColor.self) {
for format in PPColor.allCases {
let formatItem = submenu.addItem(withTitle: format.rawValue, action: #selector(selectFormat(_:)), keyEquivalent: "")
formatItem.representedObject = format
if PPState.shared.chosenFormat == format { formatItem.state = .on }
@@ -282,6 +281,7 @@ extension AppDelegate: NSMenuDelegate {
contextMenu.addItem(withTitle: "Picker Shortcut", action: nil, keyEquivalent: "")
let shortcutView = MASShortcutView()
shortcutView.style = .flat
shortcutView.shortcutValue = PPState.shared.activatingShortcut
shortcutView.shortcutValueChange = { PPState.shared.activatingShortcut = $0?.shortcutValue }
+1 -1
View File
@@ -7,7 +7,7 @@ import MASShortcut
import CleanroomLogger
// The app's menu bar item.
let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(rawValue: "icon")))
let ICON = setupMenuBarIcon(NSImage(named: NSImage.Name(stringLiteral: "icon")))
@NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate {
+2 -2
View File
@@ -17,9 +17,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.1</string>
<string>1.2.3</string>
<key>CFBundleVersion</key>
<string>6</string>
<string>8</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
+4 -4
View File
@@ -11,10 +11,10 @@ extension NSBezierPath {
var points = [CGPoint](repeating: .zero, count: 3)
for i in 0 ..< self.elementCount {
switch self.element(at: i, associatedPoints: &points) {
case .moveToBezierPathElement: path.move(to: points[0])
case .lineToBezierPathElement: path.addLine(to: points[0])
case .curveToBezierPathElement: path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePathBezierPathElement: path.closeSubpath()
case .moveTo: path.move(to: points[0])
case .lineTo: path.addLine(to: points[0])
case .curveTo: path.addCurve(to: points[2], control1: points[0], control2: points[1])
case .closePath: path.closeSubpath()
}
}
return path
@@ -90,19 +90,28 @@ class PPOverlayController: NSWindowController {
// When the user clicks, animate the cursor out, save the picked color and copy it.
override func mouseDown(with event: NSEvent) {
hidePicker(animate: true)
let pickedColor = PPPickedColor(color: lastHighlightedColor, format: PPState.shared.chosenFormat)
PPState.shared.addRecentPick(pickedColor)
copyToPasteboard(stringValue: pickedColor.asString)
pickColor()
}
// Close picker immediately when the user presses the escape key.
// Listen to key events.
override func keyDown(with event: NSEvent) {
if event.keyCode == kVK_Escape { hidePicker(animate: false) }
if event.keyCode == kVK_Space {
let format = PPState.shared.chosenFormat
PPState.shared.chosenFormat = event.modifierFlags.contains(.shift) ? format.previous() : format.next()
switch Int(event.keyCode) {
// Close immediately on escape.
case kVK_Escape:
hidePicker(animate: false)
// Act as if the user clicked on space.
case kVK_Space:
hidePicker(animate: true)
pickColor()
// Cycle between formats when the user presses the left or right arrow keys.
case kVK_LeftArrow:
PPState.shared.chosenFormat = PPState.shared.chosenFormat.previous()
updateInfoPanel(lastHighlightedColor, lastHighlightedColor.bestContrastingColor())
case kVK_RightArrow:
PPState.shared.chosenFormat = PPState.shared.chosenFormat.next()
updateInfoPanel(lastHighlightedColor, lastHighlightedColor.bestContrastingColor())
default:
break
}
}
@@ -147,6 +156,12 @@ class PPOverlayController: NSWindowController {
}
}
func pickColor() {
let pickedColor = PPPickedColor(color: lastHighlightedColor, format: PPState.shared.chosenFormat)
PPState.shared.addRecentPick(pickedColor)
copyToPasteboard(stringValue: pickedColor.asString)
}
// Show the picker, save the previously active application, and hide the cursor.
func showPicker() {
if overlayPanel.isVisible || infoPanel.isVisible { return }
@@ -20,8 +20,8 @@ class PPOverlayPreview: NSView, CALayerDelegate {
override func awakeFromNib() {
// Make layers contents resize to fill, and disable antialiasing.
wantsLayer = true
layer?.magnificationFilter = kCAFilterNearest
layer?.contentsGravity = kCAGravityResizeAspectFill
layer?.magnificationFilter = .nearest
layer?.contentsGravity = .resizeAspectFill
layer?.delegate = self
// Add the grid shape layers to the view.
+4 -5
View File
@@ -39,7 +39,7 @@ struct PPPickedColor {
// This enum is responsible for each of the color formats PixelPicker supports,
// as well as being responsible for formatting them as strings, etc.
enum PPColor: String {
enum PPColor: String, CaseIterable {
case genericHex = "Generic Hex"
case generic8Bit = "Generic 8-Bit"
case genericDecimal = "Generic Decimal"
@@ -161,16 +161,15 @@ enum PPColor: String {
// Returns the PPColor that sits after this one.
func next() -> PPColor {
return next(withArray: iterateEnum(PPColor.self).map({ $0 }))
return next(withArray: PPColor.allCases)
}
// Same as next() but backwards.
func previous() -> PPColor {
return next(withArray: iterateEnum(PPColor.self).reversed())
return next(withArray: PPColor.allCases.reversed())
}
// Finds the next element after this element in the given array.
// This method should only be passed the result of iterateEnum(PPColor.self).
// Finds the next PPColor after this one in the list of PPColors.
private func next(withArray array: [PPColor]) -> PPColor {
var found = false
for x in array {
+19 -1
View File
@@ -6,6 +6,9 @@
#import "PPMenuShortcutView.h"
#import "PixelPicker-Swift.h"
// The trailing end of the cancel button in the MASShortcutView (within the NSMenuItem).
CGFloat cancelBoundary = 15;
// In order to have a MASShortcutView inside our menubar dropdown menu, we need to
// perform some hacks. Basically, we catch all the key and mouse events, and then
// use those to manually control the shortcut view (the MASShortcutView doesn't work
@@ -44,6 +47,21 @@
return self;
}
-(void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Draw a border around the view.
// Currently we don't use any of MASShortcut's custom drawing styles since they don't draw
// nicely with macOS 10.14's new dark interface style.
CGFloat xInset = 20;
CGFloat yInset = 1;
NSRect borderRect = NSMakeRect(self.frame.origin.x + xInset, self.frame.origin.y + yInset, self.frame.size.width - xInset - cancelBoundary, self.frame.size.height - (yInset * 2));
NSBezierPath *border = [NSBezierPath bezierPathWithRoundedRect:borderRect xRadius:3 yRadius:3];
[[[NSColor scrollBarColor] colorWithAlphaComponent:0.5] set];
[border stroke];
}
// This is called when the NSMenu is opened, so it's a good time to register our
// intercepting event handlers to catch all keyboard events inside the NSMenu.
- (void)viewDidMoveToWindow
@@ -95,7 +113,7 @@
// End of the MASShortcutView.
CGFloat end = start + shortcutView.frame.size.width;
// Start of the cancel button in the MASShortcutView.
CGFloat cancelStart = end - 20;
CGFloat cancelStart = end - cancelBoundary;
if (shortcutView.shortcutValue != nil) {
if (point.x >= start && point.x <= cancelStart) {
+4 -4
View File
@@ -18,14 +18,14 @@ void LogWarning(void);
// See: https://stackoverflow.com/a/3939241/5552584
// Also: https://github.com/asmagill/hammerspoon_asm.undocumented/blob/master/cursor/CGSConnection.h
/// Every application is given a singular connection ID through which it can receieve and manipulate
/// values, state, notifications, events, etc. in the Window Server.
// Every application is given a singular connection ID through which it can receieve and manipulate
// values, state, notifications, events, etc. in the Window Server.
typedef int CGSConnectionID;
/// Associates a value for the given key on the given connection.
// Associates a value for the given key on the given connection.
CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value);
/// Both of these methods get the default connection for this process.
// Gets the default connection for this process. `CGSMainConnectionID` is just a more modern name.
CGSConnectionID _CGSDefaultConnection(void);
CGSConnectionID CGSMainConnectionID(void);
+1 -13
View File
@@ -8,18 +8,6 @@ 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)
@@ -68,7 +56,7 @@ enum Coordinate {
func runAnimation(_ f: (NSAnimationContext) -> Void, done: (() -> Void)?) {
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.5
context.timingFunction = .init(name: kCAMediaTimingFunctionEaseInEaseOut)
context.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut)
context.allowsImplicitAnimation = true
f(context)
}, completionHandler: done)
-1
View File
@@ -4,6 +4,5 @@
//
import XCTest
@testable import PixelPicker
class AppDelegateTests: XCTestCase {}
-1
View File
@@ -4,4 +4,3 @@
//
import XCTest
@testable import PixelPicker
-1
View File
@@ -4,6 +4,5 @@
//
import XCTest
@testable import PixelPicker
class PPStateTests: XCTestCase {}
+2 -2
View File
@@ -27,10 +27,10 @@ After you've installed PixelPicker, it will live in your menu bar. You have many
* Select the "Pick a Pixel" item in the dropdown menu to activate the picker
* Simply right-click the menu item and the picker will activate
Once you have the picker open, it should be quite straightforward! Move your mouse around the screen and click on the pixel you want.
Once you have the picker open, it should be quite straightforward! Move your mouse around the screen and click (or press <kbd>space</kbd> on the pixel you want.
You can also hold down the <kbd>control (⌃)</kbd> key (this can be changed to another key) while the picker is open, and the picker will enter "Focus Mode". This will zoom the pixel preview even further, and will slow down the picker's movement to make it easier to pick that hard-to-get pixel!
**ProTip:** Remember, you can press <kbd>space</kbd> (or <kbd>shift</kbd> + <kbd>space</kbd>) to cycle between color formats while the picker is open.
**ProTip:** Remember, you can use the left or right arrow keys to cycle between color formats while the picker is open.
### Options