mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-25 12:42:41 -06:00
286 lines
12 KiB
Python
286 lines
12 KiB
Python
"""Undocumented Module"""
|
|
|
|
__all__ = ['DirectOptionMenu']
|
|
|
|
import types
|
|
|
|
from pandac.PandaModules import *
|
|
import DirectGuiGlobals as DGG
|
|
from DirectButton import *
|
|
from DirectLabel import *
|
|
from DirectFrame import *
|
|
|
|
class DirectOptionMenu(DirectButton):
|
|
"""
|
|
DirectOptionMenu(parent) - Create a DirectButton which pops up a
|
|
menu which can be used to select from a list of items.
|
|
Execute button command (passing the selected item through) if defined
|
|
To cancel the popup menu click anywhere on the screen outside of the
|
|
popup menu. No command is executed in this case.
|
|
"""
|
|
def __init__(self, parent = None, **kw):
|
|
# Inherits from DirectButton
|
|
optiondefs = (
|
|
# List of items to display on the popup menu
|
|
('items', [], self.setItems),
|
|
# Initial item to display on menu button
|
|
# Can be an interger index or the same string as the button
|
|
('initialitem', None, DGG.INITOPT),
|
|
# Amount of padding to place around popup button indicator
|
|
('popupMarkerBorder', (.1, .1), None),
|
|
# Background color to use to highlight popup menu items
|
|
('highlightColor', (.5, .5, .5, 1), None),
|
|
# Extra scale to use on highlight popup menu items
|
|
('highlightScale', (1, 1), None),
|
|
# Alignment to use for text on popup menu button
|
|
# Changing this breaks button layout
|
|
('text_align', TextNode.ALeft, None),
|
|
# Remove press effect because it looks a bit funny
|
|
('pressEffect', 0, DGG.INITOPT),
|
|
)
|
|
# Merge keyword options with default options
|
|
self.defineoptions(kw, optiondefs)
|
|
# Initialize superclasses
|
|
DirectButton.__init__(self, parent)
|
|
# Record any user specified frame size
|
|
self.initFrameSize = self['frameSize']
|
|
# Create a small rectangular marker to distinguish this button
|
|
# as a popup menu button
|
|
self.popupMarker = self.createcomponent(
|
|
'popupMarker', (), None,
|
|
DirectFrame, (self,),
|
|
frameSize = (-0.5, 0.5, -0.2, 0.2),
|
|
scale = 0.4,
|
|
relief = DGG.RAISED)
|
|
# This needs to popup the menu too
|
|
self.popupMarker.bind(DGG.B1PRESS, self.showPopupMenu)
|
|
# Check if item is highlighted on release and select it if it is
|
|
self.popupMarker.bind(DGG.B1RELEASE, self.selectHighlightedIndex)
|
|
# Make popup marker have the same click sound
|
|
if self['clickSound']:
|
|
self.popupMarker.guiItem.setSound(
|
|
DGG.B1PRESS + self.popupMarker.guiId, self['clickSound'])
|
|
else:
|
|
self.popupMarker.guiItem.clearSound(DGG.B1PRESS + self.popupMarker.guiId)
|
|
# This is created when you set the menu's items
|
|
self.popupMenu = None
|
|
self.selectedIndex = None
|
|
self.highlightedIndex = None
|
|
# A big screen encompassing frame to catch the cancel clicks
|
|
self.cancelFrame = self.createcomponent(
|
|
'cancelframe', (), None,
|
|
DirectFrame, (self,),
|
|
frameSize = (-1, 1, -1, 1),
|
|
relief = None,
|
|
state = 'normal')
|
|
# Make sure this is on top of all the other widgets
|
|
self.cancelFrame.setBin('gui-popup', 0)
|
|
self.cancelFrame.bind(DGG.B1PRESS, self.hidePopupMenu)
|
|
# Default action on press is to show popup menu
|
|
self.bind(DGG.B1PRESS, self.showPopupMenu)
|
|
# Check if item is highlighted on release and select it if it is
|
|
self.bind(DGG.B1RELEASE, self.selectHighlightedIndex)
|
|
# Call option initialization functions
|
|
self.initialiseoptions(DirectOptionMenu)
|
|
# Need to call this since we explicitly set frame size
|
|
self.resetFrameSize()
|
|
|
|
def setItems(self):
|
|
"""
|
|
self['items'] = itemList
|
|
Create new popup menu to reflect specified set of items
|
|
"""
|
|
# Remove old component if it exits
|
|
if self.popupMenu != None:
|
|
self.destroycomponent('popupMenu')
|
|
# Create new component
|
|
self.popupMenu = self.createcomponent('popupMenu', (), None,
|
|
DirectFrame,
|
|
(self,),
|
|
relief = 'raised',
|
|
)
|
|
# Make sure it is on top of all the other gui widgets
|
|
self.popupMenu.setBin('gui-popup', 0)
|
|
if not self['items']:
|
|
return
|
|
# Create a new component for each item
|
|
# Find the maximum extents of all items
|
|
itemIndex = 0
|
|
self.minX = self.maxX = self.minZ = self.maxZ = None
|
|
for item in self['items']:
|
|
c = self.createcomponent(
|
|
'item%d' % itemIndex, (), 'item',
|
|
DirectButton, (self.popupMenu,),
|
|
text = item, text_align = TextNode.ALeft,
|
|
command = lambda i = itemIndex: self.set(i))
|
|
bounds = c.getBounds()
|
|
if self.minX == None:
|
|
self.minX = bounds[0]
|
|
elif bounds[0] < self.minX:
|
|
self.minX = bounds[0]
|
|
if self.maxX == None:
|
|
self.maxX = bounds[1]
|
|
elif bounds[1] > self.maxX:
|
|
self.maxX = bounds[1]
|
|
if self.minZ == None:
|
|
self.minZ = bounds[2]
|
|
elif bounds[2] < self.minZ:
|
|
self.minZ = bounds[2]
|
|
if self.maxZ == None:
|
|
self.maxZ = bounds[3]
|
|
elif bounds[3] > self.maxZ:
|
|
self.maxZ = bounds[3]
|
|
itemIndex += 1
|
|
# Calc max width and height
|
|
self.maxWidth = self.maxX - self.minX
|
|
self.maxHeight = self.maxZ - self.minZ
|
|
# Adjust frame size for each item and bind actions to mouse events
|
|
for i in range(itemIndex):
|
|
item = self.component('item%d' %i)
|
|
# So entire extent of item's slot on popup is reactive to mouse
|
|
item['frameSize'] = (self.minX, self.maxX, self.minZ, self.maxZ)
|
|
# Move it to its correct position on the popup
|
|
item.setPos(-self.minX, 0, -self.maxZ - i * self.maxHeight)
|
|
item.bind(DGG.B1RELEASE, self.hidePopupMenu)
|
|
# Highlight background when mouse is in item
|
|
item.bind(DGG.WITHIN,
|
|
lambda x, i=i, item=item:self._highlightItem(item, i))
|
|
# Restore specified color upon exiting
|
|
fc = item['frameColor']
|
|
item.bind(DGG.WITHOUT,
|
|
lambda x, item=item, fc=fc: self._unhighlightItem(item, fc))
|
|
# Set popup menu frame size to encompass all items
|
|
f = self.component('popupMenu')
|
|
f['frameSize'] = (0, self.maxWidth, -self.maxHeight * itemIndex, 0)
|
|
|
|
# Determine what initial item to display and set text accordingly
|
|
if self['initialitem']:
|
|
self.set(self['initialitem'], fCommand = 0)
|
|
else:
|
|
# No initial item specified, just use first item
|
|
self.set(0, fCommand = 0)
|
|
|
|
# Position popup Marker to the right of the button
|
|
pm = self.popupMarker
|
|
pmw = (pm.getWidth() * pm.getScale()[0] +
|
|
2 * self['popupMarkerBorder'][0])
|
|
if self.initFrameSize:
|
|
# Use specified frame size
|
|
bounds = list(self.initFrameSize)
|
|
else:
|
|
# Or base it upon largest item
|
|
bounds = [self.minX, self.maxX, self.minZ, self.maxZ]
|
|
pm.setPos(bounds[1] + pmw/2.0, 0,
|
|
bounds[2] + (bounds[3] - bounds[2])/2.0)
|
|
# Adjust popup menu button to fit all items (or use user specified
|
|
# frame size
|
|
bounds[1] += pmw
|
|
self['frameSize'] = (bounds[0], bounds[1], bounds[2], bounds[3])
|
|
# Set initial state
|
|
self.hidePopupMenu()
|
|
|
|
def showPopupMenu(self, event = None):
|
|
"""
|
|
Make popup visible and try to position it just to right of
|
|
mouse click with currently selected item aligned with button.
|
|
Adjust popup position if default position puts it outside of
|
|
visible screen region
|
|
"""
|
|
# Show the menu
|
|
self.popupMenu.show()
|
|
# Make sure its at the right scale
|
|
self.popupMenu.setScale(self, VBase3(1))
|
|
# Compute bounds
|
|
b = self.getBounds()
|
|
fb = self.popupMenu.getBounds()
|
|
# Position menu at midpoint of button
|
|
xPos = (b[1] - b[0])/2.0 - fb[0]
|
|
self.popupMenu.setX(self, xPos)
|
|
# Try to set height to line up selected item with button
|
|
self.popupMenu.setZ(
|
|
self, self.minZ + (self.selectedIndex + 1)*self.maxHeight)
|
|
# Make sure the whole popup menu is visible
|
|
pos = self.popupMenu.getPos(render2d)
|
|
scale = self.popupMenu.getScale(render2d)
|
|
# How are we doing relative to the right side of the screen
|
|
maxX = pos[0] + fb[1] * scale[0]
|
|
if maxX > 1.0:
|
|
# Need to move menu to the left
|
|
self.popupMenu.setX(render2d, pos[0] + (1.0 - maxX))
|
|
# How about up and down?
|
|
minZ = pos[2] + fb[2] * scale[2]
|
|
maxZ = pos[2] + fb[3] * scale[2]
|
|
if minZ < -1.0:
|
|
# Menu too low, move it up
|
|
self.popupMenu.setZ(render2d, pos[2] + (-1.0 - minZ))
|
|
elif maxZ > 1.0:
|
|
# Menu too high, move it down
|
|
self.popupMenu.setZ(render2d, pos[2] + (1.0 - maxZ))
|
|
# Also display cancel frame to catch clicks outside of the popup
|
|
self.cancelFrame.show()
|
|
# Position and scale cancel frame to fill entire window
|
|
self.cancelFrame.setPos(render2d, 0, 0, 0)
|
|
self.cancelFrame.setScale(render2d, 1, 1, 1)
|
|
|
|
def hidePopupMenu(self, event = None):
|
|
""" Put away popup and cancel frame """
|
|
self.popupMenu.hide()
|
|
self.cancelFrame.hide()
|
|
|
|
def _highlightItem(self, item, index):
|
|
""" Set frame color of highlighted item, record index """
|
|
item['frameColor'] = self['highlightColor']
|
|
item['frameSize'] = (self['highlightScale'][0]*self.minX, self['highlightScale'][0]*self.maxX, self['highlightScale'][1]*self.minZ, self['highlightScale'][1]*self.maxZ)
|
|
item['text_scale'] = self['highlightScale']
|
|
self.highlightedIndex = index
|
|
|
|
def _unhighlightItem(self, item, frameColor):
|
|
""" Clear frame color, clear highlightedIndex """
|
|
item['frameColor'] = frameColor
|
|
item['frameSize'] = (self.minX, self.maxX, self.minZ, self.maxZ)
|
|
item['text_scale'] = (1,1)
|
|
self.highlightedIndex = None
|
|
|
|
def selectHighlightedIndex(self, event = None):
|
|
"""
|
|
Check to see if item is highlighted (by cursor being within
|
|
that item). If so, selected it. If not, do nothing
|
|
"""
|
|
if self.highlightedIndex is not None:
|
|
self.set(self.highlightedIndex)
|
|
self.hidePopupMenu()
|
|
|
|
def index(self, index):
|
|
intIndex = None
|
|
if isinstance(index, types.IntType):
|
|
intIndex = index
|
|
elif index in self['items']:
|
|
i = 0
|
|
for item in self['items']:
|
|
if item == index:
|
|
intIndex = i
|
|
break
|
|
i += 1
|
|
return intIndex
|
|
|
|
def set(self, index, fCommand = 1):
|
|
# Item was selected, record item and call command if any
|
|
newIndex = self.index(index)
|
|
if newIndex is not None:
|
|
self.selectedIndex = newIndex
|
|
item = self['items'][self.selectedIndex]
|
|
self['text'] = item
|
|
if fCommand and self['command']:
|
|
# Pass any extra args to command
|
|
apply(self['command'], [item] + self['extraArgs'])
|
|
|
|
def get(self):
|
|
""" Get currently selected item """
|
|
return self['items'][self.selectedIndex]
|
|
|
|
def commandFunc(self, event):
|
|
"""
|
|
Override popup menu button's command func
|
|
Command is executed in response to selecting menu items
|
|
"""
|
|
pass
|