| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- """Undocumented Module"""
- __all__ = ['DirectOptionMenu']
- import types
- from panda3d.core 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
|