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)