Skip to content

Commit 7a200ac

Browse files
committed
Added remember_selection to Menu constructor
1 parent fc47c9e commit 7a200ac

File tree

2 files changed

+54
-4
lines changed

2 files changed

+54
-4
lines changed

pygame_menu/menu.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class Menu(Base):
9696
:param onreset: Function executed when resetting the Menu. The function must be non-argument or single argument (Menu instance)
9797
:param overflow: Enables overflow on x/y axes. If ``False`` then scrollbars will not work and the maximum width/height of the scrollarea is the same as the Menu container. Style: (overflow_x, overflow_y). If ``False`` or ``True`` the value will be set on both axis
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
99+
:param remember_selection: Menu remembers selection when moving through submenus. By default it is false, so, when moving back the selected widget will be the first one of the Menu
99100
: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)
100101
: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
101102
: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()``
@@ -153,6 +154,7 @@ class Menu(Base):
153154
_position_default: Tuple2IntType
154155
_position_relative: bool
155156
_prev: Optional[List[Union['Menu', List['Menu']]]]
157+
_remember_selection: bool
156158
_runtime_errors: '_MenuRuntimeErrorConfig'
157159
_scrollarea: 'ScrollArea'
158160
_scrollarea_margin: List[int]
@@ -207,6 +209,7 @@ def __init__(
207209
onreset: Optional[Union[Callable[['Menu'], Any], CallableNoArgsType]] = None,
208210
overflow: Union[Vector2BoolType, bool] = (True, True),
209211
position: Union[Vector2NumberType, Tuple[NumberType, NumberType, bool]] = (50, 50, True),
212+
remember_selection: bool = False,
210213
rows: MenuRowsType = None,
211214
screen_dimension: Optional[Vector2IntType] = None,
212215
surface: Optional['pygame.Surface'] = None,
@@ -229,6 +232,7 @@ def __init__(
229232
assert isinstance(mouse_visible, bool)
230233
assert isinstance(mouse_visible_update, bool)
231234
assert isinstance(overflow, (VectorInstance, bool))
235+
assert isinstance(remember_selection, bool)
232236
assert isinstance(rows, (int, type(None), VectorInstance))
233237
assert isinstance(surface, (pygame.Surface, type(None)))
234238
assert isinstance(theme, Theme), \
@@ -369,6 +373,7 @@ def __init__(
369373
self._last_selected_type = '' # Last type selection, used for test purposes
370374
self._mainloop = False # Menu is in mainloop state
371375
self._onclose = None # Function or event called on Menu close
376+
self._remember_selection = remember_selection
372377
self._render_enabled = True
373378
self._sound = Sound(verbose=verbose)
374379
self._stats = _MenuStats()
@@ -3237,8 +3242,9 @@ def _open(self, menu: 'Menu') -> None:
32373242
self._top._current = menu._current
32383243
self._top._prev = [self._top._prev, current]
32393244

3240-
# Select the first widget
3241-
self._current._select(0, 1, SELECT_OPEN, False, update_mouse_position=False)
3245+
# Select the first widget (if not remember the selection)
3246+
if not self._current._remember_selection:
3247+
self._current._select(0, 1, SELECT_OPEN, False, update_mouse_position=False)
32423248

32433249
# Call event
32443250
if menu._onbeforeopen is not None:

test/test_menu.py

+46-2
Original file line numberDiff line numberDiff line change
@@ -2599,7 +2599,7 @@ def test_subsurface_offset(self) -> None:
25992599
"""
26002600
main_surface = surface
26012601
w, h = surface.get_size()
2602-
left_surf_w, left_surf_h = 300, h
2602+
left_surf_w, _ = 300, h
26032603
menu_w, menu_h = w - left_surf_w, h
26042604
# left_surface = main_surface.subsurface((0, 0, left_surf_w, left_surf_h))
26052605
menu_surface = main_surface.subsurface((300, 0, menu_w, menu_h))
@@ -2651,11 +2651,55 @@ def test_inheritance(self) -> None:
26512651
class SubMenu(pygame_menu.Menu):
26522652
def __init__(self) -> None:
26532653
super().__init__(title='Test', width=150, height=200, theme=pygame_menu.themes.THEME_DARK.copy())
2654-
help_menu = pygame_menu.Menu(title='Help', width=150, height=200)
2654+
help_menu = MenuUtils.generic_menu()
26552655
self.add.button(help_menu.get_title(), help_menu)
26562656
self.enable()
26572657

26582658
self.assertEqual(len(SubMenu().get_widgets()), 1)
26592659
main_menu = SubMenu()
26602660
test_menu = pygame_menu.Menu('test', 500, 400)
26612661
self.assertEqual(main_menu.add.menu_link(test_menu).get_menu(), main_menu)
2662+
2663+
def test_selection(self) -> None:
2664+
"""
2665+
Test menu widget selection. Based on #471.
2666+
"""
2667+
menu, sub, sub2 = MenuUtils.generic_menu(), MenuUtils.generic_menu(), MenuUtils.generic_menu()
2668+
2669+
# Add "sub" as a link within "menu"
2670+
sub_link = menu.add.menu_link(sub)
2671+
btn_back = sub.add.button('Back', pygame_menu.events.BACK)
2672+
2673+
# Add "sub2" as a link within "sub"
2674+
sub2_link = sub.add.menu_link(sub2)
2675+
btn_back_2 = sub2.add.button('Back', pygame_menu.events.BACK)
2676+
2677+
btn = menu.add.button('No-op Button')
2678+
btn2 = menu.add.button('Sub', sub_link.open)
2679+
btn3 = sub.add.button('Sub2', sub2_link.open)
2680+
2681+
menu.render()
2682+
self.assertEqual(menu.get_selected_widget(), btn)
2683+
2684+
# Now, we test selection preservation on return. By default, menu does not
2685+
# keep previous selection. So, selecting widget 2 (that opens the new menu
2686+
# sub2), and moving back, the index should be 0
2687+
menu.select_widget(btn2).get_selected_widget().apply()
2688+
self.assertEqual(menu.get_current(), sub)
2689+
self.assertEqual(menu.get_current().get_selected_widget(), btn_back)
2690+
# Now we select the btn3 and apply
2691+
menu.get_current().select_widget(btn3).get_selected_widget().apply()
2692+
btn_back_2.apply()
2693+
self.assertEqual(menu.get_current().get_selected_widget(), btn_back)
2694+
2695+
menu.get_current()._remember_selection = True
2696+
menu.get_current().select_widget(btn3).get_selected_widget().apply()
2697+
btn_back_2.apply()
2698+
self.assertEqual(menu.get_current().get_selected_widget(), btn3)
2699+
2700+
# Disable the feature, and now see what happens if trying to select first index if menu is empty
2701+
menu.get_current()._remember_selection = True
2702+
btn3.apply()
2703+
self.assertEqual(menu.get_current(), sub2)
2704+
sub.clear(reset=False)
2705+
self.assertEqual(menu.get_current(), sub2)

0 commit comments

Comments
 (0)