-
Notifications
You must be signed in to change notification settings - Fork 745
Expand file tree
/
Copy pathuser_input.py
More file actions
310 lines (270 loc) · 8.18 KB
/
user_input.py
File metadata and controls
310 lines (270 loc) · 8.18 KB
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# Copyright 2018 The dm_control Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ============================================================================
"""Utilities for handling keyboard events."""
import collections
# Mapped input values, so that we don't have to reference glfw everywhere.
RELEASE = 0
PRESS = 1
REPEAT = 2
KEY_UNKNOWN = -1
KEY_SPACE = 32
KEY_APOSTROPHE = 39
KEY_COMMA = 44
KEY_MINUS = 45
KEY_PERIOD = 46
KEY_SLASH = 47
KEY_0 = 48
KEY_1 = 49
KEY_2 = 50
KEY_3 = 51
KEY_4 = 52
KEY_5 = 53
KEY_6 = 54
KEY_7 = 55
KEY_8 = 56
KEY_9 = 57
KEY_SEMICOLON = 59
KEY_EQUAL = 61
KEY_A = 65
KEY_B = 66
KEY_C = 67
KEY_D = 68
KEY_E = 69
KEY_F = 70
KEY_G = 71
KEY_H = 72
KEY_I = 73
KEY_J = 74
KEY_K = 75
KEY_L = 76
KEY_M = 77
KEY_N = 78
KEY_O = 79
KEY_P = 80
KEY_Q = 81
KEY_R = 82
KEY_S = 83
KEY_T = 84
KEY_U = 85
KEY_V = 86
KEY_W = 87
KEY_X = 88
KEY_Y = 89
KEY_Z = 90
KEY_LEFT_BRACKET = 91
KEY_BACKSLASH = 92
KEY_RIGHT_BRACKET = 93
KEY_GRAVE_ACCENT = 96
KEY_ESCAPE = 256
KEY_ENTER = 257
KEY_TAB = 258
KEY_BACKSPACE = 259
KEY_INSERT = 260
KEY_DELETE = 261
KEY_RIGHT = 262
KEY_LEFT = 263
KEY_DOWN = 264
KEY_UP = 265
KEY_PAGE_UP = 266
KEY_PAGE_DOWN = 267
KEY_HOME = 268
KEY_END = 269
KEY_CAPS_LOCK = 280
KEY_SCROLL_LOCK = 281
KEY_NUM_LOCK = 282
KEY_PRINT_SCREEN = 283
KEY_PAUSE = 284
KEY_F1 = 290
KEY_F2 = 291
KEY_F3 = 292
KEY_F4 = 293
KEY_F5 = 294
KEY_F6 = 295
KEY_F7 = 296
KEY_F8 = 297
KEY_F9 = 298
KEY_F10 = 299
KEY_F11 = 300
KEY_F12 = 301
KEY_KP_0 = 320
KEY_KP_1 = 321
KEY_KP_2 = 322
KEY_KP_3 = 323
KEY_KP_4 = 324
KEY_KP_5 = 325
KEY_KP_6 = 326
KEY_KP_7 = 327
KEY_KP_8 = 328
KEY_KP_9 = 329
KEY_KP_DECIMAL = 330
KEY_KP_DIVIDE = 331
KEY_KP_MULTIPLY = 332
KEY_KP_SUBTRACT = 333
KEY_KP_ADD = 334
KEY_KP_ENTER = 335
KEY_KP_EQUAL = 336
KEY_LEFT_SHIFT = 340
KEY_LEFT_CONTROL = 341
KEY_LEFT_ALT = 342
KEY_LEFT_SUPER = 343
KEY_RIGHT_SHIFT = 344
KEY_RIGHT_CONTROL = 345
KEY_RIGHT_ALT = 346
KEY_RIGHT_SUPER = 347
MOD_NONE = 0
MOD_SHIFT = 0x0001
MOD_CONTROL = 0x0002
MOD_ALT = 0x0004
MOD_SUPER = 0x0008
MOD_SHIFT_CONTROL = MOD_SHIFT | MOD_CONTROL
MOUSE_BUTTON_LEFT = 0
MOUSE_BUTTON_RIGHT = 1
MOUSE_BUTTON_MIDDLE = 2
_NO_EXCLUSIVE_KEY = (None, lambda _: None)
_NO_CALLBACK = (None, None)
class Exclusive(collections.namedtuple('Exclusive', 'combination')):
"""Defines an exclusive action.
Exclusive actions can be invoked in response to single key clicks only. The
callback will be called twice. The first time when the key combination is
pressed, passing True as the argument to the callback. The second time when
the key is released (the modifiers don't have to be present then), passing
False as the callback argument.
Attributes:
combination: A list of integers interpreted as key codes, or tuples
in format (keycode, modifier).
"""
pass
class DoubleClick(collections.namedtuple('DoubleClick', 'combination')):
"""Defines a mouse double click action.
It will define a requirement to double click the mouse button specified in the
combination in order to be triggered.
Attributes:
combination: A list of integers interpreted as key codes, or tuples
in format (keycode, modifier). The keycodes are limited only to mouse
button codes.
"""
pass
class Range(collections.namedtuple('Range', 'collection')):
"""Binds a number of key combinations to a callback.
When triggered, the index of the triggering key combination will be passed
as an argument to the callback.
Attributes:
callback: A callable accepting a single argument - an integer index of the
triggered callback.
collection: A collection of combinations. Combinations may either be raw key
codes, tuples in format (keycode, modifier), or one of the Exclusive or
DoubleClick instances.
"""
pass
class InputMap:
"""Provides ability to alias key combinations and map them to actions."""
def __init__(self, mouse, keyboard):
"""Instance initializer.
Args:
mouse: GlfwMouse instance.
keyboard: GlfwKeyboard instance.
"""
self._keyboard = keyboard
self._mouse = mouse
self._keyboard.on_key += self._handle_key
self._mouse.on_click += self._handle_key
self._mouse.on_double_click += self._handle_double_click
self._mouse.on_move += self._handle_mouse_move
self._mouse.on_scroll += self._handle_mouse_scroll
self.clear_bindings()
def __del__(self):
"""Instance deleter."""
self._keyboard.on_key -= self._handle_key
self._mouse.on_click -= self._handle_key
self._mouse.on_double_click -= self._handle_double_click
self._mouse.on_move -= self._handle_mouse_move
self._mouse.on_scroll -= self._handle_mouse_scroll
def clear_bindings(self):
"""Clears registered action bindings, while keeping key aliases."""
self._action_callbacks = {}
self._double_click_callbacks = {}
self._plane_callback = []
self._z_axis_callback = []
self._active_exclusive = _NO_EXCLUSIVE_KEY
def bind(self, callback, key_binding):
"""Binds a key combination to a callback.
Args:
callback: An argument-less callable.
key_binding: A integer with a key code, a tuple (keycode, modifier) or one
of the actions Exclusive|DoubleClick|Range carrying the key combination.
"""
def build_callback(index, callback):
def indexed_callback():
callback(index)
return indexed_callback
if isinstance(key_binding, Range):
for index, binding in enumerate(key_binding.collection):
self._add_binding(build_callback(index, callback), binding)
else:
self._add_binding(callback, key_binding)
def _add_binding(self, callback, key_binding):
key_combination = self._extract_key_combination(key_binding)
if isinstance(key_binding, Exclusive):
self._action_callbacks[key_combination] = (True, callback)
elif isinstance(key_binding, DoubleClick):
self._double_click_callbacks[key_combination] = callback
else:
self._action_callbacks[key_combination] = (False, callback)
def _extract_key_combination(self, key_binding):
if isinstance(key_binding, Exclusive):
key_binding = key_binding.combination
elif isinstance(key_binding, DoubleClick):
key_binding = key_binding.combination
if not isinstance(key_binding, tuple):
key_binding = (key_binding, MOD_NONE)
return key_binding
def bind_plane(self, callback):
"""Binds a callback to a planar motion action (mouse movement)."""
self._plane_callback.append(callback)
def bind_z_axis(self, callback):
"""Binds a callback to a z-axis motion action (mouse scroll)."""
self._z_axis_callback.append(callback)
def _handle_key(self, key, action, modifiers):
"""Handles a single key press (mouse and keyboard)."""
alias_key = (key, modifiers)
exclusive_key, exclusive_callback = self._active_exclusive
if exclusive_key is not None:
if action == RELEASE and key == exclusive_key:
exclusive_callback(False)
self._active_exclusive = _NO_EXCLUSIVE_KEY
else:
is_exclusive, callback = self._action_callbacks.get(
alias_key, _NO_CALLBACK)
if callback:
if action == PRESS:
if is_exclusive:
callback(True)
self._active_exclusive = (key, callback)
else:
callback()
def _handle_double_click(self, key, modifiers):
"""Handles a double mouse click."""
alias_key = (key, modifiers)
callback = self._double_click_callbacks.get(alias_key, None)
if callback is not None:
callback()
def _handle_mouse_move(self, position, translation):
"""Handles mouse move."""
for callback in self._plane_callback:
callback(position, translation)
def _handle_mouse_scroll(self, value):
"""Handles mouse wheel scroll."""
for callback in self._z_axis_callback:
callback(value)