Browse Source

New code to support popup option menu

Mark Mine 23 years ago
parent
commit
4b3278ad69

+ 5 - 0
direct/src/extensions/NodePath-extensions.py

@@ -44,6 +44,11 @@
         for child in self.getChildrenAsList():
             print child.getName()
 
+    def removeChildren(self):
+        """Deletes the children of the bottom node of a node path"""
+        for child in self.getChildrenAsList():
+            child.removeNode()
+
     def toggleVis(self):
         """Toggles visibility of a nodePath"""
         if self.isHidden():

+ 34 - 18
direct/src/gui/DirectCheckButton.py

@@ -86,37 +86,51 @@ class DirectCheckButton(DirectButton):
             # Ok, they didn't set specific bounds,
             #  let's add room for the label indicator
             #  get the difference in height
-            
-            diff = self.indicator.getHeight() + (2*self['boxBorder']) - (self.bounds[3] - self.bounds[2])
+
+            ibw = self.indicator['borderWidth']
+            indicatorWidth = (self.indicator.getWidth() + (2*ibw[0]))
+            indicatorHeight = (self.indicator.getHeight() + (2*ibw[1]))
+            diff = (indicatorHeight + (2*self['boxBorder']) -
+                    (self.bounds[3] - self.bounds[2]))
+            print diff, self.bounds[3], self.bounds[2]
             # If background is smaller then indicator, enlarge background
             if diff > 0:
                 if self['boxPlacement'] == 'left':            #left
-                    self.bounds[0] += -(self.indicator.getWidth() + (2*self['boxBorder']))
+                    self.bounds[0] += -(indicatorWidth + (2*self['boxBorder']))
                     self.bounds[3] += diff/2
                     self.bounds[2] -= diff/2
                 elif self['boxPlacement'] == 'below':          #below
-                    self.bounds[2] += -(self.indicator.getHeight() + (2*self['boxBorder']))
+                    self.bounds[2] += -(indicatorHeight+(2*self['boxBorder']))
                 elif self['boxPlacement'] == 'right':          #right
-                    self.bounds[1] += self.indicator.getWidth() + (2*self['boxBorder'])
+                    self.bounds[1] += indicatorWidth + (2*self['boxBorder'])
                     self.bounds[3] += diff/2
                     self.bounds[2] -= diff/2
                 else:                                    #above
-                    self.bounds[3] += self.indicator.getHeight() + (2*self['boxBorder'])
+                    self.bounds[3] += indicatorHeight + (2*self['boxBorder'])
 
             # Else make space on correct side for indicator
             else:
                 if self['boxPlacement'] == 'left':            #left
-                    self.bounds[0] += -(self.indicator.getWidth() + (2*self['boxBorder']))
+                    self.bounds[0] += -(indicatorWidth + (2*self['boxBorder']))
                 elif self['boxPlacement'] == 'below':          #below
-                    self.bounds[2] += -(self.indicator.getHeight() + (2*self['boxBorder']))
+                    self.bounds[2] += -(indicatorHeight + (2*self['boxBorder']))
                 elif self['boxPlacement'] == 'right':          #right
-                    self.bounds[1] += self.indicator.getWidth() + (2*self['boxBorder'])
+                    self.bounds[1] += indicatorWidth + (2*self['boxBorder'])
                 else:                                    #above
-                    self.bounds[3] += self.indicator.getHeight() + (2*self['boxBorder'])
+                    self.bounds[3] += indicatorHeight + (2*self['boxBorder'])
 
         # Set frame to new dimensions
-        self.guiItem.setFrame(self.bounds[0], self.bounds[1],
-                              self.bounds[2], self.bounds[3])  #3 is top border!!
+        if ((frameType != PGFrameStyle.TNone) and
+            (frameType != PGFrameStyle.TFlat)):
+            bw = self['borderWidth']
+        else:
+            bw = (0,0)
+        # Set frame to new dimensions
+        self.guiItem.setFrame(
+            self.bounds[0] - bw[0],
+            self.bounds[1] + bw[0],
+            self.bounds[2] - bw[1],
+            self.bounds[3] + bw[1])
 
         # If they didn't specify a position, put it in the center of new area
         if not self.indicator['pos']:
@@ -125,17 +139,19 @@ class DirectCheckButton(DirectButton):
             newpos = [0,0,0]
 
             if self['boxPlacement'] == 'left':            #left
-                newpos[0] += bbounds[0]-lbounds[0] + self['boxBorder']
+                newpos[0] += bbounds[0]-lbounds[0] + self['boxBorder'] + ibw[0]
                 dropValue = (bbounds[3]-bbounds[2]-lbounds[3]+lbounds[2])/2 + self['boxBorder']
-                newpos[2] += bbounds[3]-lbounds[3] + self['boxBorder'] - dropValue
+                newpos[2] += (bbounds[3]-lbounds[3] + self['boxBorder'] -
+                              dropValue)
             elif self['boxPlacement'] == 'right':            #right
-                newpos[0] += bbounds[1]-lbounds[1] - self['boxBorder']
+                newpos[0] += bbounds[1]-lbounds[1] - self['boxBorder'] - ibw[0]
                 dropValue = (bbounds[3]-bbounds[2]-lbounds[3]+lbounds[2])/2 + self['boxBorder']
