Browse Source

*** empty log message ***

Mark Mine 24 years ago
parent
commit
45615432f2

+ 44 - 244
direct/src/gui/DirectButton.py

@@ -1,281 +1,81 @@
-from DirectGuiBase import *
+from DirectLabel import *
 
-IMAGE_SORT_INDEX = 10
-GEOM_SORT_INDEX = 20
-TEXT_SORT_INDEX = 30
-
-class DirectButton(DirectGuiBase, NodePath):
+class DirectButton(DirectLabel):
+    """
+    DirectButton(parent) - Create a DirectGuiWidget which responds
+    to mouse clicks and execute a callback function if defined
+    """
     def __init__(self, parent = guiTop, **kw):
-        # Pass in a background texture, and/or a geometry object,
-        # and/or a text string to be used as the visible
-        # representation of the button, or pass in a list of geometry
-        # objects, one for each state (normal, rollover, pressed,
-        # disabled)
-        # Bounding box to be used in button/mouse interaction
-        # If None, use bounding box of actual button geometry
-        # Otherwise, you can pass in:
-        #  - a list of [L,R,B,T] (in aspect2d coords)
-        #  - a VBase4(L,R,B,T)
-        #  - a bounding box object
+        # Inherits from DirectLabel
+        # A Direct Label can have:
+        # - A background texture (pass in path to image, or Texture Card)
+        # - A midground geometry item (pass in geometry)
+        # - A foreground text Node (pass in text string or Onscreen Text)
+        # For a direct button:
+        # Each of these has 4 states (ready, press, rollover, disabled)
+        # The same object can be used for all four states or each
+        # state can have a different text/geom/image
+        # Responds to click event and calls command if None
         optiondefs = (
-            # Button can have:
-            # A background texture
-            ('image',           None,       self.setImage),
-            # A midground geometry item
-            ('geom',            None,       self.setGeom),
-            # A foreground text node
-            ('text',            None,       self.setText),
+            # Define type of DirectGuiWidget
+            ('pgFunc',          PGButton,  None),
+            ('numStates',       4,         None),
+            ('invertedFrames',  (1,),      None),
             # Command to be called on button click
             ('command',         None,       None),
             ('extraArgs',       [],         None),
             # Which mouse buttons can be used to click the button
-            ('commandButtons',  (1,),       self.setCommandButtons),
-            # Buttons initial state
-            ('state',           NORMAL,     self.setState),
-            # Button frame characteristics
-            ('relief',          FLAT,       self.setRelief),
-            ('frameColor',      (1,1,1,1),  self.setFrameColor),
-            ('borderWidth',     (.1,.1),    self.setBorderWidth),
-            ('frameSize',       None,       self.setFrameSize),
-            ('pad',             (.25,.15),  self.resetFrameSize),
+            ('commandButtons',  (LMB,),     self.setCommandButtons),
             # Sounds to be used for button events
             ('rolloverSound',   None,       None),
             ('clickSound',      None,       None),
             # Can only be specified at time of widget contruction
             # Do the text/graphics appear to move when the button is clicked
             ('pressEffect',     1,          INITOPT),
-            # Initial pos/scale of the button
-            ('pos',             None,       INITOPT),
-            ('scale',           None,       INITOPT),
             )
         # Merge keyword options with default options
         self.defineoptions(kw, optiondefs,
                            dynamicGroups = ('text', 'geom', 'image'))
 
         # Initialize superclasses
-        DirectGuiBase.__init__(self)
-        NodePath.__init__(self)
-        # Create a button
-        self.guiItem = PGButton()
-        self.guiId = self.guiItem.getId()
-        # Attach button to parent and make that self
-        self.assign(parent.attachNewNode( self.guiItem ) )
-        # Initialize names
-        self.guiItem.setName(self.guiId)
-        self.setName(self.guiId + 'NodePath')
-        # Get a handle on the button's hidden node paths for each state
-        self.stateNodePath = []
-        for i in range(4):
-            self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
+        DirectLabel.__init__(self, parent)
+        
         # If specifed, add scaling to the pressed state to make it look
         # like the button is moving when you press it
         if self['pressEffect']:
             np = self.stateNodePath[1].attachNewNode('pressEffect')
             np.setScale(0.98)
             self.stateNodePath[1] = np
