File: input.py

package info (click to toggle)
pysdl2 0.9.17%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 3,328 kB
  • sloc: python: 24,685; makefile: 36; sh: 8
file content (418 lines) | stat: -rw-r--r-- 13,794 bytes parent folder | download
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
from ctypes import c_int, byref
from ..stdinc import SDL_TRUE
from ..keyboard import (
    SDL_GetKeyFromName, SDL_GetKeyName, SDL_StartTextInput, SDL_StopTextInput,
    SDL_IsTextInputActive, SDL_GetKeyboardState, SDL_GetScancodeFromName,
)
from ..scancode import SDL_SCANCODE_UNKNOWN, SDL_NUM_SCANCODES
from ..keycode import KMOD_ALT, KMOD_CTRL, KMOD_GUI, KMOD_SHIFT
from ..mouse import (
    SDL_BUTTON_LEFT, SDL_BUTTON_RIGHT, SDL_BUTTON_MIDDLE,
    SDL_BUTTON_X1, SDL_BUTTON_X2,
)
from ..events import (
    SDL_KEYDOWN, SDL_KEYUP, SDL_MOUSEBUTTONDOWN, SDL_MOUSEBUTTONUP,
    SDL_TEXTINPUT, SDL_PumpEvents,
)

from .compat import _is_text, byteify, isiterable

__all__ = [
    "key_pressed", "get_key_state", "mouse_clicked", "get_clicks", "get_text_input",
    "start_text_input", "stop_text_input", "text_input_enabled",  
]


KEYMOD_MAP = {
    # main mappings
    'ctrl': KMOD_CTRL,
    'alt': KMOD_ALT,
    'gui': KMOD_GUI,
    'shift': KMOD_SHIFT,
    # additional aliases
    'control': KMOD_CTRL,
    'option': KMOD_ALT,
    'command': KMOD_GUI,
    'super': KMOD_GUI,
}

MOUSE_BUTTON_MAP = {
    'left': SDL_BUTTON_LEFT,
    'right': SDL_BUTTON_RIGHT,
    'middle': SDL_BUTTON_MIDDLE,
    'x1': SDL_BUTTON_X1,
    'x2': SDL_BUTTON_X2,
}



def _parse_keycode(key):

    keycode = 0
    if isinstance(key, int):
        if SDL_GetKeyName(key) == b"":
            raise ValueError("'{0}' is not a valid SDL_Keycode".format(key))
        keycode = key

    elif _is_text(key):
        keycode = SDL_GetKeyFromName(byteify(key))
        if keycode == 0:
            raise ValueError("'{0}' is not a valid SDL key name".format(key))
        
    else:
        e = "'key' must be a string or SDL_Keycode (got {0})"
        raise TypeError(e.format(str(type(key))))
    
    return keycode


def _mod_to_masks(mod):

    if not isiterable(mod):
        mod = [mod]
    
    masks = []
    for m in mod:
        # If mod is already an int, assume it's a valid bitmask
        if isinstance(m, int):
            masks.append(m)

        # If mod is a string, validate and convert it to a bitmask
        elif _is_text(m):
            m = m.lower()
            if not m in KEYMOD_MAP.keys():
                e = "'{0}' is not a valid modifer key name"
                raise ValueError(e.format(m))
            masks.append(KEYMOD_MAP[m])

        else:
            e = "'mod' must be a list of strings or SDL bitmasks (got {0})"
            raise TypeError(e.format(str(type(m))))
    
    return masks


def _get_sdl_mouse_button(button):
    
    # Check if valid SDL_BUTTON constant
    if isinstance(button, int):
        if not button in MOUSE_BUTTON_MAP.values():
            e = "'{0}' does not correspond to a valid SDL mouse button"
            raise ValueError(e.format(button))
    
    # Check if valid mouse button name
    elif _is_text(button):
        button = button.lower()
        if not button in MOUSE_BUTTON_MAP.keys():
            e = "'{0}' is not a valid mouse button name"
            raise ValueError(e.format(button))
        button = MOUSE_BUTTON_MAP[button]

    else:
        e = "'button' must be a string or SDL_BUTTON constant (got {0})"
        raise TypeError(e.format(str(type(button))))
    
    return button



