ppizarror / pygame-menu

A menu for pygame. Simple, and easy to use
https://pygame-menu.readthedocs.io/
Other
555 stars 141 forks source link

Sub-menus not working if you create a menu class inheriting from pygame_menu.Menu #482

Closed nowottnya closed 2 weeks ago

nowottnya commented 5 months ago

Environment information Describe your environment information, such as:

Describe the bug If I create a class Menu which inherit from pygame_menu.Menu and use the menu in my code the Menu works fine as long as . As soon as add a button with a sub-menu I get an stack trace and my program is not working anymore:

Traceback (most recent call last): File "C:\pygame-menu\main.py", line 32, in main_menu = Menu() File "C:\src\pygame-menu\main.py", line 21, in init self.add.button(help_menu.get_title(), help_menu) File "C:\src\pygame-menu\venv\lib\site-packages\pygame_menu\widgets\widget\button.py", line 486, in button raise ValueError('action must be a Menu, a MenuAction (event), a ' ValueError: action must be a Menu, a MenuAction (event), a function (callable), or None

To Reproduce Execute the following code:

import pygame
import pygame_menu
from pygame import QUIT

class Menu(pygame_menu.Menu):

    def __init__(self):
        super().__init__(title="Test", width=150, height=200, theme=pygame_menu.themes.THEME_DARK.copy())

        help_menu = pygame_menu.Menu(title="Help", width=150,
                                     height=200, theme=pygame_menu.themes.THEME_DARK.copy(),
                                     center_content=True)

        help_menu.add.label("Test label", wordwrap=True,
                            font_size=15, padding=5,
                            align=pygame_menu.locals.ALIGN_CENTER)
        help_menu.add.button("Back", pygame_menu.events.BACK)

        self.add.button("Play", pygame_menu.events.EXIT)
        # if you comment out this line the program works
        self.add.button(help_menu.get_title(), help_menu)
        self.add.button("Quit", pygame_menu.events.EXIT)

        self.enable()

if __name__ == "__main__":

    pygame.init()
    screen = pygame.display.set_mode((500, 400))
    clock = pygame.time.Clock()
    main_menu = Menu()

    while True:
        screen.fill((0, 0, 0))

        events = pygame.event.get()
        for event in events:
            if event.type == QUIT:
                exit()

        main_menu.update(events)
        main_menu.draw(screen)

        pygame.display.update()

        clock.tick(60)

Expected behavior You should be able to inherit from pygame-menu and all provided methods should work same the way as when you create the menu directly like this:

import pygame
import pygame_menu
from pygame import QUIT

if __name__ == "__main__":

    pygame.init()
    screen = pygame.display.set_mode((500, 400))
    clock = pygame.time.Clock()

    main_menu = pygame_menu.Menu(title="Test", width=150, height=200, theme=pygame_menu.themes.THEME_DARK.copy())

    help_menu = pygame_menu.Menu(title="Help", width=150,
                                 height=200, theme=pygame_menu.themes.THEME_DARK.copy(),
                                 center_content=True)

    help_menu.add.label("Test label", wordwrap=True,
                        font_size=15, padding=5,
                        align=pygame_menu.locals.ALIGN_CENTER)
    help_menu.add.button("Back", pygame_menu.events.BACK)

    main_menu.add.button("Play", pygame_menu.events.EXIT)
    main_menu.add.button(help_menu.get_title(), help_menu)
    main_menu.add.button("Quit", pygame_menu.events.EXIT)

    main_menu.enable()

    while True:
        screen.fill((0, 0, 0))

        events = pygame.event.get()
        for event in events:
            if event.type == QUIT:
                exit()

        main_menu.update(events)
        main_menu.draw(screen)

        pygame.display.update()

        clock.tick(60)

Additional context Maybe I missed something and have to do additional steps, but usually I don't expect methods like the button method to behave different when you inherit the pygame_menu.Menu in your class and instantiate my class instead of direct instantiation from the pygame_menu.Menu.

neilsimp1 commented 1 month ago

Here is a possible workaround:

help_menu = pygame_menu.Menu(title="Help", width=150,
                                 height=200, theme=pygame_menu.themes.THEME_DARK.copy(),
                                 center_content=True)

# Add this:
help_menu.__class__ = Menu

...

main_menu.add.button(help_menu.get_title(), help_menu)

For me at least, this allows it to get past the check here: https://github.com/ppizarror/pygame-menu/blob/9806f59ac5f28a072f468d49ac1aec500c1d6ce5/pygame_menu/widgets/widget/button.py#L448

BrenBarn commented 1 month ago

The problem seems to be that the library thinks that any sub-menu added to a menu should be an instance of a subclass of the main menu. I don't think this makes conceptual sense. Subclassing is an "is-a" relationship whereas submenu-ing is a "contains-a" relationship. Just because, say, a ViewMenu has a DisplayPreferencesMenu doesn't mean that the display preferences menu is a view menu; it's just contained in the ViewMenu.

ppizarror commented 4 weeks ago

Hi. I just updated the library to v4.4.4. The issue has now been fixed (+ regression test)

kefabean commented 2 weeks ago
OS: MacOS 14.5
python version: v3.12.6
pygame version: v2.6.1
pygame-menu version: v4.4.7

I've upgraded to the latest version of the library 4.4.7 and I still get a similar error if I attempt to manipulate the object created in the above sample, for example by using main_menu.add.menu_link:


import pygame
import pygame_menu
from pygame import QUIT

class Menu(pygame_menu.Menu):

    def __init__(self):
        super().__init__(title="Test", width=150, height=200, theme=pygame_menu.themes.THEME_DARK.copy())

        help_menu = pygame_menu.Menu(title="Help", width=150,
                                     height=200, theme=pygame_menu.themes.THEME_DARK.copy(),
                                     center_content=True)

        help_menu.add.label("Test label", wordwrap=True,
                            font_size=15, padding=5,
                            align=pygame_menu.locals.ALIGN_CENTER)
        help_menu.add.button("Back", pygame_menu.events.BACK)

        self.add.button("Play", pygame_menu.events.EXIT)
        # if you comment out this line the program works
        self.add.button(help_menu.get_title(), help_menu)
        self.add.button("Quit", pygame_menu.events.EXIT)

        self.enable()

if __name__ == "__main__":

    pygame.init()
    screen = pygame.display.set_mode((500, 400))
    clock = pygame.time.Clock()
    main_menu = Menu()
    test_menu = pygame_menu.Menu('test', 500, 400)
    main_menu.add.menu_link(test_menu)

    while True:
        screen.fill((0, 0, 0))

        events = pygame.event.get()
        for event in events:
            if event.type == QUIT:
                exit()

        main_menu.update(events)
        main_menu.draw(screen)

        pygame.display.update()

        clock.tick(60)

I get the error: ValueError: menu object is not a pygame_menu.Menu class

ppizarror commented 2 weeks ago

Hi @kefabean, I fixed this issue in the new version 4.4.8. If you encounter any new error, please create a new GitHub issue. Thanks,

neilsimp1 commented 2 weeks ago

Confirming that your changes in 4.4.8 also appear to still work for me and my use case. Thank you!