-        # Initialize frame style
-        self.frameStyle = []
-        for i in range(4):
-            self.frameStyle.append(PGFrameStyle())
-        # For holding bounds info
-        self.ll = Point3(0)
-        self.ur = Point3(0)
+            
         # Call option initialization functions
-        # To avoid doing things redundantly set fInit flag
-        self.fInit = 1
         self.initialiseoptions(DirectButton)
-        self.fInit = 0
-        # Now allow changes to take effect
-        self.updateFrameStyle()
-        if not self['frameSize']:
-            self.setFrameSize()
-        # Update pose to initial values
-        if self['pos']:
-            pos = self['pos']
-            # Can either be a Point3 or a tuple of 3 values
-            if isintance(pos, Point3):
-                self.setPos(pos)
-            else:
-                apply(self.setPos, pos)
-        if self['scale']:
-            scale = self['scale']
-            # Can either be a Vec3 or a tuple of 3 values
-            if (isinstance(scale, Vec3) or
-                (type(scale) == types.IntType) or
-                (type(scale) == types.FloatType)):
-                self.setScale(scale)
-            else:
-                apply(self.setScale, self['scale'])
-
-    def updateFrameStyle(self):
-        for i in range(4):
-            self.guiItem.setFrameStyle(i, self.frameStyle[i])
-
-    def setRelief(self, fSetStyle = 1):
-        relief = self['relief']
-        if relief == None:
-            for i in range(4):
-                self.frameStyle[i].setType(PGFrameStyle.TNone)
-        elif (relief == FLAT) or (relief == 'flat'):
-            for i in range(4):
-                self.frameStyle[i].setType(FLAT)
-        elif (relief == RAISED) or (relief == 'raised'):
-            for i in (0,2,3):
-                self.frameStyle[i].setType(RAISED)
-            self.frameStyle[1].setType(SUNKEN)
-        elif (relief == SUNKEN) or (relief == 'sunken'):
-            for i in (0,2,3):
-                self.frameStyle[i].setType(SUNKEN)
-            self.frameStyle[1].setType(RAISED)
-        if not self.fInit:
-            self.updateFrameStyle()
-
-    def resetFrameSize(self):
-        if not self.fInit:
-            self.setFrameSize(fClearFrame = 1)
-        
-    def setFrameSize(self, fClearFrame = 0):
-        if self['frameSize']:
-            # Use user specified bounds
-            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])
-                # To force an update of the button
-                self.guiItem.getStateDef(0)
-            # Clear out frame before computing bounds
-            self.stateNodePath[0].calcTightBounds(self.ll, self.ur)
-            # Scale bounds to give a pad around graphics
-            bounds = (self.ll[0] - self['pad'][0],
-                      self.ur[0] + self['pad'][0],
-                      self.ll[2] - self['pad'][1],
-                      self.ur[2] + self['pad'][1])
-            # Restore frame style if necessary
-            if (frameType != PGFrameStyle.TNone):
-                self.frameStyle[0].setType(frameType)
-                self.guiItem.setFrameStyle(0, self.frameStyle[0])
-        # Set frame to new dimensions
-        self.guiItem.setFrame(bounds[0], bounds[1],bounds[2], bounds[3])
-
-    def setFrameColor(self):
-        color = self['frameColor']
-        for i in range(4):
-            self.frameStyle[i].setColor(color[0], color[1], color[2], color[3])
-        if not self.fInit:
-            self.updateFrameStyle()
 
-    def setBorderWidth(self):
-        width = self['borderWidth']
-        for i in range(4):
-            self.frameStyle[i].setWidth(width[0], width[1])
-        if not self.fInit:
-            self.updateFrameStyle()
-
-    def setText(self):
-        if not self['text']:
-            return
-        if ((type(self['text']) == type(())) or
-            (type(self['text']) == type([]))):
-            text = self['text']
+    def setCommandButtons(self):
+        # Attach command function to specified buttons
+        # Left mouse button
+        if LMB in self['commandButtons']:
+            self.guiItem.addClickButton(MouseButton.one())
+            self.bind(B1CLICK, self.commandFunc)
         else:
