Skip to content

Commit 1baac35

Browse files
committed
Added subsurface compatibility
1 parent c262c6c commit 1baac35

File tree

7 files changed

+98
-17
lines changed

7 files changed

+98
-17
lines changed

README.rst

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
===========
2-
pygame-menu
3-
===========
1+
==============
2+
pygame-menu-ce
3+
==============
44

55
.. image:: docs/_static/pygame_menu_small.png
66
:align: center
@@ -77,7 +77,7 @@ https://pygame-menu.readthedocs.io
7777
Install Instructions
7878
--------------------
7979

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

8282
.. code-block:: bash
8383

pygame_menu/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
pygame-menu (for pygame-ce)
2+
pygame-menu
33
https://github.com/ppizarror/pygame-menu
44
55
PYGAME-MENU
@@ -119,11 +119,11 @@
119119

120120
]
121121
__copyright__ = 'Copyright 2017 Pablo Pizarro R. @ppizarror'
122-
__description__ = 'A menu for pygame-ce. Simple, and easy to use'
122+
__description__ = 'A menu for pygame. Simple, and easy to use'
123123
__email__ = '[email protected]'
124124
__keywords__ = 'pygame menu menus gui widget input button pygame-menu image sound ui'
125125
__license__ = 'MIT'
126-
__module_name__ = 'pygame-menu-ce'
126+
__module_name__ = 'pygame-menu'
127127
__url__ = 'https://pygame-menu.readthedocs.io'
128128
__url_bug_tracker__ = 'https://github.com/ppizarror/pygame-menu/issues'
129129
__url_documentation__ = 'https://pygame-menu.readthedocs.io'

pygame_menu/menu.py

+35-5
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class Menu(Base):
9898
: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
9999
: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)
100100
: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
101+
: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()``
101102
:param theme: Menu theme
102103
:param touchscreen: Enable/disable touch action inside the Menu. Only available on pygame 2
103104
:param touchscreen_motion_selection: Select widgets using touchscreen motion. If ``True`` menu draws a ``focus`` on the selected widget
@@ -158,6 +159,8 @@ class Menu(Base):
158159
_sound: 'Sound'
159160
_stats: '_MenuStats'
160161
_submenus: Dict['Menu', List['Widget']]
162+
_surface: Optional['pygame.Surface'] # The surface that contains the menu
163+
_surface_last: Optional['pygame.Surface'] # The last surface used to draw the menu
161164
_theme: 'Theme'
162165
_top: 'Menu'
163166
_touchscreen: bool
@@ -206,6 +209,7 @@ def __init__(
206209
position: Union[Vector2NumberType, Tuple[NumberType, NumberType, bool]] = (50, 50, True),
207210
rows: MenuRowsType = None,
208211
screen_dimension: Optional[Vector2IntType] = None,
212+
surface: Optional['pygame.Surface'] = None,
209213
theme: 'Theme' = THEME_DEFAULT.copy(),
210214
touchscreen: bool = False,
211215
touchscreen_motion_selection: bool = False,
@@ -227,6 +231,7 @@ def __init__(
227231
assert isinstance(mouse_visible_update, bool)
228232
assert isinstance(overflow, (VectorInstance, bool))
229233
assert isinstance(rows, (int, type(None), VectorInstance))
234+
assert isinstance(surface, (pygame.Surface, type(None)))
230235
assert isinstance(theme, Theme), \
231236
'theme bust be a pygame_menu.themes.Theme object instance'
232237
assert isinstance(touchscreen, bool)
@@ -370,6 +375,8 @@ def __init__(
370375
self._sound = Sound()
371376
self._stats = _MenuStats()
372377
self._submenus = {}
378+
self._surface = surface
379+
self._surface_last = None
373380
self._theme = theme
374381

375382
# Set callbacks
@@ -2029,7 +2036,7 @@ def enable_render(self) -> 'Menu':
20292036
self._render()
20302037
return self
20312038

2032-
def draw(self, surface: 'pygame.Surface', clear_surface: bool = False) -> 'Menu':
2039+
def draw(self, surface: Optional['pygame.Surface'] = None, clear_surface: bool = False) -> 'Menu':
20332040
"""
20342041
Draw the **current** Menu into the given surface.
20352042
@@ -2038,13 +2045,18 @@ def draw(self, surface: 'pygame.Surface', clear_surface: bool = False) -> 'Menu'
20382045
This method should not be used along :py:meth:`pygame_menu.menu.Menu.get_current`,
20392046
for example, ``menu.get_current().draw(...)``
20402047
2041-
:param surface: Pygame surface to draw the Menu
2048+
:param surface: Pygame surface to draw the Menu. If None, the Menu will use the provided ``surface`` from the constructor
20422049
:param clear_surface: Clear surface using theme ``surface_clear_color``
20432050
:return: Self reference **(current)**
20442051
"""
2052+
if surface is None:
2053+
surface = self._surface
20452054
assert isinstance(surface, pygame.Surface)
20462055
assert isinstance(clear_surface, bool)
20472056

2057+
# Update last surface
2058+
self._surface_last = surface
2059+
20482060
if not self.is_enabled():
20492061
self._current._runtime_errors.throw(self._current._runtime_errors.draw, 'menu is not enabled')
20502062
return self._current
@@ -2451,6 +2463,21 @@ def _right(self, apply_sound: bool = False) -> bool:
24512463
return self._current._move_selected_left_right(1)
24522464
return False
24532465

2466+
def get_last_surface_offset(self) -> Tuple2IntType:
2467+
"""
2468+
Return the last menu surface offset.
2469+
2470+
.. warning::
2471+
2472+
This method should not be used along :py:meth:`pygame_menu.menu.Menu.get_current`,
2473+
for example, ``menu.get_current().update(...)``.
2474+
2475+
: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
2476+
"""
2477+
if self._surface is not None:
2478+
return self._surface.get_offset()
2479+
return self._surface_last.get_offset() if self._surface_last is not None else (0, 0)
2480+
24542481
def get_last_update_mode(self) -> List[str]:
24552482
"""
24562483
Return the update mode.
@@ -2906,7 +2933,7 @@ def collide(self, event: EventType) -> bool:
29062933

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

2986+
if surface is None:
2987+
surface = self._surface
2988+
29592989
assert isinstance(clear_surface, bool)
29602990
assert isinstance(disable_loop, bool)
29612991
assert isinstance(fps_limit, NumberInstance)

pygame_menu/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ def __str__(self) -> str:
3232
patch = property(lambda self: self[2])
3333

3434

35-
vernum = Version(4, 4, 1)
35+
vernum = Version(4, 4, 2)
3636
ver = str(vernum)
3737
rev = ''

pygame_menu/widgets/core/widget.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ def get_rect(
15371537
:param inflate: Inflate rect on x-axis and y-axis (x, y) in px
15381538
:param apply_padding: Apply widget padding
15391539
:param use_transformed_padding: Use scaled padding if the widget is scaled
1540-
:param to_real_position: Transform the widget rect to real coordinates (if the Widget change the position if scrollbars move offsets). Used by events
1540+
: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
15411541
: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
15421542
:param render: Force widget rendering
15431543
:param real_position_visible: Return only the visible width/height if ``to_real_position=True``
@@ -1566,6 +1566,9 @@ def get_rect(
15661566
'real and absolute positions cannot be True at the same time'
15671567
if to_real_position:
15681568
rect = self._scrollarea.to_real_position(rect, visible=real_position_visible)
1569+
soff = (0, 0) if self._menu is None else self._menu.get_last_surface_offset()
1570+
rect.x += soff[0]
1571+
rect.y += soff[1]
15691572
elif to_absolute_position:
15701573
rect = self._scrollarea.to_absolute_position(rect)
15711574

setup.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
pygame-menu
2+
pygame-menu-ce
33
https://github.com/ppizarror/pygame-menu
44
55
SETUP DISTRIBUTION
@@ -44,11 +44,11 @@
4444

4545
# Setup library
4646
setup(
47-
name=pygame_menu.__module_name__,
47+
name=pygame_menu.__module_name__ + '-ce',
4848
version=pygame_menu.__version__,
4949
author=pygame_menu.__author__,
5050
author_email=pygame_menu.__email__,
51-
description=pygame_menu.__description__,
51+
description=pygame_menu.__description__.replace('pygame', 'pygame-ce'),
5252
long_description=long_description,
5353
url=pygame_menu.__url__,
5454
project_urls={

test/test_menu.py

+48
Original file line numberDiff line numberDiff line change
@@ -2586,3 +2586,51 @@ def test_menu_widget_selected_events(self) -> None:
25862586
name.receive_menu_update_events = True
25872587
menu.update(PygameEventUtils.key(pygame.K_s, keydown=True, char='s'))
25882588
self.assertEqual(name.get_value(), 'as')
2589+
2590+
def test_subsurface_offset(self) -> None:
2591+
"""
2592+
Test subsurface widget offset.
2593+
"""
2594+
main_surface = surface
2595+
w, h = surface.get_size()
2596+
left_surf_w, left_surf_h = 300, h
2597+
menu_w, menu_h = w - left_surf_w, h
2598+
# left_surface = main_surface.subsurface((0, 0, left_surf_w, left_surf_h))
2599+
menu_surface = main_surface.subsurface((300, 0, menu_w, menu_h))
2600+
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)
2601+
btn_click = [False]
2602+
2603+
def btn() -> None:
2604+
"""
2605+
Method executed by button.
2606+
"""
2607+
btn_click[0] = True
2608+
2609+
b1 = menu.add.button('Button', btn)
2610+
menu._surface = None
2611+
self.assertEqual(menu.get_last_surface_offset(), (0, 0))
2612+
self.assertEqual(b1.get_rect(to_real_position=True).x, 94)
2613+
self.assertIsNone(menu._surface_last)
2614+
menu._surface = menu_surface
2615+
self.assertEqual(menu._surface, menu_surface)
2616+
self.assertEqual(menu.get_last_surface_offset(), (300, 0))
2617+
r = b1.get_rect(to_real_position=True)
2618+
self.assertEqual(r.x, 394)
2619+
self.assertIsNone(menu._surface_last)
2620+
menu.draw()
2621+
self.assertEqual(menu._surface_last, menu_surface)
2622+
menu.draw(surface) # This updates last surface
2623+
self.assertEqual(menu._surface_last, surface)
2624+
menu._surface = surface
2625+
self.assertEqual(menu.get_last_surface_offset(), (0, 0))
2626+
surface.fill((0, 0, 0))
2627+
2628+
# Now, test click event
2629+
menu._surface = menu_surface
2630+
self.assertFalse(btn_click[0])
2631+
menu.update(PygameEventUtils.middle_rect_click(r))
2632+
self.assertTrue(btn_click[0])
2633+
2634+
# Mainloop also updates last surface
2635+
menu.mainloop(disable_loop=True)
2636+
self.assertEqual(menu._surface_last, menu_surface)

0 commit comments

Comments
 (0)