示例#1
0
文件: Menu.py 项目: Crpaxton4/NanoV
class DropDownMenu(DirectObject):
    ALeft = 0
    ACenter = 1
    ARight = 2
    ENone = 0
    EFade = 1
    ESlide = 2
    EStretch = 3
    PLeft = 0
    PRight = 1
    PBottom = 2
    PTop = 3
    parents = (('a2dBottomLeft', 'a2dLeftCenter', 'a2dTopLeft'),
               ('a2dTopRight', 'a2dRightCenter', 'a2dBottomRight'),
               ('a2dBottomLeft', 'a2dBottomCenter', 'a2dBottomRight'),
               ('a2dTopLeft', 'a2dTopCenter', 'a2dTopRight'))

    def __init__(self,
                 items,
                 parent=None,
                 sidePad=.0,
                 edgePos=PTop,
                 align=ALeft,
                 effect=ENone,
                 buttonThrower=None,
                 font=None,
                 baselineOffset=.0,
                 scale=.05,
                 itemHeight=1.,
                 leftPad=.0,
                 separatorHeight=.5,
                 underscoreThickness=1,
                 BGColor=(0, 0, 0, .7),
                 BGBorderColor=(1, .85, .4, 1),
                 separatorColor=(1, 1, 1, 1),
                 frameColorHover=(1, .85, .4, 1),
                 frameColorPress=(0, 1, 0, 1),
                 textColorReady=(1, 1, 1, 1),
                 textColorHover=(0, 0, 0, 1),
                 textColorPress=(0, 0, 0, 1),
                 textColorDisabled=(.5, .5, .5, 1),
                 draggable=False,
                 onMove=None):
        '''
      sidePad : additional space on the left and right of the text item
      edgePos : menu bar position on the screen,
                  use DropDownMenu.PLeft, PRight, PBottom, or PTop
      align   : menu items alignment on menu bar,
                  use DropDownMenu.ALeft, ACenter, or ARight
      effect  : the drop down appearance effect,
                  use DropDownMenu.ENone, EFade, ESlide, or EStretch
      draggable : menu bar's draggability status
      onMove : a function which will be called after changing edge position

      Read the remaining options documentation in PopupMenu class.
      '''
        self.parent = parent if parent else getattr(
            base, DropDownMenu.parents[edgePos][align])
        self.BT = buttonThrower if buttonThrower else base.buttonThrowers[
            0].node()
        self.menu = self.parent.attachNewNode('dropdownmenu-%s' % id(self))
        self.font = font if font else TextNode.getDefaultFont()
        self.baselineOffset = baselineOffset
        self.scale = scale
        self.itemHeight = itemHeight
        self.sidePad = sidePad
        self.edgePos = edgePos
        self.alignment = align
        self.effect = effect
        self.leftPad = leftPad
        self.underscoreThickness = underscoreThickness
        self.separatorHeight = separatorHeight
        self.BGColor = BGColor
        self.BGBorderColor = BGBorderColor
        self.separatorColor = separatorColor
        self.frameColorHover = frameColorHover
        self.frameColorPress = frameColorPress
        self.textColorReady = textColorReady
        self.textColorHover = textColorHover
        self.textColorPress = textColorPress
        self.textColorDisabled = textColorDisabled
        self.draggable = draggable
        self.onMove = onMove
        self.dropDownMenu = self.whoseDropDownMenu = None

        self.gapFromEdge = gapFromEdge = .008
        texMargin = self.font.getTextureMargin() * self.scale * .25
        b = DirectButton(parent=NodePath(''),
                         text='^|g_',
                         text_font=self.font,
                         scale=self.scale)
        fr = b.node().getFrame()
        b.getParent().removeNode()
        baselineToCenter = (fr[2] + fr[3]) * self.scale
        LH = (fr[3] - fr[2]) * self.itemHeight * self.scale
        baselineToTop = (fr[3] * self.itemHeight * self.scale /
                         LH) / (1. + self.baselineOffset)
        baselineToBot = LH / self.scale - baselineToTop
        self.height = LH + .01
        l, r, b, t = 0, 5, -self.height, 0
        self.menuBG = DirectFrame(parent=self.menu,
                                  frameColor=BGColor,
                                  frameSize=(l, r, b, t),
                                  state=DGG.NORMAL,
                                  suppressMouse=1)
        if self.draggable:
            self.setDraggable(1)
        LSborder = LineSegs()
        LSborder.setThickness(2)
        LSborder.setColor(0, 0, 0, 1)
        LSborder.moveTo(l, 0, b)
        LSborder.drawTo(r, 0, b)
        self.menuBG.attachNewNode(LSborder.create())
        self.itemsParent = self.menu.attachNewNode('menu items parent')

        x = sidePad * self.scale + gapFromEdge
        for t, menuItemsGenerator in items:
            underlinePos = t.find('_')
            t = t.replace('_', '')
            b = DirectButton(
                parent=self.itemsParent,
                text=t,
                text_font=self.font,
                pad=(sidePad, 0),
                scale=self.scale,
                pos=(x, 0, -baselineToTop * self.scale - gapFromEdge),
                text_fg=textColorReady,
                # text color when mouse over
                text2_fg=textColorHover,
                # text color when pressed
                text1_fg=textColorPress,
                # framecolor when pressed
                frameColor=frameColorPress,
                command=self.__createMenu,
                extraArgs=[True, menuItemsGenerator],
                text_align=TextNode.ALeft,
                relief=DGG.FLAT,
                rolloverSound=0,
                clickSound=0)
            b['extraArgs'] += [b.getName()]
            b.stateNodePath[2].setColor(
                *frameColorHover)  # framecolor when mouse over
            b.stateNodePath[0].setColor(0, 0, 0, 0)  # framecolor when ready
            fr = b.node().getFrame()
            b['frameSize'] = (fr[0], fr[1], -baselineToBot, baselineToTop)
            self.accept(DGG.ENTER + b.guiId, self.__createMenu,
                        [False, menuItemsGenerator,
                         b.getName()])
            if underlinePos > -1:
                tn = TextNode('')
                tn.setFont(self.font)
                tn.setText(t[:underlinePos + 1])
                tnp = NodePath(tn.getInternalGeom())
                underlineXend = tnp.getTightBounds()[1][0]
                tnp.removeNode()
                tn.setText(t[underlinePos])
                tnp = NodePath(tn.getInternalGeom())
                b3 = tnp.getTightBounds()
                underlineXstart = underlineXend - (b3[1] - b3[0])[0]
                tnp.removeNode()
                LSunder = LineSegs()
                LSunder.setThickness(underscoreThickness)
                LSunder.moveTo(underlineXstart + texMargin, 0,
                               -.7 * baselineToBot)
                LSunder.drawTo(underlineXend - texMargin, 0,
                               -.7 * baselineToBot)
                underline = b.stateNodePath[0].attachNewNode(LSunder.create())
                underline.setColor(Vec4(*textColorReady), 1)
                underline.copyTo(b.stateNodePath[1],
                                 10).setColor(Vec4(*textColorPress), 1)
                underline.copyTo(b.stateNodePath[2],
                                 10).setColor(Vec4(*textColorHover), 1)
                self.accept('alt-' + t[underlinePos].lower(),
                            self.__createMenu,
                            [True, menuItemsGenerator,
                             b.getName()])
            x += (fr[1] - fr[0]) * self.scale
        self.width = x - 2 * gapFromEdge
        self.align(align)
        self.setEdgePos(edgePos)
        self.minZ = base.a2dBottom + self.height if edgePos == DropDownMenu.PBottom else None
        viewPlaneNode = PlaneNode('cut menu')
        viewPlaneNode.setPlane(Plane(Vec3(0, 0, -1), Point3(0, 0, -LH)))
        self.clipPlane = self.menuBG.attachNewNode(viewPlaneNode)

    def __createMenu(self, clicked, menuItemsGenerator, DBname, crap=None):
        itself = self.dropDownMenu and self.whoseDropDownMenu == DBname
        if not (clicked or self.dropDownMenu) or (not clicked and itself):
            return
        self.__removeMenu()
        if clicked and itself: return
        # removes any context menu
        if clicked:
            self.__removePopupMenu()
        self.dropDownMenu = PopupMenu(
            items=menuItemsGenerator(),
            parent=self.parent,
            buttonThrower=self.BT,
            font=self.font,
            baselineOffset=self.baselineOffset,
            scale=self.scale,
            itemHeight=self.itemHeight,
            leftPad=self.leftPad,
            separatorHeight=self.separatorHeight,
            underscoreThickness=self.underscoreThickness,
            BGColor=self.BGColor,
            BGBorderColor=self.BGBorderColor,
            separatorColor=self.separatorColor,
            frameColorHover=self.frameColorHover,
            frameColorPress=self.frameColorPress,
            textColorReady=self.textColorReady,
            textColorHover=self.textColorHover,
            textColorPress=self.textColorPress,
            textColorDisabled=self.textColorDisabled,
            minZ=self.minZ)
        self.acceptOnce(self.dropDownMenu.BTprefix + 'destroyed', setattr,
                        [self, 'dropDownMenu', None])
        self.whoseDropDownMenu = DBname
        item = self.menu.find('**/%s' % DBname)
        fr = item.node().getFrame()
        #~ if self.edgePos==DropDownMenu.PLeft:
        #~ x=max(fr[1],self.dropDownMenu.menu.getX(item))
        #~ z=fr[2]
        #~ elif self.edgePos==DropDownMenu.PRight:
        #~ x=min(fr[0],self.dropDownMenu.menu.getX(item))
        #~ z=fr[2]-self.dropDownMenu.maxWidth
        #~ elif self.edgePos in (DropDownMenu.PBottom,DropDownMenu.PTop):
        #~ x=fr[1]-self.dropDownMenu.maxWidth if self.alignment==DropDownMenu.ARight else fr[0]
        #~ z=fr[2] if self.edgePos==DropDownMenu.PTop else fr[3]+self.dropDownMenu.height
        if self.edgePos == DropDownMenu.PLeft:
            x = max(fr[1], self.dropDownMenu.menu.getX(item))
            z = fr[3] - (self.height - self.gapFromEdge) / self.scale
        elif self.edgePos == DropDownMenu.PRight:
            x = min(fr[0], self.dropDownMenu.menu.getX(item))
            z = fr[3] - (self.height - self.gapFromEdge
                         ) / self.scale - self.dropDownMenu.maxWidth
        elif self.edgePos in (DropDownMenu.PBottom, DropDownMenu.PTop):
            x = fr[
                1] - self.dropDownMenu.maxWidth if self.alignment == DropDownMenu.ARight else fr[
                    0]
            z = fr[3] - (
                self.height - self.gapFromEdge
            ) / self.scale if self.edgePos == DropDownMenu.PTop else fr[2] + (
                self.height) / self.scale + self.dropDownMenu.height
        self.dropDownMenu.menu.setPos(item, x, 0, z)

        if self.effect == DropDownMenu.EFade:
            self.dropDownMenu.menu.colorScaleInterval(
                .3, Vec4(1), Vec4(1, 1, 1, 0), blendType='easeIn').start()
        elif self.effect == DropDownMenu.ESlide:
            pos = self.dropDownMenu.menu.getPos()
            if self.edgePos == DropDownMenu.PTop:
                startPos = Point3(0, 0, self.dropDownMenu.height * self.scale)
            elif self.edgePos == DropDownMenu.PBottom:
                startPos = Point3(0, 0, -self.dropDownMenu.height * self.scale)
            elif self.edgePos == DropDownMenu.PLeft:
                startPos = Point3(-self.dropDownMenu.maxWidth * self.scale, 0,
                                  0)
            elif self.edgePos == DropDownMenu.PRight:
                startPos = Point3(self.dropDownMenu.maxWidth * self.scale, 0,
                                  0)
            self.dropDownMenu.menu.posInterval(.3,
                                               pos,
                                               pos + startPos,
                                               blendType='easeIn').start()
            self.dropDownMenu.menu.setClipPlane(self.clipPlane)
        elif self.effect == DropDownMenu.EStretch:
            if self.edgePos == DropDownMenu.PTop:
                startHpr = Vec3(0, -90, 0)
            elif self.edgePos == DropDownMenu.PBottom:
                dz = self.dropDownMenu.height * self.scale
                for c in asList(self.dropDownMenu.menu.getChildren()):
                    c.setZ(c.getZ() + dz)
                self.dropDownMenu.menu.setZ(self.dropDownMenu.menu, -dz)
                startHpr = Vec3(0, 90, 0)
            elif self.edgePos == DropDownMenu.PLeft:
                startHpr = Vec3(90, 0, 0)
            elif self.edgePos == DropDownMenu.PRight:
                dx = self.dropDownMenu.maxWidth * self.scale
                for c in asList(self.dropDownMenu.menu.getChildren()):
                    c.setX(c.getX() - dx)
                self.dropDownMenu.menu.setX(self.dropDownMenu.menu, dx)
                startHpr = Vec3(-90, 0, 0)
            self.dropDownMenu.menu.hprInterval(.3,
                                               Vec3(0),
                                               startHpr,
                                               blendType='easeIn').start()

    def __removeMenu(self):
        if self.dropDownMenu:
            self.dropDownMenu.destroy()
            self.dropDownMenu = None

    def __removePopupMenu(self):
        menuEvents = messenger.find('menu-')
        if menuEvents:
            menuEvent = menuEvents.keys()
            activeMenu = menuEvents[menuEvent[0]].values()[0][0].im_self
            activeMenu.destroy(delParents=True)

    def __reverseItems(self):
        tmp = NodePath('')
        self.itemsParent.getChildren().reparentTo(tmp)
        children = asList(tmp.getChildren())
        for c in reversed(children):
            c.reparentTo(self.itemsParent)
        tmp.removeNode()
        x = self.sidePad * self.scale + self.gapFromEdge
        for c in asList(self.itemsParent.getChildren()):
            c.setX(x)
            fr = c.node().getFrame()
            x += (fr[1] - fr[0]) * self.scale

    def __startDrag(self, crap):
        taskMgr.add(self.__drag,
                    'dragging menu bar',
                    extraArgs=[Point2(base.mouseWatcherNode.getMouse())])
        self.__removePopupMenu()
        self.origBTprefix = self.BT.getPrefix()
        self.BT.setPrefix('dragging menu bar')

    def __drag(self, origMpos):
        if base.mouseWatcherNode.hasMouse():
            mpos = base.mouseWatcherNode.getMouse()
            if mpos != origMpos:
                x, y = mpos.getX(), mpos.getY(),
                deltas = [x + 1, 1 - x, y + 1, 1 - y]
                closestEdge = deltas.index(min(deltas))
                if closestEdge != self.edgePos:
                    self.setEdgePos(closestEdge)
        return Task.cont

    def __stopDrag(self, crap):
        taskMgr.remove('dragging menu bar')
        self.BT.setPrefix(self.origBTprefix)

    def destroy(self):
        self.__removeMenu()
        self.ignoreAll()
        self.menu.removeNode()

    def isDraggable(self):
        '''
      Returns menu bar's draggable status
      '''
        return self.draggable

    def setDraggable(self, d):
        '''
      Sets menu bar's draggable status
      '''
        self.draggable = d
        if d:
            self.menuBG.bind(DGG.B1PRESS, self.__startDrag)
            self.menuBG.bind(DGG.B1RELEASE, self.__stopDrag)
        else:
            self.menuBG.unbind(DGG.B1PRESS)
            self.menuBG.unbind(DGG.B1RELEASE)

    def align(self, align=None):
        '''
      Aligns menu text on menu bar.
      Use one of DropDownMenu.ALeft, DropDownMenu.ACenter, or DropDownMenu.ARight.
      '''
        self.parent = getattr(
            base, DropDownMenu.parents[self.edgePos]
            [self.alignment if align is None else align])
        self.menu.reparentTo(self.parent)
        if align is not None:
            self.itemsParent.setX(-.5 * self.width * align)
            self.menuBG.setX(-.5 * (5 - self.width) * align)
            self.alignment = align

    def setEdgePos(self, edge):
        '''
      Sticks menu bar to 4 possible screen edges :
          DropDownMenu.PLeft, DropDownMenu.PRight,
          DropDownMenu.PBottom, or DropDownMenu.PTop.
      '''
        lastEdge = self.edgePos
        reverseItems = lastEdge == DropDownMenu.PLeft
        self.edgePos = edge
        self.itemsParent.setZ(0)
        self.menuBG.setSz(1)
        alignment = None
        if self.edgePos == DropDownMenu.PLeft:
            self.menu.setR(-90)
            if self.alignment != DropDownMenu.ACenter:
                alignment = 2 - self.alignment
            self.__reverseItems()
            self.minZ = None
        else:
            if self.edgePos == DropDownMenu.PRight:
                self.menu.setR(90)
                self.minZ = None
            elif self.edgePos == DropDownMenu.PBottom:
                self.menu.setR(0)
                self.menuBG.setSz(-1)
                self.itemsParent.setZ(-self.menuBG.node().getFrame()[2])
                self.minZ = base.a2dBottom + self.height
            elif self.edgePos == DropDownMenu.PTop:
                self.menu.setR(0)
                self.minZ = None
            if reverseItems:
                if self.alignment != DropDownMenu.ACenter:
                    alignment = 2 - self.alignment
                self.__reverseItems()
        self.align(alignment)
        if callable(self.onMove):
            self.onMove()