ScrolledButtonsList

Panda3D Forum Index -> Code Snippets Post new topic   Reply to topic
View previous topic :: View next topic  
Author Message
ynjh_jo


Posts: 1596
Location: Malang, Indonesia

PostPosted: Thu Dec 06, 2007 2:24 am    Post subject: ScrolledButtonsList Reply with quote
Some1 asked me for this, so here it is.
Code:
__all__=['ScrolledButtonsList']

from pandac.PandaModules import *
from direct.showbase.DirectObject import DirectObject
from direct.gui.OnscreenText import OnscreenText
from direct.gui.DirectGui import DirectFrame,DirectButton,DirectScrolledFrame,DGG
from direct.interval.IntervalGlobal import Sequence
from direct.task import Task
from direct.showbase.PythonUtil import clampScalar
from types import IntType

class ScrolledButtonsList(DirectObject):
  """
     A class to display a list of selectable buttons.
     It is displayed using scrollable window (DirectScrolledFrame).
  """
  def __init__(self, parent=None, frameSize=(.8,1.2), buttonTextColor=(1,1,1,1),
               font=None, itemScale=.045, itemTextScale=1, itemTextZ=0,
               command=None, contextMenu=None, autoFocus=0,
               colorChange=1, colorChangeDuration=1.0, newItemColor=(0,1,0,1),
               rolloverColor=Vec4(1,.8,.2,1),
               suppressMouseWheel=1, modifier='control'):
      self.focusButton=None
      self.command=command
      self.contextMenu=contextMenu
      self.autoFocus=autoFocus
      self.colorChange=colorChange
      self.colorChangeDuration=colorChangeDuration*.5
      self.newItemColor=Vec4(*newItemColor)
      self.rolloverColor=Vec4(*rolloverColor)
      self.rightClickTextColors=(Vec4(0,1,0,1),Vec4(0,35,100,1))
      self.font=font
      if font:
         self.fontHeight=font.getLineHeight()
      else:
         self.fontHeight=TextNode.getDefaultFont().getLineHeight()
      self.fontHeight*=1.2 # let's enlarge font height a little
      self.xtraSideSpace=.2*self.fontHeight
      self.itemTextScale=itemTextScale
      self.itemTextZ=itemTextZ
      self.buttonTextColor=buttonTextColor
      self.suppressMouseWheel=suppressMouseWheel
      self.modifier=modifier
      self.buttonsList=[]
      self.numItems=0
      self.__eventReceivers={}
      # DirectScrolledFrame to hold items
      self.itemScale=itemScale
      self.itemVertSpacing=self.fontHeight*self.itemScale
      self.frameWidth,self.frameHeight=frameSize
      # I set canvas' Z size smaller than the frame to avoid the auto-generated vertical slider bar
      self.frame = DirectScrolledFrame(
                   parent=parent,pos=(-self.frameWidth*.5,0,.5*self.frameHeight), relief=DGG.GROOVE,
                   state=DGG.NORMAL, # to create a mouse watcher region
                   frameSize=(0, self.frameWidth, -self.frameHeight, 0), frameColor=(0,0,0,.7),
                   canvasSize=(0, 0, -self.frameHeight*.5, 0), borderWidth=(0.01,0.01),
                   manageScrollBars=0, enableEdit=0, suppressMouse=0, sortOrder=1000 )
      # the real canvas is "self.frame.getCanvas()",
      # but if the frame is hidden since the beginning,
      # no matter how I set the canvas Z pos, the transform would be resistant,
      # so just create a new node under the canvas to be my canvas
      self.canvas=self.frame.getCanvas().attachNewNode('myCanvas')
      # slider background
      SliderBG=DirectFrame( parent=self.frame,frameSize=(-.025,.025,-self.frameHeight,0),
                   frameColor=(0,0,0,.7), pos=(-.03,0,0),enableEdit=0, suppressMouse=0)
      # slider thumb track
      sliderTrack = DirectFrame( parent=SliderBG, relief=DGG.FLAT, #state=DGG.NORMAL,
                   frameColor=(1,1,1,.2), frameSize=(-.015,.015,-self.frameHeight+.01,-.01),
                   enableEdit=0, suppressMouse=0)
      # page up
      self.pageUpRegion=DirectFrame( parent=SliderBG, relief=DGG.FLAT, state=DGG.NORMAL,
                   frameColor=(1,.8,.2,.1), frameSize=(-.015,.015,0,0),
                   enableEdit=0, suppressMouse=0)
      self.pageUpRegion.setAlphaScale(0)
      self.pageUpRegion.bind(DGG.B1PRESS,self.__startScrollPage,[-1])
      self.pageUpRegion.bind(DGG.WITHIN,self.__continueScrollUp)
      self.pageUpRegion.bind(DGG.WITHOUT,self.__suspendScrollUp)
      # page down
      self.pageDnRegion=DirectFrame( parent=SliderBG, relief=DGG.FLAT, state=DGG.NORMAL,
                   frameColor=(1,.8,.2,.1), frameSize=(-.015,.015,0,0),
                   enableEdit=0, suppressMouse=0)
      self.pageDnRegion.setAlphaScale(0)
      self.pageDnRegion.bind(DGG.B1PRESS,self.__startScrollPage,[1])
      self.pageDnRegion.bind(DGG.WITHIN,self.__continueScrollDn)
      self.pageDnRegion.bind(DGG.WITHOUT,self.__suspendScrollDn)
      self.pageUpDnSuspended=[0,0]
      # slider thumb
      self.vertSliderThumb=DirectButton(parent=SliderBG, relief=DGG.FLAT,
                   frameColor=(1,1,1,.6), frameSize=(-.015,.015,0,0),
                   enableEdit=0, suppressMouse=0)
      self.vertSliderThumb.bind(DGG.B1PRESS,self.__startdragSliderThumb)
      self.vertSliderThumb.bind(DGG.WITHIN,self.__enteringThumb)
      self.vertSliderThumb.bind(DGG.WITHOUT,self.__exitingThumb)
      self.oldPrefix=base.buttonThrowers[0].node().getPrefix()
      self.sliderThumbDragPrefix='draggingSliderThumb-'
      # GOD & I DAMN IT !!!
      # These things below don't work well if the canvas has a lot of buttons.
      # So I end up checking the mouse region every frame by myself using a continuous task.
#       self.accept(DGG.WITHIN+self.frame.guiId,self.__enteringFrame)
#       self.accept(DGG.WITHOUT+self.frame.guiId,self.__exitingFrame)
      self.isMouseInRegion=False
      self.mouseOutInRegionCommand=(self.__exitingFrame,self.__enteringFrame)
      taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion')

  def __getFrameRegion(self,t):
      for g in range(base.mouseWatcherNode.getNumGroups()):
          region=base.mouseWatcherNode.getGroup(g).findRegion(self.frame.guiId)
          if region!=None:
             self.frameRegion=region
             taskMgr.add(self.__mouseInRegionCheck,'mouseInRegionCheck')
             break

  def __mouseInRegionCheck(self,t):
      """
         check if the mouse is within or without the scrollable frame, and
         upon within or without, run the provided command
      """
      if not base.mouseWatcherNode.hasMouse(): return Task.cont
      m=base.mouseWatcherNode.getMouse()
      bounds=self.frameRegion.getFrame()
      inRegion=bounds[0]<m[0]<bounds[1] and bounds[2]<m[1]<bounds[3]
      if self.isMouseInRegion==inRegion: return Task.cont
      self.isMouseInRegion=inRegion
      self.mouseOutInRegionCommand[inRegion]()
      return Task.cont

  def __startdragSliderThumb(self,m=None):
      mpos=base.mouseWatcherNode.getMouse()
      parentZ=self.vertSliderThumb.getParent().getZ(render2d)
      sliderDragTask=taskMgr.add(self.__dragSliderThumb,'dragSliderThumb')
      sliderDragTask.ZposNoffset=mpos[1]-self.vertSliderThumb.getZ(render2d)+parentZ
#       sliderDragTask.mouseX=base.winList[0].getPointer(0).getX()
      self.oldPrefix=base.buttonThrowers[0].node().getPrefix()
      base.buttonThrowers[0].node().setPrefix(self.sliderThumbDragPrefix)
      self.acceptOnce(self.sliderThumbDragPrefix+'mouse1-up',self.__stopdragSliderThumb)

  def __dragSliderThumb(self,t):
      if not base.mouseWatcherNode.hasMouse():
         return
      mpos=base.mouseWatcherNode.getMouse()
#       newY=base.winList[0].getPointer(0).getY()
      self.__updateCanvasZpos((t.ZposNoffset-mpos[1])/self.canvasRatio)
#       base.winList[0].movePointer(0, t.mouseX, newY)
      return Task.cont

  def __stopdragSliderThumb(self,m=None):
      taskMgr.remove('dragSliderThumb')
      self.__stopScrollPage()
      base.buttonThrowers[0].node().setPrefix(self.oldPrefix)
      if self.isMouseInRegion:
         self.mouseOutInRegionCommand[self.isMouseInRegion]()

  def __startScrollPage(self,dir,m):
      self.oldPrefix=base.buttonThrowers[0].node().getPrefix()
      base.buttonThrowers[0].node().setPrefix(self.sliderThumbDragPrefix)
      self.acceptOnce(self.sliderThumbDragPrefix+'mouse1-up',self.__stopdragSliderThumb)
      t=taskMgr.add(self.__scrollPage,'scrollPage',extraArgs=[int((dir+1)*.5),dir*.01/self.canvasRatio])
      self.pageUpDnSuspended=[0,0]

  def __scrollPage(self,dir,scroll):
      if not self.pageUpDnSuspended[dir]:
         self.__scrollCanvas(scroll)
      return Task.cont

  def __stopScrollPage(self,m=None):
      taskMgr.remove('scrollPage')

  def __suspendScrollUp(self,m=None):
      self.pageUpRegion.setAlphaScale(0)
      self.pageUpDnSuspended[0]=1
  def __continueScrollUp(self,m=None):
      if taskMgr.hasTaskNamed('dragSliderThumb'):
         return
      self.pageUpRegion.setAlphaScale(1)
      self.pageUpDnSuspended[0]=0
 
  def __suspendScrollDn(self,m=None):
      self.pageDnRegion.setAlphaScale(0)
      self.pageUpDnSuspended[1]=1
  def __continueScrollDn(self,m=None):
      if taskMgr.hasTaskNamed('dragSliderThumb'):
         return
      self.pageDnRegion.setAlphaScale(1)
      self.pageUpDnSuspended[1]=0

  def __suspendScrollPage(self,m=None):
      self.__suspendScrollUp()
      self.__suspendScrollDn()
 
  def __enteringThumb(self,m=None):
      self.vertSliderThumb['frameColor']=(1,1,1,1)
      self.__suspendScrollPage()

  def __exitingThumb(self,m=None):
      self.vertSliderThumb['frameColor']=(1,1,1,.6)

  def __scrollCanvas(self,scroll):
      if self.vertSliderThumb.isHidden():
         return
      self.__updateCanvasZpos(self.canvas.getZ()+scroll)

  def __updateCanvasZpos(self,Zpos):
      newZ=clampScalar(Zpos, .0, self.canvasLen-self.frameHeight+.015)
      self.canvas.setZ(newZ)
      thumbZ=-newZ*self.canvasRatio
      self.vertSliderThumb.setZ(thumbZ)
      self.pageUpRegion['frameSize']=(-.015,.015,thumbZ-.01,-.01)
      self.pageDnRegion['frameSize']=(-.015,.015,-self.frameHeight+.01,thumbZ+self.vertSliderThumb['frameSize'][2])

  def __adjustCanvasLength(self,numItem):
      self.canvasLen=float(numItem)*self.itemVertSpacing
      self.canvasRatio=(self.frameHeight-.015)/(self.canvasLen+.01)
      if self.canvasLen<=self.frameHeight-.015:
         canvasZ=.0
         self.vertSliderThumb.hide()
         self.pageUpRegion.hide()
         self.pageDnRegion.hide()
         self.canvasLen=self.frameHeight-.015
      else:
         canvasZ=self.canvas.getZ()
         self.vertSliderThumb.show()
         self.pageUpRegion.show()
         self.pageDnRegion.show()
      self.__updateCanvasZpos(canvasZ)
      self.vertSliderThumb['frameSize']=(-.015,.015,-self.frameHeight*self.canvasRatio,-.01)
      thumbZ=self.vertSliderThumb.getZ()
      self.pageUpRegion['frameSize']=(-.015,.015,thumbZ-.01,-.01)
      self.pageDnRegion['frameSize']=(-.015,.015,-self.frameHeight+.01,thumbZ+self.vertSliderThumb['frameSize'][2])

  def __acceptAndIgnoreWorldEvent(self,event,command,extraArgs=[]):
      receivers=messenger.whoAccepts(event)
      if receivers is None:
         self.__eventReceivers[event]={}
      else:
         newD={}
         for r in receivers:
             newr=messenger._getObject(r) if type(r)==tuple else r
             newD[newr]=receivers[r]
         self.__eventReceivers[event]=newD
      for r in self.__eventReceivers[event].keys():
          r.ignore(event)
      self.accept(event,command,extraArgs)

  def __ignoreAndReAcceptWorldEvent(self,events):
      for event in events:
          self.ignore(event)
          if self.__eventReceivers.has_key(event):
             for r, method_xtraArgs_persist in self.__eventReceivers[event].items():
                 messenger.accept(event,r,*method_xtraArgs_persist)
          self.__eventReceivers[event]={}

  def __enteringFrame(self,m=None):
      # sometimes the WITHOUT event for page down region doesn't fired,
      # so directly suspend the page scrolling here
      self.__suspendScrollPage()
      BTprefix=base.buttonThrowers[0].node().getPrefix()
      if BTprefix==self.sliderThumbDragPrefix:
         return
      self.inOutBTprefix=BTprefix
      if self.suppressMouseWheel:
         self.__acceptAndIgnoreWorldEvent(self.inOutBTprefix+'wheel_up',
              command=self.__scrollCanvas, extraArgs=[-.07])
         self.__acceptAndIgnoreWorldEvent(self.inOutBTprefix+'wheel_down',
              command=self.__scrollCanvas, extraArgs=[.07])
      else:
         self.accept(self.inOutBTprefix+self.modifier+'-wheel_up',self.__scrollCanvas, [-.07])
         self.accept(self.inOutBTprefix+self.modifier+'-wheel_down',self.__scrollCanvas, [.07])
      print 'enteringFrame'

  def __exitingFrame(self,m=None):
      if not hasattr(self,'inOutBTprefix'):
         return
      if self.suppressMouseWheel:
         self.__ignoreAndReAcceptWorldEvent( (
                                             self.inOutBTprefix+'wheel_up',
                                             self.inOutBTprefix+'wheel_down',
                                             ) )
      else:
         self.ignore(self.inOutBTprefix+self.modifier+'-wheel_up')
         self.ignore(self.inOutBTprefix+self.modifier+'-wheel_down')
      print 'exitingFrame'

  def __setFocusButton(self,button,item):
      if self.focusButton:
         self.deselect()
      self.focusButton=button
      self.select()
      if callable(self.command):
         # run user command and pass the selected item, it's index, and the button
         self.command(item,self.buttonsList.index(button),button)

  def __rightPressed(self,button,m):
      self.__isRightIn=True
#       text0 : normal
#       text1 : pressed
#       text2 : rollover
#       text3 : disabled
      button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusButton==button])
      button.bind(DGG.B3RELEASE,self.__rightReleased,[button])
      button.bind(DGG.WITHIN,self.__rightIn,[button])
      button.bind(DGG.WITHOUT,self.__rightOut,[button])

  def __rightIn(self,button,m):
      self.__isRightIn=True
      button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusButton==button])
  def __rightOut(self,button,m):
      self.__isRightIn=False
      button._DirectGuiBase__componentInfo['text2'][0].setColorScale(Vec4(1,1,1,1))

  def __rightReleased(self,button,m):
      button.unbind(DGG.B3RELEASE)
      button.unbind(DGG.WITHIN)
      button.unbind(DGG.WITHOUT)
      button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor)
      if not self.__isRightIn:
         return
      if callable(self.contextMenu):
         # run user command and pass the selected item, it's index, and the button
         self.contextMenu(button['extraArgs'][1],self.buttonsList.index(button),button)

  def deselect(self):
      """
         stop highlighting item
      """
      if self.focusButton:
         self.focusButton['text_fg']=(1,1,1,1)
         self.focusButton['frameColor']=(0,0,0,0)

  def select(self,idx=None):
      """
         highlight the item
      """
      if idx is not None:
         if not 0<=idx<self.numItems:
            print 'SELECT : invalid index (%s)' %idx
            return
         self.focusButton=self.buttonsList[idx]
      if self.focusButton:
         self.focusButton['text_fg']=(.01,.01,.01,1)
         self.focusButton['frameColor']=(1,.8,.2,1)

  def clear(self):
      """
         clear the list
      """
      for c in self.buttonsList:
          c.remove()
      self.buttonsList=[]
      self.focusButton=None
      self.numItems=0

  def addItem(self,text,extraArgs=None,atIndex=None):
      """
         add item to the list
         text : text for the button
         extraArgs : the object which will be passed to user command(s)
                     (both command and contextMenu) when the button get clicked
         atIndex : where to add the item
                   <None> : put item at the end of list
                   <integer> : put item at index <integer>
                   <button> : put item at <button>'s index
      """
      button = DirectButton(parent=self.canvas,
          scale=self.itemScale,
          relief=DGG.FLAT,
          frameColor=(0,0,0,0),text_scale=self.itemTextScale,
          text=text, text_pos=(0,self.itemTextZ),text_fg=self.buttonTextColor,
          text_font=self.font, text_align=TextNode.ALeft,
          command=self.__setFocusButton,
          enableEdit=0, suppressMouse=0)

      l,r,b,t=button.getBounds()
      # top & bottom are blindly set without knowing where exactly the baseline is,
      # but this ratio fits most fonts
      baseline=-self.fontHeight*.25
      button['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,baseline,baseline+self.fontHeight)

#          Zc=NodePath(button).getBounds().getCenter()[1]-self.fontHeight*.5+.25
# #          Zc=button.getCenter()[1]-self.fontHeight*.5+.25
#          button['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,Zc,Zc+self.fontHeight)
     
      button['extraArgs']=[button,extraArgs]
      button._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor)
      button.bind(DGG.B3PRESS,self.__rightPressed,[button])
      isButton=isinstance(atIndex,DirectButton)
      if isButton:
         if atIndex.isEmpty():
            atIndex=None
         else:
            index=self.buttonsList.index(atIndex)
            self.buttonsList.insert(index,button)
      if atIndex==None:
         self.buttonsList.append(button)
         index=self.numItems
      elif not isButton:
         index=int(atIndex)
         self.buttonsList.insert(index,button)
      Zpos=(-.7-index)*self.itemVertSpacing
      button.setPos(.02,0,Zpos)
      if index!=self.numItems:
         for i in range(index+1,self.numItems+1):
             self.buttonsList[i].setZ(self.buttonsList[i],-self.fontHeight)
      self.numItems+=1
      self.__adjustCanvasLength(self.numItems)
      if self.autoFocus:
         self.focusViewOnItem(index)
      if self.colorChange:
         Sequence(
            button.colorScaleInterval(self.colorChangeDuration,self.newItemColor,Vec4(1,.2,0,1)),
            button.colorScaleInterval(self.colorChangeDuration,Vec4(1,1,1,1),self.newItemColor)
            ).start()

  def focusViewOnItem(self,idx):
      """
         Scroll the window so the newly added item will be displayed
         in the middle of the window, if possible.
      """
      Zpos=(idx+.7)*self.itemVertSpacing-self.frameHeight*.5
      self.__updateCanvasZpos(Zpos)
     
  def setAutoFocus(self,b):
      """
         set auto-view-focus state of newly added item
      """
      self.autoFocus=b

  def index(self,button):
      """
         get the index of button
      """
      if not button in self.buttonsList:
         return None
      return self.buttonsList.index(button)
     
  def getSelected(self):
      """
         get the currently selected button,
         returns None if no button is selected
      """
      return self.focusButton

  def getSelectedIndex(self):
      """
         get the currently selected button index,
         returns None if no button is selected
      """
      return self.index(self.focusButton)

  def getNumItems(self):
      """
         get the current number of items on the list
      """
      return self.numItems

  def disableItem(self,i):
      if not 0<=i<self.numItems:
         print 'DISABLING : invalid index (%s)' %i
         return
      self.buttonsList[i]['state']=DGG.DISABLED
      self.buttonsList[i].setColorScale(.3,.3,.3,1)
 
  def enableItem(self,i):
      if not 0<=i<self.numItems:
         print 'ENABLING : invalid index (%s)' %i
         return
      self.buttonsList[i]['state']=DGG.NORMAL
      self.buttonsList[i].setColorScale(1,1,1,1)

  def removeItem(self,index):
      if not 0<=index<self.numItems:
         print 'REMOVAL : invalid index (%s)' %index
         return
      if self.numItems==0: return
      if self.focusButton==self.buttonsList[index]:
         self.focusButton=None
      self.buttonsList[index].removeNode()
      del self.buttonsList[index]
      self.numItems-=1
      for i in range(index,self.numItems):
          self.buttonsList[i].setZ(self.buttonsList[i],self.fontHeight)
      self.__adjustCanvasLength(self.numItems)

  def destroy(self):
      self.clear()
      self.__exitingFrame()
      self.ignoreAll()
      self.frame.removeNode()
      taskMgr.remove('mouseInRegionCheck')

  def hide(self):
      self.frame.hide()
      self.isMouseInRegion=False
      self.__exitingFrame()
      taskMgr.remove('mouseInRegionCheck')

  def show(self):
      self.frame.show()
      if not hasattr(self,'frameRegion'):
         taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion')
      elif not taskMgr.hasTaskNamed('mouseInRegionCheck'):
         taskMgr.add(self.__mouseInRegionCheck,'mouseInRegionCheck')

  def toggleVisibility(self):
      if self.frame.isHidden():
         self.show()
      else:
         self.hide()
 
  def sort(self,reverse=False):
      buttonsTexts=[(b['text'],b) for b in self.buttonsList]
      buttonsTexts.sort()
      if reverse:
         buttonsTexts.reverse()
      self.buttonsList=[bt[1] for bt in buttonsTexts]
      for i in range(self.getNumItems()):
          Zpos=(-.7-i)*self.itemVertSpacing
          self.buttonsList[i].setPos(.02,0,Zpos)



if __name__=='__main__':
   import direct.directbase.DirectStart
   import string,sys
   from random import uniform
   
   selectedButton=None

   def itemSelected(item,index,button): # don't forget to receive item, it's index, and the button
       global selectedButton
       selectedButton=button
       print item,'(idx : %s) SELECTED' %index

   def itemRightClicked(item,index,button): # don't forget to receive item, it's index, and the button
       print item,'(idx : %s) RIGHT-CLICKED' %index

   def addNewItem():
       num=btnList.getNumItems()
       i='new item '+str(num)
       btnList.addItem(text=i, extraArgs='NEW_%s'%num)

   def insertNewItemAtSelected():
       num=btnList.getNumItems()
       i='new item '+str(num)
       btnList.addItem(text=i, extraArgs='NEW_%s'%num, atIndex=selectedButton)

   def insertNewItemBelowSelected():
       index=btnList.index(selectedButton)
       num=btnList.getNumItems()
       if index==None:
          index=num-1
       i='new item '+str(num)
       btnList.addItem(text=i, extraArgs='NEW_%s'%num, atIndex=index+1)

   def disableRandomItem():
       btnList.disableItem(int(uniform(0,btnList.getNumItems())))
       
   def enableRandomItem():
       btnList.enableItem(int(uniform(0,btnList.getNumItems())))

   def removeRandomItem():
       btnList.removeItem(int(uniform(0,btnList.getNumItems())))
       
   # 3d node
   teapot=loader.loadModel('teapot')
   teapot.reparentTo(render)

   OnscreenText(text='''[ SPACE ] : toggle visibility       [ S ] : sort list       [ DEL ] : destroy list
[ MOUSE WHEEL ]  inside scrollable window : scroll window
[ MOUSE WHEEL ]  outside scrollable window : change teapot scale
hold  [ ENTER ] : add new item at the end of list
hold  [ INSERT ] : insert new item at selected item
hold  [ CTRL-INSERT ] : insert new item below selected item
''', scale=.045, fg=(1,1,1,1), shadow=(0,0,0,1)).setZ(.96)
   OnscreenText(text='''[ CAPS LOCK] : disable random item
[ TAB ] : enable random item
hold  [ X ] : remove random item
''', scale=.045, fg=(1,1,1,1), shadow=(0,0,0,1)).setZ(-.75)


   # create ScrolledButtonsList
   btnList = ScrolledButtonsList(
      parent=None, # attach to this parent node
      frameSize=(.8,1.2), buttonTextColor=(1,1,1,1),
      font=None, itemScale=.045, itemTextScale=1, itemTextZ=0,
#                font=transMtl, itemScale=.05, itemTextScale=1, itemTextZ=0,
      command=itemSelected, # user defined method, executed when a node get selected,
                            # receiving extraArgs (which passed to addItem)
      contextMenu=itemRightClicked, # user defined method, executed when a node right-clicked,
                                    # receiving extraArgs (which passed to addItem)
      autoFocus=0, # initial auto view-focus on newly added item
      colorChange=1,
      colorChangeDuration=.7,
      newItemColor=(0,1,0,1),
      rolloverColor=(1,.8,.2,1),
      suppressMouseWheel=1,  # 1 : blocks mouse wheel events from being sent to all other objects.
                             #     You can scroll the window by putting mouse cursor
                             #     inside the scrollable window.
                             # 0 : does not block mouse wheel events from being sent to all other objects.
                             #     You can scroll the window by holding down the modifier key
                             #     (defined below) while scrolling your wheel.
      modifier='control'  # shift/control/alt
      )
   # populate the list with alphabets and numbers
   for a in string.ascii_lowercase+string.digits:
       # extraArgs will be passed to command and contextMenu
       btnList.addItem(text='item  '+(a+' ')*10, extraArgs=a,)
   btnList.setAutoFocus(1)  # after adding those items,
                            # enable auto view-focus on newly added item

   DO=DirectObject()
   # events for btnList
   DO.accept('space',btnList.toggleVisibility)
   DO.accept('delete',btnList.destroy)
   DO.accept('enter',addNewItem)
   DO.accept('enter-repeat',addNewItem)
   DO.accept('insert',insertNewItemAtSelected)
   DO.accept('insert-repeat',insertNewItemAtSelected)
   DO.accept('control-insert',insertNewItemBelowSelected)
   DO.accept('control-insert-repeat',insertNewItemBelowSelected)
   DO.accept('caps_lock',disableRandomItem)
   DO.accept('tab',enableRandomItem)
   DO.accept('x',removeRandomItem)
   DO.accept('x-repeat',removeRandomItem)
   DO.accept('s',btnList.sort)
   # events for the world
   DO.accept('wheel_up',teapot.setScale,[teapot,1.2])
   DO.accept('wheel_down',teapot.setScale,[teapot,.8])
   DO.accept('escape',sys.exit)

   camera.setPos(11.06, -16.65, 8.73)
   camera.setHpr(42.23, -25.43, -0.05)
   base.setBackgroundColor(.2,.2,.2,1)
   base.disableMouse()
   run()

