Skip to content

Reducing the selection_box_height for more than one dropselect during runtime fails #491

@gabindu

Description

@gabindu

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?)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions