Browse Source

PGItemNotify interface, PGScrollFrame, and more general PGSliderBar

David Rose 20 years ago
parent
commit
1e81e656ab
88 changed files with 5379 additions and 942 deletions
  1. 1 0
      direct/src/gui/DirectButton.py
  2. 3 1
      direct/src/gui/DirectGui.py
  3. 16 5
      direct/src/gui/DirectGuiBase.py
  4. 6 0
      direct/src/gui/DirectGuiGlobals.py
  5. 123 0
      direct/src/gui/DirectScrollBar.py
  6. 95 0
      direct/src/gui/DirectScrolledFrame.py
  7. 113 0
      direct/src/gui/DirectSlider.py
  8. 0 147
      direct/src/gui/DirectSliderBar.py
  9. 1 5
      panda/src/collide/collisionEntry.cxx
  10. 2 3
      panda/src/collide/collisionPlane.cxx
  11. 14 10
      panda/src/collide/collisionPolygon.cxx
  12. 3 1
      panda/src/collide/collisionVisualizer.cxx
  13. 9 3
      panda/src/mathutil/Sources.pp
  14. 22 17
      panda/src/mathutil/boundingPlane.I
  15. 153 0
      panda/src/mathutil/boundingPlane.cxx
  16. 86 0
      panda/src/mathutil/boundingPlane.h
  17. 47 0
      panda/src/mathutil/boundingVolume.cxx
  18. 6 0
      panda/src/mathutil/boundingVolume.h
  19. 2 0
      panda/src/mathutil/config_mathutil.cxx
  20. 1 0
      panda/src/mathutil/mathutil_composite1.cxx
  21. 21 1
      panda/src/mathutil/plane_src.I
  22. 1 1
      panda/src/mathutil/plane_src.cxx
  23. 8 6
      panda/src/mathutil/plane_src.h
  24. 3 0
      panda/src/pgraph/Sources.pp
  25. 77 0
      panda/src/pgraph/clipPlaneAttrib.cxx
  26. 1 0
      panda/src/pgraph/clipPlaneAttrib.h
  27. 68 0
      panda/src/pgraph/cullPlanes.I
  28. 218 0
      panda/src/pgraph/cullPlanes.cxx
  29. 77 0
      panda/src/pgraph/cullPlanes.h
  30. 1 2
      panda/src/pgraph/cullTraverser.cxx
  31. 36 6
      panda/src/pgraph/cullTraverserData.I
  32. 124 37
      panda/src/pgraph/cullTraverserData.cxx
  33. 9 2
      panda/src/pgraph/cullTraverserData.h
  34. 30 1
      panda/src/pgraph/modelNode.I
  35. 21 0
      panda/src/pgraph/modelNode.cxx
  36. 5 0
      panda/src/pgraph/modelNode.h
  37. 63 11
      panda/src/pgraph/pandaNode.I
  38. 31 0
      panda/src/pgraph/pandaNode.cxx
  39. 10 0
      panda/src/pgraph/pandaNode.h
  40. 1 0
      panda/src/pgraph/pgraph_composite2.cxx
  41. 2 1
      panda/src/pgraph/planeNode.cxx
  42. 1 1
      panda/src/pgraph/planeNode.h
  43. 19 0
      panda/src/pgraph/renderState.I
  44. 16 0
      panda/src/pgraph/renderState.cxx
  45. 5 0
      panda/src/pgraph/renderState.h
  46. 21 9
      panda/src/pgui/Sources.pp
  47. 18 4
      panda/src/pgui/config_pgui.cxx
  48. 3 0
      panda/src/pgui/config_pgui.h
  49. 26 0
      panda/src/pgui/pgButton.I
  50. 5 13
      panda/src/pgui/pgButton.cxx
  51. 7 4
      panda/src/pgui/pgButton.h
  52. 27 0
      panda/src/pgui/pgButtonNotify.I
  53. 29 0
      panda/src/pgui/pgButtonNotify.cxx
  54. 45 0
      panda/src/pgui/pgButtonNotify.h
  55. 46 1
      panda/src/pgui/pgFrameStyle.I
  56. 49 5
      panda/src/pgui/pgFrameStyle.cxx
  57. 7 0
      panda/src/pgui/pgFrameStyle.h
  58. 81 5
      panda/src/pgui/pgItem.I
  59. 378 41
      panda/src/pgui/pgItem.cxx
  60. 30 3
      panda/src/pgui/pgItem.h
  61. 27 0
      panda/src/pgui/pgItemNotify.I
  62. 202 0
      panda/src/pgui/pgItemNotify.cxx
  63. 67 0
      panda/src/pgui/pgItemNotify.h
  64. 245 0
      panda/src/pgui/pgScrollFrame.I
  65. 445 0
      panda/src/pgui/pgScrollFrame.cxx
  66. 137 0
      panda/src/pgui/pgScrollFrame.h
  67. 276 122
      panda/src/pgui/pgSliderBar.I
  68. 664 149
      panda/src/pgui/pgSliderBar.cxx
  69. 95 51
      panda/src/pgui/pgSliderBar.h
  70. 27 0
      panda/src/pgui/pgSliderBarNotify.I
  71. 40 0
      panda/src/pgui/pgSliderBarNotify.cxx
  72. 46 0
      panda/src/pgui/pgSliderBarNotify.h
  73. 0 94
      panda/src/pgui/pgSliderButton.cxx
  74. 0 77
      panda/src/pgui/pgSliderButton.h
  75. 16 7
      panda/src/pgui/pgTop.I
  76. 2 1
      panda/src/pgui/pgTop.h
  77. 104 0
      panda/src/pgui/pgVirtualFrame.I
  78. 241 0
      panda/src/pgui/pgVirtualFrame.cxx
  79. 112 0
      panda/src/pgui/pgVirtualFrame.h
  80. 4 2
      panda/src/pgui/pgWaitBar.cxx
  81. 4 3
      panda/src/pgui/pgui_composite1.cxx
  82. 4 4
      panda/src/pgui/pgui_composite2.cxx
  83. 2 1
      panda/src/putil/bam.h
  84. 1 1
      panda/src/tform/Sources.pp
  85. 72 60
      panda/src/tform/mouseWatcher.cxx
  86. 6 4
      panda/src/tform/mouseWatcher.h
  87. 189 19
      panda/src/tform/mouseWatcherGroup.cxx
  88. 28 1
      panda/src/tform/mouseWatcherGroup.h

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

@@ -28,6 +28,7 @@ class DirectButton(DirectFrame):
             ('pgFunc',         PGButton,   None),
             ('numStates',      4,          None),
             ('state',          NORMAL,     None),
+            ('relief',         RAISED,     None),
             ('invertedFrames', (1,),       None),
             # Command to be called on button click
             ('command',        None,       None),

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

@@ -17,6 +17,8 @@ from DirectLabel import *
 from DirectScrolledList import *
 from DirectDialog import *
 from DirectWaitBar import *
-from DirectSliderBar import *
+from DirectSlider import *
+from DirectScrollBar import *
+from DirectScrolledFrame import *
 from DirectCheckButton import *
 from DirectOptionMenu import *

+ 16 - 5
direct/src/gui/DirectGuiBase.py

@@ -685,6 +685,7 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             ('frameSize',      None,         self.setFrameSize),
             ('frameColor',     (.8,.8,.8,1), self.setFrameColor),
             ('frameTexture',   None,         self.setFrameTexture),
+            ('frameVisibleScale', (1,1),     self.setFrameVisibleScale),
             ('pad',            (0,0),        self.resetFrameSize),
             # Override button id (beware! your name may not be unique!)
             ('guiId',          None,         INITOPT),
@@ -866,6 +867,8 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
             # Use user specified bounds
             self.bounds = self['frameSize']
             #print "%s bounds = %s" % (self.getName(),self.bounds)            
+            bw = (0,0)
+
         else:
             if fClearFrame and (frameType != PGFrameStyle.TNone):
                 self.frameStyle[0].setType(PGFrameStyle.TNone)
@@ -878,11 +881,13 @@ 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)
+
+            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],
@@ -974,6 +979,12 @@ class DirectGuiWidget(DirectGuiBase, NodePath):
                 self.frameStyle[i].setTexture(texture)
         self.updateFrameStyle()
 
+    def setFrameVisibleScale(self):
+        scale = self['frameVisibleScale']
+        for i in range(self['numStates']):
+            self.frameStyle[i].setVisibleScale(scale[0], scale[1])
+        self.updateFrameStyle()
+
     def setBorderWidth(self):
         width = self['borderWidth']
         for i in range(self['numStates']):

+ 6 - 0
direct/src/gui/DirectGuiGlobals.py

@@ -29,6 +29,10 @@ FrameStyleDict = {'flat' : FLAT, 'raised' : RAISED, 'sunken': SUNKEN,
                   'groove' : GROOVE, 'ridge' : RIDGE 
                   }
 
+# Orientation of DirectSlider and DirectScrollBar
+HORIZONTAL = 'horizontal'
+VERTICAL = 'vertical'
+
 # Dialog button values
 DIALOG_NO = 0
 DIALOG_OK = DIALOG_YES = DIALOG_RETRY = 1
@@ -55,6 +59,8 @@ OVERFLOW = PGEntry.getOverflowPrefix()
 ACCEPT = PGEntry.getAcceptPrefix() + KeyboardButton.enter().getName() + '-'
 TYPE = PGEntry.getTypePrefix()
 ERASE = PGEntry.getErasePrefix()
+# For DirectSlider and DirectScrollBar widgets
+ADJUST = PGSliderBar.getAdjustPrefix()
 
 
 # For setting the sorting order of a widget's visible components

+ 123 - 0
direct/src/gui/DirectScrollBar.py

@@ -0,0 +1,123 @@
+from DirectFrame import *
+from DirectButton import *
+from DirectGuiBase import _OPT_VALUE
+
+"""
+import DirectScrollBar
+d = DirectScrollBar(borderWidth=(0,0))
+
+"""
+
+class DirectScrollBar(DirectFrame):
+    """
+    DirectScrollBar -- a widget which represents a scroll bar the user can
+    use for paging through a large document or panel.
+    """
+    def __init__(self, parent = None, **kw):
+        optiondefs = (
+            # Define type of DirectGuiWidget
+            ('pgFunc',         PGSliderBar,        None),
+            ('state',          NORMAL,             None),
+            ('frameColor',     (0.6,0.6,0.6,1),    None),
+
+            ('range',          (0, 1),             self.setRange),
+            ('value',          0,                  self.__setValue),
+            ('pageSize',       0.1,                self.setPageSize),
+            ('orientation',    HORIZONTAL,         self.setOrientation),
+            ('manageButtons',  1,                  self.setManageButtons),
+            ('resizeThumb',    1,                  self.setResizeThumb),
+
+            # Function to be called repeatedly as the bar is scrolled
+            ('command',        None,               None),
+            ('extraArgs',      [],                 None),
+            )
+
+        if kw.get('orientation') == VERTICAL:
+            # These are the default options for a vertical layout.
+            optiondefs += (
+                ('frameSize',      (-0.04,0.04,-0.5,0.5),   None),
+                )
+        else:
+            # These are the default options for a horizontal layout.
+            optiondefs += (
+                ('frameSize',      (-0.5,0.5,-0.04,0.04),  None),
+                )
+
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize superclasses
+        DirectFrame.__init__(self, parent)
+
+        self.thumb = self.createcomponent(
+            "thumb", (), None,
+            DirectButton, (self,),
+            borderWidth = self['borderWidth'])
+        self.incButton = self.createcomponent(
+            "incButton", (), None,
+            DirectButton, (self,),
+            borderWidth = self['borderWidth'])
+        self.decButton = self.createcomponent(
+            "decButton", (), None,
+            DirectButton, (self,),
+            borderWidth = self['borderWidth'])
+
+        self.guiItem.setThumbButton(self.thumb.guiItem)
+        self.guiItem.setLeftButton(self.decButton.guiItem)
+        self.guiItem.setRightButton(self.incButton.guiItem)
+
+        # Bind command function
+        self.bind(ADJUST, self.commandFunc)
+
+        # Call option initialization functions
+        self.initialiseoptions(DirectScrollBar)
+        
+    def setRange(self):
+        # Try to preserve the value across a setRange call.
+        v = self['value']
+        r = self['range']
+        self.guiItem.setRange(r[0], r[1])
+        self['value'] = v
+
+    def __setValue(self):
+        # This is the internal function that is called when
+        # self['value'] is directly assigned.
+        self.guiItem.setValue(self['value'])
+
+    def setValue(self, value):
+        # This is the public function that is meant to be called by a
+        # user that doesn't like to use (or doesn't understand) the
+        # preferred interface of self['value'].
+        self['value'] = value
+
+    def getValue(self):
+        return self.guiItem.getValue()
+
+    def getRatio(self):
+        return self.guiItem.getRatio()
+
+    def setPageSize(self):
+        self.guiItem.setPageSize(self['pageSize'])
+
+    def setOrientation(self):
+        if self['orientation'] == HORIZONTAL:
+            self.guiItem.setAxis(Vec3(1, 0, 0))
+        else:
+            self.guiItem.setAxis(Vec3(0, 0, -1))
+
+    def setManageButtons(self):
+        self.guiItem.setManagePieces(self['manageButtons'])
+
+    def setResizeThumb(self):
+        self.guiItem.setResizeThumb(self['resizeThumb'])
+
+    def destroy(self):
+        DirectFrame.destroy(self)
+
+    def commandFunc(self):
+        # Store the updated value in self['value']
+        self._optionInfo['value'][_OPT_VALUE] = self.guiItem.getValue()
+
+        if self['command']:
+            apply(self['command'], self['extraArgs'])
+            

+ 95 - 0
direct/src/gui/DirectScrolledFrame.py

@@ -0,0 +1,95 @@
+from DirectFrame import *
+from DirectScrollBar import *
+
+"""
+import DirectScrolledFrame
+d = DirectScrolledFrame(borderWidth=(0,0))
+
+"""
+
+class DirectScrolledFrame(DirectFrame):
+    """
+    DirectScrolledFrame -- a special frame that uses DirectScrollBar to
+    implement a small window (the frameSize) into a potentially much
+    larger virtual canvas (the canvasSize, scrolledFrame.getCanvas()).
+
+    Unless specified otherwise, scroll bars are automatically created
+    and managed as needed, based on the relative sizes od the
+    frameSize and the canvasSize.  You can also set manageScrollBars =
+    0 and explicitly position and hide or show the scroll bars
+    yourself.
+    """
+    def __init__(self, parent = None, **kw):
+        optiondefs = (
+            # Define type of DirectGuiWidget
+            ('pgFunc',         PGScrollFrame,      None),
+            ('frameSize',      (-0.5,0.5,-0.5,0.5), None),
+             
+            ('canvasSize',     (-1,1,-1,1),        self.setCanvasSize),
+            ('manageScrollBars', 1,                self.setManageScrollBars),
+            ('autoHideScrollBars', 1,              self.setAutoHideScrollBars),
+            ('scrollBarWidth', 0.08,               None),
+            )
+
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize superclasses
+        DirectFrame.__init__(self, parent)
+
+        # The scrollBarWidth parameter is just used at scroll bar
+        # construction time, and supplies a default frame.  It does
+        # not override an explicit frame specified on the scroll bar.
+        # If you want to change the frame width after construction,
+        # you must specify their frameSize tuples explicitly.
+        w = self['scrollBarWidth']
+
+        self.verticalScroll = self.createcomponent(
+            "verticalScroll", (), None,
+            DirectScrollBar, (self,),
+            borderWidth = self['borderWidth'],
+            frameSize = (-w / 2.0, w / 2.0, -1, 1),
+            orientation = VERTICAL)
+
+        self.horizontalScroll = self.createcomponent(
+            "horizontalScroll", (), None,
+            DirectScrollBar, (self,),
+            borderWidth = self['borderWidth'],
+            frameSize = (-1, 1, -w / 2.0, w / 2.0),
+            orientation = HORIZONTAL)
+
+        self.guiItem.setVerticalSlider(self.verticalScroll.guiItem)
+        self.guiItem.setHorizontalSlider(self.horizontalScroll.guiItem)
+
+        self.canvas = NodePath(self.guiItem.getCanvasNode())
+
+        # Call option initialization functions
+        self.initialiseoptions(DirectScrolledFrame)
+
+    def setCanvasSize(self):
+        f = self['canvasSize']
+        self.guiItem.setVirtualFrame(f[0], f[1], f[2], f[3])
+
+    def getCanvas(self):
+        return self.canvas
+
+    def setManageScrollBars(self):
+        self.guiItem.setManagePieces(self['manageScrollBars'])
+
+    def setAutoHideScrollBars(self):
+        self.guiItem.setAutoHide(self['autoHideScrollBars'])
+
+    def destroy(self):
+        DirectFrame.destroy(self)
+
+    def commandFunc(self):
+        if self['command']:
+            apply(self['command'], self['extraArgs'])
+            
+    def destroy(self):
+        # Destroy children of the canvas
+        for child in self.canvas.getChildrenAsList():
+            childGui = self.guiDict.get(child.getName())
+            if childGui: childGui.destroy()
+        DirectFrame.DirectFrame.destroy(self)
+        

+ 113 - 0
direct/src/gui/DirectSlider.py

@@ -0,0 +1,113 @@
+from DirectFrame import *
+from DirectButton import *
+from DirectGuiBase import _OPT_VALUE
+
+"""
+import DirectSlider
+d = DirectSlider(borderWidth=(0,0))
+
+"""
+
+class DirectSlider(DirectFrame):
+    """
+    DirectSlider -- a widget which represents a slider that the
+    user can pull left and right to represent a continuous value.
+    """
+    def __init__(self, parent = None, **kw):
+        optiondefs = (
+            # Define type of DirectGuiWidget
+            ('pgFunc',         PGSliderBar,        None),
+            ('state',          NORMAL,             None),
+            ('frameColor',     (0.6,0.6,0.6,1),    None),
+
+            ('range',          (0, 1),             self.setRange),
+            ('value',          0,                  self.__setValue),
+            ('pageSize',       0.1,                self.setPageSize),
+            ('orientation',    HORIZONTAL,         self.setOrientation),
+
+            # Function to be called repeatedly as slider is moved
+            ('command',        None,               None),
+            ('extraArgs',      [],                 None),
+            )
+
+        if kw.get('orientation') == VERTICAL:
+            # These are the default options for a vertical layout.
+            optiondefs += (
+                ('frameSize',      (-0.08,0.08,-1,1),   None),
+                ('frameVisibleScale', (0.25,1),         None),
+                )
+        else:
+            # These are the default options for a horizontal layout.
+            optiondefs += (
+                ('frameSize',      (-1,1,-0.08,0.08),  None),
+                ('frameVisibleScale', (1,0.25),        None),
+                )
+
+        # Merge keyword options with default options
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize superclasses
+        DirectFrame.__init__(self, parent)
+
+        self.thumb = self.createcomponent("thumb", (), None,
+                                          DirectButton, (self,),
+                                          borderWidth = self['borderWidth'])
+        if self.thumb['frameSize'] == None and \
+           self.thumb.bounds == [0.0, 0.0, 0.0, 0.0]:
+            # Compute a default frameSize for the thumb.
+            f = self['frameSize']
+            if self['orientation'] == HORIZONTAL:
+                self.thumb['frameSize'] = (f[0]*0.05,f[1]*0.05,f[2],f[3])
+            else:
+                self.thumb['frameSize'] = (f[0],f[1],f[2]*0.05,f[3]*0.05)
+
+        self.guiItem.setThumbButton(self.thumb.guiItem)
+
+        # Bind command function
+        self.bind(ADJUST, self.commandFunc)
+
+        # Call option initialization functions
+        self.initialiseoptions(DirectSlider)
+        
+    def setRange(self):
+        # Try to preserve the value across a setRange call.
+        v = self['value']
+        r = self['range']
+        self.guiItem.setRange(r[0], r[1])
+        self['value'] = v
+
+    def __setValue(self):
+        # This is the internal function that is called when
+        # self['value'] is directly assigned.
+        self.guiItem.setValue(self['value'])
+
+    def setValue(self, value):
+        # This is the public function that is meant to be called by a
+        # user that doesn't like to use (or doesn't understand) the
+        # preferred interface of self['value'].
+        self['value'] = value
+
+    def getValue(self):
+        return self.guiItem.getValue()
+
+    def getRatio(self):
+        return self.guiItem.getRatio()
+
+    def setPageSize(self):
+        self.guiItem.setPageSize(self['pageSize'])
+
+    def setOrientation(self):
+        if self['orientation'] == HORIZONTAL:
+            self.guiItem.setAxis(Vec3(1, 0, 0))
+        else:
+            self.guiItem.setAxis(Vec3(0, 0, 1))
+
+    def destroy(self):
+        DirectFrame.destroy(self)
+
+    def commandFunc(self):
+        # Store the updated value in self['value']
+        self._optionInfo['value'][_OPT_VALUE] = self.guiItem.getValue()
+        
+        if self['command']:
+            apply(self['command'], self['extraArgs'])

+ 0 - 147
direct/src/gui/DirectSliderBar.py

@@ -1,147 +0,0 @@
-from DirectButton import *
-from DirectFrame import *
-
-"""
-import DirectSliderBar
-d = DirectSliderBar(borderWidth=(0,0))
-
-"""
-
-class DirectSliderButton(DirectButton):
-    def __init__(self, parent = None, **kw):
-        optiondefs = (
-            # Define type of DirectGuiWidget
-            ('pgFunc',         PGSliderButton,   None),
-            )
-        # Merge keyword options with default options
-        self.defineoptions(kw, optiondefs)
-        # Initialize superclasses
-        DirectButton.__init__(self,parent)
-        # Call option initialization functions
-        self.initialiseoptions(DirectSliderButton)
-
-class DirectSliderBar(DirectFrame):
-    """
-    DirectEntry(parent) - Create a DirectGuiWidget which responds
-    to keyboard buttons
-    """
-    def __init__(self, parent = None, **kw):
-        # Inherits from DirectFrame
-        # A Direct Frame 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)
-        optiondefs = (
-            # Define type of DirectGuiWidget
-            ('pgFunc',         PGSliderBar,        None),
-            ('width',          10,                 None),
-            ('height',         1,                  None),
-            ('button',         None,               None),
-            ('sliderOnly',     0,                  self.setSliderOnly),
-            ('negativeMapping',0,                  self.setNegativeMapping),
-            ('range',          100,                self.setRange),
-            ('value',          0,                  self.setValue),
-            ('barBorderWidth', (0,0),              self.setBarBorderWidth),
-            ('barColor',       (1,0,0,1),          self.setBarColor),
-            ('barRelief',      FLAT,               self.setBarRelief),
-            ('active',         0,                  self.setActive),
-            ('sortOrder',      NO_FADE_SORT_INDEX, None),
-            # Command to be called on button movement
-            ('command',        None,               None),
-            ('extraArgs',      [],                 None),
-            )
-        if kw.has_key('text'):
-            textoptiondefs = (
-                ('text_pos',    (0,-0.025),          None),
-                ('text_scale',  0.1,                 None),
-                ('text_fg',     (1,1,1,1),           None),
-                ('text_shadow', (0,0,0,1),           None),
-                ('text_align',  TextNode.ALeft,      None)
-                )
-        else:
-            textoptiondefs = ()
-        # Merge keyword options with default options
-        self.defineoptions(kw, optiondefs + textoptiondefs)
-        # Initialize superclasses
-        DirectFrame.__init__(self, parent)
-        self.barStyle = PGFrameStyle()
-        # Call option initialization functions
-        self.initialiseoptions(DirectSliderBar)
-
-        if (self['button'] != None):
-            self.guiItem.setSliderButton(self['button'], self['button'].guiItem)
-        
-        if (self['image'] != None):
-            self.guiItem.setScale(self['image_scale'][0])
-            self.guiItem.setup(self.getWidth(), self.getHeight(), self['range'])
-        else:
-            #figure out what is happening?????
-            #self.guiItem.setState(0)
-            #self.guiItem.clearStateDef(0)
-            self.guiItem.setFrame(-3.0, 3.0, -0.125, 0.125)
-            self.barStyle.setWidth(0.05, 0.05)
-            self.barStyle.setColor(0.6,0.6,0.6,1)
-            self.barStyle.setType(PGFrameStyle.TBevelIn)
-            self.guiItem.setFrameStyle(0, self.barStyle)
-            self.guiItem.setScale(2)
-            self.guiItem.setup(6, 0.5, self['range'])
-            self.guiItem.setValue(self['value'])
-            if (self['scale'] != None):
-                self.setScale(self['scale'])
-            else:
-                self.setScale(0.1)
-
-        self.guiItem.setActive(1)
-
-        #self.barStyle.setColor(0.8,0.8,0.8,1)
-        #self.barStyle.setType(PGFrameStyle.TBevelOut)
-        #self.updateBarStyle()
-
-        if (self['command'] != None):
-            # Attach command function to slider button movement
-            self.bind('updated-slider-', self.commandFunc)
-
-    def destroy(self):
-        del self.barStyle
-        DirectFrame.destroy(self)
-        
-    def setSliderOnly(self):
-        self.guiItem.setSliderOnly(self['sliderOnly'])
-
-    def setNegativeMapping(self):
-        self.guiItem.setNegativeMapping(self['negativeMapping'])
-
-    def setRange(self):
-        self.guiItem.setRange(self['range'])
-
-    def setValue(self):
-        self.guiItem.setValue(self['value'])
-
-    def getPercent(self):
-        return self.guiItem.getPercent()
-
-    def updateBarStyle(self):
-        if not self.fInit:
-            self.guiItem.setBarStyle(self.barStyle)            
-
-    def setBarRelief(self):
-        self.barStyle.setType(self['barRelief'])
-        self.updateBarStyle()
-
-    def setBarBorderWidth(self):
-        self.barStyle.setWidth(*self['barBorderWidth'])
-        self.updateBarStyle()
-
-    def setBarColor(self):
-        color = self['barColor']
-        self.barStyle.setColor(color[0], color[1], color[2], color[3])
-        self.updateBarStyle()
-
-    def setActive(self):
-        self.guiItem.setActive(self['active'])
-
-    def commandFunc(self):
-        if self['command']:
-            # Pass any extra args to command
-            apply(self['command'], self['extraArgs'])
-            

+ 1 - 5
panda/src/collide/collisionEntry.cxx

