class ScrolledItemSelector(DirectObject): '''Touch optimised list that holds a set of items. By clicking on one you select it and return its Value with get_selected(). To add an item simply use add_item(). You can also pass a function with the 'command' field that gets called on a selection switch''' def __init__(self, frame_size=(1, 1.5), frame_color=(0.2, 0.2, 0.2, 0.8), pos=(0, 0, 0), item_v_padding=0.02, item_h_padding=0.04, item_scale=1, item_side_ratio=0.2, item_background=(0.3, 0.3, 0.3, 1), item_background_active=(0.6, 0.6, 0.6, 1), command=lambda: None): self.f_x, self.f_y = frame_size self.c_x = self.f_x self.c_y = self.f_y self.item_v_padding = item_v_padding self.item_h_padding = item_h_padding self.item_scale = item_scale self.item_background = item_background self.item_background_active = item_background_active self.frame = DirectScrolledFrame( frameSize=(-self.f_x / 2, self.f_x / 2, -self.f_y / 2, self.f_y / 2), canvasSize=(-self.c_x / 2, self.c_x / 2, -self.c_y / 2, self.c_y / 2), frameColor=frame_color, pos=pos, scrollBarWidth=(0), manageScrollBars=False, state=DGG.NORMAL) self.frame.verticalScroll.destroy() self.frame.horizontalScroll.destroy() self.frame.bind(DGG.WITHIN, self._start_listening) self.frame.bind(DGG.WITHOUT, self._stop_listening) self.canvas = self.frame.getCanvas() self.i_x = self.f_x * (1 - item_h_padding) self.i_y = self.f_y * item_side_ratio self.i_size = (-self.i_x / 2, self.i_x / 2, -self.i_y / 2, self.i_y / 2) self.i_start = self.c_y / 2 + self.i_y / 2 self.active_item = None self.item_list = [] self.value_list = [] self.start_mY = None self.old_mY = None self.m_diff = 0 self.is_scrolling = False self.command = command # messenger.toggleVerbose() def _start_listening(self, watcher): print('start listening') self.accept("mouse1", self._start_scroll) self.accept("mouse1-up", self._stop_scroll) def _stop_listening(self, watcher): print('stop listening') self.ignore("mouse1") self.ignore("mouse1-up") self._stop_scroll() def _switch_active_item(self, item, watcher): if not self.is_scrolling: print('switched') if self.active_item is not None: self.active_item['frameColor'] = self.item_background item['frameColor'] = self.item_background_active self.active_item = item self.command() def _start_scroll(self): n = len(self.item_list) content_length = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding) # Add one padding for the bottom if content_length > self.c_y: taskMgr.doMethodLater(0.01, self._scroll_task, 'scroll_task') self.start_mY = base.mouseWatcherNode.getMouse().getY() self.old_mY = self.start_mY def _stop_scroll(self): taskMgr.remove('scroll_task') taskMgr.doMethodLater(0.01, self._scroll_fade_task, 'scrollfadetask') self.is_scrolling = False def _scroll_task(self, task): mY = base.mouseWatcherNode.getMouse().getY() old_c = self.canvas.getZ() self.m_diff = self.old_mY - mY n = len(self.item_list) if self.m_diff != 0: self.is_scrolling = True self.c_scroll_start = 0 self.c_scroll_stop = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding - # Add one padding for the bottom self.f_y) # Substract the length of the canvas self.c_new_pos = (old_c - self.m_diff) hits_not_upper_bound = self.c_new_pos >= self.c_scroll_start hits_not_lower_bound = self.c_new_pos <= self.c_scroll_stop # print('canvas : ' + str(self.canvas.getZ())) if hits_not_upper_bound and hits_not_lower_bound: self.canvas.setZ(self.c_new_pos) elif not hits_not_upper_bound: self.canvas.setZ(self.c_scroll_start) elif not hits_not_lower_bound: self.canvas.setZ(self.c_scroll_stop) self.old_mY = mY return task.again def _scroll_fade_task(self, task): if self.m_diff is None or abs(self.m_diff) < 0.005: self.m_diff = 0 return task.done old_c = self.canvas.getZ() n = len(self.item_list) self.c_scroll_start = 0 self.c_scroll_stop = ( (n * (self.i_y + self.item_v_padding)) + # Size of all elements with padding self.item_v_padding - # Add one padding for the bottom self.c_y) # Substract the length of the canvas hits_not_upper_bound = (old_c - self.m_diff) >= self.c_scroll_start hits_not_lower_bound = (old_c - self.m_diff) <= self.c_scroll_stop if hits_not_upper_bound and hits_not_lower_bound: self.canvas.setZ(old_c - self.m_diff) self.m_diff *= 0.85 return task.again elif not hits_not_upper_bound: self.canvas.setZ(self.c_scroll_start) self.m_diff = 0 return task.done elif not hits_not_lower_bound: self.canvas.setZ(self.c_scroll_stop) self.m_diff = 0 return task.done def add_item(self, image=None, image_scale=0.15, image_pos=(0, 0, 0), title=None, title_pos=(0, 0, 0), text=None, text_pos=(0, 0, 0), value=None): '''Appends an item to the end of the list. It can hold an image, title and text. Image: Panda3d path eg. 'models/picture.jpg'. Value: The item has function 'get/set_value()' to work with individual values of an activated element. Value gets set to the item on adding it.''' item_nr = len(self.item_list) + 1 item_pos = self.i_start - (self.i_y + self.item_v_padding) * item_nr item = DirectFrame( parent=self.canvas, text=str(item_nr), # Abused as an ID tag text_fg=(0, 0, 0, 0), frameSize=self.i_size, frameColor=self.item_background, borderWidth=(0.01, 0.01), pos=(0, 0, item_pos), relief=DGG.FLAT, state=DGG.NORMAL, enableEdit=0, suppressMouse=0) item.bind(DGG.B1RELEASE, self._switch_active_item, [item]) if image is not None: OnscreenImage( # Add an Image image=image, pos=image_pos, scale=(1 * image_scale, 1, 1 * image_scale), parent=(item)) DirectLabel( parent=item, # Add a Title text=title, text_scale=self.i_y / 4, text_fg=(1, 1, 1, 1), text_align=TextNode.ALeft, frameColor=(0, 0, 0, 0), pos=title_pos) DirectLabel( parent=item, # Add a Text text=text, text_scale=self.i_y / 5, text_fg=(1, 1, 1, 1), text_align=TextNode.ALeft, frameColor=(0, 0, 0, 0), pos=text_pos) self.item_list.append(item) self.value_list.append(value) def get_active_item(self): return self.active_item def set_active_item(self, pos): self._switch_active_item(self.item_list[pos], None) def get_active_id(self): return int(self.active_item['text']) def get_active_value(self): return self.value_list[int(self.active_item['text']) - 1] def hide(self): '''Triggers the DirectFrame.hide() of the main frame''' self.frame.hide() def show(self): '''Triggers the DirectFrame.show() of the main frame''' self.frame.show() def clear(self): '''Destroys every item that was added to the list''' for item in self.item_list: item.destroy() self.item_list = [] self.active_item = None self.value_list = [] self.canvas.setZ(0) def destroy(self): '''Destroys the whole list and every item in it''' self.canvas.destroy() self.frame.destroy()
class DirectTree(DirectObject): def __init__(self, pos=(0,0,0), parent=None, frameSize=(1,1), treeStructure=DirectTreeItem(),): if parent is None: parent = aspect2d self.treeStructure = treeStructure self.treeStructureNodes = dict() self.highlightedNodes = list() self.frameWidth = frameSize[0] #0.8 self.frameHeight = frameSize[1] #1.5 self.itemIndent = 0.05 self.itemScale = 0.03 self.itemTextScale = 1.2 self.verticalSpacing = 0.0375 self.__createTreeLines() self.childrenFrame = DirectScrolledFrame( parent=parent,pos=pos, relief=DGG.GROOVE, state=DGG.NORMAL, # to create a mouse watcher region manageScrollBars=0, enableEdit=0, #suppressMouse=1, sortOrder=1000, frameColor=(0,0,0,.7), borderWidth=(0.005,0.005), frameSize =(0, self.frameWidth, 0, self.frameHeight), canvasSize=(0, self.frameWidth-self.itemScale*2, 0, self.frameHeight), verticalScroll_frameSize = [0,self.itemScale,0,1], horizontalScroll_frameSize = [0,1,0,self.itemScale], ) self.childrenCanvas=self.childrenFrame.getCanvas().attachNewNode('myCanvas') self.childrenCanvas.setX(0.05) self.childrenCanvas.setZ(self.frameHeight-1) # some fix to have the list in the window self.accept(EVENT_DIRECTTREE_REFRESH, self.update) def destroy(self): self.treeStructure = DirectTreeItem() self.render() self.childrenFrame.destroy() #del self.treeStructureNodes def __createLine(self, length=1, color=(1,1,1,1), endColor=None): LS=LineSegs() LS.setColor(*color) LS.moveTo(0,0,0) LS.drawTo(length*1,0,0) node=LS.create() if endColor: LS.setVertexColor(1,*endColor) return node def __createTreeLines(self): # create horisontal tree line color=(1,0,0,.9) self.horizontalTreeLine=NodePath(self.__createLine( self.itemIndent+self.itemScale*.5, color,endColor=(1,1,1,.1))) self.horizontalTreeLine.setTwoSided(0,100) self.horizontalTreeLine.setTransparency(1) # create vertical tree line self.verticalTreeLine=NodePath(self.__createLine( self.verticalSpacing,color)) self.verticalTreeLine.setR(90) self.verticalTreeLine.setTwoSided(0,100) self.verticalTreeLine.setTransparency(1) def render(self): ''' traverse the tree and update the visuals according to it ''' for treeItem in self.treeStructure.getRec(): # create nodes that have no visual elements if not treeItem in self.treeStructureNodes: treeNode = self.childrenCanvas.attachNewNode('') hor=self.horizontalTreeLine.instanceUnderNode(treeNode,'') vert=self.verticalTreeLine.instanceUnderNode(treeNode,'') vert.setZ(0.007) hor.setPos(-1.5*self.itemIndent,0,self.itemScale*.25) vert.setX(-.5*self.itemIndent) nodeButton = DirectButton( parent=treeNode, scale=self.itemScale, relief=DGG.FLAT, text_scale=self.itemTextScale, text_align=TextNode.ALeft, text=treeItem.name, rolloverSound=None, #clickSound=None, ) nodeButton.bind(DGG.B1PRESS,treeItem.button1press) nodeButton.bind(DGG.B2PRESS,treeItem.button2press) nodeButton.bind(DGG.B3PRESS,treeItem.button3press) #treeButton = None #if len(treeItem.childrens) > 0: treeButton = DirectButton( parent=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, command=treeItem.setOpen, sortOrder=1000, rolloverSound=None, #clickSound=None, ) self.treeStructureNodes[treeItem] = [treeNode, nodeButton, treeButton, hor, vert] # destroy nodes no more used for treeItem in self.treeStructureNodes.keys()[:]: #treeItem = self.treeStructureNodes[treeName] if treeItem not in self.treeStructure.getRec(): treeNode, nodeButton, treeButton, hor, vert = self.treeStructureNodes[treeItem] #nodeButton['text']='' nodeButton.unbind(DGG.B1PRESS) nodeButton.unbind(DGG.B2PRESS) nodeButton.unbind(DGG.B3PRESS) #nodeButton.detachNode() #nodeButton.removeNode() nodeButton.destroy() if treeButton: #treeButton['text']='' #treeButton['command']=None treeButton.detachNode() treeButton.removeNode() hor.detachNode() hor.removeNode() vert.detachNode() vert.removeNode() treeItem.destroy() #treeNode.detachNode() treeNode.removeNode() #treeNode.destroy() del self.treeStructureNodes[treeItem] frameHeight = len(self.treeStructureNodes) * self.verticalSpacing self.childrenFrame['canvasSize'] = (0, self.frameWidth-self.itemScale*2, 0, frameHeight) self.childrenCanvas.setZ(frameHeight-1) def highlight(self, selectedTreeNodes): for treeNode in self.highlightedNodes: treeNode.highlighted = False self.highlightedNodes = selectedTreeNodes for treeNode in self.highlightedNodes: treeNode.highlighted = True self.update() def update(self): ''' update the tree, updating the positions and hidden status of childrens ''' idx = 0 for treeItem in self.treeStructure.getRec(): # show or hide the items treeNode, nodeButton, treeButton, hor, vert = self.treeStructureNodes[treeItem] if treeItem.getShow(): treeNode.show() vert.show() if treeItem.parent: # dont show the horizontal line on the parent hor.show() else: hor.hide() idx += 1 else: treeNode.hide() hor.hide() vert.hide() if len(treeItem.childrens) > 0: treeButton.show() else: treeButton.hide() if treeItem.highlighted: nodeButton['text_fg'] = (1,0,0,1) else: nodeButton['text_fg'] = (0,0,0,1) # update the vertical position of the node treeNode.setPos(treeItem.getDepth()*self.itemIndent,0,1-idx*self.verticalSpacing) # if the tree element has a treebutton (if it has childrens), update the +/- if treeButton: # update the text for the open/close button tag = ['+','-'][treeItem.open] treeButton['text']=tag # calculate length to the last children with the same depth as this treeitem # this gives the length of the vertical line c = -1; i = 0; treeItemDepth = treeItem.getDepth() for recItem in treeItem.getRec(): if recItem.getShow(): c+=1 if recItem.getDepth() == treeItemDepth+1: i = c vert.setSz(i)
class DirectTree(DirectObject): def __init__( self, pos=(0, 0, 0), parent=None, frameSize=(1, 1), treeStructure=DirectTreeItem(), ): if parent is None: parent = aspect2d self.treeStructure = treeStructure self.treeStructureNodes = dict() self.highlightedNodes = list() self.frameWidth = frameSize[0] #0.8 self.frameHeight = frameSize[1] #1.5 self.itemIndent = 0.05 self.itemScale = 0.03 self.itemTextScale = 1.2 self.verticalSpacing = 0.0375 self.__createTreeLines() self.childrenFrame = DirectScrolledFrame( parent=parent, pos=pos, relief=DGG.GROOVE, state=DGG.NORMAL, # to create a mouse watcher region manageScrollBars=0, enableEdit=0, #suppressMouse=1, sortOrder=1000, frameColor=(0, 0, 0, .7), borderWidth=(0.005, 0.005), frameSize=(0, self.frameWidth, 0, self.frameHeight), canvasSize=(0, self.frameWidth - self.itemScale * 2, 0, self.frameHeight), verticalScroll_frameSize=[0, self.itemScale, 0, 1], horizontalScroll_frameSize=[0, 1, 0, self.itemScale], ) self.childrenCanvas = self.childrenFrame.getCanvas().attachNewNode( 'myCanvas') self.childrenCanvas.setX(0.05) self.childrenCanvas.setZ(self.frameHeight - 1) # some fix to have the list in the window self.accept(EVENT_DIRECTTREE_REFRESH, self.update) def destroy(self): self.treeStructure = DirectTreeItem() self.render() self.childrenFrame.destroy() #del self.treeStructureNodes def __createLine(self, length=1, color=(1, 1, 1, 1), endColor=None): LS = LineSegs() LS.setColor(*color) LS.moveTo(0, 0, 0) LS.drawTo(length * 1, 0, 0) node = LS.create() if endColor: LS.setVertexColor(1, *endColor) return node def __createTreeLines(self): # create horisontal tree line color = (1, 0, 0, .9) self.horizontalTreeLine = NodePath( self.__createLine(self.itemIndent + self.itemScale * .5, color, endColor=(1, 1, 1, .1))) self.horizontalTreeLine.setTwoSided(0, 100) self.horizontalTreeLine.setTransparency(1) # create vertical tree line self.verticalTreeLine = NodePath( self.__createLine(self.verticalSpacing, color)) self.verticalTreeLine.setR(90) self.verticalTreeLine.setTwoSided(0, 100) self.verticalTreeLine.setTransparency(1) def render(self): ''' traverse the tree and update the visuals according to it ''' for treeItem in self.treeStructure.getRec(): # create nodes that have no visual elements if not treeItem in self.treeStructureNodes: treeNode = self.childrenCanvas.attachNewNode('') hor = self.horizontalTreeLine.instanceUnderNode(treeNode, '') vert = self.verticalTreeLine.instanceUnderNode(treeNode, '') vert.setZ(0.007) hor.setPos(-1.5 * self.itemIndent, 0, self.itemScale * .25) vert.setX(-.5 * self.itemIndent) nodeButton = DirectButton( parent=treeNode, scale=self.itemScale, relief=DGG.FLAT, text_scale=self.itemTextScale, text_align=TextNode.ALeft, text=treeItem.name, rolloverSound=None, #clickSound=None, ) nodeButton.bind(DGG.B1PRESS, treeItem.button1press) nodeButton.bind(DGG.B2PRESS, treeItem.button2press) nodeButton.bind(DGG.B3PRESS, treeItem.button3press) #treeButton = None #if len(treeItem.childrens) > 0: treeButton = DirectButton( parent=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, command=treeItem.setOpen, sortOrder=1000, rolloverSound=None, #clickSound=None, ) self.treeStructureNodes[treeItem] = [ treeNode, nodeButton, treeButton, hor, vert ] # destroy nodes no more used for treeItem in self.treeStructureNodes.keys()[:]: #treeItem = self.treeStructureNodes[treeName] if treeItem not in self.treeStructure.getRec(): treeNode, nodeButton, treeButton, hor, vert = self.treeStructureNodes[ treeItem] #nodeButton['text']='' nodeButton.unbind(DGG.B1PRESS) nodeButton.unbind(DGG.B2PRESS) nodeButton.unbind(DGG.B3PRESS) #nodeButton.detachNode() #nodeButton.removeNode() nodeButton.destroy() if treeButton: #treeButton['text']='' #treeButton['command']=None treeButton.detachNode() treeButton.removeNode() hor.detachNode() hor.removeNode() vert.detachNode() vert.removeNode() treeItem.destroy() #treeNode.detachNode() treeNode.removeNode() #treeNode.destroy() del self.treeStructureNodes[treeItem] frameHeight = len(self.treeStructureNodes) * self.verticalSpacing self.childrenFrame['canvasSize'] = (0, self.frameWidth - self.itemScale * 2, 0, frameHeight) self.childrenCanvas.setZ(frameHeight - 1) def highlight(self, selectedTreeNodes): for treeNode in self.highlightedNodes: treeNode.highlighted = False self.highlightedNodes = selectedTreeNodes for treeNode in self.highlightedNodes: treeNode.highlighted = True self.update() def update(self): ''' update the tree, updating the positions and hidden status of childrens ''' idx = 0 for treeItem in self.treeStructure.getRec(): # show or hide the items treeNode, nodeButton, treeButton, hor, vert = self.treeStructureNodes[ treeItem] if treeItem.getShow(): treeNode.show() vert.show() if treeItem.parent: # dont show the horizontal line on the parent hor.show() else: hor.hide() idx += 1 else: treeNode.hide() hor.hide() vert.hide() if len(treeItem.childrens) > 0: treeButton.show() else: treeButton.hide() if treeItem.highlighted: nodeButton['text_fg'] = (1, 0, 0, 1) else: nodeButton['text_fg'] = (0, 0, 0, 1) # update the vertical position of the node treeNode.setPos(treeItem.getDepth() * self.itemIndent, 0, 1 - idx * self.verticalSpacing) # if the tree element has a treebutton (if it has childrens), update the +/- if treeButton: # update the text for the open/close button tag = ['+', '-'][treeItem.open] treeButton['text'] = tag # calculate length to the last children with the same depth as this treeitem # this gives the length of the vertical line c = -1 i = 0 treeItemDepth = treeItem.getDepth() for recItem in treeItem.getRec(): if recItem.getShow(): c += 1 if recItem.getDepth() == treeItemDepth + 1: i = c vert.setSz(i)