Skip to content

Commit

Permalink
Added subsurface compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
ppizarror committed Apr 4, 2023
1 parent c262c6c commit 1baac35
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 17 deletions.
8 changes: 4 additions & 4 deletions README.rst
@@ -1,6 +1,6 @@
===========
pygame-menu
===========
==============
pygame-menu-ce
==============

.. image:: docs/_static/pygame_menu_small.png
:align: center
Expand Down Expand Up @@ -77,7 +77,7 @@ https://pygame-menu.readthedocs.io
Install Instructions
--------------------

Pygame-menu can be installed via pip. Simply run:
Pygame-menu-ce can be installed via pip. Simply run:

.. code-block:: bash
Expand Down
6 changes: 3 additions & 3 deletions pygame_menu/__init__.py
@@ -1,5 +1,5 @@
"""
pygame-menu (for pygame-ce)
pygame-menu
https://github.com/ppizarror/pygame-menu
PYGAME-MENU
Expand Down Expand Up @@ -119,11 +119,11 @@

]
__copyright__ = 'Copyright 2017 Pablo Pizarro R. @ppizarror'
__description__ = 'A menu for pygame-ce. Simple, and easy to use'
__description__ = 'A menu for pygame. Simple, and easy to use'
__email__ = 'pablo@ppizarror.com'
__keywords__ = 'pygame menu menus gui widget input button pygame-menu image sound ui'
__license__ = 'MIT'
__module_name__ = 'pygame-menu-ce'
__module_name__ = 'pygame-menu'
__url__ = 'https://pygame-menu.readthedocs.io'
__url_bug_tracker__ = 'https://github.com/ppizarror/pygame-menu/issues'
__url_documentation__ = 'https://pygame-menu.readthedocs.io'
Expand Down
40 changes: 35 additions & 5 deletions pygame_menu/menu.py
Expand Up @@ -98,6 +98,7 @@ class Menu(Base):
:param position: Position on x-axis and y-axis. If the value is only 2 elements, the position is relative to the window width (thus, values must be 0-100%); else, the third element defines if the position is relative or not. If ``(x, y, False)`` the values of ``(x, y)`` are in px
:param rows: Number of rows of each column, if there's only 1 column ``None`` can be used for no-limit. Also, a tuple can be provided for defining different number of rows for each column, for example ``rows=10`` (each column can have a maximum 10 widgets), or ``rows=[2, 3, 5]`` (first column has 2 widgets, second 3, and third 5)
:param screen_dimension: List/Tuple representing the dimensions the Menu should reference for sizing/positioning (width, height), if ``None`` pygame is queried for the display mode. This value defines the ``window_size`` of the Menu
:param surface: The surface that contains the Menu. By default the Menu always considers that it is drawn on a surface that uses all window width/height. However, if a sub-surface is used the ``surface`` value will be used instead to retrieve the offset. Also, if ``surface`` is provided the menu can be drawn without providing a surface object while calling ``Menu.draw()``
:param theme: Menu theme
:param touchscreen: Enable/disable touch action inside the Menu. Only available on pygame 2
:param touchscreen_motion_selection: Select widgets using touchscreen motion. If ``True`` menu draws a ``focus`` on the selected widget
Expand Down Expand Up @@ -158,6 +159,8 @@ class Menu(Base):
_sound: 'Sound'
_stats: '_MenuStats'
_submenus: Dict['Menu', List['Widget']]
_surface: Optional['pygame.Surface'] # The surface that contains the menu
_surface_last: Optional['pygame.Surface'] # The last surface used to draw the menu
_theme: 'Theme'
_top: 'Menu'
_touchscreen: bool
Expand Down Expand Up @@ -206,6 +209,7 @@ def __init__(
position: Union[Vector2NumberType, Tuple[NumberType, NumberType, bool]] = (50, 50, True),
rows: MenuRowsType = None,
screen_dimension: Optional[Vector2IntType] = None,
surface: Optional['pygame.Surface'] = None,
theme: 'Theme' = THEME_DEFAULT.copy(),
touchscreen: bool = False,
touchscreen_motion_selection: bool = False,
Expand All @@ -227,6 +231,7 @@ def __init__(
assert isinstance(mouse_visible_update, bool)
assert isinstance(overflow, (VectorInstance, bool))
assert isinstance(rows, (int, type(None), VectorInstance))
assert isinstance(surface, (pygame.Surface, type(None)))
assert isinstance(theme, Theme), \
'theme bust be a pygame_menu.themes.Theme object instance'
assert isinstance(touchscreen, bool)
Expand Down Expand Up @@ -370,6 +375,8 @@ def __init__(
self._sound = Sound()
self._stats = _MenuStats()
self._submenus = {}
self._surface = surface
self._surface_last = None
self._theme = theme

# Set callbacks
Expand Down Expand Up @@ -2029,7 +2036,7 @@ def enable_render(self) -> 'Menu':
self._render()
return self

def draw(self, surface: 'pygame.Surface', clear_surface: bool = False) -> 'Menu':
def draw(self, surface: Optional['pygame.Surface'] = None, clear_surface: bool = False) -> 'Menu':
"""
Draw the **current** Menu into the given surface.
Expand All @@ -2038,13 +2045,18 @@ def draw(self, surface: 'pygame.Surface', clear_surface: bool = False) -> 'Menu'
This method should not be used along :py:meth:`pygame_menu.menu.Menu.get_current`,
for example, ``menu.get_current().draw(...)``
:param surface: Pygame surface to draw the Menu
:param surface: Pygame surface to draw the Menu. If None, the Menu will use the provided ``surface`` from the constructor
:param clear_surface: Clear surface using theme ``surface_clear_color``
:return: Self reference **(current)**
"""
if surface is None:
surface = self._surface
assert isinstance(surface, pygame.Surface)
assert isinstance(clear_surface, bool)

# Update last surface
self._surface_last = surface

if not self.is_enabled():
self._current._runtime_errors.throw(self._current._runtime_errors.draw, 'menu is not enabled')
return self._current
Expand Down Expand Up @@ -2451,6 +2463,21 @@ def _right(self, apply_sound: bool = False) -> bool:
return self._current._move_selected_left_right(1)
return False

def get_last_surface_offset(self) -> Tuple2IntType:
"""
Return the last menu surface offset.
.. warning::
This method should not be used along :py:meth:`pygame_menu.menu.Menu.get_current`,
for example, ``menu.get_current().update(...)``.
:return: Return the offset of the last surface used. If ``surface`` param was provided within Menu constructor that offset will be used instead, else, the returned value will be the last surface used to draw the Menu. Else, ``(0, 0)`` will be returned
"""
if self._surface is not None:
return self._surface.get_offset()
return self._surface_last.get_offset() if self._surface_last is not None else (0, 0)

def get_last_update_mode(self) -> List[str]:
"""
Return the update mode.
Expand Down Expand Up @@ -2906,7 +2933,7 @@ def collide(self, event: EventType) -> bool:

