How-To: Easily Implement PyGame User Interface Elements — Part III — Popup Menus

GameElementGuy
6 min readJan 20, 2023

--

In my previous entries, I introduced you to the new open source library pyguix that enables you to create desired user interface elements during game design and game run time, for python based pygames.

You can find the first article in this series, located here: How-To: Easily Create PyGame User Interface & Heads Up Display Elements

pygame.org logo

Further I continued to showcase how you can easily create desired user interactions by quickly deploying a configurable heads up display for your python — pygames, using the SnapHUD user interface element from pyguix.

pyguix contextual popup menu in action @PyFryDay

In this post I will showcase for you, how to enable contextual, right-click based popup menus within your pygames.

PyGames-Mars | 02_PopupMenu game instance highlight

What you see in the above image is a snippet of the game running, with a pyguix.ui.elements.PopupMenu instance created, after the user right clicked on ROVI, the mars roving, robot.

How did we get to displaying the ui.PopupMenu user interface element?

I — Getting Started:

1. First you will need to clone the repo of pyguix, from its home on GitHub.

2. Next, you will need to copy/paste the pyguix folder as a ‘sub-folder’ of the desired game instance in which you will be developing your game. You can see a similar action has taken place for the ‘PyGames-Mars/02_PopupMenu’ pygame.

enabling pyguix

Once this is in place then you have what you need to start implementing and using instances of user interface elements like ui.PopupMenu.

Now lets break down the steps needed to implement the ui.PopupMenu, like the example found in he ‘PyGames-Mars/02_PopupMenu’ game instance.:

II — pyguix.ui.elements.PopupMenu (ui.PopupMenu):

1. Open the main.py file, and create an import line that will import the pyguix.ui.elements as ui.

import pyguix.ui.elements as ui

2. Create a new class, derived of type ui.PopupMenuActions, that will house the logic for bound functions in which a contextual popup menu will fire. This will later be mapped to what sprite class for setting context, for when a right-click is detected on a sprite.

# Rover Right-click PopupMenu action class:
class RoverMenu(ui.PopupMenuActions):

def rover_info(self):
pmi = self.get_active_menuitem()

ui.MessageBox(
window=self.window,
title="Rover Info (Right-Click)",
message_text=f"Rover located at {pmi.contextof.rect.center}", #NOTE: contextof is right-clicked sprite.
width=300,
event_list=pygame.event.get()
)

def sample_soil(self):
pmi = self.get_active_menuitem()
pmi.contextof.samplesoil() #NOTE: contextof is right-clicked sprite. (ie:Player.samplesoil() call works)

def __init__(self,window):
self.window = window
super().__init__()

3. Within the __init__() of the Game class create a single instance of the RoverMenu() which you defined in step two (2) above. What you should note is that this instance is generated, yet it is not stored in any variable. The reason for this is that the pyguix logic has an object cache that only allows for one instance of any given PopupMenuActions class type to exist.

For example, if you try to create another instance after the fact, then you will be returned the previously created instance from the global cache.

Finally the other line, of the two lines of code you need here, is setting a variable used to house the ui.PopupMenu in, for the Game instance.

# PopupMenu Variables / Setup:
RoverMenu(self.screen)
self.pu = None

4. The last lines of code needed to enable a contextual based, right-click generated, popup menu is housed within the while/run loop of the game instance. Specifically within the check logic check for when an event is fired in which pygame is expected to react.

This starts with the pygame.MOUSEBUTTONDOWN, and then pygame.BUTTON_RIGHT and pygame.BUTTON_LEFT respectively.

if event.type == pygame.MOUSEBUTTONDOWN:
# Popup Menu Setup:
if event.button == pygame.BUTTON_RIGHT:
ui.PopupMenu.clearall(self.pu)
# Create new instance of ui.PopupMenu:
self.pu = ui.PopupMenu(
window=pygame.display.get_surface(),
target_mouse_pos=pygame.mouse.get_pos(),
rg=self.level.visible_sprites,
)