_________________
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3


Last edited by ynjh_jo on Sun May 16, 2010 10:25 am; edited 2 times in total
ColShag


Posts: 59

PostPosted: Tue Dec 11, 2007 8:23 pm    Post subject: Reply with quote
Thanks!
ColShag


Posts: 59

PostPosted: Fri Dec 28, 2007 4:04 pm    Post subject: Reply with quote
Hey ynjh, Thanks again for posting this code, I can see you put a lot of effort into this List Control and I love it! Its very full featured with the scrollable mouse wheel support, etc... I was able to easily put it into the Asteroids Sample as a test.

Couple Questions for you (or anyone interested)

1) I noticed a significant frame rate drop if I expanded the window or went full-screen, is this normal when adding panda3d gui?

2) I can live with 1) as long as there is a way to remove the overhead if I switch modes within my game (ie, is there a clean way to remove the list? I tried using the Destroy method, but the frame rate did not go back up, I tried setting List=None, that didn't help either, any suggestions?

3) ynjh, have you thought of expanding your obvious gui creating talents and making a nice gui toolkit for us developers to use? I can see you put a lot of effort into this ListView (and the treeview). I have seen that someone was starting a gui toolkit, but that kinda died off.

Thanks again,

C
treeform


Posts: 2027
Location: Seattle

