Repository: ppizarror/pygame-menu

Issue:
get_cursor() removed from SDL2
**Environment information**
- OS: Ubuntu 20.04
- python version: v3.7
- pygame version: v2.0.0
- pygame-menu version: v4.0.4

**Bug description**

In SDL2, the `pygame.mouse.get_cursor()` function has been removed, see https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.get_cursor .

To be compatible, `pygame-menu` shall not use the `get_cursor()` function. Currently the following line throw an error:

https://github.com/ppizarror/pygame-menu/blob/9e21edadbaf4b8e013d2590d0941c9b0377ba5be/pygame_menu/widgets/core/widget.py#L598-L602

```python
  File "/usr/local/lib/python3.8/dist-packages/pygame_menu/menu.py", line 2590, in update
    widget._check_mouseover(event)
  File "/usr/local/lib/python3.8/dist-packages/pygame_menu/widgets/core/widget.py", line 679, in _check_mouseover
    self.mouseover(event, check_all_widget_mouseleave)
  File "/usr/local/lib/python3.8/dist-packages/pygame_menu/widgets/core/widget.py", line 600, in mouseover
    previous_cursor = pygame.mouse.get_cursor()  # Previous cursor
TypeError: The get_cursor method is unavailable in SDL2
```



Discussion:

Patch:
diff --git a/.gitignore b/.gitignore
index a58858dd..71921c80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,7 @@ pyproject_update.py
 pyproject.toml
 
 # Lock file