def mainloop(
self,
surface: 'pygame.Surface',
surface: Optional['pygame.Surface'] = None,
bgfun: Optional[Union[Callable[['Menu'], Any], CallableNoArgsType]] = None,
**kwargs
) -> 'Menu':
Expand Down Expand Up @@ -2935,7 +2962,7 @@ def mainloop(
Finally, mainloop can be disabled externally if menu.disable() is called.
kwargs (Optional)
- ``clear_surface`` (bool) – If ``True`` surface is cleared using ``theme.surface_clear_color``
- ``clear_surface`` (bool) – If ``True`` surface is cleared using ``theme.surface_clear_color``. Default equals to ``True``
- ``disable_loop`` (bool) – If ``True`` the mainloop only runs once. Use for running draw and update in a single call
- ``fps_limit`` (int) – Maximum FPS of the loop. Default equals to ``theme.fps``. If ``0`` there's no limit
- ``wait_for_event`` (bool) – Holds the loop until an event is provided, useful to save CPU power
Expand All @@ -2945,7 +2972,7 @@ def mainloop(
This method should not be used along :py:meth:`pygame_menu.menu.Menu.get_current`,
for example, ``menu.get_current().mainloop(...)``.
:param surface: Pygame surface to draw the Menu
:param surface: Pygame surface to draw the Menu. If None, the Menu will use the provided ``surface`` from the constructor
:param bgfun: Background function called on each loop iteration before drawing the Menu
:param kwargs: Optional keyword arguments
:return: Self reference **(current)**
Expand All @@ -2956,6 +2983,9 @@ def mainloop(
fps_limit = kwargs.get('fps_limit', self._theme.fps)
wait_for_event = kwargs.get('wait_for_event', False)

if surface is None:
surface = self._surface

assert isinstance(clear_surface, bool)
assert isinstance(disable_loop, bool)
assert isinstance(fps_limit, NumberInstance)
Expand Down
2 changes: 1 addition & 1 deletion pygame_menu/version.py
Expand Up @@ -32,6 +32,6 @@ def __str__(self) -> str:
patch = property(lambda self: self[2])


vernum = Version(4, 4, 1)
vernum = Version(4, 4, 2)
ver = str(vernum)
rev = ''
5 changes: 4 additions & 1 deletion pygame_menu/widgets/core/widget.py
Expand Up @@ -1537,7 +1537,7 @@ def get_rect(
:param inflate: Inflate rect on x-axis and y-axis (x, y) in px
:param apply_padding: Apply widget padding
:param use_transformed_padding: Use scaled padding if the widget is scaled
:param to_real_position: Transform the widget rect to real coordinates (if the Widget change the position if scrollbars move offsets). Used by events
:param to_real_position: Transform the widget rect to real coordinates (if the Widget change the position if scrollbars move offsets) within the window. Used by events
:param to_absolute_position: Transform the widget rect to absolute coordinates (if the Widget does not change the position if scrollbars move offsets). Used by events
:param render: Force widget rendering
:param real_position_visible: Return only the visible width/height if ``to_real_position=True``
Expand Down Expand Up @@ -1566,6 +1566,9 @@ def get_rect(
'real and absolute positions cannot be True at the same time'
if to_real_position:
rect = self._scrollarea.to_real_position(rect, visible=real_position_visible)
soff = (0, 0) if self._menu is None else self._menu.get_last_surface_offset()
rect.x += soff[0]
rect.y += soff[1]
elif to_absolute_position:
rect = self._scrollarea.to_absolute_position(rect)

Expand Down
6 changes: 3 additions & 3 deletions setup.py
@@ -1,5 +1,5 @@
"""
pygame-menu
pygame-menu-ce
https://github.com/ppizarror/pygame-menu
SETUP DISTRIBUTION
Expand Down Expand Up @@ -44,11 +44,11 @@

# Setup library
setup(
name=pygame_menu.__module_name__,
name=pygame_menu.__module_name__ + '-ce',
version=pygame_menu.__version__,
author=pygame_menu.__author__,
author_email=pygame_menu.__email__,
description=pygame_menu.__description__,
description=pygame_menu.__description__.replace('pygame', 'pygame-ce'),
long_description=long_description,
url=pygame_menu.__url__,
project_urls={
Expand Down
48 changes: 48 additions & 0 deletions test/test_menu.py
Expand Up @@ -2586,3 +2586,51 @@ def test_menu_widget_selected_events(self) -> None:
name.receive_menu_update_events = True
menu.update(PygameEventUtils.key(pygame.K_s, keydown=True, char='s'))
self.assertEqual(name.get_value(), 'as')

def test_subsurface_offset(self) -> None:
"""
Test subsurface widget offset.
"""
main_surface = surface
w, h = surface.get_size()
left_surf_w, left_surf_h = 300, h
menu_w, menu_h = w - left_surf_w, h
# left_surface = main_surface.subsurface((0, 0, left_surf_w, left_surf_h))
menu_surface = main_surface.subsurface((300, 0, menu_w, menu_h))
menu = MenuUtils.generic_menu(title='Subsurface', width=menu_w, height=menu_h, position_x=0, position_y=0, mouse_motion_selection=True, surface=menu_surface)
btn_click = [False]

def btn() -> None:
"""
Method executed by button.
"""
btn_click[0] = True

b1 = menu.add.button('Button', btn)
menu._surface = None
self.assertEqual(menu.get_last_surface_offset(), (0, 0))
self.assertEqual(b1.get_rect(to_real_position=True).x, 94)
self.assertIsNone(menu._surface_last)
menu._surface = menu_surface
self.assertEqual(menu._surface, menu_surface)
self.assertEqual(menu.get_last_surface_offset(), (300, 0))
r = b1.get_rect(to_real_position=True)
self.assertEqual(r.x, 394)
self.assertIsNone(menu._surface_last)
menu.draw()
self.assertEqual(menu._surface_last, menu_surface)
menu.draw(surface) # This updates last surface
self.assertEqual(menu._surface_last, surface)
menu._surface = surface
self.assertEqual(menu.get_last_surface_offset(), (0, 0))
surface.fill((0, 0, 0))

# Now, test click event
menu._surface = menu_surface
self.assertFalse(btn_click[0])
menu.update(PygameEventUtils.middle_rect_click(r))
self.assertTrue(btn_click[0])

# Mainloop also updates last surface
menu.mainloop(disable_loop=True)
self.assertEqual(menu._surface_last, menu_surface)

0 comments on commit 1baac35

Please sign in to comment.