PostPosted: Fri Dec 28, 2007 4:10 pm    Post subject: Reply with quote
no it kinda did not die off i am using it my game now
i guess i should try to make it public ... and wait for features
_________________
Panda3d IRC irc://irc.freenode.net/panda3d | BUGs https://bugs.launchpad.net/panda3d
My MMORTS game: http://aff2aw.com
GitHub: http://github.com/treeform | Twitter: http://twitter.com/treeform
ynjh_jo


Posts: 1596
Location: Malang, Indonesia

PostPosted: Sat Dec 29, 2007 2:15 am    Post subject: Reply with quote
1. I'm not sure if it's caused by the 2d scene, but I could be wrong though. What resolution for fullscreen did you use ? The same like when windowed ? If so, then there must be a deep problem.
2. I'll take a deeper look. I'm not on my dev pc.
3. Sorry, I don't have time for that.

ColShag, while you're around, do you mind telling us about your experience with PyUI ? The pros and cons.
I guess it's possible to use it within panda, as within PyGame, since it simply calls PyGame's drawing commands, similar like using LineSegs in Panda for lines, and so on.
_________________
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3
ColShag


Posts: 59

PostPosted: Sat Dec 29, 2007 7:08 pm    Post subject: Reply with quote
treeform: That's great that you are already using it, I'm sure its the last thing on your mind, but if you could share the results I would appreciate it greatly (see if it would work out for my potential port).

ynjh: Yeah I've noticed that if I expand the window simply the framerate seems to half usually. I'm unsure of why, but more importantly, if I remove the listbox the framerate does not change at all (ie go back to the higher framerate), that worries me more.

As for pyui, yes, I've used it quite a bit with my game. I basically used the pyui, plus the simple open-source engine that the pyui author made as a part of his python game programming book to successfully make a full fledged game. It was quite successful. The drawbacks are that pyui depends on pyOpenGl, which is not being well supported, when I went to ubuntu-gutsy I noticed serious framerate problems so I started to look seriously at panda3d again.

Pyui seems to be setup to allow for rendering using other engines, certainly panda3d would be a possible option. I have looked at the source to see what kind of work that would be but I have not gone down that road as I'm afraid it might be a bit beyond my abilities as I am very new to panda3d.

I'll share some screenshots of pyui within my game (these are a bit old, but you will get the idea).

Ok, I would share, but I don't have a website setup right now (my game website is down for now), so any suggestions on how to post to this forum?

Thanks,

C
treeform


Posts: 2027
Location: Seattle

PostPosted: Sat Dec 29, 2007 7:49 pm    Post subject: Reply with quote
ColShag, what are you porting? i could build an example for you as a tutorial.
_________________
Panda3d IRC irc://irc.freenode.net/panda3d | BUGs https://bugs.launchpad.net/panda3d
My MMORTS game: http://aff2aw.com
GitHub: http://github.com/treeform | Twitter: http://twitter.com/treeform
ynjh_jo


Posts: 1596
Location: Malang, Indonesia

