├── Sources
└── ShortcutGuide
│ ├── ShortcutGuide.swift
│ ├── ShortcutGuideDataSource.swift
│ ├── ManagedWindow.swift
│ ├── ShortcutItem.swift
│ ├── ShortcutItemGroup.swift
│ ├── KeyboardCharacter.swift
│ ├── NotificationToken.swift
│ ├── ShortcutItemView.swift
│ ├── ShortcutGuidePageControl.swift
│ ├── ShortcutGuideViewController.swift
│ ├── ShortcutGuidePageController.swift
│ └── ShortcutGuideWindowController.swift
├── Screenshots
└── Example-1.png
├── Example
├── ShortcutGuideExample
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── ShortcutGuideExample.entitlements
│ ├── AppDelegate.swift
│ ├── WindowController.swift
│ ├── Info.plist
│ ├── ViewController.swift
│ └── Base.lproj
│ │ └── Main.storyboard
└── ShortcutGuideExample.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── project.pbxproj
├── Tests
├── LinuxMain.swift
└── ShortcutGuideTests
│ ├── XCTestManifests.swift
│ └── ShortcutGuideTests.swift
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── ShortcutGuide.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── ShortcutGuide.h
├── Info.plist
├── LICENSE
├── README.md
├── Package.swift
└── .gitignore
/Sources/ShortcutGuide/ShortcutGuide.swift:
--------------------------------------------------------------------------------
1 | // Placeholder
2 |
--------------------------------------------------------------------------------
/Screenshots/Example-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lessica/ShortcutGuide/HEAD/Screenshots/Example-1.png
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import ShortcutGuideTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += ShortcutGuideTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Tests/ShortcutGuideTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | return [
6 | testCase(ShortcutGuideTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/ShortcutGuide.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ShortcutGuide.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutGuideDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutGuideDataSource.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Rachel on 2021/3/27.
6 | // Copyright © 2021 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | public protocol ShortcutGuideDataSource: NSResponder {
12 | var shortcutItems: [ShortcutItem] { get }
13 | }
14 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/ShortcutGuideExample.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ManagedWindow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManagedWindow.swift
3 | //
4 | //
5 | // Created by Rachel on 2021/3/28.
6 | //
7 |
8 | import Cocoa
9 |
10 | final class ManagedWindow {
11 | var window: NSWindow
12 | let closingSubscription: NotificationToken
13 |
14 | init(window: NSWindow, closingSubscription: NotificationToken) {
15 | self.window = window
16 | self.closingSubscription = closingSubscription
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Tests/ShortcutGuideTests/ShortcutGuideTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import ShortcutGuide
3 |
4 | final class ShortcutGuideTests: XCTestCase {
5 | func testExample() {
6 | // This is an example of a functional test case.
7 | // Use XCTAssert and related functions to verify your tests produce the correct
8 | // results.
9 | XCTAssertEqual(ShortcutGuide().text, "Hello, World!")
10 | }
11 |
12 | static var allTests = [
13 | ("testExample", testExample),
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ShortcutGuideExample
4 | //
5 | // Created by Rachel on 2021/3/29.
6 | //
7 |
8 | import Cocoa
9 |
10 | @main
11 | class AppDelegate: NSObject, NSApplicationDelegate {
12 |
13 | func applicationDidFinishLaunching(_ aNotification: Notification) {
14 | // Insert code here to initialize your application
15 | }
16 |
17 | func applicationWillTerminate(_ aNotification: Notification) {
18 | // Insert code here to tear down your application
19 | }
20 |
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/WindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WindowController.swift
3 | // ShortcutGuideExample
4 | //
5 | // Created by Rachel on 2021/3/29.
6 | //
7 |
8 | import Cocoa
9 | import ShortcutGuide
10 |
11 | class WindowController: NSWindowController {
12 | override func windowDidLoad() {
13 | super.windowDidLoad()
14 | ShortcutGuideWindowController.registerShortcutGuideForWindow(window!)
15 | }
16 | }
17 |
18 | extension WindowController: ShortcutGuideDataSource {
19 | var shortcutItems: [ShortcutItem] {
20 | return []
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ShortcutGuide.h:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutGuide.h
3 | // ShortcutGuide
4 | //
5 | // Created by Rachel on 2021/3/27.
6 | // Copyright © 2021 JST. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for ShortcutGuide.
12 | FOUNDATION_EXPORT double ShortcutGuideVersionNumber;
13 |
14 | //! Project version string for ShortcutGuide.
15 | FOUNDATION_EXPORT const unsigned char ShortcutGuideVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSHumanReadableCopyright
22 | Copyright © 2021 JST. All rights reserved.
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutItem.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Darwin on 11/1/20.
6 | // Copyright © 2020 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | public struct ShortcutItem {
12 | public init(name: String, keyString: String, toolTip: String, modifierFlags: NSEvent.ModifierFlags) {
13 | self.name = name
14 | self.keyString = keyString
15 | self.toolTip = toolTip
16 | self.modifierFlags = modifierFlags
17 | }
18 |
19 | public init(name: String, keyString: ShortcutItem.KeyboardCharacter, toolTip: String, modifierFlags: NSEvent.ModifierFlags) {
20 | self.name = name
21 | self.keyString = keyString.rawValue
22 | self.toolTip = toolTip
23 | self.modifierFlags = modifierFlags
24 | }
25 |
26 | public let name: String
27 | public let keyString: String
28 | public let toolTip: String
29 | public let modifierFlags: NSEvent.ModifierFlags
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutItemGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutItemGroup.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Rachel on 2021/3/25.
6 | // Copyright © 2021 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | internal extension Array {
12 | func chunked(into size: Int) -> [[Element]] {
13 | if count <= size {
14 | return [Array(self)]
15 | }
16 | return stride(from: 0, to: count, by: size).map {
17 | Array(self[$0 ..< Swift.min($0 + size, count)])
18 | }
19 | }
20 | }
21 |
22 | internal struct ShortcutItemGroup {
23 | let identifier: String?
24 | let items: [ShortcutItem]
25 |
26 | static func splitItemsIntoGroups(_ items: [ShortcutItem], maximumCount max: Int) -> [ShortcutItemGroup]
27 | {
28 | var cnt = 0
29 | return items.chunked(into: max).map {
30 | cnt += 1
31 | return ShortcutItemGroup(identifier: "group-\(cnt)", items: $0)
32 | }
33 | }
34 |
35 | static let empty = ShortcutItemGroup(identifier: "empty", items: [])
36 | }
37 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSMainStoryboardFile
26 | Main
27 | NSPrincipalClass
28 | NSApplication
29 |
30 |
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Zheng Wu.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ShortcutGuide
2 |
3 | Double-click command to present a shortcut guide for your macOS application.
4 |
5 | 
6 |
7 | ## Usage
8 |
9 | Add these lines to your root window's `NSWindowController`:
10 |
11 | ```swift
12 | import ShortcutGuide
13 | // ...
14 | override func windowDidLoad() {
15 | super.windowDidLoad()
16 | // ...
17 | ShortcutGuideWindowController.registerShortcutGuideForWindow(window!)
18 | }
19 | ```
20 |
21 | Then, make some parts of your responder chain (`NSView` or `NSViewController`) conform to `ShortcutGuideDataSource`:
22 |
23 | ```swift
24 | public protocol ShortcutGuideDataSource: NSResponder {
25 | var shortcutItems: [ShortcutItem] { get }
26 | }
27 | ```
28 |
29 | Define and provide your shortcuts from the protocol method above:
30 |
31 | ```swift
32 | public struct ShortcutItem {
33 | let name: String
34 | let keyString: String
35 | let toolTip: String
36 | let modifierFlags: NSEvent.ModifierFlags
37 | }
38 | ```
39 |
40 | ## License
41 |
42 | Copyright © 2021 Zheng Wu. All rights reserved.
43 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "ShortcutGuide",
8 | platforms: [
9 | .macOS(.v10_11),
10 | ],
11 | products: [
12 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
13 | .library(
14 | name: "ShortcutGuide",
15 | targets: ["ShortcutGuide"]),
16 | ],
17 | dependencies: [
18 | // Dependencies declare other packages that this package depends on.
19 | // .package(url: /* package url */, from: "1.0.0"),
20 | ],
21 | targets: [
22 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
23 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
24 | .target(
25 | name: "ShortcutGuide",
26 | dependencies: [],
27 | resources: [.process("Resources")]),
28 | .testTarget(
29 | name: "ShortcutGuideTests",
30 | dependencies: ["ShortcutGuide"]),
31 | ],
32 | swiftLanguageVersions: [
33 | .v5
34 | ]
35 | )
36 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/KeyboardCharacter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyboardCharacter.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Rachel on 2021/3/25.
6 | // Copyright © 2021 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | extension ShortcutItem {
12 | public enum KeyboardCharacter: String, CaseIterable {
13 | case command = "⌘"
14 | case control = "⌃"
15 | case esc = "⎋"
16 | case option = "⌥"
17 | case shift = "⇧"
18 | case tab = "⇥"
19 | case space = "␣"
20 | case delete = "⌫"
21 | case deleteForward = "⌦"
22 | case `return` = "⏎"
23 | case numericEnter = "⌤"
24 | case capsLock = "⇪"
25 | case clear = "⌧"
26 | case home = "⤒"
27 | case end = "⤓"
28 | case pageUp = "↑"
29 | case pageDown = "↓"
30 | case up = "▲"
31 | case down = "▼"
32 | case left = "◀"
33 | case right = "▶"
34 | case eject = "⏏"
35 |
36 | static let function = "fn"
37 | static let backspace = KeyboardCharacter.delete
38 | static let enter = KeyboardCharacter.return
39 | }
40 | }
41 |
42 | public extension CharacterSet {
43 | static var keyboard: CharacterSet { CharacterSet(charactersIn: ShortcutItem.KeyboardCharacter.allCases.map({ $0.rawValue }).joined()) }
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/NotificationToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationToken.swift
3 | //
4 | //
5 | // Created by Rachel on 2021/3/28.
6 | //
7 |
8 | import Cocoa
9 |
10 | final class NotificationToken: NSObject {
11 | let notificationCenter: NotificationCenter
12 | let notificationToken: Any
13 | let eventMonitors: [Any]?
14 |
15 | init(
16 | notificationCenter: NotificationCenter = .default,
17 | notificationToken token: Any,
18 | eventMonitors monitors: [Any]?
19 | ) {
20 | self.notificationCenter = notificationCenter
21 | self.notificationToken = token
22 | self.eventMonitors = monitors
23 | }
24 |
25 | deinit {
26 | notificationCenter.removeObserver(notificationToken)
27 | eventMonitors?.forEach({ NSEvent.removeMonitor($0) })
28 | }
29 | }
30 |
31 | extension NotificationCenter {
32 | func observe(
33 | name: NSNotification.Name?,
34 | object obj: Any?,
35 | eventMonitors monitors: [Any]?,
36 | queue: OperationQueue? = nil,
37 | using block: @escaping (Notification) -> ()
38 | )
39 | -> NotificationToken
40 | {
41 | let token = addObserver(forName: name, object: obj, queue: queue, using: block)
42 | return NotificationToken(notificationCenter: self, notificationToken: token, eventMonitors: monitors)
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutItemView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutItemView.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Darwin on 11/1/20.
6 | // Copyright © 2020 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | internal class ShortcutItemView: NSView {
12 |
13 | @IBOutlet weak var itemLabel: NSTextField!
14 | @IBOutlet weak var itemKeyLabelControl: NSTextField!
15 | @IBOutlet weak var itemKeyLabelOption: NSTextField!
16 | @IBOutlet weak var itemKeyLabelShift: NSTextField!
17 | @IBOutlet weak var itemKeyLabelCommand: NSTextField!
18 | @IBOutlet weak var itemKeyLabelFunction: NSTextField!
19 | @IBOutlet weak var itemKeyLabel: NSTextField!
20 |
21 | func updateDisplayWithItem(_ item: ShortcutItem) {
22 | itemLabel.stringValue = item.name
23 | itemKeyLabelControl.isHidden = !item.modifierFlags.contains(.control)
24 | itemKeyLabelOption.isHidden = !item.modifierFlags.contains(.option)
25 | itemKeyLabelShift.isHidden = !item.modifierFlags.contains(.shift)
26 | itemKeyLabelCommand.isHidden = !item.modifierFlags.contains(.command)
27 | itemKeyLabelFunction.isHidden = !item.modifierFlags.contains(.function)
28 | itemKeyLabel.stringValue = item.keyString
29 | itemLabel.toolTip = item.toolTip
30 | }
31 |
32 | func resetDisplay() {
33 | itemLabel.stringValue = ""
34 | itemKeyLabelControl.isHidden = true
35 | itemKeyLabelOption.isHidden = true
36 | itemKeyLabelShift.isHidden = true
37 | itemKeyLabelCommand.isHidden = true
38 | itemKeyLabelFunction.isHidden = true
39 | itemKeyLabel.stringValue = ""
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ShortcutGuideExample
4 | //
5 | // Created by Rachel on 2021/3/29.
6 | //
7 |
8 | import Cocoa
9 | import ShortcutGuide
10 |
11 | class ViewController: NSViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 |
16 | // Do any additional setup after loading the view.
17 | }
18 |
19 | override var representedObject: Any? {
20 | didSet {
21 | // Update the view, if already loaded.
22 | }
23 | }
24 |
25 | }
26 |
27 | extension ViewController: ShortcutGuideDataSource {
28 | private func randomModifierFlags() -> NSEvent.ModifierFlags {
29 | let maxCnt = Int.random(in: 1...5)
30 | let closures: [(NSEvent.ModifierFlags) -> NSEvent.ModifierFlags] = [
31 | { input in
32 | return input.union(.control)
33 | },
34 | { input in
35 | return input.union(.command)
36 | },
37 | { input in
38 | return input.union(.option)
39 | },
40 | { input in
41 | return input.union(.shift)
42 | },
43 | { input in
44 | return input.union(.function)
45 | },
46 | ]
47 | var masks: NSEvent.ModifierFlags = []
48 | for _ in 0.. ShortcutItem] = [
57 | { [unowned self] in
58 | return ShortcutItem(name: "Short Item", keyString: String(characters.randomElement()!), toolTip: "", modifierFlags: self.randomModifierFlags())
59 | },
60 | { [unowned self] in
61 | return ShortcutItem(name: "Medium Item Medium Item", keyString: String(characters.randomElement()!), toolTip: "", modifierFlags: self.randomModifierFlags())
62 | },
63 | { [unowned self] in
64 | return ShortcutItem(name: "Long Item Long Item Long Item Long Item", keyString: String(characters.randomElement()!), toolTip: "", modifierFlags: self.randomModifierFlags())
65 | },
66 | {
67 | return ShortcutItem(name: "Custom Item", keyString: "Whatever you want", toolTip: "Whatever you want", modifierFlags: [])
68 | }
69 | ]
70 | var items = [ShortcutItem]()
71 | for _ in 0..= dotWidth
70 | let hasEnoughWidth = bounds.width >= minimumRequiredWidth
71 |
72 | if !hasEnoughWidth || !hasEnoughHeight {
73 | debugPrint("bounds doesn't have enough space to draw all dots")
74 | debugPrint("current rect : \(dirtyRect)")
75 | debugPrint("required size: \(CGSize(width: minimumRequiredWidth, height: dotWidth))")
76 | return
77 | }
78 |
79 | var rects = [CGRect]()
80 | guard let ctx = NSGraphicsContext.current?.cgContext else { return }
81 | for idx in 0.. itemIdx {
135 | itemWrapper.updateDisplayWithItem(items[itemIdx])
136 | } else {
137 | itemWrapper.resetDisplay()
138 | }
139 | itemIdx += 1
140 | }
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutGuidePageController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutGuidePageController.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Rachel on 2021/3/25.
6 | // Copyright © 2021 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | internal class ShortcutGuidePageController: NSPageController, NSPageControllerDelegate {
12 |
13 | @IBOutlet weak var visualEffectView: NSVisualEffectView!
14 | @IBOutlet weak var pageControl: ShortcutGuidePageControl!
15 |
16 | var items: [ShortcutItem]?
17 | var groups: [ShortcutItemGroup]? { arrangedObjects as? [ShortcutItemGroup] }
18 | func group(with identifier: String) -> ShortcutItemGroup? {
19 | guard let groups = groups else {
20 | return nil
21 | }
22 | return groups.first(where: { $0.identifier == identifier })
23 | }
24 |
25 | private var pageConstraints: [NSLayoutConstraint]?
26 | private var columnStyle: ShortcutGuideColumnStyle = .dual
27 | private var isSinglePage: Bool { arrangedObjects.count <= 1 }
28 |
29 | func prepareForPresentation(window: NSWindow, columnStyle style: ShortcutGuideColumnStyle) {
30 | attachedWindow = window
31 | columnStyle = style
32 | if let items = items, items.count > 0 {
33 | let maximumCount = style == .dual ? 16 : 8
34 | arrangedObjects = ShortcutItemGroup.splitItemsIntoGroups(items, maximumCount: maximumCount)
35 | } else {
36 | arrangedObjects = [ ShortcutItemGroup.empty ]
37 | }
38 | selectedIndex = 0
39 | pageControl.numberOfPages = arrangedObjects.count
40 | pageControl.currentPage = 0
41 | pageControl.isHidden = isSinglePage
42 | view.needsUpdateConstraints = true
43 | }
44 |
45 | private func maskImage(cornerRadius: CGFloat) -> NSImage {
46 | let edgeLength = 2.0 * cornerRadius + 1.0
47 | let maskImage = NSImage(size: NSSize(width: edgeLength, height: edgeLength), flipped: false) { rect in
48 | let bezierPath = NSBezierPath(roundedRect: rect, xRadius: cornerRadius, yRadius: cornerRadius)
49 | NSColor.black.set()
50 | bezierPath.fill()
51 | return true
52 | }
53 | maskImage.capInsets = NSEdgeInsets(top: cornerRadius, left: cornerRadius, bottom: cornerRadius, right: cornerRadius)
54 | maskImage.resizingMode = .stretch
55 | return maskImage
56 | }
57 |
58 | private weak var attachedWindow: NSWindow?
59 |
60 | private func centerInScreenForWindow(_ parent: NSWindow?) {
61 | if let window = view.window, let screen = parent?.screen ?? window.screen {
62 | let xPos = screen.frame.minX + screen.frame.width / 2.0 - window.frame.width / 2.0
63 | let yPos = screen.frame.minY + screen.frame.height / 2.0 - window.frame.height / 2.0
64 | window.setFrame(NSRect(x: xPos, y: yPos, width: window.frame.width, height: window.frame.height), display: false)
65 | }
66 | }
67 |
68 | override func awakeFromNib() {
69 | super.awakeFromNib()
70 | delegate = self
71 | transitionStyle = .horizontalStrip
72 | }
73 |
74 | override func viewDidLoad() {
75 | super.viewDidLoad()
76 |
77 | pageControl.target = self
78 | pageControl.action = #selector(navigateWithPageControl(_:))
79 |
80 | visualEffectView.blendingMode = .behindWindow
81 | if #available(OSX 10.14, *) {
82 | visualEffectView.material = .hudWindow
83 | } else {
84 | visualEffectView.material = .appearanceBased
85 | // Fallback on earlier versions
86 | }
87 | visualEffectView.state = .active
88 | visualEffectView.maskImage = maskImage(cornerRadius: 16.0)
89 | visualEffectView.translatesAutoresizingMaskIntoConstraints = false
90 |
91 | view.wantsLayer = false
92 | view.window?.contentView = visualEffectView
93 | view.translatesAutoresizingMaskIntoConstraints = false
94 | }
95 |
96 | override func viewDidLayout() {
97 | super.viewDidLayout()
98 | centerInScreenForWindow(attachedWindow)
99 | }
100 |
101 | override func updateViewConstraints() {
102 | super.updateViewConstraints()
103 | if let superview = view.superview {
104 | if let pageConstraints = pageConstraints {
105 | NSLayoutConstraint.deactivate(pageConstraints)
106 | self.pageConstraints = nil
107 | }
108 | let constraints = [
109 | view.topAnchor.constraint(equalTo: superview.topAnchor),
110 | view.bottomAnchor.constraint(equalTo: superview.bottomAnchor),
111 | view.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
112 | view.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
113 | ]
114 | NSLayoutConstraint.activate(constraints)
115 | pageConstraints = constraints
116 | }
117 | }
118 |
119 | @objc private func navigateWithPageControl(_ sender: ShortcutGuidePageControl) {
120 | NSAnimationContext.runAnimationGroup({ context in
121 | self.animator().selectedIndex = sender.currentPage
122 | }, completionHandler: {
123 | self.completeTransition()
124 | })
125 | }
126 |
127 | private func setupViewController(_ viewController: ShortcutGuideViewController, with itemGroup: ShortcutItemGroup) {
128 | viewController.updateDisplayWithItems(itemGroup.items)
129 | viewController.isSinglePage = isSinglePage
130 | viewController.view.needsUpdateConstraints = true
131 | }
132 |
133 | func pageController(_ pageController: NSPageController, prepare viewController: NSViewController, with object: Any?) {
134 | guard let itemGroup = object as? ShortcutItemGroup, let ctrl = viewController as? ShortcutGuideViewController else { return }
135 | setupViewController(ctrl, with: itemGroup)
136 | }
137 |
138 | func pageController(_ pageController: NSPageController, viewControllerForIdentifier identifier: NSPageController.ObjectIdentifier) -> NSViewController {
139 | let ctrl = self.storyboard!.instantiateController(withIdentifier: "ShortcutGuideViewController") as! ShortcutGuideViewController
140 | if let itemGroup = groups?.first(where: { $0.identifier == identifier }) {
141 | setupViewController(ctrl, with: itemGroup)
142 | }
143 | return ctrl
144 | }
145 |
146 | func pageController(_ pageController: NSPageController, identifierFor object: Any) -> NSPageController.ObjectIdentifier {
147 | return (object as! ShortcutItemGroup).identifier!
148 | }
149 |
150 | func pageController(_ pageController: NSPageController, didTransitionTo object: Any) {
151 | pageControl.currentPage = selectedIndex
152 | }
153 |
154 | func pageControllerDidEndLiveTransition(_ pageController: NSPageController) {
155 | completeTransition()
156 | }
157 |
158 | override func cursorUpdate(with event: NSEvent) {
159 | super.cursorUpdate(with: event)
160 | NSCursor.arrow.set()
161 | }
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/Sources/ShortcutGuide/ShortcutGuideWindowController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ShortcutGuideWindowController.swift
3 | // JSTColorPicker
4 | //
5 | // Created by Darwin on 11/1/20.
6 | // Copyright © 2020 JST. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | public enum ShortcutGuideColumnStyle {
12 | case single
13 | case dual
14 | }
15 |
16 | public class ShortcutGuideWindowController: NSWindowController {
17 |
18 | public static let shared = newShortcutGuideController()
19 |
20 | public var animationBehavior: NSWindow.AnimationBehavior? {
21 | get { window?.animationBehavior }
22 | set { window?.animationBehavior = newValue ?? .default }
23 | }
24 |
25 | private static func newShortcutGuideController() -> ShortcutGuideWindowController {
26 | let windowStoryboard = NSStoryboard(name: "ShortcutGuide", bundle: Bundle.module)
27 | let sgWindowController = windowStoryboard.instantiateInitialController() as! ShortcutGuideWindowController
28 | return sgWindowController
29 | }
30 |
31 | public override func awakeFromNib() {
32 | super.awakeFromNib()
33 | window?.level = .statusBar
34 | window?.isOpaque = false
35 | window?.backgroundColor = .clear
36 | window?.animationBehavior = .default
37 | }
38 |
39 | private var localMonitor: Any?
40 | private var globalMonitor: Any?
41 |
42 | deinit {
43 | // Clean up click recognizer
44 | removeCloseOnOutsideClick()
45 | }
46 |
47 | /**
48 | Creates a monitor for outside clicks. If clicking outside of this view or
49 | any views in `ignoringViews`, the view will be hidden.
50 | */
51 | private func addCloseOnOutsideClick(ignoring ignoringViews: [NSView]? = nil) {
52 | guard let window = window, let contentView = window.contentView else { return }
53 | localMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown], handler: { [weak self] (event) -> NSEvent? in
54 | guard window.isVisible else { return event }
55 | var shouldHide = false
56 | if window != event.window {
57 | // Click other windows
58 | shouldHide = true
59 | } else {
60 | let localLoc = contentView.convert(event.locationInWindow, from: nil)
61 | if !contentView.bounds.contains(localLoc) {
62 | // If the click is in any of the specified views to ignore, don't hide
63 | for ignoreView in ignoringViews ?? [NSView]() {
64 | let frameInWindow: NSRect = ignoreView.convert(ignoreView.bounds, to: nil)
65 | if frameInWindow.contains(event.locationInWindow) {
66 | // Abort if clicking in an ignored view
67 | return event
68 | }
69 | }
70 | // Getting here means the click should hide the view
71 | shouldHide = true
72 | }
73 | }
74 | if shouldHide {
75 | // Perform your hiding code here
76 | self?.hide()
77 | }
78 | return event
79 |
80 | })
81 | globalMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .rightMouseDown]) { [weak self] (event) -> Void in
82 | guard window.isVisible else { return }
83 | self?.hide()
84 | }
85 | }
86 |
87 | private func removeCloseOnOutsideClick() {
88 | if localMonitor != nil {
89 | NSEvent.removeMonitor(localMonitor!)
90 | localMonitor = nil
91 | }
92 | if globalMonitor != nil {
93 | NSEvent.removeMonitor(globalMonitor!)
94 | globalMonitor = nil
95 | }
96 | }
97 |
98 |
99 | // MARK: - Registered Window Events
100 |
101 | public var preferredColumnStyle: ShortcutGuideColumnStyle = .dual
102 | private var managedWindows = [ManagedWindow]()
103 |
104 | public static func registerShortcutGuideForWindow(_ extWindow: NSWindow) {
105 | ShortcutGuideWindowController.shared.registerShortcutGuideForWindow(extWindow)
106 | }
107 |
108 | private func registerShortcutGuideForWindow(_ extWindow: NSWindow) {
109 | guard let eventMonitor = (NSEvent.addLocalMonitorForEvents(matching: [.flagsChanged]) { [weak self] (event) -> NSEvent? in
110 | guard let self = self, event.window == extWindow else { return event }
111 | if self.monitorWindowFlagsChanged(with: event) {
112 | return nil
113 | }
114 | return event
115 | }) else {
116 | fatalError("fail to add local monitor")
117 | }
118 | let closingSubscription = NotificationCenter.default.observe(
119 | name: NSWindow.willCloseNotification,
120 | object: extWindow,
121 | eventMonitors: [eventMonitor]
122 | ) { [weak self] notification in
123 | guard let window = notification.object as? NSWindow else { return }
124 | self?.managedWindows.removeAll(where: { $0.window == window })
125 | }
126 | managedWindows.append(
127 | ManagedWindow(
128 | window: extWindow,
129 | closingSubscription: closingSubscription
130 | )
131 | )
132 | }
133 |
134 | private var lastCommandPressedAt: TimeInterval = 0.0
135 | private func commandPressed(with event: NSEvent?) -> Bool {
136 | guard let eventWindow = event?.window else { return false }
137 | let now = event?.timestamp ?? Date().timeIntervalSinceReferenceDate
138 | if now - lastCommandPressedAt < 0.4 {
139 | ShortcutGuideWindowController.shared
140 | .loadItemsForWindow(eventWindow)
141 | ShortcutGuideWindowController.shared
142 | .toggleForWindow(eventWindow, columnStyle: preferredColumnStyle)
143 | lastCommandPressedAt = 0.0
144 | return true
145 | } else {
146 | lastCommandPressedAt = now
147 | }
148 | return false
149 | }
150 |
151 | private func commandCancelled() -> Bool {
152 | lastCommandPressedAt = 0.0
153 | return false
154 | }
155 |
156 | @discardableResult
157 | private func monitorWindowFlagsChanged(with event: NSEvent?, forceReset: Bool = false) -> Bool {
158 | guard let eventWindow = event?.window, eventWindow.isKeyWindow else { return false }
159 | var handled = false
160 | let modifierFlags = (event?.modifierFlags ?? NSEvent.modifierFlags)
161 | .intersection(.deviceIndependentFlagsMask)
162 | if modifierFlags.isEmpty
163 | {
164 | handled = false
165 | }
166 | else
167 | {
168 | if modifierFlags.contains(.command) &&
169 | modifierFlags.subtracting(.command).isEmpty
170 | {
171 | handled = commandPressed(with: event)
172 | } else {
173 | handled = commandCancelled()
174 | }
175 | }
176 | return handled
177 | }
178 |
179 |
180 | // MARK: - Toggle
181 |
182 | public var isVisible: Bool { window?.isVisible ?? false }
183 |
184 | private static func inspectItemsForWindow(_ extWindow: NSWindow) -> [ShortcutItem] {
185 | var items = [ShortcutItem]()
186 | var responder: NSResponder? = extWindow.firstResponder
187 | while responder != nil {
188 | if let thisResponder = responder as? ShortcutGuideDataSource {
189 | items.append(contentsOf: thisResponder.shortcutItems)
190 | }
191 | responder = responder?.nextResponder
192 | }
193 | return items
194 | }
195 |
196 | public func loadItemsForWindow(_ extWindow: NSWindow) {
197 | items = ShortcutGuideWindowController.inspectItemsForWindow(extWindow)
198 | }
199 |
200 | public func showForWindow(_ extWindow: NSWindow, columnStyle style: ShortcutGuideColumnStyle?) {
201 | prepareForPresentation(window: extWindow, columnStyle: style ?? preferredColumnStyle)
202 | showWindow(nil)
203 | addCloseOnOutsideClick()
204 | }
205 |
206 | public func hide() {
207 | removeCloseOnOutsideClick()
208 | window?.orderOut(nil)
209 | }
210 |
211 | public func toggleForWindow(_ extWindow: NSWindow, columnStyle style: ShortcutGuideColumnStyle?) {
212 | guard let window = window else { return }
213 | if !window.isVisible {
214 | showForWindow(extWindow, columnStyle: style)
215 | } else {
216 | hide()
217 | }
218 | }
219 |
220 |
221 | // MARK: - Shortcut Items
222 |
223 | public var items: [ShortcutItem]? {
224 | get {
225 | rootViewController.items
226 | }
227 | set {
228 | rootViewController.items = newValue
229 | }
230 | }
231 |
232 | private var rootViewController: ShortcutGuidePageController {
233 | contentViewController as! ShortcutGuidePageController
234 | }
235 |
236 | private func prepareForPresentation(window: NSWindow, columnStyle style: ShortcutGuideColumnStyle) {
237 | rootViewController.prepareForPresentation(window: window, columnStyle: style)
238 | }
239 |
240 | }
241 |
242 | extension ShortcutGuideWindowController: NSWindowDelegate {
243 |
244 | public func windowDidResignKey(_ notification: Notification) {
245 | self.hide()
246 | }
247 |
248 | }
249 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | CC5D88322611E0E7005FAF4E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC5D88312611E0E7005FAF4E /* AppDelegate.swift */; };
11 | CC5D88342611E0E7005FAF4E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC5D88332611E0E7005FAF4E /* ViewController.swift */; };
12 | CC5D88362611E0E7005FAF4E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CC5D88352611E0E7005FAF4E /* Assets.xcassets */; };
13 | CC5D88392611E0E7005FAF4E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CC5D88372611E0E7005FAF4E /* Main.storyboard */; };
14 | CC5D88462611E182005FAF4E /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC5D88452611E182005FAF4E /* WindowController.swift */; };
15 | CC5D884B2611E19F005FAF4E /* ShortcutGuide in Frameworks */ = {isa = PBXBuildFile; productRef = CC5D884A2611E19F005FAF4E /* ShortcutGuide */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | CC5D882E2611E0E7005FAF4E /* ShortcutGuideExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ShortcutGuideExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
20 | CC5D88312611E0E7005FAF4E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
21 | CC5D88332611E0E7005FAF4E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
22 | CC5D88352611E0E7005FAF4E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
23 | CC5D88382611E0E7005FAF4E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
24 | CC5D883A2611E0E7005FAF4E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
25 | CC5D883B2611E0E7005FAF4E /* ShortcutGuideExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShortcutGuideExample.entitlements; sourceTree = ""; };
26 | CC5D88452611E182005FAF4E /* WindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowController.swift; sourceTree = ""; };
27 | /* End PBXFileReference section */
28 |
29 | /* Begin PBXFrameworksBuildPhase section */
30 | CC5D882B2611E0E7005FAF4E /* Frameworks */ = {
31 | isa = PBXFrameworksBuildPhase;
32 | buildActionMask = 2147483647;
33 | files = (
34 | CC5D884B2611E19F005FAF4E /* ShortcutGuide in Frameworks */,
35 | );
36 | runOnlyForDeploymentPostprocessing = 0;
37 | };
38 | /* End PBXFrameworksBuildPhase section */
39 |
40 | /* Begin PBXGroup section */
41 | CC5D88252611E0E7005FAF4E = {
42 | isa = PBXGroup;
43 | children = (
44 | CC5D88302611E0E7005FAF4E /* ShortcutGuideExample */,
45 | CC5D882F2611E0E7005FAF4E /* Products */,
46 | CC5D88492611E19F005FAF4E /* Frameworks */,
47 | );
48 | sourceTree = "";
49 | };
50 | CC5D882F2611E0E7005FAF4E /* Products */ = {
51 | isa = PBXGroup;
52 | children = (
53 | CC5D882E2611E0E7005FAF4E /* ShortcutGuideExample.app */,
54 | );
55 | name = Products;
56 | sourceTree = "";
57 | };
58 | CC5D88302611E0E7005FAF4E /* ShortcutGuideExample */ = {
59 | isa = PBXGroup;
60 | children = (
61 | CC5D88312611E0E7005FAF4E /* AppDelegate.swift */,
62 | CC5D88332611E0E7005FAF4E /* ViewController.swift */,
63 | CC5D88452611E182005FAF4E /* WindowController.swift */,
64 | CC5D88352611E0E7005FAF4E /* Assets.xcassets */,
65 | CC5D88372611E0E7005FAF4E /* Main.storyboard */,
66 | CC5D883A2611E0E7005FAF4E /* Info.plist */,
67 | CC5D883B2611E0E7005FAF4E /* ShortcutGuideExample.entitlements */,
68 | );
69 | path = ShortcutGuideExample;
70 | sourceTree = "";
71 | };
72 | CC5D88492611E19F005FAF4E /* Frameworks */ = {
73 | isa = PBXGroup;
74 | children = (
75 | );
76 | name = Frameworks;
77 | sourceTree = "";
78 | };
79 | /* End PBXGroup section */
80 |
81 | /* Begin PBXNativeTarget section */
82 | CC5D882D2611E0E7005FAF4E /* ShortcutGuideExample */ = {
83 | isa = PBXNativeTarget;
84 | buildConfigurationList = CC5D883E2611E0E7005FAF4E /* Build configuration list for PBXNativeTarget "ShortcutGuideExample" */;
85 | buildPhases = (
86 | CC5D882A2611E0E7005FAF4E /* Sources */,
87 | CC5D882B2611E0E7005FAF4E /* Frameworks */,
88 | CC5D882C2611E0E7005FAF4E /* Resources */,
89 | );
90 | buildRules = (
91 | );
92 | dependencies = (
93 | );
94 | name = ShortcutGuideExample;
95 | packageProductDependencies = (
96 | CC5D884A2611E19F005FAF4E /* ShortcutGuide */,
97 | );
98 | productName = ShortcutGuideExample;
99 | productReference = CC5D882E2611E0E7005FAF4E /* ShortcutGuideExample.app */;
100 | productType = "com.apple.product-type.application";
101 | };
102 | /* End PBXNativeTarget section */
103 |
104 | /* Begin PBXProject section */
105 | CC5D88262611E0E7005FAF4E /* Project object */ = {
106 | isa = PBXProject;
107 | attributes = {
108 | LastSwiftUpdateCheck = 1240;
109 | LastUpgradeCheck = 1240;
110 | TargetAttributes = {
111 | CC5D882D2611E0E7005FAF4E = {
112 | CreatedOnToolsVersion = 12.4;
113 | };
114 | };
115 | };
116 | buildConfigurationList = CC5D88292611E0E7005FAF4E /* Build configuration list for PBXProject "ShortcutGuideExample" */;
117 | compatibilityVersion = "Xcode 9.3";
118 | developmentRegion = en;
119 | hasScannedForEncodings = 0;
120 | knownRegions = (
121 | en,
122 | Base,
123 | );
124 | mainGroup = CC5D88252611E0E7005FAF4E;
125 | productRefGroup = CC5D882F2611E0E7005FAF4E /* Products */;
126 | projectDirPath = "";
127 | projectRoot = "";
128 | targets = (
129 | CC5D882D2611E0E7005FAF4E /* ShortcutGuideExample */,
130 | );
131 | };
132 | /* End PBXProject section */
133 |
134 | /* Begin PBXResourcesBuildPhase section */
135 | CC5D882C2611E0E7005FAF4E /* Resources */ = {
136 | isa = PBXResourcesBuildPhase;
137 | buildActionMask = 2147483647;
138 | files = (
139 | CC5D88362611E0E7005FAF4E /* Assets.xcassets in Resources */,
140 | CC5D88392611E0E7005FAF4E /* Main.storyboard in Resources */,
141 | );
142 | runOnlyForDeploymentPostprocessing = 0;
143 | };
144 | /* End PBXResourcesBuildPhase section */
145 |
146 | /* Begin PBXSourcesBuildPhase section */
147 | CC5D882A2611E0E7005FAF4E /* Sources */ = {
148 | isa = PBXSourcesBuildPhase;
149 | buildActionMask = 2147483647;
150 | files = (
151 | CC5D88342611E0E7005FAF4E /* ViewController.swift in Sources */,
152 | CC5D88462611E182005FAF4E /* WindowController.swift in Sources */,
153 | CC5D88322611E0E7005FAF4E /* AppDelegate.swift in Sources */,
154 | );
155 | runOnlyForDeploymentPostprocessing = 0;
156 | };
157 | /* End PBXSourcesBuildPhase section */
158 |
159 | /* Begin PBXVariantGroup section */
160 | CC5D88372611E0E7005FAF4E /* Main.storyboard */ = {
161 | isa = PBXVariantGroup;
162 | children = (
163 | CC5D88382611E0E7005FAF4E /* Base */,
164 | );
165 | name = Main.storyboard;
166 | sourceTree = "";
167 | };
168 | /* End PBXVariantGroup section */
169 |
170 | /* Begin XCBuildConfiguration section */
171 | CC5D883C2611E0E7005FAF4E /* Debug */ = {
172 | isa = XCBuildConfiguration;
173 | buildSettings = {
174 | ALWAYS_SEARCH_USER_PATHS = NO;
175 | CLANG_ANALYZER_NONNULL = YES;
176 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
177 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
178 | CLANG_CXX_LIBRARY = "libc++";
179 | CLANG_ENABLE_MODULES = YES;
180 | CLANG_ENABLE_OBJC_ARC = YES;
181 | CLANG_ENABLE_OBJC_WEAK = YES;
182 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
183 | CLANG_WARN_BOOL_CONVERSION = YES;
184 | CLANG_WARN_COMMA = YES;
185 | CLANG_WARN_CONSTANT_CONVERSION = YES;
186 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
187 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
188 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
189 | CLANG_WARN_EMPTY_BODY = YES;
190 | CLANG_WARN_ENUM_CONVERSION = YES;
191 | CLANG_WARN_INFINITE_RECURSION = YES;
192 | CLANG_WARN_INT_CONVERSION = YES;
193 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
194 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
195 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
196 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
197 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
198 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
199 | CLANG_WARN_STRICT_PROTOTYPES = YES;
200 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
201 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
202 | CLANG_WARN_UNREACHABLE_CODE = YES;
203 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
204 | COPY_PHASE_STRIP = NO;
205 | DEBUG_INFORMATION_FORMAT = dwarf;
206 | ENABLE_STRICT_OBJC_MSGSEND = YES;
207 | ENABLE_TESTABILITY = YES;
208 | GCC_C_LANGUAGE_STANDARD = gnu11;
209 | GCC_DYNAMIC_NO_PIC = NO;
210 | GCC_NO_COMMON_BLOCKS = YES;
211 | GCC_OPTIMIZATION_LEVEL = 0;
212 | GCC_PREPROCESSOR_DEFINITIONS = (
213 | "DEBUG=1",
214 | "$(inherited)",
215 | );
216 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
217 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
218 | GCC_WARN_UNDECLARED_SELECTOR = YES;
219 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
220 | GCC_WARN_UNUSED_FUNCTION = YES;
221 | GCC_WARN_UNUSED_VARIABLE = YES;
222 | MACOSX_DEPLOYMENT_TARGET = 11.0;
223 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
224 | MTL_FAST_MATH = YES;
225 | ONLY_ACTIVE_ARCH = YES;
226 | SDKROOT = macosx;
227 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
228 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
229 | };
230 | name = Debug;
231 | };
232 | CC5D883D2611E0E7005FAF4E /* Release */ = {
233 | isa = XCBuildConfiguration;
234 | buildSettings = {
235 | ALWAYS_SEARCH_USER_PATHS = NO;
236 | CLANG_ANALYZER_NONNULL = YES;
237 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
238 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
239 | CLANG_CXX_LIBRARY = "libc++";
240 | CLANG_ENABLE_MODULES = YES;
241 | CLANG_ENABLE_OBJC_ARC = YES;
242 | CLANG_ENABLE_OBJC_WEAK = YES;
243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
244 | CLANG_WARN_BOOL_CONVERSION = YES;
245 | CLANG_WARN_COMMA = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
249 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
250 | CLANG_WARN_EMPTY_BODY = YES;
251 | CLANG_WARN_ENUM_CONVERSION = YES;
252 | CLANG_WARN_INFINITE_RECURSION = YES;
253 | CLANG_WARN_INT_CONVERSION = YES;
254 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
255 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
256 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
257 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
258 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
263 | CLANG_WARN_UNREACHABLE_CODE = YES;
264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
265 | COPY_PHASE_STRIP = NO;
266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
267 | ENABLE_NS_ASSERTIONS = NO;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | GCC_C_LANGUAGE_STANDARD = gnu11;
270 | GCC_NO_COMMON_BLOCKS = YES;
271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
273 | GCC_WARN_UNDECLARED_SELECTOR = YES;
274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
275 | GCC_WARN_UNUSED_FUNCTION = YES;
276 | GCC_WARN_UNUSED_VARIABLE = YES;
277 | MACOSX_DEPLOYMENT_TARGET = 11.0;
278 | MTL_ENABLE_DEBUG_INFO = NO;
279 | MTL_FAST_MATH = YES;
280 | SDKROOT = macosx;
281 | SWIFT_COMPILATION_MODE = wholemodule;
282 | SWIFT_OPTIMIZATION_LEVEL = "-O";
283 | };
284 | name = Release;
285 | };
286 | CC5D883F2611E0E7005FAF4E /* Debug */ = {
287 | isa = XCBuildConfiguration;
288 | buildSettings = {
289 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
290 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
291 | CODE_SIGN_ENTITLEMENTS = ShortcutGuideExample/ShortcutGuideExample.entitlements;
292 | CODE_SIGN_STYLE = Automatic;
293 | COMBINE_HIDPI_IMAGES = YES;
294 | INFOPLIST_FILE = ShortcutGuideExample/Info.plist;
295 | LD_RUNPATH_SEARCH_PATHS = (
296 | "$(inherited)",
297 | "@executable_path/../Frameworks",
298 | );
299 | PRODUCT_BUNDLE_IDENTIFIER = com.darwindev.ShortcutGuideExample;
300 | PRODUCT_NAME = "$(TARGET_NAME)";
301 | SWIFT_VERSION = 5.0;
302 | };
303 | name = Debug;
304 | };
305 | CC5D88402611E0E7005FAF4E /* Release */ = {
306 | isa = XCBuildConfiguration;
307 | buildSettings = {
308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
309 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
310 | CODE_SIGN_ENTITLEMENTS = ShortcutGuideExample/ShortcutGuideExample.entitlements;
311 | CODE_SIGN_STYLE = Automatic;
312 | COMBINE_HIDPI_IMAGES = YES;
313 | INFOPLIST_FILE = ShortcutGuideExample/Info.plist;
314 | LD_RUNPATH_SEARCH_PATHS = (
315 | "$(inherited)",
316 | "@executable_path/../Frameworks",
317 | );
318 | PRODUCT_BUNDLE_IDENTIFIER = com.darwindev.ShortcutGuideExample;
319 | PRODUCT_NAME = "$(TARGET_NAME)";
320 | SWIFT_VERSION = 5.0;
321 | };
322 | name = Release;
323 | };
324 | /* End XCBuildConfiguration section */
325 |
326 | /* Begin XCConfigurationList section */
327 | CC5D88292611E0E7005FAF4E /* Build configuration list for PBXProject "ShortcutGuideExample" */ = {
328 | isa = XCConfigurationList;
329 | buildConfigurations = (
330 | CC5D883C2611E0E7005FAF4E /* Debug */,
331 | CC5D883D2611E0E7005FAF4E /* Release */,
332 | );
333 | defaultConfigurationIsVisible = 0;
334 | defaultConfigurationName = Release;
335 | };
336 | CC5D883E2611E0E7005FAF4E /* Build configuration list for PBXNativeTarget "ShortcutGuideExample" */ = {
337 | isa = XCConfigurationList;
338 | buildConfigurations = (
339 | CC5D883F2611E0E7005FAF4E /* Debug */,
340 | CC5D88402611E0E7005FAF4E /* Release */,
341 | );
342 | defaultConfigurationIsVisible = 0;
343 | defaultConfigurationName = Release;
344 | };
345 | /* End XCConfigurationList section */
346 |
347 | /* Begin XCSwiftPackageProductDependency section */
348 | CC5D884A2611E19F005FAF4E /* ShortcutGuide */ = {
349 | isa = XCSwiftPackageProductDependency;
350 | productName = ShortcutGuide;
351 | };
352 | /* End XCSwiftPackageProductDependency section */
353 | };
354 | rootObject = CC5D88262611E0E7005FAF4E /* Project object */;
355 | }
356 |
--------------------------------------------------------------------------------
/Example/ShortcutGuideExample/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
--------------------------------------------------------------------------------