@@ -230,10 +230,6 @@ write(ostream &out, int indent_level) const {
 ////////////////////////////////////////////////////////////////////
 void CollisionEntry::
 check_clip_planes() {
-  const RenderAttrib *cpa_attrib =
-    _into_node_path.get_net_state()->get_attrib(ClipPlaneAttrib::get_class_type());
-  if (cpa_attrib != (const RenderAttrib *)NULL) {
-    _into_clip_planes = DCAST(ClipPlaneAttrib, cpa_attrib);
-  }
+  _into_clip_planes = _into_node_path.get_net_state()->get_clip_plane();
   _flags |= F_checked_clip_planes;
 }

+ 2 - 3
panda/src/collide/collisionPlane.cxx

@@ -32,7 +32,7 @@
 #include "datagramIterator.h"
 #include "bamReader.h"
 #include "bamWriter.h"
-#include "omniBoundingVolume.h"
+#include "boundingPlane.h"
 #include "geom.h"
 #include "geomTrifans.h"
 #include "geomLinestrips.h"
@@ -94,11 +94,10 @@ output(ostream &out) const {
 ////////////////////////////////////////////////////////////////////
 BoundingVolume *CollisionPlane::
 recompute_bound() {
-  // Planes always have an infinite bounding volume.
   BoundedObject::recompute_bound();
   // Less than ideal: we throw away whatever we just allocated in
   // BoundedObject.
-  return set_bound_ptr(new OmniBoundingVolume);
+  return set_bound_ptr(new BoundingPlane(_plane));
 }
 
 ////////////////////////////////////////////////////////////////////

+ 14 - 10
panda/src/collide/collisionPolygon.cxx

@@ -279,9 +279,8 @@ get_collision_origin() const {
 PT(PandaNode) CollisionPolygon::
 get_viz(const CullTraverser *trav, const CullTraverserData &data, 
         bool bounds_only) const {
-  const RenderAttrib *cpa_attrib =
-    data._state->get_attrib(ClipPlaneAttrib::get_class_type());
-  if (cpa_attrib == (const RenderAttrib *)NULL) {
+  const ClipPlaneAttrib *cpa = data._state->get_clip_plane();
+  if (cpa == (const ClipPlaneAttrib *)NULL) {
     // Fortunately, the polygon is not clipped.  This is the normal,
     // easy case.
     return CollisionSolid::get_viz(trav, data, bounds_only);
@@ -289,7 +288,7 @@ get_viz(const CullTraverser *trav, const CullTraverserData &data,
 
   if (collide_cat.is_debug()) {
     collide_cat.debug()
-      << "drawing polygon with clip plane " << *cpa_attrib << "\n";
+      << "drawing polygon with clip plane " << *cpa << "\n";
   }
 
   // The polygon is clipped.  We need to render it clipped.  We could
@@ -298,7 +297,6 @@ get_viz(const CullTraverser *trav, const CullTraverserData &data,
   // and clip it by hand instead, just to prove that our clipping
   // algorithm works properly.  This does require some more dynamic
   // work.
-  const ClipPlaneAttrib *cpa = DCAST(ClipPlaneAttrib, cpa_attrib);
   Points new_points;
   if (apply_clip_plane(new_points, cpa, data.get_net_transform(trav))) {
     // All points are behind the clip plane; just draw the original
@@ -1093,15 +1091,21 @@ clip_polygon(CollisionPolygon::Points &new_points,
     const LPoint2f &this_point = (*pi)._p;
     bool this_is_in = !is_right(this_point - from2d, delta2d);
 
-    if (this_is_in != last_is_in) {
+    // There appears to be a compiler bug in gcc 4.0: we need to
+    // extract this comparison outside of the if statement.
+    bool crossed_over = (this_is_in != last_is_in);
+    if (crossed_over) {
       // We have just crossed over the clipping line.  Find the point
       // of intersection.
       LVector2f d = this_point - last_point;
-      float t = -(a * last_point[0] + b * last_point[1] + c) / (a * d[0] + b * d[1]);
-      LPoint2f p = last_point + t * d;
+      float denom = (a * d[0] + b * d[1]);
+      if (denom != 0.0) {
+        float t = -(a * last_point[0] + b * last_point[1] + c) / denom;
+        LPoint2f p = last_point + t * d;
 
-      new_points.push_back(PointDef(p[0], p[1]));
-      last_is_in = this_is_in;
+        new_points.push_back(PointDef(p[0], p[1]));
+        last_is_in = this_is_in;
+      }
     } 
 
     if (this_is_in) {

+ 3 - 1
panda/src/collide/collisionVisualizer.cxx

@@ -34,6 +34,7 @@
 #include "depthOffsetAttrib.h"
 #include "colorScaleAttrib.h"
 #include "transparencyAttrib.h"
+#include "clipPlaneAttrib.h"
 
 
 #ifdef DO_COLLISION_RECORDING
@@ -137,7 +138,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     xform_data._modelview_transform = trav->get_world_transform();
     xform_data.apply_transform_and_state(trav, net_transform, 
                                          RenderState::make_empty(),
-                                         RenderEffects::make_empty());
+                                         RenderEffects::make_empty(),
+                                         ClipPlaneAttrib::make());
 
     // Draw all the collision solids.
     Solids::const_iterator si;

+ 9 - 3
panda/src/mathutil/Sources.pp

@@ -12,7 +12,9 @@
 
   #define SOURCES  \
     boundingHexahedron.I boundingHexahedron.h boundingLine.I  \
-    boundingLine.h boundingSphere.I boundingSphere.h  \
+    boundingLine.h \
+    boundingPlane.I boundingPlane.h \
+    boundingSphere.I boundingSphere.h  \
     boundingVolume.I boundingVolume.h config_mathutil.h  \
     fftCompressor.h finiteBoundingVolume.h frustum.h  \
     frustum_src.I frustum_src.h geometricBoundingVolume.I  \
@@ -24,7 +26,9 @@
     plane_src.h rotate_to.h rotate_to_src.cxx
      
   #define INCLUDED_SOURCES \
-    boundingHexahedron.cxx boundingLine.cxx boundingSphere.cxx  \
+    boundingHexahedron.cxx boundingLine.cxx \
+    boundingPlane.cxx \
+    boundingSphere.cxx  \
     boundingVolume.cxx config_mathutil.cxx fftCompressor.cxx  \
     finiteBoundingVolume.cxx geometricBoundingVolume.cxx  \
     look_at.cxx \
@@ -33,7 +37,9 @@
 
   #define INSTALL_HEADERS \
     boundingHexahedron.I boundingHexahedron.h boundingLine.I \
-    boundingLine.h boundingSphere.I boundingSphere.h boundingVolume.I \
+    boundingLine.h \
+    boundingPlane.I boundingPlane.h \
+    boundingSphere.I boundingSphere.h boundingVolume.I \
     boundingVolume.h config_mathutil.h fftCompressor.h \
     finiteBoundingVolume.h frustum.h frustum_src.I frustum_src.h \
     geometricBoundingVolume.I geometricBoundingVolume.h look_at.h \

+ 22 - 17
panda/src/pgui/pgSliderButton.I → panda/src/mathutil/boundingPlane.I

@@ -1,5 +1,5 @@
-// Filename: pgButton.I
-// Created by:  masad (21Oct04)
+// Filename: boundingPlane.I
+// Created by:  drose (19Aug05)
 //
 ////////////////////////////////////////////////////////////////////
 //
@@ -16,32 +16,37 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::is_drag_n_drop
+//     Function: BoundingPlane::Default Constructor
 //       Access: Published
-//  Description: Returns if this button is drag_n_drop kind
+//  Description: Constructs an empty "plane" that has no
+//               intersections.
 ////////////////////////////////////////////////////////////////////
-INLINE bool PGSliderButton::
-is_drag_n_drop() {
-  return _drag_n_drop;
+INLINE_MATHUTIL BoundingPlane::
+BoundingPlane() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::set_drag_n_drop
+//     Function: BoundingPlane::Constructor
 //       Access: Published
-//  Description: Makes this button a drag_n_drop kind
+//  Description: 
 ////////////////////////////////////////////////////////////////////
-INLINE void PGSliderButton::
-set_drag_n_drop(bool value) {
-  _drag_n_drop = value;
+INLINE_MATHUTIL BoundingPlane::
+BoundingPlane(const Planef &plane) :
+  _plane(plane)
+{
+  _flags = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::set_slider_bar
+//     Function: BoundingPlane::get_plane
 //       Access: Published
-//  Description: Store a copy of the controlling slider bar item
+//  Description: 
 ////////////////////////////////////////////////////////////////////
-INLINE void PGSliderButton::
-set_slider_bar(PGItem *item) {
-  _slider_bar = item;
+INLINE_MATHUTIL const Planef &BoundingPlane::
+get_plane() const {
+  nassertr(!is_empty(), _plane);
+  nassertr(!is_infinite(), _plane);
+  return _plane;
 }

+ 153 - 0
panda/src/mathutil/boundingPlane.cxx

@@ -0,0 +1,153 @@
+// Filename: boundingPlane.cxx
+// Created by:  drose (19Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "boundingPlane.h"
+#include "boundingSphere.h"
+#include "config_mathutil.h"
+
+TypeHandle BoundingPlane::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::make_copy
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+BoundingVolume *BoundingPlane::
+make_copy() const {
+  return new BoundingPlane(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::get_approx_center
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+LPoint3f BoundingPlane::
+get_approx_center() const {
+  nassertr(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
+  nassertr(!is_infinite(), LPoint3f(0.0f, 0.0f, 0.0f));
+  return _plane.get_point();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::xform
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void BoundingPlane::
+xform(const LMatrix4f &mat) {
+  nassertv(!mat.is_nan());
+
+  if (!is_empty() && !is_infinite()) {
+    _plane.xform(mat);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void BoundingPlane::
+output(ostream &out) const {
+  if (is_empty()) {
+    out << "bplane, empty";
+  } else if (is_infinite()) {
+    out << "bplane, infinite";
+  } else {
+    out << "bplane: " << _plane;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::extend_other
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool BoundingPlane::
+extend_other(BoundingVolume *other) const {
+  return other->extend_by_plane(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::around_other
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool BoundingPlane::
+around_other(BoundingVolume *other,
+             const BoundingVolume **first,
+             const BoundingVolume **last) const {
+  return other->around_planes(first, last);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::contains_other
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+int BoundingPlane::
+contains_other(const BoundingVolume *other) const {
+  return other->contains_plane(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::extend_by_plane
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+bool BoundingPlane::
+extend_by_plane(const BoundingPlane *plane) {
+  nassertr(!plane->is_empty() && !plane->is_infinite(), false);
+  nassertr(!is_infinite(), false);
+
+  if (is_empty()) {
+    _plane = plane->get_plane();
+    _flags = 0;
+  } else {
+    _flags = F_infinite;
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingPlane::contains_sphere
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+int BoundingPlane::
+contains_sphere(const BoundingSphere *sphere) const {
+  nassertr(!is_empty() && !is_infinite(), 0);
+  nassertr(!sphere->is_empty() && !sphere->is_infinite(), 0);
+
+  float r = sphere->get_radius();
+  float d = _plane.dist_to_plane(sphere->get_center());
+
+  if (d <= -r) {
+    // The sphere is completely behind the plane.
+    return IF_all | IF_possible | IF_some;
+
+  } else if (d <= r) {
+    // The sphere is intersecting with the plane itself.
+    return IF_possible | IF_some;
+
+  } else {
+    // The sphere is completely in front of the plane.
+    return IF_no_intersection;
+  }
+}

+ 86 - 0
panda/src/mathutil/boundingPlane.h

@@ -0,0 +1,86 @@
+// Filename: boundingPlane.h
+// Created by:  drose (19Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef BOUNDINGPLANE_H
+#define BOUNDINGPLANE_H
+
+#include "pandabase.h"
+
+#include "geometricBoundingVolume.h"
+#include "plane.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : BoundingPlane
+// Description : This funny bounding volume is an infinite plane that
+//               divides space into two regions: the part behind the
+//               normal, which is "inside" the bounding volume, and
+//               the part in front of the normal, which is "outside"
+//               the bounding volume.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA BoundingPlane : public GeometricBoundingVolume {
+PUBLISHED:
+  INLINE_MATHUTIL BoundingPlane();
+  INLINE_MATHUTIL BoundingPlane(const Planef &plane);
+
+public:
+  virtual BoundingVolume *make_copy() const;
+
+  virtual LPoint3f get_approx_center() const;
+  virtual void xform(const LMatrix4f &mat);
+
+  virtual void output(ostream &out) const;
+
+PUBLISHED:
+  INLINE_MATHUTIL const Planef &get_plane() const;
+
+protected:
+  virtual bool extend_other(BoundingVolume *other) const;
+  virtual bool around_other(BoundingVolume *other,
+                            const BoundingVolume **first,
+                            const BoundingVolume **last) const;
+  virtual int contains_other(const BoundingVolume *other) const;
+
+  virtual bool extend_by_plane(const BoundingPlane *plane);
+
+  virtual int contains_sphere(const BoundingSphere *sphere) const;
+
+private:
+  Planef _plane;
+
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    GeometricBoundingVolume::init_type();
+    register_type(_type_handle, "BoundingPlane",
+                  GeometricBoundingVolume::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "boundingPlane.I"
+
+#endif

+ 47 - 0
panda/src/mathutil/boundingVolume.cxx

@@ -119,6 +119,19 @@ extend_by_line(const BoundingLine *) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingVolume::extend_by_plane
+//       Access: Protected, Virtual
+//  Description: Double-dispatch support: called by extend_other()
+//               when the type we're extending by is known to be a
+//               plane.
+////////////////////////////////////////////////////////////////////
+bool BoundingVolume::
+extend_by_plane(const BoundingPlane *) {
+  _flags = F_infinite;
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BoundingVolume::around_spheres
 //       Access: Protected, Virtual
@@ -167,6 +180,28 @@ around_lines(const BoundingVolume **, const BoundingVolume **) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingVolume::around_planes
+//       Access: Protected, Virtual
+//  Description: Double-dispatch support: called by around_other()
+//               when the type of the first element in the list is
+//               known to be a nonempty plane.
+////////////////////////////////////////////////////////////////////
+bool BoundingVolume::
+around_planes(const BoundingVolume **, const BoundingVolume **) {
+  _flags = F_infinite;
+  if (is_of_type(FiniteBoundingVolume::get_class_type())) {
+    // If it's a FiniteBoundingVolume, we can't do any better than
+    // making it infinite.  So we return true.
+    return true;
+  }
+
+  // Otherwise, we might do better, and we require each class to
+  // define a function.  If we get here, the function isn't defined,
+  // so we return false to indicate this.
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BoundingVolume::contains_sphere
 //       Access: Protected, Virtual
@@ -202,3 +237,15 @@ int BoundingVolume::
 contains_line(const BoundingLine *) const {
   return IF_dont_understand;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: BoundingVolume::contains_plane
+//       Access: Protected, Virtual
+//  Description: Double-dispatch support: called by contains_other()
+//               when the type we're testing for intersection is known
+//               to be a plane.
+////////////////////////////////////////////////////////////////////
+int BoundingVolume::
+contains_plane(const BoundingPlane *) const {
+  return IF_dont_understand;
+}

+ 6 - 0
panda/src/mathutil/boundingVolume.h

@@ -27,6 +27,7 @@
 class BoundingSphere;
 class BoundingHexahedron;
 class BoundingLine;
+class BoundingPlane;
 
 
 ////////////////////////////////////////////////////////////////////
@@ -118,6 +119,7 @@ protected:
   virtual bool extend_by_sphere(const BoundingSphere *sphere);
   virtual bool extend_by_hexahedron(const BoundingHexahedron *hexahedron);
   virtual bool extend_by_line(const BoundingLine *line);
+  virtual bool extend_by_plane(const BoundingPlane *plane);
 
   virtual bool around_spheres(const BoundingVolume **first,
                               const BoundingVolume **last);
@@ -125,10 +127,13 @@ protected:
                                   const BoundingVolume **last);
   virtual bool around_lines(const BoundingVolume **first,
                             const BoundingVolume **last);
+  virtual bool around_planes(const BoundingVolume **first,
+                            const BoundingVolume **last);
 
   virtual int contains_sphere(const BoundingSphere *sphere) const;
   virtual int contains_hexahedron(const BoundingHexahedron *hexahedron) const;
   virtual int contains_line(const BoundingLine *line) const;
+  virtual int contains_plane(const BoundingPlane *plane) const;
 
 
 public:
@@ -151,6 +156,7 @@ private:
   friend class BoundingSphere;
   friend class BoundingHexahedron;
   friend class BoundingLine;
+  friend class BoundingPlane;
 };
 
 INLINE_MATHUTIL ostream &operator << (ostream &out, const BoundingVolume &bound);

+ 2 - 0
panda/src/mathutil/config_mathutil.cxx

@@ -24,6 +24,7 @@
 #include "boundingSphere.h"
 #include "boundingHexahedron.h"
 #include "boundingLine.h"
+#include "boundingPlane.h"
 #include "linmath_events.h"
 #include "dconfig.h"
 #include "pandaSystem.h"
@@ -51,6 +52,7 @@ ConfigureFn(config_mathutil) {
   GeometricBoundingVolume::init_type();
   OmniBoundingVolume::init_type();
   BoundingLine::init_type();
+  BoundingPlane::init_type();
   EventStoreVec2::init_type("EventStoreVec2");
   EventStoreVec3::init_type("EventStoreVec3");
   EventStoreMat4::init_type("EventStoreMat4");

+ 1 - 0
panda/src/mathutil/mathutil_composite1.cxx

@@ -2,6 +2,7 @@
 #include "boundingVolume.cxx"
 #include "boundingHexahedron.cxx"
 #include "boundingLine.cxx"
+#include "boundingPlane.cxx"
 #include "boundingSphere.cxx"
 #include "finiteBoundingVolume.cxx"
 #include "geometricBoundingVolume.cxx"

+ 21 - 1
panda/src/mathutil/plane_src.I

@@ -24,7 +24,7 @@
 //               It's not clear how useful a default plane is.
 ////////////////////////////////////////////////////////////////////
 INLINE_MATHUTIL FLOATNAME(Plane)::
-FLOATNAME(Plane)(void) {
+FLOATNAME(Plane)() {
   _v.v._0 = 0.0f;
   _v.v._1 = 0.0f;
   _v.v._2 = 1.0f;
@@ -116,6 +116,26 @@ operator * (const FLOATNAME(LMatrix4) &mat) const {
   return FLOATNAME(Plane)(new_normal, new_point);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Plane::Operator *= LMatrix4
+//       Access: Published
+//  Description: Transforms the plane by the indicated matrix.
+////////////////////////////////////////////////////////////////////
+INLINE_MATHUTIL void FLOATNAME(Plane)::
+operator *= (const FLOATNAME(LMatrix4) &mat) {
+  (*this) = (*this) * mat;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Plane::xform
+//       Access: Published
+//  Description: Transforms the plane by the indicated matrix.
+////////////////////////////////////////////////////////////////////
+void FLOATNAME(Plane)::
+xform(const FLOATNAME(LMatrix4) &mat) {
+  (*this) = (*this) * mat;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Plane::Unary -
 //       Access: Published

+ 1 - 1
panda/src/mathutil/plane_src.cxx

@@ -25,7 +25,7 @@
 //               mirror.
 ////////////////////////////////////////////////////////////////////
 FLOATNAME(LMatrix4) FLOATNAME(Plane)::
-get_reflection_mat(void) const {
+get_reflection_mat() const {
   FLOATTYPE aa = _v.v._0 * _v.v._0; 
   FLOATTYPE ab = _v.v._0 * _v.v._1;
   FLOATTYPE ac = _v.v._0 * _v.v._2;

+ 8 - 6
panda/src/mathutil/plane_src.h

@@ -23,7 +23,7 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA FLOATNAME(Plane) : public FLOATNAME(LVecBase4) {
 PUBLISHED:
-  INLINE_MATHUTIL FLOATNAME(Plane)(void);
+  INLINE_MATHUTIL FLOATNAME(Plane)();
   INLINE_MATHUTIL FLOATNAME(Plane)(const FLOATNAME(LVecBase4) &copy);
   INLINE_MATHUTIL FLOATNAME(Plane)(const FLOATNAME(LPoint3) &a, 
                                    const FLOATNAME(LPoint3) &b,
@@ -35,20 +35,22 @@ PUBLISHED:
 
   INLINE_MATHUTIL FLOATNAME(Plane) operator * (const FLOATNAME(LMatrix3) &mat) const;
   INLINE_MATHUTIL FLOATNAME(Plane) operator * (const FLOATNAME(LMatrix4) &mat) const;
+  INLINE_MATHUTIL void operator *= (const FLOATNAME(LMatrix4) &mat);
+  INLINE_MATHUTIL void xform(const FLOATNAME(LMatrix4) &mat);
   INLINE_MATHUTIL FLOATNAME(Plane) operator - () const;
 
-  FLOATNAME(LMatrix4) get_reflection_mat(void) const;
+  FLOATNAME(LMatrix4) get_reflection_mat() const;
 
   INLINE_MATHUTIL FLOATNAME(LVector3) get_normal() const;
   FLOATNAME(LPoint3) get_point() const;
 
   INLINE_MATHUTIL FLOATTYPE dist_to_plane(const FLOATNAME(LPoint3) &point) const;
   INLINE_MATHUTIL bool intersects_line(FLOATNAME(LPoint3) &intersection_point,
-                              const FLOATNAME(LPoint3) &p1,
-                              const FLOATNAME(LPoint3) &p2) const;
+                                       const FLOATNAME(LPoint3) &p1,
+                                       const FLOATNAME(LPoint3) &p2) const;
   INLINE_MATHUTIL bool intersects_line(FLOATTYPE &t,
-                              const FLOATNAME(LPoint3) &from,
-                              const FLOATNAME(LVector3) &delta) const;
+                                       const FLOATNAME(LPoint3) &from,
+                                       const FLOATNAME(LVector3) &delta) const;
 
   bool intersects_plane(FLOATNAME(LPoint3) &from,
                         FLOATNAME(LVector3) &delta,

+ 3 - 0
panda/src/pgraph/Sources.pp

@@ -35,6 +35,7 @@
     cullBinUnsorted.I cullBinUnsorted.h \
     cullFaceAttrib.I cullFaceAttrib.h \
     cullHandler.I cullHandler.h \
+    cullPlanes.I cullPlanes.h \
     cullResult.I cullResult.h \
     cullTraverser.I cullTraverser.h \
     cullTraverserData.I cullTraverserData.h \
@@ -134,6 +135,7 @@
     cullBinUnsorted.cxx \
     cullFaceAttrib.cxx \
     cullHandler.cxx \
+    cullPlanes.cxx \
     cullResult.cxx \
     cullTraverser.cxx \
     cullTraverserData.cxx \
@@ -229,6 +231,7 @@
     cullBinUnsorted.I cullBinUnsorted.h \
     cullFaceAttrib.I cullFaceAttrib.h \
     cullHandler.I cullHandler.h \
+    cullPlanes.I cullPlanes.h \
     cullResult.I cullResult.h \
     cullTraverser.I cullTraverser.h \
     cullTraverserData.I cullTraverserData.h \

+ 77 - 0
panda/src/pgraph/clipPlaneAttrib.cxx

@@ -516,6 +516,83 @@ filter_to_max(int max_clip_planes) const {
   return planeNode_attrib;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ClipPlaneAttrib::compose_off
+//       Access: Public
+//  Description: This is a special method which composes two
+//               ClipPlaneAttribs with regard only to their set of
+//               "off" clip planes, for the purposes of deriving
+//               PandaNode::get_off_clip_planes().
+//
+//               The result will be a ClipPlaneAttrib that represents
+//               the union of all of the clip planes turned off in
+//               either attrib.  The set of on planes in the result is
+//               undefined and should be ignored.
+////////////////////////////////////////////////////////////////////
+CPT(RenderAttrib) ClipPlaneAttrib::
+compose_off(const RenderAttrib *other) const {
+  const ClipPlaneAttrib *ta;
+  DCAST_INTO_R(ta, other, 0);
+
+  if (_off_all_planes || (!ta->_off_all_planes && ta->_off_planes.empty())) {
+    // If we turn off all planes, or the other turns none off, the
+    // result is the same as this one.
+    return this;
+  }
+
+  if (ta->_off_all_planes || _off_planes.empty()) {
+    // And contrariwise.
+    return ta;
+  }
+
+  Planes::const_iterator ai = _off_planes.begin();
+  Planes::const_iterator bi = ta->_off_planes.begin();
+
+  // Create a new ClipPlaneAttrib that will hold the result.
+  ClipPlaneAttrib *new_attrib = new ClipPlaneAttrib;
+  back_insert_iterator<Planes> result = 
+    back_inserter(new_attrib->_on_planes);
+
+  while (ai != _off_planes.end() && 
+         bi != ta->_off_planes.end()) {
+    if ((*ai) < (*bi)) {
+      // Here is a plane that we have in the original, which is not
+      // present in the secondary.
+      *result = *ai;
+      ++ai;
+      ++result;
+
+    } else if ((*bi) < (*ai)) {
+      // Here is a new plane we have in the secondary, that was not
+      // present in the original.
+      *result = *bi;
+      ++bi;
+      ++result;
+
+    } else {  // (*bi) == (*ai)
+      // Here is a plane we have in both.
+      *result = *bi;
+      ++ai;
+      ++bi;
+      ++result;
+    }
+  }
+
+  while (ai != _off_planes.end()) {
+    *result = *ai;
+    ++ai;
+    ++result;
+  }
+
+  while (bi != ta->_off_planes.end()) {
+    *result = *bi;
+    ++bi;
+    ++result;
+  }
+
+  return return_new(new_attrib);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ClipPlaneAttrib::issue
 //       Access: Public, Virtual

+ 1 - 0
panda/src/pgraph/clipPlaneAttrib.h

@@ -96,6 +96,7 @@ PUBLISHED:
   CPT(ClipPlaneAttrib) filter_to_max(int max_clip_planes) const;
 
 public:
+  CPT(RenderAttrib) compose_off(const RenderAttrib *other) const;
   virtual void issue(GraphicsStateGuardianBase *gsg) const;
   virtual void output(ostream &out) const;
 

+ 68 - 0
panda/src/pgraph/cullPlanes.I

@@ -0,0 +1,68 @@
+// Filename: cullPlanes.I
+// Created by:  drose (23Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullPlanes::
+CullPlanes() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullPlanes::
+CullPlanes(const CullPlanes &copy) :
+  _planes(copy._planes)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::Copy Assignment Operator
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void CullPlanes::
+operator = (const CullPlanes &copy) {
+  _planes = copy._planes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullPlanes::
+~CullPlanes() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::is_empty
+//       Access: Public
+//  Description: Returns true if there are no planes listed in the
+//               CullPlanes object.
+////////////////////////////////////////////////////////////////////
+INLINE bool CullPlanes::
+is_empty() const {
+  return _planes.empty();
+}

+ 218 - 0
panda/src/pgraph/cullPlanes.cxx

@@ -0,0 +1,218 @@
+// Filename: cullPlanes.cxx
+// Created by:  drose (23Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "cullPlanes.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::make_empty
+//       Access: Public, Static
+//  Description: Returns a pointer to an empty CullPlanes object.
+////////////////////////////////////////////////////////////////////
+CPT(CullPlanes) CullPlanes::
+make_empty() {
+  static CPT(CullPlanes) empty;
+  if (empty == NULL) {
+    empty = new CullPlanes;
+    // Artificially tick the reference count, just to ensure we won't
+    // accidentally modify this object in any of the copy-on-write
+    // operations below.
+    empty->ref();
+  }
+  return empty;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::xform
+//       Access: Public
+//  Description: Returns a pointer to a new CullPlanes object that is
+//               the same as this one, but with the clip planes
+//               modified by the indicated transform.
+////////////////////////////////////////////////////////////////////
+CPT(CullPlanes) CullPlanes::
+xform(const LMatrix4f &mat) const {
+  PT(CullPlanes) new_planes;
+  if (get_ref_count() == 1) {
+    new_planes = (CullPlanes *)this;
+  } else {
+    new_planes = new CullPlanes(*this);
+  }
+
+  for (Planes::iterator pi = new_planes->_planes.begin();
+       pi != new_planes->_planes.end();
+       ++pi) {
+    if ((*pi).second->get_ref_count() != 1) {
+      (*pi).second = DCAST(BoundingPlane, (*pi).second->make_copy());
+    }
+    (*pi).second->xform(mat);
+  }
+
+  return new_planes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::apply_state
+//       Access: Public
+//  Description: Returns a pointer to a new CullPlanes object that is
+//               the same as this one, but with the indicated
+//               attributes applied to the state.
+//
+//               In particular, any new ClipPlanes given in
+//               net_attrib, if it is not NULL, will be added to the
+//               state, unless those ClipPlanes are also listed in
+//               off_attrib.
+////////////////////////////////////////////////////////////////////
+CPT(CullPlanes) CullPlanes::
+apply_state(const CullTraverser *trav, const CullTraverserData *data,
+            const ClipPlaneAttrib *net_attrib,
+            const ClipPlaneAttrib *off_attrib) const {
+  if (net_attrib == (ClipPlaneAttrib *)NULL) {
+    return this;
+  }
+
+  PT(CullPlanes) new_planes;
+  if (get_ref_count() == 1) {
+    new_planes = (CullPlanes *)this;
+  } else {
+    new_planes = new CullPlanes(*this);
+  }
+
+  CPT(TransformState) net_transform = NULL;
+
+  int num_on_planes = net_attrib->get_num_on_planes();
+  for (int i = 0; i < num_on_planes; ++i) {
+    NodePath clip_plane = net_attrib->get_on_plane(i);
+    Planes::const_iterator pi = new_planes->_planes.find(clip_plane);
+    if (pi == new_planes->_planes.end()) {
+      if (!off_attrib->has_off_plane(clip_plane)) {
+        // Here's a new clip plane; add it to the list.  For this we
+        // need the net transform to this node.
+        if (net_transform == (TransformState *)NULL) {
+          net_transform = data->get_net_transform(trav);
+        }
+
+        PlaneNode *plane_node = DCAST(PlaneNode, clip_plane.node());
+        CPT(TransformState) new_transform = 
+          net_transform->invert_compose(clip_plane.get_net_transform());
+        
+        Planef plane = plane_node->get_plane() * new_transform->get_mat();
+        new_planes->_planes[clip_plane] = new BoundingPlane(-plane);
+      }
+    }
+  }
+
+  return new_planes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::do_cull
+//       Access: Public
+//  Description: Tests the indicated bounding volume against all of
+//               the clip planes in this object.  Sets result to an
+//               appropriate union of
+//               BoundingVolume::IntersectionFlags, similar to the
+//               result of BoundingVolume::contains().
+//
+//               Also, if the bounding volume is completely in front
+//               of any of the clip planes, removes those planes both
+//               from this object and from the indicated state,
+//               returning a new CullPlanes object in that case.
+////////////////////////////////////////////////////////////////////
+CPT(CullPlanes) CullPlanes::
+do_cull(int &result, CPT(RenderState) &state,
+        const GeometricBoundingVolume *node_gbv) const {
+  // We should have a ClipPlaneAttrib in the state if we've called
+  // this method.
+  CPT(ClipPlaneAttrib) orig_cpa = state->get_clip_plane();
+  nassertr(orig_cpa != (ClipPlaneAttrib *)NULL, this);
+
+  result = 
+    BoundingVolume::IF_all | BoundingVolume::IF_possible | BoundingVolume::IF_some;
+
+  CPT(CullPlanes) new_planes = this;
+  CPT(ClipPlaneAttrib) new_cpa = orig_cpa;
+
+  Planes::const_iterator pi;
+  for (pi = _planes.begin(); pi != _planes.end(); ++pi) {
+    int plane_result = (*pi).second->contains(node_gbv);
+    if (plane_result == BoundingVolume::IF_no_intersection) {
+      // The node is completely behind this clip plane.  Short-circuit
+      // the rest of the logic; none of the other planes matter.
+      result = plane_result;
+      return new_planes;
+    }
+
+    if ((plane_result & BoundingVolume::IF_all) != 0) {
+      // The node is completely in front of this clip plane.  We don't
+      // need to consider this plane ever again for any descendents of
+      // this node.
+      new_planes = new_planes->remove_plane((*pi).first);
+      nassertr(new_planes != this, new_planes);
+      new_cpa = DCAST(ClipPlaneAttrib, new_cpa->remove_on_plane((*pi).first));
+    }
+
+    result &= plane_result;
+  }
+
+  if (new_cpa != orig_cpa) {
+    if (new_cpa->is_identity()) {
+      state = state->remove_attrib(ClipPlaneAttrib::get_class_type());
+    } else {
+      state = state->add_attrib(new_cpa);
+    }
+  }
+    
+  return new_planes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::remove_plane
+//       Access: Public
+//  Description: Returns a pointer to a new CullPlanes object that is
+//               the same as this one, but with the indicated
+//               clip plane removed.
+////////////////////////////////////////////////////////////////////
+CPT(CullPlanes) CullPlanes::
+remove_plane(const NodePath &clip_plane) const {
+  PT(CullPlanes) new_planes;
+  if (get_ref_count() == 1) {
+    new_planes = (CullPlanes *)this;
+  } else {
+    new_planes = new CullPlanes(*this);
+  }
+
+  Planes::iterator pi = new_planes->_planes.find(clip_plane);
+  nassertr(pi != new_planes->_planes.end(), new_planes);
+  new_planes->_planes.erase(pi);
+
+  return new_planes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullPlanes::write
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void CullPlanes::
+write(ostream &out) const {
+  out << "CullPlanes (" << _planes.size() << " planes):\n";
+  Planes::const_iterator pi;
+  for (pi = _planes.begin(); pi != _planes.end(); ++pi) {
+    out << "  " << (*pi).first << " : " << *(*pi).second << "\n";
+  }
+}

+ 77 - 0
panda/src/pgraph/cullPlanes.h

@@ -0,0 +1,77 @@
+// Filename: cullPlanes.h
+// Created by:  drose (23Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef CULLPLANES_H
+#define CULLPLANES_H
+
+#include "pandabase.h"
+#include "referenceCount.h"
+#include "nodePath.h"
+#include "clipPlaneAttrib.h"
+#include "boundingPlane.h"
+#include "pointerTo.h"
+#include "luse.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : CullPlanes
+// Description : This represents the set of clip planes that are
+//               definitely in effect for the current node of the
+//               CullTraverserData, as well as on all child nodes.
+//               Any clip planes in this list may be safely culled
+//               against.
+//
+//               This does not include the clip planes that are in
+//               effect now, but might later be turned off by a child
+//               node, since we can't safely cull against such clip
+//               planes.
+//
+//               The bounding volumes in this object are transformed
+//               for each level of the scene graph.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA CullPlanes : public ReferenceCount {
+protected:
+  INLINE CullPlanes();
+  INLINE CullPlanes(const CullPlanes &copy);
+  INLINE void operator = (const CullPlanes &copy); 
+
+public:
+  INLINE ~CullPlanes();
+
+  INLINE bool is_empty() const;
+
+  static CPT(CullPlanes) make_empty();
+  CPT(CullPlanes) xform(const LMatrix4f &mat) const;
+  CPT(CullPlanes) apply_state(const CullTraverser *trav, 
+                              const CullTraverserData *data,
+                              const ClipPlaneAttrib *net_attrib,
+                              const ClipPlaneAttrib *off_attrib) const;
+  CPT(CullPlanes) do_cull(int &result, CPT(RenderState) &state,
+                          const GeometricBoundingVolume *node_gbv) const;
+
+  CPT(CullPlanes) remove_plane(const NodePath &clip_plane) const;
+
+  void write(ostream &out) const;
+
+private:
+  typedef pmap<NodePath, PT(BoundingPlane) > Planes;
+  Planes _planes;
+};
+
+#include "cullPlanes.I"
+
+#endif

+ 1 - 2
panda/src/pgraph/cullTraverser.cxx

@@ -166,8 +166,7 @@ traverse(CullTraverserData &data) {
 
     data.apply_transform_and_state(this);
 
-    const RenderState *node_state = node->get_state();
-    const FogAttrib *fog = node_state->get_fog();
+    const FogAttrib *fog = node->get_state()->get_fog();
     if (fog != (const FogAttrib *)NULL && fog->get_fog() != (Fog *)NULL) {
       // If we just introduced a FogAttrib here, call adjust_to_camera()
       // now.  This maybe isn't the perfect time to call it, but it's

+ 36 - 6
panda/src/pgraph/cullTraverserData.I

@@ -31,7 +31,8 @@ CullTraverserData(const NodePath &start,
   _modelview_transform(modelview_transform),
   _state(state),
   _view_frustum(view_frustum),
-  _guard_band(guard_band)
+  _guard_band(guard_band),
+  _cull_planes(CullPlanes::make_empty())
 {
 }
 
@@ -46,7 +47,8 @@ CullTraverserData(const CullTraverserData &copy) :
   _modelview_transform(copy._modelview_transform),
   _state(copy._state),
   _view_frustum(copy._view_frustum),
-  _guard_band(copy._guard_band)
+  _guard_band(copy._guard_band),
+  _cull_planes(copy._cull_planes)
 {
 }
 
@@ -62,6 +64,7 @@ operator = (const CullTraverserData &copy) {
   _state = copy._state;
   _view_frustum = copy._view_frustum;
   _guard_band = copy._guard_band;
+  _cull_planes = copy._cull_planes;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -76,7 +79,8 @@ CullTraverserData(const CullTraverserData &parent, PandaNode *child) :
   _modelview_transform(parent._modelview_transform),
   _state(parent._state),
   _view_frustum(parent._view_frustum),
-  _guard_band(parent._guard_band)
+  _guard_band(parent._guard_band),
+  _cull_planes(parent._cull_planes)
 {
 }
 
@@ -132,9 +136,10 @@ is_in_view(const DrawMask &camera_mask) {
     return false;
   }
 
-  if (_view_frustum == (GeometricBoundingVolume *)NULL) {
-    // If the transform is valid, but we don't have a frustum, it's
-    // always in.
+  if (_view_frustum == (GeometricBoundingVolume *)NULL &&
+      _cull_planes->is_empty()) {
+    // If the transform is valid, but we don't have a frustum or any
+    // clip planes, it's always in.
     return true;
   }
 
@@ -142,3 +147,28 @@ is_in_view(const DrawMask &camera_mask) {
   return is_in_view_impl();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::test_within_clip_planes
+//       Access: Public
+//  Description: Returns the BoundingVolume intersection bits
+//               appropriate for the intersection of the current clip
+//               planes with the current node.
+//
+//               That is, if the current node is within all clip
+//               planes (and will not be clipped at all), returns
+//               BoundingVolume::IF_all.  If it is completely behind
+//               at least one clip plane, returns
+//               BoundingVolume::IF_intersection.  If it is partially
+//               behind one or more clip planes, returns
+//               BoundingVolume::IF_some.
+////////////////////////////////////////////////////////////////////
+INLINE int CullTraverserData::
+test_within_clip_planes(const CullTraverser *trav) const {
+  const ClipPlaneAttrib *cpa = _state->get_clip_plane();
+  if (cpa != (ClipPlaneAttrib *)NULL) {
+    return test_within_clip_planes_impl(trav, cpa);
+  }
+  // No clip plane attrib; therefore, the node is not clipped at
+  // all.
+  return BoundingVolume::IF_all | BoundingVolume::IF_possible | BoundingVolume::IF_some;
+}

+ 124 - 37
panda/src/pgraph/cullTraverserData.cxx

@@ -23,6 +23,8 @@
 #include "colorAttrib.h"
 #include "textureAttrib.h"
 #include "renderModeAttrib.h"
+#include "clipPlaneAttrib.h"
+#include "boundingPlane.h"
 #include "billboardEffect.h"
 #include "compassEffect.h"
 #include "polylightEffect.h"
@@ -62,7 +64,8 @@ apply_transform_and_state(CullTraverser *trav) {
   }
 
   apply_transform_and_state(trav, node->get_transform(),
-                            node_state, node->get_effects());
+                            node_state, node->get_effects(),
+                            node->get_off_clip_planes());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -76,7 +79,8 @@ void CullTraverserData::
 apply_transform_and_state(CullTraverser *trav, 
                           CPT(TransformState) node_transform, 
                           CPT(RenderState) node_state,
-                          CPT(RenderEffects) node_effects) {
+                          CPT(RenderEffects) node_effects,
+                          const RenderAttrib *off_clip_planes) {
   if (node_effects->has_cull_callback()) {
     node_effects->cull_callback(trav, *this, node_transform, node_state);
   }
@@ -85,7 +89,8 @@ apply_transform_and_state(CullTraverser *trav,
     _modelview_transform = _modelview_transform->compose(node_transform);
 
     if ((_view_frustum != (GeometricBoundingVolume *)NULL) ||
-        (_guard_band != (GeometricBoundingVolume *)NULL)) {
+        (_guard_band != (GeometricBoundingVolume *)NULL) ||
+        (!_cull_planes->is_empty())) {
       // We need to move the viewing frustums into the node's
       // coordinate space by applying the node's inverse transform.
       if (node_transform->is_singular()) {
@@ -94,6 +99,7 @@ apply_transform_and_state(CullTraverser *trav,
         // point down.
         _view_frustum = (GeometricBoundingVolume *)NULL;
         _guard_band = (GeometricBoundingVolume *)NULL;
+        _cull_planes = CullPlanes::make_empty();
 
       } else {
         CPT(TransformState) inv_transform = 
@@ -110,11 +116,16 @@ apply_transform_and_state(CullTraverser *trav,
           _guard_band = DCAST(GeometricBoundingVolume, _guard_band->make_copy());
           _guard_band->xform(inv_transform->get_mat());
         }
+
+        _cull_planes = _cull_planes->xform(inv_transform->get_mat());
       }
     }
   }
 
   _state = _state->compose(node_state);
+  _cull_planes = _cull_planes->apply_state(trav, this, 
+                                           _state->get_clip_plane(),
+                                           DCAST(ClipPlaneAttrib, off_clip_planes));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -124,55 +135,131 @@ apply_transform_and_state(CullTraverser *trav,
 ////////////////////////////////////////////////////////////////////
 bool CullTraverserData::
 is_in_view_impl() {
-  // By the time we get here, we know we have a viewing frustum.
-  nassertr(_view_frustum != (GeometricBoundingVolume *)NULL, true);
-
   const BoundingVolume &node_volume = node()->get_bound();
   nassertr(node_volume.is_of_type(GeometricBoundingVolume::get_class_type()), false);
   const GeometricBoundingVolume *node_gbv =
     DCAST(GeometricBoundingVolume, &node_volume);
 
-  int result = _view_frustum->contains(node_gbv);
-
-  if (pgraph_cat.is_spam()) {
-    pgraph_cat.spam()
-      << _node_path << " cull result = " << hex << result << dec << "\n";
+  if (_view_frustum != (GeometricBoundingVolume *)NULL) {
+    int result = _view_frustum->contains(node_gbv);
+    
+    if (pgraph_cat.is_spam()) {
+      pgraph_cat.spam()
+        << _node_path << " cull result = " << hex << result << dec << "\n";
+    }
+    
+    if (result == BoundingVolume::IF_no_intersection) {
+      // No intersection at all.  Cull.
+      if (!fake_view_frustum_cull) {
+        return false;
+      }
+      
+      // If we have fake view-frustum culling enabled, instead of
+      // actually culling an object we simply force it to be drawn in
+      // red wireframe.
+      _view_frustum = (GeometricBoundingVolume *)NULL;
+      CPT(RenderState) fake_state = get_fake_view_frustum_cull_state();
+      _state = _state->compose(fake_state);
+      
+    } else if ((result & BoundingVolume::IF_all) != 0) {
+      // The node and its descendents are completely enclosed within
+      // the frustum.  No need to cull further.
+      _view_frustum = (GeometricBoundingVolume *)NULL;
+      _guard_band = (GeometricBoundingVolume *)NULL;
+      
+    } else {
+      // The node is partially, but not completely, within the viewing
+      // frustum.
+      if (node()->is_final()) {
+        // Normally we'd keep testing child bounding volumes as we
+        // continue down.  But this node has the "final" flag, so the
+        // user is claiming that there is some important reason we
+        // should consider everything visible at this point.  So be it.
+        _view_frustum = (GeometricBoundingVolume *)NULL;
+      }
+    }
   }
 
-  if (result == BoundingVolume::IF_no_intersection) {
-    // No intersection at all.  Cull.
-    if (!fake_view_frustum_cull) {
-      return false;
+  if (!_cull_planes->is_empty()) {
+    // Also cull against the current clip planes.
+    int result;
+    _cull_planes = _cull_planes->do_cull(result, _state, node_gbv);
+    
+    if (pgraph_cat.is_spam()) {
+      pgraph_cat.spam()
+        << _node_path << " cull planes cull result = " << hex
+        << result << dec << "\n";
+      _cull_planes->write(pgraph_cat.spam(false));
     }
-
-    // If we have fake view-frustum culling enabled, instead of
-    // actually culling an object we simply force it to be drawn in
-    // red wireframe.
-    _view_frustum = (GeometricBoundingVolume *)NULL;
-    CPT(RenderState) fake_state = get_fake_view_frustum_cull_state();
-    _state = _state->compose(fake_state);
     
-  } else if ((result & BoundingVolume::IF_all) != 0) {
-    // The node and its descendants are completely enclosed within
-    // the frustum.  No need to cull further.
-    _view_frustum = (GeometricBoundingVolume *)NULL;
-    _guard_band = (GeometricBoundingVolume *)NULL;
-
-  } else {
-    if (node()->is_final()) {
-      // The bounding volume is partially, but not completely,
-      // within the viewing frustum.  Normally we'd keep testing
-      // child bounding volumes as we continue down.  But this node
-      // has the "final" flag, so the user is claiming that there is
-      // some important reason we should consider everything visible
-      // at this point.  So be it.
-      _view_frustum = (GeometricBoundingVolume *)NULL;
+    if (result == BoundingVolume::IF_no_intersection) {
+      // No intersection at all.  Cull.
+      return false;
+      
+    } else if ((result & BoundingVolume::IF_all) != 0) {
+      // The node and its descendents are completely in front of all
+      // of the clip planes.  The do_cull() call should therefore have
+      // removed all of the clip planes.
+      nassertr(_cull_planes->is_empty(), true);
+      
+    } else {
+      // The node is partially within one or more clip planes.
+      if (node()->is_final()) {
+        // Even though the node is only partially within the clip
+        // planes, stop culling against them.
+        _cull_planes = CullPlanes::make_empty();
+      }
     }
   }
 
+
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::test_within_clip_planes_impl
+//       Access: Private
+//  Description: The private implementation of test_within_clip_planes().
+////////////////////////////////////////////////////////////////////
+int CullTraverserData::
+test_within_clip_planes_impl(const CullTraverser *trav,
+                             const ClipPlaneAttrib *cpa) const {
+  int result = BoundingVolume::IF_all | BoundingVolume::IF_possible | BoundingVolume::IF_some;
+
+
+  const BoundingVolume &node_volume = node()->get_bound();
+  nassertr(node_volume.is_of_type(GeometricBoundingVolume::get_class_type()), result);
+  const GeometricBoundingVolume *node_gbv =
+    DCAST(GeometricBoundingVolume, &node_volume);
+
+  CPT(TransformState) net_transform = get_net_transform(trav);
+
+  cerr << "considering " << _node_path << ", bv = " << *node_gbv << "\n";
+  cerr << "  net_transform = " << *net_transform << "\n";
+
+  int num_planes = cpa->get_num_on_planes();
+  for (int i = 0; 
+       i < num_planes && result != BoundingVolume::IF_no_intersection; 
+       ++i) {
+    NodePath plane_path = cpa->get_on_plane(i);
+    PlaneNode *plane_node = DCAST(PlaneNode, plane_path.node());
+    CPT(TransformState) new_transform = 
+      net_transform->invert_compose(plane_path.get_net_transform());
+
+    Planef plane = plane_node->get_plane() * new_transform->get_mat();
+    BoundingPlane bplane(-plane);
+    cerr << "  " << bplane << " -> " << bplane.contains(node_gbv) << "\n";
+    result &= bplane.contains(node_gbv);
+  }
+
+  if (pgraph_cat.is_spam()) {
+    pgraph_cat.spam()
+      << _node_path << " test_within_clip_planes result = "
+      << hex << result << dec << "\n";
+  }
+
+  return result;
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CullTraverserData::get_fake_view_frustum_cull_state

+ 9 - 2
panda/src/pgraph/cullTraverserData.h

@@ -20,7 +20,7 @@
 #define CULLTRAVERSERDATA_H
 
 #include "pandabase.h"
-
+#include "cullPlanes.h"
 #include "workingNodePath.h"
 #include "renderState.h"
 #include "transformState.h"
@@ -64,20 +64,27 @@ public:
   CPT(TransformState) get_net_transform(const CullTraverser *trav) const;
 
   INLINE bool is_in_view(const DrawMask &camera_mask);
+  INLINE int test_within_clip_planes(const CullTraverser *trav) const;
+
   void apply_transform_and_state(CullTraverser *trav);
   void apply_transform_and_state(CullTraverser *trav, 
                                  CPT(TransformState) node_transform, 
                                  CPT(RenderState) node_state,
-                                 CPT(RenderEffects) node_effects);
+                                 CPT(RenderEffects) node_effects,
+                                 const RenderAttrib *off_clip_planes);
 
   WorkingNodePath _node_path;
   CPT(TransformState) _modelview_transform;
   CPT(RenderState) _state;
   PT(GeometricBoundingVolume) _view_frustum;
   PT(GeometricBoundingVolume) _guard_band;
+  CPT(CullPlanes) _cull_planes;
 
 private:
   bool is_in_view_impl();
+  int test_within_clip_planes_impl(const CullTraverser *trav,
+                                   const ClipPlaneAttrib *cpa) const;
+
   static CPT(RenderState) get_fake_view_frustum_cull_state();
 };
 

+ 30 - 1
panda/src/pgraph/modelNode.I

@@ -27,6 +27,7 @@ ModelNode(const string &name) :
   PandaNode(name)
 {
   _preserve_transform = PT_none;
+  _preserve_attributes = 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -66,6 +67,33 @@ get_preserve_transform() const {
   return _preserve_transform;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ModelNode::set_preserve_attributes
+//       Access: Public
+//  Description: Sets the preserve_attributes flag.  This restricts the
+//               ability of a flatten operation to affect the
+//               render attributes stored on this node.
+//
+//               The value should be the union of bits from
+//               SceneGraphReducer::AttribTypes that represent the
+//               attributes that should *not* be changed.
+////////////////////////////////////////////////////////////////////
+INLINE void ModelNode::
+set_preserve_attributes(int preserve_attributes) {
+  _preserve_attributes = preserve_attributes;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ModelNode::get_preserve_attributes
+//       Access: Public
+//  Description: Returns the current setting of the preserve_attributes
+//               flag.  See set_preserve_attributes().
+////////////////////////////////////////////////////////////////////
+INLINE int ModelNode::
+get_preserve_attributes() const {
+  return _preserve_attributes;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ModelNode::Copy Constructor
 //       Access: Protected
@@ -74,6 +102,7 @@ get_preserve_transform() const {
 INLINE ModelNode::
 ModelNode(const ModelNode &copy) :
   PandaNode(copy),
-  _preserve_transform(copy._preserve_transform)
+  _preserve_transform(copy._preserve_transform),
+  _preserve_attributes(copy._preserve_attributes)
 {
 }

+ 21 - 0
panda/src/pgraph/modelNode.cxx

@@ -109,6 +109,22 @@ preserve_name() const {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ModelNode::get_unsafe_to_apply_attribs
+//       Access: Public, Virtual
+//  Description: Returns the union of all attributes from
+//               SceneGraphReducer::AttribTypes that may not safely be
+//               applied to the vertices of this node.  If this is
+//               nonzero, these attributes must be dropped at this
+//               node as a state change.
+//
+//               This is a generalization of safe_to_transform().
+////////////////////////////////////////////////////////////////////
+int ModelNode::
+get_unsafe_to_apply_attribs() const {
+  return _preserve_attributes;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: ModelNode::register_with_read_factory
 //       Access: Public, Static
@@ -130,6 +146,7 @@ void ModelNode::
 write_datagram(BamWriter *manager, Datagram &dg) {
   PandaNode::write_datagram(manager, dg);
   dg.add_uint8((int)_preserve_transform);
+  dg.add_uint16(_preserve_attributes);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -164,4 +181,8 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   PandaNode::fillin(scan, manager);
 
   _preserve_transform = (PreserveTransform)scan.get_uint8();
+  _preserve_attributes = 0;
+  if (manager->get_file_minor_ver() >= 3) {
+    _preserve_attributes = scan.get_uint16();
+  }
 }

+ 5 - 0
panda/src/pgraph/modelNode.h

@@ -50,6 +50,7 @@ public:
   virtual bool safe_to_modify_transform() const;
   virtual bool safe_to_combine() const;
   virtual bool preserve_name() const;
+  virtual int get_unsafe_to_apply_attribs() const;
 
 PUBLISHED:
   enum PreserveTransform {
@@ -61,8 +62,12 @@ PUBLISHED:
   INLINE void set_preserve_transform(PreserveTransform preserve_transform);
   INLINE PreserveTransform get_preserve_transform() const;
 
+  INLINE void set_preserve_attributes(int attrib_mask);
+  INLINE int get_preserve_attributes() const;
+
 private:
   PreserveTransform _preserve_transform;
+  int _preserve_attributes;
 
 public:
   static void register_with_read_factory();

+ 63 - 11
panda/src/pgraph/pandaNode.I

@@ -436,7 +436,15 @@ get_stashed_sort(int n) const {
 INLINE void PandaNode::
 set_attrib(const RenderAttrib *attrib, int override) {
   CDWriter cdata(_cycler);
-  cdata->_state = cdata->_state->add_attrib(attrib, override);
+  CPT(RenderState) new_state = cdata->_state->add_attrib(attrib, override);
+  if (cdata->_state != new_state) {
+    cdata->_state = new_state;
+
+    // We mark the bound stale when the state changes, in case we have
+    // changed a ClipPlaneAttrib.
+    mark_bound_stale();
+    state_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -483,7 +491,15 @@ has_attrib(TypeHandle type) const {
 INLINE void PandaNode::
 clear_attrib(TypeHandle type) {
   CDWriter cdata(_cycler);
-  cdata->_state = cdata->_state->remove_attrib(type);
+  CPT(RenderState) new_state = cdata->_state->remove_attrib(type);
+  if (cdata->_state != new_state) {
+    cdata->_state = new_state;
+  
+    // We mark the bound stale when the state changes, in case we have
+    // changed a ClipPlaneAttrib.
+    mark_bound_stale();
+    state_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -554,7 +570,14 @@ clear_effect(TypeHandle type) {
 INLINE void PandaNode::
 set_state(const RenderState *state) {
   CDWriter cdata(_cycler);
-  cdata->_state = state;
+  if (cdata->_state != state) {
+    cdata->_state = state;
+
+    // We mark the bound stale when the state changes, in case we have
+    // changed a ClipPlaneAttrib.
+    mark_bound_stale();
+    state_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -583,7 +606,14 @@ get_state() const {
 INLINE void PandaNode::
 clear_state() {
   CDWriter cdata(_cycler);
-  cdata->_state = RenderState::make_empty();
+  if (!cdata->_state->is_empty()) {
+    cdata->_state = RenderState::make_empty();
+
+    // We mark the bound stale when the state changes, in case we have
+    // changed a ClipPlaneAttrib.
+    mark_bound_stale();
+    state_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -633,9 +663,11 @@ clear_effects() {
 INLINE void PandaNode::
 set_transform(const TransformState *transform) {
   CDWriter cdata(_cycler);
-  cdata->_transform = transform;
-  mark_bound_stale();
-  transform_changed();
+  if (cdata->_transform != transform) {
+    cdata->_transform = transform;
+    mark_bound_stale();
+    transform_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -661,9 +693,11 @@ get_transform() const {
 INLINE void PandaNode::
 clear_transform() {
   CDWriter cdata(_cycler);
-  cdata->_transform = TransformState::make_identity();
-  mark_bound_stale();
-  transform_changed();
+  if (!cdata->_transform->is_identity()) {
+    cdata->_transform = TransformState::make_identity();
+    mark_bound_stale();
+    transform_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -819,7 +853,10 @@ ls(ostream &out, int indent_level) const {
 INLINE void PandaNode::
 set_draw_mask(DrawMask mask) {
   CDWriter cdata(_cycler);
-  cdata->_draw_mask = mask;
+  if (cdata->_draw_mask != mask) {
+    cdata->_draw_mask = mask;
+    draw_mask_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -887,6 +924,21 @@ get_net_collide_mask() const {
   return cdata->_net_collide_mask;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_off_clip_planes
+//       Access: Published
+//  Description: Returns a ClipPlaneAttrib which represents the union
+//               of all of the clip planes that have been turned *off*
+//               at this level and below.
+////////////////////////////////////////////////////////////////////
+INLINE const RenderAttrib *PandaNode::
+get_off_clip_planes() const {
+  // Call get_bound() first to ensure the attrib is recomputed.
+  BoundedObject::get_bound();
+  CDReader cdata(_cycler);
+  return cdata->_off_clip_planes;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::set_bound
 //       Access: Published

+ 31 - 0
panda/src/pgraph/pandaNode.cxx

@@ -25,6 +25,7 @@
 #include "geometricBoundingVolume.h"
 #include "sceneGraphReducer.h"
 #include "accumulatedAttribs.h"
+#include "clipPlaneAttrib.h"
 
 
 TypeHandle PandaNode::_type_handle;
@@ -1581,6 +1582,12 @@ recompute_bound() {
   CDWriter cdata(_cycler);
   cdata->_net_collide_mask = cdata->_into_collide_mask;
 
+  // And compute the set of "off" clip planes.
+  cdata->_off_clip_planes = cdata->_state->get_clip_plane();
+  if (cdata->_off_clip_planes == (RenderAttrib *)NULL) {
+    cdata->_off_clip_planes = ClipPlaneAttrib::make();
+  }
+
   // Now actually compute the bounding volume by putting it around all
   // of our child bounding volumes.
   pvector<const BoundingVolume *> child_volumes;
@@ -1595,6 +1602,8 @@ recompute_bound() {
     const BoundingVolume &child_bound = child->get_bound();
     child_volumes.push_back(&child_bound);
     cdata->_net_collide_mask |= child->get_net_collide_mask();
+    CPT(ClipPlaneAttrib) orig = DCAST(ClipPlaneAttrib, cdata->_off_clip_planes);
+    cdata->_off_clip_planes = orig->compose_off(child->get_off_clip_planes());
   }
 
   const BoundingVolume **child_begin = &child_volumes[0];
@@ -1678,6 +1687,28 @@ void PandaNode::
 transform_changed() {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::state_changed
+//       Access: Protected, Virtual
+//  Description: Called after the node's RenderState has been changed
+//               for any reason, this just provides a hook so derived
+//               classes can do something special in this case.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+state_changed() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::draw_mask_changed
+//       Access: Protected, Virtual
+//  Description: Called after the node's DrawMask has been changed
+//               for any reason, this just provides a hook so derived
+//               classes can do something special in this case.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+draw_mask_changed() {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::r_copy_subgraph
 //       Access: Protected, Virtual

+ 10 - 0
panda/src/pgraph/pandaNode.h

@@ -187,6 +187,7 @@ PUBLISHED:
   virtual CollideMask get_legal_collide_mask() const;
 
   INLINE CollideMask get_net_collide_mask() const;
+  INLINE const RenderAttrib *get_off_clip_planes() const;
 
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
@@ -225,6 +226,8 @@ protected:
   virtual void parents_changed();
   virtual void children_changed();
   virtual void transform_changed();
+  virtual void state_changed();
+  virtual void draw_mask_changed();
   INLINE void add_net_collide_mask(CollideMask mask);
 
   typedef pmap<PandaNode *, PandaNode *> InstanceMap;
@@ -363,6 +366,13 @@ private:
     // we update them together.
     CollideMask _net_collide_mask;
 
+    // This is a ClipPlaneAttrib that represents the union of all clip
+    // planes that have been turned *off* at and below this level.  As
+    // above, it's similar to a bounding volume, and is updated at the
+    // same time.  TODO: fix the circular reference counts involved
+    // here.
+    CPT(RenderAttrib) _off_clip_planes;
+
     bool _fixed_internal_bound;
   };
 

+ 1 - 0
panda/src/pgraph/pgraph_composite2.cxx

@@ -8,6 +8,7 @@
 #include "cullBinUnsorted.cxx"
 #include "cullFaceAttrib.cxx"
 #include "cullHandler.cxx"
+#include "cullPlanes.cxx"
 #include "cullResult.cxx"
 #include "cullTraverser.cxx"
 #include "cullTraverserData.cxx"

+ 2 - 1
panda/src/pgraph/planeNode.cxx

@@ -66,10 +66,11 @@ fillin(DatagramIterator &scan, BamReader *) {
 //  Description:
 ////////////////////////////////////////////////////////////////////
 PlaneNode::
-PlaneNode(const string &name) :
+PlaneNode(const string &name, const Planef &plane) :
   PandaNode(name),
   _priority(0)
 {
+  set_plane(plane);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/pgraph/planeNode.h

@@ -34,7 +34,7 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA PlaneNode : public PandaNode {
 PUBLISHED:
-  PlaneNode(const string &name);
+  PlaneNode(const string &name, const Planef &plane = Planef());
 
 protected:
   PlaneNode(const PlaneNode &copy);

+ 19 - 0
panda/src/pgraph/renderState.I

@@ -336,6 +336,25 @@ get_render_mode() const {
   return _render_mode;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RenderState::get_clip_plane
+//       Access: Published
+//  Description: This function is provided as an optimization, to
+//               speed up the render-time checking for the existance
+//               of a ClipPlaneAttrib on this state.  It returns a
+//               pointer to the ClipPlaneAttrib, if there is one, or
+//               NULL if there is not.
+////////////////////////////////////////////////////////////////////
+INLINE const ClipPlaneAttrib *RenderState::
+get_clip_plane() const {
+  if ((_flags & F_checked_clip_plane) == 0) {
+    // We pretend this function is const, even though it transparently
+    // modifies the internal clip_plane cache.
+    ((RenderState *)this)->determine_clip_plane();
+  }
+  return _clip_plane;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::set_destructing
 //       Access: Private

+ 16 - 0
panda/src/pgraph/renderState.cxx

@@ -21,6 +21,7 @@
 #include "cullBinAttrib.h"
 #include "cullBinManager.h"
 #include "fogAttrib.h"
+#include "clipPlaneAttrib.h"
 #include "transparencyAttrib.h"
 #include "colorAttrib.h"
 #include "colorScaleAttrib.h"
@@ -1647,6 +1648,21 @@ determine_render_mode() {
   _flags |= F_checked_render_mode;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: RenderState::determine_clip_plane
+//       Access: Private
+//  Description: This is the private implementation of get_clip_plane().
+////////////////////////////////////////////////////////////////////
+void RenderState::
+determine_clip_plane() {
+  const RenderAttrib *attrib = get_attrib(ClipPlaneAttrib::get_class_type());
+  _clip_plane = (const ClipPlaneAttrib *)NULL;
+  if (attrib != (const RenderAttrib *)NULL) {
+    _clip_plane = DCAST(ClipPlaneAttrib, attrib);
+  }
+  _flags |= F_checked_clip_plane;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: RenderState::update_pstats
 //       Access: Private

+ 5 - 0
panda/src/pgraph/renderState.h

@@ -40,6 +40,7 @@ class ColorAttrib;
 class ColorScaleAttrib;
 class TextureAttrib;
 class TexGenAttrib;
+class ClipPlaneAttrib;
 class FactoryParams;
 
 ////////////////////////////////////////////////////////////////////
@@ -133,6 +134,7 @@ PUBLISHED:
   INLINE const TexGenAttrib *get_tex_gen() const;
   INLINE const TexMatrixAttrib *get_tex_matrix() const;
   INLINE const RenderModeAttrib *get_render_mode() const;
+  INLINE const ClipPlaneAttrib *get_clip_plane() const;
 
   int get_geom_rendering(int geom_rendering) const;
 
@@ -177,6 +179,7 @@ private:
   void determine_tex_gen();
   void determine_tex_matrix();
   void determine_render_mode();
+  void determine_clip_plane();
 
   INLINE void set_destructing();
   INLINE bool is_destructing() const;
@@ -274,6 +277,7 @@ private:
   const TexGenAttrib *_tex_gen;
   const TexMatrixAttrib *_tex_matrix;
   const RenderModeAttrib *_render_mode;
+  const ClipPlaneAttrib *_clip_plane;
 
   enum Flags {
     F_checked_bin_index    = 0x0001,
@@ -286,6 +290,7 @@ private:
     F_checked_tex_gen      = 0x0080,
     F_checked_tex_matrix   = 0x0100,
     F_checked_render_mode  = 0x0200,
+    F_checked_clip_plane   = 0x0400,
     F_is_destructing       = 0x8000,
   };
   unsigned short _flags;

+ 21 - 9
panda/src/pgui/Sources.pp

@@ -12,49 +12,61 @@
   #define SOURCES  \
     config_pgui.h \
     pgButton.I pgButton.h \
-    pgSliderButton.I pgSliderButton.h \
+    pgButtonNotify.I pgButtonNotify.h \
     pgCullTraverser.I pgCullTraverser.h \
     pgEntry.I pgEntry.h \
     pgMouseWatcherGroup.I pgMouseWatcherGroup.h \
     pgMouseWatcherParameter.I pgMouseWatcherParameter.h \
     pgFrameStyle.I pgFrameStyle.h \
     pgItem.I pgItem.h \
+    pgItemNotify.I pgItemNotify.h \
     pgMouseWatcherBackground.h \
     pgMouseWatcherRegion.I pgMouseWatcherRegion.h \
+    pgScrollFrame.I pgScrollFrame.h \
+    pgSliderBar.I pgSliderBar.h \
+    pgSliderBarNotify.I pgSliderBarNotify.h \
     pgTop.I pgTop.h \
-    pgWaitBar.I pgWaitBar.h \
-    pgSliderBar.I pgSliderBar.h
+    pgVirtualFrame.I pgVirtualFrame.h \
+    pgWaitBar.I pgWaitBar.h
     
   #define INCLUDED_SOURCES  \
     config_pgui.cxx \
     pgButton.cxx \
-    pgSliderButton.cxx \
+    pgButtonNotify.cxx \
     pgCullTraverser.cxx \
     pgEntry.cxx \
     pgMouseWatcherGroup.cxx \
     pgMouseWatcherParameter.cxx \
     pgFrameStyle.cxx \
     pgItem.cxx \
+    pgItemNotify.cxx \
     pgMouseWatcherBackground.cxx \
     pgMouseWatcherRegion.cxx \
+    pgScrollFrame.cxx \
+    pgSliderBar.cxx \
+    pgSliderBarNotify.cxx \
     pgTop.cxx \
-    pgWaitBar.cxx \
-    pgSliderBar.cxx
+    pgVirtualFrame.cxx \
+    pgWaitBar.cxx
 
   #define INSTALL_HEADERS \
     pgButton.I pgButton.h \
-    pgSliderButton.I pgSliderButton.h \
+    pgButtonNotify.I pgButtonNotify.h \
     pgCullTraverser.I pgCullTraverser.h \
     pgEntry.I pgEntry.h \
     pgMouseWatcherGroup.I pgMouseWatcherGroup.h \
     pgMouseWatcherParameter.I pgMouseWatcherParameter.h \
     pgFrameStyle.I pgFrameStyle.h \
     pgItem.I pgItem.h \
+    pgItemNotify.I pgItemNotify.h \
     pgMouseWatcherBackground.h \
     pgMouseWatcherRegion.I pgMouseWatcherRegion.h \
+    pgScrollFrame.I pgScrollFrame.h \
+    pgSliderBar.I pgSliderBar.h \
+    pgSliderBarNotify.I pgSliderBarNotify.h \
     pgTop.I pgTop.h \
-    pgWaitBar.I pgWaitBar.h \
-    pgSliderBar.I pgSliderBar.h
+    pgVirtualFrame.I pgVirtualFrame.h \
+    pgWaitBar.I pgWaitBar.h
     
 
   #define IGATESCAN all

+ 18 - 4
panda/src/pgui/config_pgui.cxx

@@ -18,7 +18,6 @@
 
 #include "config_pgui.h"
 #include "pgButton.h"
-#include "pgSliderButton.h"
 #include "pgCullTraverser.h"
 #include "pgEntry.h"
 #include "pgMouseWatcherParameter.h"
@@ -26,9 +25,11 @@
 #include "pgItem.h"
 #include "pgMouseWatcherBackground.h"
 #include "pgMouseWatcherRegion.h"
+#include "pgScrollFrame.h"
+#include "pgSliderBar.h"
 #include "pgTop.h"
+#include "pgVirtualFrame.h"
 #include "pgWaitBar.h"
-#include "pgSliderBar.h"
 
 #include "dconfig.h"
 
@@ -39,6 +40,18 @@ ConfigureFn(config_pgui) {
   init_libpgui();
 }
 
+ConfigVariableDouble scroll_initial_delay
+("scroll-initial-delay", 0.3,
+ PRC_DESC("This is the amount of time, in seconds, to delay after the user "
+          "first clicks and holds on a scrollbar button before the scrolling "
+          "continues automatically."));
+
+ConfigVariableDouble scroll_continued_delay
+("scroll-continued-delay", 0.1,
+ PRC_DESC("This is the amount of time, in seconds, to delay between lines "
+          "scrolled while the user is continuing to hold down the scrollbar "
+          "button."));
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libpgui
 //  Description: Initializes the library.  This must be called at
@@ -56,7 +69,6 @@ init_libpgui() {
   initialized = true;
 
   PGButton::init_type();
-  PGSliderButton::init_type();
   PGCullTraverser::init_type();
   PGEntry::init_type();
   PGMouseWatcherParameter::init_type();
@@ -64,7 +76,9 @@ init_libpgui() {
   PGItem::init_type();
   PGMouseWatcherBackground::init_type();
   PGMouseWatcherRegion::init_type();
+  PGScrollFrame::init_type();
+  PGSliderBar::init_type();
   PGTop::init_type();
+  PGVirtualFrame::init_type();
   PGWaitBar::init_type();
-  PGSliderBar::init_type();
 }

+ 3 - 0
panda/src/pgui/config_pgui.h

@@ -21,10 +21,13 @@
 
 #include "pandabase.h"
 #include "notifyCategoryProxy.h"
+#include "configVariableDouble.h"
 
 NotifyCategoryDecl(pgui, EXPCL_PANDA, EXPTP_PANDA);
 
 // Configure variables for pgui package.
+extern ConfigVariableDouble scroll_initial_delay;
+extern ConfigVariableDouble scroll_continued_delay;
 
 extern EXPCL_PANDA void init_libpgui();
 

+ 26 - 0
panda/src/pgui/pgButton.I

@@ -17,6 +17,32 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGButton::set_notify
+//       Access: Published
+//  Description: Sets the object which will be notified when the
+//               PGButton changes.  Set this to NULL to disable
+//               this effect.  The PGButton does not retain
+//               ownership of the pointer; it is your responsibility
+//               to ensure that the notify object does not destruct.
+////////////////////////////////////////////////////////////////////
+INLINE void PGButton:: 
+set_notify(PGButtonNotify *notify) {
+  PGItem::set_notify(notify);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGButton::get_notify
+//       Access: Published
+//  Description: Returns the object which will be notified when the
+//               PGButton changes, if any.  Returns NULL if there
+//               is no such object configured.
+////////////////////////////////////////////////////////////////////
+INLINE PGButtonNotify *PGButton:: 
+get_notify() const {
+  return (PGButtonNotify *)PGItem::get_notify();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGButton::setup
 //       Access: Published

+ 5 - 13
panda/src/pgui/pgButton.cxx

@@ -156,18 +156,10 @@ click(const MouseWatcherParameter &param) {
   string event = get_click_event(param.get_button());
   play_sound(event);
   throw_event(event, EventParameter(ep));
-}
 
-////////////////////////////////////////////////////////////////////
-//     Function: PGButton::move
-//       Access: Public, Virtual
-//  Description: This is a callback hook function, called whenever a
-//               mouse is moved while the mouse
-//               is within the region.
-////////////////////////////////////////////////////////////////////
-void PGButton::
-move(const MouseWatcherParameter &param) {
-  PGItem::move(param);
+  if (has_notify()) {
+    get_notify()->button_click(this, param);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -180,7 +172,7 @@ move(const MouseWatcherParameter &param) {
 //               according to the size of the text.
 ////////////////////////////////////////////////////////////////////
 void PGButton::
-setup(const string &label) {
+setup(const string &label, float bevel) {
   clear_state_def(S_ready);
   clear_state_def(S_depressed);
   clear_state_def(S_rollover);
@@ -200,7 +192,7 @@ setup(const string &label) {
 
   PGFrameStyle style;
   style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
-  style.set_width(0.1f, 0.1f);
+  style.set_width(bevel, bevel);
 
   style.set_type(PGFrameStyle::T_bevel_out);
   set_frame_style(S_ready, style);

+ 7 - 4
panda/src/pgui/pgButton.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 
 #include "pgItem.h"
+#include "pgButtonNotify.h"
 #include "nodePath.h"
 #include "pset.h"
 
@@ -37,6 +38,9 @@ PUBLISHED:
   PGButton(const string &name);
   virtual ~PGButton();
 
+protected:
+  PGButton(const PGButton &copy);
+
 public:
   virtual PandaNode *make_copy() const;
 
@@ -46,10 +50,9 @@ public:
   virtual void release(const MouseWatcherParameter &param, bool background);
 
   virtual void click(const MouseWatcherParameter &param);
-  
-  virtual void move(const MouseWatcherParameter &param);
 
-  PGButton(const PGButton &copy);
+  INLINE void set_notify(PGButtonNotify *notify);
+  INLINE PGButtonNotify *get_notify() const;
 
 PUBLISHED:
   enum State {
@@ -59,7 +62,7 @@ PUBLISHED:
     S_inactive
   };
 
-  void setup(const string &label);
+  void setup(const string &label, float bevel = 0.1f);
   INLINE void setup(const NodePath &ready);
   INLINE void setup(const NodePath &ready, const NodePath &depressed);
   INLINE void setup(const NodePath &ready, const NodePath &depressed, 

+ 27 - 0
panda/src/pgui/pgButtonNotify.I

@@ -0,0 +1,27 @@
+// Filename: pgButtonNotify.I
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGButtonNotify::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PGButtonNotify::
+PGButtonNotify() {
+}

+ 29 - 0
panda/src/pgui/pgButtonNotify.cxx

@@ -0,0 +1,29 @@
+// Filename: pgButtonNotify.cxx
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgButtonNotify.h"
+#include "pgButton.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGButtonNotify::button_click
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGButton has been clicked.
+////////////////////////////////////////////////////////////////////
+void PGButtonNotify::
+button_click(PGButton *, const MouseWatcherParameter &) {
+}

+ 45 - 0
panda/src/pgui/pgButtonNotify.h

@@ -0,0 +1,45 @@
+// Filename: pgButtonNotify.h
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGBUTTONNOTIFY_H
+#define PGBUTTONNOTIFY_H
+
+#include "pandabase.h"
+#include "pgItemNotify.h"
+
+class PGButton;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGButtonNotify
+// Description : Objects that inherit from this class can receive
+//               notify messages when a slider bar moves or otherwise
+//               is reconfigured.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGButtonNotify : public PGItemNotify {
+public:
+  INLINE PGButtonNotify();
+
+protected:
+  virtual void button_click(PGButton *button, const MouseWatcherParameter &param);
+
+  friend class PGButton;
+};
+
+#include "pgButtonNotify.I"
+
+#endif

+ 46 - 1
panda/src/pgui/pgFrameStyle.I

@@ -27,6 +27,7 @@ PGFrameStyle() {
   _type = T_none;
   _color.set(1.0f, 1.0f, 1.0f, 1.0f);
   _width.set(0.1f, 0.1f);
+  _visible_scale.set(1.0f, 1.0f);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -39,7 +40,8 @@ PGFrameStyle(const PGFrameStyle &copy) :
   _type(copy._type),
   _color(copy._color),
   _texture(copy._texture),
-  _width(copy._width)
+  _width(copy._width),
+  _visible_scale(copy._visible_scale)
 {
 }
 
@@ -54,6 +56,7 @@ operator = (const PGFrameStyle &copy) {
   _color = copy._color;
   _texture = copy._texture;
   _width = copy._width;
+  _visible_scale = copy._visible_scale;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -197,6 +200,48 @@ get_width() const {
   return _width;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGFrameStyle::set_visible_scale
+//       Access: Published
+//  Description: Sets a scale factor on the visible representation of
+//               the frame, in the X and Y directions.  If this scale
+//               factor is other than 1, it will affect the size of
+//               the visible frame representation within the actual
+//               frame border.
+////////////////////////////////////////////////////////////////////
+INLINE void PGFrameStyle::
+set_visible_scale(float x, float y) {
+  set_visible_scale(LVecBase2f(x, y));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGFrameStyle::set_visible_scale
+//       Access: Published
+//  Description: Sets a scale factor on the visible representation of
+//               the frame, in the X and Y directions.  If this scale
+//               factor is other than 1, it will affect the size of
+//               the visible frame representation within the actual
+//               frame border.
+////////////////////////////////////////////////////////////////////
+INLINE void PGFrameStyle::
+set_visible_scale(const LVecBase2f &visible_scale) {
+  _visible_scale = visible_scale;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGFrameStyle::get_visible_scale
+//       Access: Published
+//  Description: Returns the scale factor on the visible
+//               representation of the frame, in the X and Y
+//               directions.  If this scale factor is other than 1, it
+//               will affect the size of the visible frame
+//               representation within the actual frame border.
+////////////////////////////////////////////////////////////////////
+INLINE const LVecBase2f &PGFrameStyle::
+get_visible_scale() const {
+  return _visible_scale;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGFrameStyle ostream output
 //  Description: 

+ 49 - 5
panda/src/pgui/pgFrameStyle.cxx

@@ -60,6 +60,39 @@ operator << (ostream &out, PGFrameStyle::Type type) {
   return out << "**unknown(" << (int)type << ")**";
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGFrameStyle::get_internal_frame
+//       Access: Published
+//  Description: Computes the size of the internal frame, given the
+//               indicated external frame, appropriate for this kind
+//               of frame style.  This simply subtracts the border
+//               width for those frame styles that include a border.
+////////////////////////////////////////////////////////////////////
+LVecBase4f PGFrameStyle::
+get_internal_frame(const LVecBase4f &frame) const {
+  LPoint2f center((frame[0] + frame[1]) / 2.0f,
+                  (frame[2] + frame[3]) / 2.0f);
+  LVecBase4f scaled_frame
+    ((frame[0] - center[0]) * _visible_scale[0] + center[0],
+     (frame[1] - center[0]) * _visible_scale[0] + center[0],
+     (frame[2] - center[1]) * _visible_scale[1] + center[1],
+     (frame[3] - center[1]) * _visible_scale[1] + center[1]);
+
+  switch (_type) {
+  case T_none:
+  case T_flat:
+    return scaled_frame;
+
+  default:
+    break;
+  }
+
+  return LVecBase4f(scaled_frame[0] + _width[0],
+                    scaled_frame[1] - _width[0],
+                    scaled_frame[2] + _width[1],
+                    scaled_frame[3] - _width[1]);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGFrameStyle::output
 //       Access: Published
@@ -68,6 +101,9 @@ operator << (ostream &out, PGFrameStyle::Type type) {
 void PGFrameStyle::
 output(ostream &out) const {
   out << _type << " color = " << _color << " width = " << _width;
+  if (_visible_scale != LVecBase2f(1.0f, 1.0f)) {
+    out << "visible_scale = " << get_visible_scale();
+  }
   if (has_texture()) {
     out << " texture = " << *get_texture();
   }
@@ -126,28 +162,36 @@ NodePath PGFrameStyle::
 generate_into(const NodePath &parent, const LVecBase4f &frame) {
   PT(PandaNode) new_node;
 
+  LPoint2f center((frame[0] + frame[1]) / 2.0f,
+                  (frame[2] + frame[3]) / 2.0f);
+  LVecBase4f scaled_frame
+    ((frame[0] - center[0]) * _visible_scale[0] + center[0],
+     (frame[1] - center[0]) * _visible_scale[0] + center[0],
+     (frame[2] - center[1]) * _visible_scale[1] + center[1],
+     (frame[3] - center[1]) * _visible_scale[1] + center[1]);
+
   switch (_type) {
   case T_none:
     return NodePath();
 
   case T_flat:
-    new_node = generate_flat_geom(frame);
+    new_node = generate_flat_geom(scaled_frame);
     break;
 
   case T_bevel_out:
-    new_node = generate_bevel_geom(frame, false);
+    new_node = generate_bevel_geom(scaled_frame, false);
     break;
 
   case T_bevel_in:
-    new_node = generate_bevel_geom(frame, true);
+    new_node = generate_bevel_geom(scaled_frame, true);
     break;
 
   case T_groove:
-    new_node = generate_groove_geom(frame, true);
+    new_node = generate_groove_geom(scaled_frame, true);
     break;
 
   case T_ridge:
-    new_node = generate_groove_geom(frame, false);
+    new_node = generate_groove_geom(scaled_frame, false);
     break;
 
   default:

+ 7 - 0
panda/src/pgui/pgFrameStyle.h

@@ -65,6 +65,12 @@ PUBLISHED:
   INLINE void set_width(const LVecBase2f &width);
   INLINE const LVecBase2f &get_width() const;
 
+  INLINE void set_visible_scale(float x, float y);
+  INLINE void set_visible_scale(const LVecBase2f &visible_scale);
+  INLINE const LVecBase2f &get_visible_scale() const;
+
+  LVecBase4f get_internal_frame(const LVecBase4f &frame) const;
+
   void output(ostream &out) const;
 
 public:
@@ -81,6 +87,7 @@ private:
   Colorf _color;
   PT(Texture) _texture;
   LVecBase2f _width;
+  LVecBase2f _visible_scale;
 };
 
 INLINE ostream &operator << (ostream &out, const PGFrameStyle &pfs);

+ 81 - 5
panda/src/pgui/pgItem.I

@@ -33,6 +33,49 @@ get_region() const {
   return _region;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::set_notify
+//       Access: Published
+//  Description: Sets the object which will be notified when the
+//               PGItem changes.  Set this to NULL to disable
+//               this effect.  The PGItem does not retain
+//               ownership of the pointer; it is your responsibility
+//               to ensure that the notify object does not destruct.
+////////////////////////////////////////////////////////////////////
+INLINE void PGItem:: 
+set_notify(PGItemNotify *notify) {
+  if (_notify != (PGItemNotify *)NULL) {
+    _notify->remove_item(this);
+  }
+  _notify = notify;
+  if (_notify != (PGItemNotify *)NULL) {
+    _notify->add_item(this);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::has_notify
+//       Access: Published
+//  Description: Returns true if there is an object configured to be
+//               notified when the PGItem changes, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool PGItem:: 
+has_notify() const {
+  return (_notify != (PGItemNotify *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_notify
+//       Access: Published
+//  Description: Returns the object which will be notified when the
+//               PGItem changes, if any.  Returns NULL if there
+//               is no such object configured.
+////////////////////////////////////////////////////////////////////
+INLINE PGItemNotify *PGItem:: 
+get_notify() const {
+  return _notify;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::set_frame
 //       Access: Published
@@ -58,9 +101,11 @@ set_frame(float left, float right, float bottom, float top) {
 ////////////////////////////////////////////////////////////////////
 INLINE void PGItem::
 set_frame(const LVecBase4f &frame) {
-  _has_frame = true;
-  _frame = frame;
-  mark_frames_stale();
+  if (!_has_frame || _frame != frame) {
+    _has_frame = true;
+    _frame = frame;
+    frame_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -96,8 +141,10 @@ has_frame() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void PGItem::
 clear_frame() {
-  _has_frame = false;
-  mark_frames_stale();
+  if (_has_frame) {
+    _has_frame = false;
+    frame_changed();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -476,3 +523,32 @@ INLINE LMatrix4f PGItem::
 get_frame_inv_xform() const {
   return _frame_inv_xform;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::compute_area
+//       Access: Private, Static
+//  Description: Computes the area of the indicated frame.
+////////////////////////////////////////////////////////////////////
+INLINE float PGItem::
+compute_area(const LVecBase4f &frame) {
+  return (frame[1] - frame[0]) * (frame[3] - frame[2]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::compare_largest
+//       Access: Private, Static
+//  Description: Given that largest is the pointer to the largest
+//               frame so far, and largest_area is its area, compare
+//               that to the area of the new frame; if the new frame
+//               is larger, adjust largest and largest_area
+//               appropriately.
+////////////////////////////////////////////////////////////////////
+INLINE void PGItem::
+compare_largest(const LVecBase4f *&largest, float &largest_area, 
+                const LVecBase4f *new_frame) {
+  float new_area = compute_area(*new_frame);
+  if (new_area > largest_area) {
+    largest = new_frame;
+    largest_area = new_area;
+  }
+}

+ 378 - 41
panda/src/pgui/pgItem.cxx

@@ -20,7 +20,7 @@
 #include "pgMouseWatcherParameter.h"
 #include "pgCullTraverser.h"
 #include "config_pgui.h"
-
+#include "boundingVolume.h"
 #include "pandaNode.h"
 #include "sceneGraphReducer.h"
 #include "throw_event.h"
@@ -29,6 +29,7 @@
 #include "cullTraverser.h"
 #include "cullTraverserData.h"
 #include "cullBinManager.h"
+#include "clipPlaneAttrib.h"
 #include "dcast.h"
 
 #ifdef HAVE_AUDIO
@@ -40,6 +41,16 @@ PT(TextNode) PGItem::_text_node;
 PGItem *PGItem::_focus_item = (PGItem *)NULL;
 PGItem::BackgroundFocus PGItem::_background_focus;
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: is_right
+//  Description: Returns true if the 2-d v1 is to the right of v2.
+////////////////////////////////////////////////////////////////////
+INLINE bool
+is_right(const LVector2f &v1, const LVector2f &v2) {
+  return (-v1[0] * v2[1] + v1[1] * v2[0]) > 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::Constructor
 //       Access: Published
@@ -49,6 +60,7 @@ PGItem::
 PGItem(const string &name) : 
   PandaNode(name)
 {
+  _notify = NULL;
   _has_frame = false;
   _frame.set(0, 0, 0, 0);
   _region = new PGMouseWatcherRegion(this);
@@ -63,6 +75,11 @@ PGItem(const string &name) :
 ////////////////////////////////////////////////////////////////////
 PGItem::
 ~PGItem() {
+  if (_notify != (PGItemNotify *)NULL) {
+    _notify->remove_item(this);
+    _notify = NULL;
+  }
+
   nassertv(_region->_item == this);
   _region->_item = (PGItem *)NULL;
 
@@ -85,6 +102,7 @@ PGItem(const PGItem &copy) :
   _state(copy._state),
   _flags(copy._flags)
 {
+  _notify = NULL;
   _region = new PGMouseWatcherRegion(this);
 }
 
@@ -102,38 +120,33 @@ make_copy() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGItem::xform
+//     Function: PGItem::transform_changed
 //       Access: Protected, Virtual
-//  Description: Transforms the contents of this node by the indicated
-//               matrix, if it means anything to do so.  For most
-//               kinds of nodes, this does nothing.
+//  Description: Called after the node's transform has been changed
+//               for any reason, this just provides a hook so derived
+//               classes can do something special in this case.
 ////////////////////////////////////////////////////////////////////
 void PGItem::
-xform(const LMatrix4f &mat) {
-  // Transform the frame.
-  LPoint3f ll(_frame[0], 0.0f, _frame[2]);
-  LPoint3f ur(_frame[1], 0.0f, _frame[3]);
-  ll = ll * mat;
-  ur = ur * mat;
-  _frame.set(ll[0], ur[0], ll[2], ur[2]);
-
-  // Transform the individual states and their frame styles.
-  StateDefs::iterator di;
-  for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
-    NodePath &root = (*di)._root;
-    // Apply the matrix to the previous transform.
-    root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat)));
-
-    // Now flatten the transform into the subgraph.
-    SceneGraphReducer gr;
-    gr.apply_attribs(root.node());
+transform_changed() {
+  PandaNode::transform_changed();
+  if (has_notify()) {
+    get_notify()->item_transform_changed(this);
+  }
+}
 
-    // Transform the frame style too.
-    if ((*di)._frame_style.xform(mat)) {
-      (*di)._frame_stale = true;
-    }
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::draw_mask_changed
+//       Access: Protected, Virtual
+//  Description: Called after the node's draw_mask has been changed
+//               for any reason, this just provides a hook so derived
+//               classes can do something special in this case.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+draw_mask_changed() {
+  PandaNode::draw_mask_changed();
+  if (has_notify()) {
+    get_notify()->item_draw_mask_changed(this);
   }
-  mark_bound_stale();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -221,8 +234,9 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
       // which only provides one.
       sort = (bin_sort << 16) | ((sort + 0x8000) & 0xffff);
 
-      activate_region(transform, sort);
-      pg_trav->_top->add_region(get_region());
+      if (activate_region(transform, sort, data._state->get_clip_plane())) {
+        pg_trav->_top->add_region(get_region());
+      }
     }
   }
 
@@ -273,14 +287,53 @@ recompute_internal_bound() {
   return bound;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+xform(const LMatrix4f &mat) {
+  // Transform the frame.
+  LPoint3f ll(_frame[0], 0.0f, _frame[2]);
+  LPoint3f ur(_frame[1], 0.0f, _frame[3]);
+  ll = ll * mat;
+  ur = ur * mat;
+  _frame.set(ll[0], ur[0], ll[2], ur[2]);
+
+  // Transform the individual states and their frame styles.
+  StateDefs::iterator di;
+  for (di = _state_defs.begin(); di != _state_defs.end(); ++di) {
+    NodePath &root = (*di)._root;
+    // Apply the matrix to the previous transform.
+    root.set_transform(root.get_transform()->compose(TransformState::make_mat(mat)));
+
+    // Now flatten the transform into the subgraph.
+    SceneGraphReducer gr;
+    gr.apply_attribs(root.node());
+
+    // Transform the frame style too.
+    if ((*di)._frame_style.xform(mat)) {
+      (*di)._frame_stale = true;
+    }
+  }
+  mark_bound_stale();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::activate_region
 //       Access: Public
 //  Description: Applies the indicated scene graph transform and order
 //               as determined by the traversal from PGTop.
+//
+//               The return value is true if the region is valid, or
+//               false if it is empty or completely clipped.
 ////////////////////////////////////////////////////////////////////
-void PGItem::
-activate_region(const LMatrix4f &transform, int sort) {
+bool PGItem::
+activate_region(const LMatrix4f &transform, int sort,
+                const ClipPlaneAttrib *cpa) {
   // Transform all four vertices, and get the new bounding box.  This
   // way the region works (mostly) even if has been rotated.
   LPoint3f ll(_frame[0], 0.0f, _frame[2]);
@@ -291,10 +344,54 @@ activate_region(const LMatrix4f &transform, int sort) {
   lr = lr * transform;
   ul = ul * transform;
   ur = ur * transform;
-  _region->set_frame(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
-                     max(max(ll[0], lr[0]), max(ul[0], ur[0])),
-                     min(min(ll[2], lr[2]), min(ul[2], ur[2])),
-                     max(max(ll[2], lr[2]), max(ul[2], ur[2])));
+
+  if (cpa != (ClipPlaneAttrib *)NULL && cpa->get_num_on_planes() != 0) {
+    // Apply the clip plane(s) now that we are here in world space.
+    
+    pvector<LPoint2f> points;
+    points.reserve(4);
+    points.push_back(LPoint2f(ll[0], ll[2]));
+    points.push_back(LPoint2f(lr[0], lr[2]));
+    points.push_back(LPoint2f(ur[0], ur[2]));
+    points.push_back(LPoint2f(ul[0], ul[2]));
+
+    int num_on_planes = cpa->get_num_on_planes();
+    for (int i = 0; i < num_on_planes; ++i) {
+      NodePath plane_path = cpa->get_on_plane(i);
+      Planef plane = DCAST(PlaneNode, plane_path.node())->get_plane();
+      plane.xform(plane_path.get_net_transform()->get_mat());
+
+      // We ignore the y coordinate, assuming the frame is still in
+      // the X-Z plane after being transformed.  Not sure if we really
+      // need to support general 3-D transforms on 2-D objects.
+      clip_frame(points, plane);
+    }
+
+    if (points.empty()) {
+      // Turns out it's completely clipped after all.
+      return false;
+    }
+
+    pvector<LPoint2f>::iterator pi;
+    pi = points.begin();
+    LVecBase4f frame((*pi)[0], (*pi)[0], (*pi)[1], (*pi)[1]);
+    ++pi;
+    while (pi != points.end()) {
+      frame[0] = min(frame[0], (*pi)[0]);
+      frame[1] = max(frame[1], (*pi)[0]);
+      frame[2] = min(frame[2], (*pi)[1]);
+      frame[3] = max(frame[3], (*pi)[1]);
+      ++pi;
+    }
+    _region->set_frame(frame);
+
+  } else {
+    // Since there are no clip planes involved, just set the frame.
+    _region->set_frame(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
+                       max(max(ll[0], lr[0]), max(ul[0], ur[0])),
+                       min(min(ll[2], lr[2]), min(ul[2], ur[2])),
+                       max(max(ll[2], lr[2]), max(ul[2], ur[2])));
+  }
                      
   _region->set_sort(sort);
   _region->set_active(true);
@@ -302,6 +399,8 @@ activate_region(const LMatrix4f &transform, int sort) {
   // calculate the inverse of this transform, which is needed to 
   // go back to the frame space.
   _frame_inv_xform.invert_from(transform);
+
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -319,6 +418,11 @@ enter(const MouseWatcherParameter &param) {
   string event = get_enter_event();
   play_sound(event);
   throw_event(event, EventParameter(ep));
+
+  if (has_notify()) {
+    get_notify()->item_enter(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::enter()" << endl;
 }
 
@@ -337,6 +441,11 @@ exit(const MouseWatcherParameter &param) {
   string event = get_exit_event();
   play_sound(event);
   throw_event(event, EventParameter(ep));
+
+  if (has_notify()) {
+    get_notify()->item_exit(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::exit()" << endl;
 }
 
@@ -356,6 +465,11 @@ within(const MouseWatcherParameter &param) {
   string event = get_within_event();
   play_sound(event);
   throw_event(event, EventParameter(ep));
+
+  if (has_notify()) {
+    get_notify()->item_within(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::within()" << endl;
 }
 
@@ -372,6 +486,11 @@ without(const MouseWatcherParameter &param) {
   string event = get_without_event();
   play_sound(event);
   throw_event(event, EventParameter(ep));
+
+  if (has_notify()) {
+    get_notify()->item_without(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::without()" << endl;
 }
 
@@ -386,6 +505,11 @@ focus_in() {
   string event = get_focus_in_event();
   play_sound(event);
   throw_event(event);
+
+  if (has_notify()) {
+    get_notify()->item_focus_in(this);
+  }
+
   //pgui_cat.info() << get_name() << "::focus_in()" << endl;
 }
 
@@ -400,6 +524,11 @@ focus_out() {
   string event = get_focus_out_event();
   play_sound(event);
   throw_event(event);
+
+  if (has_notify()) {
+    get_notify()->item_focus_out(this);
+  }
+
   //pgui_cat.info() << get_name() << "::focus_out()" << endl;
 }
 
@@ -418,6 +547,11 @@ press(const MouseWatcherParameter &param, bool background) {
     play_sound(event);
     throw_event(event, EventParameter(ep));
   }
+
+  if (has_notify()) {
+    get_notify()->item_press(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::press()" << endl;
 }
 
@@ -436,6 +570,11 @@ release(const MouseWatcherParameter &param, bool background) {
     play_sound(event);
     throw_event(event, EventParameter(ep));
   }
+
+  if (has_notify()) {
+    get_notify()->item_release(this, param);
+  }
+
   //pgui_cat.info() << get_name() << "::release()" << endl;
 }
 
@@ -452,6 +591,10 @@ keystroke(const MouseWatcherParameter &param, bool background) {
     string event = get_keystroke_event();
     play_sound(event);
     throw_event(event, EventParameter(ep));
+
+    if (has_notify()) {
+      get_notify()->item_keystroke(this, param);
+    }
   }
 }
 
@@ -464,6 +607,11 @@ keystroke(const MouseWatcherParameter &param, bool background) {
 void PGItem::
 candidate(const MouseWatcherParameter &param, bool background) {
   // We don't throw sound events for candidate selections for now.
+  if (!background) {
+    if (has_notify()) {
+      get_notify()->item_candidate(this, param);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -474,11 +622,9 @@ candidate(const MouseWatcherParameter &param, bool background) {
 ////////////////////////////////////////////////////////////////////
 void PGItem::
 move(const MouseWatcherParameter &param) {
-  PGMouseWatcherParameter *ep = new PGMouseWatcherParameter(param);
-  string event = get_press_event(param.get_button());
-  play_sound(event);
-  throw_event(event, EventParameter(ep));
-  //pgui_cat.info() << get_name() << "::move()" << endl;
+  if (has_notify()) {
+    get_notify()->item_move(this, param);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -861,6 +1007,109 @@ play_sound(const string &event) {
 #endif  // HAVE_AUDIO
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::reduce_region
+//       Access: Protected
+//  Description: The frame parameter is an in/out parameter.  This
+//               function adjusts frame so that it represents the
+//               largest part of the rectangular region passed in,
+//               that does not overlap with the rectangular region of
+//               the indicated obscurer.  If the obscurer is NULL, or
+//               is a hidden node, it is not considered and the frame
+//               is left unchanged.
+//
+//               This is used by slider bars and scroll frames, which
+//               have to automatically figure out how much space they
+//               have to work with after allowing space for scroll
+//               bars and buttons.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+reduce_region(LVecBase4f &frame, PGItem *obscurer) const {
+  if (obscurer != (PGItem *)NULL && !obscurer->get_draw_mask().is_zero()) {
+    LVecBase4f oframe = get_relative_frame(obscurer);
+
+    // Determine the four rectangular regions on the four sides of the
+    // obscuring region.
+    LVecBase4f right(max(frame[0], oframe[1]), frame[1], frame[2], frame[3]);
+    LVecBase4f left(frame[0], min(frame[1], oframe[0]), frame[2], frame[3]);
+    LVecBase4f above(frame[0], frame[1], max(frame[2], oframe[3]), frame[3]);
+    LVecBase4f below(frame[0], frame[1], frame[2], min(frame[3], oframe[2]));
+
+    // Now choose the largest of those four.
+    const LVecBase4f *largest = &right;
+    float largest_area = compute_area(*largest);
+    compare_largest(largest, largest_area, &left);
+    compare_largest(largest, largest_area, &above);
+    compare_largest(largest, largest_area, &below);
+
+    frame = *largest;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::get_relative_frame
+//       Access: Protected
+//  Description: Returns the LVecBase4f frame of the indicated item,
+//               converted into this item's coordinate space.
+//               Presumably, item is a child of this node.
+////////////////////////////////////////////////////////////////////
+LVecBase4f PGItem::
+get_relative_frame(PGItem *item) const {
+  NodePath this_np = NodePath::any_path((PGItem *)this);
+  NodePath item_np = this_np.find_path_to(item);
+  if (item_np.is_empty()) { 
+    item_np = NodePath::any_path(item);
+  }
+  const LVecBase4f &orig_frame = item->get_frame();
+  LMatrix4f transform = item_np.get_mat(this_np);
+  
+  // Transform the item's frame into the PGScrollFrame's
+  // coordinate space.  Transform all four vertices, and get the
+  // new bounding box.  This way the region works (mostly) even if
+  // has been rotated.
+  LPoint3f ll(orig_frame[0], 0.0f, orig_frame[2]);
+  LPoint3f lr(orig_frame[1], 0.0f, orig_frame[2]);
+  LPoint3f ul(orig_frame[0], 0.0f, orig_frame[3]);
+  LPoint3f ur(orig_frame[1], 0.0f, orig_frame[3]);
+  ll = ll * transform;
+  lr = lr * transform;
+  ul = ul * transform;
+  ur = ur * transform;
+  
+  return LVecBase4f(min(min(ll[0], lr[0]), min(ul[0], ur[0])),
+                    max(max(ll[0], lr[0]), max(ul[0], ur[0])),
+                    min(min(ll[2], lr[2]), min(ul[2], ur[2])),
+                    max(max(ll[2], lr[2]), max(ul[2], ur[2])));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::mouse_to_local
+//       Access: Protected
+//  Description: Converts from the 2-d mouse coordinates into the
+//               coordinate space of the item.
+////////////////////////////////////////////////////////////////////
+LPoint3f PGItem::
+mouse_to_local(const LPoint2f &mouse_point) const {
+  // This is ambiguous if the PGItem has multiple instances.  Why
+  // would you do that, anyway?
+  NodePath this_np((PGItem *)this);
+  CPT(TransformState) inv_transform = NodePath().get_transform(this_np);
+  return inv_transform->get_mat().xform_point(LVector3f::rfu(mouse_point[0], 0, mouse_point[1]));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::frame_changed
+//       Access: Protected, Virtual
+//  Description: Called when the user changes the frame size.
+////////////////////////////////////////////////////////////////////
+void PGItem::
+frame_changed() {
+  mark_frames_stale();
+  if (has_notify()) {
+    get_notify()->item_frame_changed(this);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGItem::slot_state_def
 //       Access: Private
@@ -919,3 +1168,91 @@ mark_frames_stale() {
   }
   mark_bound_stale();
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItem::clip_frame
+//       Access: Private
+//  Description: Clips the four corners of the item's frame by the
+//               indicated clipping plane, and modifies the points to
+//               reflect the new set of clipped points.
+//
+//               The return value is true if the set of points is
+//               unmodified (all points are behind the clip plane), or
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PGItem::
+clip_frame(pvector<LPoint2f> &source_points, const Planef &plane) const {
+  if (source_points.empty()) {
+    return true;
+  }
+
+  LPoint3f from3d;
+  LVector3f delta3d;
+  if (!plane.intersects_plane(from3d, delta3d, Planef(LVector3f(0, 1, 0), LPoint3f::zero()))) {
+    // The clipping plane is parallel to the polygon.  The polygon is
+    // either all in or all out.
+    if (plane.dist_to_plane(LPoint3f::zero()) < 0.0) {
+      // A point within the polygon is behind the clipping plane: the
+      // polygon is all in.
+      return true;
+    }
+    return false;
+  }
+
+  // Project the line of intersection into the X-Z plane.  Now we have
+  // a 2-d clipping line.
+  LPoint2f from2d(from3d[0], from3d[2]);
+  LVector2f delta2d(delta3d[0], delta3d[2]);
+
+  float a = -delta2d[1];
+  float b = delta2d[0];
+  float c = from2d[0] * delta2d[1] - from2d[1] * delta2d[0];
+
+  // Now walk through the points.  Any point on the left of our line
+  // gets removed, and the line segment clipped at the point of
+  // intersection.
+
+  // We might increase the number of vertices by as many as 1, if the
+  // plane clips off exactly one corner.  (We might also decrease the
+  // number of vertices, or keep them the same number.)
+  pvector<LPoint2f> new_points;
+  new_points.reserve(source_points.size() + 1);
+
+  LPoint2f last_point = source_points.back();
+  bool last_is_in = !is_right(last_point - from2d, delta2d);
+  bool all_in = last_is_in;
+  pvector<LPoint2f>::const_iterator pi;
+  for (pi = source_points.begin(); pi != source_points.end(); ++pi) {
+    const LPoint2f &this_point = (*pi);
+    bool this_is_in = !is_right(this_point - from2d, delta2d);
+
+    // There appears to be a compiler bug in gcc 4.0: we need to
+    // extract this comparison outside of the if statement.
+    bool crossed_over = (this_is_in != last_is_in);
+    if (crossed_over) {
+      // We have just crossed over the clipping line.  Find the point
+      // of intersection.
+      LVector2f d = this_point - last_point;
+      float denom = (a * d[0] + b * d[1]);
+      if (denom != 0.0) {
+        float t = -(a * last_point[0] + b * last_point[1] + c) / denom;
+        LPoint2f p = last_point + t * d;
+        
+        new_points.push_back(p);
+        last_is_in = this_is_in;
+      }
+    } 
+
+    if (this_is_in) {
+      // We are behind the clipping line.  Keep the point.
+      new_points.push_back(this_point);
+    } else {
+      all_in = false;
+    }
+
+    last_point = this_point;
+  }
+
+  source_points.swap(new_points);
+  return all_in;
+}

+ 30 - 3
panda/src/pgui/pgItem.h

@@ -23,6 +23,7 @@
 
 #include "pgMouseWatcherRegion.h"
 #include "pgFrameStyle.h"
+#include "pgItemNotify.h"
 
 #include "pandaNode.h"
 #include "nodePath.h"
@@ -30,12 +31,13 @@
 #include "pointerTo.h"
 #include "audioSound.h"
 #include "textNode.h"
-
+#include "plane.h"
 #include "pmap.h"
 
 class PGTop;
 class MouseWatcherParameter;
 class AudioSound;
+class ClipPlaneAttrib;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PGItem
@@ -60,14 +62,18 @@ protected:
   PGItem(const PGItem &copy);
 
   virtual PandaNode *make_copy() const;
-  virtual void xform(const LMatrix4f &mat);
+  virtual void transform_changed();
+  virtual void draw_mask_changed();
+
   virtual bool has_cull_callback() const;
   virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
 
   virtual BoundingVolume *recompute_internal_bound();
 
 public:
-  void activate_region(const LMatrix4f &transform, int sort);
+  virtual void xform(const LMatrix4f &mat);
+  bool activate_region(const LMatrix4f &transform, int sort,
+                       const ClipPlaneAttrib *cpa);
   INLINE PGMouseWatcherRegion *get_region() const;
 
   virtual void enter(const MouseWatcherParameter &param);
@@ -87,6 +93,10 @@ public:
   static void background_keystroke(const MouseWatcherParameter &param);
   static void background_candidate(const MouseWatcherParameter &param);
 
+  INLINE void set_notify(PGItemNotify *notify);
+  INLINE bool has_notify() const;
+  INLINE PGItemNotify *get_notify() const;
+
 PUBLISHED:
   INLINE void set_frame(float left, float right, float bottom, float top);
   INLINE void set_frame(const LVecBase4f &frame);
@@ -158,11 +168,28 @@ PUBLISHED:
 protected:
   void play_sound(const string &event);
 
+  void reduce_region(LVecBase4f &clip, PGItem *obscurer) const;
+  void reduce_region(LVecBase4f &frame, float px, float py) const;
+  LVecBase4f get_relative_frame(PGItem *item) const;
+  LPoint3f mouse_to_local(const LPoint2f &mouse_point) const;
+
+  virtual void frame_changed();
+
 private:
   void slot_state_def(int state);
   void update_frame(int state);
   void mark_frames_stale();
 
+  INLINE static float compute_area(const LVecBase4f &frame);
+  INLINE static void compare_largest(const LVecBase4f *&largest, 
+                                     float &largest_area, 
+                                     const LVecBase4f *new_frame);
+
+  bool clip_frame(pvector<LPoint2f> &source_points, const Planef &plane) const;
+
+private:
+  PGItemNotify *_notify;
+
   bool _has_frame;
   LVecBase4f _frame;
   int _state;

+ 27 - 0
panda/src/pgui/pgItemNotify.I

@@ -0,0 +1,27 @@
+// Filename: pgItemNotify.I
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PGItemNotify::
+PGItemNotify() {
+}

+ 202 - 0
panda/src/pgui/pgItemNotify.cxx

@@ -0,0 +1,202 @@
+// Filename: pgItemNotify.cxx
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgItemNotify.h"
+#include "pgItem.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGItemNotify::
+~PGItemNotify() {
+  while (!_items.empty()) {
+    // Disconnect all of the items that are connected to this
+    // object.
+    PGItem *item = (*_items.begin());
+    nassertv(item->get_notify() == this);
+    (*_items.begin())->set_notify(NULL);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_transform_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's local transform
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_transform_changed(PGItem *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_frame_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's frame
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_frame_changed(PGItem *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_draw_mask_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's draw_mask
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_draw_mask_changed(PGItem *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_enter
+//       Access: Protected, Virtual
+//  Description: Called whenever the "enter" event is triggered on a
+//               watched PGItem.  See PGItem::enter().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_enter(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_exit
+//       Access: Protected, Virtual
+//  Description: Called whenever the "exit" event is triggered on a
+//               watched PGItem.  See PGItem::exit().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_exit(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_within
+//       Access: Protected, Virtual
+//  Description: Called whenever the "within" event is triggered on a
+//               watched PGItem.  See PGItem::within().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_within(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_without
+//       Access: Protected, Virtual
+//  Description: Called whenever the "without" event is triggered on a
+//               watched PGItem.  See PGItem::without().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_without(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_focus_in
+//       Access: Protected, Virtual
+//  Description: Called whenever the "focus_in" event is triggered on a
+//               watched PGItem.  See PGItem::focus_in().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_focus_in(PGItem *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_focus_out
+//       Access: Protected, Virtual
+//  Description: Called whenever the "focus_out" event is triggered on a
+//               watched PGItem.  See PGItem::focus_out().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_focus_out(PGItem *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_press
+//       Access: Protected, Virtual
+//  Description: Called whenever the "press" event is triggered on a
+//               watched PGItem.  See PGItem::press().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_press(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_release
+//       Access: Protected, Virtual
+//  Description: Called whenever the "release" event is triggered on a
+//               watched PGItem.  See PGItem::release().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_release(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_keystroke
+//       Access: Protected, Virtual
+//  Description: Called whenever the "keystroke" event is triggered on a
+//               watched PGItem.  See PGItem::keystroke().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_keystroke(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_candidate
+//       Access: Protected, Virtual
+//  Description: Called whenever the "candidate" event is triggered on a
+//               watched PGItem.  See PGItem::candidate().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_candidate(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::item_move
+//       Access: Protected, Virtual
+//  Description: Called whenever the "move" event is triggered on a
+//               watched PGItem.  See PGItem::move().
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+item_move(PGItem *, const MouseWatcherParameter &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::add_item
+//       Access: Protected, Virtual
+//  Description: Called by PGItem when a new item is set up to
+//               notify this object.
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+add_item(PGItem *item) {
+  bool inserted = _items.insert(item).second;
+  nassertv(inserted);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGItemNotify::remove_item
+//       Access: Protected, Virtual
+//  Description: Called by PGItem when an item is no longer set up
+//               to notify this object.
+////////////////////////////////////////////////////////////////////
+void PGItemNotify::
+remove_item(PGItem *item) {
+  Items::iterator bi;
+  bi = _items.find(item);
+  nassertv(bi != _items.end());
+  _items.erase(bi);
+}

+ 67 - 0
panda/src/pgui/pgItemNotify.h

@@ -0,0 +1,67 @@
+// Filename: pgItemNotify.h
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGITEMNOTIFY_H
+#define PGITEMNOTIFY_H
+
+#include "pandabase.h"
+
+class PGItem;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGItemNotify
+// Description : Objects that inherit from this class can receive
+//               specialized messages when PGItems change in certain
+//               ways.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGItemNotify {
+public:
+  INLINE PGItemNotify();
+  virtual ~PGItemNotify();
+
+protected:
+  virtual void item_transform_changed(PGItem *item);
+  virtual void item_frame_changed(PGItem *item);
+  virtual void item_draw_mask_changed(PGItem *item);
+
+  virtual void item_enter(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_exit(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_within(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_without(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_focus_in(PGItem *item);
+  virtual void item_focus_out(PGItem *item);
+  virtual void item_press(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_release(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_keystroke(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_candidate(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_move(PGItem *item, const MouseWatcherParameter &param);
+
+protected:
+  void add_item(PGItem *item);
+  void remove_item(PGItem *item);
+
+private:
+  typedef pset<PGItem *> Items;
+  Items _items;
+
+  friend class PGItem;
+};
+
+#include "pgItemNotify.I"
+
+#endif

+ 245 - 0
panda/src/pgui/pgScrollFrame.I

@@ -0,0 +1,245 @@
+// Filename: pgScrollFrame.I
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_virtual_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the virtual frame.
+//               This is the size of the large, virtual canvas which
+//               we can see only a portion of at any given time.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+set_virtual_frame(float left, float right, float bottom, float top) {
+  set_virtual_frame(LVecBase4f(left, right, bottom, top));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_virtual_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the virtual frame.
+//               This is the size of the large, virtual canvas which
+//               we can see only a portion of at any given time.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+set_virtual_frame(const LVecBase4f &frame) {
+  _has_virtual_frame = true;
+  _virtual_frame = frame;
+
+  if (_auto_hide) {
+    _needs_remanage = true;
+  }
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::get_virtual_frame
+//       Access: Published
+//  Description: Returns the bounding rectangle of the virtual frame.
+//               See set_virtual_frame().  If has_virtual_frame() is
+//               false, this returns the item's clip frame.
+////////////////////////////////////////////////////////////////////
+INLINE const LVecBase4f &PGScrollFrame::
+get_virtual_frame() const {
+  return _has_virtual_frame ? _virtual_frame : get_clip_frame();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::has_virtual_frame
+//       Access: Published
+//  Description: Returns true if the virtual frame has a bounding
+//               rectangle; see set_virtual_frame().  Most
+//               PGScrollFrame objects will have a virtual frame.
+////////////////////////////////////////////////////////////////////
+INLINE bool PGScrollFrame::
+has_virtual_frame() const {
+  return _has_virtual_frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::clear_virtual_frame
+//       Access: Published
+//  Description: Removes the virtual frame from the item.  This
+//               effectively sets the virtual frame to the same size
+//               as the clip frame.  Scrolling will no longer be
+//               possible.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+clear_virtual_frame() {
+  _has_virtual_frame = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_manage_pieces
+//       Access: Published
+//  Description: Sets the manage_pieces flag.  When this is true, the
+//               sub-pieces of the scroll frame--that is, the two
+//               scroll bars--are automatically positioned and/or
+//               resized when the scroll frame's overall frame is
+//               changed.  They are also automatically resized to fill
+//               in the gap when one or the other is hidden.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame:: 
+set_manage_pieces(bool manage_pieces) {
+  _manage_pieces = manage_pieces;
+  _needs_remanage = true;
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::get_manage_pieces
+//       Access: Published
+//  Description: Returns the manage_pieces flag.  See
+//               set_manage_pieces().
+////////////////////////////////////////////////////////////////////
+INLINE bool PGScrollFrame:: 
+get_manage_pieces() const {
+  return _manage_pieces;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_auto_hide
+//       Access: Published
+//  Description: Sets the auto_hide flag.  When this is true, the
+//               two scroll bars are automatically hidden if they are
+//               not needed (that is, if the virtual frame would fit
+//               within the clip frame without them), and they are
+//               automatically shown when they are needed.
+//
+//               Setting this flag true forces the manage_pieces flag
+//               to also be set true.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame:: 
+set_auto_hide(bool auto_hide) {
+  _auto_hide = auto_hide;
+  if (_auto_hide) {
+    set_manage_pieces(true);
+    _needs_remanage = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::get_auto_hide
+//       Access: Published
+//  Description: Returns the auto_hide flag.  See
+//               set_auto_hide().
+////////////////////////////////////////////////////////////////////
+INLINE bool PGScrollFrame:: 
+get_auto_hide() const {
+  return _auto_hide;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_horizontal_slider
+//       Access: Published
+//  Description: Sets the PGSliderBar object that will serve as the
+//               horizontal scroll bar for this frame.  It is your
+//               responsibility to parent this slider bar to the frame
+//               and move it to the appropriate place.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+set_horizontal_slider(PGSliderBar *horizontal_slider) {
+  if (_horizontal_slider != (PGSliderBar *)NULL) {
+    _horizontal_slider->set_notify(NULL);
+  }
+  _horizontal_slider = horizontal_slider;
+  if (_horizontal_slider != (PGSliderBar *)NULL) {
+    _horizontal_slider->set_notify(this);
+  }
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::clear_horizontal_slider
+//       Access: Published
+//  Description: Removes the horizontal scroll bar from control of the
+//               frame.  It is your responsibility to actually remove
+//               or hide the object itself.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+clear_horizontal_slider() {
+  set_horizontal_slider(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::get_horizontal_slider
+//       Access: Published
+//  Description: Returns the PGSliderBar that serves as the horizontal
+//               scroll bar for this frame, if any, or NULL if it is
+//               not set.
+////////////////////////////////////////////////////////////////////
+INLINE PGSliderBar *PGScrollFrame::
+get_horizontal_slider() const {
+  return _horizontal_slider;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::set_vertical_slider
+//       Access: Published
+//  Description: Sets the PGSliderBar object that will serve as the
+//               vertical scroll bar for this frame.  It is your
+//               responsibility to parent this slider bar to the frame
+//               and move it to the appropriate place.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+set_vertical_slider(PGSliderBar *vertical_slider) {
+  if (_vertical_slider != (PGSliderBar *)NULL) {
+    _vertical_slider->set_notify(NULL);
+  }
+  _vertical_slider = vertical_slider;
+  if (_vertical_slider != (PGSliderBar *)NULL) {
+    _vertical_slider->set_notify(this);
+  }
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::clear_vertical_slider
+//       Access: Published
+//  Description: Removes the vertical scroll bar from control of the
+//               frame.  It is your responsibility to actually remove
+//               or hide the object itself.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+clear_vertical_slider() {
+  set_vertical_slider(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::get_vertical_slider
+//       Access: Published
+//  Description: Returns the PGSliderBar that serves as the vertical
+//               scroll bar for this frame, if any, or NULL if it is
+//               not set.
+////////////////////////////////////////////////////////////////////
+INLINE PGSliderBar *PGScrollFrame::
+get_vertical_slider() const {
+  return _vertical_slider;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::recompute
+//       Access: Published
+//  Description: Forces the PGScrollFrame to recompute itself right
+//               now.  Normally this should not be required.
+////////////////////////////////////////////////////////////////////
+INLINE void PGScrollFrame::
+recompute() {
+  recompute_clip();
+  recompute_canvas();
+}

+ 445 - 0
panda/src/pgui/pgScrollFrame.cxx

@@ -0,0 +1,445 @@
+// Filename: pgScrollFrame.cxx
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgScrollFrame.h"
+
+TypeHandle PGScrollFrame::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGScrollFrame::
+PGScrollFrame(const string &name) : PGVirtualFrame(name)
+{
+  _needs_remanage = false;
+  _needs_recompute_canvas = false;
+  _needs_recompute_clip = false;
+  _has_virtual_frame = false;
+  _virtual_frame.set(0.0f, 0.0f, 0.0f, 0.0f);
+  _manage_pieces = false;
+  _auto_hide = false;
+  _horizontal_slider = NULL;
+  _vertical_slider = NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGScrollFrame::
+~PGScrollFrame() {
+  set_horizontal_slider(NULL);
+  set_vertical_slider(NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGScrollFrame::
+PGScrollFrame(const PGScrollFrame &copy) :
+  PGVirtualFrame(copy),
+  _has_virtual_frame(copy._has_virtual_frame),
+  _virtual_frame(copy._virtual_frame),
+  _manage_pieces(copy._manage_pieces),
+  _auto_hide(copy._auto_hide)
+{
+  _needs_remanage = false;
+  _needs_recompute_canvas = true;
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *PGScrollFrame::
+make_copy() const {
+  return new PGScrollFrame(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::has_cull_callback
+//       Access: Protected, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool PGScrollFrame::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::cull_callback
+//       Access: Protected, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool PGScrollFrame::
+cull_callback(CullTraverser *trav, CullTraverserData &data) {
+  if (_manage_pieces && _needs_remanage) {
+    remanage();
+  }
+  if (_needs_recompute_clip) {
+    recompute_clip();
+  }
+  if (_needs_recompute_canvas) {
+    recompute_canvas();
+  }
+  return PGVirtualFrame::cull_callback(trav, data);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+xform(const LMatrix4f &mat) {
+  PGVirtualFrame::xform(mat);
+
+  _needs_remanage = true;
+  _needs_recompute_clip = true;
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::setup
+//       Access: Published
+//  Description: Creates a PGScrollFrame with the indicated 
+//               dimensions, and the indicated virtual frame.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+setup(float width, float height,
+      float left, float right, float bottom, float top,
+      float slider_width, float bevel) {
+  set_state(0);
+  clear_state_def(0);
+
+  set_frame(0, width, 0, height);
+
+  PGFrameStyle style;
+  style.set_width(bevel, bevel);
+
+  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
+  style.set_type(PGFrameStyle::T_ridge);
+  set_frame_style(0, style);
+
+  set_clip_frame(bevel, width - bevel,
+                 bevel, height - bevel);
+
+  set_virtual_frame(left, right, bottom, top);
+
+  // Remove the slider nodes created by a previous call to setup(), if
+  // any.
+  if (_horizontal_slider != (PGSliderBar *)NULL) {
+    remove_child(_horizontal_slider);
+    set_horizontal_slider(NULL);
+  }
+  if (_vertical_slider != (PGSliderBar *)NULL) {
+    remove_child(_vertical_slider);
+    set_vertical_slider(NULL);
+  }
+
+  // Create new slider bars.
+  PT(PGSliderBar) horizontal_slider = new PGSliderBar("horizontal");
+  horizontal_slider->setup_scroll_bar(false, width - slider_width - bevel * 2, slider_width, bevel);
+  horizontal_slider->set_transform(TransformState::make_pos(LVector3f::rfu(width / 2.0f - slider_width / 2.0f, 0, slider_width / 2.0f + bevel)));
+  add_child(horizontal_slider);
+  set_horizontal_slider(horizontal_slider);
+
+  PT(PGSliderBar) vertical_slider = new PGSliderBar("vertical");
+  vertical_slider->setup_scroll_bar(true, width - slider_width - bevel * 2, slider_width, bevel);
+  add_child(vertical_slider);
+  vertical_slider->set_transform(TransformState::make_pos(LVector3f::rfu(width - slider_width / 2.0f - bevel, 0, width / 2.0f + slider_width / 2.0f)));
+  set_vertical_slider(vertical_slider);
+
+  set_manage_pieces(true);
+  set_auto_hide(true);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::remanage
+//       Access: Published
+//  Description: Manages the position and size of the scroll bars.
+//               Normally this should not need to be called directly.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+remanage() {
+  _needs_remanage = false;
+
+  const LVecBase4f &frame = get_frame();
+  LVecBase4f clip = get_frame_style(get_state()).get_internal_frame(frame);
+
+  // Determine which scroll bars we have in the frame.
+  
+  bool got_horizontal = false;
+  float horizontal_width = 0.0f;
+  if (_horizontal_slider != (PGSliderBar *)NULL) {
+    got_horizontal = true;
+    const LVecBase4f &slider_frame = _horizontal_slider->get_frame();
+    horizontal_width = slider_frame[3] - slider_frame[2];
+  }
+  
+  bool got_vertical = false;
+  float vertical_width = 0.0f;
+  if (_vertical_slider != (PGSliderBar *)NULL) {
+    got_vertical = true;
+    const LVecBase4f &slider_frame = _vertical_slider->get_frame();
+    vertical_width = slider_frame[1] - slider_frame[0];
+  }
+
+  if (_auto_hide) {
+    // Should we hide or show either of the scroll bars?  TODO.
+
+    // Start by figuring out what our maximum clipping area will be.
+    float clip_width = clip[1] - clip[0];
+    float clip_height = clip[3] - clip[2];
+
+    // And our virtual frame too.
+    const LVecBase4f &virtual_frame = get_virtual_frame();
+    float virtual_width = virtual_frame[1] - virtual_frame[0];
+    float virtual_height = virtual_frame[3] - virtual_frame[2];
+
+    if (virtual_width <= clip_width &&
+        virtual_height <= clip_height) {
+      // No need for either slider.
+      got_horizontal = false;
+      got_vertical = false;
+
+    } else {
+      if (virtual_width <= clip_width - vertical_width) {
+        // No need for the horizontal slider.
+        got_horizontal = false;
+      }
+      
+      if (virtual_height <= clip_height - horizontal_width) {
+        // No need for the vertical slider.
+        got_vertical = false;
+        
+        // Now reconsider the need for the horizontal slider.
+        if (virtual_width <= clip_width) {
+          got_horizontal = false;
+        }
+      }
+    }
+
+    // Now show or hide the sliders appropriately.
+    if (_horizontal_slider != (PGSliderBar *)NULL) {
+      if (got_horizontal) {
+        _horizontal_slider->set_draw_mask(DrawMask::all_on());
+      } else {
+        _horizontal_slider->set_draw_mask(DrawMask::all_off());
+        _horizontal_slider->set_ratio(0.0f);
+        horizontal_width = 0.0f;
+      }
+    }
+    if (_vertical_slider != (PGSliderBar *)NULL) {
+      if (got_vertical) {
+        _vertical_slider->set_draw_mask(DrawMask::all_on());
+      } else {
+        _vertical_slider->set_draw_mask(DrawMask::all_off());
+        _vertical_slider->set_ratio(0.0f);
+        vertical_width = 0.0f;
+      }
+    }
+
+    // Showing or hiding one of the scroll bars might have set this
+    // flag again indirectly; we clear it again to avoid a feedback
+    // loop.
+    _needs_remanage = false;
+  }
+
+  // Are either or both of the scroll bars hidden?
+  if (got_horizontal && _horizontal_slider->get_draw_mask().is_zero()) {
+    got_horizontal = false;
+    horizontal_width = 0.0f;
+  }
+  if (got_vertical && _vertical_slider->get_draw_mask().is_zero()) {
+    got_vertical = false;
+    vertical_width = 0.0f;
+  }
+
+  if (got_horizontal) {
+    _horizontal_slider->set_frame(clip[0], clip[1] - vertical_width,
+                                  clip[2], clip[2] + horizontal_width);
+    _horizontal_slider->clear_transform();
+  }
+  if (got_vertical) {
+    _vertical_slider->set_frame(clip[1] - vertical_width, clip[1],
+                                clip[2] + horizontal_width, clip[3]);
+    _vertical_slider->clear_transform();
+  }
+    
+
+  recompute();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::frame_changed
+//       Access: Protected, Virtual
+//  Description: Called when the user changes the frame size.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+frame_changed() {
+  PGVirtualFrame::frame_changed();
+  _needs_remanage = true;
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::item_transform_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's local transform
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+item_transform_changed(PGItem *) {
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::item_frame_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's frame
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+item_frame_changed(PGItem *) {
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::item_draw_mask_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's draw_mask
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+item_draw_mask_changed(PGItem *) {
+  _needs_remanage = true;
+  _needs_recompute_clip = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::slider_bar_adjust
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGSliderBar's value
+//               has been changed by the user or programmatically.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+slider_bar_adjust(PGSliderBar *) {
+  _needs_recompute_canvas = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::recompute_clip
+//       Access: Private
+//  Description: Recomputes the clipping window of the PGScrollFrame,
+//               based on the position of the slider bars.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+recompute_clip() {
+  _needs_recompute_clip = false;
+  _needs_recompute_canvas = true;
+
+  // Figure out how to remove the scroll bars from the clip region.
+  LVecBase4f clip = get_frame_style(get_state()).get_internal_frame(get_frame());
+  reduce_region(clip, _horizontal_slider);
+  reduce_region(clip, _vertical_slider);
+
+  set_clip_frame(clip);
+
+  if (_horizontal_slider != (PGSliderBar *)NULL) {
+    _horizontal_slider->set_page_size((clip[1] - clip[0]) / (_virtual_frame[1] - _virtual_frame[0]));
+  }
+  if (_vertical_slider != (PGSliderBar *)NULL) {
+    _vertical_slider->set_page_size((clip[3] - clip[2]) / (_virtual_frame[3] - _virtual_frame[2]));
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::recompute_canvas
+//       Access: Private
+//  Description: Recomputes the portion of the virtual canvas that is
+//               visible within the PGScrollFrame, based on the values
+//               of the slider bars.
+////////////////////////////////////////////////////////////////////
+void PGScrollFrame::
+recompute_canvas() {
+  _needs_recompute_canvas = false;
+
+  const LVecBase4f &clip = get_clip_frame();
+
+  float x = interpolate_canvas(clip[0], clip[1], 
+                               _virtual_frame[0], _virtual_frame[1],
+                               _horizontal_slider);
+
+  float y = interpolate_canvas(clip[3], clip[2], 
+                               _virtual_frame[3], _virtual_frame[2],
+                               _vertical_slider);
+
+  get_canvas_node()->set_transform(TransformState::make_pos(LVector3f::rfu(x, 0, y)));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGScrollFrame::interpolate_canvas
+//       Access: Private
+//  Description: Computes the linear translation that should be
+//               applied to the virtual canvas node, based on the
+//               corresponding slider bar's position.
+////////////////////////////////////////////////////////////////////
+float PGScrollFrame::
+interpolate_canvas(float clip_min, float clip_max, 
+                   float canvas_min, float canvas_max,
+                   PGSliderBar *slider_bar) {
+  float t = 0.0f;
+  if (slider_bar != (PGSliderBar *)NULL) {
+    t = slider_bar->get_ratio();
+  }
+
+  float min = clip_min - canvas_min;
+  float max = clip_max - canvas_max;
+
+  return min + t * (max - min);
+}

+ 137 - 0
panda/src/pgui/pgScrollFrame.h

@@ -0,0 +1,137 @@
+// Filename: pgScrollFrame.h
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGSCROLLFRAME_H
+#define PGSCROLLFRAME_H
+
+#include "pandabase.h"
+
+#include "pgVirtualFrame.h"
+#include "pgSliderBarNotify.h"
+#include "pgSliderBar.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGScrollFrame
+// Description : This is a special kind of frame that pretends to be
+//               much larger than it actually is.  You can scroll
+//               through the frame, as if you're looking through a
+//               window at the larger frame beneath.  All children of
+//               this frame node are scrolled and clipped as if they
+//               were children of the larger, virtual frame.
+//
+//               This is implemented as a specialization of
+//               PGVirtualFrame, which handles the meat of the virtual
+//               canvas.  This class adds automatic support for scroll
+//               bars, and restricts the virtual transform to
+//               translate only (no scale or rotate).
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGScrollFrame : public PGVirtualFrame, public PGSliderBarNotify {
+PUBLISHED:
+  PGScrollFrame(const string &name = "");
+  virtual ~PGScrollFrame();
+
+protected:
+  PGScrollFrame(const PGScrollFrame &copy);
+
+public:
+  virtual PandaNode *make_copy() const;
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
+  virtual void xform(const LMatrix4f &mat);
+
+PUBLISHED:
+  void setup(float width, float height,
+             float left, float right, float bottom, float top,
+             float slider_width, float bevel);
+
+  INLINE void set_virtual_frame(float left, float right, float bottom, float top);
+  INLINE void set_virtual_frame(const LVecBase4f &virtual_frame);
+  INLINE const LVecBase4f &get_virtual_frame() const;
+  INLINE bool has_virtual_frame() const;
+  INLINE void clear_virtual_frame();
+
+  INLINE void set_manage_pieces(bool manage_pieces);
+  INLINE bool get_manage_pieces() const;
+
+  INLINE void set_auto_hide(bool auto_hide);
+  INLINE bool get_auto_hide() const;
+
+  INLINE void set_horizontal_slider(PGSliderBar *horizontal_slider);
+  INLINE void clear_horizontal_slider();
+  INLINE PGSliderBar *get_horizontal_slider() const;
+
+  INLINE void set_vertical_slider(PGSliderBar *vertical_slider);
+  INLINE void clear_vertical_slider();
+  INLINE PGSliderBar *get_vertical_slider() const;
+
+  void remanage();
+  INLINE void recompute();
+
+protected:
+  virtual void frame_changed();
+
+  virtual void item_transform_changed(PGItem *item);
+  virtual void item_frame_changed(PGItem *item);
+  virtual void item_draw_mask_changed(PGItem *item);
+  virtual void slider_bar_adjust(PGSliderBar *slider_bar);
+
+private:
+  void recompute_clip();
+
+  void recompute_canvas();
+  float interpolate_canvas(float clip_min, float clip_max, 
+                           float canvas_min, float canvas_max,
+                           PGSliderBar *slider_bar);
+
+private:
+  bool _needs_remanage;
+  bool _needs_recompute_clip;
+  bool _needs_recompute_canvas;
+
+  LVecBase4f _orig_clip_frame;
+
+  bool _has_virtual_frame;
+  LVecBase4f _virtual_frame;
+
+  bool _manage_pieces;
+  bool _auto_hide;
+
+  PT(PGSliderBar) _horizontal_slider;
+  PT(PGSliderBar) _vertical_slider;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PGVirtualFrame::init_type();
+    register_type(_type_handle, "PGScrollFrame",
+                  PGVirtualFrame::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "pgScrollFrame.I"
+
+#endif

+ 276 - 122
panda/src/pgui/pgSliderBar.I

@@ -18,264 +18,418 @@
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_range
+//     Function: PGSliderBar::set_notify
 //       Access: Published
-//  Description: Sets the value at which the SliderBar indicates 100%.
+//  Description: Sets the object which will be notified when the
+//               PGSliderBar changes.  Set this to NULL to disable
+//               this effect.  The PGSliderBar does not retain
+//               ownership of the pointer; it is your responsibility
+//               to ensure that the notify object does not destruct.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_range(float range) {
-  _range = range;
-  _bar_state = -1;
+set_notify(PGSliderBarNotify *notify) {
+  PGItem::set_notify(notify);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_range
+//     Function: PGSliderBar::get_notify
 //       Access: Published
-//  Description: Returns the value at which the SliderBar indicates 100%.
+//  Description: Returns the object which will be notified when the
+//               PGSliderBar changes, if any.  Returns NULL if there
+//               is no such object configured.
 ////////////////////////////////////////////////////////////////////
-INLINE float PGSliderBar:: 
-get_range() const {
-  return _range;
+INLINE PGSliderBarNotify *PGSliderBar:: 
+get_notify() const {
+  return (PGSliderBarNotify *)PGItem::get_notify();
 }
 
-/*
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_magnifier
+//     Function: PGSliderBar::set_axis
 //       Access: Published
-//  Description: Sets a magnifier value for the mouseWatcherRegion in 
-//               the Y direction. 
+//  Description: Specifies the axis of the slider bar's motion.  This
+//               should be only one of four vectors: (1, 0, 0), (0, 0,
+//               1), (-1, 0, 0), or (0, 0, -1).
+//
+//               This specifies the vector in which the thumb moves
+//               when it is moving from the minimum to the maximum
+//               value.
+//
+//               The axis must be parallel to one of the screen axes,
+//               and it must be normalized.  Hence, it may only be one
+//               of the above four possibilities; anything else is an
+//               error and will result in indeterminate behavior.
+//
+//               Normally, you should not try to set the axis
+//               directly.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_magnifier(float value) {
-  PGItem::_mouseWatcherMagnifier = value;
+set_axis(const LVector3f &axis) {
+  _axis = axis;
+  _needs_remanage = true;
+  _needs_recompute = true;
 }
+
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_value
+//     Function: PGSliderBar::get_axis
 //       Access: Published
-//  Description: Returns the current value of the bar.
+//  Description: Returns the axis of the slider bar's motion.  See
+//               set_axis().
 ////////////////////////////////////////////////////////////////////
-INLINE float PGSliderBar:: 
-get_magnifier() const {
-  return PGItem::_mouseWatcherMagnifier;
+INLINE const LVector3f &PGSliderBar:: 
+get_axis() const {
+  return _axis;
 }
-*/
+
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_value
+//     Function: PGSliderBar::set_range
 //       Access: Published
-//  Description: Sets the current value of the bar.  This should range
-//               between 0 and get_range().
+//  Description: Sets the minimum and maxmimum value for the slider.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_value(float value) {
-  _value = value;
-  _mapped_value = _negative_mapping ? (_value*0.5 + 0.5) : _value;
-  _update_x = _width * (2*_mapped_value - 1);
-  _bar_state = -1;
-  _update_slider = true;
+set_range(float min_value, float max_value) {
+  nassertv(min_value != max_value);
+  _min_value = min_value;
+  _max_value = max_value;
+  _needs_recompute = true;
+
+  if (has_notify()) {
+    get_notify()->slider_bar_set_range(this);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_value
+//     Function: PGSliderBar::get_min_value
 //       Access: Published
-//  Description: Returns the current value of the bar.
+//  Description: Returns the value when the slider is all the way to
+//               the left.
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_value() const {
-  return _value;
+get_min_value() const {
+  return _min_value;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar:get_mapped_value
+//     Function: PGSliderBar::get_max_value
 //       Access: Published
-//  Description: Returns the _mapped_value which is caluclated from 
-//               _value with negative_mapping bool
+//  Description: Returns the value when the slider is all the way to
+//               the right.
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_mapped_value() const {
-  return _mapped_value;
+get_max_value() const {
+  return _max_value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::set_scroll_size
+//       Access: Published
+//  Description: Specifies the amount the slider will move when the
+//               user clicks on the left or right buttons.
+////////////////////////////////////////////////////////////////////
+INLINE void PGSliderBar:: 
+set_scroll_size(float value) {
+  _scroll_value = value;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_update_x
+//     Function: PGSliderBar::get_scroll_size
 //       Access: Published
-//  Description: Returns the _update_x which is caluclated from 
-//               _mapped_value with width formula
+//  Description: Returns the value last set by set_scroll_size().
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_update_x() const {
-  return _update_x;
+get_scroll_size() const {
+  return _scroll_value;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::set_page_size
+//       Access: Published
+//  Description: Specifies the amount of data contained in a single
+//               page.  This indicates how much the thumb will jump
+//               when the trough is directly clicked; and if
+//               resize_thumb is true, it also controls the visible
+//               size of the thumb button.
+////////////////////////////////////////////////////////////////////
+INLINE void PGSliderBar:: 
+set_page_size(float value) {
+  _page_value = value;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_update_y
+//     Function: PGSliderBar::get_page_size
 //       Access: Published
-//  Description: Returns the _update_y which is caluclated from 
-//               _mapped_value with width formula
+//  Description: Returns the value last set by set_page_size().
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_update_y() const {
-  return _update_y;
+get_page_size() const {
+  return _page_value;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_speed
+//     Function: PGSliderBar::set_value
 //       Access: Published
-//  Description: Sets the current speed at which the slider moves
+//  Description: Sets the current value of the slider
+//               programmatically.  This should range between
+//               get_min_value() and get_max_value().
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_speed(float speed) {
-  _speed = speed;
+set_value(float value) {
+  set_ratio((value - _min_value) / (_max_value - _min_value));
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_speed
+//     Function: PGSliderBar::get_value
 //       Access: Published
-//  Description: Returns the current speed at which the slider moves.
+//  Description: Returns the current value of the slider.
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_speed() const {
-  return _speed;
+get_value() const {
+  return get_ratio() * (_max_value - _min_value) + _min_value;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_scale
+//     Function: PGSliderBar::set_ratio
 //       Access: Published
-//  Description: Sets the scale of the traugh image
+//  Description: Sets the current value of the slider, expressed in
+//               the range 0 .. 1.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_scale(float scale) {
-  _scale = scale/10;
+set_ratio(float ratio) {
+  _ratio = max(min(ratio, 1.0f), 0.0f);
+  _needs_reposition = true;
+  adjust();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_scale
+//     Function: PGSliderBar::get_ratio
 //       Access: Published
-//  Description: Returns the scale set on the traugh image
+//  Description: Returns the current value of the slider, expressed in
+//               the range 0 .. 1.
 ////////////////////////////////////////////////////////////////////
 INLINE float PGSliderBar:: 
-get_scale() const {
-  return _scale;
+get_ratio() const {
+  return _ratio;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_slider_only
+//     Function: PGSliderBar::set_resize_thumb
 //       Access: Published
-//  Description: Sets _slider_only. True:only slider button desired.
-//               No left or right button to control slider needed.
-//               Else, the left and right slider buttons are shown.
+//  Description: Sets the resize_thumb flag.  When this is true, the
+//               thumb button's frame will be adjusted so that its
+//               width visually represents the page size.  When this
+//               is false, the thumb button will be left alone.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_slider_only(bool value) {
-  _slider_only = value;
+set_resize_thumb(bool resize_thumb) {
+  _resize_thumb = resize_thumb;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_slider_only
+//     Function: PGSliderBar::get_resize_thumb
 //       Access: Published
-//  Description: Returns what type of slider option set
+//  Description: Returns the resize_thumb flag.  See
+//               set_resize_thumb().
 ////////////////////////////////////////////////////////////////////
 INLINE bool PGSliderBar:: 
-get_slider_only() const {
-  return _slider_only;
+get_resize_thumb() const {
+  return _resize_thumb;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_negative_mapping
+//     Function: PGSliderBar::set_manage_pieces
 //       Access: Published
-//  Description: Sets _negative_mapping. True: slider will receive
-//               _value from -1 to +1 else, _value from 0 to + 1
+//  Description: Sets the manage_pieces flag.  When this is true, the
+//               sub-pieces of the slider bar--that is, the thumb, and
+//               the left and right scroll buttons--are automatically
+//               positioned and/or resized when the slider bar's
+//               overall frame is changed.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar:: 
-set_negative_mapping(bool value) {
-  _negative_mapping = value;
+set_manage_pieces(bool manage_pieces) {
+  _manage_pieces = manage_pieces;
+  _needs_remanage = true;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_negative_mapping
+//     Function: PGSliderBar::get_manage_pieces
 //       Access: Published
-//  Description: Returns what type of value range option set
+//  Description: Returns the manage_pieces flag.  See
+//               set_manage_pieces().
 ////////////////////////////////////////////////////////////////////
 INLINE bool PGSliderBar:: 
-get_negative_mapping() const {
-  return _negative_mapping;
+get_manage_pieces() const {
+  return _manage_pieces;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_bar_style
+//     Function: PGSliderBar::set_thumb_button
 //       Access: Published
-//  Description: Sets the kind of frame that is drawn on top of the
-//               SliderBar to represent the amount completed.
+//  Description: Sets the PGButton object that will serve as the thumb
+//               for this slider.  This button visually represents the
+//               position of the slider, and can be dragged left and
+//               right by the user.
+//
+//               It is the responsibility of the caller to ensure that
+//               the button object is parented to the PGSliderBar
+//               node.
 ////////////////////////////////////////////////////////////////////
-INLINE void PGSliderBar:: 
-set_bar_style(const PGFrameStyle &style) {
-  _bar_style = style;
-  _bar_state = -1;
+INLINE void PGSliderBar::
+set_thumb_button(PGButton *thumb_button) {
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->set_notify(NULL);
+  }
+  _thumb_button = thumb_button;
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->set_notify(this);
+  }
+  _needs_remanage = true;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_bar_style
+//     Function: PGSliderBar::clear_thumb_button
 //       Access: Published
-//  Description: Returns the kind of frame that is drawn on top of the
-//               SliderBar to represent the amount completed.
+//  Description: Removes the thumb button object from control of the
+//               frame.  It is your responsibility to actually remove
+//               or hide the button itself.
 ////////////////////////////////////////////////////////////////////
-INLINE PGFrameStyle PGSliderBar:: 
-get_bar_style() const {
-  return _bar_style;
+INLINE void PGSliderBar::
+clear_thumb_button() {
+  set_thumb_button(NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_click_event
+//     Function: PGSliderBar::get_thumb_button
 //       Access: Published
-//  Description: Returns the event name that will be thrown when the
-//               slider button is moved
+//  Description: Returns the PGButton that serves as the thumb for
+//               this slider, or NULL if it is not set.
 ////////////////////////////////////////////////////////////////////
-INLINE string PGSliderBar::
-get_click_event() const {
-  return "updated-slider-" + get_id();
+INLINE PGButton *PGSliderBar::
+get_thumb_button() const {
+  return _thumb_button;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::get_slider_button
+//     Function: PGSliderBar::set_left_button
 //       Access: Published
-//  Description: Returns the slider button that is drawn on the
-//               SliderBar to move the slider left or right 
+//  Description: Sets the PGButton object that will serve as the left
+//               scroll button for this slider.  This button is
+//               optional; if present, the user can click on it to
+//               move scroll_size units at a time to the left.
+//
+//               It is the responsibility of the caller to ensure that
+//               the button object is parented to the PGSliderBar
+//               node.
 ////////////////////////////////////////////////////////////////////
-INLINE NodePath PGSliderBar::
-get_slider_button() const {
-  return _slider_button;
+INLINE void PGSliderBar::
+set_left_button(PGButton *left_button) {
+  if (_left_button != (PGButton *)NULL) {
+    _left_button->set_notify(NULL);
+  }
+  _left_button = left_button;
+  if (_left_button != (PGButton *)NULL) {
+    _left_button->set_notify(this);
+  }
+  _needs_remanage = true;
+  _needs_recompute = true;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::set_slider_button
+//     Function: PGSliderBar::clear_left_button
 //       Access: Published
-//  Description: sets slider button that is drawn on the
-//               SliderBar to move the slider left or right 
+//  Description: Removes the left button object from control of the
+//               frame.  It is your responsibility to actually remove
+//               or hide the button itself.
 ////////////////////////////////////////////////////////////////////
 INLINE void PGSliderBar::
-set_slider_button(NodePath &np, PGSliderButton *button) {
-  _slider_button = np;
-  _slider = button;
+clear_left_button() {
+  set_left_button(NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PGSliderBar::get_left_button
 //       Access: Published
-//  Description: Returns the left button that is drawn on left of the
-//               SliderBar to move the slider left
+//  Description: Returns the PGButton that serves as the left scroll
+//               button for this slider, if any, or NULL if it is not
+//               set.
 ////////////////////////////////////////////////////////////////////
-INLINE NodePath PGSliderBar::
+INLINE PGButton *PGSliderBar::
 get_left_button() const {
   return _left_button;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::set_right_button
+//       Access: Published
+//  Description: Sets the PGButton object that will serve as the right
+//               scroll button for this slider.  This button is
+//               optional; if present, the user can click on it to
+//               move scroll_size units at a time to the right.
+//
+//               It is the responsibility of the caller to ensure that
+//               the button object is parented to the PGSliderBar
+//               node.
+////////////////////////////////////////////////////////////////////
+INLINE void PGSliderBar::
+set_right_button(PGButton *right_button) {
+  if (_right_button != (PGButton *)NULL) {
+    _right_button->set_notify(NULL);
+  }
+  _right_button = right_button;
+  if (_right_button != (PGButton *)NULL) {
+    _right_button->set_notify(this);
+  }
+  _needs_remanage = true;
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::clear_right_button
+//       Access: Published
+//  Description: Removes the right button object from control of the
+//               frame.  It is your responsibility to actually remove
+//               or hide the button itself.
+////////////////////////////////////////////////////////////////////
+INLINE void PGSliderBar::
+clear_right_button() {
+  set_right_button(NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGSliderBar::get_right_button
 //       Access: Published
-//  Description: Returns the right button that is drawn on right of the
-//               SliderBar to move the slider right
+//  Description: Returns the PGButton that serves as the right scroll
+//               button for this slider, if any, or NULL if it is not
+//               set.
 ////////////////////////////////////////////////////////////////////
-INLINE NodePath PGSliderBar::
+INLINE PGButton *PGSliderBar::
 get_right_button() const {
   return _right_button;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::get_adjust_prefix
+//       Access: Published, Static
+//  Description: Returns the prefix that is used to define the adjust
+//               event for all PGSliderBars.  The adjust event is the
+//               concatenation of this string followed by get_id().
+////////////////////////////////////////////////////////////////////
+INLINE string PGSliderBar::
+get_adjust_prefix() {
+  return "adjust-";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::get_adjust_event
+//       Access: Published
+//  Description: Returns the event name that will be thrown when the
+//               slider bar value is adjusted by the user or
+//               programmatically.
+////////////////////////////////////////////////////////////////////
+INLINE string PGSliderBar::
+get_adjust_event() const {
+  return get_adjust_prefix() + get_id();
+}

+ 664 - 149
panda/src/pgui/pgSliderBar.cxx

@@ -18,8 +18,12 @@
 
 #include "pgSliderBar.h"
 #include "pgMouseWatcherParameter.h"
-
+#include "clockObject.h"
+#include "throw_event.h"
+#include "config_pgui.h"
 #include "throw_event.h"
+#include "transformState.h"
+#include "mouseButton.h"
 
 TypeHandle PGSliderBar::_type_handle;
 
@@ -29,26 +33,26 @@ TypeHandle PGSliderBar::_type_handle;
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 PGSliderBar::
-PGSliderBar(const string &name) :
-  PGItem(name),
-  //  _slider("slider"),
-  _left("left"),
-  _right("right")
+PGSliderBar(const string &name) 
+  : PGItem(name)
 {
-  _slider = NULL;
-  _range = 100.0;
-  // _value is a range from (-1 to +1) that represents slider
-  _value = 0.0;
-  // _mapped_value is a mapping of (-1<->1) into (0<->1)
-  _mapped_value = 0.5*_value + 0.5;
-  // _update_x is mapping of _mapped_value wrt slider width
-  _update_x = 0.0; //will be set when _width is defined
-  _speed = 0.05;
-  _scale = 0.05;
-  _bar_state = -1;
-  _update_slider = false;
-  _slider_only = true;
-  _negative_mapping = false;
+  _min_value = 0.0f;
+  _max_value = 1.0f;
+  set_scroll_size(0.01f);
+  set_page_size(0.1f);
+  _ratio = 0.0f;
+  _resize_thumb = false;
+  _manage_pieces = false;
+  _axis.set(1.0f, 0.0f, 0.0f);
+  _needs_remanage = false;
+  _needs_recompute = true;
+  _needs_reposition = false;
+  _scroll_button_held = NULL;
+  _mouse_button_page = false;
+  _dragging = false;
+  _thumb_width = 0.1f;
+
+  set_active(true);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -68,14 +72,22 @@ PGSliderBar::
 PGSliderBar::
 PGSliderBar(const PGSliderBar &copy) :
   PGItem(copy),
-  _value(copy._value),
-  _range(copy._range),
-  //  _slider(copy._slider),
-  _left(copy._left),
-  _right(copy._right)
+  _min_value(copy._min_value),
+  _max_value(copy._max_value),
+  _scroll_value(copy._scroll_value),
+  _scroll_ratio(copy._scroll_ratio),
+  _page_value(copy._page_value),
+  _page_ratio(copy._page_ratio),
+  _ratio(copy._ratio),
+  _resize_thumb(copy._resize_thumb),
+  _manage_pieces(copy._manage_pieces),
+  _axis(copy._axis)
 {
-  _bar_state = -1;
-  _slider = NULL;
+  _needs_remanage = false;
+  _needs_recompute = true;
+  _scroll_button_held = NULL;
+  _mouse_button_page = false;
+  _dragging = false;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -91,6 +103,71 @@ make_copy() const {
   return new PGSliderBar(*this);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::press
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button is depressed while the mouse
+//               is within the region.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+press(const MouseWatcherParameter &param, bool background) {
+  if (param.has_mouse()) {
+    _mouse_pos = param.get_mouse();
+  }
+  if (get_active() && param.get_button() == MouseButton::one()) {
+    if (_needs_recompute) {
+      recompute();
+    }
+
+    if (_range_x != 0.0f) {
+      _mouse_button_page = true;
+      _scroll_button_held = NULL;
+      advance_page();
+      _next_advance_time = 
+        ClockObject::get_global_clock()->get_frame_time() + scroll_initial_delay;
+    }
+  }
+  PGItem::press(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::release
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse or keyboard button previously depressed with
+//               press() is released.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+release(const MouseWatcherParameter &param, bool background) {
+  if (MouseButton::is_mouse_button(param.get_button())) {
+    _mouse_button_page = false;
+  }
+  if (_dragging) {
+    end_drag();
+  }
+  PGItem::release(param, background);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::move
+//       Access: Protected, Virtual
+//  Description: This is a callback hook function, called whenever a
+//               mouse is moved while within the region.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+move(const MouseWatcherParameter &param) {
+  _mouse_pos = param.get_mouse();
+  if (_dragging) {
+    // We only get here if we the user originally clicked on the
+    // track, which caused the slider to move all the way to the mouse
+    // position, and then started dragging the mouse along the track.
+    // In this case, we start moving the thumb as if the user had
+    // started by dragging the thumb directly.
+    continue_drag();
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGSliderBar::has_cull_callback
 //       Access: Protected, Virtual
@@ -126,161 +203,599 @@ has_cull_callback() const {
 ////////////////////////////////////////////////////////////////////
 bool PGSliderBar::
 cull_callback(CullTraverser *trav, CullTraverserData &data) {
-  update();
+  if (_manage_pieces && _needs_remanage) {
+    remanage();
+  }
+  if (_needs_recompute) {
+    recompute();
+  }
+
+  if (_scroll_button_held != (PGItem *)NULL && 
+      _next_advance_time <= ClockObject::get_global_clock()->get_frame_time()) {
+    advance_scroll();
+  }
+
+  if (_mouse_button_page && 
+      _next_advance_time <= ClockObject::get_global_clock()->get_frame_time()) {
+    advance_page();
+  }
+
+  if (_needs_reposition) {
+    reposition();
+  }
+
   return PGItem::cull_callback(trav, data);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::setup
-//       Access: Public
-//  Description: Creates a PGSliderBar with the indicated dimensions,
-//               with the indicated maximum range.
+//     Function: PGSliderBar::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
 ////////////////////////////////////////////////////////////////////
 void PGSliderBar::
-setup(float width, float height, float range) {
-  //set_state(0);
-  //clear_state_def(0);
+xform(const LMatrix4f &mat) {
+  PGItem::xform(mat);
+  _axis = _axis * mat;
+
+  // Make sure we set the thumb to identity position first, so it
+  // won't be accidentally flattened.
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->clear_transform();
+  }
 
-  _width = 0.5 * width; // quick reference to find the left and right max points
-  //set_frame(-0.5f * width, 0.5f * width, -0.5f * height, 0.5f * height);
-  set_range(range);
+  _needs_remanage = true;
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::adjust
+//       Access: Public, Virtual
+//  Description: This is a callback hook function, called whenever the
+//               slider value is adjusted by the user or
+//               programmatically.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+adjust() {
+  string event = get_adjust_event();
+  play_sound(event);
+  throw_event(event);
 
-  NodePath current = NodePath(this);
-  if (!_slider) {
-    _slider = new PGSliderButton("slider");
-    _slider_button = current.attach_new_node(_slider);
+  if (has_notify()) {
+    get_notify()->slider_bar_adjust(this);
   }
-    
-  _slider->set_slider_bar(this);
-  //_slider->setup(_slider->get_name());
-  _slider->setup("");
-  _slider->set_drag_n_drop(true);
-  _slider->set_frame(-1,1,-1.5,1.5);
-  _slider_button.set_scale(_scale);
-  _slider_button.set_pos(0, 0, 0); // center it
-
-  // if left or right button to control slider desired, create them
-  if (!_slider_only) {
-    _left_button = current.attach_new_node(&_left);
-    _left.set_slider_bar(this);
-    _right_button = current.attach_new_node(&_right);
-    _right.set_slider_bar(this);
-    _left.setup(_left.get_name());
-    _right.setup(_right.get_name());
-    _left_button.set_scale(0.5);
-    _left_button.set_pos(-(_width+1), 0, -0.25);
-    _right_button.set_scale(0.5);
-    _right_button.set_pos((_width+0.5), 0, -0.25);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::setup_scroll_bar
+//       Access: Published
+//  Description: Creates PGSliderBar that represents a vertical or
+//               horizontal scroll bar (if vertical is true or false,
+//               respectively), with additional buttons for scrolling,
+//               and a range of 0 .. 1.
+//
+//               length here is the measurement along the scroll bar,
+//               and width is the measurement across the scroll bar,
+//               whether it is vertical or horizontal (so for a
+//               horizontal scroll bar, the length is actually the x
+//               dimension, and the width is the y dimension).
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+setup_scroll_bar(bool vertical, float length, float width, float bevel) {
+  set_state(0);
+  clear_state_def(0);
+
+  if (vertical) {
+    set_frame(-width / 2.0f, width / 2.0f, -length / 2.0f, length / 2.0f);
+    _axis = LVector3f::rfu(0.0f, 0.0f, -1.0f);
+  } else {
+    set_frame(-length / 2.0f, length / 2.0f, -width / 2.0f, width / 2.0f);
+    _axis = LVector3f::rfu(1.0f, 0.0f, 0.0f);
   }
 
   PGFrameStyle style;
-  style.set_width(0.3f, 0.3f);
-
-  /*
   style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
-  style.set_type(PGFrameStyle::T_bevel_in);
-  //set_frame_style(0, style);
-  */
+  style.set_type(PGFrameStyle::T_flat);
+  set_frame_style(0, style);
 
   style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
   style.set_type(PGFrameStyle::T_bevel_out);
-  _slider->set_frame_style(PGButton::S_ready, style);
-  style.set_type(PGFrameStyle::T_bevel_in);
-  _slider->set_frame_style(PGButton::S_depressed, style);
+  style.set_width(bevel, bevel);
+
+  // Remove the button nodes created by a previous call to setup(), if
+  // any.
+  if (_thumb_button != (PGButton *)NULL) {
+    remove_child(_thumb_button);
+    set_thumb_button(NULL);
+  }
+  if (_left_button != (PGButton *)NULL) {
+    remove_child(_left_button);
+    set_left_button(NULL);
+  }
+  if (_right_button != (PGButton *)NULL) {
+    remove_child(_right_button);
+    set_right_button(NULL);
+  }
+
+  PT(PGButton) thumb = new PGButton("thumb");
+  thumb->setup("", bevel);
+  thumb->set_frame(-width / 2.0f, width / 2.0f, 
+                   -width / 2.0f, width / 2.0f);
+  add_child(thumb);
+  set_thumb_button(thumb);
+
+  PT(PGButton) left = new PGButton("left");
+  left->setup("", bevel);
+  left->set_frame(-width / 2.0f, width / 2.0f, 
+                  -width / 2.0f, width / 2.0f);
+  left->set_transform(TransformState::make_pos(((width - length) / 2.0f) * _axis));
+  add_child(left);
+  set_left_button(left);
+
+  PT(PGButton) right = new PGButton("right");
+  right->setup("", bevel);
+  right->set_frame(-width / 2.0f, width / 2.0f, 
+                   -width / 2.0f, width / 2.0f);
+  right->set_transform(TransformState::make_pos(((length - width) / 2.0f) * _axis));
+  add_child(right);
+  set_right_button(right);
+
+  set_resize_thumb(true);
+  set_manage_pieces(true);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::press
-//       Access: Public, Virtual
-//  Description: This is a callback hook function, called whenever a
-//               mouse or keyboard button is depressed while the mouse
-//               is within the region.
+//     Function: PGSliderBar::setup_slider
+//       Access: Published
+//  Description: Creates PGSliderBar that represents a slider that the
+//               user can use to control an analog quantity.
+//
+//               This is functionally the same as a scroll bar, but it
+//               has a distinctive look.
 ////////////////////////////////////////////////////////////////////
 void PGSliderBar::
-press(const MouseWatcherParameter &param, bool background) {
-  PGItem::press(param, background);
-  //pgui_cat.info() << get_name() << "::" << param << endl;
-  //pgui_cat.info() << _slider->get_name() << "::press:x" << _slider_button.get_x() 
-  //                << "    press:y" << _slider_button.get_y() << endl;
-
-  // translate the mouse param position into frame space
-  LPoint2f mouse_point = param.get_mouse();
-  LVector3f result(mouse_point[0], mouse_point[1], 0);
-  result = get_frame_inv_xform().xform_point(result);
-  _update_slider = true;
-  _update_x = result[0];
-  _update_y = result[1];
-  //_slider_button.set_x(result[0]);
+setup_slider(bool vertical, float length, float width, float bevel) {
+  set_state(0);
+  clear_state_def(0);
+
+  if (vertical) {
+    set_frame(-width / 2.0f, width / 2.0f, -length / 2.0f, length / 2.0f);
+    _axis = LVector3f::rfu(0.0f, 0.0f, -1.0f);
+  } else {
+    set_frame(-length / 2.0f, length / 2.0f, -width / 2.0f, width / 2.0f);
+    _axis = LVector3f::rfu(1.0f, 0.0f, 0.0f);
+  }
+
+  PGFrameStyle style;
+  style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
+  style.set_type(PGFrameStyle::T_flat);
+  style.set_visible_scale(1.0f, 0.25f);
+  style.set_width(bevel, bevel);
+  set_frame_style(0, style);
+
+  // Remove the button nodes created by a previous call to setup(), if
+  // any.
+  if (_thumb_button != (PGButton *)NULL) {
+    remove_child(_thumb_button);
+    set_thumb_button(NULL);
+  }
+  if (_left_button != (PGButton *)NULL) {
+    remove_child(_left_button);
+    set_left_button(NULL);
+  }
+  if (_right_button != (PGButton *)NULL) {
+    remove_child(_right_button);
+    set_right_button(NULL);
+  }
+
+  PT(PGButton) thumb = new PGButton("thumb");
+  thumb->setup(" ", bevel);
+  thumb->set_frame(-width / 4.0f, width / 4.0f, 
+                   -width / 2.0f, width / 2.0f);
+  add_child(thumb);
+  set_thumb_button(thumb);
+
+  set_resize_thumb(false);
+  set_manage_pieces(true);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::drag
-//       Access: Public, Virtual
-//  Description: This is a hook function, called when the user 
-//               is trying to drag the slider button 
+//     Function: PGSliderBar::set_active
+//       Access: Published, Virtual
+//  Description: Sets whether the PGItem is active for mouse watching.
+//               This is not necessarily related to the
+//               active/inactive appearance of the item, which is
+//               controlled by set_state(), but it does affect whether
+//               it responds to mouse events.
 ////////////////////////////////////////////////////////////////////
 void PGSliderBar::
-drag(const MouseWatcherParameter &param) {
-  //pgui_cat.info() << get_name() << "::" << param << endl;
-  //pgui_cat.info() << _slider->get_name() << "::drag:x" << _slider_button.get_x() 
-  //                <<"    drag:y" << _slider_button.get_y() << endl;
-
-  // translate the mouse param position into frame space
-  LPoint2f mouse_point = param.get_mouse();
-  LVector3f result(mouse_point[0], mouse_point[1], 0);
-  result = get_frame_inv_xform().xform_point(result);
-  // keep the slider button within slider bar
-  if (result[0] < -_width)
-    result[0] = -_width;
-  if (result[0] > _width)
-    result[0] = _width;
-  _update_slider = true;
-  _update_x = result[0];
-  _update_y = result[1];
-  //_slider_button.set_x(result[0]);
+set_active(bool active) {
+  PGItem::set_active(active);
+
+  // This also implicitly sets the managed pieces.
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->set_active(active);
+  }
+  if (_left_button != (PGButton *)NULL) {
+    _left_button->set_active(active);
+  }
+  if (_right_button != (PGButton *)NULL) {
+    _right_button->set_active(active);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PGSliderBar::update
-//       Access: Private
-//  Description: Computes the appropriate size of the bar frame
-//               according to the percentage completed.
-////////////////////////////////////////////////////////////////////
-void PGSliderBar:: 
-update() {
-  //  int state = get_state();
-
-  // need left and right button input if they exist
-  if (!_slider_only) {
-    // Handle left and right button presses
-    if (_left.is_button_down()) {
-      // move the slider to the left
-      float x = _slider_button.get_x() - _speed;
-      _update_slider = true;
-      _update_x = max(x, -_width);
-      //_slider_button.set_x(max(x, -_width));
+//     Function: PGSliderBar::remanage
+//       Access: Published
+//  Description: Manages the position and size of the scroll bars and
+//               the thumb.  Normally this should not need to be
+//               called directly.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+remanage() {
+  _needs_remanage = false;
+
+  const LVecBase4f &frame = get_frame();
+
+  float width, length;
+  if (fabs(_axis[0]) > fabs(_axis[1] + _axis[2])) {
+    // The slider is X-dominant.
+    width = frame[3] - frame[2];
+    length = frame[1] - frame[0];
+    
+  } else {
+    // The slider is Y-dominant.
+    width = frame[1] - frame[0];
+    length = frame[3] - frame[2];
+  }
+
+  LVector3f center = LVector3f::rfu((frame[0] + frame[1]) / 2.0f,
+                                    0.0f,
+                                    (frame[2] + frame[3]) / 2.0f);
+
+  if (_left_button != (PGButton *)NULL) {
+    _left_button->set_frame(-width / 2.0f, width / 2.0f, 
+                            -width / 2.0f, width / 2.0f);
+    _left_button->xform(LMatrix4f::translate_mat(center + ((width - length) / 2.0f) * _axis));
+    _left_button->clear_transform();
+  }
+
+  if (_right_button != (PGButton *)NULL) {
+    _right_button->set_frame(-width / 2.0f, width / 2.0f, 
+                             -width / 2.0f, width / 2.0f);
+    _right_button->xform(LMatrix4f::translate_mat(center + ((length - width) / 2.0f) * _axis));
+    _right_button->clear_transform();
+  }
+
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->set_frame(-width / 2.0f, width / 2.0f, 
+                             -width / 2.0f, width / 2.0f);
+    _thumb_button->set_transform(TransformState::make_pos(center));
+  }
+
+  recompute();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::recompute
+//       Access: Published
+//  Description: Recomputes the position and size of the thumb.
+//               Normally this should not need to be called directly.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+recompute() {
+  _needs_recompute = false;
+
+  if (_min_value != _max_value) {
+    _scroll_ratio = fabs(_scroll_value / (_max_value - _min_value));
+    _page_ratio = fabs(_page_value / (_max_value - _min_value));
+
+  } else {
+    _scroll_ratio = 0.0f;
+    _page_ratio = 0.0f;
+  }
+
+  LVecBase4f frame = get_frame();
+  reduce_region(frame, _left_button);
+  reduce_region(frame, _right_button);
+
+  if (fabs(_axis[0]) > fabs(_axis[1] + _axis[2])) {
+    // The slider is X-dominant.
+    
+    _min_x = frame[0];
+    _max_x = frame[1];
+  
+    float trough_width = _max_x - _min_x;
+    
+    if (_thumb_button != (PGButton *)NULL) {
+      const LVecBase4f &thumb_frame = _thumb_button->get_frame();
+
+      if (_resize_thumb) {
+        // If we're allowed to adjust the thumb's size, we don't need to
+        // find out how wide it is.
+        _thumb_width = trough_width * min(1.0f, _page_ratio);
+        _thumb_button->set_frame(-_thumb_width / 2.0f, _thumb_width / 2.0f,
+                                 thumb_frame[2], thumb_frame[3]);
+      } else {
+        // If we're not adjusting the thumb's size, we do need to know
+        // its current width.
+        _thumb_width = thumb_frame[1] - thumb_frame[0];
+      }
+    }
+
+    _range_x = trough_width - _thumb_width;
+
+    const LVecBase4f &thumb_frame = _thumb_button->get_frame();
+    if (_axis[0] >= 0.0f) {
+      // The slider runs forwards, left to right.
+      _thumb_start = (_min_x - thumb_frame[0]) * _axis;
+    } else {
+      // The slider runs backwards: right to left.
+      _thumb_start = (thumb_frame[1] - _max_x) * _axis;
+    }
+    _thumb_start += LVector3f::rfu(0.0f, 0.0f, (frame[2] + frame[3]) / 2.0f);
+
+  } else {
+    // The slider is Y-dominant.  We call it X in the variable names,
+    // but it's really Y (or even Z).
+    
+    _min_x = frame[2];
+    _max_x = frame[3];
+  
+    float trough_width = _max_x - _min_x;
+    
+    if (_thumb_button == (PGButton *)NULL) {
+      _thumb_width = 0.0f;
+      _range_x = 0.0f;
+      _thumb_start.set(0.0f, 0.0f, 0.0f);
+
+    } else {
+      const LVecBase4f &thumb_frame = _thumb_button->get_frame();
+
+      if (_resize_thumb) {
+        // If we're allowed to adjust the thumb's size, we don't need to
+        // find out how wide it is.
+        _thumb_width = trough_width * min(1.0f, _page_ratio);
+        _thumb_button->set_frame(thumb_frame[0], thumb_frame[1],
+                                 -_thumb_width / 2.0f, _thumb_width / 2.0f);
+      } else {
+        // If we're not adjusting the thumb's size, we do need to know
+        // its current width.
+        _thumb_width = thumb_frame[3] - thumb_frame[2];
+      }
+
+      _range_x = trough_width - _thumb_width;
+
+      if (_axis[1] >= 0.0f && _axis[2] >= 0.0f) {
+        // The slider runs forwards, bottom to top.
+        _thumb_start = (_min_x - thumb_frame[2]) * _axis;
+      } else {
+        // The slider runs backwards: top to bottom.
+        _thumb_start = (thumb_frame[3] - _max_x) * _axis;
+      }
+      _thumb_start += LVector3f::rfu((frame[0] + frame[1]) / 2.0f, 0.0f, 0.0f);
     }
+  }
     
-    if (_right.is_button_down()) {
-      // move the slider to the right
-      float x = _slider_button.get_x() + _speed;
-      _update_slider = true;
-      _update_x = min(x, _width);
-      //_slider_button.set_x(min(x, _width));
+  reposition();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::frame_changed
+//       Access: Protected, Virtual
+//  Description: Called when the user changes the frame size.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+frame_changed() {
+  PGItem::frame_changed();
+  _needs_remanage = true;
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_transform_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's local transform
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_transform_changed(PGItem *) {
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_frame_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's frame
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_frame_changed(PGItem *) {
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_draw_mask_changed
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGItem's draw_mask
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_draw_mask_changed(PGItem *) {
+  _needs_recompute = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_press
+//       Access: Protected, Virtual
+//  Description: Called whenever the "press" event is triggered on a
+//               watched PGItem.  See PGItem::press().
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_press(PGItem *item, const MouseWatcherParameter &param) {
+  if (param.has_mouse()) {
+    _mouse_pos = param.get_mouse();
+  }
+  if (item == _left_button || item == _right_button) {
+    _scroll_button_held = item;
+    _mouse_button_page = false;
+    advance_scroll();
+    _next_advance_time = 
+      ClockObject::get_global_clock()->get_frame_time() + scroll_initial_delay;
+
+  } else if (item == _thumb_button) {
+    _scroll_button_held = NULL;
+    begin_drag();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_release
+//       Access: Protected, Virtual
+//  Description: Called whenever the "release" event is triggered on a
+//               watched PGItem.  See PGItem::release().
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_release(PGItem *item, const MouseWatcherParameter &) {
+  if (item == _scroll_button_held) {
+    _scroll_button_held = NULL;
+
+  } else if (item == _thumb_button) {
+    _scroll_button_held = NULL;
+    if (_dragging) {
+      end_drag();
     }
   }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::item_move
+//       Access: Protected, Virtual
+//  Description: Called whenever the "move" event is triggered on a
+//               watched PGItem.  See PGItem::move().
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+item_move(PGItem *item, const MouseWatcherParameter &param) {
+  _mouse_pos = param.get_mouse();
+  if (item == _thumb_button) {
+    if (_dragging) {
+      continue_drag();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::reposition
+//       Access: Private
+//  Description: A lighter-weight version of recompute(), this just
+//               moves the thumb, assuming all other properties are
+//               unchanged.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+reposition() {
+  _needs_reposition = false;
+
+  float t = get_ratio();
+  
+  if (_thumb_button != (PGButton *)NULL) {
+    _thumb_button->set_transform(TransformState::make_pos((t * _range_x) * _axis + _thumb_start));
+  }
+}
 
-  // press() and drag() update schedules this values that need to be
-  // applied here so that value of current slider position as a ratio
-  // of range can be updated
-  if (_update_slider) {
-    //pgui_cat.info() << "mouse_point:x " << _update_x 
-    //                << "   mouse_point:y " << _update_y << endl;
-    if (!_slider_button.is_empty())
-      _slider_button.set_x(_update_x);
-    _mapped_value = (_update_x + _width)/(2*_width);
-    _value = _negative_mapping ? ((_mapped_value-0.5)*2) : _mapped_value;
-    _update_slider = false;
-    throw_event(get_click_event());
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::advance_scroll
+//       Access: Private
+//  Description: Advances the scroll bar by one unit in the left or
+//               right direction while the user is holding down the
+//               left or right scroll button.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+advance_scroll() {
+  if (_scroll_button_held == _left_button) {
+    set_ratio(max(_ratio - _scroll_ratio, 0.0f));
+
+  } else if (_scroll_button_held == _right_button) {
+    set_ratio(min(_ratio + _scroll_ratio, 1.0f));
   }
+
+  _next_advance_time = 
+    ClockObject::get_global_clock()->get_frame_time() + scroll_continued_delay;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::advance_page
+//       Access: Private
+//  Description: Advances the scroll bar by one page in the left or
+//               right direction while the user is holding down the
+//               mouse button on the track.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+advance_page() {
+  // Is the mouse position left or right of the current thumb
+  // position?
+  LPoint3f mouse = mouse_to_local(_mouse_pos) - _thumb_start;
+  float target_ratio = mouse.dot(_axis) / _range_x;
+
+  float t;
+  if (target_ratio < _ratio) {
+    t = max(_ratio - _page_ratio + _scroll_ratio, target_ratio);
+
+  } else {
+    t = min(_ratio + _page_ratio - _scroll_ratio, target_ratio);
+  }
+  set_ratio(t);
+  if (t == target_ratio) {
+    // We made it; begin dragging from now on until the user releases
+    // the mouse.
+    begin_drag();
+  }
+
+  _next_advance_time = 
+    ClockObject::get_global_clock()->get_frame_time() + scroll_continued_delay;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::begin_drag
+//       Access: Private
+//  Description: Called when the user clicks down on the thumb button,
+//               possibly to begin dragging.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+begin_drag() {
+  if (_needs_recompute) {
+    recompute();
+  }
+  if (_range_x != 0.0f) {
+    float current_x = mouse_to_local(_mouse_pos).dot(_axis);
+    _drag_start_x = current_x - get_ratio() * _range_x;
+    _dragging = true;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::continue_drag
+//       Access: Private
+//  Description: Called as the user moves the mouse while still
+//               dragging on the thumb button.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+continue_drag() {
+  if (_needs_recompute) {
+    recompute();
+  }
+  if (_range_x != 0.0f) {
+    float current_x = mouse_to_local(_mouse_pos).dot(_axis);
+    set_ratio((current_x - _drag_start_x) / _range_x);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBar::end_drag
+//       Access: Private
+//  Description: Called as the user releases the mouse after dragging.
+////////////////////////////////////////////////////////////////////
+void PGSliderBar::
+end_drag() {
+  _dragging = false;
 }

+ 95 - 51
panda/src/pgui/pgSliderBar.h

@@ -22,15 +22,20 @@
 #include "pandabase.h"
 
 #include "pgItem.h"
-#include "pgSliderButton.h"
+#include "pgSliderBarNotify.h"
+#include "pgButtonNotify.h"
+#include "pgButton.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PGSliderBar
 // Description : This is a particular kind of PGItem that draws a
 //               little bar with a slider that moves from left to 
-//               right indicating a value between the ranges
+//               right indicating a value between the ranges.
+//
+//               This is used as an implementation for both
+//               DirectSlider and for DirectScrollBar.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA PGSliderBar : public PGItem {
+class EXPCL_PANDA PGSliderBar : public PGItem, public PGButtonNotify {
 PUBLISHED:
   PGSliderBar(const string &name = "");
   virtual ~PGSliderBar();
@@ -42,71 +47,110 @@ public:
   virtual PandaNode *make_copy() const;
   virtual bool has_cull_callback() const;
   virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
+  virtual void xform(const LMatrix4f &mat);
 
   virtual void press(const MouseWatcherParameter &param, bool background);
-  virtual void drag(const MouseWatcherParameter &param);
+  virtual void release(const MouseWatcherParameter &param, bool background);
+  virtual void move(const MouseWatcherParameter &param);
+
+  virtual void adjust();
+
+  INLINE void set_notify(PGSliderBarNotify *notify);
+  INLINE PGSliderBarNotify *get_notify() const;
 
 PUBLISHED:
-  void setup(float width, float height, float range);
+  void setup_scroll_bar(bool vertical, float length, float width, float bevel);
+  void setup_slider(bool vertical, float length, float width, float bevel);
+
+  INLINE void set_axis(const LVector3f &axis);
+  INLINE const LVector3f &get_axis() const;
 
-  INLINE void set_range(float range);
-  INLINE float get_range() const;
+  INLINE void set_range(float min_value, float max_value);
+  INLINE float get_min_value() const;
+  INLINE float get_max_value() const;
 
-  //INLINE void set_magnifier(float value);
-  //INLINE float get_magnifier() const;
+  INLINE void set_scroll_size(float scroll_size);
+  INLINE float get_scroll_size() const;
+
+  INLINE void set_page_size(float page_size);
+  INLINE float get_page_size() const;
 
   INLINE void set_value(float value);
   INLINE float get_value() const;
-  INLINE float get_mapped_value() const;
-  INLINE float get_update_x() const;
-  INLINE float get_update_y() const;
 
-  INLINE void set_speed(float speed);
-  INLINE float get_speed() const;
+  INLINE void set_ratio(float ratio);
+  INLINE float get_ratio() const;
+
+  INLINE void set_resize_thumb(bool resize_thumb);
+  INLINE bool get_resize_thumb() const;
+
+  INLINE void set_manage_pieces(bool manage_pieces);
+  INLINE bool get_manage_pieces() const;
 
-  INLINE void set_scale(float speed);
-  INLINE float get_scale() const;
+  INLINE void set_thumb_button(PGButton *thumb_button);
+  INLINE void clear_thumb_button();
+  INLINE PGButton *get_thumb_button() const;
 
-  INLINE void set_slider_only(bool value);
-  INLINE bool get_slider_only() const ;
-  INLINE void set_negative_mapping(bool value);
-  INLINE bool get_negative_mapping() const ;
+  INLINE void set_left_button(PGButton *left_button);
+  INLINE void clear_left_button();
+  INLINE PGButton *get_left_button() const;
 
-  INLINE void set_bar_style(const PGFrameStyle &style);
-  INLINE PGFrameStyle get_bar_style() const;
-  INLINE string get_click_event() const;
+  INLINE void set_right_button(PGButton *right_button);
+  INLINE void clear_right_button();
+  INLINE PGButton *get_right_button() const;
 
-  INLINE void set_slider_button(NodePath &np, PGSliderButton *button);
+  INLINE static string get_adjust_prefix();
+  INLINE string get_adjust_event() const;
 
-  INLINE NodePath get_slider_button() const;
-  INLINE NodePath get_left_button() const;
-  INLINE NodePath get_right_button() const;
+  virtual void set_active(bool active);
+
+  void remanage();
+  void recompute();
+
+protected:
+  virtual void frame_changed();
+
+  virtual void item_transform_changed(PGItem *item);
+  virtual void item_frame_changed(PGItem *item);
+  virtual void item_draw_mask_changed(PGItem *item);
+  virtual void item_press(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_release(PGItem *item, const MouseWatcherParameter &param);
+  virtual void item_move(PGItem *item, const MouseWatcherParameter &param);
+
+private:
+  void reposition();
+  void advance_scroll();
+  void advance_page();
+  void begin_drag();
+  void continue_drag();
+  void end_drag();
 
 private:
-  void update();
-
-  bool _slider_only;
-  bool _negative_mapping;
-  bool _update_slider;
-
-  // These 3 variables control slider range
-  float _value;
-  float _mapped_value;
-  float _update_x;
-  float _update_y;
-
-  float _range;
-  float _speed, _width;
-  float _scale;
-  int _bar_state;
-  PGFrameStyle _bar_style;
-  PGSliderButton *_slider;
-  PGSliderButton _left;
-  PGSliderButton _right;
-  NodePath _bar;
-  NodePath _slider_button;
-  NodePath _left_button;
-  NodePath _right_button;
+  bool _needs_remanage;
+  bool _needs_recompute;
+  bool _needs_reposition;
+
+  float _min_value, _max_value;
+  float _scroll_value, _scroll_ratio;
+  float _page_value, _page_ratio;
+  float _ratio;
+  bool _resize_thumb;
+  bool _manage_pieces;
+
+  LVector3f _axis;
+
+  PT(PGButton) _thumb_button;
+  PT(PGButton) _left_button;
+  PT(PGButton) _right_button;
+
+  float _min_x, _max_x, _thumb_width, _range_x;
+  LPoint3f _thumb_start;
+  PGItem *_scroll_button_held;
+  bool _mouse_button_page;
+  LPoint2f _mouse_pos;
+  double _next_advance_time;
+  bool _dragging;
+  float _drag_start_x;
 
 public:
   static TypeHandle get_class_type() {

+ 27 - 0
panda/src/pgui/pgSliderBarNotify.I

@@ -0,0 +1,27 @@
+// Filename: pgSliderBarNotify.I
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBarNotify::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PGSliderBarNotify::
+PGSliderBarNotify() {
+}

+ 40 - 0
panda/src/pgui/pgSliderBarNotify.cxx

@@ -0,0 +1,40 @@
+// Filename: pgSliderBarNotify.cxx
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgSliderBarNotify.h"
+#include "pgSliderBar.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBarNotify::slider_bar_adjust
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGSliderBar's value
+//               has been changed by the user or programmatically.
+////////////////////////////////////////////////////////////////////
+void PGSliderBarNotify::
+slider_bar_adjust(PGSliderBar *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGSliderBarNotify::slider_bar_set_range
+//       Access: Protected, Virtual
+//  Description: Called whenever a watched PGSliderBar's overall range
+//               has been changed.
+////////////////////////////////////////////////////////////////////
+void PGSliderBarNotify::
+slider_bar_set_range(PGSliderBar *) {
+}

+ 46 - 0
panda/src/pgui/pgSliderBarNotify.h

@@ -0,0 +1,46 @@
+// Filename: pgSliderBarNotify.h
+// Created by:  drose (18Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGSLIDERBARNOTIFY_H
+#define PGSLIDERBARNOTIFY_H
+
+#include "pandabase.h"
+#include "pgItemNotify.h"
+
+class PGSliderBar;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGSliderBarNotify
+// Description : Objects that inherit from this class can receive
+//               notify messages when a slider bar moves or otherwise
+//               is reconfigured.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGSliderBarNotify : public PGItemNotify {
+public:
+  INLINE PGSliderBarNotify();
+
+protected:
+  virtual void slider_bar_adjust(PGSliderBar *slider_bar);
+  virtual void slider_bar_set_range(PGSliderBar *slider_bar);
+
+  friend class PGSliderBar;
+};
+
+#include "pgSliderBarNotify.I"
+
+#endif

+ 0 - 94
panda/src/pgui/pgSliderButton.cxx

@@ -1,94 +0,0 @@
-// Filename: pgSliderButton.cxx
-// Created by:  masad (21Oct04)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
-//
-// All use of this software is subject to the terms of the Panda 3d
-// Software license.  You should have received a copy of this license
-// along with this source code; you will also find a current copy of
-// the license at http://etc.cmu.edu/panda3d/docs/license/ .
-//
-// To contact the maintainers of this program write to
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#include "pgSliderButton.h"
-#include "pgSliderBar.h"
-#include "dcast.h"
-#include "pgMouseWatcherParameter.h"
-
-#include "throw_event.h"
-#include "mouseButton.h"
-#include "mouseWatcherParameter.h"
-#include "colorAttrib.h"
-#include "transformState.h"
-
-TypeHandle PGSliderButton::_type_handle;
-
-////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::Constructor
-//       Access: Published
-//  Description: 
-////////////////////////////////////////////////////////////////////
-PGSliderButton::
-PGSliderButton(const string &name) : PGButton(name)
-{
-  _drag_n_drop = false;
-  _slider_bar = NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::Destructor
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-PGSliderButton::
-~PGSliderButton() {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::Copy Constructor
-//       Access: Public
-//  Description: 
-////////////////////////////////////////////////////////////////////
-PGSliderButton::
-PGSliderButton(const PGSliderButton &copy) :
-  PGButton(copy),
-  _drag_n_drop(copy._drag_n_drop)
-  //  _slider_bar(copy._slider_bar)
-{
-  _slider_bar = NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::make_copy
-//       Access: Public, Virtual
-//  Description: Returns a newly-allocated Node that is a shallow copy
-//               of this one.  It will be a different Node pointer,
-//               but its internal data may or may not be shared with
-//               that of the original Node.
-////////////////////////////////////////////////////////////////////
-PandaNode *PGSliderButton::
-make_copy() const {
-  return new PGSliderButton(*this);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: PGSliderButton::move
-//       Access: Public, Virtual
-//  Description: This is a callback hook function, called whenever the
-//               button is dragged left or right  by the user normally.
-////////////////////////////////////////////////////////////////////
-void PGSliderButton::
-move(const MouseWatcherParameter &param) {
-  PGButton::move(param);
-  if (_drag_n_drop && is_button_down()) {
-    PGSliderBar *slider = DCAST(PGSliderBar, _slider_bar);
-    slider->drag(param);
-    //pgui_cat.info() << get_name() << "::move()" << endl;
-  }
-}

+ 0 - 77
panda/src/pgui/pgSliderButton.h

@@ -1,77 +0,0 @@
-// Filename: pgSliderButton.h
-// Created by:  masad (21Oct04)
-//
-////////////////////////////////////////////////////////////////////
-//
-// PANDA 3D SOFTWARE
-// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
-//
-// All use of this software is subject to the terms of the Panda 3d
-// Software license.  You should have received a copy of this license
-// along with this source code; you will also find a current copy of
-// the license at http://etc.cmu.edu/panda3d/docs/license/ .
-//
-// To contact the maintainers of this program write to
-// [email protected] .
-//
-////////////////////////////////////////////////////////////////////
-
-#ifndef PGSLIDERBUTTON_H
-#define PGSLIDERBUTTON_H
-
-#include "pandabase.h"
-
-#include "pgButton.h"
-#include "nodePath.h"
-#include "pset.h"
-
-////////////////////////////////////////////////////////////////////
-//       Class : PGSliderButton
-// Description : This is a particular kind of PGItem that is
-//               specialized to behave like a normal button object.
-//               It keeps track of its own state, and handles mouse
-//               events sensibly.
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA PGSliderButton : public PGButton {
-PUBLISHED:
-  PGSliderButton(const string &name);
-  virtual ~PGSliderButton();
-
-public:
-
-  virtual void move(const MouseWatcherParameter &param);
-  PGSliderButton(const PGSliderButton &copy);
-
-PUBLISHED:
-
-  INLINE bool is_drag_n_drop();
-  INLINE void set_drag_n_drop(bool value);
-  INLINE void set_slider_bar(PGItem *item);
-
-public:
-  virtual PandaNode *make_copy() const;
-  
-  bool _drag_n_drop;
-  PGItem *_slider_bar;
-
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    PGButton::init_type();
-    register_type(_type_handle, "PGSliderButton",
-                  PGButton::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-
-};
-
-#include "pgSliderButton.I"
-
-#endif

+ 16 - 7
panda/src/pgui/pgTop.I

@@ -42,6 +42,18 @@ get_mouse_watcher() const {
   return _watcher;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PGTop::get_group
+//       Access: Published
+//  Description: Returns the MouseWatcherGroup pointer that the PGTop
+//               object registers its PG items with, or NULL if the
+//               MouseWatcher has not yet been set.
+////////////////////////////////////////////////////////////////////
+INLINE MouseWatcherGroup *PGTop::
+get_group() const {
+  return _watcher_group;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PGTop::set_start_sort
 //       Access: Published
@@ -83,15 +95,12 @@ get_start_sort() const {
 //     Function: PGTop::add_region
 //       Access: Public
 //  Description: Adds the indicated region to the set of regions in
-//               the group.  Returns true if it was successfully
-//               added, or false if it was already on the list.
+//               the group.
 ////////////////////////////////////////////////////////////////////
-INLINE bool PGTop::
+INLINE void PGTop::
 add_region(MouseWatcherRegion *region) {
-  if (_watcher_group == (PGMouseWatcherGroup *)NULL) {
-    return false;
-  }
-  return _watcher_group->add_region(region);
+  nassertv(_watcher_group != (PGMouseWatcherGroup *)NULL);
+  _watcher_group->add_region(region);
 }
 
 ////////////////////////////////////////////////////////////////////

+ 2 - 1
panda/src/pgui/pgTop.h

@@ -60,13 +60,14 @@ public:
 PUBLISHED:
   void set_mouse_watcher(MouseWatcher *watcher);
   INLINE MouseWatcher *get_mouse_watcher() const;
+  INLINE MouseWatcherGroup *get_group() const;
 
   INLINE void set_start_sort(int start_sort);
   INLINE int get_start_sort() const;
 
 public:
   // These methods duplicate the functionality of MouseWatcherGroup.
-  INLINE bool add_region(MouseWatcherRegion *region);
+  INLINE void add_region(MouseWatcherRegion *region);
   INLINE void clear_regions();
 
 private:

+ 104 - 0
panda/src/pgui/pgVirtualFrame.I

@@ -0,0 +1,104 @@
+// Filename: pgVirtualFrame.I
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::set_clip_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the clip frame.
+//               This is the size of the small window through which we
+//               can see the virtual canvas.  Normally, this is the
+//               same size as the actual frame or smaller (typically
+//               it is smaller by the size of the bevel, or to make
+//               room for scroll bars).
+////////////////////////////////////////////////////////////////////
+INLINE void PGVirtualFrame::
+set_clip_frame(float left, float right, float bottom, float top) {
+  set_clip_frame(LVecBase4f(left, right, bottom, top));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::get_clip_frame
+//       Access: Published
+//  Description: Returns the bounding rectangle of the clip frame.
+//               See set_clip_frame().  If has_clip_frame() is
+//               false, this returns the item's actual frame.
+////////////////////////////////////////////////////////////////////
+INLINE const LVecBase4f &PGVirtualFrame::
+get_clip_frame() const {
+  return _has_clip_frame ? _clip_frame : get_frame();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::has_clip_frame
+//       Access: Published
+//  Description: Returns true if the clip frame has been set; see
+//               set_clip_frame().  If it has not been set, objects in
+//               the virtual frame will not be clipped.
+////////////////////////////////////////////////////////////////////
+INLINE bool PGVirtualFrame::
+has_clip_frame() const {
+  return _has_clip_frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::set_canvas_transform
+//       Access: Published
+//  Description: Changes the transform of the virtual canvas.  This
+//               transform is applied to all child nodes of the
+//               canvas_node.
+////////////////////////////////////////////////////////////////////
+INLINE void PGVirtualFrame::
+set_canvas_transform(const TransformState *transform) {
+  _canvas_node->set_transform(transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::get_canvas_transform
+//       Access: Published
+//  Description: Returns the transform of the virtual canvas.  This
+//               transform is applied to all child nodes of the
+//               canvas_node.
+////////////////////////////////////////////////////////////////////
+INLINE const TransformState *PGVirtualFrame::
+get_canvas_transform() const {
+  return _canvas_node->get_transform();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::get_canvas_node
+//       Access: Published
+//  Description: Returns the special node that holds all of the
+//               children that appear in the virtual canvas.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *PGVirtualFrame::
+get_canvas_node() const {
+  return _canvas_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::get_clip_plane_node
+//       Access: Published
+//  Description: Returns the special node that holds all of PlaneNodes
+//               that are used for applying the clip planes to the
+//               canvas_node.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *PGVirtualFrame::
+get_clip_plane_node() const {
+  return _clip_plane_node;
+}

+ 241 - 0
panda/src/pgui/pgVirtualFrame.cxx

@@ -0,0 +1,241 @@
+// Filename: pgVirtualFrame.cxx
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pgVirtualFrame.h"
+#include "clipPlaneAttrib.h"
+#include "planeNode.h"
+#include "sceneGraphReducer.h"
+
+TypeHandle PGVirtualFrame::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGVirtualFrame::
+PGVirtualFrame(const string &name) : PGItem(name)
+{
+  _has_clip_frame = false;
+  _clip_frame.set(0.0f, 0.0f, 0.0f, 0.0f);
+
+  setup_child_nodes();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGVirtualFrame::
+~PGVirtualFrame() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::Copy Constructor
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PGVirtualFrame::
+PGVirtualFrame(const PGVirtualFrame &copy) :
+  PGItem(copy),
+  _has_clip_frame(copy._has_clip_frame),
+  _clip_frame(copy._clip_frame)
+{
+  setup_child_nodes();
+
+  // Reassign the clip planes according to the clip frame.
+  if (_has_clip_frame) {
+    set_clip_frame(_clip_frame);
+  } else {
+    clear_clip_frame();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *PGVirtualFrame::
+make_copy() const {
+  return new PGVirtualFrame(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::r_copy_children
+//       Access: Protected, Virtual
+//  Description: This is called by r_copy_subgraph(); the copy has
+//               already been made of this particular node (and this
+//               is the copy); this function's job is to copy all of
+//               the children from the original.
+//
+//               Note that it includes the parameter inst_map, which
+//               is a map type, and is not (and cannot be) exported
+//               from PANDA.DLL.  Thus, any derivative of PandaNode
+//               that is not also a member of PANDA.DLL *cannot*
+//               access this map, and probably should not even
+//               override this function.
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
+  PandaNode::r_copy_children(from, inst_map);
+
+  // Reassign the canvas_node to point to the new copy, if it's there.
+  const PGVirtualFrame *from_frame = DCAST(PGVirtualFrame, from);
+  PandaNode *from_canvas_node = from_frame->get_canvas_node();
+  PandaNode *from_clip_plane_node = from_frame->get_clip_plane_node();
+
+  InstanceMap::const_iterator ci;
+  ci = inst_map.find(from_canvas_node);
+  if (ci != inst_map.end()) {
+    remove_child(_canvas_node);
+    _canvas_node = DCAST(ModelNode, (*ci).second);
+  }
+
+  ci = inst_map.find(from_clip_plane_node);
+  if (ci != inst_map.end()) {
+    remove_child(_clip_plane_node);
+    _clip_plane_node = (*ci).second;
+  }
+
+  // Reassign the clip planes according to the clip frame.
+  if (_has_clip_frame) {
+    set_clip_frame(_clip_frame);
+  } else {
+    clear_clip_frame();
+  }
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::setup
+//       Access: Published
+//  Description: Creates a PGVirtualFrame with the indicated 
+//               dimensions.
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+setup(float width, float height) {
+  set_state(0);
+  clear_state_def(0);
+
+  set_frame(0, width, 0, height);
+
+  float bevel = 0.05f;
+
+  PGFrameStyle style;
+  style.set_width(bevel, bevel);
+
+  style.set_color(0.8f, 0.8f, 0.8f, 1.0f);
+  style.set_type(PGFrameStyle::T_bevel_out);
+  set_frame_style(0, style);
+
+  set_clip_frame(bevel, width - 2 * bevel,
+                 bevel, height - 2 * bevel);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::set_clip_frame
+//       Access: Published
+//  Description: Sets the bounding rectangle of the clip frame.
+//               This is the size of the small window through which we
+//               can see the virtual canvas.  Normally, this is the
+//               same size as the actual frame or smaller (typically
+//               it is smaller by the size of the bevel, or to make
+//               room for scroll bars).
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+set_clip_frame(const LVecBase4f &frame) {
+  if (!_has_clip_frame || _clip_frame != frame) {
+    _has_clip_frame = true;
+    _clip_frame = frame;
+    
+    const LVector3f r = LVector3f::right();
+    const LVector3f u = LVector3f::up();
+    
+    PT(PlaneNode) left_clip = 
+      new PlaneNode("left_clip", Planef(r, r * _clip_frame[0]));
+    PT(PlaneNode) right_clip = 
+      new PlaneNode("right_clip", Planef(-r, r * _clip_frame[1]));
+    
+    PT(PlaneNode) bottom_clip = 
+      new PlaneNode("bottom_clip", Planef(u, u * _clip_frame[2]));
+    PT(PlaneNode) top_clip = 
+      new PlaneNode("top_clip", Planef(-u, u * _clip_frame[3]));
+    
+    _clip_plane_node->remove_all_children();
+    _clip_plane_node->add_child(left_clip);
+    _clip_plane_node->add_child(right_clip);
+    _clip_plane_node->add_child(bottom_clip);
+    _clip_plane_node->add_child(top_clip);
+    
+    CPT(ClipPlaneAttrib) plane_attrib = DCAST(ClipPlaneAttrib, ClipPlaneAttrib::make());
+    plane_attrib = DCAST(ClipPlaneAttrib, plane_attrib->add_on_plane(NodePath::any_path(left_clip)));
+    plane_attrib = DCAST(ClipPlaneAttrib, plane_attrib->add_on_plane(NodePath::any_path(right_clip)));
+    plane_attrib = DCAST(ClipPlaneAttrib, plane_attrib->add_on_plane(NodePath::any_path(bottom_clip)));
+    plane_attrib = DCAST(ClipPlaneAttrib, plane_attrib->add_on_plane(NodePath::any_path(top_clip)));
+    
+    _canvas_node->set_attrib(plane_attrib);
+    clip_frame_changed();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::clear_clip_frame
+//       Access: Published
+//  Description: Removes the clip frame from the item.  This
+//               disables clipping.
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+clear_clip_frame() {
+  if (_has_clip_frame) {
+    _has_clip_frame = false;
+    
+    _canvas_node->clear_attrib(ClipPlaneAttrib::get_class_type());
+    clip_frame_changed();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::clip_frame_changed
+//       Access: Protected, Virtual
+//  Description: Called when the user changes the clip_frame size.
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+clip_frame_changed() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PGVirtualFrame::setup_child_nodes
+//       Access: Private
+//  Description: Creates the special canvas_node and clip_plane_node
+//               for this object.
+////////////////////////////////////////////////////////////////////
+void PGVirtualFrame::
+setup_child_nodes() {
+  _canvas_node = new ModelNode("canvas");
+  _canvas_node->set_preserve_transform(ModelNode::PT_local);
+  // We have to preserve the clip plane attribute.
+  _canvas_node->set_preserve_attributes(SceneGraphReducer::TT_other);
+  add_child(_canvas_node);
+
+  _clip_plane_node = new PandaNode("clip_planes");
+  add_child(_clip_plane_node);
+}

+ 112 - 0
panda/src/pgui/pgVirtualFrame.h

@@ -0,0 +1,112 @@
+// Filename: pgVirtualFrame.h
+// Created by:  drose (17Aug05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef PGVIRTUALFRAME_H
+#define PGVIRTUALFRAME_H
+
+#include "pandabase.h"
+
+#include "pgItem.h"
+#include "modelNode.h"
+
+class TransformState;
+
+////////////////////////////////////////////////////////////////////
+//       Class : PGVirtualFrame
+// Description : This represents a frame that is rendered as a window
+//               onto another (possibly much larger) canvas.  You can
+//               only see the portion of the canvas that is below the
+//               window at any given time.
+//
+//               This works simply by automatically defining clipping
+//               planes to be applied to a special child node, called
+//               the canvas_node, of the PGVirtualFrame node.  Every
+//               object that is parented to the canvas_node will be
+//               clipped by the clip_frame.  Also, you can modify the
+//               canvas_transform through convenience methods here,
+//               which actually modifies the transform on the
+//               canvas_node.
+//
+//               The net effect is that the virtual canvas is
+//               arbitrarily large, and we can peek at it through the
+//               clip_frame, and scroll through different parts of it
+//               by modifying the canvas_transform.
+//
+//               See PGScrollFrame for a specialization of this class
+//               that handles the traditional scrolling canvas, with
+//               scroll bars.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA PGVirtualFrame : public PGItem {
+PUBLISHED:
+  PGVirtualFrame(const string &name = "");
+  virtual ~PGVirtualFrame();
+
+protected:
+  PGVirtualFrame(const PGVirtualFrame &copy);
+  virtual PandaNode *make_copy() const;
+  virtual void r_copy_children(const PandaNode *from, InstanceMap &inst_map);
+
+PUBLISHED:
+  void setup(float width, float height);
+
+  INLINE void set_clip_frame(float left, float right, float bottom, float top);
+  void set_clip_frame(const LVecBase4f &clip_frame);
+  INLINE const LVecBase4f &get_clip_frame() const;
+  INLINE bool has_clip_frame() const;
+  void clear_clip_frame();
+
+  INLINE void set_canvas_transform(const TransformState *transform);
+  INLINE const TransformState *get_canvas_transform() const;
+
+  INLINE PandaNode *get_canvas_node() const;
+  INLINE PandaNode *get_clip_plane_node() const;
+
+protected:
+  virtual void clip_frame_changed();
+
+private:
+  void setup_child_nodes();
+
+private:
+  bool _has_clip_frame;
+  LVecBase4f _clip_frame;
+
+  PT(ModelNode) _canvas_node;
+  PT(PandaNode) _clip_plane_node;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PGItem::init_type();
+    register_type(_type_handle, "PGVirtualFrame",
+                  PGItem::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "pgVirtualFrame.I"
+
+#endif

+ 4 - 2
panda/src/pgui/pgWaitBar.cxx

@@ -113,7 +113,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PGWaitBar::setup
-//       Access: Public
+//       Access: Published
 //  Description: Creates a PGWaitBar with the indicated dimensions,
 //               with the indicated maximum range.
 ////////////////////////////////////////////////////////////////////
@@ -124,8 +124,10 @@ setup(float width, float height, float range) {
 
   set_frame(-0.5f * width, 0.5f * width, -0.5f * height, 0.5f * height);
 
+  float bevel = 0.05f;
+
   PGFrameStyle style;
-  style.set_width(0.05f, 0.05f);
+  style.set_width(bevel, bevel);
 
   style.set_color(0.6f, 0.6f, 0.6f, 1.0f);
   style.set_type(PGFrameStyle::T_bevel_in);

+ 4 - 3
panda/src/pgui/pgui_composite1.cxx

@@ -1,9 +1,10 @@
-    
 #include "config_pgui.cxx"
 #include "pgButton.cxx"
-#include "pgSliderButton.cxx"
+#include "pgButtonNotify.cxx"
 #include "pgCullTraverser.cxx"
 #include "pgEntry.cxx"
 #include "pgMouseWatcherGroup.cxx"
 #include "pgMouseWatcherParameter.cxx"
-
+#include "pgFrameStyle.cxx"
+#include "pgItem.cxx"
+#include "pgItemNotify.cxx"

+ 4 - 4
panda/src/pgui/pgui_composite2.cxx

@@ -1,9 +1,9 @@
-
-#include "pgFrameStyle.cxx"
-#include "pgItem.cxx"
 #include "pgMouseWatcherBackground.cxx"
 #include "pgMouseWatcherRegion.cxx"
+#include "pgScrollFrame.cxx"
+#include "pgSliderBar.cxx"
+#include "pgSliderBarNotify.cxx"
 #include "pgTop.cxx"
+#include "pgVirtualFrame.cxx"
 #include "pgWaitBar.cxx"
-#include "pgSliderBar.cxx"
 

+ 2 - 1
panda/src/putil/bam.h

@@ -35,9 +35,10 @@ static const unsigned short _bam_major_ver = 5;
 // Bumped to major version 4 on 4/10/02 to store new scene graph.
 // Bumped to major version 5 on 5/6/05 for new Geom implementation.
 
-static const unsigned short _bam_minor_ver = 2;
+static const unsigned short _bam_minor_ver = 3;
 // Bumped to minor version 1 on 7/14/05 to add TextureStage::_saved_result.
 // Bumped to minor version 2 on 7/21/05 to add TransformState::is_2d.
+// Bumped to minor version 3 on 8/25/05 to add ModelNode::_preserve_attributes.
 
 
 #endif

+ 1 - 1
panda/src/tform/Sources.pp

@@ -4,7 +4,7 @@
 #begin lib_target
   #define TARGET tform
   #define LOCAL_LIBS \
-    dgraph pgraph linmath display event putil gobj gsgbase \
+    grutil dgraph pgraph linmath display event putil gobj gsgbase \
     mathutil device
 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 

+ 72 - 60
panda/src/tform/mouseWatcher.cxx

@@ -29,6 +29,7 @@
 #include "transformState.h"
 #include "displayRegion.h"
 #include "dcast.h"
+#include "indent.h"
 
 #include <algorithm>
 
@@ -122,58 +123,9 @@ get_over_region(const LPoint2f &pos) const {
   return get_preferred_region(regions);
 }
 
-
-////////////////////////////////////////////////////////////////////
-//     Function: MouseWatcher::output
-//       Access: Public, Virtual
-//  Description:
-////////////////////////////////////////////////////////////////////
-void MouseWatcher::
-output(ostream &out) const {
-  DataNode::output(out);
-
-  int count = _regions.size();
-  Groups::const_iterator gi;
-  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
-    MouseWatcherGroup *group = (*gi);
-    count += group->_regions.size();
-  }
-
-  out << " (" << count << " regions)";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: MouseWatcher::write
-//       Access: Public, Virtual
-//  Description:
-////////////////////////////////////////////////////////////////////
-void MouseWatcher::
-write(ostream &out, int indent_level) const {
-  indent(out, indent_level)
-    << "MouseWatcher " << get_name() << ":\n";
-  Regions::const_iterator ri;
-  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
-    MouseWatcherRegion *region = (*ri);
-    region->write(out, indent_level + 2);
-  }
-
-  if (!_groups.empty()) {
-    Groups::const_iterator gi;
-    for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
-      MouseWatcherGroup *group = (*gi);
-      indent(out, indent_level + 2)
-        << "Subgroup:\n";
-      for (ri = group->_regions.begin(); ri != group->_regions.end(); ++ri) {
-        MouseWatcherRegion *region = (*ri);
-        region->write(out, indent_level + 4);
-      }
-    }
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::add_group
-//       Access: Public
+//       Access: Published
 //  Description: Adds the indicated group of regions to the set of
 //               regions the MouseWatcher will monitor each frame.
 //
@@ -207,7 +159,7 @@ add_group(MouseWatcherGroup *group) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::remove_group
-//       Access: Public
+//       Access: Published
 //  Description: Removes the indicated group from the set of extra
 //               groups associated with the MouseWatcher.  Returns
 //               true if successful, or false if the group was already
@@ -237,6 +189,71 @@ remove_group(MouseWatcherGroup *group) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_num_groups
+//       Access: Published
+//  Description: Returns the number of separate groups added to the
+//               MouseWatcher via add_group().
+////////////////////////////////////////////////////////////////////
+int MouseWatcher::
+get_num_groups() const {
+  return _groups.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::get_group
+//       Access: Published
+//  Description: Returns the nth group added to the MouseWatcher via
+//               add_group().
+////////////////////////////////////////////////////////////////////
+MouseWatcherGroup *MouseWatcher::
+get_group(int n) const {
+  nassertr(n >= 0 && n < (int)_groups.size(), NULL);
+  return _groups[n];
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::output
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+output(ostream &out) const {
+  DataNode::output(out);
+
+  int count = _regions.size();
+  Groups::const_iterator gi;
+  for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+    MouseWatcherGroup *group = (*gi);
+    count += group->_regions.size();
+  }
+
+  out << " (" << count << " regions)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcher::write
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseWatcher::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level)
+    << "MouseWatcher " << get_name() << ":\n";
+  MouseWatcherGroup::write(out, indent_level + 2);
+
+  if (!_groups.empty()) {
+    Groups::const_iterator gi;
+    for (gi = _groups.begin(); gi != _groups.end(); ++gi) {
+      MouseWatcherGroup *group = (*gi);
+      indent(out, indent_level + 2)
+        << "Subgroup:\n";
+      group->write(out, indent_level + 4);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcher::get_over_regions
 //       Access: Protected
@@ -611,14 +628,14 @@ throw_event_pattern(const string &pattern, const MouseWatcherRegion *region,
 //               being moved from last position.
 ////////////////////////////////////////////////////////////////////
 void MouseWatcher::
-move(ButtonHandle button) {
+move() {
   MouseWatcherParameter param;
-  param.set_button(button);
   param.set_modifier_buttons(_mods);
   param.set_mouse(_mouse);
 
-  if (_preferred_button_down_region != (MouseWatcherRegion *)NULL)
+  if (_preferred_button_down_region != (MouseWatcherRegion *)NULL) {
     _preferred_button_down_region->move(param);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -980,7 +997,6 @@ consider_keyboard_suppress(const MouseWatcherRegion *region) {
 ////////////////////////////////////////////////////////////////////
 void MouseWatcher::
 do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
-  bool mouse_moved = false;
   // Initially, we do not suppress any events to objects below us in
   // the data graph.
   _internal_suppress = 0;
@@ -1002,7 +1018,7 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
     // Asad: determine if mouse moved from last position
     const LVecBase2f &last_f = _xy->get_value();
     if (f != last_f) {
-      mouse_moved = true;
+      move();
     }
 
     if (_display_region != (DisplayRegion *)NULL) {
@@ -1083,10 +1099,6 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
 
   if (_has_mouse &&
       (_internal_suppress & MouseWatcherRegion::SF_mouse_position) == 0) {
-    if (mouse_moved) {
-      move(ButtonHandle::none());
-      //tform_cat.info() << "do_transmit_data()::mouse_moved" << endl;
-    }
     // Transmit the mouse position.
     _xy->set_value(_mouse);
     output.set_data(_xy_output, EventParameter(_xy));

+ 6 - 4
panda/src/tform/mouseWatcher.h

@@ -109,13 +109,15 @@ PUBLISHED:
   INLINE DisplayRegion *get_display_region() const;
   INLINE bool has_display_region() const;
 
+  bool add_group(MouseWatcherGroup *group);
+  bool remove_group(MouseWatcherGroup *group);
+  int get_num_groups() const;
+  MouseWatcherGroup *get_group(int n) const;
+
 public:
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level = 0) const;
 
-  bool add_group(MouseWatcherGroup *group);
-  bool remove_group(MouseWatcherGroup *group);
-
 protected:
   typedef pvector< PT(MouseWatcherRegion) > VRegions;
   void get_over_regions(VRegions &regions, const LPoint2f &pos) const;
@@ -136,7 +138,7 @@ protected:
                            const MouseWatcherRegion *region,
                            const ButtonHandle &button);
 
-  void move(ButtonHandle button);
+  void move();
   void press(ButtonHandle button);
   void release(ButtonHandle button);
   void keystroke(int keycode);

+ 189 - 19
panda/src/tform/mouseWatcherGroup.cxx

@@ -17,10 +17,24 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "mouseWatcherGroup.h"
-
+#include "lineSegs.h"
+#include "indent.h"
 
 TypeHandle MouseWatcherGroup::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+MouseWatcherGroup::
+MouseWatcherGroup() {
+#ifndef NDEBUG
+  _show_regions = false;
+  _color.set(0.4f, 0.6f, 1.0f, 1.0f);
+#endif  // NDEBUG
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: MouseWatcherGroup::Destructor
 //       Access: Public, Virtual
@@ -34,31 +48,30 @@ MouseWatcherGroup::
 //     Function: MouseWatcherGroup::add_region
 //       Access: Published
 //  Description: Adds the indicated region to the set of regions in
-//               the group.  Returns true if it was successfully
-//               added, or false if it was already on the list.
+//               the group.  It is an error to add the same region to
+//               the set more than once.
 ////////////////////////////////////////////////////////////////////
-bool MouseWatcherGroup::
+void MouseWatcherGroup::
 add_region(MouseWatcherRegion *region) {
-  //return _regions.insert(region).second;
-
   PT(MouseWatcherRegion) pt = region;
 
   // We will only bother to check for duplicates in the region list if
-  // we are building opt 1 or 2.  The overhead for doing this may be
-  // too high if we have many regions.
-#ifdef _DEBUG
+  // we are building a development Panda.  The overhead for doing this
+  // may be too high if we have many regions.
+#ifndef NDEBUG
   // See if the region is in the set/vector already
   Regions::const_iterator ri = 
     find(_regions.begin(), _regions.end(), pt);
-  if (ri != _regions.end()) {
-    // Already in the set, return false
-    return false;
+  nassertv(ri == _regions.end());
+
+  // Also add it to the vizzes if we have them.
+  if (_show_regions) {
+    nassertv(_vizzes.size() == _regions.size());
+    _vizzes.push_back(make_viz_region(pt));
   }
-#endif
+#endif  // NDEBUG
 
-  // Not in the set, add it and return true
   _regions.push_back(pt);
-  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -69,7 +82,7 @@ add_region(MouseWatcherRegion *region) {
 ////////////////////////////////////////////////////////////////////
 bool MouseWatcherGroup::
 has_region(MouseWatcherRegion *region) const {
-  // See if the region is in the set/vector
+  // See if the region is in the vector.
   PT(MouseWatcherRegion) pt = region;
   Regions::const_iterator ri = 
     find(_regions.begin(), _regions.end(), pt);
@@ -90,14 +103,23 @@ has_region(MouseWatcherRegion *region) const {
 ////////////////////////////////////////////////////////////////////
 bool MouseWatcherGroup::
 remove_region(MouseWatcherRegion *region) {
-  //return _regions.erase(region) != 0;
-
-  // See if the region is in the set/vector
+  // See if the region is in the vector.
   PT(MouseWatcherRegion) pt = region;
   Regions::iterator ri = 
     find(_regions.begin(), _regions.end(), pt);
   if (ri != _regions.end()) {
     // Found it, now erase it
+#ifndef NDEBUG
+    // Also remove it from the vizzes.
+    if (_show_regions) {
+      nassertr(_vizzes.size() == _regions.size(), false);
+      size_t index = ri - _regions.begin();
+      Vizzes::iterator vi = _vizzes.begin() + index;
+      _show_regions_root.node()->remove_child(*vi);
+      _vizzes.erase(vi);
+    }
+#endif  // NDEBUG    
+
     _regions.erase(ri);
     return true;
   }
@@ -134,4 +156,152 @@ find_region(const string &name) const {
 void MouseWatcherGroup::
 clear_regions() {
   _regions.clear();
+
+#ifndef NDEBUG
+  if (_show_regions) {
+    _show_regions_root.node()->remove_all_children();
+    _vizzes.clear();
+  }
+#endif  // NDEBUG
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::get_num_regions
+//       Access: Published
+//  Description: Returns the number of regions in the group.
+////////////////////////////////////////////////////////////////////
+int MouseWatcherGroup::
+get_num_regions() const {
+  return _regions.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::get_region
+//       Access: Published
+//  Description: Returns the nth regions in the group.
+////////////////////////////////////////////////////////////////////
+MouseWatcherRegion *MouseWatcherGroup::
+get_region(int n) const {
+  nassertr(n >= 0 && n < (int)_regions.size(), NULL);
+  return _regions[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::output
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+output(ostream &out) const {
+  out << "MouseWatcherGroup (" << _regions.size() << " regions)";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::write
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+write(ostream &out, int indent_level) const {
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    MouseWatcherRegion *region = (*ri);
+    region->write(out, indent_level);
+  }
+}
+
+#ifndef NDEBUG
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::show_regions
+//       Access: Published
+//  Description: Enables the visualization of all of the regions
+//               handled by this MouseWatcherGroup.  The supplied
+//               NodePath should be the root of the 2-d scene graph
+//               for the window.
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+show_regions(const NodePath &render2d) {
+  _show_regions = true;
+  _show_regions_root = render2d.attach_new_node("show_regions");
+  _show_regions_root.set_bin("unsorted", 0);
+  update_regions();
+}
+#endif  // NDEBUG
+
+#ifndef NDEBUG
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::set_color
+//       Access: Published
+//  Description: Specifies the color used to draw the region
+//               rectangles for the regions visualized by
+//               show_regions().
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+set_color(const Colorf &color) {
+  _color = color;
+  update_regions();
+}
+#endif  // NDEBUG
+
+#ifndef NDEBUG
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::hide_regions
+//       Access: Published
+//  Description: Stops the visualization created by a previous call to
+//               show_regions().
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+hide_regions() {
+  _show_regions_root.remove_node();
+  _show_regions = false;
+  _vizzes.clear();
+}
+#endif  // NDEBUG
+
+#ifndef NDEBUG
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::update_regions
+//       Access: Private
+//  Description: Internally regenerates the show_regions()
+//               visualization.
+////////////////////////////////////////////////////////////////////
+void MouseWatcherGroup::
+update_regions() {
+  _show_regions_root.node()->remove_all_children();
+  _vizzes.clear();
+  _vizzes.reserve(_regions.size());
+
+  Regions::const_iterator ri;
+  for (ri = _regions.begin(); ri != _regions.end(); ++ri) {
+    _vizzes.push_back(make_viz_region(*ri));
+  }
+}
+#endif  // NDEBUG
+
+#ifndef NDEBUG
+////////////////////////////////////////////////////////////////////
+//     Function: MouseWatcherGroup::make_viz_region
+//       Access: Private
+//  Description: Creates a node to represent the indicated region, and
+//               attaches it to the _show_regions_root.  Does not add
+//               it to _vizzes.
+////////////////////////////////////////////////////////////////////
+PandaNode *MouseWatcherGroup::
+make_viz_region(MouseWatcherRegion *region) {
+  LineSegs ls("show_regions");
+  ls.set_color(_color);
+
+  const LVecBase4f &f = region->get_frame();
+
+  ls.move_to(LVector3f::rfu(f[0], 0.0f, f[2]));
+  ls.draw_to(LVector3f::rfu(f[1], 0.0f, f[2]));
+  ls.draw_to(LVector3f::rfu(f[1], 0.0f, f[3]));
+  ls.draw_to(LVector3f::rfu(f[0], 0.0f, f[3]));
+  ls.draw_to(LVector3f::rfu(f[0], 0.0f, f[2]));
+
+  PT(PandaNode) node = ls.create();
+  _show_regions_root.attach_new_node(node);
+
+  return node;
 }
+#endif  // NDEBUG

+ 28 - 1
panda/src/tform/mouseWatcherGroup.h

@@ -25,6 +25,7 @@
 #include "pointerTo.h"
 #include "referenceCount.h"
 #include "pvector.h"
+#include "nodePath.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : MouseWatcherGroup
@@ -33,19 +34,45 @@
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA MouseWatcherGroup : virtual public ReferenceCount {
 public:
+  MouseWatcherGroup();
   virtual ~MouseWatcherGroup();
 
 PUBLISHED:
-  bool add_region(MouseWatcherRegion *region);
+  void add_region(MouseWatcherRegion *region);
   bool has_region(MouseWatcherRegion *region) const;
   bool remove_region(MouseWatcherRegion *region);
   MouseWatcherRegion *find_region(const string &name) const;
   void clear_regions();
 
+  int get_num_regions() const;
+  MouseWatcherRegion *get_region(int n) const;
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+#ifndef NDEBUG
+  void show_regions(const NodePath &render2d);
+  void set_color(const Colorf &color);
+  void hide_regions();
+#endif  // NDEBUG
+
 protected:
   typedef pvector< PT(MouseWatcherRegion) > Regions;
   Regions _regions;
 
+private:
+#ifndef NDEBUG
+  void update_regions();
+  PandaNode *make_viz_region(MouseWatcherRegion *region);
+
+  typedef pvector< PT(PandaNode) > Vizzes;
+  Vizzes _vizzes;
+
+  bool _show_regions;
+  NodePath _show_regions_root;
+  Colorf _color;
+#endif  // NDEBUG
+
 public:
   static TypeHandle get_class_type() {
     ReferenceCount::init_type();