-
-
Notifications
You must be signed in to change notification settings - Fork 147
Description
Environment information
- OS: Mac OS 15.2, Windows 11
- python version: v3.11.11 and 3.12.4
- pygame version: v2.6.1
- pygame-menu version: v4.5.1
Describe the bug
Reducing the selection_box_height for more than one dropselect_multi during runtime (for example when the game window was resized) fails, since the second dropselect still has the old size (which can be larger than the new menu size) - but is forced to render while trying to update the first one.
My understanding is that calling _make_selection_drop() causes a full rerender of the full menu - and therefore the second droplist, which hasn't been adjusted yet.
I have attempted to set all selection_box_height values for all dropselects first, before then calling _make_selection_drop(), but this has also failed.
It's possible that I'm just not aware of another API function which would help with this, if so, please let me know.
To Reproduce
This is maybe not as minimal as it could be, but shows:
- that resizing works fine when there is only one dropselect
- resizing fails when there are two dropselects
- resizing also fails when the second dropselect is hidden
- completely removing the second dropselect (and any reference to it) returns to the good behaviour.
import pygame
import pygame_menu
import os
# --------------------------------------------------------------------------------
# create a resizable pygame window
# --------------------------------------------------------------------------------
# ensure window is at 0,0 for easier testing
os.environ["SDL_VIDEO_WINDOW_POS"] = "0,0" # has to be called before pygame.init()
pygame.init()
w, h = (800, 600)
gamewindow = pygame.display.set_mode((w, h), pygame.RESIZABLE)
# --------------------------------------------------------------------------------
# setup the menu
# --------------------------------------------------------------------------------
my_menu_theme = pygame_menu.themes.THEME_DARK.copy()
my_menu_theme.background_color = pygame_menu.BaseImage(
image_path=pygame_menu.baseimage.IMAGE_EXAMPLE_GRAY_LINES,
drawing_mode=pygame_menu.baseimage.IMAGE_MODE_REPEAT_XY,
)
ftsize = 24
my_menu_theme.widget_font_size = ftsize
def menusize():
"""return a size that is 95% of the current window size"""
w, h = gamewindow.get_size()
return (int(w * 0.95), int(h * 0.95))
menu = pygame_menu.Menu(
width=menusize()[0],
height=menusize()[1],
theme=my_menu_theme,
title="Resizing test, ok with 1 dropselect",
)
# function to estimate the max number of items we can show without scrolling
def estimate_max_items():
return int((menu.get_height() - 55) / (ftsize * 1.5)) - 2
max_items = estimate_max_items()
# add some debugging information
l1 = menu.add.label(f"Current window size: {w}x{h}")
l2 = menu.add.label(f"Current menu size: {menu.get_width()}x{menu.get_height()}")
l3 = menu.add.label(f"Current menu title height: {menu.get_menubar().get_height()}")
l4 = menu.add.label(f"Current selection box height: {max_items}")
menu.add.vertical_margin(20)
# --------------------------------------------------------------------------------
# a dropselect with many entries
# --------------------------------------------------------------------------------
droplist1 = menu.add.dropselect_multiple(
title="droplist1",
dropselect_multiple_id="droplist1",
placeholder_add_to_selection_box=False,
items=[(f"{a:02}", a) for a in range(1, 100)],
default=[2, 5, 7],
# dropselect_multiple_id="droplist1",
open_middle=True, # if false, the checkboxes are drawn incorrectly?
selection_box_height=max_items,
selection_infinite=True,
)
droplist1.reset_value() # needed to update the highlights before entering?
# --------------------------------------------------------------------------------
# Prepare to add a second dropselect later
# --------------------------------------------------------------------------------
droplist2 = None
hidedroplist2 = False
def add_droplist2():
global droplist2
menu.set_title("Resizing test, crash with 2 dropselect")
max_items = estimate_max_items()
menu.remove_widget(lastbutton)
droplist2 = menu.add.dropselect_multiple(
title="droplist2",
dropselect_multiple_id="droplist2",
placeholder_add_to_selection_box=False,
items=[(f"{a:02}", a) for a in range(1, 100)],
default=[2, 5, 7],
# dropselect_multiple_id="droplist2",
open_middle=True, # if false, the checkboxes are drawn incorrectly?
selection_box_height=max_items,
selection_infinite=True,
)
# droplist2.reset_value() # needed to update the highlights before entering?
menu.add.vertical_margin(20)
def toggle_droplist2(data):
global hidedroplist2
hidedroplist2 = data
if data:
droplist2.hide()
else:
droplist2.show()
def remove_droplist2():
global droplist2
# remove droplist2 (and the hide/remove buttons)
for w in [droplist2, hide_button, remove_button]:
menu.remove_widget(w)
droplist2 = None # also remove the reference to the widget
menu.force_surface_update()
menu.render()
hide_button = menu.add.toggle_switch(
"Hide droplist2",
default=False,
onchange=toggle_droplist2,
)
remove_button = menu.add.button("Remove droplist2", remove_droplist2)
menu.add.vertical_margin(20)
menu.add.button("Quit", pygame_menu.events.EXIT)
menu.add.vertical_margin(60)
lastbutton = menu.add.button("Add droplist2", add_droplist2)
# --------------------------------------------------------------------------------
# Function to resize and readjust the max number of items in the dropselect
# (gets called when the window is resized)
# --------------------------------------------------------------------------------
def refresh_menu():
w, h = gamewindow.get_size()
menu.resize(*menusize())
# estimate the max number of items we can show without scrolling
max_items = estimate_max_items()
widgetnames = ["droplist1"]
if droplist2 and not hidedroplist2:
widgetnames.append("droplist2")
# go through the dropselects and update the selection box height
for widgetname in widgetnames:
ds = menu.get_widget(widgetname)
print(f"**** Updating widget {widgetname} ****")
print(
f"(BEFORE) drop_frame_total_height = {ds._drop_frame.get_attribute('height')}"
)
# update the selection box height
ds._selection_box_height = max_items
# force the dropselect to recreate the selection box
ds._make_selection_drop()
print(
f"(AFTER) drop_frame_total_height = {ds._drop_frame.get_attribute('height')}"
)
# update debug info labels
l1.set_title(f"Current window size: {w}x{h}")
l2.set_title(f"Current menu size: {menu.get_width()}x{menu.get_height()}")
l3.set_title(f"Current menu title height: {menu.get_menubar().get_height()}")
l4.set_title(f"Current selection box height: {max_items}")
menu.render()
# --------------------------------------------------------------------------------
if __name__ == "__main__":
while menu.is_enabled():
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT or (
event.type == pygame.KEYDOWN and (event.key == pygame.K_q)
):
menu.disable()
elif event.type == pygame.VIDEORESIZE:
# make sure the previously drawn menu (of different size) is cleared
gamewindow.fill((0, 0, 0))
# now resize the menu and adjust the dropselects
refresh_menu()
if menu.is_enabled():
menu.draw(gamewindow)
menu.update(events)
pygame.display.update()
# --------------------------------------------------------------------------------Expected behavior
It should be possible to resize several dropselect selection boxes simultaneously, before the menu gets rendered.
(Additionally, it would be nice if there was an easier way to know beforehand what the largest possible selection_box_height can be for a given menu size. Maybe even allowing this to be done automatically, maybe by setting selection_box_height=0?)