Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

get_just_pressed and get_just_released #1912

Merged
merged 12 commits into from
Sep 16, 2023
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/key.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ class ScancodeWrapper(Tuple[bool, ...]): ...

def get_focused() -> bool: ...
def get_pressed() -> ScancodeWrapper: ...
def get_just_pressed() -> ScancodeWrapper: ...
def get_just_released() -> ScancodeWrapper: ...
def get_mods() -> int: ...
def set_mods(mods: int) -> None: ...
def set_repeat(delay: int = 0, interval: int = 0) -> None: ...
Expand Down
59 changes: 59 additions & 0 deletions docs/reST/ref/key.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,65 @@ for ``KMOD_NONE``, which should be compared using equals ``==``). For example:

.. ## pygame.key.get_pressed ##

.. function:: get_just_pressed

| :sl:`returns a pygame.key.ScancodeWrapper containing the most recent key presses`
| :sg:`get_just_pressed() -> bools`

Returns a mapping from key codes to booleans indicating which keys were
newly pressed as of the last time events were processed. This can be used
as a convenience function to detect keys that were pressed "this frame."

The result of this function is updated when new events are processed,
e.g. in :func:`pygame.event.get()` or :func:`pygame.event.pump()`.

A key can be marked as "just pressed" even if it is not currently pressed
according to :func:`pygame.key.get_pressed()`, if it was pressed and released
again during the same frame. Multiple presses and releases of the same key
are not distinguished from a single press with this function.

.. seealso:: :func:`pygame.key.get_just_released()`

.. note::
If you require getting the key presses in order, use the event queue
``KEYDOWN`` events

::

if pygame.key.get_just_pressed()[pygame.K_b]:
print("B key just pressed")

.. versionadded:: 2.4.0

.. ## pygame.key.get_just_pressed ##

.. function:: get_just_released

| :sl:`returns a pygame.key.ScancodeWrapper containing the most recent key releases`
| :sg:`get_just_pressed() -> bools`

Returns a mapping from key codes to booleans indicating which keys were
newly released as of the last time events were processed. This can be used
as a convenience function to detect keys that were released "this frame."

The result of this function is updated when new events are processed,
e.g. in :func:`pygame.event.get()` or :func:`pygame.event.pump()`.

.. seealso:: :func:`pygame.key.get_just_pressed()`

.. note::
If you require getting the key releases in order, use the event queue
``KEYUP`` events.

::

if pygame.key.get_just_released()[pygame.K_b]:
print("B key just released")

.. versionadded:: 2.4.0

.. ## pygame.key.get_just_released ##

.. function:: get_mods