The above code block shows an example of working with the ‘self.pu’ variable declared in step 3. This is taking place upon the event of a right mouse click from the user.

First a line of code exist, ui.PopupMenu.clearall(self.pu), which clears any previous ui.PopupMenu for the self.pu variable, before a new instance is declared.

Further you can see below that line, a ui.PopupMenu(…) instance is created and stored within the self.pu variable. What is great about this pyguix UI element, is the fact that this is all that is needed for any/all popup menu instances that might exist for a game instance. For example, having a popup menu for the player sprite, vs different enemy sprite instances.

elif event.button == pygame.BUTTON_LEFT:
# Respond to left mouse click, if on rover_snap_hud ui element.:
self.level.rover_snap_hud.clicked()
# NOTE: If PopupMenu instance active then call PopupMenu.clicked() passing in event_list:
if isinstance(self.pu,ui.PopupMenu): self.pu.clicked(event_list)

The above code block shows an example of logic checking to see if the self.pu variable is an active ui.PopupMenu. If so, then, it fires the PopupMenu.clicked() bound function.

if isinstance(self.pu,ui.PopupMenu): self.pu.update()

Finally the above line of code checks once again if the self.pu variable is an active ui.PopupMenu instance. If so, then it calls the .update() bound function for each pass of the game loop, while the self.pu(ui.PopupMenu) is active.

5. The final piece for enabling the pyguix.ui.elements.PopupMenu UI element to work as shown in the game instance example is the creation and deployment of the corresponding JSON context file.

PopupMenu JSON Context File Snippet

In the above image, you can see how the JSON context file maps in its details section, the ‘actionclass’ to targetclasses. For the target classes, you can have multiple sprite classes in which a specific action class/Popup Menu generate for.

{
"PopupMenu": {
"details": {
"actionclass": "<class '__main__.RoverMenu'>",
"targetclasses": ["<class 'player.Player'>"]
},
"menuitems":{
"info":{
"popupmenuitem":{
"text": "Get Info",
"type": 0,
"action": "rover_info",
"enabled": true
}
},
"sep1":{
"popupmenuitem":{
"text": "[&SEP]",
"type": 1,
"action": "[&SEP]",
"enabled": false
}
},
"sample":{
"popupmenuitem":{
"text": "Sample Soil",
"type": 0,
"action": "sample_soil",
"enabled": true
}
}
},
"notes": "Rover Right-Click PopupMenu for PyGames-Mars pygame."
}
}

You will noticed, after the ‘details’ section in the PopupMenu JSON context file is a section called ‘menuitems’. Within this block is where the mapping of menu item buttons to bound functions of a targeted actionclass takes place. You will notice that you can easily change the text, type (ie: action vs. separator [&SEP]), action and if the menuitem is enabled or not when the popup menu initially renders.

Once you have the JSON context file ready, in the expected schema for PopupMenu, place it in the pyguix.ui.context sub-folder of the game instance.

That is it! Now when the game loads, with what you have provided, you will see the pyguix.ui.elements.PopupMenu fire as desired. For this example, that means nothing will fire for anything of a right-click other than on the player.Player() sprite class instance. Since there is just once instance of that class, ROVI the Mar Rover, that is when the contextual popup menu renders.

III — Conclusion

My goal with this article was to take you through the concepts introduced in my previous article, (title and link to previous article), so you could start implementing globally themed, easy to use, popup menus in your python — pygames.

We covered the steps for creating an instance of the pyguix.ui.elements.PopupMenu class for game interaction needs. With this example in hand, you now have powerful, easy to implement, in-game, contextual, popup menus for your pygames.

More entries are on the way, however any feedback, comments, thoughts, or request are most welcome!

J. Brandon George

twitter: @PyFryDay

email: darth.data410@gmail.com

github: DarthData410

GitHub Project Source: https://github.com/DarthData410/PyGames-Mars

--

--

GameElementGuy
GameElementGuy

Written by GameElementGuy

C/C++, C#, python, GoDot, Game Modding and more.

No responses yet