|
|
|
| Author |
Message |
ynjh_jo
 Posts: 1596 Location: Malang, Indonesia
 |
(pulled out from my A2Deditor, since some1 asked me to)
I've improved it a little :
1. improved collapse/expand process
2. simplified tree line nodes complexity to gain some more render speed
3. added collapse/expand all (still need improvement, it's rather slow)
4. added right click on the node button, so it's possible to hook a command
| Code: | __all__=['SceneGraphBrowser']
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 StringType
class SceneGraphBrowser(DirectObject):
"""
A class to display a node's selectable children with collapsible & expandable hierarchy tree
"""
class SceneGraphItem:
def __init__(self,np,level,editable):
self.NP=np
self.level=level
self.editable=editable
self.hidden=False
nameStr=' '.join(np.getName().splitlines())[:25]
if len(nameStr)==25:
nameStr+='....'
self.nameStr='(%s) %s' %(np.node().getType(), nameStr)
def remove(self):
if hasattr(self,'treeButton'):
self.treeButton.destroy()
self.nodeButton.destroy()
self.holder.removeNode()
self.NP=None
def __init__(self, parent=None, root=None, command=None, contextMenu=None,
selectTag=[], noSelectTag=[], exclusionTag=[],
frameSize=(1,1.2),
font=None, titleScale=.05, itemScale=.045, itemTextScale=1, itemTextZ=0,
rolloverColor=Vec4(1,.8,.2,1),
collapseAll=0, suppressMouseWheel=1, modifier='control'):
self.root=root
self.focusSGitem=None
self.command=command
self.contextMenu=contextMenu
self.selectTag=self.__wantSeq(selectTag)
self.noSelectTag=self.__wantSeq(noSelectTag)
self.exclusionTag=self.__wantSeq(exclusionTag)
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.itemScale=itemScale
self.itemTextScale=itemTextScale
self.itemIndent=.07
self.verticalSpacing=self.fontHeight*self.itemScale
self.frameWidth,self.frameHeight=frameSize
self.suppressMouseWheel=suppressMouseWheel
self.modifier=modifier
self.childrenList=[]
self.__eventReceivers={}
# DirectScrolledFrame to hold items
# I set canvas' Z size smaller than the frame to avoid the auto-generated vertical slider bar
self.childrenFrame = 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.childrenFrame.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.childrenCanvas=self.childrenFrame.getCanvas().attachNewNode('myCanvas')
# DirectScrolledFrame title
self.rootTitle = DirectFrame( parent=self.childrenFrame,
frameSize=(-self.frameWidth*.5,self.frameWidth*.5,0,.06),frameColor=(.1,.3,.7,.7),
pos=(.5*self.frameWidth,0,.005), text='', text_font=font,
text_scale=titleScale, text_pos=(0,.015),
text_fg=(1,1,1,1), text_shadow=(0,0,0,1),
enableEdit=0, suppressMouse=0)
# slider background
SliderBG=DirectFrame( parent=self.childrenFrame,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=1)
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=1)
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=1)
self.vertSliderThumb.bind(DGG.B1PRESS,self.__startdragSliderThumb)
self.vertSliderThumb.bind(DGG.WITHIN,self.__enteringThumb)
self.vertSliderThumb.bind(DGG.WITHOUT,self.__exitingThumb)
self.childrenFrame.setTag('internal component','')
self.sliderThumbDragPrefix='draggingvertSliderThumb-'
# collapse-all button
DirectButton(parent=self.rootTitle,
frameColor=(0,.8,1,1),frameSize=(-.4,.4,-.4,.4),
pos=(-.5*self.frameWidth+.03,0,.03), scale=.05,
text='-', text_pos=(-.1,-.22), text_scale=(1.8,1), text_fg=(0,0,0,1),
enableEdit=0, suppressMouse=0,command=self.collapseAllTree)
# expand-all button
DirectButton(parent=self.rootTitle,
frameColor=(0,.8,1,1),frameSize=(-.4,.4,-.4,.4),
pos=(-.5*self.frameWidth+.075,0,.03), scale=.05,
text='+', text_pos=(-.05,-.22), text_scale=(1.2,1.2), text_fg=(0,0,0,1),
enableEdit=0, suppressMouse=0,command=self.expandAllTree)
self.__createTreeLines()
if self.root:
self.setRoot(self.root,collapseAll)
# 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.childrenFrame.guiId,self.__enteringFrame)
# self.accept(DGG.WITHOUT+self.childrenFrame.guiId,self.__exitingFrame)
self.isMouseInRegion=False
self.mouseOutInRegionCommand=(self.__exitingFrame,self.__enteringFrame)
self.mouseInRegionTaskName='mouseInRegionCheck %s'%id(self)
self.dragSliderThumbTaskName='dragSliderThumb %s'%id(self)
taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion')
def __getFrameRegion(self,t):
for g in range(base.mouseWatcher.node().getNumGroups()):
region=base.mouseWatcher.node().getGroup(g).findRegion(self.childrenFrame.guiId)
if region!=None:
self.frameRegion=region
taskMgr.add(self.__mouseInRegionCheck,self.mouseInRegionTaskName)
break
def __mouseInRegionCheck(self,t):
"""
checks if the mouse is within or without the scrollable frame, and
upon within or without, run the provided command
"""
if not base.mouseWatcher.node().hasMouse(): return Task.cont
m=base.mouseWatcher.node().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 __wantSeq(self,obj):
if type(obj)==StringType:
return [obj]
else:
return obj
def __startdragSliderThumb(self,m=None):
mpos=base.mouseWatcherNode.getMouse()
parentZ=self.vertSliderThumb.getParent().getZ(render2d)
sliderDragTask=taskMgr.add(self.__dragSliderThumb,self.dragSliderThumbTaskName)
sliderDragTask.ZposNoffset=mpos[1]-self.vertSliderThumb.getZ(render2d)+parentZ
sliderDragTask.mouseX=(mpos[0]+1)*.5*base.winList[0].getXSize()
sliderDragTask.winY=base.winList[0].getYSize()
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()
self.__updateChildrenCanvasZpos((t.ZposNoffset-mpos[1])/self.canvasRatio)
return Task.cont
def __stopdragSliderThumb(self,m=None):
taskMgr.remove(self.dragSliderThumbTaskName)
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.__scrollChildrenCanvas(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(self.dragSliderThumbTaskName):
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(self.dragSliderThumbTaskName):
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 __scrollChildrenCanvas(self,scroll):
if self.vertSliderThumb.isHidden():
return
self.__updateChildrenCanvasZpos(self.childrenCanvas.getZ()+scroll)
def __updateChildrenCanvasZpos(self,Zpos):
newZ=clampScalar(Zpos, .0, self.canvasLen-self.frameHeight+.015)
self.childrenCanvas.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 __collapseTree(self,idx):
self.childrenList[idx].status=1
self.childrenList[idx].treeButton['text']='+'
self.childrenList[idx].treeButton['text_scale']=1
self.childrenList[idx].treeButton['text_pos']=(-.07,-.21)
self.childrenList[idx].treeButton['command']=self.__expandTreeNupdate
collapsed=0
for i in range(idx+1,len(self.childrenList)):
if self.childrenList[i].level>self.childrenList[idx].level:
if not self.childrenList[i].hidden:
collapsed+=1
self.childrenList[i].holder.hide()
self.childrenList[i].hidden=True
else:
break
Zoffset=collapsed*self.verticalSpacing
for i in range(idx+collapsed,len(self.childrenList)):
self.childrenList[i].holder.setZ(self.childrenList[i].holder,Zoffset)
self.numVisibleItems-=collapsed
def __expandTree(self,idx):
self.childrenList[idx].status=-1
self.childrenList[idx].treeButton['text']='-'
self.childrenList[idx].treeButton['text_scale']=(1.6,1)
self.childrenList[idx].treeButton['text_pos']=(-.1,-.22)
self.childrenList[idx].treeButton['command']=self.__collapseTreeNupdate
expanded=0
ignoredLevel=0
for i in range(idx+1,len(self.childrenList)):
if self.childrenList[i].level>self.childrenList[idx].level:
if self.childrenList[i].level<=ignoredLevel:
ignoredLevel=0
if not ignoredLevel:
expanded+=1
self.childrenList[i].holder.show()
self.childrenList[i].hidden=False
if self.childrenList[i].status==1:
ignoredLevel=self.childrenList[i].level
else:
break
Zoffset=-expanded*self.verticalSpacing
for i in range(idx+expanded,len(self.childrenList)):
self.childrenList[i].holder.setZ(self.childrenList[i].holder,Zoffset)
self.numVisibleItems+=expanded
def __collapseTreeNupdate(self,idx):
self.__collapseTree(idx)
self.__adjustCanvasLength(self.numVisibleItems)
self.__updateVertTreeLines(idx)
prevLevel=self.__getchildrenPrevLevel(idx)
while self.childrenList[prevLevel].level>0 and prevLevel>=0:
self.__updateVertTreeLines(prevLevel)
prevLevel=self.__getchildrenPrevLevel(prevLevel)
def __expandTreeNupdate(self,idx):
self.__expandTree(idx)
self.__adjustCanvasLength(self.numVisibleItems)
self.__updateVertTreeLines(idx)
prevLevel=self.__getchildrenPrevLevel(idx)
while self.childrenList[prevLevel].level>0 and prevLevel>=0:
self.__updateVertTreeLines(prevLevel)
prevLevel=self.__getchildrenPrevLevel(prevLevel)
def collapseAllTree(self):
print '----'
start=globalClock.getRealTime()
self.expandAllTree()
for i in range(len(self.childrenList)-1,-1,-1):
if self.childrenList[i].status==-1:
self.__collapseTree(i)
self.__adjustCanvasLength(self.numVisibleItems)
self.__updateVertTreeLines(i)
print globalClock.getRealTime()-start
def expandAllTree(self):
print '++++'
start=globalClock.getRealTime()
for i in range(len(self.childrenList)):
if self.childrenList[i].status==1:
self.__expandTree(i)
self.__adjustCanvasLength(self.numVisibleItems)
for i in range(len(self.childrenList)-1,-1,-1):
if self.childrenList[i].status!=0:
self.__updateVertTreeLines(i)
print globalClock.getRealTime()-start
def __updateVertTreeLines(self,idx):
if idx<0:
return
for i in range(idx+1,len(self.childrenList)):
if self.childrenList[i].level<=self.childrenList[idx].level:
break
if self.childrenList[i].level==self.childrenList[idx].level+1:
lastNextLevel=i
visible=0
for i in range(idx+1,lastNextLevel+1):
if not self.childrenList[i].hidden:
visible+=1
if visible:
vert=self.childrenList[idx].VtreeLines.getChild(0)
vert.setZ(.25*self.itemScale)
vert.setSz(visible)
self.childrenList[idx].VtreeLines.show()
else:
self.childrenList[idx].VtreeLines.hide()
def __getchildrenPrevLevel(self,idx):
prevLevel=-1
for i in range(idx,-1,-1):
if self.childrenList[i].level==self.childrenList[idx].level-1:
return i
return prevLevel
def __adjustCanvasLength(self,numItem):
self.canvasLen=float(numItem)*self.verticalSpacing
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.childrenCanvas.getZ()
self.vertSliderThumb.show()
self.pageUpRegion.show()
self.pageDnRegion.show()
self.__updateChildrenCanvasZpos(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 __createTreeLines(self):
# create horisontal tree line
color=(1,0,0,.9)
self.horisontalTreeLine=NodePath(self.__createLine(
self.itemIndent+self.itemScale*.5,
color,endColor=(1,1,1,.1),centered=0))
self.horisontalTreeLine.setTag('internal component','')
self.horisontalTreeLine.setTwoSided(0,100)
self.horisontalTreeLine.setTransparency(1)
# create vertical tree line
self.verticalTreeLine=NodePath(self.__createLine(self.verticalSpacing,color,centered=0))
self.verticalTreeLine.setR(90)
self.verticalTreeLine.setTag('internal component','')
self.verticalTreeLine.setTwoSided(0,100)
self.verticalTreeLine.setTransparency(1)
def __createLine(self, length=1, color=(1,1,1,1), endColor=None, thickness=1, centered=1):
LS=LineSegs()
LS.setColor(*color)
LS.setThickness(thickness)
LS.moveTo(-length*.5*centered,0,0)
LS.drawTo(length*(1-.5*centered),0,0)
node=LS.create()
if endColor:
LS.setVertexColor(1,*endColor)
return node
def __acceptAndIgnoreWorldEvent(self,event,command,extraArgs=[]):
receivers=messenger.whoAccepts(event)
if receivers is None:
self.__eventReceivers[event]={}
else:
self.__eventReceivers[event]=receivers.copy()
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.__scrollChildrenCanvas, extraArgs=[-.07])
self.__acceptAndIgnoreWorldEvent(self.inOutBTprefix+'wheel_down',
command=self.__scrollChildrenCanvas, extraArgs=[.07])
else:
self.accept(self.inOutBTprefix+self.modifier+'-wheel_up',self.__scrollChildrenCanvas, [-.07])
self.accept(self.inOutBTprefix+self.modifier+'-wheel_down',self.__scrollChildrenCanvas, [.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 __listchildren(self,np,level,editable=True):
status=0
listIdx=len(self.childrenList)-1
if np!=self.root:
self.childrenList[-1].holder=self.childrenCanvas.attachNewNode('')
self.childrenList[-1].HtreeLines=self.childrenList[-1].holder.attachNewNode('HtreeLines')
self.childrenList[-1].VtreeLines=self.childrenList[-1].holder.attachNewNode('VtreeLines')
self.childrenList[-1].VtreeLines.setX(-.5*self.itemIndent)
for c in np.getChildrenAsList():
tagExist=False
for t in self.exclusionTag:
tagExist|=c.hasTag(t)
if not tagExist:
noSelectTagExist=not editable
if self.selectTag:
tagExist=False
for t in self.selectTag:
tagExist|=c.hasTag(t)
self.childrenList.append( self.SceneGraphItem(c,level,tagExist) )
elif self.noSelectTag:
if editable: # if parent is already not editable, no need to check child's tag(s)
for t in self.noSelectTag:
noSelectTagExist|=c.hasTag(t)
self.childrenList.append( self.SceneGraphItem(c,level,not noSelectTagExist) )
else:
self.childrenList.append( self.SceneGraphItem(c,level,True) )
lastNextLevel=len(self.childrenList)-1
self.__listchildren(c,level+1,editable=not noSelectTagExist)
if np!=self.root:
hor=self.horisontalTreeLine.instanceUnderNode(self.childrenList[lastNextLevel].HtreeLines,'')
hor.setPos(-1.5*self.itemIndent,0,self.itemScale*.25)
status=-1
if status!=0:
if np!=self.root:
vert=self.verticalTreeLine.instanceUnderNode(self.childrenList[listIdx].VtreeLines,'')
vert.setZ((listIdx-lastNextLevel+.25)*self.verticalSpacing)
vert.setSz(listIdx-lastNextLevel)
if listIdx>=0:
self.childrenList[listIdx].status=status
def __rightPressed(self,SGitem,m):
self.__isRightIn=True
# text0 : normal
# text1 : pressed
# text2 : rollover
# text3 : disabled
SGitem.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusSGitem==SGitem])
SGitem.nodeButton.bind(DGG.B3RELEASE,self.__rightReleased,[SGitem])
SGitem.nodeButton.bind(DGG.WITHIN,self.__rightIn,[SGitem])
SGitem.nodeButton.bind(DGG.WITHOUT,self.__rightOut,[SGitem])
def __rightIn(self,SGitem,m):
self.__isRightIn=True
SGitem.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rightClickTextColors[self.focusSGitem==SGitem])
def __rightOut(self,SGitem,m):
self.__isRightIn=False
SGitem.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(Vec4(1,1,1,1))
def __rightReleased(self,SGitem,m):
SGitem.nodeButton.unbind(DGG.B3RELEASE)
SGitem.nodeButton.unbind(DGG.WITHIN)
SGitem.nodeButton.unbind(DGG.WITHOUT)
SGitem.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor)
if not self.__isRightIn:
return
if callable(self.contextMenu):
self.contextMenu(SGitem.NP) # run user command and pass the selected node
def __selectSGitem(self,SGitem,runUserCommand=True):
self.focusSGitem=SGitem
if self.button:
self.restoreNodeButton2Normal()
self.button=SGitem.nodeButton
self.highlightNodeButton()
if callable(self.command) and runUserCommand:
self.command(SGitem.NP) # run user command and pass the selected node
def selectNode(self,np,runUserCommand=True):
"""
deselects currently selected node and selects the given one
np : the desired nodepath
runUserCommand : run user command or not
(in case you call selectNode from the command method itself,
set this to False, so the method won't be called again)
Returns the node's index.
"""
for i in self.childrenList:
if i.NP==np:
selectedIdx=self.childrenList.index(i)
self.__selectSGitem(i,runUserCommand)
selectedIdx=self.childrenList.index(self.focusSGitem)
self.__exposeSelectedNode()
return selectedIdx
return None
def __exposeSelectedNode(self):
selectedIdx=self.childrenList.index(self.focusSGitem)
# before counting the visible items until the selected one,
# expand all collapsed parents until the selected item, if the item is hidden
if self.focusSGitem.hidden:
mustBeExpanded=[]
currentLevel=self.focusSGitem.level
idx=selectedIdx
while idx>=0:
if self.childrenList[idx].level<currentLevel:
if self.childrenList[idx].status==1:
# the tree expansion works sequential from the root to the leaves,
# so expand it later after get reversed
mustBeExpanded.append(idx)
currentLevel-=1
if self.childrenList[idx].level==1:
break
idx-=1
mustBeExpanded.reverse()
for i in mustBeExpanded:
self.__expandTreeNupdate(i)
# count the visible items until the selected item
idx=0
visibleItemIdx=1
ignoredLevel=0
while idx!=selectedIdx:
if self.childrenList[idx].level<=ignoredLevel:
ignoredLevel=0
if not ignoredLevel:
visibleItemIdx+=1
if self.childrenList[idx].status==1:
ignoredLevel=self.childrenList[idx].level
idx+=1
# adjust canvas' Z if the selected item is outside the frame
canvasZ=self.childrenCanvas.getZ()
newZ=canvasZ
if canvasZ-(visibleItemIdx+.9)*self.verticalSpacing<-self.frameHeight+.015:
newZ=(visibleItemIdx+.9)*self.verticalSpacing-self.frameHeight
elif canvasZ-(visibleItemIdx-1.5)*self.verticalSpacing>.0:
newZ=(visibleItemIdx-1.5)*self.verticalSpacing
if newZ!=canvasZ:
self.__updateChildrenCanvasZpos(newZ)
def restoreNodeButton2Normal(self):
"""
removes the highlight from the currently selected node
"""
self.button['text_fg']=(1,1,1,1)
self.button['frameColor']=(0,0,0,0)
def highlightNodeButton(self,idx=None):
"""
sets highlight on currently selected node, or node which has the given index
"""
if idx is not None:
self.button=self.childrenList[idx].nodeButton
self.button['text_fg']=(.01,.01,.01,1)
self.button['frameColor']=(1,.8,.2,1)
def clear(self):
"""
clears all nodes from the list
"""
for c in self.childrenList:
c.remove()
self.childrenList=[]
self.root=None
self.focusSGitem=None
self.button=None
def setRoot(self,root,collapseAll=0):
"""
sets the root node whose children will be displayed
collapseAll : initial tree state
"""
self.clear()
self.root=root
# startT=globalClock.getRealTime()
self.__listchildren(root,1)
# self.SGreadTime=globalClock.getRealTime()-startT
# startT=globalClock.getRealTime()
colors=((.5,.5,.5,1),(1,1,1,1))
# top & bottom of the buttons' frame are blindly set without knowing
# where exactly the baseline is, but this ratio fits most fonts
baseline=-self.fontHeight*.25
topFrame=baseline+self.fontHeight
textColors=((.5,.5,.5,1),(1,1,1,1))
idx=0
for c in self.childrenList:
c.holder.setPos(c.level*self.itemIndent,0,(-.7-idx)*self.verticalSpacing)
c.nodeButton = DirectButton(parent=c.holder,
scale=self.itemScale, relief=DGG.FLAT,
frameColor=(0,0,0,0),text_scale=self.itemTextScale,
text=c.nameStr, text_fg=textColors[c.editable],
text_font=self.font, text_align=TextNode.ALeft,
command=self.__selectSGitem,extraArgs=[c],
enableEdit=0, suppressMouse=0)
l,r,b,t=c.nodeButton.getBounds()
c.nodeButton['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,baseline,topFrame)
c.nodeButton.node().setActive(c.editable)
c.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor)
c.nodeButton.bind(DGG.B3PRESS,self.__rightPressed,[c])
if c.status==-1:
c.treeButton = DirectButton(parent=c.nodeButton,
frameColor=(1,1,1,1),frameSize=(-.4,.4,-.4,.4),
pos=(-.5*self.itemIndent/self.itemScale,0,.25),
text='-', text_pos=(-.1,-.22), text_scale=(1.6,1), text_fg=(0,0,0,1),
enableEdit=0, suppressMouse=0,command=self.__collapseTreeNupdate,extraArgs=[idx])
idx+=1
# self.btnGenTime=globalClock.getRealTime()-startT
self.numVisibleItems=len(self.childrenList)
self.__adjustCanvasLength(self.numVisibleItems)
self.rootTitle['text']='CHILDREN of '+self.root.getName().upper()
if collapseAll:
self.collapseAllTree()
# print '===== %s =====' %root.getName().upper()
# print 'SGreadTime : %s sec{s}\nbtnGenTime : %s sec{s}\nratio = 1:%f' %(self.SGreadTime,self.btnGenTime,self.btnGenTime/self.SGreadTime)
# print '====================='
def refresh(self):
"""
refreshes the displayed scenegraph, means re-read (traverse) the scenegraph
to update any changes
"""
if self.root:
self.setRoot(self.root)
def destroy(self):
self.clear()
self.__exitingFrame()
self.ignoreAll()
self.childrenFrame.removeNode()
taskMgr.remove(self.mouseInRegionTaskName)
def hide(self):
self.childrenFrame.hide()
self.isMouseInRegion=False
self.__exitingFrame()
taskMgr.remove(self.mouseInRegionTaskName)
def show(self):
self.childrenFrame.show()
if not hasattr(self,'frameRegion'):
taskMgr.doMethodLater(.2,self.__getFrameRegion,'getFrameRegion')
elif not taskMgr.hasTaskNamed(self.mouseInRegionTaskName):
taskMgr.add(self.__mouseInRegionCheck,self.mouseInRegionTaskName)
def toggleVisibility(self):
if self.childrenFrame.isHidden():
self.show()
else:
self.hide()
if __name__=='__main__':
import direct.directbase.DirectStart
from random import uniform
import sys
selected=None
def nodeSelected(np): # don't forget to receive the selected node (np)
global selected
selected=np
np.hprInterval(1,Vec3(0,0,np.getR()+360)).start()
print np.getName(),'SELECTED'
def nodeRightClicked(np): # don't forget to receive the selected node (np)
np.hprInterval(1.2,Vec3(0,0,np.getR()-360)).start()
origScale=np.getScale()
Sequence(
np.scaleInterval(.6,origScale*1.2,origScale),
np.scaleInterval(.6,origScale),
).start()
print np.getName(),'RIGHT CLICKED, DO SOMETHING !'
def putNewSmiley():
if not selected:
return
lilSmiley=loader.loadModel('misc/lilsmiley').find('**/poly')
lilSmiley.reparentTo(selected,sort=-100)
lilSmiley.clearColor()
lilSmiley.setScale(.2,.2,.1)
lilSmiley.setPos(selected.getBounds().getCenter()+Point3(uniform(-1,1),0,uniform(-1,1)))
lilSmiley.setName('newSmiley_'+str(id(lilSmiley)))
def createNestedSmileys(parent,depth,branch,color):
parent.setColor(color,1000-depth)
for d in range(depth):
scale=.55
kidSmileys=lilsmileyGeom.instanceUnderNode(parent,'kid_Smileys_'+str(id(parent)))
kidSmileys.setX(d+1)
kidSmileys.setScale(scale)
kidSmileys.setR(60*((d%2)*2-1))
color=color*.8
color.setW(1)
for b in range(branch):
z=b-(branch-1)*.5
kidSmiley=lilsmileyGeom.instanceUnderNode(kidSmileys,'smileyBranch_%i-%i' %(id(kidSmileys),b))
kidSmiley.setPos(1,0,z*scale)
kidSmiley.setScale(scale)
kidSmiley.setR(-z*30)
createNestedSmileys(kidSmiley,depth-1,branch,color)
# some 3d nodes
teapot=loader.loadModel('teapot')
teapot.reparentTo(render)
panda=loader.loadModel('zup-axis')
panda.reparentTo(render)
panda.setScale(.22)
panda.setPos(4.5,-5,0)
# some 2d nodes
lilsmiley1=loader.loadModelCopy('misc/lilsmiley')
lilsmiley1.reparentTo(aspect2d)
lilsmileyGeom=lilsmiley1.find('**/poly')
createNestedSmileys(lilsmiley1,3,2,Vec4(0,1,0,1))
lilsmiley1.setX(render2d,-.7)
lilsmiley1.setScale(.5)
OnscreenText(text='''[ 1 ] : display render's children [ 2 ] : display aspect2d's children [ 3 ] : display render2d's children
[ H ] : hide browser [ S ] : show browser [ SPACE ] : toggle visibility
[ R ] : refresh browser [ DEL ] : destroy browser
[ MOUSE WHEEL ] inside scrollable window : scroll window
[ MOUSE WHEEL ] outside scrollable window : change teapot scale
hold [ ENTER ] : attach new smiley on selected node
''', scale=.045, fg=(1,1,1,1), shadow=(0,0,0,1)).setZ(.96)
# # load other font
# transMtl=loader.loadFont('Transmetals.ttf')
# transMtl.setLineHeight(.9)
# create SceneGraphBrowser and point it on aspect2d
treeList=SceneGraphBrowser(
parent=None, # where to attach SceneGraphBrowser frame
root=aspect2d, # display children under this root node
command=nodeSelected, # user defined method, executed when a node get selected,
# with the selected node passed to it
contextMenu=nodeRightClicked,
# selectTag and noSelectTag are used to filter the selectable nodes.
# The unselectable nodes will be grayed.
# You should use only selectTag or noSelectTag at a time. Don't use both at the same time.
#selectTag=['select'], # only nodes which have the tag(s) are selectable. You could use multiple tags.
noSelectTag=['noSelect','dontSelectMe'], # only nodes which DO NOT have the tag(s) are selectable. You could use multiple tags.
# nodes which have exclusionTag wouldn't be displayed at all
exclusionTag=['internal component'],
frameSize=(1,1.2),
font=None, titleScale=.05, itemScale=.035, itemTextScale=1.2, itemTextZ=0,
# font=transMtl, titleScale=.055, itemScale=.043, itemTextScale=1, itemTextZ=0,
rolloverColor=(1,.8,.2,1),
collapseAll=0, # initial tree state
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
)
# You could change selectTag, noSelectTag, or exclusionTag any time,
# in case you need different filter for different root, or at a special moment.
# They are instance attributes, you can change them directly :
# treeList.selectTag
# treeList.noSelectTag
# treeList.exclusionTag
# If you have multiple tags, pack them in a sequence.
# The top node of treeList's scrollable frame is childrenFrame,
# you could do any nodepath (or DirectScrolledFrame) operation on it,
# except hide, show, and destroy.
# If you need to use them, use treeList's hide, show, and destroy instead
# (see events accepting below), because they do some additional stuff
# in order to work properly.
treeListFrame=treeList.childrenFrame
DO=DirectObject()
# events for treeList
DO.accept('1',treeList.setRoot,[render])
DO.accept('2',treeList.setRoot,[aspect2d])
DO.accept('3',treeList.setRoot,[render2d])
DO.accept('r',treeList.refresh)
DO.accept('h',treeList.hide)
DO.accept('s',treeList.show)
DO.accept('space',treeList.toggleVisibility)
DO.accept('delete',treeList.destroy)
# events for the world
DO.accept('wheel_up',teapot.setScale,[teapot,1.2])
DO.accept('wheel_down',teapot.setScale,[teapot,.8])
DO.accept('enter',putNewSmiley)
DO.accept('enter-repeat',putNewSmiley)
DO.accept('escape',sys.exit)
camera.setPos(11.06, -16.65, 8.73)
camera.setHpr(42.23, -25.43, -0.05)
camera.setTag('noSelect','')
# base.cam.setTag('dontSelectMe','')
base.setBackgroundColor(.2,.2,.2,1)
base.disableMouse()
# messenger.toggleVerbose()
# to visualize the mouse regions, uncomment the next line
# base.mouseWatcherNode.showRegions(render2d,'gui-popup',0)
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 Feb 24, 2008 11:45 am; edited 9 times in total |
|
Manakel
 Posts: 317 Location: France
 |
