Skip to content

Commit

Permalink
Add pygame.mouse.get_just_pressed/released() (#2836)
Browse files Browse the repository at this point in the history
* Add pygame.mouse.get_just_pressed/released + stubs, tests, docs

* Added user docs

* Fix format issue caused by clang-format

* Rename buttons variable

* Docs syntax fix

* Rename API functions for clarity

* Remove num_button args, update docs

* Add METH_NOARGS to function signature

* Fix language in docs

* add missing newlines

* Fix typo and docs
  • Loading branch information
damusss authored May 18, 2024
1 parent 0449dca commit a39d374
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 5 deletions.
2 changes: 2 additions & 0 deletions buildconfig/stubs/pygame/mouse.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ from ._common import Coordinate, Sequence, IntCoordinate
def get_pressed(num_buttons: Literal[3] = 3) -> Tuple[bool, bool, bool]: ...
@overload
def get_pressed(num_buttons: Literal[5]) -> Tuple[bool, bool, bool, bool, bool]: ...
def get_just_pressed() -> Tuple[bool, bool, bool, bool, bool]: ...
def get_just_released() -> Tuple[bool, bool, bool, bool, bool]: ...
def get_pos() -> Tuple[int, int]: ...
def get_rel() -> Tuple[int, int]: ...
@overload
Expand Down
20 changes: 20 additions & 0 deletions docs/reST/c_api/event.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,26 @@ Header file: src_c/include/pygame.h
If *event* is ``NULL`` then create an empty event object.
On failure raise a Python exception and return ``NULL``.
.. c:function:: char* pgEvent_GetKeyDownInfo(void)
Return an array of bools (using char) of length SDL_NUM_SCANCODES
with the most recent key presses.
.. c:function:: char* pgEvent_GetKeyUpInfo(void)
Return an array of bools (using char) of length SDL_NUM_SCANCODES
with the most recent key releases.
.. c:function:: char* pgEvent_GetMouseButtonDownInfo(void)
Return an array of bools (using char) of length 5
with the most recent button presses.
.. c:function:: char* pgEvent_GetMouseButtonUpInfo(void)
Return an array of bools (using char) of length 5
with the most recent button releases.
.. c:function:: int pg_post_event(Uint32 type, PyObject *dict)
Posts a pygame event that is an ``SDL_USEREVENT`` on the SDL side. This
Expand Down
54 changes: 52 additions & 2 deletions docs/reST/ref/mouse.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the
.. function:: get_pressed

| :sl:`get the state of the mouse buttons`
| :sg:`get_pressed(num_buttons=3) -> (button1, button2, button3)`
| :sg:`get_pressed(num_buttons=5) -> (button1, button2, button3, button4, button5)`
| :sg:`get_pressed(num_buttons=3) -> (left_button, middle_button, right_button)`
| :sg:`get_pressed(num_buttons=5) -> (left_button, middle_button, right_button, x1_button, x2_button)`
Returns a sequence of booleans representing the state of all the mouse
buttons. A true value means the mouse is currently being pressed at the time
Expand All @@ -107,6 +107,56 @@ scroll, such as ``which`` (it will tell you what exact mouse device trigger the

.. ## pygame.mouse.get_pressed ##
.. function:: get_just_pressed

| :sl:`get the most recently pressed buttons`
| :sg:`get_just_pressed() -> (left_button, middle_button, right_button, x1_button, x2_button)`
Very similar to :func:`pygame.mouse.get_pressed()`, returning a tuple
of length 5 with the important difference that the buttons are
True only in the frame they start being pressed. This can be convenient
for checking the buttons pressed "this frame", but for more precise results
and correct ordering prefer using the pygame.MOUSEBUTTONDOWN event.

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.mouse.get_just_released()`

::
if pygame.mouse.get_just_pressed()[0]:
print("LMB just pressed")

.. versionadded:: 2.5.0

.. ## pygame.mouse.get_just_pressed ##
.. function:: get_just_released

| :sl:`get the most recently released buttons`
| :sg:`get_just_released() -> (left_button, middle_button, right_button, x1_button, x2_button)`
Similar to :func:`pygame.mouse.get_pressed()`, returning a tuple
of length 5 with the important difference that the buttons are
True only in the frame they stop being pressed. This can be convenient
for checking the buttons released "this frame", but for more precise results
and correct ordering prefer using the pygame.MOUSEBUTTONUP event.

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.mouse.get_just_pressed()`

::
if pygame.mouse.get_just_released()[0]:
print("LMB just released")

.. versionadded:: 2.5.0

.. ## pygame.mouse.get_just_released ##
.. function:: get_pos

| :sl:`get the mouse cursor position`
Expand Down
2 changes: 1 addition & 1 deletion src_c/_pygame.h
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ typedef enum {
#define PYGAMEAPI_COLOR_NUMSLOTS 5
#define PYGAMEAPI_MATH_NUMSLOTS 2
#define PYGAMEAPI_BASE_NUMSLOTS 29
#define PYGAMEAPI_EVENT_NUMSLOTS 8
#define PYGAMEAPI_EVENT_NUMSLOTS 10
#define PYGAMEAPI_WINDOW_NUMSLOTS 1
#define PYGAMEAPI_GEOMETRY_NUMSLOTS 1

Expand Down
4 changes: 3 additions & 1 deletion src_c/doc/mouse_doc.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* Auto generated file: with makeref.py . Docs go in docs/reST/ref/ . */
#define DOC_MOUSE "pygame module to work with the mouse"
#define DOC_MOUSE_GETPRESSED "get_pressed(num_buttons=3) -> (button1, button2, button3)\nget_pressed(num_buttons=5) -> (button1, button2, button3, button4, button5)\nget the state of the mouse buttons"
#define DOC_MOUSE_GETPRESSED "get_pressed(num_buttons=3) -> (left_button, middle_button, right_button)\nget_pressed(num_buttons=5) -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the state of the mouse buttons"
#define DOC_MOUSE_GETJUSTPRESSED "get_just_pressed() -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the most recently pressed buttons"
#define DOC_MOUSE_GETJUSTRELEASED "get_just_released() -> (left_button, middle_button, right_button, x1_button, x2_button)\nget the most recently released buttons"
#define DOC_MOUSE_GETPOS "get_pos() -> (x, y)\nget the mouse cursor position"
#define DOC_MOUSE_GETREL "get_rel() -> (x, y)\nget the amount of mouse movement"
#define DOC_MOUSE_SETPOS "set_pos([x, y], /) -> None\nset the mouse cursor position"
Expand Down
28 changes: 27 additions & 1 deletion src_c/event.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ 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};
static char pressed_mouse_buttons[5] = {0};
static char released_mouse_buttons[5] = {0};

#ifdef __EMSCRIPTEN__
/* these macros are no-op here */
Expand Down Expand Up @@ -539,6 +541,14 @@ pg_event_filter(void *_, SDL_Event *event)

else if (event->type == SDL_MOUSEBUTTONDOWN ||
event->type == SDL_MOUSEBUTTONUP) {
if (event->type == SDL_MOUSEBUTTONDOWN &&
event->button.button - 1 < 5) {
pressed_mouse_buttons[event->button.button - 1] = 1;
}
else if (event->type == SDL_MOUSEBUTTONUP &&
event->button.button - 1 < 5) {
released_mouse_buttons[event->button.button - 1] = 1;
}
if (event->button.button & PGM_BUTTON_KEEP)
event->button.button ^= PGM_BUTTON_KEEP;
else if (event->button.button >= PGM_BUTTON_WHEELUP)
Expand Down Expand Up @@ -1602,6 +1612,8 @@ _pg_event_pump(int dopump)
* 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));
memset(pressed_mouse_buttons, 0, sizeof(pressed_mouse_buttons));
memset(released_mouse_buttons, 0, sizeof(released_mouse_buttons));

SDL_PumpEvents();
}
Expand Down Expand Up @@ -1797,6 +1809,18 @@ pgEvent_GetKeyUpInfo(void)
return released_keys;
}