-*.lock
\ No newline at end of file
+*.lock
+
+# MacOS files
+.DS_Store
\ No newline at end of file
diff --git a/pygame_menu/utils.py b/pygame_menu/utils.py
index 2987c672..d4075062 100644
--- a/pygame_menu/utils.py
+++ b/pygame_menu/utils.py
@@ -43,6 +43,7 @@ __all__ = [
     'check_key_pressed_valid',
     'fill_gradient',
     'format_color',
+    'get_cursor',
     'get_finger_pos',
     'is_callable',
     'load_pygame_image_file',
@@ -352,6 +353,19 @@ def format_color(
     return c.r, c.g, c.b, c.a
 
 
+def get_cursor() -> CursorInputType:
+    """
+    Returns the pygame cursor object.
+
+    :return: Cursor object
+    """
+    try:
+        return pygame.mouse.get_cursor()
+    except TypeError as e:
+        warn(str(e))
+    return None
+
+
 def get_finger_pos(menu: 'pygame_menu.Menu', event: EventType) -> Tuple2IntType:
     """
     Return the position from finger (or mouse) event on x-axis and y-axis (x, y).
diff --git a/pygame_menu/widgets/core/widget.py b/pygame_menu/widgets/core/widget.py
index d4ae8902..37aa23c6 100644
--- a/pygame_menu/widgets/core/widget.py
+++ b/pygame_menu/widgets/core/widget.py
@@ -65,7 +65,8 @@ from pygame_menu.locals import POSITION_NORTHWEST, POSITION_SOUTHWEST, POSITION_
 from pygame_menu.sound import Sound
 from pygame_menu.utils import make_surface, assert_alignment, assert_color, \
     assert_position, assert_vector, is_callable, parse_padding, uuid4, \
-    mouse_motion_current_mouse_position, PYGAME_V2, set_pygame_cursor, warn
+    mouse_motion_current_mouse_position, PYGAME_V2, set_pygame_cursor, warn, \
+    get_cursor
 from pygame_menu.widgets.core.selection import Selection
 
 from pygame_menu._types import Optional, ColorType, Tuple2IntType, NumberType, \
@@ -597,7 +598,7 @@ class Widget(Base):
             check_widget_mouseleave(event)
 
         # Change cursor
-        previous_cursor = pygame.mouse.get_cursor()  # Previous cursor
+        previous_cursor = get_cursor()  # Previous cursor
         set_pygame_cursor(self._cursor)
 
         # Update previous state


Test patch:
diff --git a/test/test_base.py b/test/test_base.py
index 3b10d1b5..7ef1c515 100644
--- a/test/test_base.py
+++ b/test/test_base.py
@@ -1,6 +1,6 @@
 """
-pygame-obj
-https://github.com/ppizarror/pygame-obj
+pygame-menu
+https://github.com/ppizarror/pygame-menu
 
 TEST BASE
 Test base class.
diff --git a/test/test_controls.py b/test/test_controls.py
index 371cef4a..ccd3c094 100644
--- a/test/test_controls.py
+++ b/test/test_controls.py
@@ -1,6 +1,6 @@
 """
-pygame-obj
-https://github.com/ppizarror/pygame-obj
+pygame-menu
+https://github.com/ppizarror/pygame-menu
 
 TEST CONTROLS
 Test controls configuration.
diff --git a/test/test_frame.py b/test/test_frame.py
index d58e6b1d..738d6c64 100644
--- a/test/test_frame.py
+++ b/test/test_frame.py
@@ -42,7 +42,7 @@ import pygame_menu
 import pygame_menu.controls as ctrl
 
 from pygame_menu.locals import ORIENTATION_VERTICAL, ORIENTATION_HORIZONTAL
-from pygame_menu.utils import set_pygame_cursor
+from pygame_menu.utils import set_pygame_cursor, get_cursor
 from pygame_menu.widgets import Button
 
 
@@ -1504,11 +1504,13 @@ class FrameWidgetTest(unittest.TestCase):
         f3._id__repr__ = True
 
         # Get cursors
-        cur_none = pygame.mouse.get_cursor()
+        cur_none = get_cursor()
+        if cur_none is None:
+            return
         set_pygame_cursor(pygame_menu.locals.CURSOR_ARROW)
-        cur_arrow = pygame.mouse.get_cursor()
+        cur_arrow = get_cursor()
         set_pygame_cursor(pygame_menu.locals.CURSOR_HAND)
-        cur_hand = pygame.mouse.get_cursor()
+        cur_hand = get_cursor()
         set_pygame_cursor(cur_none)
 
         if PYGAME_V2:
@@ -1628,92 +1630,92 @@ class FrameWidgetTest(unittest.TestCase):
         # | .---------------------. |
         # .-------------------------.
         # vbottom <100px>
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(f1))
         self.assertEqual(test, [True, False, False, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [f1, [f1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_hand)
+        self.assertEqual(get_cursor(), cur_hand)
 
         # Move to b1 inside f1
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b1))
         self.assertEqual(test, [True, True, False, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [b1, [b1, cur_hand, [f1, cur_none, []]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # Move to f2, inside f1
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(f2))
         self.assertEqual(test, [True, False, True, False, False, False])  # out from b1
         self.assertEqual(WIDGET_MOUSEOVER, [f2, [f2, cur_hand, [f1, cur_none, []]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_hand)
+        self.assertEqual(get_cursor(), cur_hand)
 
         # Move to b2, inside f2+f1
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b2))
         self.assertEqual(test, [True, False, True, True, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [b2, [b2, cur_hand, [f2, cur_hand, [f1, cur_none, []]]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # Move to f3
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(f3))
         self.assertEqual(test, [True, False, True, False, True, False])  # out from b2
         self.assertEqual(WIDGET_MOUSEOVER, [f3, [f3, cur_hand, [f2, cur_hand, [f1, cur_none, []]]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_hand)
+        self.assertEqual(get_cursor(), cur_hand)
 
         # Move to b3, inside f3+f2+f1
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b3))
         self.assertEqual(test, [True, False, True, False, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [b3, [b3, cur_hand, [f3, cur_hand, [f2, cur_hand, [f1, cur_none, []]]]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # From b3, move mouse out from window
         menu.update(PygameEventUtils.leave_window())
         self.assertEqual(test, [False, False, False, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Move from out to inner widget (b3), this should call f1->f2->f3->b3
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b3))
         self.assertEqual(test, [True, False, True, False, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [b3, [b3, cur_hand, [f3, cur_hand, [f2, cur_hand, [f1, cur_none, []]]]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # Move from b3->f2, this should call b3, f3 but not call f2 as this is actually over
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(f2))
         self.assertEqual(test, [True, False, True, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [f2, [f2, cur_hand, [f1, cur_none, []]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_hand)
+        self.assertEqual(get_cursor(), cur_hand)
 
         # Move from f2->b1, this should call f2
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b1))
         self.assertEqual(test, [True, True, False, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [b1, [b1, cur_hand, [f1, cur_none, []]]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # Move from b1 to outside the menu
         menu.update(PygameEventUtils.topleft_rect_mouse_motion((1, 1)))
         self.assertEqual(test, [False, False, False, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Move from out to b2, this should call f1->f2->b2
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b2))
         self.assertEqual(test, [True, False, True, True, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [b2, [b2, cur_hand, [f2, cur_hand, [f1, cur_none, []]]]])
         self.assertEqual(menu.get_mouseover_widget(), b2)
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
         # Unpack b2
         f2.unpack(b2)
         if test != [False, False, False, False, False, False]:
             return
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Check b2
         menu.scroll_to_widget(b2)
         menu.update(PygameEventUtils.topleft_rect_mouse_motion(b2))
         self.assertEqual(test, [False, False, False, True, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [b2, [b2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_arrow)
+        self.assertEqual(get_cursor(), cur_arrow)
 
     def test_title(self) -> None:
         """
diff --git a/test/test_menu.py b/test/test_menu.py
index ae35b385..e9d27ce8 100644
--- a/test/test_menu.py
+++ b/test/test_menu.py
@@ -46,7 +46,7 @@ import pygame_menu.controls as ctrl
 
 from pygame_menu import events
 from pygame_menu.locals import FINGERDOWN, FINGERMOTION
-from pygame_menu.utils import set_pygame_cursor
+from pygame_menu.utils import set_pygame_cursor, get_cursor
 from pygame_menu.widgets import Label, Button
 
 # Configure the tests
@@ -1803,13 +1803,15 @@ class MenuTest(unittest.TestCase):
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
 
         # Get cursors
-        cur_none = pygame.mouse.get_cursor()
+        cur_none = get_cursor()
+        if cur_none is None:
+            return
 
         set_pygame_cursor(btn1._cursor)
-        cur1 = pygame.mouse.get_cursor()
+        cur1 = get_cursor()
 
         set_pygame_cursor(btn2._cursor)
-        cur2 = pygame.mouse.get_cursor()
+        cur2 = get_cursor()
 
         set_pygame_cursor(cur_none)
 
@@ -1823,31 +1825,31 @@ class MenuTest(unittest.TestCase):
             surface.fill((255, 255, 255), btn1.get_rect(to_real_position=True))
 
         deco.add_callable(draw_rect, prev=False, pass_args=False)
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn1))
         self.assertEqual(menu.get_selected_widget(), btn1)
         self.assertEqual(test, [True, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn1, [btn1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur1)
+        self.assertEqual(get_cursor(), cur1)
 
         # Place mouse away. This should force widget 1 mouseleave
         mouse_away_event = PygameEventUtils.middle_rect_click((1000, 1000), evtype=pygame.MOUSEMOTION)
         menu.update(mouse_away_event)
         self.assertEqual(test, [True, True, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Place over widget 2
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(test, [True, True, True, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn2, [btn2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur2)
+        self.assertEqual(get_cursor(), cur2)
 
         # Place mouse away. This should force widget 1 mouseleave
         menu.update(mouse_away_event)
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Test immediate switch, from 1 to 2, then from 2 to 1, then off
         test = [False, False, False, False]
@@ -1855,16 +1857,16 @@ class MenuTest(unittest.TestCase):
         self.assertEqual(menu.get_selected_widget(), btn1)
         self.assertEqual(test, [True, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn1, [btn1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur1)
+        self.assertEqual(get_cursor(), cur1)
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(menu.get_selected_widget(), btn1)
         self.assertEqual(test, [True, True, True, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn2, [btn2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur2)
+        self.assertEqual(get_cursor(), cur2)
         menu.update(mouse_away_event)
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Same switch test, but now with widget selection by mouse motion
         menu._mouse_motion_selection = True
@@ -1875,20 +1877,20 @@ class MenuTest(unittest.TestCase):
         self.assertEqual(menu.get_selected_widget(), btn1)
         self.assertEqual(test, [True, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn1, [btn1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur1)
+        self.assertEqual(get_cursor(), cur1)
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(menu.get_selected_widget(), btn2)
         self.assertEqual(test, [True, True, True, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn2, [btn2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur2)
+        self.assertEqual(get_cursor(), cur2)
         menu.update(mouse_away_event)
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
         menu.update(mouse_away_event)
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
         self.assertEqual(menu.get_selected_widget(), btn2)
 
         # Mouseover btn1, but then hide it
@@ -1897,37 +1899,37 @@ class MenuTest(unittest.TestCase):
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn1))
         self.assertEqual(test, [True, False, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn1, [btn1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur1)
+        self.assertEqual(get_cursor(), cur1)
         btn1.hide()
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Test close
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(test, [True, True, True, False])
         self.assertEqual(WIDGET_MOUSEOVER, [btn2, [btn2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur2)
+        self.assertEqual(get_cursor(), cur2)
         menu.disable()
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
         btn2.mouseleave(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(test, [True, True, True, True])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Enable
         menu.enable()
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn2))
         self.assertEqual(test, [True, True, False, True])
         self.assertEqual(WIDGET_MOUSEOVER, [btn2, [btn2, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur2)
+        self.assertEqual(get_cursor(), cur2)
 
         # Move to hidden
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn1))
         self.assertEqual(test, [True, True, False, False])
         self.assertEqual(WIDGET_MOUSEOVER, [None, []])
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
         # Unhide
         btn1.show()
@@ -1954,13 +1956,13 @@ class MenuTest(unittest.TestCase):
         # Select btn1
         menu.update(PygameEventUtils.middle_rect_mouse_motion(btn1))
         self.assertEqual(WIDGET_MOUSEOVER, [btn1, [btn1, cur_none, []]])
-        self.assertEqual(pygame.mouse.get_cursor(), cur1)
+        self.assertEqual(get_cursor(), cur1)
 
         # Change previous cursor to assert an error
         self.assertEqual(cur_none, WIDGET_TOP_CURSOR[0])
         WIDGET_MOUSEOVER[1][1] = cur2
         menu.update(mouse_away_event)
-        self.assertEqual(pygame.mouse.get_cursor(), cur_none)
+        self.assertEqual(get_cursor(), cur_none)
 
     # noinspection SpellCheckingInspection
     def test_floating_pos(self) -> None: