diff --git a/buildconfig/stubs/pygame/mouse.pyi b/buildconfig/stubs/pygame/mouse.pyi index e8d6f954e9..bec7b2899c 100644 --- a/buildconfig/stubs/pygame/mouse.pyi +++ b/buildconfig/stubs/pygame/mouse.pyi @@ -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 diff --git a/docs/reST/c_api/event.rst b/docs/reST/c_api/event.rst index 6f115b6cbe..7f053faf3d 100644 --- a/docs/reST/c_api/event.rst +++ b/docs/reST/c_api/event.rst @@ -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 diff --git a/docs/reST/ref/mouse.rst b/docs/reST/ref/mouse.rst index f6e454b534..c01a70997c 100644 --- a/docs/reST/ref/mouse.rst +++ b/docs/reST/ref/mouse.rst @@ -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 @@ -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` diff --git a/src_c/_pygame.h b/src_c/_pygame.h index a02b623f87..6c3dff6ae8 100644 --- a/src_c/_pygame.h +++ b/src_c/_pygame.h @@ -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 diff --git a/src_c/doc/mouse_doc.h b/src_c/doc/mouse_doc.h index d571264a32..9ede94f210 100644 --- a/src_c/doc/mouse_doc.h +++ b/src_c/doc/mouse_doc.h @@ -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" diff --git a/src_c/event.c b/src_c/event.c index 685dff68ca..c651dd5cf3 100644 --- a/src_c/event.c +++ b/src_c/event.c @@ -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 */ @@ -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) @@ -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(); } @@ -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) { @@ -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; @@ -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)) { diff --git a/src_c/include/_pygame.h b/src_c/include/_pygame.h index 31aa7341b3..508d89fbd1 100644 --- a/src_c/include/_pygame.h +++ b/src_c/include/_pygame.h @@ -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 diff --git a/src_c/mouse.c b/src_c/mouse.c index a359570b16..ca43a27f32 100644 --- a/src_c/mouse.c +++ b/src_c/mouse.c @@ -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) { @@ -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, @@ -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); diff --git a/test/mouse_test.py b/test/mouse_test.py index 917f6ee8f6..3373bd2853 100644 --- a/test/mouse_test.py +++ b/test/mouse_test.py @@ -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