Skip to content

Commit

Permalink
Support SwiftUI
Browse files Browse the repository at this point in the history
  • Loading branch information
Kyome22 committed Jul 2, 2022
1 parent 282e158 commit 48f99ea
Show file tree
Hide file tree
Showing 43 changed files with 862 additions and 423 deletions.
Binary file removed Materials/DemoApp.png
Binary file not shown.
8 changes: 8 additions & 0 deletions Materials/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// swift-tools-version:5.5
import PackageDescription

let package = Package(
name: "",
products: [],
targets: []
)
Binary file added Materials/demo_app_appkit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Materials/demo_app_swiftui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// swift-tools-version:5.3
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "SpiceKey",
platforms: [
.macOS(.v10_12)
.macOS(.v10_15)
],
products: [
.library(
Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Global Shortcuts for macOS written in Swift.

Demo App is in this Project.

![demo](https://github.com/Kyome22/SpiceKey/raw/master/Materials/DemoApp.png)

<img src="Materials/demo_app_appkit.png" alt="demo_appkit" height="177px" />
<img src="Materials/demo_app_swiftui.png" alt="demo_swiftui" height="168px" />

## Usage

Expand Down Expand Up @@ -96,7 +96,7 @@ let spiceKeyData = SpiceKeyData(_ primaryKey: String,
_ modifierFlags: ModifierFlags,
_ spiceKey: SpiceKey)

let data = try! NSKeyedArchiver.archivedData(withRootObject: spiceKeyData,
let data = try! NSKeyedArchiver.archivedData(withRootObject: spiceKeyData,
requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "spiceKeyData")
```
Expand All @@ -108,3 +108,12 @@ let data = UserDefaults.standard.data(forKey: "spiceKeyData")!
let spiceKeyData = try! NSKeyedUnarchiver
.unarchiveTopLevelObjectWithData(data) as! SpiceKeyData
```

## SpiceKeyField & SKTextField

A special text field that can be used to register SpiceKey.

- SpiceKeyField is for AppKit only.
- SKTextField is a SpiceKeyField made available from SwiftUI.

Please see the Demo App for detailed instructions.
1 change: 0 additions & 1 deletion Sources/SpiceKey/Key.swift
Original file line number Diff line number Diff line change
Expand Up @@ -403,4 +403,3 @@ public enum Key {
return UInt32(self.keyCode)
}
}

2 changes: 0 additions & 2 deletions Sources/SpiceKey/KeyCombination.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
//

public struct KeyCombination {

public var key: Key
public var modifierFlags: ModifierFlags
public var string: String {
Expand All @@ -18,7 +17,6 @@ public struct KeyCombination {
self.key = key
self.modifierFlags = modifierFlags
}

}


Expand Down
2 changes: 0 additions & 2 deletions Sources/SpiceKey/ModifierBothFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import AppKit.NSEvent
import Carbon.HIToolbox.Events

public struct ModifierBothFlags {

public let isLControl: Bool
public let isRControl: Bool
public let isLOption: Bool
Expand Down Expand Up @@ -54,5 +53,4 @@ public struct ModifierBothFlags {
public var isBothCommand: Bool {
return !isControl && !isOption && !isShift && isLCommand && isRCommand
}

}
17 changes: 11 additions & 6 deletions Sources/SpiceKey/ModifierFlag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import AppKit.NSEvent

public enum ModifierFlag: Int {

public enum ModifierFlag: Int, CaseIterable {
case control // ⌃
case option // ⌥
case shift // ⇧
Expand All @@ -33,7 +32,16 @@ public enum ModifierFlag: Int {
case .command: return ""
}
}


public var title: String {
switch self {
case .control: return "control"
case .option: return "option"
case .shift: return "shift"
case .command: return "command"
}
}

public var flags: ModifierFlags {
switch self {
case .control: return .ctrl
Expand All @@ -42,7 +50,4 @@ public enum ModifierFlag: Int {
case .command: return .cmd
}
}

}


25 changes: 22 additions & 3 deletions Sources/SpiceKey/ModifierFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import AppKit.NSEvent
import Carbon.HIToolbox.Events

public enum ModifierFlags: Int {

public enum ModifierFlags: Int, CaseIterable {
case empty
case ctrl // ⌃
case opt // ⌥
Expand Down Expand Up @@ -93,6 +92,27 @@ public enum ModifierFlags: Int {
case .ctrlOptSftCmd: return "⌃⌥⇧⌘"
}
}

public var title: String {
switch self {
case .empty: return "empty"
case .ctrl: return "control"
case .opt: return "option"
case .sft: return "shift"
case .cmd: return "command"
case .ctrlOpt: return "ctrl + opt"
case .ctrlSft: return "ctrl + sft"
case .ctrlCmd: return "ctrl + cmd"
case .optSft: return "opt + sft"
case .optCmd: return "opt + cmd"
case .sftCmd: return "sft + cmd"
case .ctrlOptSft: return "ctrl + opt + sft"
case .ctrlOptCmd: return "ctrl + opt + cmd"
case .ctrlSftCmd: return "ctrl + sft + cmd"
case .optSftCmd: return "opt + sft + cmd"
case .ctrlOptSftCmd: return "ctrl + opt + sft + cmd"
}
}

public var flags: NSEvent.ModifierFlags {
switch self {
Expand Down Expand Up @@ -147,5 +167,4 @@ public enum ModifierFlags: Int {
}
return flags
}

}
2 changes: 0 additions & 2 deletions Sources/SpiceKey/NSEvent.ModifierFlags+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import AppKit.NSEvent
import Carbon

public extension NSEvent.ModifierFlags {

var pureFlags: Self {
var flags = Self.init()
if self.contains(.control) {
Expand All @@ -26,5 +25,4 @@ public extension NSEvent.ModifierFlags {
}
return flags
}

}
86 changes: 86 additions & 0 deletions Sources/SpiceKey/SKTextField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// SKTextField.swift
// SpiceKey
//
// Created by Takuto Nakamura on 2022/06/29.
// Copyright © 2019 Takuto Nakamura. All rights reserved.
//

import SwiftUI
import Combine

public struct SKTextField: NSViewRepresentable {
public typealias NSViewType = SpiceKeyField

private let id: String?
private let initialSpiceKey: SpiceKey?
private var registeredHandler: ((String?, KeyCombination) -> Void)?
private var deletedHandler: ((String?) -> Void)?

public init(id: String? = nil, initialSpiceKey: SpiceKey? = nil) {
self.id = id
self.initialSpiceKey = initialSpiceKey
}

public func makeNSView(context: Context) -> SpiceKeyField {
let textField = SpiceKeyField(frame: .zero)
textField.delegate = context.coordinator
textField.id = id
if let initialSpiceKey = initialSpiceKey {
textField.setInitialSpiceKey(initialSpiceKey)
context.coordinator.innerIsEnabled = textField.isEnabled
}
return textField
}

public func updateNSView(_ nsView: SpiceKeyField, context: Context) {
nsView.isEnabled = context.coordinator.innerIsEnabled
context.coordinator.registeredHandler = registeredHandler
context.coordinator.deletedHandler = deletedHandler
}

public func makeCoordinator() -> Coordinator {
return Coordinator(self)
}

public class Coordinator: NSObject, SpiceKeyFieldDelegate {
let parent: SKTextField
var registeredHandler: ((String?, KeyCombination) -> Void)?
var deletedHandler: ((String?) -> Void)?
var innerIsEnabled: Bool = true

public init(_ parent: SKTextField) {
self.parent = parent
}

public func didRegisterSpiceKey(_ field: SpiceKeyField, _ key: Key, _ flags: ModifierFlags) {
guard parent.id == nil || parent.id! == field.id else { return }
innerIsEnabled = field.isEnabled
registeredHandler?(parent.id, KeyCombination(key, flags))
}

public func didDelete(_ field: SpiceKeyField) {
guard parent.id == nil || parent.id! == field.id else { return }
innerIsEnabled = field.isEnabled
deletedHandler?(parent.id)
}
}

public func onRegistered(perform action: @escaping (String?, KeyCombination) -> Void) -> SKTextField {
var skTextField = self
skTextField.registeredHandler = action
return skTextField
}

public func onDeleted(perform action: @escaping (String?) -> Void) -> SKTextField {
var skTextField = self
skTextField.deletedHandler = action
return skTextField
}
}

struct SKTextField_Previews: PreviewProvider {
static var previews: some View {
SKTextField()
}
}
14 changes: 11 additions & 3 deletions Sources/SpiceKey/SpiceKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public final class SpiceKey {
}

public init(_ modifierFlag: ModifierFlag,
bothModifierKeysPressHandler: @escaping Handler,
bothModifierKeysPressHandler: Handler? = nil,
releaseKeyHandler: Handler? = nil) {
id = SpiceKeyManager.shared.generateID()
keyCombination = nil
Expand All @@ -55,7 +55,7 @@ public final class SpiceKey {

public init?(_ modifierFlags: ModifierFlags,
_ interval: Double,
modifierKeysLongPressHandler: @escaping Handler,
modifierKeysLongPressHandler: Handler? = nil,
releaseKeyHandler: Handler? = nil) {
guard 0.0 < interval && interval <= 3.0 else { return nil }
id = SpiceKeyManager.shared.generateID()
Expand All @@ -77,5 +77,13 @@ public final class SpiceKey {
SpiceKeyManager.shared.unregister(self)
}

public var string: String {
if let keyCombination = keyCombination {
return keyCombination.string
} else if let modifierFlags = modifierFlags {
return modifierFlags.string
} else {
fatalError("Impossible")
}
}
}

2 changes: 0 additions & 2 deletions Sources/SpiceKey/SpiceKeyData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import Foundation.NSObject

open class SpiceKeyData: NSObject, NSCoding {

public var primaryKey: String
public var keyCode: CGKeyCode
public var control: Bool
Expand Down Expand Up @@ -75,5 +74,4 @@ open class SpiceKeyData: NSObject, NSCoding {
coder.encode(shift, forKey: "shift")
coder.encode(command, forKey: "command")
}

}
2 changes: 0 additions & 2 deletions Sources/SpiceKey/SpiceKeyDeleteButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import Cocoa

public class SpiceKeyDeleteButton: NSButton {

override public var isEnabled: Bool {
didSet {
if isEnabled {
Expand Down Expand Up @@ -37,5 +36,4 @@ public class SpiceKeyDeleteButton: NSButton {
private func bundleImage(name: String) -> NSImage? {
return Bundle.module.image(forResource: name)
}

}
15 changes: 7 additions & 8 deletions Sources/SpiceKey/SpiceKeyField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public protocol SpiceKeyFieldDelegate: NSTextFieldDelegate {
}

open class SpiceKeyField: NSTextField {

private var isTyping: Bool = false
private var skfDelegate: SpiceKeyFieldDelegate? {
return delegate as? SpiceKeyFieldDelegate
Expand All @@ -41,15 +40,16 @@ open class SpiceKeyField: NSTextField {
layer?.borderColor = CGColor(red: 0.69, green: 0.745, blue: 0.773, alpha: 1.0)
layer?.borderWidth = 1.0
layer?.cornerRadius = 4.0
let rect = NSRect(x: bounds.width - 20.0,
y: 0.5 * (bounds.height - 20.0),
width: 20.0,
height: 20.0)
let rect = NSRect(origin: .zero, size: CGSize(width: 20, height: 20))
deleteButton = SpiceKeyDeleteButton(frame: rect)
addSubview(deleteButton)
deleteButton.target = self
deleteButton.action = #selector(self.delete)
deleteButton.isEnabled = false

deleteButton.translatesAutoresizingMaskIntoConstraints = false
deleteButton.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
deleteButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
}

private func register(key: Key, modifierFlags: ModifierFlags) {
Expand Down Expand Up @@ -112,10 +112,9 @@ open class SpiceKeyField: NSTextField {
skfDelegate?.didDelete(self)
}

public func setInitialSpiceKey(string: String) {
stringValue = string
public func setInitialSpiceKey(_ spiceKey: SpiceKey) {
stringValue = spiceKey.string
isEnabled = false
deleteButton.isEnabled = true
}

}
2 changes: 0 additions & 2 deletions Sources/SpiceKey/SpiceKeyManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import Carbon.HIToolbox.Events
typealias SpiceKeyID = UInt32

final class SpiceKeyManager {

public static let shared = SpiceKeyManager()
internal var spiceKeys = [SpiceKeyID : SpiceKey]()
private var hotKeyEventHandlerRef: EventHandlerRef? = nil
Expand Down Expand Up @@ -204,7 +203,6 @@ final class SpiceKeyManager {
invokeLongPressSpiceKey(flags)
}
}

}

private func hotKeyHandleNegotiator(eventHandlerCall: EventHandlerCallRef?,
Expand Down
Loading

0 comments on commit 48f99ea

Please sign in to comment.