PostPosted: Sat Dec 29, 2007 11:18 pm    Post subject: Reply with quote
It's not the 2d scene as far as I can see. Lower framerate is normal for greater resolution.
The results here (simply that scene, idle) are :
1. normal window 800x600
shown : 202 fps
hidden : 265 fps
2. maximized window 1014x705
shown : 158 fps
hidden : 188 fps
3. fullscreen 800x600
shown : 217 fps
hidden : 564 fps
Obviously going fullscreen naturally speeds things up, I guess the route is different. I haven't read any technical explanation about this.
If removing/hiding the list didn't restore the framerate, you're in trouble I can't see.
In clear(), try replacing c.remove with c.destroy, see if it help, but I doubt it since I couldn't see the difference.
Have you tried imageshack.net ?
_________________
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3
ColShag


Posts: 59

PostPosted: Sun Dec 30, 2007 4:27 am    Post subject: Thanks For the Quick Reply Guys Reply with quote
I have to say I'm really impressed with you guys, I really appreciate your help on this.

ynjh: That's great news that you seem to have a new framerate when hidden, I guess I must be doing something wrong here, certainly that is possible as my experience with pandad3d is very limited. I'm running ubuntu gutsy with python 2.5 if that makes any difference.

treeform: I really appreciate your offer to make an example for me, now how to go about it? First off let me explain the game. Its a multiplayer client/server space strategy type game where you manage an empire of systems, ships, research, diplomacy etc... My friends really enjoy it, but I've decided to try and improve the client now that I am using a somewhat dead engine/ui.

As you can guess it requires some gui, rendered of course. Mainly I've managed to use pyui in its somewhat limited form. I use a lot of selectable lists, nothing as fancy as what ynjh made, but they did the job. I used buttons, textboxes, etc... The key for me was being able to select an object with the mouse which would bring up the gui as required. I used a mode system to traverse the game, I would be interested in your input on a good way to transition between modes in panda3d. By modes I mean allowing the game to switch between different scenes and properly load an unload objects, gui, etc. From reading the documentation I'm thinking maybe of the finite state machine logic for that. Of course an example of how you fine gents would do this would be helpful. One thing I've noticed from looking at most of the code in the examples, it seems that everything is thrown into the world object and then that object is run.

Modes in these examples are methods of that world object. I would prefer personally to have modes as their own objects, inheriting from a central node object with functions that all modes use. The player would then start in a mode and depending on actions close one mode and go to another. I like this method as it separates the code which can get messy when dealing with all the client and client/server logic involved. What are your thoughts on this?

There are two main functions of the game, the first is building the empire, research, diplomacy, fleet and army movement. The second is watching ship battle results. The Ship Battles are where I'm most worried about performance issues, but I'm sure Panda could handle it as its not that crazy. The other advantage of panda that I can see is the nice 3d abilities, currently my game is fully 2d, but I would like to transition to a mix of the two in the future.

I tried to setup image shack, as per your suggestion, here are a few screen shots... (hopefully they come through)

This is the research screen, beakers are selected and the gui panel allows for research, pretty basic here.

By colshag

This is the ship design screen, allows for ship design, you add components and weapons. I use a lot of list boxes, which work ok. Notice the 3 tabs at the top of the gui on the right, does panda allow for tabs somehow?

By colshag

This is the main galaxy screen, I have one panel opening another.

By colshag

This is a slightly older screenshot of the ship battle viewer. Battles are viewed as an afterfact. The player cannot "control" the ships, however he can give them orders before they go off to fight, much like a real emperor could in my opinion.

By colshag

Thanks again,

C
ColShag


Posts: 59

PostPosted: Tue Jun 10, 2008 7:12 pm    Post subject: small fix Reply with quote
I'm actually using your ButtonList in my game (attempting), seems to work well, thanks for the post. Small bug fix:


Code:
    def clear(self):
        """
           clear the list
        """
        for c in self.buttonsList:
            c.remove()
        self.buttonsList=[]
        self.focusButton=None
        self.numItems=0
ynjh_jo


Posts: 1596
Location: Malang, Indonesia

PostPosted: Wed Jun 11, 2008 4:06 am    Post subject: Reply with quote
Sorry for not really using it.
_________________
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3
ColShag


Posts: 59

PostPosted: Mon Aug 24, 2009 10:27 am    Post subject: I agree Reply with quote
Yes, thanks ynjh for all your help and sharing your code. I am still working on my game, its getting close, and I do use an old version of your scrolled button list which works great.

C
ynjh_jo


Posts: 1596
Location: Malang, Indonesia

PostPosted: Sun May 16, 2010 6:02 am    Post subject: Reply with quote
UPDATE :
[+] added sorting ability
[+] changed some methods' name
_________________
http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3
Display posts from previous:   
Post new topic   Reply to topic    Panda3D Forum Index -> Code Snippets All times are GMT - 5 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group