from ..stdinc import SDL_TRUE
from ..keyboard import (
SDL_GetKeyFromName, SDL_GetKeyName, SDL_StartTextInput, SDL_StopTextInput,
SDL_IsTextInputActive,
)
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,
)
from .compat import _is_text, byteify, isiterable
__all__ = [
"key_pressed", "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
[docs]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
[docs]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
[docs]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
[docs]def start_text_input():
"""Enables SDL unicode text input events.
"""
SDL_StartTextInput()
[docs]def stop_text_input():
"""Disables SDL unicode text input events.
"""
SDL_StopTextInput()
[docs]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
[docs]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