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

Add pygame.mouse.get_just_pressed/released() #2836

Merged
Merged
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
56 changes: 54 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,58 @@ 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()`, returing a tuple
damusss marked this conversation as resolved.
Show resolved Hide resolved
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 to have the correct order the pygame.MOUSEBUTTONDOWN event could be
preferred.
damusss marked this conversation as resolved.
Show resolved Hide resolved

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()`, returing 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 to have the correct order the pygame.MOUSEBUTTONUP event could be
preferred.
damusss marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -525,7 +525,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]));
}
Comment on lines +172 to +177
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the mouse doesn't support 5 buttons here? Just being curious here. And also what if we made these function configurable concerning the number of buttons we want to return? like by passing in an optional int like num_buttons.
Would require METH_FASTCALL tho.

Copy link
Member Author

@damusss damusss May 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the mouse doesn't support 5 buttons here? Just being curious here. And also what if we made these function configurable concerning the number of buttons we want to return? like by passing in an optional int like num_buttons. Would require METH_FASTCALL tho.

Hi,
That's what MyreMylar talked about in the first comment. If the mouse doesn't support the buttons, the entries will always be False, and I personally don't see an issue with that. The tuple values come from a function that I created (edit: don't want to remove credits to who made it for the key module as I used it as a base) which memsets to 0, and if no events with those buttons are passed, it will never be set to 1.
The num_buttons added to get_pressed was for compatibility between pygame 1 and pygame 2 (added by MyreMylar) and MyreMylar even suggested that it could be removed with pygame 3. This is new API, so it doesn't need it. Removing that argument also cleaned up the code a lot.


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 @@ -497,6 +531,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 @@ -537,6 +575,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
Loading