def key_pressed(events, key=None, mod=None, released=False):
    """Checks for key press events in a given event queue.
    
    By default, this function will return True if any key has been pressed.
    However, you can also check a specific key by providing its name (e.g.
    'up') or SDL keycode (e.g. ``sdl2.SDLK_up``) to the 'key' argument.

    This function is meant to be used with :func:`~sdl2.ext.get_events`::

        response = None
        while not response:
            q = get_events() # Fetch latest SDL input events
            if key_pressed(q, 'z'):
                response = 'left'
            elif key_pressed(q, '/'):
                response = 'right'

    Additionally, you can check if the key has been pressed while holding one
    or more modifier keys (e.g. control + q to quit the program) by providing
    the name(s) (e.g. 'ctrl') or SDL bitmask(s) (e.g. ``sdl2.KMOD_LCTRL``)
    of the modifiers to the 'mod' argument::

        q = get_events()
        if key_pressed(q, 'q', mod='ctrl'):
            exit_app()
        elif key_pressed(q, 'd', mod=['ctrl', 'shift']):
            debug_mode = True

    Valid modifier names include 'ctrl' and 'control' for the Control keys,
    'alt' and 'option' for the Alt keys, 'gui', 'command', and 'super' for the
    Command/Win/Super keys, and 'shift' for the shift keys. A full list of SDL
    modifier bitmasks can be found here: https://wiki.libsdl.org/SDL2/SDL_Keymod
    
    For a comprehensive list of valid key names, see the 'Name' column of the
    following table: https://wiki.libsdl.org/SDL2/SDL_Scancode

    For a comprehensive list of valid SDL keycodes, consult the following table:
    https://wiki.libsdl.org/SDL_Keycode

    Args:
        events (list of :obj:`sdl2.SDL_Event`): A list of SDL events to check
            for matching key presses (or releases).
        key (str or :obj:`sdl2.SDL_Keycode`, optional): The name or SDL keycode
            of the key to check. If ``None``, will return True on any keypress.
            Defaults to ``None``.
        mod (str or list, optional): The key modifiers (if any) to require for
            the key press (e.g. 'ctrl' for Control-Q). Has no effect if ``key``
            is not specified. Defaults to ``None``.
        released (bool, optional): If True, will check for key release
            events instead of key presses. Defaults to False.

    Returns:
        bool: True if key has been pressed, otherwise False.

    """
    # If key specified, validate and coerce to SDL_Keycode
    keycode = None
    if key:
        keycode = _parse_keycode(key)
        
    # If modifier key(s) specified, validate and coerce to list of bitmasks
    if mod:
        mod = _mod_to_masks(mod)
        
    # Ensure 'events' is iterable
    if not isiterable(events):
        events = [events]

    # Check for any key events matching the criteria in the given event queue
    pressed = False
    for e in events:
        if e.type == (SDL_KEYUP if released else SDL_KEYDOWN):
            if not keycode:
                pressed = True
                break
            elif e.key.keysym.sym == keycode:
                if not mod or all([e.key.keysym.mod & m for m in mod]):
                    pressed = True
                    break

    return pressed


def get_key_state(key):
    """Checks the current state (pressed or released) of a given keyboard key.

    Unlike :func:`key_pressed`, which checks an SDL event queue for key down
    and key up events, this function checks the current state of a given key
    directly. This can be helpful in certain situations, such as ignoring
    repeated keydown events from a held key::

       key_released = False
       while True:
           q = pump(True)
           if not key_released:
               # Ignore repeated keydown events from held down space bar by
               # requiring key be 'up' on at least one loop before a response
               # can be registered
               if get_key_state('space') == 0:
                   key_released = True
           else:
               if key_pressed('space', queue=q):
                   break

    Args:
        key (int or str): The name (or SDL scancode) of the key to check.

    Returns:
        int: 1 if the key is currently pressed, otherwise 0.

    """
    # If key given as string, get the corresponding scancode
    if _is_text(key):
        scancode = SDL_GetScancodeFromName(byteify(key))
        if scancode == SDL_SCANCODE_UNKNOWN:
            e = "'{0}' is not a valid name for an SDL scancode."
            raise ValueError(e.format(key))
    else:
        if key <= 0 or key >= SDL_NUM_SCANCODES:
            e = "'{0}' is not a valid SDL scancode constant."
            raise ValueError(e.format(key))
        scancode = key

    # Check for and return the current key state
    SDL_PumpEvents()
    numkeys = c_int(0)
    keys = SDL_GetKeyboardState(byref(numkeys))
    return keys[scancode]


