Skip to content

Commit 588e6ae

Browse files
authored
Implement multiple actions (double tap, triple tap) (#349)
* Implement double tap and new actions array in config * Update native widgets to use new actions parameter * Refactor new actions parameter moving it to main definition Renamed old action and longAction to legacy * Fix tests * Remove unused code * Readd test for legacyAction * Implement triple tap * Add new actions explanation * Add support for multiple actions and same trigger
1 parent 87141e3 commit 588e6ae

14 files changed

+487
-128
lines changed

MTMR/CustomButtonTouchBarItem.swift

Lines changed: 116 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,32 @@
88

99
import Cocoa
1010

11+
struct ItemAction {
12+
typealias TriggerClosure = (() -> Void)?
13+
14+
let trigger: Action.Trigger
15+
let closure: TriggerClosure
16+
17+
init(trigger: Action.Trigger, _ closure: TriggerClosure) {
18+
self.trigger = trigger
19+
self.closure = closure
20+
}
21+
}
22+
1123
class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegate {
12-
var tapClosure: (() -> Void)?
13-
var longTapClosure: (() -> Void)? {
24+
25+
var actions: [ItemAction] = [] {
1426
didSet {
15-
longClick.isEnabled = longTapClosure != nil
27+
multiClick.isDoubleClickEnabled = actions.filter({ $0.trigger == .doubleTap }).count > 0
28+
multiClick.isTripleClickEnabled = actions.filter({ $0.trigger == .tripleTap }).count > 0
29+
longClick.isEnabled = actions.filter({ $0.trigger == .longTap }).count > 0
1630
}
1731
}
1832
var finishViewConfiguration: ()->() = {}
1933

2034
private var button: NSButton!
21-
private var singleClick: HapticClickGestureRecognizer!
2235
private var longClick: LongPressGestureRecognizer!
36+
private var multiClick: MultiClickGestureRecognizer!
2337

2438
init(identifier: NSTouchBarItem.Identifier, title: String) {
2539
attributedTitle = title.defaultTouchbarAttributedString
@@ -31,10 +45,17 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
3145
longClick.isEnabled = false
3246
longClick.allowedTouchTypes = .direct
3347
longClick.delegate = self
34-
35-
singleClick = HapticClickGestureRecognizer(target: self, action: #selector(handleGestureSingle))
36-
singleClick.allowedTouchTypes = .direct
37-
singleClick.delegate = self
48+
49+
multiClick = MultiClickGestureRecognizer(
50+
target: self,
51+
action: #selector(handleGestureSingleTap),
52+
doubleAction: #selector(handleGestureDoubleTap),
53+
tripleAction: #selector(handleGestureTripleTap)
54+
)
55+
multiClick.allowedTouchTypes = .direct
56+
multiClick.delegate = self
57+
multiClick.isDoubleClickEnabled = false
58+
multiClick.isTripleClickEnabled = false
3859

3960
reinstallButton()
4061
button.attributedTitle = attributedTitle
@@ -100,33 +121,43 @@ class CustomButtonTouchBarItem: NSCustomTouchBarItem, NSGestureRecognizerDelegat
100121
view = button
101122

102123
view.addGestureRecognizer(longClick)
103-
view.addGestureRecognizer(singleClick)
124+
// view.addGestureRecognizer(singleClick)
125+
view.addGestureRecognizer(multiClick)
104126
finishViewConfiguration()
105127
}
106128

107129
func gestureRecognizer(_ gestureRecognizer: NSGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: NSGestureRecognizer) -> Bool {
108-
if gestureRecognizer == singleClick && otherGestureRecognizer == longClick
109-
|| gestureRecognizer == longClick && otherGestureRecognizer == singleClick // need it
130+
if gestureRecognizer == multiClick && otherGestureRecognizer == longClick
131+
|| gestureRecognizer == longClick && otherGestureRecognizer == multiClick // need it
110132
{
111133
return false
112134
}
113135
return true
114136
}
115-
116-
@objc func handleGestureSingle(gr: NSClickGestureRecognizer) {
117-
switch gr.state {
118-
case .ended:
119-
tapClosure?()
120-
break
121-
default:
122-
break
137+
138+
func callActions(for trigger: Action.Trigger) {
139+
let itemActions = self.actions.filter { $0.trigger == trigger }
140+
for itemAction in itemActions {
141+
itemAction.closure?()
123142
}
124143
}
144+
145+
@objc func handleGestureSingleTap() {
146+
callActions(for: .singleTap)
147+
}
148+
149+
@objc func handleGestureDoubleTap() {
150+
callActions(for: .doubleTap)
151+
}
152+
153+
@objc func handleGestureTripleTap() {
154+
callActions(for: .tripleTap)
155+
}
125156

126157
@objc func handleGestureLong(gr: NSPressGestureRecognizer) {
127158
switch gr.state {
128159
case .possible: // tiny hack because we're calling action manually
129-
(self.longTapClosure ?? self.tapClosure)?()
160+
callActions(for: .longTap)
130161
break
131162
default:
132163
break
@@ -176,15 +207,78 @@ class CustomButtonCell: NSButtonCell {
176207
}
177208
}
178209

179-
class HapticClickGestureRecognizer: NSClickGestureRecognizer {
210+
// Thanks to https://stackoverflow.com/a/49843893
211+
final class MultiClickGestureRecognizer: NSClickGestureRecognizer {
212+
213+
private let _action: Selector
214+
private let _doubleAction: Selector
215+
private let _tripleAction: Selector
216+
private var _clickCount: Int = 0
217+
218+
public var isDoubleClickEnabled = true
219+
public var isTripleClickEnabled = true
220+
221+
override var action: Selector? {
222+
get {
223+
return nil /// prevent base class from performing any actions
224+
} set {
225+
if newValue != nil { // if they are trying to assign an actual action
226+
fatalError("Only use init(target:action:doubleAction) for assigning actions")
227+
}
228+
}
229+
}
230+
231+
required init(target: AnyObject, action: Selector, doubleAction: Selector, tripleAction: Selector) {
232+
_action = action
233+
_doubleAction = doubleAction
234+
_tripleAction = tripleAction
235+
super.init(target: target, action: nil)
236+
}
237+
238+
required init?(coder: NSCoder) {
239+
fatalError("init(target:action:doubleAction:tripleAction) is only support atm")
240+
}
241+
180242
override func touchesBegan(with event: NSEvent) {
181243
HapticFeedback.shared?.tap(strong: 2)
182244
super.touchesBegan(with: event)
183245
}
184-
246+
185247
override func touchesEnded(with event: NSEvent) {
186248
HapticFeedback.shared?.tap(strong: 1)
187249
super.touchesEnded(with: event)
250+
_clickCount += 1
251+
252+
var delayThreshold: TimeInterval // fine tune this as needed
253+
254+
guard isDoubleClickEnabled || isTripleClickEnabled else {
255+
_ = target?.perform(_action)
256+
return
257+
}
258+
259+
if (isTripleClickEnabled) {
260+
delayThreshold = 0.4
261+
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
262+
if _clickCount == 3 {
263+
_ = target?.perform(_tripleAction)
264+
}
265+
} else {
266+
delayThreshold = 0.3
267+
perform(#selector(_resetAndPerformActionIfNecessary), with: nil, afterDelay: delayThreshold)
268+
if _clickCount == 2 {
269+
_ = target?.perform(_doubleAction)
270+
}
271+
}
272+
}
273+
274+
@objc private func _resetAndPerformActionIfNecessary() {
275+
if _clickCount == 1 {
276+
_ = target?.perform(_action)
277+
}
278+
if isTripleClickEnabled && _clickCount == 2 {
279+
_ = target?.perform(_doubleAction)
280+
}
281+
_clickCount = 0
188282
}
189283
}
190284

0 commit comments

Comments
 (0)