char *
pgEvent_GetMouseButtonDownInfo(void)
{
return pressed_mouse_buttons;
}

char *
pgEvent_GetMouseButtonUpInfo(void)
{
return released_mouse_buttons;
}

static PyObject *
_pg_get_all_events_except(PyObject *obj)
{
Expand Down Expand Up @@ -2303,7 +2327,7 @@ MODINIT_DEFINE(event)
}

/* export the c api */
assert(PYGAMEAPI_EVENT_NUMSLOTS == 8);
assert(PYGAMEAPI_EVENT_NUMSLOTS == 10);
c_api[0] = &pgEvent_Type;
c_api[1] = pgEvent_New;
c_api[2] = pg_post_event;
Expand All @@ -2312,6 +2336,8 @@ MODINIT_DEFINE(event)
c_api[5] = pg_GetKeyRepeat;
c_api[6] = pgEvent_GetKeyDownInfo;
c_api[7] = pgEvent_GetKeyUpInfo;
c_api[8] = pgEvent_GetMouseButtonDownInfo;
c_api[9] = pgEvent_GetMouseButtonUpInfo;

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

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

#define pgEvent_GetMouseButtonDownInfo \
(*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 8))

#define pgEvent_GetMouseButtonUpInfo \
(*(char *(*)(void))PYGAMEAPI_GET_SLOT(event, 9))