def mouse_clicked(events, button=None, released=False):
    """Checks for any mouse clicks in a given event queue.

    This function is meant to be used with :func:`~sdl2.ext.get_events`::

        response = None
        while not response:
            q = get_events() # Fetch latest SDL input events
            if mouse_clicked(q, 'left'):
                response = 'left'
            elif mouse_clicked(q, 'right'):
                response = 'right'

    By default, this function checks for clicks from any button. However, you
    can also check for clicks from a specific button by specifying one of the
    following strings or SDL constants for the ``button`` argument:

    ===================== =============
    SDL Constant          String
    ===================== =============
    ``SDL_BUTTON_LEFT``   ``'left'``
    ``SDL_BUTTON_RIGHT``  ``'right'``
    ``SDL_BUTTON_MIDDLE`` ``'middle'``
    ``SDL_BUTTON_X1``     ``'x1'``
    ``SDL_BUTTON_X2``     ``'x2'``
    ===================== =============

    Args:
        events (list of :obj:`sdl2.SDL_Event`): A list of SDL events to check
            for mouse click events.
        button (str or int, optional): The name or SDL constant of the mouse
            button to listen for. If ``None``, all mouse buttons will . Defaults to ``None``.
        released (bool, optional): If True, will check the queue for mouse
            button release events instead of mouse button down events. Defaults
            to False.

    Returns:
        bool: True if the mouse has been clicked, otherwise False.

    """
    # If button specified, validate and coerce to SDL_BUTTON constant
    if button:
        button = _get_sdl_mouse_button(button)

    # Ensure 'events' is iterable
    if not isiterable(events):
        events = [events]

    # Check for any click events matching the criteria in the given event queue
    clicked = False
    for e in events:
        if e.type == (SDL_MOUSEBUTTONUP if released else SDL_MOUSEBUTTONDOWN):
            if not button or e.button.button == button:
                clicked = True
                break

    return clicked


def get_clicks(events, button=None, released=False):
    """Returns the (x, y) coordinates of the mouse clicks in an event queue.

    By default, this function returns clicks from any button. However, you can
    also return clicks from a specific button only by specifying a string or
    SDL button constant (see :func:`mouse_clicked` for details).

    Args:
        events (list of :obj:`sdl2.SDL_Event`): A list of SDL events to check
            for mouse click events.
        button (str or int, optional): The name or SDL constant of the mouse
            button to listen for. If ``None``, will return clicks from any mouse
            button. Defaults to ``None``.
        released (bool, optional): If True, will return the coordinates for
            mouse button release events instead of mouse button click events.
            Defaults to False.

    Returns:
        list: A list of the (x, y) coordinates for each matching click event
        in the queue.
    
    """
    # If button specified, validate and coerce to SDL_BUTTON constant
    if button:
        button = _get_sdl_mouse_button(button)

    # Ensure 'events' is iterable
    if not isiterable(events):
        events = [events]

    # Gather and return any matching mouse clicks in the given queue
    clicks = []
    for e in events:
        if e.type == (SDL_MOUSEBUTTONUP if released else SDL_MOUSEBUTTONDOWN):
            if not button or e.button.button == button:
                clicks.append((e.button.x, e.button.y))

    return clicks


def start_text_input():
    """Enables SDL unicode text input events.

    """
    SDL_StartTextInput()


def stop_text_input():
    """Disables SDL unicode text input events.

    """
    SDL_StopTextInput()


def text_input_enabled():
    """Checks whether SDL text input events are currently enabled.

    Returns:
        bool: True if text input events are enabled, otherwise False.

    """
    return SDL_IsTextInputActive() == SDL_TRUE


def get_text_input(events):
    """Returns the text input events from a queue as a unicode string.

    Note that SDL text input events need to be enabled for this function to
    work. This can be toggled with :func:`start_text_input` /
    :func:`stop_text_input` and queried with :func:`text_input_enabled`::

        start_text_input()

        response = u""
        while True:
            q = get_events()
            if key_pressed(q, 'return'):
                break
            response += get_text_input(q)
            draw_text(response)

        stop_text_input()

    If there are no text input events in the given event queue, an empty unicode
    string will be returned.

    Args:
        events (list of :obj:`sdl2.SDL_Event`): A list of SDL events to check
            for unicode text input (``SDL_TEXTINPUT``) events.

    Returns:
        str: A UTF8-encoded unicode string containing all text input from the
        queue.

    """
    # Make sure text input events are enabled
    if not text_input_enabled():
        e = "Text input events must be enabled before using get_text()"
        raise RuntimeError(e)

    # Ensure 'events' is iterable
    if not isiterable(events):
        events = [events]

    # Check for any key events matching the criteria in the given event queue
    text = u""
    for e in events:
        if e.type == SDL_TEXTINPUT:
            text += e.text.text.decode('utf-8')

    return text