447 lines
15 KiB
Python
447 lines
15 KiB
Python
from panda3d.core import *
|
|
from direct.gui.DirectGui import *
|
|
from direct.task import Task
|
|
from .SCConstants import *
|
|
from direct.interval.IntervalGlobal import *
|
|
from .SCObject import SCObject
|
|
from direct.showbase.PythonUtil import makeTuple
|
|
|
|
class SCMenu(SCObject, NodePath):
|
|
SpeedChatRolloverTolerance = ConfigVariableDouble('speedchat-rollover-tolerance', 0.08).value
|
|
WantFade = ConfigVariableBool('want-speedchat-fade', 0).value
|
|
FadeDuration = ConfigVariableDouble('speedchat-fade-duration', 0.2).value
|
|
SerialNum = 0
|
|
BackgroundModelName = None
|
|
GuiModelName = None
|
|
|
|
def __init__(self, holder = None):
|
|
SCObject.__init__(self)
|
|
self.SerialNum = SCMenu.SerialNum
|
|
SCMenu.SerialNum += 1
|
|
node = hidden.attachNewNode('SCMenu%s' % self.SerialNum)
|
|
NodePath.__init__(self, node)
|
|
self.setHolder(holder)
|
|
self.FinalizeTaskName = 'SCMenu%s_Finalize' % self.SerialNum
|
|
self.ActiveMemberSwitchTaskName = 'SCMenu%s_SwitchActiveMember' % self.SerialNum
|
|
self.bg = loader.loadModel(self.BackgroundModelName)
|
|
|
|
def findNodes(names, model = self.bg):
|
|
results = []
|
|
for name in names:
|
|
for nm in makeTuple(name):
|
|
node = model.find('**/%s' % nm)
|
|
if not node.isEmpty():
|
|
results.append(node)
|
|
break
|
|
|
|
return results
|
|
|
|
self.bgTop, self.bgBottom, self.bgLeft, self.bgRight, self.bgMiddle, self.bgTopLeft, self.bgBottomLeft, self.bgTopRight, self.bgBottomRight = findNodes([('top', 'top1'),
|
|
'bottom',
|
|
'left',
|
|
'right',
|
|
'middle',
|
|
'topLeft',
|
|
'bottomLeft',
|
|
'topRight',
|
|
'bottomRight'])
|
|
self.bg.reparentTo(self, -1)
|
|
self.__members = []
|
|
self.activeMember = None
|
|
self.activeCandidate = None
|
|
self.fadeIval = None
|
|
self.width = 1
|
|
self.inFinalize = 0
|
|
return
|
|
|
|
def destroy(self):
|
|
self.stopFade()
|
|
SCObject.destroy(self)
|
|
del self.bgTop
|
|
del self.bgBottom
|
|
del self.bgLeft
|
|
del self.bgRight
|
|
del self.bgMiddle
|
|
del self.bgBottomLeft
|
|
del self.bgTopRight
|
|
del self.bgBottomRight
|
|
self.bg.removeNode()
|
|
del self.bg
|
|
self.holder = None
|
|
for member in self.__members:
|
|
member.destroy()
|
|
|
|
del self.__members
|
|
self.removeNode()
|
|
taskMgr.remove(self.FinalizeTaskName)
|
|
taskMgr.remove(self.ActiveMemberSwitchTaskName)
|
|
return
|
|
|
|
def clearMenu(self):
|
|
while len(self):
|
|
item = self[0]
|
|
del self[0]
|
|
item.destroy()
|
|
|
|
def rebuildFromStructure(self, structure, title = None):
|
|
self.clearMenu()
|
|
if title:
|
|
holder = self.getHolder()
|
|
if holder:
|
|
holder.setTitle(title)
|
|
self.appendFromStructure(structure)
|
|
|
|
def appendFromStructure(self, structure):
|
|
from .SpeedChatTypes import SCMenuHolder, SCStaticTextTerminal, SCGMTextTerminal
|
|
from otp.otpbase import OTPLocalizer
|
|
|
|
def addChildren(menu, childList):
|
|
for child in childList:
|
|
emote = None
|
|
if type(child) == type({}):
|
|
item = list(child.keys())[0]
|
|
emote = child[item]
|
|
child = item
|
|
if type(child) == type(0):
|
|
terminal = SCStaticTextTerminal(child)
|
|
if emote is not None:
|
|
terminal.setLinkedEmote(emote)
|
|
menu.append(terminal)
|
|
elif type(child) == type([]):
|
|
if type(child[0]) == type(''):
|
|
holderTitle = child[0]
|
|
subMenu = SCMenu()
|
|
subMenuChildren = child[1:]
|
|
else:
|
|
menuType, holderTitle = child[0], child[1]
|
|
subMenu = menuType()
|
|
subMenuChildren = child[2:]
|
|
if emote:
|
|
print('warning: tried to link emote %s to a menu holder' % emote)
|
|
holder = SCMenuHolder(holderTitle, menu=subMenu)
|
|
menu.append(holder)
|
|
addChildren(subMenu, subMenuChildren)
|
|
elif type(child) == type('') and child[:2] == 'gm':
|
|
terminal = SCGMTextTerminal(child)
|
|
menu.append(terminal)
|
|
else:
|
|
raise 'error parsing speedchat structure. invalid child: %s' % child
|
|
|
|
return
|
|
|
|
addChildren(self, structure)
|
|
addChildren = None
|
|
return
|
|
|
|
def fadeFunc(self, t):
|
|
cs = self.getColorScale()
|
|
self.setColorScale(cs[0], cs[1], cs[2], t)
|
|
|
|
def stopFade(self):
|
|
if self.fadeIval is not None:
|
|
self.fadeIval.pause()
|
|
self.fadeIval = None
|
|
return
|
|
|
|
def enterVisible(self):
|
|
SCObject.enterVisible(self)
|
|
self.privScheduleFinalize()
|
|
for member in self:
|
|
if member.isViewable():
|
|
if not member.isVisible():
|
|
member.enterVisible()
|
|
|
|
self.childHasFaded = 0
|
|
alreadyFaded = 0
|
|
parentMenu = None
|
|
if self.holder is not None:
|
|
if self.holder.parentMenu is not None:
|
|
parentMenu = self.holder.parentMenu
|
|
alreadyFaded = parentMenu.childHasFaded
|
|
if SCMenu.WantFade:
|
|
if alreadyFaded:
|
|
self.fadeFunc(1.0)
|
|
else:
|
|
self.stopFade()
|
|
self.fadeIval = LerpFunctionInterval(self.fadeFunc, fromData=0.0, toData=1.0, duration=SCMenu.FadeDuration)
|
|
self.fadeIval.play()
|
|
if parentMenu is not None:
|
|
parentMenu.childHasFaded = 1
|
|
return
|
|
|
|
def exitVisible(self):
|
|
SCObject.exitVisible(self)
|
|
self.stopFade()
|
|
self.privCancelFinalize()
|
|
self.__cancelActiveMemberSwitch()
|
|
self.__setActiveMember(None)
|
|
for member in self:
|
|
if member.isVisible():
|
|
member.exitVisible()
|
|
|
|
return
|
|
|
|
def setHolder(self, holder):
|
|
self.holder = holder
|
|
|
|
def getHolder(self):
|
|
return self.holder
|
|
|
|
def isTopLevel(self):
|
|
return self.holder == None
|
|
|
|
def memberSelected(self, member):
|
|
self.__cancelActiveMemberSwitch()
|
|
self.__setActiveMember(member)
|
|
|
|
def __setActiveMember(self, member):
|
|
if self.activeMember is member:
|
|
return
|
|
if self.activeMember is not None:
|
|
self.activeMember.exitActive()
|
|
self.activeMember = member
|
|
if self.activeMember is not None:
|
|
self.activeMember.reparentTo(self)
|
|
self.activeMember.enterActive()
|
|
return
|
|
|
|
def memberGainedInputFocus(self, member):
|
|
self.__cancelActiveMemberSwitch()
|
|
if member is self.activeMember:
|
|
return
|
|
if self.activeMember is None or SCMenu.SpeedChatRolloverTolerance == 0 or member.posInParentMenu < self.activeMember.posInParentMenu:
|
|
self.__setActiveMember(member)
|
|
else:
|
|
|
|
def doActiveMemberSwitch(task, self = self, member = member):
|
|
self.activeCandidate = None
|
|
self.__setActiveMember(member)
|
|
return Task.done
|
|
|
|
minFrameRate = 1.0 / SCMenu.SpeedChatRolloverTolerance
|
|
if globalClock.getAverageFrameRate() > minFrameRate:
|
|
taskMgr.doMethodLater(SCMenu.SpeedChatRolloverTolerance, doActiveMemberSwitch, self.ActiveMemberSwitchTaskName)
|
|
self.activeCandidate = member
|
|
else:
|
|
self.__setActiveMember(member)
|
|
return
|
|
|
|
def __cancelActiveMemberSwitch(self):
|
|
taskMgr.remove(self.ActiveMemberSwitchTaskName)
|
|
self.activeCandidate = None
|
|
return
|
|
|
|
def memberLostInputFocus(self, member):
|
|
if member is self.activeCandidate:
|
|
self.__cancelActiveMemberSwitch()
|
|
if member is not self.activeMember:
|
|
pass
|
|
elif not member.hasStickyFocus():
|
|
self.__setActiveMember(None)
|
|
return
|
|
|
|
def memberViewabilityChanged(self, member):
|
|
self.invalidate()
|
|
|
|
def invalidate(self):
|
|
SCObject.invalidate(self)
|
|
if self.isVisible():
|
|
self.privScheduleFinalize()
|
|
|
|
def privScheduleFinalize(self):
|
|
|
|
def finalizeMenu(task, self = self):
|
|
self.finalize()
|
|
return Task.done
|
|
|
|
taskMgr.remove(self.FinalizeTaskName)
|
|
taskMgr.add(finalizeMenu, self.FinalizeTaskName, priority=SCMenuFinalizePriority)
|
|
|
|
def privCancelFinalize(self):
|
|
taskMgr.remove(self.FinalizeTaskName)
|
|
|
|
def isFinalizing(self):
|
|
return self.inFinalize
|
|
|
|
def finalize(self):
|
|
if not self.isDirty():
|
|
return
|
|
self.inFinalize = 1
|
|
SCObject.finalize(self)
|
|
visibleMembers = []
|
|
for member in self:
|
|
if member.isViewable():
|
|
visibleMembers.append(member)
|
|
member.reparentTo(self)
|
|
else:
|
|
member.reparentTo(hidden)
|
|
if self.activeMember is member:
|
|
self.__setActiveMember(None)
|
|
|
|
maxWidth = 0.0
|
|
maxHeight = 0.0
|
|
for member in visibleMembers:
|
|
width, height = member.getMinDimensions()
|
|
maxWidth = max(maxWidth, width)
|
|
maxHeight = max(maxHeight, height)
|
|
|
|
holder = self.getHolder()
|
|
if holder is not None:
|
|
widthToCover = holder.getMinSubmenuWidth()
|
|
maxWidth = max(maxWidth, widthToCover)
|
|
memberWidth, memberHeight = maxWidth, maxHeight
|
|
self.width = maxWidth
|
|
for i in range(len(visibleMembers)):
|
|
member = visibleMembers[i]
|
|
member.setPos(0, 0, -i * maxHeight)
|
|
member.setDimensions(memberWidth, memberHeight)
|
|
member.finalize()
|
|
|
|
if len(visibleMembers) > 0:
|
|
z1 = visibleMembers[0].getZ(aspect2d)
|
|
visibleMembers[0].setZ(-maxHeight)
|
|
z2 = visibleMembers[0].getZ(aspect2d)
|
|
visibleMembers[0].setZ(0)
|
|
actualHeight = (z2 - z1) * len(visibleMembers)
|
|
bottomZ = self.getZ(aspect2d) + actualHeight
|
|
if bottomZ < -1.0:
|
|
overlap = bottomZ - -1.0
|
|
self.setZ(aspect2d, self.getZ(aspect2d) - overlap)
|
|
if self.getZ(aspect2d) > 1.0:
|
|
self.setZ(aspect2d, 1.0)
|
|
sX = memberWidth
|
|
sZ = memberHeight * len(visibleMembers)
|
|
self.bgMiddle.setScale(sX, 1, sZ)
|
|
self.bgTop.setScale(sX, 1, 1)
|
|
self.bgBottom.setScale(sX, 1, 1)
|
|
self.bgLeft.setScale(1, 1, sZ)
|
|
self.bgRight.setScale(1, 1, sZ)
|
|
self.bgBottomLeft.setZ(-sZ)
|
|
self.bgBottom.setZ(-sZ)
|
|
self.bgTopRight.setX(sX)
|
|
self.bgRight.setX(sX)
|
|
self.bgBottomRight.setX(sX)
|
|
self.bgBottomRight.setZ(-sZ)
|
|
sB = 0.15
|
|
self.bgTopLeft.setSx(aspect2d, sB)
|
|
self.bgTopLeft.setSz(aspect2d, sB)
|
|
self.bgBottomRight.setSx(aspect2d, sB)
|
|
self.bgBottomRight.setSz(aspect2d, sB)
|
|
self.bgBottomLeft.setSx(aspect2d, sB)
|
|
self.bgBottomLeft.setSz(aspect2d, sB)
|
|
self.bgTopRight.setSx(aspect2d, sB)
|
|
self.bgTopRight.setSz(aspect2d, sB)
|
|
self.bgTop.setSz(aspect2d, sB)
|
|
self.bgBottom.setSz(aspect2d, sB)
|
|
self.bgLeft.setSx(aspect2d, sB)
|
|
self.bgRight.setSx(aspect2d, sB)
|
|
r, g, b = self.getColorScheme().getFrameColor()
|
|
a = self.getColorScheme().getAlpha()
|
|
self.bg.setColorScale(r, g, b, a)
|
|
if self.activeMember is not None:
|
|
self.activeMember.reparentTo(self)
|
|
self.validate()
|
|
self.inFinalize = 0
|
|
return
|
|
|
|
def append(self, element):
|
|
if isinstance(self.__members, tuple):
|
|
self.__members = list(self.__members)
|
|
self.__members.append(element)
|
|
self.privMemberListChanged(added=[element])
|
|
|
|
def extend(self, elements):
|
|
self += elements
|
|
|
|
def index(self, element):
|
|
return self.__members.index(element)
|
|
|
|
def __len__(self):
|
|
return len(self.__members)
|
|
|
|
def __getitem__(self, index):
|
|
if isinstance(index, slice):
|
|
if isinstance(self.__members, tuple):
|
|
self.__members = list(self.__members)
|
|
return self.__members[index.start:index.stop]
|
|
return self.__members[index]
|
|
|
|
def __setitem__(self, index, value):
|
|
if isinstance(self.__members, tuple):
|
|
self.__members = list(self.__members)
|
|
if isinstance(index, slice):
|
|
removedMembers = self.__members[index.start:index.stop]
|
|
self.__members[index.start:index.stop] = list(value)
|
|
self.privMemberListChanged(added=list(value), removed=removedMembers)
|
|
else:
|
|
removedMember = self.__members[index]
|
|
self.__members[index] = value
|
|
self.privMemberListChanged(added=[value], removed=[removedMember])
|
|
|
|
def __delitem__(self, index):
|
|
if isinstance(self.__members, tuple):
|
|
self.__members = list(self.__members)
|
|
if isinstance(index, slice):
|
|
removedMembers = self.__members[index.start:index.stop]
|
|
del self.__members[index.start:index.stop]
|
|
self.privMemberListChanged(removed=removedMembers)
|
|
else:
|
|
removedMember = self.__members[index]
|
|
del self.__members[index]
|
|
self.privMemberListChanged(removed=[removedMember])
|
|
|
|
def __iadd__(self, other):
|
|
if isinstance(self.__members, tuple):
|
|
self.__members = list(self.__members)
|
|
if isinstance(other, SCMenu):
|
|
otherMenu = other
|
|
other = otherMenu.__members
|
|
del otherMenu[:]
|
|
self.__members += list(other)
|
|
self.privMemberListChanged(added=list(other))
|
|
return self
|
|
|
|
def privMemberListChanged(self, added = None, removed = None):
|
|
if removed is not None:
|
|
for element in removed:
|
|
if element is self.activeMember:
|
|
self.__setActiveMember(None)
|
|
if element.getParentMenu() is self:
|
|
if element.isVisible():
|
|
element.exitVisible()
|
|
element.setParentMenu(None)
|
|
element.reparentTo(hidden)
|
|
|
|
if added is not None:
|
|
for element in added:
|
|
self.privAdoptSCObject(element)
|
|
element.setParentMenu(self)
|
|
|
|
if self.holder is not None:
|
|
self.holder.updateViewability()
|
|
for i in range(len(self.__members)):
|
|
self.__members[i].posInParentMenu = i
|
|
|
|
self.invalidate()
|
|
return
|
|
|
|
def privSetSettingsRef(self, settingsRef):
|
|
SCObject.privSetSettingsRef(self, settingsRef)
|
|
for member in self:
|
|
member.privSetSettingsRef(settingsRef)
|
|
|
|
def invalidateAll(self):
|
|
SCObject.invalidateAll(self)
|
|
for member in self:
|
|
member.invalidateAll()
|
|
|
|
def finalizeAll(self):
|
|
SCObject.finalizeAll(self)
|
|
for member in self:
|
|
member.finalizeAll()
|
|
|
|
def getWidth(self):
|
|
return self.width
|
|
|
|
def __str__(self):
|
|
return '%s: menu%s' % (self.__class__.__name__, self.SerialNum)
|