-                newpos[2] += bbounds[3]-lbounds[3] + self['boxBorder'] - dropValue
+                newpos[2] += (bbounds[3]-lbounds[3] + self['boxBorder']
+                              - dropValue)
             elif self['boxPlacement'] == 'above':            #above
-                newpos[2] += bbounds[3]-lbounds[3] - self['boxBorder']
+                newpos[2] += bbounds[3]-lbounds[3] - self['boxBorder'] - ibw[1]
             else:                                      #below
-                newpos[2] += bbounds[2]-lbounds[2] + self['boxBorder']
+                newpos[2] += bbounds[2]-lbounds[2] + self['boxBorder'] + ibw[1]
 
             self.indicator.setPos(newpos[0],newpos[1],newpos[2])
 

+ 1 - 0
direct/src/gui/DirectGui.py

@@ -15,3 +15,4 @@ from DirectScrolledList import *
 from DirectDialog import *
 from DirectWaitBar import *
 from DirectCheckButton import *
+from DirectOptionMenu import *

+ 15 - 4
direct/src/gui/DirectGuiBase.py

@@ -856,12 +856,12 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             self.setFrameSize(fClearFrame = 1)
         
     def setFrameSize(self, fClearFrame = 0):
+        # Use ready state to determine frame Type
+        frameType = self.getFrameType()
         if self['frameSize']:
             # Use user specified bounds
             self.bounds = self['frameSize']
         else:
-            # Use ready state to compute bounds
-            frameType = self.frameStyle[0].getType()
             if fClearFrame and (frameType != PGFrameStyle.TNone):
                 self.frameStyle[0].setType(PGFrameStyle.TNone)
                 self.guiItem.setFrameStyle(0, self.frameStyle[0])
@@ -873,9 +873,17 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             if (frameType != PGFrameStyle.TNone):
                 self.frameStyle[0].setType(frameType)
                 self.guiItem.setFrameStyle(0, self.frameStyle[0])
+        if ((frameType != PGFrameStyle.TNone) and
+            (frameType != PGFrameStyle.TFlat)):
+            bw = self['borderWidth']
+        else:
+            bw = (0,0)
         # Set frame to new dimensions
-        self.guiItem.setFrame(self.bounds[0], self.bounds[1],
-                              self.bounds[2], self.bounds[3])
+        self.guiItem.setFrame(
+            self.bounds[0] - bw[0],
+            self.bounds[1] + bw[0],
+            self.bounds[2] - bw[1],
+            self.bounds[3] + bw[1])
 
 
     def getBounds(self, state = 0):
@@ -898,6 +906,9 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
         y = self.bounds[2] + (self.bounds[3] - self.bounds[2])/2.0
         return (x,y)
 
+    def getFrameType(self, state = 0):
+        return self.frameStyle[state].getType()
+    
     def updateFrameStyle(self):
         if not self.fInit:
             for i in range(self['numStates']):

+ 252 - 0
direct/src/gui/DirectOptionMenu.py

@@ -0,0 +1,252 @@
+from DirectButton import *
+from DirectLabel 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 = aspect2d, **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,           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),
+            # 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,          INITOPT),
+           )
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs)
+        # Initialize superclasses
+        DirectButton.__init__(self, parent)
+        # This is created when you set the menu's items
+        self.popupMenu = None
+        self.selectedIndex = None
+        # 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 = RAISED)
+        # This needs to popup the menu too
+        self.popupMarker.bind(B1PRESS, self.showPopupMenu)
+        # Make popup marker have the same click sound
+        self.popupMarker.guiItem.setSound(
+            B1PRESS + self.popupMarker.guiId,self['clickSound'])
+        # 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)
+        self.cancelFrame.bind(B1PRESS, self.hidePopupMenu)
+        # Default action on press is to show popup menu
+        self.bind(B1PRESS, self.showPopupMenu)
+        # 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',
+                                              )
+        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(B1RELEASE, self.hidePopupMenu)
+            # Highlight background when mouse is in item
+            item.bind(ENTER, lambda x, item=item: self._highlightItem(item))
+            # Restore specified color upon exiting
+            fc = item['frameColor']
+            item.bind(
+                EXIT,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
+        """
+        b = self.getBounds()
+        fb = self.popupMenu.getBounds()
+        # Where did the user click his mouse?
+        if (base.mouseWatcherNode.hasMouse()):
+            xPos = base.mouseWatcherNode.getMouseX()
+            self.popupMenu.setX(render2d, xPos)
+        else:
+            # If no mouse watcher, use midpoint of menu button
+            xPos = (b[1] - b[0])/2.0 - fb[0]
+            self.popupMenu.setX(xPos)
+        # Try to set height to line up selected item with button
+        self.popupMenu.setZ((self.selectedIndex + 1)*self.maxHeight - fb[3])
+        # 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))
+        # Show the menu in its new position
+        self.popupMenu.show()
+        # 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):
+        item['frameColor'] = self['highlightColor']
+
+    def _unhighlightItem(self, item, frameColor):
+        item['frameColor'] = frameColor
+
+    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
+        
+
+
+
+
+
+
+