Sample class for a vertically-arranged menu of options. Options can be selected with mouse, keyboard arrows, or gamepad dpad, and confirmed with mouse click, enter, spacebar, or gamepad A buttton.
#!/usr/bin/env python3
from panda3d.core import *
from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenImage import OnscreenImage
from direct.gui.DirectGui import *
color_ready=(0.5,0.5,1.0,1)
color_hilite=(1,1,1,1)
color_locked=(0.5,0.5,0.5,1)
class Menu():
def __init__(self,gamedata,name="",scale=(1,1,1),pos=(0,0,0),choices=[],textsize=0.125):
#gamedata['font'] should be an object containing your desired font, the rest is ignored
self.gamedata = gamedata
self.pos = pos
self.textsize = textsize
self.name=name
self.choices=choices
self.frames=[]
self.nodesReady=[]
self.nodesHilite=[]
self.selected = 0
zoff = textsize
zstart = (textsize * (len(choices)-1))/2.25
# replace this with your own background texture for the menu
self.bg = OnscreenImage(image='data/menu/menu_bg.png', pos=pos, scale=scale)
self.bg.setTransparency(TransparencyAttrib.MAlpha)
self.bg.setAlphaScale(0.75)
x, y, z = self.pos
z += zstart
i = 0
for text in choices:
# have nodes for each state to swap as needed
textnodeH = self.MakeTextNode(text,color_hilite)
textnodeR = self.MakeTextNode(text,color_ready)
frame = DirectFrame(geom=textnodeR, frameColor=(0,0,0,0))
frame.setTransparency(TransparencyAttrib.MAlpha)
frame.setPos(x,y,z)
frame.setScale(self.textsize)
# mouse controls
frame.guiItem.setActive(True)
frame.bind(DGG.WITHIN, self.Select, [i])
frame.bind(DGG.B1PRESS, self.Return, [i])
z -= zoff
i += 1
self.nodesReady.append(textnodeR)
self.nodesHilite.append(textnodeH)
self.frames.append(frame)
# default to top option
self.Select(0, 0)
# gamepad controls
base.accept("gamepad-dpad_up", self.SelectUp)
base.accept("gamepad-dpad_down", self.SelectDown)
base.accept("gamepad-face_a", self.Confirm)
# keyboard controls
base.accept("arrow_up", self.SelectUp)
base.accept("arrow_down", self.SelectDown)
base.accept("space", self.Confirm)
base.accept("enter", self.Confirm)
def Select(self, i, junk):
#unselect every option
for j in range(0, len(self.choices)):
l = self.nodesReady[j]
f = self.frames[j]
f.setGeom(l)
#then select the desired option
self.selected = i
l = self.nodesHilite[i]
f = self.frames[i]
f.setGeom(l)
def SelectDown(self):
i = self.selected
i += 1
if i >= len(self.choices):
i = 0
self.Select(i, 0)
def SelectUp(self):
i = self.selected
i -= 1
if i < 0:
i = len(self.choices) - 1
self.Select(i, 0)
def UnSelect(self, i, junk):
self.selected = None
l = self.nodesReady[i]
f = self.frames[i]
f.setGeom(l)
def Confirm(self):
i = self.selected
self.Return(i,0)
def Return(self, i, junk):
# send an event indicating that we've chosen an option on this menu
# NOTE: it is the responsibility of the MenuReturn event handler to hide this menu
option = self.choices[i]
messenger.send('MenuReturn',[self.name,option])
def MakeTextNode(self, text, color):
# helper function to make text nodes easily
label = TextNode('label')
label.setFont(self.gamedata['font'])
label.setTextColor(color)
label.clearShadow()
label.setText(text)
label.setAlign(TextNode.ACenter)
node=render.attachNewNode(label)
return node
def hide(self):
# hide the whole menu
self.bg.hide()
for button in self.frames:
button.hide()
def show(self):
# show the menu after being hidden
self.bg.show()
for button in self.frames:
button.show()