| :sl:`determine which modifier keys are being held`
Expand Down
2 changes: 1 addition & 1 deletion src_c/_pygame.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ typedef enum {
#define PYGAMEAPI_COLOR_NUMSLOTS 5
#define PYGAMEAPI_MATH_NUMSLOTS 2
#define PYGAMEAPI_BASE_NUMSLOTS 24
#define PYGAMEAPI_EVENT_NUMSLOTS 6
#define PYGAMEAPI_EVENT_NUMSLOTS 8
#define PYGAMEAPI_WINDOW_NUMSLOTS 1

#endif /* _PYGAME_INTERNAL_H */
2 changes: 2 additions & 0 deletions src_c/doc/key_doc.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#define DOC_KEY "pygame module to work with the keyboard"
#define DOC_KEY_GETFOCUSED "get_focused() -> bool\ntrue if the display is receiving keyboard input from the system"
#define DOC_KEY_GETPRESSED "get_pressed() -> bools\nget the state of all keyboard buttons"
#define DOC_KEY_GETJUSTPRESSED "get_just_pressed() -> bools\nreturns a pygame.key.ScancodeWrapper containing the most recent key presses"
#define DOC_KEY_GETJUSTRELEASED "get_just_pressed() -> bools\nreturns a pygame.key.ScancodeWrapper containing the most recent key releases"
#define DOC_KEY_GETMODS "get_mods() -> int\ndetermine which modifier keys are being held"
#define DOC_KEY_SETMODS "set_mods(int) -> None\ntemporarily set which modifier keys are pressed"
#define DOC_KEY_SETREPEAT "set_repeat() -> None\nset_repeat(delay) -> None\nset_repeat(delay, interval) -> None\ncontrol how held keys are repeated"
Expand Down
28 changes: 27 additions & 1 deletion src_c/event.c
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ static SDL_TimerID _pg_repeat_timer = 0;
static SDL_Event _pg_repeat_event;
static SDL_Event _pg_last_keydown_event = {0};

/* Not used as text, acts as an array of bools */
static char pressed_keys[SDL_NUM_SCANCODES] = {0};
static char released_keys[SDL_NUM_SCANCODES] = {0};

#ifdef __EMSCRIPTEN__
/* these macros are no-op here */
#define PG_LOCK_EVFILTER_MUTEX
Expand Down Expand Up @@ -492,6 +496,7 @@ pg_event_filter(void *_, SDL_Event *event)
return 0;

PG_LOCK_EVFILTER_MUTEX
pressed_keys[event->key.keysym.scancode] = 1;
if (pg_key_repeat_delay > 0) {
if (_pg_repeat_timer)
SDL_RemoveTimer(_pg_repeat_timer);
Expand Down Expand Up @@ -521,6 +526,7 @@ pg_event_filter(void *_, SDL_Event *event)

else if (event->type == SDL_KEYUP) {
PG_LOCK_EVFILTER_MUTEX
released_keys[event->key.keysym.scancode] = 1;
if (_pg_repeat_timer && _pg_repeat_event.key.keysym.scancode ==
event->key.keysym.scancode) {
SDL_RemoveTimer(_pg_repeat_timer);
Expand Down Expand Up @@ -1587,8 +1593,14 @@ static void
_pg_event_pump(int dopump)
{
if (dopump) {
/* This needs to be reset just before calling pump, e.g. on calls to
* pygame.event.get(), but not on pygame.event.get(pump=False). */
memset(pressed_keys, 0, sizeof(pressed_keys));
memset(released_keys, 0, sizeof(released_keys));

SDL_PumpEvents();
}

/* We need to translate WINDOWEVENTS. But if we do that from the
* from event filter, internal SDL stuff that rely on WINDOWEVENT
* might break. So after every event pump, we translate events from
Expand Down Expand Up @@ -1768,6 +1780,18 @@ _pg_event_append_to_list(PyObject *list, SDL_Event *event)
return 1;
}

char *
pgEvent_GetKeyDownInfo(void)
{
return pressed_keys;
}

char *
pgEvent_GetKeyUpInfo(void)
{
return released_keys;
}

static PyObject *
_pg_get_all_events_except(PyObject *obj)
{
Expand Down Expand Up @@ -2274,13 +2298,15 @@ MODINIT_DEFINE(event)
}

/* export the c api */
assert(PYGAMEAPI_EVENT_NUMSLOTS == 6);
assert(PYGAMEAPI_EVENT_NUMSLOTS == 8);
c_api[0] = &pgEvent_Type;
c_api[1] = pgEvent_New;
c_api[2] = pg_post_event;
c_api[3] = pg_post_event_dictproxy;
c_api[4] = pg_EnableKeyRepeat;
c_api[5] = pg_GetKeyRepeat;
c_api[6] = pgEvent_GetKeyDownInfo;
c_api[7] = pgEvent_GetKeyUpInfo;

apiobj = encapsulate_api(c_api, "event");
if (PyModule_AddObject(module, PYGAMEAPI_LOCAL_ENTRY, apiobj)) {
Expand Down
4 changes: 4 additions & 0 deletions src_c/include/_pygame.h
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ typedef struct pgEventObject pgEventObject;

#define pg_GetKeyRepeat (*(void (*)(int *, int *))PYGAMEAPI_GET_SLOT(event, 5))

#define pgEvent_GetKeyDownInfo (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 6))

#define pgEvent_GetKeyUpInfo (*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 7))

#define import_pygame_event() IMPORT_PYGAME_MODULE(event)
#endif

Expand Down
60 changes: 60 additions & 0 deletions src_c/key.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,62 @@ key_get_pressed(PyObject *self, PyObject *_null)
return ret_obj;
}

static PyObject *
get_just_pressed(PyObject *self, PyObject *_null)
{
VIDEO_INIT_CHECK();

char *pressed_keys = pgEvent_GetKeyDownInfo();
PyObject *key_tuple = NULL;
PyObject *ret_obj = NULL;

if (!(key_tuple = PyTuple_New(SDL_NUM_SCANCODES)))
return NULL;

int i;
for (i = 0; i < SDL_NUM_SCANCODES; i++) {
PyObject *key_elem;
key_elem = PyBool_FromLong(pressed_keys[i]);
ankith26 marked this conversation as resolved.
Show resolved Hide resolved
if (!key_elem) {
Py_DECREF(key_tuple);
return NULL;
}
PyTuple_SET_ITEM(key_tuple, i, key_elem);
}
ret_obj = PyObject_CallFunctionObjArgs((PyObject *)&pgScancodeWrapper_Type,
key_tuple, NULL);
Py_DECREF(key_tuple);
return ret_obj;
}

static PyObject *
get_just_released(PyObject *self, PyObject *_null)
{
VIDEO_INIT_CHECK();

char *released_keys = pgEvent_GetKeyUpInfo();
PyObject *key_tuple = NULL;
PyObject *ret_obj = NULL;

if (!(key_tuple = PyTuple_New(SDL_NUM_SCANCODES)))
return NULL;

int i;
for (i = 0; i < SDL_NUM_SCANCODES; i++) {
PyObject *key_elem;
key_elem = PyBool_FromLong(released_keys[i]);
ankith26 marked this conversation as resolved.
Show resolved Hide resolved
if (!key_elem) {
Py_DECREF(key_tuple);
return NULL;
}
PyTuple_SET_ITEM(key_tuple, i, key_elem);
}
ret_obj = PyObject_CallFunctionObjArgs((PyObject *)&pgScancodeWrapper_Type,
key_tuple, NULL);
Py_DECREF(key_tuple);
return ret_obj;
}

Starbuck5 marked this conversation as resolved.
Show resolved Hide resolved
/* Keep our own key-name table for backwards compatibility.
* This has to be kept updated (only new things can be added, existing records
* in this must not be changed).
Expand Down Expand Up @@ -522,6 +578,10 @@ static PyMethodDef _key_methods[] = {
DOC_KEY_STOPTEXTINPUT},
{"set_text_input_rect", key_set_text_input_rect, METH_O,
DOC_KEY_SETTEXTINPUTRECT},
{"get_just_pressed", (PyCFunction)get_just_pressed, METH_NOARGS,
DOC_KEY_GETJUSTPRESSED},
{"get_just_released", (PyCFunction)get_just_released, METH_NOARGS,
DOC_KEY_GETJUSTRELEASED},

{NULL, NULL, 0, NULL}};

Expand Down
8 changes: 8 additions & 0 deletions test/key_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ def test_get_pressed(self):
states = pygame.key.get_pressed()
self.assertEqual(states[pygame.K_RIGHT], 0)

def test_get_just_pressed(self):
pressed_keys = pygame.key.get_just_pressed()
self.assertEqual(pressed_keys[pygame.K_RIGHT], 0)

def test_get_just_released(self):
released_keys = pygame.key.get_just_released()
self.assertEqual(released_keys[pygame.K_RIGHT], 0)

def test_get_pressed_not_iter(self):
states = pygame.key.get_pressed()
with self.assertRaises(TypeError):
Expand Down