#define import_pygame_event() IMPORT_PYGAME_MODULE(event)
#endif

Expand Down
42 changes: 42 additions & 0 deletions src_c/mouse.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,40 @@ mouse_get_pressed(PyObject *self, PyObject *args, PyObject *kwargs)
return tuple;
}

static PyObject *
mouse_get_just_pressed(PyObject *self, PyObject *_null)
{
PyObject *tuple;
VIDEO_INIT_CHECK();

char *pressed_buttons = pgEvent_GetMouseButtonDownInfo();
if (!(tuple = PyTuple_New(5)))
return NULL;

for (int i = 0; i < 5; i++) {
PyTuple_SET_ITEM(tuple, i, PyBool_FromLong(pressed_buttons[i]));
}

return tuple;
}

static PyObject *
mouse_get_just_released(PyObject *self, PyObject *_null)
{
PyObject *tuple;
VIDEO_INIT_CHECK();

char *released_buttons = pgEvent_GetMouseButtonUpInfo();
if (!(tuple = PyTuple_New(5)))
return NULL;

for (int i = 0; i < 5; i++) {
PyTuple_SET_ITEM(tuple, i, PyBool_FromLong(released_buttons[i]));
}

return tuple;
}

static PyObject *
mouse_set_visible(PyObject *self, PyObject *args)
{
Expand Down Expand Up @@ -500,6 +534,10 @@ static PyMethodDef _mouse_methods[] = {
{"get_rel", (PyCFunction)mouse_get_rel, METH_NOARGS, DOC_MOUSE_GETREL},
{"get_pressed", (PyCFunction)mouse_get_pressed,
METH_VARARGS | METH_KEYWORDS, DOC_MOUSE_GETPRESSED},
{"get_just_pressed", (PyCFunction)mouse_get_just_pressed, METH_NOARGS,
DOC_MOUSE_GETJUSTPRESSED},
{"get_just_released", (PyCFunction)mouse_get_just_released, METH_NOARGS,
DOC_MOUSE_GETJUSTRELEASED},
{"set_visible", mouse_set_visible, METH_VARARGS, DOC_MOUSE_SETVISIBLE},
{"get_visible", mouse_get_visible, METH_NOARGS, DOC_MOUSE_GETVISIBLE},
{"get_focused", (PyCFunction)mouse_get_focused, METH_NOARGS,
Expand Down Expand Up @@ -540,6 +578,10 @@ MODINIT_DEFINE(mouse)
if (PyErr_Occurred()) {
return NULL;
}
import_pygame_event();
if (PyErr_Occurred()) {
return NULL;
}

/* create the module */
return PyModule_Create(&_module);
Expand Down
16 changes: 16 additions & 0 deletions test/mouse_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,22 @@ def test_get_pressed(self):
with self.assertRaises(ValueError):
pygame.mouse.get_pressed(4)

def test_get_just_pressed(self):
mouse_buttons = pygame.mouse.get_just_pressed()
self.assertIsInstance(mouse_buttons, tuple)
self.assertEqual(len(mouse_buttons), 5)
for value in mouse_buttons:
self.assertIsInstance(value, bool)
self.assertEqual(value, False)

def test_get_just_released(self):
mouse_buttons = pygame.mouse.get_just_released()
self.assertIsInstance(mouse_buttons, tuple)
self.assertEqual(len(mouse_buttons), 5)
for value in mouse_buttons:
self.assertIsInstance(value, bool)
self.assertEqual(value, False)

def test_get_pos(self):
"""Ensures get_pos returns the correct types."""
expected_length = 2
Expand Down

0 comments on commit a39d374

Please sign in to comment.