Wow that trully rocks !!
i've just one question, i 'm not 100% confident with the part:
| Code: | | __builtins__.selected=np in the main. |
Is it a way to replace a self.selected = np because main is not an "object" in your sample? _________________ Bold words are keyword to help you decide quickly if this post interest you or not....
(you should be able to understand the post by reading only bold words ) |
|
ynjh_jo
 Posts: 1596 Location: Malang, Indonesia
 |
If I understand it correctly, main is a module.
| Code: | | print sys.modules['__main__'] |
results : <module '__main__' from '******'>
About using __builtins__, hmm... all I want is to store it globally, but I'm too lazy to put "global xxxx" before any write operation of xxxx. So I prefer __builtins__, but I do know I shouldn't messing around with everyone's __builtins__, someone might stab my back someday. :O
For every write operation, in main module, xxxx is accessed using attribute style (__builtins__.xxxx), while in other modules, it's accessed using dictionary style (__builtins__['xxxx']).
By using __builtins__, I can store anything which is accessible across namespaces, not like global, which is bound to a module's namespace.
I've just changed that.  _________________ http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3 |
|
ynjh_jo
 Posts: 1596 Location: Malang, Indonesia
 |
[UPDATE] :
1. page up/down regions are independent now, both could be suspended/resumed when mouse cursor get outside/inside the region.
2. integrated with selected-node exposure code, just got pulled out from A2Ded, for the sake of cleanliness and consistency of OO.
3. minor visual changes
4. more public methods documentation _________________ http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3 |
|
rdb pro-rsoft
Posts: 5836 Location: Netherlands
 |
