-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathscan_key.go
188 lines (166 loc) · 4.91 KB
/
scan_key.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package input
import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
// KeyScanStatus represents the KeyScanner.Scan operation result.
type KeyScanStatus int
const (
KeyScanUnchanged KeyScanStatus = iota
KeyScanChanged
KeyScanCompleted
)
// KeyScanner checks the currently pressed keys and buttons and tries to map them
// to a local Key type that can be used in a Keymap.
//
// Use NewKeyScanner to create a usable object of this type.
//
// Experimental: this is a part of a key remapping API, which is not stable yet.
type KeyScanner struct {
lastNumKeys int
canScan bool
key Key
h *Handler
}
// NewKeyScanner creates a key scanner for the specifier input Handler.
//
// You don't have to create a new scanner for every remap; they can be reused.
//
// It's important to have the correct Handler though: their ID is used to
// check the appropriate device keys.
//
// Experimental: this is a part of a key remapping API, which is not stable yet.
func NewKeyScanner(h *Handler) *KeyScanner {
return &KeyScanner{h: h}
}
// Scan reads the buttons state and tries to map them to a Key.
//
// It's intended to work with keyboard keys as well as gamepad buttons,
// but right now it only works for the keyboard.
//
// This function should be called on every frame where you're reading
// the new keybind combination.
// See the remap example for more info.
//
// The function can return these result statuses:
// * Unchanged - nothing updated since the last Scan() operation
// * Changed - some keys changed, you may want to update the prompt to the user
// * Completed - the user finished specifying the keys combination, you can use the Key as a new binding
func (s *KeyScanner) Scan() (Key, KeyScanStatus) {
// TODO: respect the enabled input devices.
// TODO: scan the gamepad buttons as well.
// Note that this function may not be needed by some users,
// so we're better of making it as independent as possible, so it
// doesn't make the package more expensive if you don't use it.
//
// This function doesn't have to be very fast, but it should be relatively
// inexpensive for the "no keys were pressed" case.
// When some keys combo is being pressed, it's OK to spend some resources.
// This slice is stack-allocated; for the most cases, 4 keys are enough.
keys := make([]ebiten.Key, 0, 4)
keys = inpututil.AppendPressedKeys(keys)
if !s.canScan {
if len(keys) != 0 {
return Key{}, KeyScanUnchanged
}
s.canScan = true
}
if len(keys) == s.lastNumKeys {
// It's either empty or we're still collecting the keys.
return Key{}, KeyScanUnchanged
}
if len(keys) < s.lastNumKeys {
// One or more keys are released.
// Consider it to be a confirmation event.
result := s.key
s.lastNumKeys = 0
s.key = Key{}
s.canScan = false
return result, KeyScanCompleted
}
s.lastNumKeys = len(keys)
k, ok := scanKey(keys)
status := KeyScanUnchanged
if ok {
s.key = k
status = KeyScanChanged
}
return k, status
}
func scanKey(keys []ebiten.Key) (Key, bool) {
if len(keys) == 0 {
return Key{}, false
}
containsKeyCode := func(keys []ebiten.Key, code int) bool {
for _, k := range keys {
if int(k) == code {
return true
}
}
return false
}
// Parse the keys combination into something that this library can handle.
// Round 1: walk the actual keys that are being pressed and collect the modifiers.
// Remove the modifiers from the slice (inplace).
var ctrlKey Key
var shiftKey Key
keysWithoutMods := keys[:0]
for _, k := range keys {
switch k {
case ebiten.KeyControl, ebiten.KeyShift:
// Just omit them from the slice.
case ebiten.KeyControlLeft:
ctrlKey = KeyControlLeft
case ebiten.KeyControlRight:
ctrlKey = KeyControlRight
case ebiten.KeyShiftLeft:
shiftKey = KeyShiftLeft
case ebiten.KeyShiftRight:
shiftKey = KeyShiftRight
default:
keysWithoutMods = append(keysWithoutMods, k)
}
}
hasCtrl := ctrlKey.name != ""
hasShift := shiftKey.name != ""
var mappedKey Key
// Round 2: map the Ebitengine keys to the local types.
// In theory, we could generate a big LUT to make this mapping very fast.
// But this would mean more data reserved for this package.
// Since this part of the code is not that performance-sensitive,
// we'll handle it in a less efficient, but less memory-hungry way.
Loop:
for _, lk := range allKeys {
switch lk.kind {
case keyKeyboard:
if containsKeyCode(keysWithoutMods, lk.code) {
mappedKey = lk
break Loop
}
}
}
if mappedKey.name == "" {
switch {
case hasCtrl:
return ctrlKey, true
case hasShift:
return shiftKey, true
}
}
var keymod KeyModifier
switch {
case hasCtrl && hasShift:
keymod = ModControlShift
case hasCtrl:
keymod = ModControl
case hasShift:
keymod = ModShift
}
if keymod != ModUnknown {
switch mappedKey.kind {
case keyKeyboard, keyMouse:
mappedKey = KeyWithModifier(mappedKey, keymod)
}
}
return mappedKey, mappedKey.name != ""
}