ppizarror / pygame-menu

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

Controller has no influence on menu actions #457

Closed MichaelWoodc closed 1 year ago

MichaelWoodc commented 1 year ago

Describe the bug Importing the controller does nothing for the menu when using my nes usb controller. When I try and assign keys, it doesn't do anything

To Reproduce simply create any menu and import the controller, use USB controller, observe nothing happens

Expected behavior I want to be able to move the selection on the menu using a USB controller

Additional context I have no problem making code that uses the controller for other things, but when I try and import it into the code I'm using for the menu, it doesn't work. The menu continues to work with the keyboard. But not the controller. See my code for getting the events from the controller here: (everything that I want to work works, just cannot get it to work with the menu in pygame_menu. I will also include the code with the working menu, but no controller functionality, under this:

import os
import sys
import pygame
from pygame.locals import *

player1Score = 0
player2Score = 0
player = 0

# I see no reason to disable screensaver for this tool.
os.environ["SDL_VIDEO_ALLOW_SCREENSAVER"] = "1"
# Maybe people want to keep watching the joystick feedback even when this
# window doesn't have focus. Possibly by capturing this window into OBS.
os.environ["SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"
# A tiny performance/latency hit isn't a problem here. Instead, it's more
# important to keep the desktop compositing effects running fine. Disabling
# compositing is known to cause issues on KDE/KWin/Plasma/X11 on Linux.
os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"] = "0"

def buttonAction(player,button):  # format def 'function name'(required, quantity=1) quantity is not required because we have passed a default of 1
    print('this is in the buttonAction function' + str(player))
    print('The pushed button was' + str(button))

class joystick_handler(object):
    def __init__(self, id):
        self.id = id
        self.joy = pygame.joystick.Joystick(id)
        self.name = self.joy.get_name()
        self.joy.init()
        self.numaxes    = self.joy.get_numaxes()
        self.numbuttons = self.joy.get_numbuttons()

        self.axis = []
        for i in range(self.numaxes):
            self.axis.append(self.joy.get_axis(i))

        self.button = []
        for i in range(self.numbuttons):
            self.button.append(self.joy.get_button(i))

class input_test(object):
    class program:
        "Program metadata"
        name    = "Rat Basketball"
        version = "0.2"
        author  = "Michael Woodcock & Dr. Andrew Bulla"
        nameversion = name + " " + version

    def init(self):
        pygame.init()
        pygame.event.set_blocked((MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN))
        self.joycount = pygame.joystick.get_count()
        if self.joycount == 0:
            print("This program only works with at least one joystick plugged in. No joysticks were detected.")
            self.quit(1)
        self.joy = []
        for i in range(self.joycount):
            self.joy.append(joystick_handler(i))

    def run(self):
        self.screen = pygame.display.set_mode((1000,500), RESIZABLE)
        pygame.display.set_caption(self.program.nameversion)

        while True:

            pygame.display.flip()
            # self.clock.tick(30)
            for event in [pygame.event.wait(), ] + pygame.event.get():
                # QUIT             none
                # ACTIVEEVENT      gain, state
                # KEYDOWN          unicode, key, mod
                # KEYUP            key, mod
                # MOUSEMOTION      pos, rel, buttons
                # MOUSEBUTTONUP    pos, button
                # MOUSEBUTTONDOWN  pos, button
                # JOYAXISMOTION    joy, axis, value
                # JOYBALLMOTION    joy, ball, rel
                # JOYHATMOTION     joy, hat, value
                # JOYBUTTONUP      joy, button
                # JOYBUTTONDOWN    joy, button
                # VIDEORESIZE      size, w, h
                # VIDEOEXPOSE      none
                # USEREVENT        code
                if event.type == QUIT:
                    print('event.type == QUIT')
                    self.quit()
                elif event.type == KEYDOWN and event.key in [K_ESCAPE, K_q]:
                    print('KEYDOWN and event.key in [K_ESCAPE, K_q]:')
                    self.quit()
                elif event.type == VIDEORESIZE:
                    print('Video Resized')
                    self.screen = pygame.display.set_mode(event.size, RESIZABLE)

                elif event.type == JOYAXISMOTION:                    
                    self.joy[event.joy].axis[event.axis] = event.value
                    # print(event.dict['joy'])
                    # print('JoyAxisMotion')
                    # print(event.dict)
                    player = event.joy + 1
                    # print (player)
                    if event.axis == 4:
                        # print (player)
                        if round(event.value) == 0:
                            # print('')
                            # print('') #print('zero, do nothing')
                            pass
                        elif round(event.value) == 1:
                            # print('')
                            print (player)
                            print('Down button pressed')
                        elif round(event.value) == -1:
                            print('')
                            print (player)
                            print('Up button pressed')
                        # this doesn't work: print(pygame.event.Event.pygame.JOYAXISMOTION)
                    elif event.axis == 0:
                        if round(event.value) == 0:
                            # print('')
                            # print('') #print('zero, do nothing')
                            pass
                        elif round(event.value) == 1:
                            print('')
                            print (player)
                            print('Right Button Pressed')
                        elif round(event.value) == -1:
                            print('')
                            print (player)
                            print('Left Button Pressed')
                elif event.type == JOYBUTTONUP:
                    self.joy[event.joy].button[event.button] = 0
                    print(event.dict['joy'])
                    player = (event.joy) + 1
                    button = event.button
                    buttonAction(player,button)
                    print('Player' + str(player) +'Button' + str(button))
                    print('joybuttonup')
                    print(event.joy)
                    print(event.dict)
                elif event.type == JOYBUTTONDOWN:
                    player = event.joy + 1
                    self.joy[event.joy].button[event.button] = 1
                    event.dict['joy']
                    # global player
                    # global buttonDown
                    buttonDown = 'test'
                    print ('Player' + str(player))
                    print('JoyButtonDown')
                    # print(program.joy[0].button[1])
                    # print(program.joy[0].button[2])
                    # print(program.joy[0].button[9])
                    # print(program.joy[0].button[8])
                    # print(program.joy[1].button[9])
                    print('Joystick Number:')
                    print(event.dict['joy'])

    def quit(self, status=0):
        pygame.quit()
        sys.exit(status)

if __name__ == "__main__":
    program = input_test()
    program.init()
    program.run()  # This function should never return`

Here is the code that also contains the menu, please do not hit the play button as it will jam up the menu at this time. Only hit the other ones

"""
pygame-menu
https://github.com/ppizarror/pygame-menu

CONTROLS
Default controls of Menu object and key definition.
"""

__all__ = [

    # Joy pad
    'JOY_AXIS_X',
    'JOY_AXIS_Y',
    'JOY_BUTTON_BACK',
    'JOY_BUTTON_SELECT',
    'JOY_DEADZONE',
    'JOY_DELAY',
    'JOY_DOWN',
    'JOY_LEFT',
    'JOY_REPEAT',
    'JOY_RIGHT',
    'JOY_UP',

    # Keyboard events
    'KEY_APPLY',
    'KEY_BACK',
    'KEY_CLOSE_MENU',
    'KEY_LEFT',
    'KEY_MOVE_DOWN',
    'KEY_MOVE_UP',
    'KEY_RIGHT',

    # Controller object
    'Controller'

]

# Imports
# noinspection PyUnresolvedReferences
import pygame
import pygame_menu
from pygame_menu.controls import Controller
import pygame_menu.events 

import pygame.locals as _locals
from pygame.event import Event as EventType
from typing import Union
from pygame_menu.examples import create_example_window
from typing import Tuple, Any
import os
from random import randrange
import time

# was from pygame_menu.controls import Controller
custom_controller = Controller()

def btn_apply(event, menu_object):
    applied = event.key in (pygame.K_a, pygame.K_b, pygame.K_c)
    if applied:
        random_color = (randrange(0, 255), randrange(0, 255), randrange(0, 255))
        menu_object.get_scrollarea().update_area_color(random_color)
    return applied

custom_controller.apply = btn_apply

## Let's set some settings to make it run smooth
# let's leave the screensaver enabled, besides, a change in score would quit screensaver, right?
os.environ["SDL_VIDEO_ALLOW_SCREENSAVER"] = "1"
# Maybe people want to keep watching the joystick feedback even when this
# window doesn't have focus. Possibly by capturing this window into OBS.
os.environ["SDL_JOYSTICK_ALLOW_BACKGROUND_EVENTS"] = "1"
# A tiny performance/latency hit isn't a problem here. Instead, it's more
# important to keep the desktop compositing effects running fine. Disabling
# compositing is known to cause issues on KDE/KWin/Plasma/X11 on Linux.
os.environ["SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR"] = "0"

pygame.joystick.init()
print(pygame.joystick.get_init())

WidgetType = Union['pygame_menu.Menu', 'pygame_menu.widgets.Widget']

surface = create_example_window('Example - Simple', (600, 400))

def set_difficulty(selected: Tuple, value: Any) -> None:
    """
    Set the difficulty of the game.
    """
    print(f'Set difficulty to {selected[0]} ({value})')
    # print(pygame_menu._last_selected_type)

def start_the_game() -> None:
    while True:
        """
        Function that starts a game. This is raised by the menu button,
        here menu can be disabled, etc.
        """
        print('while True')
        # print(pygame_menu._last_selected_type)
        if menu._handle_joy_event() != None:
            print('something happened with the joystick')
        for event in [pygame.event.wait(), ] + pygame.event.get():
            # QUIT             none
            # ACTIVEEVENT      gain, state
            # KEYDOWN          unicode, key, mod
            # KEYUP            key, mod
            # MOUSEMOTION      pos, rel, buttons
            # MOUSEBUTTONUP    pos, button
            # MOUSEBUTTONDOWN  pos, button
            # JOYAXISMOTION    joy, axis, value
            # JOYBALLMOTION    joy, ball, rel
            # JOYHATMOTION     joy, hat, value
            # JOYBUTTONUP      joy, button
            # JOYBUTTONDOWN    joy, button
            # VIDEORESIZE      size, w, h
            # VIDEOEXPOSE      none
            # USEREVENT        code
            if pygame.event.Event.type == pygame.QUIT:
                print('pygame quit')
            elif pygame.event.Event.type == pygame.KEYDOWN and event.key in [pygame.K_ESCAPE, pygame.K_q]:
                print('keydown esc etc')

            elif pygame.event.Event.type == pygame.VIDEORESIZE:
                self.screen = pygame.display.set_mode(event.size, RESIZABLE)
                print('I resized')
            elif pygame.event.Event.type == pygame.JOYAXISMOTION:
                self.joy[event.joy].axis[event.axis] = event.value
                print(event.dict['joy'])
                print('JoyAxisMotion')
                print(event.dict)
                # this doesn't work: print(pygame.event.Event.pygame.JOYAXISMOTION)
            elif event.type == pygame.JOYBUTTONUP:
                self.joy[event.joy].button[event.button] = 0
                print(event.dict['joy'])
                print('joybuttonup')
                print(event.joy)
                print(event.dict)
            elif event.type == pygame.JOYBUTTONDOWN:
                self.joy[event.joy].button[event.button] = 1
                print('JoyButtonDown')
                # print(program.joy[0].button[1])
                # print(program.joy[0].button[2])
                # print(program.joy[0].button[9])
                # print(program.joy[0].button[8])
                # print(program.joy[1].button[9])
                print('Joystick Number:')
                print(event.dict['joy'])

menu = pygame_menu.Menu(
    height=300,
    theme=pygame_menu.themes.THEME_BLUE,
    title='Welcome',
    width=400
)

user_name = menu.add.text_input('Name: ', default='John Doe', maxchar=10)
menu.add.selector('Difficulty: ', [('Hard', 1), ('Easy', 2)], onchange=set_difficulty)
menu.add.button('Play', start_the_game)
menu.add.button('Quit', pygame_menu.events.EXIT)
button = menu.add.button('My button', lambda: print('Clicked!'))
button.set_controller(custom_controller) # Pass new controller to object

if __name__ == '__main__':
    menu.mainloop(surface)

# Joy pad
JOY_AXIS_X = 0 # was JOY_AXIS_X = 0
JOY_AXIS_Y = 1 #pygame.JOYAXISMOTION
JOY_BUTTON_BACK = 1
JOY_BUTTON_SELECT = 0
JOY_DEADZONE = 0.5
JOY_DELAY = 300  # ms
JOY_DOWN = (0, -1)
JOY_LEFT = (-1, 0)
JOY_REPEAT = 100  # ms
JOY_RIGHT = (1, 0)
JOY_UP = (0, 1)

# Keyboard events see https://www.pygame.org/docs/ref/key.html for list of keys
KEY_APPLY = _locals.K_RETURN
KEY_BACK = _locals.K_BACKSPACE
KEY_CLOSE_MENU = _locals.K_ESCAPE
KEY_LEFT = _locals.K_LEFT
KEY_MOVE_DOWN = _locals.K_UP
KEY_MOVE_UP = _locals.K_DOWN  # Consider keys are "inverted"
KEY_RIGHT = _locals.K_RIGHT
KEY_TAB = _locals.K_TAB

print(pygame_menu.events)
print(pygame_menu._last_selected_type)
# pygame_menu.events
# joy_axis_y_down()
running = True
for event in [pygame.event.wait(), ] + pygame.event.get():
    print('this is an event')
    print('This is the event.joy'+ event.joy)
    print(event)

    print('This is the event.joy'+ event.dict)
while running: 
# noinspection PyUnusedLocal
    for event in [pygame.event.wait(), ] + pygame.event.get():
        print('this is an event')
        print('This is the event.joy'+ event.joy)
        print(event)

        print('This is the event.joy'+ event.dict)

class Controller(object):
    """
    Controller class. Accepts any object and provides functions to handle each
    event.
    """
    joy_delay: int
    joy_repeat: int

    def __init__(self) -> None:
        self.joy_delay = JOY_DELAY
        self.joy_repeat = JOY_REPEAT

    @staticmethod
    def apply(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts apply key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_APPLY

    @staticmethod
    def back(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts back key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_BACK

    @staticmethod
    def close_menu(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts close menu key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_CLOSE_MENU

    @staticmethod
    def delete(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts delete key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == _locals.K_DELETE

    @staticmethod
    def end(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts end key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == _locals.K_END

    @staticmethod
    def escape(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts escape key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        print('escape was pushed')
        return event.key == _locals.K_ESCAPE

    @staticmethod
    def home(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts home key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == _locals.K_HOME

    @staticmethod
    def joy_axis_x_left(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement on x-axis (left direction). Requires ``pygame.JOYAXISMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.axis == JOY_AXIS_X and event.value < -JOY_DEADZONE

    @staticmethod
    def joy_axis_x_right(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement on x-axis (right direction). Requires ``pygame.JOYAXISMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.axis == JOY_AXIS_X and event.value > JOY_DEADZONE

    @staticmethod
    def joy_axis_y_down(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement on y-axis (down direction). Requires ``pygame.JOYAXISMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.axis == JOY_AXIS_Y and event.value > JOY_DEADZONE

    @staticmethod
    def joy_axis_y_up(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement on y-axis (up direction). Requires ``pygame.JOYAXISMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.axis == JOY_AXIS_Y and event.value < -JOY_DEADZONE

    @staticmethod
    def joy_back(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy back button. Requires ``pygame.JOYBUTTONDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.button == JOY_BUTTON_BACK

    @staticmethod
    def joy_down(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement to down direction. Requires ``pygame.JOYHATMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.value == JOY_DOWN

    @staticmethod
    def joy_left(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement to left direction. Requires ``pygame.JOYHATMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.value == JOY_LEFT

    @staticmethod
    def joy_right(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement to right direction. Requires ``pygame.JOYHATMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.value == JOY_RIGHT

    @staticmethod
    def joy_select(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy select button. Also used for apply(). Requires ``pygame.JOYBUTTONDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.button == JOY_BUTTON_SELECT

    @staticmethod
    def joy_up(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts joy movement to up direction. Requires ``pygame.JOYHATMOTION``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.value == JOY_UP

    @staticmethod
    def left(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts left key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_LEFT

    @staticmethod
    def move_down(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts move down key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_MOVE_DOWN

    @staticmethod
    def move_up(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts move up key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_MOVE_UP

    @staticmethod
    def right(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts right key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_RIGHT

    @staticmethod
    def tab(event: EventType, widget: WidgetType) -> bool:
        """
        Accepts tab key. Requires ``pygame.KEYDOWN``.

        :param event: Event
        :param widget: Widget that accepts the event
        :return: True if event matches
        """
        return event.key == KEY_TAB
MichaelWoodc commented 1 year ago

I got this resolved. I didn't resolve it the way I would like (I can probably make changes more directly without affecting things system wide) but I just went into the controls.py file and modified everything for my controller.

I also had to run the menu inside my other program loop.

Absolutely not sure where I was going wrong, and I know it shouldn't have to be this way, but it's the way I had to solve it at the time. So, I can confirm, there's no bug here, just requires modification for my controller! Thanks!