-            text = (self['text'],) * 4
-        for i in range(4):
-            component = 'text' + `i`
-            if not self.hascomponent(component):
-                self.createcomponent(
-                    component, (), 'text',
-                    OnscreenText.OnscreenText,
-                    (), parent = self.stateNodePath[i],
-                    text = text[i], scale = 1,
-                    sort = TEXT_SORT_INDEX,
-                    mayChange = 1)
-            else:
-                self[component + '_text'] = text[i]
-
-    def setGeom(self):
-        if not self['geom']:
-            return
-        if ((type(self['geom']) == type(())) or
-            (type(self['geom']) == type([]))):
-            geom = self['geom']
+            self.unbind(B1CLICK)
+            self.guiItem.removeClickButton(MouseButton.one())
+        # Middle mouse button
+        if MMB in self['commandButtons']:
+            self.guiItem.addClickButton(MouseButton.two())
+            self.bind(B2CLICK, self.commandFunc)
         else:
-            geom = (self['geom'],) * 4
-        for i in range(4):
-            component = 'geom' + `i`
-            if not self.hascomponent(component):
-                self.createcomponent(
-                    component, (), 'geom',
-                    OnscreenGeom.OnscreenGeom,
-                    (), parent = self.stateNodePath[i],
-                    sort = GEOM_SORT_INDEX,
-                    geom = geom[i], scale = 1)
-            else:
-                self[component + '_geom'] = geom[i]
-
-    def setImage(self):
-        if not self['image']:
-            return
-        if type(self['image']) == type(''):
-            # Assume its a file name
-            image = (self['image'],) * 4
-        elif ((type(self['image']) == type(())) or
-            (type(self['image']) == type([]))):
-            if len(self['image']) == 2:
-                # Assume its a model/node pair of strings
-                image = (self['image'],) * 4
-            elif len(self['image']) == 4:
-                # Assume its a 4 tuple with one entry per state
-                image = self['image']
-            else:
-                print 'DirectButton.setImage: wrong argument'
-        for i in range(4):
-            component = 'image' + `i`
-            if not self.hascomponent(component):
-                self.createcomponent(
-                    component, (), 'image',
-                    OnscreenImage.OnscreenImage,
-                    (), parent = self.stateNodePath[i],
-                    sort = IMAGE_SORT_INDEX,
-                    image = image[i], scale = 1)
-            else:
-                self[component + '_image'] = image[i]
-
-    def setState(self):
-        if type(self['state']) == type(0):
-            self.guiItem.setActive(self['state'])
-        elif (self['state'] == NORMAL) or (self['state'] == 'normal'):
-            self.guiItem.setActive(1)
+            self.unbind(B2CLICK)
+            self.guiItem.removeClickButton(MouseButton.two())
+        # Right mouse button
+        if RMB in self['commandButtons']:
+            self.guiItem.addClickButton(MouseButton.three())
+            self.bind(B3CLICK, self.commandFunc)
         else:
-            self.guiItem.setActive(0)
-
-    def setCommandButtons(self):
-        # Attach command function to specified buttons
-        self.unbind(B1CLICK)
-        self.guiItem.removeClickButton(MouseButton.one())
-        self.unbind(B2CLICK)
-        self.guiItem.removeClickButton(MouseButton.two())
-        self.unbind(B3CLICK)
-        self.guiItem.removeClickButton(MouseButton.three())
-        for i in self['commandButtons']:
-            if i == 1:
-                self.guiItem.addClickButton(MouseButton.one())
-                self.bind(B1CLICK, self.commandFunc)
-            elif i == 2:
-                self.guiItem.addClickButton(MouseButton.two())
-                self.bind(B2CLICK, self.commandFunc)
-            elif i == 3:
-                self.guiItem.addClickButton(MouseButton.three())
-                self.bind(B3CLICK, self.commandFunc)
+            self.unbind(B3CLICK)
+            self.guiItem.removeClickButton(MouseButton.three())
 
     def commandFunc(self, event):
         if self['command']:
             # Pass any extra args to command
             apply(self['command'], self['extraArgs'])
             