Just now I changed to wx, something impressive about Panda's GUI comes up  |
|
treeform
 Posts: 2027 Location: Seattle
 |
|
ynjh_jo
 Posts: 1596 Location: Malang, Indonesia
 |
No, there isn't any infinite loop. Actually, most of the time is spent to create button's text quads. The time spent for reading the scenegraph isn't that noticable. And then after all buttons are created, there is some more time spent, I think it's to upload the text textures to gra'card.
And I guess you attached too many new smiley on smiley's GeomNode, which is instanced all the way. For 15 new ones, resulting 2017 buttons, the time spent is around 18 secs here.
Try this variant to see the ratio of scenegraph reading time and button generation :
| Code: | def setRoot(self,root,collapseAll=0):
"""
sets the root node whose children will be displayed
collapseAll : initial tree state
"""
self.clear()
self.root=root
startT=globalClock.getRealTime()
self.__listchildren(root,1)
self.SGreadTime=globalClock.getRealTime()-startT
startT=globalClock.getRealTime()
# top & bottom of the buttons' frame are blindly set without knowing
# where exactly the baseline is, but this ratio fits most fonts
baseline=-self.fontHeight*.25
topFrame=baseline+self.fontHeight
idx=0
for c in self.childrenList:
if c.editable:
fg=(1,1,1,1)
else:
fg=(.5,.5,.5,1)
c.holder.setPos(c.level*self.itemIndent,0,(-.7-idx)*self.verticalSpacing)
c.nodeButton = DirectButton(parent=c.holder,
scale=self.itemScale, relief=DGG.FLAT,
frameColor=(0,0,0,0),text_scale=self.itemTextScale,
text=c.nameStr, text_fg=fg,
text_font=self.font, text_align=TextNode.ALeft,
command=self.__selectSGitem,extraArgs=[c],
enableEdit=0, suppressMouse=0)
l,r,b,t=c.nodeButton.getBounds()
c.nodeButton['frameSize']=(l-self.xtraSideSpace,r+self.xtraSideSpace,baseline,topFrame)
c.nodeButton.node().setActive(c.editable)
c.nodeButton._DirectGuiBase__componentInfo['text2'][0].setColorScale(self.rolloverColor)
c.nodeButton.bind(DGG.B3PRESS,self.__rightPressed,[c])
if c.status==-1:
c.treeButton = DirectButton(parent=c.nodeButton,
frameColor=(1,1,1,1),frameSize=(-.4,.4,-.4,.4),
pos=(-.5*self.itemIndent/self.itemScale,0,.25),
text='-', text_pos=(-.1,-.22), text_scale=(1.6,1), text_fg=(0,0,0,1),
enableEdit=0, suppressMouse=0,command=self.__collapseTreeNupdate,extraArgs=[idx])
idx+=1
self.btnGenTime=globalClock.getRealTime()-startT
self.numVisibleItems=len(self.childrenList)
self.__adjustCanvasLength(self.numVisibleItems)
self.rootTitle['text']='CHILDREN of '+self.root.getName().upper()
if collapseAll:
self.collapseAllTree()
print '===== %s =====' %root.getName().upper()
print 'SGreadTime : %s sec{s}\nbtnGenTime : %s sec{s}\nratio = 1:%f' %(self.SGreadTime,self.btnGenTime,self.btnGenTime/self.SGreadTime)
print '=====================' |
I thought using egg-mkfont would help, but didn't, it's even worst .2 sec. While using "text-render-mode polygon" simply crashed here. _________________ http://ynjh.panda3dprojects.com | http://ynjh.p3dp.com
Intel P4Prescott 2.8GHz HT | Elixir 1.5GB | ATI HD4670 1GB GDDR3 |
|
ColShag
 Posts: 59
 |
|
|
|
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
| | |