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)
Example #3
0
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)