-    def destroy(self):
-        DirectGuiBase.destroy(self)
-        # Get rid of node path
-        self.removeNode()

+ 171 - 2
direct/src/gui/DirectGuiBase.py

@@ -90,6 +90,8 @@ class DirectGuiBase(PandaObject):
         self.guiId = 'guiObject'
         # List of all active hooks
         self.hookDict = {}
+        # To avoid doing things redundantly during initialisation
+        self.fInit = 1
 	# Mapping from each megawidget option to a list of information
 	# about the option
 	#   - default value
@@ -218,10 +220,12 @@ class DirectGuiBase(PandaObject):
 	if self.__class__ is myClass:
 	    # Call the configuration callback function for every option.
 	    FUNCTION = _OPT_FUNCTION
+            self.fInit = 1
 	    for info in self._optionInfo.values():
 		func = info[FUNCTION]
 		if func is not None and func is not INITOPT:
 		    func()
+            self.fInit = 0
 
             # Now check if anything is left over
 	    unusedOptions = []
@@ -243,6 +247,11 @@ class DirectGuiBase(PandaObject):
 		    text = 'Unknown options "'
 		raise KeyError, text + string.join(unusedOptions, ', ') + \
 			'" for ' + myClass.__name__
+            # Can now call post init func
+            self.postInitialiseFunc()
+
+    def postInitialiseFunc(self):
+        pass
                     
     def isinitoption(self, option):
         """
@@ -609,6 +618,166 @@ class DirectGuiBase(PandaObject):
             del(self.hookDict[gEvent])
 
 
-class DirectGuiWidget(DirectGuiBase):
-    pass
+class DirectGuiWidget(DirectGuiBase, NodePath):
+    def __init__(self, parent = guiTop, **kw):
+        # Direct gui widgets are node paths
+        # Direct gui widgets have:
+        # -  stateNodePaths (to hold visible representation of widget)
+        # State node paths can have:
+        # -  a frame of type (None, FLAT, RAISED, GROOVE, RIDGE)
+        # -  arbitrary geometry for each state
+        # They inherit from DirectGuiWidget
+        # -  Can create components (with aliases and groups)
+        # -  Can bind to mouse events
+        # They inherit from NodePath
+        # -  Can position/scale them
+        optiondefs = (
+            # Widget's constructor
+            ('pgFunc',          PGItem,     None),
+            ('numStates',       1,          None),
+            ('invertedFrames',  (),         None),
+            # Widget's initial state
+            ('state',           NORMAL,     self.setState),
+            # Widget's frame characteristics
+            ('relief',          FLAT,       self.setRelief),
+            ('borderWidth',     (.1,.1),    self.setBorderWidth),
+            ('frameSize',       None,       self.setFrameSize),
+            ('frameColor',      (1,1,1,1),  self.setFrameColor),
+            ('pad',             (.25,.15),  self.resetFrameSize),
+            # Initial pos/scale of the widget
+            ('pos',             None,       INITOPT),
+            ('scale',           None,       INITOPT),
+            )
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize the base classes (after defining the options).
+        DirectGuiBase.__init__(self)
+        NodePath.__init__(self)
+        # Create a button
+        self.guiItem = self['pgFunc']()
+        self.guiId = self.guiItem.getId()
+        # Attach button to parent and make that self
+        self.assign(parent.attachNewNode( self.guiItem ) )
+        # Update pose to initial values
+        if self['pos']:
+            pos = self['pos']
+            # Can either be a Point3 or a tuple of 3 values
+            if isinstance(pos, Point3):
+                self.setPos(pos)
+            else:
+                apply(self.setPos, pos)
+        if self['scale']:
+            scale = self['scale']
+            # Can either be a Vec3 or a tuple of 3 values
+            if (isinstance(scale, Vec3) or
+                (type(scale) == types.IntType) or
+                (type(scale) == types.FloatType)):
+                self.setScale(scale)
+            else:
+                apply(self.setScale, self['scale'])
+        # Initialize names
+        self.guiItem.setName(self.guiId)
+        self.setName(self.guiId + 'NodePath')
+        # Create
+        self.stateNodePath = []
+        for i in range(self['numStates']):
+            self.stateNodePath.append(NodePath(self.guiItem.getStateDef(i)))
+        # Initialize frame style
+        self.frameStyle = []
+        for i in range(self['numStates']):
+            self.frameStyle.append(PGFrameStyle())
+        # For holding bounds info
+        self.ll = Point3(0)
+        self.ur = Point3(0)
+        # Call option initialization functions
+        self.initialiseoptions(DirectGuiWidget)
+
+    def postInitialiseFunc(self):
+        # Now allow changes to take effect
+        self.updateFrameStyle()
+        if not self['frameSize']:
+            self.setFrameSize()
+
+    def setState(self):
+        if type(self['state']) == type(0):
+            self.guiItem.setActive(self['state'])
+        elif (self['state'] == NORMAL) or (self['state'] == 'normal'):
+            self.guiItem.setActive(1)
+        else:
+            self.guiItem.setActive(0)
+
+    def resetFrameSize(self):
+        if not self.fInit:
+            self.setFrameSize(fClearFrame = 1)
+        
+    def setFrameSize(self, fClearFrame = 0):
+        if self['frameSize']:
+            # Use user specified bounds
+            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])
+                # To force an update of the button
+                self.guiItem.getStateDef(0)
+            # Clear out frame before computing bounds
+            self.stateNodePath[0].calcTightBounds(self.ll, self.ur)
+            # Scale bounds to give a pad around graphics
+            bounds = (self.ll[0] - self['pad'][0],
+                      self.ur[0] + self['pad'][0],
+                      self.ll[2] - self['pad'][1],
+                      self.ur[2] + self['pad'][1])
+            # Restore frame style if necessary
+            if (frameType != PGFrameStyle.TNone):
+                self.frameStyle[0].setType(frameType)
+                self.guiItem.setFrameStyle(0, self.frameStyle[0])
+        # Set frame to new dimensions
+        self.guiItem.setFrame(bounds[0], bounds[1],bounds[2], bounds[3])
+
+    def updateFrameStyle(self):
+        if not self.fInit:
+            for i in range(self['numStates']):
+                self.guiItem.setFrameStyle(i, self.frameStyle[i])
+
+    def setRelief(self, fSetStyle = 1):
+        relief = self['relief']
+        if relief == None:
+            for i in range(self['numStates']):
+                self.frameStyle[i].setType(PGFrameStyle.TNone)
+        elif (relief == FLAT) or (relief == 'flat'):
+            for i in range(self['numStates']):
+                self.frameStyle[i].setType(FLAT)
+        elif (relief == RAISED) or (relief == 'raised'):
+            for i in range(self['numStates']):
+                if i in self['invertedFrames']:
+                    self.frameStyle[1].setType(SUNKEN)
+                else:
+                    self.frameStyle[i].setType(RAISED)
+        elif (relief == SUNKEN) or (relief == 'sunken'):
+            for i in range(self['numStates']):
+                if i in self['invertedFrames']:
+                    self.frameStyle[1].setType(RAISED)
+                else:
+                    self.frameStyle[i].setType(SUNKEN)
+        self.updateFrameStyle()
+
+    def setFrameColor(self):
+        color = self['frameColor']
+        for i in range(self['numStates']):
+            self.frameStyle[i].setColor(color[0], color[1], color[2], color[3])
+        self.updateFrameStyle()
+
+    def setBorderWidth(self):
+        width = self['borderWidth']
+        for i in range(self['numStates']):
+            self.frameStyle[i].setWidth(width[0], width[1])
+        self.updateFrameStyle()
+            
+    def destroy(self):
+        DirectGuiBase.destroy(self)
+        # Get rid of node path
+        self.removeNode()
 

+ 13 - 3
direct/src/gui/DirectGuiGlobals.py

@@ -16,6 +16,14 @@ import OnscreenImage
 import types
 
 # USEFUL GUI CONSTANTS
+# Constant used to indicate that an option can only be set by a call
+# to the constructor.
+INITOPT = ['initopt']
+
+# Mouse buttons
+LMB = 0
+MMB = 1
+RMB = 2
 
 # Widget state
 NORMAL = 'normal'
@@ -56,6 +64,8 @@ B1RELEASE = getGenericMouseEvent('release', 1)
 B2RELEASE = getGenericMouseEvent('release', 2)
 B3RELEASE = getGenericMouseEvent('release', 3)
 
-# Constant used to indicate that an option can only be set by a call
-# to the constructor.
-INITOPT = ['initopt']
+# For setting the sorting order of a widget's visible components
+IMAGE_SORT_INDEX = 10
+GEOM_SORT_INDEX = 20
+TEXT_SORT_INDEX = 30
+

+ 37 - 34
direct/src/gui/DirectGuiTest.py

@@ -5,41 +5,22 @@ from whrandom import *
 # Load a model
 smiley = loader.loadModel('models/directmodels/smiley')
 
-# Create a button with a background image, smiley as a geometry element,
-# and a text overlay, set a different text for the four button states:
-# (normal, press, rollover, and disabled), set scale = .15, and relief raised
-db = DirectButton(image = 'phase_4/maps/middayskyB.jpg',
-                  geom = smiley,
-                  text = ('Hi!', 'Ouch!', 'Bye!', 'ZZZZ!'),
-                  scale = .15, relief = 'raised',
-                  # Here we set an option for a component of the button
-                  geom1_color = Vec4(1,0,0,1),
-                  # Here is an example of a component group option
-                  text_pos = (.6, -.8))
-
-# You can set component or component group options after a gui item
-# has been created
-db['text_scale'] = 0.5
-
 # Here we specify the button's command
-def dummyCmd():
-    print 'POW!!!!'
-
-db['command'] = dummyCmd
-
-# Get a handle on the geometry for the rollover state
-rolloverSmiley = db.component('geom2')
+def dummyCmd(index):
+    print 'Button %d POW!!!!' % index
 
 # Define some commands to bind to enter, exit and click events
-def shrink(event):
+def shrink(db):
     db['text2_text'] = 'Hi!'
     taskMgr.removeTasksNamed('shrink')
     taskMgr.removeTasksNamed('expand')
+    # Get a handle on the geometry for the rollover state
+    rolloverSmiley = db.component('geom2')
     rolloverSmiley.setScale(db.component('geom0').getScale()[0])
     rolloverSmiley.lerpScale(.1,.1,.1, 1.0, blendType = 'easeInOut',
                              task = 'shrink')
 
-def expand(event):
+def expand(db):
     db['text0_text'] = 'Bye!'
     taskMgr.removeTasksNamed('shrink')
     taskMgr.removeTasksNamed('expand')
@@ -48,7 +29,7 @@ def expand(event):
                              task = 'expand')
     db.component('geom2').clearColor()
 
-def ouch(event):
+def ouch(db):
     taskMgr.removeTasksNamed('shrink')
     taskMgr.removeTasksNamed('expand')
     taskMgr.removeTasksNamed('runAway')
@@ -61,12 +42,34 @@ def ouch(event):
     db.lerpPos(Point3(newX, 0, newZ), 1.0, task = 'runAway',
                blendType = 'easeOut')
 
-# Bind the commands
-db.bind(ENTER, shrink)
-db.bind(EXIT, expand)
-db.bind(B1PRESS, ouch)
-# Pop up placer when button 2 is pressed
-db.bind(B2PRESS, lambda x, s = db: s.place())
+dl = DirectLabel(image = 'phase_4/maps/middayskyB.jpg')
+dl.setScale(.5)
+
+# Create a button with a background image, smiley as a geometry element,
+# and a text overlay, set a different text for the four button states:
+# (normal, press, rollover, and disabled), set scale = .15, and relief raised
+for i in range(10):
+    db = DirectButton(parent = dl,
+                      image = 'phase_4/maps/middayskyB.jpg',
+                      geom = smiley,
+                      text = ('Hi!', 'Ouch!', 'Bye!', 'ZZZZ!'),
+                      scale = .15, relief = 'raised',
+                      # Here we set an option for a component of the button
+                      geom1_color = Vec4(1,0,0,1),
+                      # Here is an example of a component group option
+                      text_pos = (.6, -.8))
+
+    # You can set component or component group options after a gui item
+    # has been created
+    db['text_scale'] = 0.5
+    db['command'] = lambda i = i: dummyCmd(i)
 
-# To get rid of button and clear out hooks call:
-# db.destroy()
+    # Bind the commands
+    db.bind(ENTER, lambda x, db = db: shrink(db))
+    db.bind(EXIT, lambda x, db = db: expand(db))
+    db.bind(B1PRESS, lambda x, db = db: ouch(db))
+    # Pop up placer when button 2 is pressed
+    db.bind(B2PRESS, lambda x, db = db: db.place())
+    
+    # To get rid of button and clear out hooks call:
+    # db.destroy()

+ 147 - 43
direct/src/gui/DirectLabel.py

@@ -1,52 +1,156 @@
 from DirectGuiBase import *
 
-class DirectLabel(DirectGuiBase, PGItem):
-    def __init__(self, parent = None, **kw):
-        # Pass in a background texture, and/or a geometry object,
-        # and/or a text string to be used as the visible
-        # representation of the label
+class DirectLabel(DirectGuiWidget):
+    def __init__(self, parent = guiTop, **kw):
+        # Inherits from DirectGuiWidget
+        # A Direct Label can have:
+        # - A background texture (pass in path to image, or Texture Card)
+        # - A midground geometry item (pass in geometry)
+        # - A foreground text Node (pass in text string or Onscreen Text)
+        # Each of these has 1 or more states
+        # The same object can be used for all states or each
+        # state can have a different text/geom/image (for radio button
+        # and check button indicators, for example
         optiondefs = (
-            ('image',         None,       self.setImage),
-            ('geom',          None,       self.setGeom),
-            ('text',          None,       self.setText),
-            ('pos',           (0,0,0),    self.setPos),
-            ('scale',         (1,1,1),    self.setScale),
-            ('bounds',        None,       self.setBounds),
-            ('imagePos',      (0,0,0),    self.setImagePos),
-            ('imageScale',    (1,1,1),    self.setImagePos),
-            ('geomPos',       (0,0,0),    self.setGeomPos),
-            ('geomScale',     (1,1,1),    self.setGeomPos),
-            ('textPos',       (0,0,0),    self.setTextPos),
-            ('textScale',     (1,1,1),    self.setTextPos),
+            # Define type of DirectGuiWidget
+            ('pgFunc',          PGItem,     None),
+            ('numStates',       1,          None),
+            # Label can have:
+            # A background texture
+            ('image',           None,       self.setImage),
+            # A midground geometry item
+            ('geom',            None,       self.setGeom),
+            # A foreground text node
+            ('text',            None,       self.setText),
             )
-        apply(DirectGuiBase.__init__, (self, optiondefs, ()), kw)            
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs,
+                           dynamicGroups = ('text', 'geom', 'image'))
+
+        # Initialize superclasses
+        DirectGuiWidget.__init__(self, parent)
+        
+        # Call option initialization functions
         self.initialiseoptions(DirectLabel)
 
-    def setImage(self):
-        pass
-    def setGeom(self):
-        pass
     def setText(self):
-        pass
-    def setPos(self):
-        pass
-    def setScale(self):
-        pass
-    def setBounds(self):
-        pass
-    def setImagePos(self):
-        pass
-    def setImagePos(self):
-        pass
-    def setGeomPos(self):
-        pass
-    def setGeomPos(self):
-        pass
-    def setTextPos(self):
-        pass
-    def setTextPos(self):
-        pass
-    def setState(self):
-        pass
+        # Determine if user passed in single string or a sequence
+        if self['text'] == None:
+            textList = (None,) * self['numStates']
+        elif type(self['text']) == types.StringType:
+            # If just passing in a single string, make a tuple out of it
+            textList = (self['text'],) * self['numStates']
+        else:
+            # Otherwise, hope that the user has passed in a tuple/list
+            textList = self['text']
+        # Create/destroy components
+        for i in range(self['numStates']):
+            component = 'text' + `i`
+            # If fewer items specified than numStates,
+            # just repeat last item
+            try:
+                text = textList[i]
+            except IndexError:
+                text = textList[-1]
+                
+            if self.hascomponent(component):
+                if text == None:
+                    # Destroy component
+                    self.destroycomponent(component)
+                else:
+                    self[component + '_text'] = text
+            else:
+                if text == None:
+                    return
+                else:
+                    self.createcomponent(
+                        component, (), 'text',
+                        OnscreenText.OnscreenText,
+                        (), parent = self.stateNodePath[i],
+                        text = text, scale = 1,
+                        sort = TEXT_SORT_INDEX,
+                        mayChange = 1)
 
+    def setGeom(self):
+        # Determine argument type
+        if self['geom'] == None:
+            # Passed in None
+            geomList = (None,) * self['numStates']
+        elif isinstance(self['geom'], NodePath):
+            # Passed in a single node path, make a tuple out of it
+            geomList = (self['geom'],) * self['numStates']
+        else:
+            # Otherwise, hope that the user has passed in a tuple/list
+            geomList = self['geom']
+        # Create/destroy components
+        for i in range(self['numStates']):
+            component = 'geom' + `i`
+            # If fewer items specified than numStates,
+            # just repeat last item
+            try:
+                geom = geomList[i]
+            except IndexError:
+                geom = geomList[-1]
+                
+            if self.hascomponent(component):
+                if geom == None:
+                    # Destroy component
+                    self.destroycomponent(component)
+                else:
+                    self[component + '_geom'] = geom
+            else:
+                if geom == None:
+                    return
+                else:
+                    self.createcomponent(
+                        component, (), 'geom',
+                        OnscreenGeom.OnscreenGeom,
+                        (), parent = self.stateNodePath[i],
+                        geom = geom, scale = 1,
+                        sort = GEOM_SORT_INDEX)
 
+    def setImage(self):
+        # Determine argument type
+        arg = self['image']
+        if arg == None:
+            # Passed in None
+            imageList = (None,) * self['numStates']
+        elif type(arg) == types.StringType:
+            # Passed in a single node path, make a tuple out of it
+            imageList = (arg,) * self['numStates']
+        else:
+            # Otherwise, hope that the user has passed in a tuple/list
+            if ((len(arg) == 2) and
+                (type(arg[0]) == types.StringType) and
+                (type(arg[1]) == types.StringType)):
+                # Its a model/node pair of strings
+                image = (arg,) * self['numStates']
+            else:
+                # Assume its a list of node paths
+                imageList = arg
+        # Create/destroy components
+        for i in range(self['numStates']):
+            component = 'image' + `i`
+            # If fewer items specified than numStates,
+            # just repeat last item
+            try:
+                image = imageList[i]
+            except IndexError:
+                image = imageList[-1]
+                
+            if self.hascomponent(component):
+                if image == None:
+                    # Destroy component
+                    self.destroycomponent(component)
+                else:
+                    self[component + '_image'] = image
+            else:
+                if image == None:
+                    return
+                else:
+                    self.createcomponent(
+                        component, (), 'image',
+                        OnscreenImage.OnscreenImage,
+                        (), parent = self.stateNodePath[i],
+                        image = image, scale = 1,
+                        sort = IMAGE_SORT_INDEX)

+ 2 - 0
direct/src/gui/OnscreenGeom.py

@@ -119,3 +119,5 @@ class OnscreenGeom(PandaObject, NodePath):
     # Allow index style refererences
     __getitem__ = cget
     
+    def destroy(self):
+        self.removeNode()

+ 2 - 1
direct/src/gui/OnscreenImage.py

@@ -138,4 +138,5 @@ class OnscreenImage(PandaObject, NodePath):
     # Allow index style refererences
     __getitem__ = cget
     
-
+    def destroy(self):
+        self.removeNode()

+ 3 - 0
direct/src/gui/OnscreenText.py

@@ -216,6 +216,9 @@ class OnscreenText(PandaObject, NodePath):
             if self.hasArcs():
                 self.removeNode()
 
+    def destroy(self):
+        self.cleanup()
+
     def freeze(self):
         self.textNode.freeze()