class panda3dIOClass( DirectObject.DirectObject ): # set gui key to None if you want to call toggleConsole from outside this class gui_key = CONSOLE_TOGGLE_KEY autocomplete_key = CONSOLE_AUTOCOMPLETE_KEY autohelp_key = CONSOLE_AUTOHELP_KEY scroll_up_repeat_key = CONSOLE_SCROLL_UP_KEY + "-repeat" scroll_down_repeat_key = CONSOLE_SCROLL_DOWN_KEY + "-repeat" # change size of text and number of characters on one line # scale of frame ( must be small (0.0x) scale = PANDA3D_CONSOLE_SCALE # to define a special font, if loading fails the default font is used (without warning) font = PANDA3D_CONSOLE_FONT fontWidth = PANDA3D_CONSOLE_FONT_WIDTH # frame position and size (vertical & horizontal) h_pos = PANDA3D_CONSOLE_HORIZONTAL_POS h_size = PANDA3D_CONSOLE_HORIZONTAL_SIZE # v_size + v_pos should not exceed 2.0, else parts of the interface will not be visible # space above the frame ( must be below 2.0, best between 0.0 and 1.0 ) v_pos = PANDA3D_CONSOLE_VERTICAL_POS # vertical size of the frame ( must be at max 2.0, best between 0.5 and 2.0 ) v_size = PANDA3D_CONSOLE_VERTICAL_SIZE linelength = int((h_size/scale - 5) / fontWidth) print "max number of characters on a length:", linelength numlines = int(v_size/scale - 5) defaultTextColor = (0.0,0.0,0.0,1.0) autoCompleteColor = (0.9,0.9,0.9,1.0) def __init__( self, parent ): self.parent = parent # line wrapper self.linewrap = textwrap.TextWrapper() self.linewrap.width = self.linelength # calculate window size left = (self.h_pos) / self.scale right = (self.h_pos + self.h_size) / self.scale bottom = (self.v_pos) / self.scale top = (self.v_pos + self.v_size) /self.scale # panda3d interface self.consoleFrame = DirectFrame ( relief = DGG.GROOVE , frameColor = (200, 200, 200, 0.5) , scale=self.scale , frameSize = (0, self.h_size / self.scale, 0, self.v_size / self.scale) ) self.windowEvent( base.win ) # try to load the defined font try: fixedWidthFont = loader.loadFont(self.font) except: print "pandaInteractiveConsole.py :: could not load the defined font %s" % str(self.font) fixedWidthFont = DGG.getDefaultFont() # if font is not valid use default font if not fixedWidthFont.isValid(): if self.font is None: print "pandaInteractiveConsole.py :: could not load the defined font %s" % str(self.font) fixedWidthFont = DGG.getDefaultFont() # text entry line self.consoleEntry = DirectEntry ( self.consoleFrame , text = "" , command = self.onEnterPress , width = self.h_size/self.scale - 2 , pos = (1, 0, 1.5) , initialText = "" , numLines = 1 , focus = 1 , entryFont = fixedWidthFont) # output lines self.consoleOutputList = list() for i in xrange( self.numlines ): label = OnscreenText( parent = self.consoleFrame , text = "" , pos = (1, -i+3+self.numlines) , align=TextNode.ALeft , mayChange=1 , scale=1.0 , fg = self.defaultTextColor ) label.setFont( fixedWidthFont ) self.consoleOutputList.append( label ) # list of the last commands of the user self.userCommandList = list() self.userCommandListLength = 100 for i in xrange(self.userCommandListLength): self.userCommandList.append('') self.userCommandPos = 0 # buffer for data self.textBuffer = list() self.textBufferLength = 1000 for i in xrange(self.textBufferLength): self.textBuffer.append(['', DEFAULT_COLOR]) self.textBufferPos = self.textBufferLength-self.numlines # toggle the window at least once to activate the events self.toggleConsole() self.toggleConsole() # call the help-command on start self.onEnterPress("help") # write a string to the panda3d console def write( self, printString, color=defaultTextColor ): # remove not printable characters (which can be input by console input) printString = re.sub( r'[^%s]' % re.escape(string.printable[:95]), "", printString) splitLines = self.linewrap.wrap(printString) for line in splitLines: self.textBuffer.append( [line, color] ) self.textBuffer.pop(0) self.updateOutput() def updateOutput( self ): for lineNumber in xrange(self.numlines): lineText, color = self.textBuffer[lineNumber + self.textBufferPos] self.consoleOutputList[lineNumber].setText( lineText ) self.consoleOutputList[lineNumber]['fg'] = color # toggle the gui console def toggleConsole( self ): self.consoleFrame.toggleVis() hidden = self.consoleFrame.isHidden() self.consoleEntry['focus'] != hidden if hidden: self.ignoreAll() self.accept( self.gui_key, self.toggleConsole ) #playerController.enableInput() #unpause("v") else: self.ignoreAll() #playerController.disableInput() #pause("v") self.accept( CONSOLE_SCROLL_UP_KEY, self.scroll, [-5] ) self.accept( self.scroll_up_repeat_key, self.scroll, [-5] ) self.accept( CONSOLE_SCROLL_DOWN_KEY, self.scroll, [5] ) self.accept( self.scroll_down_repeat_key, self.scroll, [5] ) self.accept( 'window-event', self.windowEvent) self.accept( CONSOLE_PREVIOUS_COMMAND_KEY , self.scrollCmd, [ 1] ) self.accept( CONSOLE_NEXT_COMMAND_KEY, self.scrollCmd, [-1] ) self.accept( self.gui_key, self.toggleConsole ) #self.accept( self.autocomplete_key, self.autocomplete ) #self.accept( self.autohelp_key, self.autohelp ) # accept v, c and x, where c & x copy's the whole console text #messenger.toggleVerbose() #for osx use ('meta') if sys.platform == 'darwin': self.accept( 'meta', self.unfocus ) self.accept( 'meta-up', self.focus ) self.accept( 'meta-c', self.copy ) self.accept( 'meta-x', self.cut ) self.accept( 'meta-v', self.paste ) #for windows use ('control') if sys.platform == 'win32' or sys.platform == 'linux2': self.accept( 'control', self.unfocus ) self.accept( 'control-up', self.focus ) self.accept( 'control-c', self.copy ) self.accept( 'control-x', self.cut ) self.accept( 'control-v', self.paste ) # def autohelp( self ): # currentText = self.consoleEntry.get() # currentPos = self.consoleEntry.guiItem.getCursorPosition() # self.parent.autohelp( currentText, currentPos ) def focus( self ): self.consoleEntry['focus'] = 1 def unfocus( self ): self.consoleEntry['focus'] = 0 def copy( self ): copy = self.consoleEntry.get() clipboard.setText( copy ) def paste( self ): oldCursorPos = self.consoleEntry.guiItem.getCursorPosition() clipboardText = clipboard.getText() # compose new text line oldText = self.consoleEntry.get() newText = oldText[0:oldCursorPos] + clipboardText + oldText[oldCursorPos:] clipboardTextLines = newText.split(os.linesep) for i in xrange( len(clipboardTextLines)-1 ): currentLine = clipboardTextLines[i] # we only want printable characters currentLine = re.sub( r'[^' + re.escape(string.printable[:95]) + ']', "", currentLine) # set new text and position self.consoleEntry.set( currentLine ) self.onEnterPress( currentLine ) currentLine = clipboardTextLines[-1] currentLine = re.sub( r'[^' + re.escape(string.printable[:95]) + ']', "", currentLine) self.consoleEntry.set( currentLine ) self.consoleEntry.setCursorPosition( len(self.consoleEntry.get()) ) self.focus() def cut( self ): clipboard.setText( self.consoleEntry.get() ) self.consoleEntry.enterText('') self.focus() def scroll( self, step ): self.textBufferPos += step self.textBufferPos = min( self.textBufferLength-self.numlines, max( 0, self.textBufferPos ) ) self.updateOutput() def scrollCmd( self, step ): oldCmdPos = self.userCommandPos self.userCommandPos += step self.userCommandPos = min( self.userCommandListLength-1, max( 0, self.userCommandPos ) ) self.userCommandList[oldCmdPos] = self.consoleEntry.get() newCmd = self.userCommandList[self.userCommandPos] self.consoleEntry.set( newCmd ) self.consoleEntry.setCursorPosition( len(newCmd) ) def onEnterPress( self, textEntered ): # set to last message self.textBufferPos = self.textBufferLength-self.numlines # clear line self.consoleEntry.enterText('') self.focus() # add text entered to user command list & remove oldest entry self.userCommandList.insert( 1, textEntered ) self.userCommandList[0] = '' self.userCommandList.pop( -1 ) self.userCommandPos = 0 # call the interpreter to handle the input interpreter = cliClass() result = interpreter.interpreter(textEntered) # write the entered text to the output self.write(textEntered, (0.0, 0.0, 1, 1)) print textEntered # write each line seperately to the output for line in result.split('\n'): line = " " + line self.write(line, (0, 0, 0, 1)) print line def windowEvent( self, window ): """ This is a special callback. It is called when the panda window is modified. """ wp = window.getProperties() width = wp.getXSize() / float(wp.getYSize()) height = wp.getYSize() / float(wp.getXSize()) if width > height: height = 1.0 else: width = 1.0 # aligned to center consolePos = Vec3(-self.h_size/2, 0, -self.v_size/2) # aligned to left bottom #consolePos = Vec3(-width+self.h_pos, 0, -height+self.v_pos) # aligned to right top #consolePos = Vec3(width-self.h_size, 0, height-self.v_size) # set position self.consoleFrame.setPos( consolePos )
class GagSelectionGui(DirectFrame, FSM): InactivityTime = 5.0 AmmoZSelect = -0.15 AmmoZIdle = 0.035 def __init__(self): DirectFrame.__init__(self, parent=aspect2d, pos=(0, 0, 0.93), scale=0.7) FSM.__init__(self, 'GagSelectionGui') self.setTransparency(TransparencyAttrib.MDual) self.tracks = [] self.currentTrack = 0 self.currentGag = None self.fwdShakeIval = None self.revShakeIval = None self.newTrackSound = None self.keyScrollSound = None self.selectSound = None self.selectDenySound = None self.lastActivityTime = 0.0 self.activityTask = None self.midpoint = 0.0 self.ammoFrame = DirectFrame(parent=self, pos=(0, 0, -0.2), image='phase_14/maps/status_bar.png', image_scale=(0.461 * 0.7, 0, 0.098), relief=None) self.ammoFrame.hide() self.ammoTitle = OnscreenText(parent=self.ammoFrame, text='SUPPLY', fg=(0, 0, 0, 0.65), align=TextNode.ALeft, pos=(-0.37 * 0.7, -0.015, 0)) self.ammoText = OnscreenText(parent=self.ammoFrame, text='', fg=(1, 1, 1, 1), shadow=(0, 0, 0, 1), align=TextNode.ARight, pos=(0.37 * 0.7, -0.015, 0)) def update(self): plyr = base.localAvatar if not base.localAvatar.hasAttacks(): return gagId = -1 if self.getCurrentOrNextState() == 'Idle': gagId = plyr.getEquippedAttack() elif self.getCurrentOrNextState() == 'Select' and self.currentGag: gagId = self.currentGag.gagId if gagId != -1: self.ammoFrame.showThrough() if plyr.hasAttackId(gagId): self.ammoText.setText( '%i/%i' % (plyr.getAttackAmmo(gagId), plyr.getAttackMaxAmmo(gagId))) else: self.ammoText.setText('') col = GagGlobals.TrackColorByName[GagGlobals.getTrackOfGag(gagId)] self.ammoFrame['image_color'] = (col[0], col[1], col[2], 1.0) else: self.ammoFrame.hide() def enterOff(self): pass def exitOff(self): pass def hide(self): showAmmo = False if not self.ammoFrame.isHidden(): showAmmo = True DirectFrame.hide(self) if showAmmo: self.ammoFrame.showThrough() def enterSelect(self): base.localAvatar.disableGagKeys() self.ammoFrame.setZ(self.AmmoZSelect) self.show() self.update() self.acceptSelectionClick() self.resetTimeout() self.activityTask = taskMgr.add(self.__activityTask, "activityTask") def acceptSelectionClick(self): self.accept('mouse1-up', self.selectCurrentGag) def ignoreSelectionClick(self): self.ignore('mouse1-up') def resetTimeout(self): self.lastActivityTime = globalClock.getFrameTime() def __activityTask(self, task): time = globalClock.getFrameTime() if time - self.lastActivityTime >= self.InactivityTime: self.request('Idle') return task.done return task.cont def exitSelect(self): self.activityTask.remove() self.activityTask = None self.hide() self.ignoreSelectionClick() def enterIdle(self): self.ammoFrame.setZ(self.AmmoZIdle) self.hide() self.update() if base.localAvatar.avatarMovementEnabled: base.localAvatar.enableGagKeys() def exitIdle(self): pass def disable(self): self.disableControls() self.hide() def enable(self): self.enableControls() self.show() def cleanup(self): self.request('Off') self.disableControls() self.newTrackSound = None self.keyScrollSound = None self.selectSound = None self.selectDenySound = None if self.fwdShakeIval: self.fwdShakeIval.finish() self.fwdShakeIval = None if self.revShakeIval: self.revShakeIval.finish() self.revShakeIval = None self.currentTrack = None self.currentGag = None if self.tracks: for track in self.tracks: track.cleanup() self.tracks = None self.destroy() def __accumulateTracks(self): tracks = [] for gagId in base.localAvatar.attacks.keys(): trackId = GagGlobals.getTrackOfGag(gagId, getId=True) if trackId not in tracks: tracks.append(trackId) tracks.sort() return tracks def load(self): tracks = self.__accumulateTracks() for i in xrange(len(tracks)): track = GagTrack(self, tracks[i]) track.load() track.reparentTo(self) track.setX(FRAME_OFFSET * i) self.tracks.append(track) self.midpoint = (len(self.tracks) / 2.0) * -FRAME_OFFSET # Center the gui horizontally self.setX(self.midpoint) self.ammoFrame.setX(-self.midpoint) if base.config.GetBool('gsg-want-hlsounds', False): self.newTrackSound = base.loadSfx( "phase_14/audio/sfx/wpn_hudon.ogg") self.keyScrollSound = base.loadSfx( 'phase_14/audio/sfx/wpn_moveselect.ogg') self.selectSound = base.loadSfx( 'phase_14/audio/sfx/wpn_select.ogg') self.selectDenySound = base.loadSfx( 'phase_14/audio/sfx/wpn_denyselect.ogg') else: self.newTrackSound = base.loadSfx( "phase_3/audio/sfx/GUI_create_toon_back.ogg") self.keyScrollSound = base.loadSfx( 'phase_3/audio/sfx/GUI_rollover.ogg') self.selectSound = base.loadSfx( 'phase_3/audio/sfx/GUI_create_toon_fwd.ogg') self.selectDenySound = base.loadSfx( 'phase_4/audio/sfx/ring_miss.ogg') self.fwdShakeIval = Effects.createXBounce(self, 1, Vec3(self.midpoint, 0, 0.93), 0.05, 0.05) self.revShakeIval = Effects.createXBounce(self, 1, Vec3(self.midpoint, 0, 0.93), 0.05, -0.05) if base.localAvatar.hasAttacks(): self.updateCurrentTrack(0) def enableControls(self): self.accept('wheel_up', self.__handleScrollUp) self.accept('wheel_down', self.__handleScrollDown) for i in xrange(len(self.tracks)): self.accept(str(i + 1), self.__handleTrackChoose, [i]) self.request('Idle') def selectCurrentGag(self): selected = False self.newTrackSound.stop() self.keyScrollSound.stop() if self.currentGag is not None: if base.localAvatar.getEquippedAttack() == self.currentGag.gagId: selected = True elif (not self.currentGag.locked and self.currentGag.hasAmmo()): gagId = self.currentGag.gagId base.localAvatar.needsToSwitchToGag = gagId if base.localAvatar.gagsTimedOut == False: base.localAvatar.selectGag(gagId) selected = True if not selected: # Denied! self.selectDenySound.play() self.resetTimeout() else: self.selectSound.play() self.request('Idle') def disableControls(self): self.ignore('wheel_up') self.ignore('wheel_down') for i in xrange(len(self.tracks)): self.ignore(str(i + 1)) self.request('Idle') def __maybeDoSelect(self): if self.getCurrentOrNextState() == 'Idle': self.request('Select') def __handleTrackChoose(self, idx): if not base.localAvatar.hasAttacks(): return self.__maybeDoSelect() self.resetTimeout() if self.currentTrack == idx: # Scroll through the current track. self.tracks[self.currentTrack].selectNextGag() self.newTrackSound.stop() self.keyScrollSound.play() else: # Always start from the beginning when using the keys to choose a track. self.updateCurrentTrack(idx, 0) self.newTrackSound.play() def __handleScrollUp(self): if not base.localAvatar.hasAttacks(): return self.__maybeDoSelect() self.resetTimeout() track = self.tracks[self.currentTrack] if track.isOnFirstGag(): self.prevTrack() else: track.selectPrevGag() self.newTrackSound.stop() self.keyScrollSound.play() def __handleScrollDown(self): if not base.localAvatar.hasAttacks(): return self.__maybeDoSelect() self.resetTimeout() track = self.tracks[self.currentTrack] if track.isOnLastGag(): self.nextTrack() else: track.selectNextGag() self.newTrackSound.stop() self.keyScrollSound.play() def nextTrack(self): newIdx = self.currentTrack + 1 if newIdx > len(self.tracks) - 1: newIdx = 0 self.updateCurrentTrack(newIdx) def prevTrack(self): newIdx = self.currentTrack - 1 if newIdx < 0: newIdx = len(self.tracks) - 1 self.updateCurrentTrack(newIdx) def updateCurrentTrack(self, idx, startLoc=None): oldTrack = self.tracks[self.currentTrack] oldTrack.deselectAll() oldTrack.stashContents() if idx - self.currentTrack < 0: direction = 1 else: direction = 0 if startLoc is None: startLoc = direction if direction == 0: self.fwdShakeIval.start() else: self.revShakeIval.start() self.currentTrack = idx # Resort the tracks numTracks = len(self.tracks) maxTrack = numTracks - 1 for i in xrange(len(self.tracks)): track = self.tracks[i] if i == idx: sort = FRAME_FRONT_SORT elif i > idx: sort = FRAME_SORT_BEGIN + (maxTrack - i) * FRAME_SORT_DISTANCE elif i < idx: sort = FRAME_SORT_BEGIN + (i * FRAME_SORT_DISTANCE) track.setBin('gsg-popup', sort) if i == idx: track.unstashContents() if startLoc == 0: track.selectFirstGag() else: track.selectLastGag() else: track.stashContents()
class GuiConsole(DirectObject.DirectObject): def __init__(self, class_parent, parent, h_size, v_size, aspect, hugpos): self.h_size = h_size self.v_size = v_size self.scale = 0.04 self.parent = class_parent self.numlines = int(v_size / self.scale - 2) self.pos_min_x = 0 self.pos_min_y = 0 self.pos_max_x = self.h_size self.pos_max_y = 0.7 if aspect > 0: self.pos_max_x /= aspect else: self.pos_max_y *= aspect self.consoleFrame = DirectFrame(relief=DGG.RIDGE, frameColor=(0, 0, 0, 0), scale=self.scale, frameSize=(0, self.h_size / self.scale, 0, self.v_size / self.scale)) if parent == base.a2dBottomLeft: #@UndefinedVariable self.pos_min_x -= 1 self.pos_min_y -= 1 self.pos_max_x -= 1 self.pos_max_y -= 1 if hugpos == "bottom": self.consoleFrame.setPos(0, 0, GUI_BOTTOM_OFFSET - 0.085) self.pos_min_x = 0 self.pos_min_y = GUI_BOTTOM_OFFSET - 0.085 - 0.07 self.pos_max_x = self.h_size self.pos_max_y = GUI_BOTTOM_OFFSET - 0.085 fixedWidthFont = loader.loadFont(GUI_FONT) #@UndefinedVariable #fixedWidthFont.setPixelsPerUnit(60) #fixedWidthFont.setRenderMode(fixedWidthFont.RMSolid) if not fixedWidthFont.isValid(): print "pandaInteractiveConsole.py :: could not load the defined font %s" % str( self.font) fixedWidthFont = DGG.getDefaultFont() #fixedWidthFont.setPageSize(512,512) #fixedWidthFont.setPixelsPerUnit(60) self.consoleEntry = DirectEntry( self.consoleFrame, text="", command=self.onEnterPress #, width = self.h_size/self.scale -2 , pos=(0.01, 0, 0.02), initialText="Enter text...", numLines=1, focus=0, entryFont=fixedWidthFont, scale=1, frameColor=(0, 0, 0, 0.2), text_fg=(0, 1, 0, 1), text_shadow=(0, 0, 0, 1)) #self.consoleEntry = DirectEntry(self.consoleFrame) self.consoleEntry["frameSize"] = (0, self.h_size / self.scale, 0, 1) self.consoleEntry["width"] = self.h_size / self.scale self.consoleEntry["focusInCommand"] = self.focusInCallback self.consoleEntry["focusOutCommand"] = self.focusOutCallback self.consoleFrame.reparentTo(parent) self.textBuffer = list() self.textBufferLength = 100 for i in xrange(self.textBufferLength): self.textBuffer.append(['', (100, 100, 100, 1)]) self.textBufferPos = self.textBufferLength - self.numlines # output lines self.consoleOutputList = list() for i in xrange(self.numlines): label = OnscreenText(parent=self.consoleFrame, text="", pos=(0, i + 1.5), align=TextNode.ALeft, mayChange=1, scale=1.0, fg=(100, 100, 100, 1), shadow=(0, 0, 0, 1)) # , frame = (200,0,0,1) ) label.setFont(fixedWidthFont) self.consoleOutputList.append(label) self.linelength = 57 self.linewrap = textwrap.TextWrapper() self.linewrap.width = self.linelength self.toggleConsole() def focusInCallback(self): self.parent.parent.camera_manager.disableKeyMovement() def focusOutCallback(self): self.parent.parent.camera_manager.enableKeyMovement() def show(self): if self.consoleFrame.isHidden(): self.consoleFrame.toggleVis() def hide(self): pass #if not self.consoleFrame.isHidden(): # self.consoleFrame.toggleVis() def toggleConsole(self): self.consoleFrame.toggleVis() hidden = self.consoleFrame.isHidden() #self.consoleEntry['focus'] != hidden if hidden: #self.ignoreAll() self.accept('control', self.toggleConsole) self.accept('enter', self.manageFocus) self.accept('escape', self.unfocus) else: #self.ignoreAll() #self.accept( 'page_up', self.scroll, [-5] ) #self.accept( 'page_up-repeat', self.scroll, [-5] ) #self.accept( 'page_down', self.scroll, [5] ) #self.accept( 'page_down-repeat', self.scroll, [5] ) #self.accept( 'window-event', self.windowEvent) #self.accept( 'arrow_up' , self.scrollCmd, [ 1] ) #self.accept( 'arrow_down', self.scrollCmd, [-1] ) self.accept('control', self.toggleConsole) self.accept('enter', self.manageFocus) self.accept('escape', self.unfocus) #self.accept( self.autocomplete_key, self.autocomplete ) #self.accept( self.autohelp_key, self.autohelp ) # accept v, c and x, where c & x copy's the whole console text #messenger.toggleVerbose() #for osx use ('meta') #if sys.platform == 'darwin': # self.accept( 'meta', self.unfocus ) # self.accept( 'meta-up', self.focus ) # self.accept( 'meta-c', self.copy ) # self.accept( 'meta-x', self.cut ) # self.accept( 'meta-v', self.paste ) #for windows use ('control') #if sys.platform == 'win32' or sys.platform == 'linux2': # self.accept( 'control', self.unfocus ) # self.accept( 'control-up', self.focus ) # self.accept( 'control-c', self.copy ) # self.accept( 'control-x', self.cut ) # self.accept( 'control-v', self.paste ) def onEnterPress(self, textEntered): # set to last message self.textBufferPos = self.textBufferLength - self.numlines # clear line self.consoleEntry.enterText('') self.consoleOutput(textEntered, utils.CONSOLE_PLAYER1_TEXT) ClientMsg.chat(textEntered) self.focus() def manageFocus(self): if self.consoleFrame.isHidden(): self.consoleFrame.toggleVis() if self.consoleEntry["focus"] == 0: self.focus() def consoleOutput(self, printString, msgType): if msgType == utils.CONSOLE_SYSTEM_ERROR: self.write(printString, utils.CONSOLE_SYSTEM_ERROR_TEXT_COLOR) elif msgType == utils.CONSOLE_SYSTEM_MESSAGE: self.write(printString, utils.CONSOLE_SYSTEM_MESSAGE_TEXT_COLOR) elif msgType == utils.CONSOLE_PLAYER1_TEXT: self.write(printString, utils.CONSOLE_PLAYER1_TEXT_COLOR) else: self.write(printString, utils.CONSOLE_PLAYER2_TEXT_COLOR) def write(self, printString, color=(100, 100, 100, 0.5)): # remove not printable characters (which can be input by console input) printString = re.sub(r'[^%s]' % re.escape(string.printable[:95]), "", printString) splitLines = self.linewrap.wrap(printString) for line in splitLines: self.textBuffer.append([line, color]) self.textBuffer.pop(0) self.updateOutput() def updateOutput(self): for lineNumber in xrange(self.numlines): lineText, color = self.textBuffer[lineNumber + self.textBufferPos] self.consoleOutputList[self.numlines - lineNumber - 1].setText(lineText) self.consoleOutputList[self.numlines - lineNumber - 1]['fg'] = color def focus(self): self.consoleEntry['focus'] = 1 def unfocus(self): self.consoleEntry['focus'] = 0 def getTightBounds(self): l, r, b, t = self.consoleFrame.getBounds() print l, r, b, t bottom_left = Point3(l, 0, b) top_right = Point3(r, 0, t) return (bottom_left, top_right)
class GuiFrame(DirectObject, HasKeybinds): #should be able to show/hide, do conditional show hide #position where you want #parent to other frames TEXT_MAGIC_NUMBER = .833333333334 #5/6 ?!? DRAW_ORDER = { 'frame': ('unsorted', 0), 'frame_bg': ('unsorted', 0), 'items': ('unsorted', 0), 'title': ('unsorted', 0), 'border': ('unsorted', 0), } def __init__( self, title, shortcut=None, # XXX obsolete, but needs a non deco replacement x=0, y=.1, width=.2, height=.8, #scale = .05, # there is some black magic here :/ bdr_thickness=2, bdr_color=(.1, .1, .1, 1), bg_color=(.7, .7, .7, .5), text_color=(0, 0, 0, 1), text_font=TextNode.getDefaultFont(), #text_h = .05, # do not use directly text_height_mm=4, items=tuple(), ): #item_w_pad = 1 #item_h_pad = 1 self.title = title self.do_xywh(x, y, width, height) self.bdr_thickness = bdr_thickness # FIXME ?? self.bdr_color = bdr_color self.bg_color = bg_color self.text_color = text_color self.text_font = text_font self.text_height_mm = text_height_mm #set up variables self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() self.__ar__ = base.camLens.getAspectRatio() self.__was_dragging__ = False self.__first_item__ = None self.__add_head__ = None self.items = OrderedDict() # ordered dict to allow sequential addition #self.BT = buttonThrower if buttonThrower else base.buttonThrowers[0].node() self.BT = base.buttonThrowers[0].node() # get our aspect ratio, and pixels per mm self.pixels_per_mm = render.getPythonTag('system_data')['max_ppmm'] self.getWindowData() self.accept('window-event', self.getWindowData) #set the text height using the above data self.setTextHeight() # get the root for all frames in the scene self.frameRoot = aspect2d.find('frameRoot') if not self.frameRoot: self.frameRoot = aspect2d.attachNewNode('frameRoot') # create the parent node for this frame #parent = self.frameRoot.find('frame-*') #if not parent: #parent = self.frameRoot self.frame = self.frameRoot.attachNewNode('frame-%s-%s' % (title, id(self))) self.frame.setBin(*self.DRAW_ORDER['frame']) # background l, r, b, t = 0, self.width, 0, self.height self.frame_bg = DirectFrame( parent=self.frame, frameColor=self.bg_color, pos=LVecBase3f(self.x, 0, self.y), frameSize=(l, r, b, t), state=DGG.NORMAL, # FIXME framesize is >_< suppressMouse=1) self.frame_bg.setBin(*self.DRAW_ORDER['frame_bg']) # border self.__make_border__(self.frame_bg, self.bdr_thickness, self.bdr_color, l, r, b, t) # setup for items self.itemsParent = self.frame_bg.attachNewNode('items parent') # title self.title_button = self.__create_item__(title, self.title_toggle_vis) # add any items that we got for item in items: self.__create_item__( *item ) # FIXME when we call frame adjust we will loose the record of any data items # dragging self.title_button.bind(DGG.B1PRESS, self.__startDrag) self.title_button.bind(DGG.B1RELEASE, self.__stopDrag) # raise if we click the frame background self.frame_bg.bind(DGG.B1PRESS, self.raise_) #self.frame_bg.bind(DGG.B1RELEASE, self.__stopDrag) # this can cause problems w/ was dragging # toggle vis if shortcut: self.accept(shortcut, self.toggle_vis) # adjust the frame self.frame_adjust() @property def text_s(self): return self.text_h * self.TEXT_MAGIC_NUMBER def setTextHeight(self): h_units = 2 * base.a2dTop units_per_pixel = h_units / self.__winy__ text_h = self.text_height_mm * self.pixels_per_mm * units_per_pixel self.text_h = text_h def do_xywh(self, x, y, w, h): """ makes negative wneg xidths and heights work as well as negative x and y (bottom right is 0) """ if x < 0: x = 1 + x if y < 0: y = 1 + y if w < 0: x, w = x + w, -w if h < 0: y, h = y + h, -h self.x = self.fix_x(x) # for top left self.y = self.fix_y(y) # for top left self.width = self.fix_w(w) self.height = self.fix_h(h) def getWindowData(self, window=None): x = base.win.getXSize() y = base.win.getYSize() if x != self.__winx__ or y != self.__winy__: self.__ar__ = base.camLens.getAspectRatio() # w/h self.__winx__ = x self.__winy__ = y self.frame_adjust() def raise_(self, *args): """ function that raises windows call FIRST inside any function that should raise """ self.frame.reparentTo( self.frameRoot) # self.frame doesn't move so no wrt def frame_adjust( self ): # FIXME sometimes this fails to call, also calls too often at startup self.setTextHeight() MI = self.getMaxItems() # does not count title >_< LI = len(self.items) DI = MI - LI if DI >= 0: for i in range(DI + 1): self.__create_item__(' blank') else: for i in range(-(DI + 1)): k, v = self.items.popitem() # remove the last nodes in order v.removeNode() # FIXME consider keeping these around? for k, b in self.items.items(): if k == 'title': if self.frame_bg.isHidden(): x, y, z = self.frame_bg.getPos() self.title_button.setPos(LVecBase3f(x, y, z - self.text_h)) else: self.title_button.setPos(LVecBase3f(0, 0, -self.text_h)) elif k == self.__first_item__: b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) else: b.setPos(LVecBase3f(0, 0, -self.text_h)) b['frameSize'] = 0, self.width, 0, self.text_h b['text_scale'] = self.text_s, self.text_s b['text_pos'] = 0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s def getWindowSize(self, event=None): # TODO see if we really need this self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() m = max(self.__winx__, self.__winy__) self.__xscale__ = self.__winx__ / m self.__yscale__ = self.__winy__ / m # put origin in top left and positive down and right @staticmethod def fix_x(x): return (x - .5) * 2 # TODO * base.a2dLeft? @staticmethod def fix_y(y): return (y - .5) * -2 # TODO * base.a2dTop? @staticmethod def fix_w(n): return n * 2 @staticmethod def fix_h(n): return -n * 2 def add_item(self, text, command=None, args=tuple()): args = list(args) if text[0] != ' ': text = ' ' + text items = list(self.items) last_slot = len(self.items) if self.__add_head__ == last_slot: print('all slots are full, cannot add item to %s' % self) return None button = self.items[items[self.__add_head__]] button['text'] = text button['command'] = command button['extraArgs'] = args + button[ 'extraArgs'] # blank buttons always have [self,id] self.__add_head__ += 1 def __create_item__(self, text, command=None, args=tuple()): args = list(args) #if not len(self.items): #parent = self.frame if len(self.items) <= 1: parent = self.itemsParent #everyone else parents off 2nd text else: parent = list(self.items.values())[-1] if command != None: def cmd(*args): """ any item should raise """ self.raise_() command(*args) else: cmd = self.raise_ b = DirectButton( parent=parent, frameColor=(1, 1, 1, .0), # a = 0 => no border overlap frameSize=(0, self.width, 0, self.text_h), text=' ' + text, # hack to keep spacing from border text_font=self.text_font, text_fg=self.text_color, text_scale=self.text_s, text_pos=(0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s), command=cmd, relief=DGG.FLAT, text_align=TextNode.ALeft, ) b.setPos(LVecBase3f(0, 0, -self.text_h)) b.setName('DirectButton-' + text) if not len(self.items): self.items['title'] = b b.setBin(*self.DRAW_ORDER['title']) else: b['extraArgs'] = args + [self, id(b)] b.node().setPythonTag('id', id(b)) b.setBin(*self.DRAW_ORDER['items']) if len(self.items) is 1: # the first item that is not the title b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) self.__first_item__ = id(b) self.items[id(b)] = b if text == ' blank': if self.__add_head__ is None: self.__add_head__ = 1 return b def del_all(self): if self.__first_item__ != None: for id_, button in self.items.items(): if id_ != 'title': button['text'] = ' blank' button['command'] = None button['extraArgs'] = [self, id_] self.__add_head__ = 1 def del_item(self, text): # FIXME uniqueness problems #d = self.itemsParent.find('*%s*'%text) if text[0] != ' ': text = ' ' + text d = [i for i in self.items.values() if i.getName().count(text)] try: self.__del_item__(d[0].getPythonTag('id')) except IndexError: print('that item does not seem to exist') # if we have a name then there shouldn't be key errors def __del_item__(self, index): """ I have no idea how this is going to work """ out = self.items[index] p = out.getParent() if out.getNumChildren(): # avoid the printing of the AssertionError :/ c = out.getChild(0) c.reparentTo(p) if index == self.__first_item__: # XXX is fails, ints from id != c.setPos(LVecBase3f(out.getPos())) id_ = c.getPythonTag('id') self.__first_item__ = id_ out.setPos(LVecBase3f(0, 0, -self.text_h)) self.items.pop(index) parent = list(self.items.values())[-1] out['text'] = ' del blank' #out['command'] = None out['extraArgs'] = [self, index] out.reparentTo(parent) self.items[index] = out if self.__add_head__ > 1: # title is always at 0 self.__add_head__ -= 1 @classmethod def __make_border__(cls, parent, thickness, color, l, r, b, t): moveto_drawto = ( ((l, 0, t), (l, 0, b)), ((r, 0, t), (r, 0, b)), ((l, 0, b), (r, 0, b)), ((l, 0, t), (r, 0, t)), ) for moveto, drawto in moveto_drawto: Border = LineSegs() Border.setThickness(thickness) Border.setColor(*color) Border.moveTo(*moveto) Border.drawTo(*drawto) b = parent.attachNewNode(Border.create()) b.setBin(*cls.DRAW_ORDER['border']) def getMaxItems(self): return int(abs(self.height / self.text_h) - 1) @event_callback def toggle_vis(self): if self.frame_bg.isHidden(): self.frame_bg.show() self.raise_() else: self.frame_bg.hide() def title_toggle_vis(self): if not self.__was_dragging__: self.toggle_vis() if self.frame_bg.isHidden(): self.title_button.wrtReparentTo(self.frame) self.title_button['frameColor'] = (1, 1, 1, .5) # TODO else: self.title_button.wrtReparentTo(self.frame_bg) self.title_button['frameColor'] = (1, 1, 1, 0) # TODO else: self.__was_dragging__ = False def __startDrag(self, crap): self.raise_() self._ox, self._oy = base.mouseWatcherNode.getMouse() taskMgr.add(self.__drag, 'dragging %s' % self.title) self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix('dragging frame') def __drag(self, task): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() if x != self._ox or y != self._oy: m_old = aspect2d.getRelativePoint( render2d, Point3(self._ox, self._oy, 0)) m_new = aspect2d.getRelativePoint(render2d, Point3(x, y, 0)) dx, dy, _ = m_new - m_old self.setPos(self.x + dx, self.y + dy) self._ox = x self._oy = y self.__was_dragging__ = True return task.cont def __stopDrag(self, crap): taskMgr.remove('dragging %s' % self.title) self.BT.setPrefix(self.origBTprefix) def setPos(self, x, y): """ actually sets the title button position since it is really the parent node """ self.x = x self.y = y #- self.text_h # FIXME is hard :/ self.frame_bg.setPos(LVecBase3f(x, 0, y)) if self.frame_bg.isHidden(): self.title_button.setPos(LVecBase3f(x, 0, y - self.text_h)) def __enter__(self): #load the position #load other saved state pass def __exit__(self): #save the position! #save other state pass
class GuiFrame(DirectObject, HasKeybinds): #should be able to show/hide, do conditional show hide #position where you want #parent to other frames TEXT_MAGIC_NUMBER = .833333333334 #5/6 ?!? DRAW_ORDER={ 'frame':('unsorted',0), 'frame_bg':('unsorted', 0), 'items':('unsorted', 0), 'title':('unsorted', 0), 'border':('unsorted', 0), } def __init__(self, title, shortcut = None, # XXX obsolete, but needs a non deco replacement x = 0, y = .1, width = .2, height = .8, #scale = .05, # there is some black magic here :/ bdr_thickness = 2, bdr_color = (.1, .1, .1, 1), bg_color = (.7, .7, .7, .5), text_color = (0, 0, 0, 1), text_font = TextNode.getDefaultFont(), #text_h = .05, # do not use directly text_height_mm = 4, items = tuple(), ): #item_w_pad = 1 #item_h_pad = 1 self.title = title self.do_xywh(x, y, width, height) self.bdr_thickness = bdr_thickness # FIXME ?? self.bdr_color = bdr_color self.bg_color = bg_color self.text_color = text_color self.text_font = text_font self.text_height_mm = text_height_mm #set up variables self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() self.__ar__ = base.camLens.getAspectRatio() self.__was_dragging__ = False self.__first_item__ = None self.__add_head__ = None self.items = OrderedDict() # ordered dict to allow sequential addition #self.BT = buttonThrower if buttonThrower else base.buttonThrowers[0].node() self.BT = base.buttonThrowers[0].node() # get our aspect ratio, and pixels per mm self.pixels_per_mm = render.getPythonTag('system_data')['max_ppmm'] self.getWindowData() self.accept('window-event', self.getWindowData) #set the text height using the above data self.setTextHeight() # get the root for all frames in the scene self.frameRoot = aspect2d.find('frameRoot') if not self.frameRoot: self.frameRoot = aspect2d.attachNewNode('frameRoot') # create the parent node for this frame #parent = self.frameRoot.find('frame-*') #if not parent: #parent = self.frameRoot self.frame = self.frameRoot.attachNewNode('frame-%s-%s'%(title, id(self))) self.frame.setBin(*self.DRAW_ORDER['frame']) # background l,r,b,t = 0, self.width, 0, self.height self.frame_bg = DirectFrame(parent=self.frame, frameColor=self.bg_color, pos=LVecBase3f(self.x, 0, self.y), frameSize=(l,r,b,t), state=DGG.NORMAL, # FIXME framesize is >_< suppressMouse=1) self.frame_bg.setBin(*self.DRAW_ORDER['frame_bg']) # border self.__make_border__(self.frame_bg, self.bdr_thickness, self.bdr_color, l, r, b, t) # setup for items self.itemsParent = self.frame_bg.attachNewNode('items parent') # title self.title_button = self.__create_item__(title, self.title_toggle_vis) # add any items that we got for item in items: self.__create_item__(*item) # FIXME when we call frame adjust we will loose the record of any data items # dragging self.title_button.bind(DGG.B1PRESS, self.__startDrag) self.title_button.bind(DGG.B1RELEASE, self.__stopDrag) # raise if we click the frame background self.frame_bg.bind(DGG.B1PRESS, self.raise_) #self.frame_bg.bind(DGG.B1RELEASE, self.__stopDrag) # this can cause problems w/ was dragging # toggle vis if shortcut: self.accept(shortcut, self.toggle_vis) # adjust the frame self.frame_adjust() @property def text_s(self): return self.text_h * self.TEXT_MAGIC_NUMBER def setTextHeight(self): h_units = 2 * base.a2dTop units_per_pixel = h_units / self.__winy__ text_h = self.text_height_mm * self.pixels_per_mm * units_per_pixel self.text_h = text_h def do_xywh(self, x, y, w, h): """ makes negative wneg xidths and heights work as well as negative x and y (bottom right is 0) """ if x < 0: x = 1 + x if y < 0: y = 1 + y if w < 0: x, w = x + w, -w if h < 0: y, h = y + h, -h self.x = self.fix_x(x) # for top left self.y = self.fix_y(y) # for top left self.width = self.fix_w(w) self.height = self.fix_h(h) def getWindowData(self, window=None): x = base.win.getXSize() y = base.win.getYSize() if x != self.__winx__ or y != self.__winy__: self.__ar__ = base.camLens.getAspectRatio() # w/h self.__winx__ = x self.__winy__ = y self.frame_adjust() def raise_(self, *args): """ function that raises windows call FIRST inside any function that should raise """ self.frame.reparentTo(self.frameRoot) # self.frame doesn't move so no wrt def frame_adjust(self): # FIXME sometimes this fails to call, also calls too often at startup self.setTextHeight() MI = self.getMaxItems() # does not count title >_< LI = len(self.items) DI = MI - LI if DI >= 0: for i in range(DI+1): self.__create_item__(' blank') else: for i in range(-(DI+1)): k,v = self.items.popitem() # remove the last nodes in order v.removeNode() # FIXME consider keeping these around? for k,b in self.items.items(): if k == 'title': if self.frame_bg.isHidden(): x, y, z = self.frame_bg.getPos() self.title_button.setPos(LVecBase3f(x, y , z-self.text_h)) else: self.title_button.setPos(LVecBase3f(0, 0, -self.text_h)) elif k == self.__first_item__: b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) else: b.setPos(LVecBase3f(0, 0, -self.text_h)) b['frameSize'] = 0, self.width, 0, self.text_h b['text_scale'] = self.text_s, self.text_s b['text_pos'] = 0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s def getWindowSize(self, event=None): # TODO see if we really need this self.__winx__ = base.win.getXSize() self.__winy__ = base.win.getYSize() m = max(self.__winx__, self.__winy__) self.__xscale__ = self.__winx__ / m self.__yscale__ = self.__winy__ / m # put origin in top left and positive down and right @staticmethod def fix_x(x): return (x - .5) * 2 # TODO * base.a2dLeft? @staticmethod def fix_y(y): return (y - .5) * -2 # TODO * base.a2dTop? @staticmethod def fix_w(n): return n * 2 @staticmethod def fix_h(n): return -n * 2 def add_item(self, text, command = None, args = tuple()): args = list(args) if text[0] != ' ': text = ' '+text items = list(self.items) last_slot = len(self.items) if self.__add_head__ == last_slot: print('all slots are full, cannot add item to %s'%self) return None button = self.items[items[self.__add_head__]] button['text'] = text button['command'] = command button['extraArgs'] = args + button['extraArgs'] # blank buttons always have [self,id] self.__add_head__ += 1 def __create_item__(self, text, command = None, args = tuple()): args = list(args) #if not len(self.items): #parent = self.frame if len(self.items) <= 1: parent = self.itemsParent #everyone else parents off 2nd text else: parent = list(self.items.values())[-1] if command != None: def cmd(*args): """ any item should raise """ self.raise_() command(*args) else: cmd = self.raise_ b = DirectButton( parent=parent, frameColor=(1,1,1,.0), # a = 0 => no border overlap frameSize=(0, self.width, 0, self.text_h), text=' '+text, # hack to keep spacing from border text_font=self.text_font, text_fg=self.text_color, text_scale=self.text_s, text_pos=(0, self.text_h - self.TEXT_MAGIC_NUMBER * self.text_s), command=cmd, relief=DGG.FLAT, text_align=TextNode.ALeft, ) b.setPos(LVecBase3f(0, 0, -self.text_h)) b.setName('DirectButton-'+text) if not len(self.items): self.items['title'] = b b.setBin(*self.DRAW_ORDER['title']) else: b['extraArgs'] = args+[self, id(b)] b.node().setPythonTag('id', id(b)) b.setBin(*self.DRAW_ORDER['items']) if len(self.items) is 1: # the first item that is not the title b.setPos(LVecBase3f(0, 0, -(self.text_h * 2))) self.__first_item__ = id(b) self.items[id(b)] = b if text == ' blank': if self.__add_head__ is None: self.__add_head__ = 1 return b def del_all(self): if self.__first_item__ != None: for id_, button in self.items.items(): if id_ != 'title': button['text'] = ' blank' button['command'] = None button['extraArgs'] = [self, id_] self.__add_head__ = 1 def del_item(self, text): # FIXME uniqueness problems #d = self.itemsParent.find('*%s*'%text) if text[0] != ' ': text = ' '+text d = [i for i in self.items.values() if i.getName().count(text)] try: self.__del_item__(d[0].getPythonTag('id')) except IndexError: print('that item does not seem to exist') # if we have a name then there shouldn't be key errors def __del_item__(self, index): """ I have no idea how this is going to work """ out = self.items[index] p = out.getParent() if out.getNumChildren(): # avoid the printing of the AssertionError :/ c = out.getChild(0) c.reparentTo(p) if index == self.__first_item__: # XXX is fails, ints from id != c.setPos(LVecBase3f(out.getPos())) id_ = c.getPythonTag('id') self.__first_item__ = id_ out.setPos(LVecBase3f(0, 0, -self.text_h)) self.items.pop(index) parent = list(self.items.values())[-1] out['text'] = ' del blank' #out['command'] = None out['extraArgs'] = [self, index] out.reparentTo(parent) self.items[index] = out if self.__add_head__ > 1: # title is always at 0 self.__add_head__ -= 1 @classmethod def __make_border__(cls, parent, thickness, color, l, r , b, t): moveto_drawto = ( ((l,0,t), (l,0,b)), ((r,0,t), (r,0,b)), ((l,0,b), (r,0,b)), ((l,0,t), (r,0,t)), ) for moveto, drawto in moveto_drawto: Border = LineSegs() Border.setThickness(thickness) Border.setColor(*color) Border.moveTo(*moveto) Border.drawTo(*drawto) b = parent.attachNewNode(Border.create()) b.setBin(*cls.DRAW_ORDER['border']) def getMaxItems(self): return int(abs(self.height / self.text_h) - 1) @event_callback def toggle_vis(self): if self.frame_bg.isHidden(): self.frame_bg.show() self.raise_() else: self.frame_bg.hide() def title_toggle_vis(self): if not self.__was_dragging__: self.toggle_vis() if self.frame_bg.isHidden(): self.title_button.wrtReparentTo(self.frame) self.title_button['frameColor'] = (1, 1, 1, .5) # TODO else: self.title_button.wrtReparentTo(self.frame_bg) self.title_button['frameColor'] = (1, 1, 1, 0) # TODO else: self.__was_dragging__ = False def __startDrag(self, crap): self.raise_() self._ox, self._oy = base.mouseWatcherNode.getMouse() taskMgr.add(self.__drag,'dragging %s'%self.title) self.origBTprefix=self.BT.getPrefix() self.BT.setPrefix('dragging frame') def __drag(self, task): if base.mouseWatcherNode.hasMouse(): x, y = base.mouseWatcherNode.getMouse() if x != self._ox or y != self._oy: m_old = aspect2d.getRelativePoint(render2d, Point3(self._ox, self._oy, 0)) m_new = aspect2d.getRelativePoint(render2d, Point3(x, y, 0)) dx, dy, _ = m_new - m_old self.setPos(self.x + dx, self.y + dy) self._ox = x self._oy = y self.__was_dragging__ = True return task.cont def __stopDrag(self,crap): taskMgr.remove('dragging %s'%self.title) self.BT.setPrefix(self.origBTprefix) def setPos(self, x, y): """ actually sets the title button position since it is really the parent node """ self.x = x self.y = y #- self.text_h # FIXME is hard :/ self.frame_bg.setPos(LVecBase3f(x, 0, y)) if self.frame_bg.isHidden(): self.title_button.setPos(LVecBase3f(x, 0, y - self.text_h)) def __enter__(self): #load the position #load other saved state pass def __exit__(self): #save the position! #save other state pass
class ConsoleWindow(DirectObject.DirectObject): console_output = None gui_key = PANDA3D_CONSOLE_TOGGLE_KEY autocomplete_key = PANDA3D_CONSOLE_AUTOCOMPLETE_KEY autohelp_key = PANDA3D_CONSOLE_AUTOHELP_KEY # change size of text and number of characters on one line # scale of frame (must be small (0.0x) scale = PANDA3D_CONSOLE_SCALE # to define a special font, if loading fails the default font is used # (without warning) font = PANDA3D_CONSOLE_FONT fontWidth = PANDA3D_CONSOLE_FONT_WIDTH # frame position and size (vertical & horizontal) h_pos = PANDA3D_CONSOLE_HORIZONTAL_POS h_size = PANDA3D_CONSOLE_HORIZONTAL_SIZE # v_size + v_pos should not exceed 2.0, else parts of the interface # will not be visible # space above the frame (must be below 2.0, best between 0.0 and 1.0) v_pos = PANDA3D_CONSOLE_VERTICAL_POS # vertical size of the frame (must be at max 2.0, best between 0.5 and 2.0) v_size = PANDA3D_CONSOLE_VERTICAL_SIZE linelength = int((h_size / scale - 5) / fontWidth) textBuffer = list() MAX_BUFFER_LINES = 5000 commandPos = 0 _iconsole = None _commandBuffer = '' logger.debug("max number of characters on a length:", linelength) numlines = int(v_size / scale - 5) defaultTextColor = (0.0, 0.0, 0.0, 1.0) autoCompleteColor = (0.9, 0.9, 0.9, 1.0) consoleFrame = None commandList = [] maxCommandHistory = 10000 textBufferPos = -1 clipboardTextLines = None clipboardTextRaw = None def __init__(self, parent): global base if not logger.isEnabledFor(logging.DEBUG): global CONSOLE_MESSAGE CONSOLE_MESSAGE = ''' ----------------- Ship's Interface version 3.0.9_749 ------------------- Direct Ship Interface Enabled. Please use caution. Irresponsible use of this console may result in the ship's AI refusing access to this interface. type 'help' for basic commands. -------------------------------------------------------------------------''' # change up from parent/IC self.parent = parent localenv = globals() localenv['gameroot'] = self.parent self._iconsole = customConsoleClass(localsEnv=localenv) # line wrapper self.linewrap = textwrap.TextWrapper() self.linewrap.width = self.linelength # calculate window size # left = (self.h_pos) / self.scale # right = (self.h_pos + self.h_size) / self.scale # bottom = (self.v_pos) / self.scale # top = (self.v_pos + self.v_size) /self.scale # panda3d interface self.consoleFrame = DirectFrame(relief=DGG.GROOVE, frameColor=(200, 200, 200, 0.5), scale=self.scale, frameSize=(0, self.h_size / self.scale, 0, self.v_size / self.scale)) self.windowEvent(base.win) fixedWidthFont = None try: # try to load the defined font fixedWidthFont = parent.loader.loadFont(self.font) except Exception: traceback.print_exc() # if font is not valid use default font logger.warn('could not load the defined font %s" % str(self.font') fixedWidthFont = DGG.getDefaultFont() # text lines self._visibleLines = list( OnscreenText(parent=self.consoleFrame, text="", pos=(1, -i + 3 + self.numlines), align=TextNode.ALeft, mayChange=1, scale=1.0, fg=self.defaultTextColor) for i in range(self.numlines)) map(lambda x: x.setFont(fixedWidthFont), self._visibleLines) # text entry line self.consoleEntry = DirectEntry(self.consoleFrame, text="", command=self.submitTrigger, width=self.h_size / self.scale - 2, pos=(1, 0, 1.5), initialText="", numLines=1, focus=1, entryFont=fixedWidthFont) # self.console_output = ConsoleOutput(testme=True) self.echo(CONSOLE_MESSAGE) self.clipboard = pyperclip def windowEvent(self, window): """ This is a special callback. It is called when the panda window is modified. """ wp = window.getProperties() width = wp.getXSize() / float(wp.getYSize()) height = wp.getYSize() / float(wp.getXSize()) if width > height: height = 1.0 else: width = 1.0 # aligned to center consolePos = Vec3(-self.h_size / 2, 0, -self.v_size / 2) # aligned to left bottom # consolePos = Vec3(-width+self.h_pos, 0, -height+self.v_pos) # aligned to right top # consolePos = Vec3(width-self.h_size, 0, height-self.v_size) # set position self.consoleFrame.setPos(consolePos) def submitTrigger(self, cmdtext): # set to last message # clear line self.consoleEntry.enterText('') self.focus() # add text entered to user command list & remove oldest entry self.commandList.append(cmdtext) self.commandPos += 1 self._commandBuffer = '' logger.debug('CP {}'.format(self.commandPos)) # push to interp for text, pre, color in self._iconsole.push(cmdtext): self.echo(text, pre, color) # set up console controls def mapControls(self): hidden = self.consoleFrame.isHidden() self.consoleEntry['focus'] != hidden if hidden: self.ignoreAll() else: self.accept('page_up', self.scroll, [-5]) self.accept('page_up-repeat', self.scroll, [-5]) self.accept('page_down', self.scroll, [5]) self.accept('page_down-repeat', self.scroll, [5]) self.accept('window-event', self.windowEvent) self.accept('arrow_up', self.scrollCmd, [-1]) self.accept('arrow_down', self.scrollCmd, [1]) self.accept(self.gui_key, self.toggleConsole) self.accept('escape', self.toggleConsole) self.accept(self.autocomplete_key, self.autocomplete) self.accept(self.autohelp_key, self.autohelp) # accept v, c and x, where c & x copy's the whole console text # messenger.toggleVerbose() # for osx use ('meta') if sys.platform == 'darwin': self.accept('meta', self.unfocus) self.accept('meta-up', self.focus) self.accept('meta-c', self.copy) self.accept('meta-x', self.cut) self.accept('meta-v', self.paste) # for windows use ('control') if sys.platform == 'win32' or sys.platform == 'linux2': self.accept('control', self.unfocus) self.accept('control-up', self.focus) self.accept('control-c', self.copy) self.accept('control-x', self.cut) self.accept('control-v', self.paste) # toggle the gui console def toggleConsole(self, hide=False): if hide: self.consoleFrame.hide() self.ignoreAll() # express hide, don't call setControls() return if self.consoleFrame.is_hidden(): self.consoleFrame.show() self.parent.setControls(self) else: self.consoleFrame.hide() self.ignoreAll() self.parent.setControls() def scroll(self, step): newpos = self.textBufferPos + step if newpos < 0 or newpos >= len(self.textBuffer): # no... no... I no think so return self.textBufferPos = newpos self.redrawConsole() def redrawConsole(self): windowstart = max(self.textBufferPos - len(self._visibleLines) + 1, 0) windowend = min( len(self._visibleLines) + windowstart, len(self.textBuffer)) logger.debug('windowS: {} WindowE: {}'.format(windowstart, windowend)) for lineNumber, (lineText, color) in \ enumerate(self.textBuffer[ windowstart: windowend]): logger.debug("LN {}, LEN {}".format(lineNumber, len(self.textBuffer))) self._visibleLines[lineNumber].setText(lineText) self._visibleLines[lineNumber]['fg'] = color def scrollCmd(self, step): if not self.commandList: # 0 or null - nothing to scroll return # should we update a temp buffer? if self.commandPos == len(self.commandList): if step > 0: self.consoleEntry.set(self._commandBuffer) return else: tmp = self.consoleEntry.get() if self.commandList[-1] != tmp: self._commandBuffer = tmp self.commandPos += step if self.commandPos >= len(self.commandList): self.commandPos = len(self.commandList) - 1 self.consoleEntry.set(self._commandBuffer) self.consoleEntry.setCursorPosition( len(self.commandList[self.commandPos])) elif self.commandPos < 0: self.commandPos = -1 # No need to change anything, can't go past the beginning return # finally, just set it self.consoleEntry.set(self.commandList[self.commandPos]) self.consoleEntry.setCursorPosition( len(self.commandList[self.commandPos])) def autocomplete(self): currentText = self.consoleEntry.get() currentPos = self.consoleEntry.guiItem.getCursorPosition() newText = self._iconsole.autocomplete(currentText, currentPos) if newText[-1] and newText[-1] != currentText: self.consoleEntry.set(newText[-1]) self.consoleEntry.setCursorPosition(len(newText)) def autohelp(self): currentText = self.consoleEntry.get() currentPos = self.consoleEntry.guiItem.getCursorPosition() self.parent.autohelp(currentText, currentPos) def unfocus(self): self.consoleEntry['focus'] = 0 def focus(self): self.consoleEntry['focus'] = 1 def copy(self): copy = self.consoleEntry.get() pyperclip.copy(copy) def paste(self): oldCursorPos = self.consoleEntry.guiItem.getCursorPosition() self.clipboardTextRaw = pyperclip.paste() # compose new text line oldText = self.consoleEntry.get() newText = oldText[0:oldCursorPos] + self.clipboardTextRaw + oldText[ oldCursorPos:] self.clipboardTextLines = newText.split(os.linesep) for i in range(len(self.clipboardTextLines) - 1): currentLine = self.clipboardTextLines[i] # we only want printable characters currentLine = re.sub( r'[^' + re.escape(string.printable[:95]) + ']', "", currentLine) # set new text and position self.consoleEntry.set(currentLine) self.submitTrigger(currentLine) currentLine = self.clipboardTextLines[-1] currentLine = re.sub(r'[^' + re.escape(string.printable[:95]) + ']', "", currentLine) self.consoleEntry.set(currentLine) self.consoleEntry.setCursorPosition(len(self.consoleEntry.get())) self.focus() def cut(self): pyperclip.copy(self.consoleEntry.get()) self.consoleEntry.enterText('') self.focus() def echo(self, output, pre='*', color=defaultTextColor): if logger.isEnabledFor(logging.DEBUG): logger.debug('output: {}'.format(pprint.pformat(output))) for line in output.split('\n'): fmtline = "{}{}".format(pre, line) logger.debug(fmtline) if len(line) > 0: self.write_to_panel(fmtline, color) def write_to_panel(self, output, color=defaultTextColor): # remove not printable characters (which can be input by console input) output = re.sub(r'[^%s]' % re.escape(string.printable[:95]), "", output) logger.debug('write_to_panel: output="{}"'.format(output)) splitLines = self.linewrap.wrap(output) logger.debug('write_to_panel: splitLines="{}"'.format(splitLines)) for line in splitLines: self.textBuffer.append([line, color]) if len(self.textBuffer) > self.MAX_BUFFER_LINES: self.textBuffer.pop(0) else: self.textBufferPos += 1 self.redrawConsole()
class OnScreenInterface(): # Time users have for each level will be listed here LEVEL_1_TIME = (3,59) LEVEL_2_TIME = (6,59) ''' Constructor takes a reference of the current game, creates a direct frame that everything will be parented to and also sets the sound effect that will be used for aything on the onscreen interface class. @param game - pointer to current game ''' def __init__(self,game): self.previous = -1 self.min = 0 self.sec = 0 self.__game = game self.create_main_frame() self.set_sound() ''' Creates instance variables for all sounds effects that will be used for this instance of the class. Volume and playrate is also set. ''' def set_sound(self): self.intro = base.loader.loadMusic("sfx/not_seems.mp3") self.hover = base.loader.loadSfx("sfx/hover.mp3") self.click = base.loader.loadSfx("sfx/click.wav") self.pause = base.loader.loadSfx("sfx/pause.wav") self.blocked = base.loader.loadSfx("sfx/blocked.wav") self.blocked.setVolume(.05) self.hover.setPlayRate(5) self.hover.setVolume(.05) self.intro.setVolume(.5) self.intro.setLoop(True) ''' Creates instance variable that decides how much time user will have to complete the current level ''' def set_timer(self,level): if level == 'L1': self.min = OnScreenInterface.LEVEL_1_TIME[0] self.sec = OnScreenInterface.LEVEL_1_TIME[1] elif level == 'L2': self.min = OnScreenInterface.LEVEL_2_TIME[0] self.sec = OnScreenInterface.LEVEL_2_TIME[1] ''' Creates the initial two frames that are needed in the game before the gameplay starts ''' def load_initial_interface(self): self.create_player() self.create_start() self.create_stage_selector(button=False) ''' Creates the rest of the frames that are needed once gameplay has started ''' def load_essentials(self): self.create_health_bar() self.create_token_counter() self.create_coordinate_view() self.create_help_menu() self.create_control_guide() self.create_stage_selector() self.create_leaderboard_selector() self.create_leaderboard(self.__game.current_level) #------------------------------------------------------------------------- CREATE METHODS -----------------------------------------------------------------# ''' Creates frame that will be the parent of all the other frames created for this game. This is done so that when all gui interfaces need to be destroyed they can be destroyed with on call to destroy. ''' def create_main_frame(self): self.main_frame = DirectFrame(frameColor = (0,0,0,0),frameSize=(-2,2,-1,1), pos=(0,0,0)) ''' Creates game start frame. This frame is not hidden because it is the first thing the user sees. ''' def create_start(self): # Direct frame to hold contents of start frame self.start_frame = DirectFrame(parent = self.main_frame,frameColor = (0,0,0,1),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Onscreen image of kyklops kyklops = OnscreenImage(parent=self.start_frame,image = 'img/kyklops.png', pos = (.9, 0, .3), scale = (.3,0,.3)) kyklops.setTransparency(TransparencyAttrib.MAlpha) # Onscreen image of game title title = OnscreenImage(parent=self.start_frame,image = 'img/title.png', pos = (0, 0, 0), scale = (.8,0,.3)) title.setTransparency(TransparencyAttrib.MAlpha) # Onscreen image of eve rolling rolling_eve = OnscreenImage(parent=self.start_frame,image = 'img/rolling-eve.png', pos = (-.95, 0, -.1), scale = (.5,0,.5)) rolling_eve.setTransparency(TransparencyAttrib.MAlpha) # Create button to start game self.create_menu_button(self.start_frame,'START',LVecBase3f(0,0,-.5),self.show_input) # Play intro music self.intro.play() ''' Creates a fields so the player can input his/her name at the beginning of the game. This is used to store the name and user score at the end of each stage in the game. ''' def create_player(self): # Direct frame to hold input field self.input_frame = DirectFrame(parent=self.main_frame,frameColor = (0,0,0,1),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Instructions for user instructions = OnscreenText(parent=self.input_frame, text = 'Enter your name: ', pos = (0,.2), scale = 0.1, fg=(0,.2,.2,1),shadow=(1,1,1,.7)) # Name input field self.entry = DirectEntry(parent=self.input_frame,text = "",scale=(.1,1,.08),pos=(-.5,0,0),command=self.set_player,numLines=1,focus=1) # Hide frame self.input_frame.hide() ''' Creates help menu frame with buttons for each option in the menu. This frame is hidden. ''' def create_help_menu(self): # Direct frame to hold buttons and text self.help_frame = DirectFrame(parent=self.main_frame,frameColor = (.6,.6,.6,.7),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Title for frame title = OnscreenText(parent=self.help_frame, text = 'MENU', pos = (0, .4), scale = 0.2, fg=(0,.2,.2,1),shadow=(.5,.5,.5,1) ) # Buttons for menu frame self.create_menu_button(self.help_frame,'Controls',LVecBase3f(0,0,.1),self.show_controls) self.create_menu_button(self.help_frame,'Level Select',LVecBase3f(0,0,-.1),self.show_level_select) self.create_menu_button(self.help_frame,'Leaderboard',LVecBase3f(0,0,-.3),self.show_lb_selection) self.create_menu_button(self.help_frame,'Quit',LVecBase3f(0,0,-.5),sys.exit) # Hide frame self.help_frame.hide() ''' Creates control instruction frame. This frame hidden. ''' def create_control_guide(self): # Direct frame to hold player control instructions self.control_frame = DirectFrame(frameColor = (.9,.9,.9,.9),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Title for frame frame_title = OnscreenText(parent=self.control_frame, text = 'CONTROL KEYS', pos = (0, .5), scale = 0.1, fg=(0,.2,.2,1),shadow=(.5,.5,.5,1)) OnscreenText(parent=self.control_frame, text = 'PLAYER CONTROLS', pos = (-.5, .3), scale = 0.08, fg=(0,0,0,1),shadow=(.5,.5,.5,1)) OnscreenText(parent=self.control_frame, text = 'OTHER CONTROLS', pos = (.5, .3), scale = 0.08, fg=(0,0,0,1),shadow=(.5,.5,.5,1)) # Player control instructions OnscreenText(parent=self.control_frame, text = '[w] - Forward', pos = (-.5, .2), scale = 0.07, fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[a] - Left', pos = (-.5, .1), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[d] - Right', pos = (-.5, 0), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[s] - Back', pos = (-.5, -.1), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[space] - Jump', pos = (-.5, -.2), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[m] - Toggle Modes', pos = (-.5, -.3), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[h] - Help Menu', pos = (.5, .2), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[c] - Toggle Camera Modes', pos = (.5, .1), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[q] - Toggle Music On/Off', pos = (.5, 0), scale = 0.07,fg=(0,0,0,1)) OnscreenText(parent=self.control_frame, text = '[x] - Toggle SFX On/Off', pos = (.5, -.1), scale = 0.07,fg=(0,0,0,1)) # Create button to go back to main menu self.create_menu_button(self.control_frame,'Back',LVecBase3f(0,0,-.7),self.show_menu) # Hide frame self.control_frame.hide() ''' Creates stage selection frame. This frame is hidden. ''' def create_stage_selector(self,button=True): # Direct frame to hold stage selection buttons self.stage_select_frame = DirectFrame(parent=self.main_frame,frameColor = (.8,.8,.8,.9),frameSize=(-2,2,-1,1), pos=(0,0,0)) title = OnscreenText(parent=self.stage_select_frame, text = 'Stage Selection', pos = (0, .7), scale = .15,fg=(0,.2,.2,1)) # Stage select buttons with title for each button t1 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 1', pos = (-.9, .5), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/stage1.png','',LVecBase3f(-.9,0,.2),self.__game.clean_and_set,'L1') # Stage 2 will be unlocked when stage 2 is made t2 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 2', pos = (-.3, .5), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/stage2.png','',LVecBase3f(-.3,0,.2),self.__game.clean_and_set,'L2') t3 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 3', pos = (.3, .5), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(.3,0,.2),self.__game.clean_and_set,'L3') t4 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 4', pos = (.9, .5), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(.9,0,.2),self.__game.clean_and_set,'L4') t5 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 5', pos = (-.9, -.1), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(-.9,0,-.4),self.__game.clean_and_set,'L5') t6 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 6', pos = (-.3, -.1), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(-.3,0,-.4),self.__game.clean_and_set,'L6') t7 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 7', pos = (.3, -.1), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(.3,0,-.4),self.__game.clean_and_set,'L7') t8 = OnscreenText(parent=self.stage_select_frame, text = 'STAGE 8', pos = (.9, -.1), scale = 0.07,fg=(0,.2,.2,1)) self.create_stage_button(self.stage_select_frame,'img/locked.jpg','',LVecBase3f(.9,0,-.4),self.__game.clean_and_set,'L8') if button is True: # Create button to go back to main menu self.create_menu_button(self.stage_select_frame,'Back',LVecBase3f(0,0,-.8),self.show_menu) # Hide frame self.stage_select_frame.hide() ''' Creates leaderboard selection frame which will contain links to the leaderboard for the different game stages. This frame is hidden. ''' def create_leaderboard_selector(self): # Direct frame to hold links to the leaderboards for different levels self.leaderboard_selection_frame = DirectFrame(parent=self.main_frame,frameColor = (.8,.8,.8,.9),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Frame title title = OnscreenText(parent=self.leaderboard_selection_frame, text = 'LEADERBOARD SELECTION', pos = (0, .6), scale = 0.15, fg=(0,.2,.2,1),shadow=(.5,.5,.5,1)) # Links to leaderboards for the stages that are currently made self.link(self.leaderboard_selection_frame,'STAGE 1',LVecBase3f(0,0,.0),self.show_leaderboard,'L1') self.link(self.leaderboard_selection_frame,'STAGE 2',LVecBase3f(0,0,-.1),self.show_leaderboard,'L2') # Create button to go back to main menu self.create_menu_button(self.leaderboard_selection_frame,'Back',LVecBase3f(0,0,-.4),self.show_menu) # Hide frame self.leaderboard_selection_frame.hide() ''' Leaderboard is created based on the level that was clicked on the leaderboard selector frame. This information is gathered from the stored information in .leaderboard.txt. This method will look for the correct section in the file and output the information for the stage choosen onto a direct frame. ''' def create_leaderboard(self,level): # Direct frame to hold all contents of the leaderboard self.leaderboard_frame = DirectFrame(parent=self.main_frame,frameColor = (.8,.8,.8,.9),frameSize=(-2,2,-1,1), pos=(0,0,0)) # Create a scroll_frame to hold contents of leaderboard file scroll_frame = DirectScrolledFrame(parent = self.leaderboard_frame, canvasSize=(-1,1,-4,4), frameColor = (1,1,1,.9), frameSize=(-1,1,-.5,.5), pos=(0,0,0), manageScrollBars=True, scrollBarWidth = .04, autoHideScrollBars = True) # Frame title title = OnscreenText(parent=self.leaderboard_frame, text = 'LEADERBOARD', pos = (0, .6), scale = 0.15, fg=(0,.2,.2,1),shadow=(.5,.5,.5,1)) # Open .leaderboard.txt file as read only leaderboard_file = open('files/.leaderboard.txt','r') start_read = False # Boolean that will used to notify loop when to start generating text on the scroll canvas start = leaderboard_file.read(1) # Reads only the first byte of the file, equivalent to a character # Guide for leaderboard name = OnscreenText(parent=scroll_frame.getCanvas(), text = 'NAME', pos = (-.1, 3.9), scale = 0.075,fg=(0,.2,.2,1)) score = OnscreenText(parent=scroll_frame.getCanvas(), text = 'SCORE', pos = (.5, 3.9), scale = 0.075,fg=(0,.2,.2,1)) index = 1 v_pos = 3.8 # Loop will iterate through the contents of the file while len(start) != 0: if start == '#': leaderboard_file.readline() # Comments in text file start with an octothorpe and are ignored elif start == '@'and start_read is False: # Line with @ signify the beginning of a set of level info if leaderboard_file.readline().split()[0] == level: # This checks if the info that follows is the information we need start_read = True elif start == '@'and start_read is True: # If this condition is true then we read through all the information we need break elif start_read is True: coord = leaderboard_file.readline().split(',') if len(coord) == 1: name = 'Unknown' score = coord[0] else: name = start + coord[0] score = coord[1] entry_text = str(index) + ".\t " + name + '\t\t' + score # Create onscreen text for each entry entry = OnscreenText(parent=scroll_frame.getCanvas(), text = entry_text, pos = (0,v_pos), scale = 0.07,fg=(0,0,0,1)) # Increase index and decrease vertical position index += 1 v_pos -= .1 # Read the first byte of the next line start = leaderboard_file.read(1) leaderboard_file.close() # Create button to go back to the leaderboard selection frame self.create_menu_button(self.leaderboard_frame,'Back',LVecBase3f(0,0,-.7),self.show_lb_selection) # Hide frame self.leaderboard_frame.hide() ''' Creates a frame in the top of the window that shows player health and timer. Frame is hidden. ''' def create_health_bar(self): # Direct frame that holds all contents such as timer and health bar self.health_frame = DirectFrame(parent=self.main_frame,frameColor=(0, 0, 0, .4),frameSize=(-2, 3, -.1, 1),pos=(-.6, 0, .9)) # Create a direct wait bar that will be used as the health gauge. self.bar = DirectWaitBar(parent=self.health_frame, text = "HEALTH", text_fg=(1,1,1,1), value = 100, range=100, pos = (.15,0,-.02), barColor=VBase4(0,.2,.2,1), scale=.6) # Onscreen image for eve face icon on health gauge eve_icon = OnscreenImage(parent=self.bar,image = 'img/eve_face.png', pos = (-1.15, 0, -.075), scale = (.25,1,.25)) eve_icon.setTransparency(TransparencyAttrib.MAlpha) # Create a node for timer timer_txt = str(self.min) + ' min ' + str(self.sec) + ' seconds' self.timer = OnscreenText(parent=self.health_frame, text = timer_txt, pos = (1.5, -.02), scale = 0.07, fg=(1,1,1,1)) # Hide frame self.health_frame.hide() ''' Creates an a token counter in the right bottom corner of screen. This image is hidden. ''' def create_token_counter(self): # Set onscreen image and set transparency self.shape = OnscreenImage(image = 'img/tire_score.png', pos = (1, 0, -.85), scale = (.3,1,.1)) self.shape.setTransparency(TransparencyAttrib.MAlpha) # Set another onscreen image and set transparency tire = OnscreenImage(parent=self.shape,image = 'img/tire.png', pos = (-1, 0, 0), scale = (.4,1,1)) tire.setTransparency(TransparencyAttrib.MAlpha) # Set text displaying the number of token collected self.score = OnscreenText(parent=self.shape, text = str(self.__game.eve.tiresCollected), pos = (0, -.1), scale = (.3,.8), fg=(255,255,255,1)) # Hide token counter self.shape.hide() ''' Creates a frame that displays the name of the stage at the beginning of every stage in the game. Only level 1 and 2 are set. ''' def create_stage_title(self,level): s_text = "" t_text = "" # The following if statement set the text that will be displayed if level == 'L1': s_text = "Stage 1:" t_text = "The Journey Begins" elif level == 'L2': s_text = "Stage 2:" t_text = "The Dark Place" # Direct frame to hold stage title self.stage_frame = DirectFrame(parent=self.main_frame,frameColor=(0, 0, 0, .6),frameSize=(-2, 3, -.1, .4),pos=(0, 0, 0)) stage_name = OnscreenText(parent=self.stage_frame, text = s_text, pos = (0, .2), scale = 0.16, fg=(1,.84,0,1),shadow=(1,1,1,.2)) stage_title = OnscreenText(parent=self.stage_frame, text = t_text, pos = (0, 0), scale = 0.12, fg=(1,.84,0,1),shadow=(1,1,1,.2)) ''' Creates onscreen text showing current position of the character ''' def create_coordinate_view(self): self.coord = OnscreenText(parent=self.main_frame,text='1', style = 1, fg= (1,1,1,1), pos=(0,-0.95), align=TextNode.A_right, scale=0.08) self.coord.hide() #------------------------------------------------------------------------- SHOW METHODS -----------------------------------------------------------------# ''' Shows input frame so user can enter his/her name. ''' def show_input(self): self.__game.accept('h', self.do_nothing) self.start_frame.destroy() self.input_frame.show() ''' Shows menu frame and hides anything else that may be showing ''' def show_menu(self): if self.control_frame.isHidden() is False: self.control_frame.hide() elif self.leaderboard_selection_frame.isHidden() is False: self.leaderboard_selection_frame.hide() elif self.stage_select_frame.isHidden() is False: self.stage_select_frame.hide() self.__game.accept('h', self.toggleHelp) self.help_frame.show() ''' Shows frame that contains the game instructions ''' def show_controls(self): self.__game.accept('h', self.do_nothing) self.help_frame.hide() self.control_frame.show() ''' Shows stage select frame ''' def show_level_select(self): self.__game.accept('h', self.do_nothing) self.help_frame.hide() self.stage_select_frame.show() ''' Shows leaderboard selector frame ''' def show_lb_selection(self): if self.leaderboard_frame.isHidden() is False: self.leaderboard_frame.hide() elif self.help_frame.isHidden() is False: self.__game.accept('h', self.do_nothing) self.help_frame.hide() self.leaderboard_selection_frame.show() ''' Shows leaderboard frame for a specific level @param level - stage whose leaderboard will be displayed ''' def show_leaderboard(self,level): self.leaderboard_selection_frame.hide() self.create_leaderboard(level) self.leaderboard_frame.show() ''' Shows in game stats, which is health gauge, timer and token counter ''' def show_game_interface(self): self.health_frame.show() self.shape.show() ''' Hides the game interface. This is used when the help menu us shown. ''' def hide_game_interface(self): self.health_frame.hide() self.shape.hide() #------------------------------------------------------------------------- CREATE BTN METHODS -----------------------------------------------------------------# ''' Creates a button with an= simple button image as the background. This is the default btn. @param parent - who the button will be parented to @param btn_text - text that will be displayed on button @param btn_pos - position of the button @param cmd - command that will be executed when button is clicked @param level - each link created is based on a stage, this is the stage this btn represents ''' def create_menu_button(self,parent,btn_text,btn_pos,cmd): start_btn = DirectButton(parent=parent, text=btn_text, pos=btn_pos, scale=(.2,1,.15), command=cmd, pressEffect=1, text_scale=(.4,.4), text_pos=(.1,.1), text_fg=(.1,.1,.1,1), text_shadow=(1,1,1,1), image='img/btn2.png', image_scale=(2.50,1,.7), image_pos=(0,1,.25), relief=None, rolloverSound = self.hover, clickSound=self.click) start_btn.setTransparency(TransparencyAttrib.MAlpha) ''' Creates a button that contains an image of a stage or if it was not yet created it contains an image of a lock @param parent - who the button will be parented to @param btn_text - text that will be displayed on button @param btn_pos - position of the button @param cmd - command that will be executed when button is clicked @param level - each link created is based on a stage, this is the stage this btn represents ''' def create_stage_button(self,parent,img,btn_text,btn_pos,cmd,level): click_sound = self.blocked hover_sound = self.blocked # This if statement sets the sound that each button will have. # Everything that is not level 1 or level 2 will have the blocked sound effect and will do nothing if level == 'L1' or level == 'L2': click_sound = self.click hover_sound = self.hover btn = DirectButton(parent=parent, text=btn_text, pos=btn_pos, scale=(.2,1,.15), command=cmd, pressEffect=1, text_scale=(.4,.4), text_pos=(.1,.1), text_fg=(.1,.1,.1,1), text_shadow=(1,1,1,1), image=img, image_scale=(1,1,1), image_pos=(0,1,.25), relief=None, rolloverSound = hover_sound, clickSound=click_sound, extraArgs=[level]) btn.setTransparency(TransparencyAttrib.MAlpha) ''' Creates a button with no relief and no background image or color. @param parent - who the button will be parented to @param btn_text - text that will be displayed on button @param btn_pos - position of the button @param cmd - command that will be executed when button is clicked @param level - each link created is based on a stage, this is the stage this btn represents ''' def link(self,parent,btn_text,btn_pos,cmd,level): btn = DirectButton(parent=parent, text=btn_text, pos=btn_pos, scale=(.2,1,.15), command=cmd, pressEffect=1, text_scale=(.4,.4), text_pos=(.1,.1), text_fg=(.1,.1,.1,1), relief=None, rolloverSound = self.hover, clickSound=self.click, extraArgs = [level]) def link2(self,parent,btn_text,btn_pos,cmd): btn = DirectButton(parent=parent, text=btn_text, pos=btn_pos, scale=(.2,1,.15), command=cmd, pressEffect=1, text_scale=(.4,.4), text_pos=(.1,.1), text_fg=(.1,.1,.1,1), relief=None, rolloverSound = self.hover, clickSound=self.click) #------------------------------------------------------------------------- TASK METHODS -----------------------------------------------------------------# ''' At the beginning of every stage the title is show. This task only runs for the first 2 seconds of each stage. It shows the stage title for 2 seconds and then destroys the frame containing the title and the task ends. ''' def show_title(self,task): if globalClock.getRealTime() - self.__game.actual_start > 2: # Wait two seconds self.stage_frame.destroy() self.show_game_interface() self.__game.eve.enable_character_controls() # Enable character controls only after the two seconds have passed self.__game.accept('h', self.toggleHelp) self.__game.accept('f1', self.__game.toggleDebug) return Task.done return Task.cont ''' This task runs continuously throughout each stage. It updates the time remaining in the current level. This task ends when the minutes and seconds reach 0. ''' def update_timer(self,task): elapsed_time = globalClock.getRealTime() - self.__game.actual_start change_time = int(elapsed_time) % 60 if change_time == 0 and self.previous != int(elapsed_time): self.min = self.min - int(elapsed_time) / 60 self.sec = 59 else: if self.previous != int(elapsed_time): self.sec = 59 - change_time self.timer['text'] = str(self.min) + ' min ' + str(self.sec) + ' seconds' self.previous = int(elapsed_time) if self.min == 0 and self.sec == 0: self.__game.game_over = True return Task.done return Task.cont ''' In debug mode the user can view the players current position in the left bottom corner. This task takes care of updating that position onscreen text. ''' def updateCoord(self, task): x = self.__game.eve.currentNP.getX() y = self.__game.eve.currentNP.getY() z = self.__game.eve.currentNP.getZ() self.coord.setText(str(x) + " , " + (str(y)) + " , " + str(z)) return Task.cont def game_over(self): self.game_over_frame = DirectFrame(parent = self.main_frame,frameColor=(1, 1, 1, .7),frameSize=(-2, 2, 1, -1),pos=(0, 0, 0)) OnscreenText(parent = self.game_over_frame,text = 'GAME OVER', pos = (0, 0), scale = .1, fg=(1,0,0,1)) OnscreenText(parent = self.game_over_frame,text = 'RETRY?', pos = (0, -.2), scale = .07, fg=(1,0,0,1)) self.link(self.game_over_frame,'YES',LVecBase3f(.3,0,-.3),self.__game.clean_and_set,self.__game.current_level) self.link2(self.game_over_frame,'NO',LVecBase3f(-.3,0,-.3),sys.exit) def level_passed(self): self.level_passed_frame = DirectFrame(parent = self.main_frame,frameColor=(1, 1, 1, .9),frameSize=(-2, 2, 1, -1),pos=(0, 0, 0)) if self.__game.current_level == 'L1': t1 = OnscreenText(parent = self.level_passed_frame,text = 'STAGE 1 COMPLETE', pos = (0, 0), scale = .1, fg=(1,0,0,1)) elif self.__game.current_level == 'L2': t1 = OnscreenText(parent = self.level_passed_frame,text = 'STAGE 2 COMPLETE', pos = (0, 0), scale = .1, fg=(1,0,0,1)) t2 = OnscreenText(parent = self.level_passed_frame,text = 'Wheels Collected : ' + str(self.__game.eve.tiresCollected) + ' / ' + str(self.__game.e.total_tokens) , pos = (0, -.2), scale = .1, fg=(1,0,0,1)) t3 = OnscreenText(parent = self.level_passed_frame,text = 'Score : ' + str(self.__game.user.score) , pos = (0, -.3), scale = .1, fg=(1,0,0,1)) OnscreenText(parent = self.level_passed_frame,text = 'CONTINUE?', pos = (0, -.5), scale = .07, fg=(1,0,0,1)) if self.__game.current_level == 'L1': self.link(self.level_passed_frame,'YES',LVecBase3f(.3,0,-.6),self.__game.clean_and_set,'L2') else: self.link(self.level_passed_frame,'YES',LVecBase3f(.3,0,-.6),self.__game.clean_and_set,'L1') self.link2(self.level_passed_frame,'NO',LVecBase3f(-.3,0,-.6),sys.exit) ''' After user inputs his/her name in the beginning of the game, this method is execute and a new user is instantiated and the game is setup. Aside from this two things are added to the task manager...the timer and the stage title. ''' def set_player(self,entry): print '\tWELCOME ' + entry + ' ...' print '\tSETTING UP USER ...' self.input_frame.destroy() # Destroy the input frame self.__game.user = User(entry) # Frame title self.stage_select_frame.show() ''' Method used to toggle between the help menu and the game. ''' def toggleHelp(self): if self.help_frame.isHidden(): # Stop update task and disable character controls when help is activated self.pause.play() self.__game.taskMgr.remove('update') self.hide_game_interface() self.__game.eve.disable_character_controls() self.help_frame.show() else: # Restart update task and enable character controls when help is deactivated self.pause.play() self.__game.taskMgr.add(self.__game.update,'update') # Add task to task manager self.show_game_interface() self.__game.eve.enable_character_controls() self.help_frame.hide() ''' Method does nothing but is important to stop the h key from working when help menu is in a sub menu or frame ''' def do_nothing(self): pass
class Menu(UIItem): def __init__(self, list=[]): self.__menuContainer = DirectFrame( #text_pos=(0,1), pos=(0, 0, 0), text_scale=1.5, text_fg=(0, 0, 0, 0), relief=DGG.FLAT, frameColor=(1, 1, 1, 0.5)) self.__menuContainer.hide() self.__menuItems = [] self.selectedItem = 0 self.addItems(list) def addItem(self, text, action): self.__menuItems.append( DirectButton( text=text, command=self.pressEnter, extraArgs=[len(self.__menuItems)], text_align=TextNode.ALeft, scale=0.1, #left/right, forward/back, up/down pos=Vec3(-0.38, 0, 0.5 - (len(self.__menuItems) * 0.15)), text_fg=(1, 1, 1, 1), rolloverSound=None, clickSound=None, pressEffect=0, relief=None, textMayChange=True #text_font=base.fontLoader.load('Arial Bold.ttf') )) self.__menuItems[-1].reparentTo(self.__menuContainer) self.__menuItems[-1].setPythonTag('action', action) self.__menuItems[-1].setPythonTag('text', text) def addItems(self, list): for k, v in list: self.addItem(k, v) def hasItem(self, text): for i in self.__menuItems: if (i['text'] == text): return True return False def __del__(self): self.ignoreAll() def pressEnter(self, item=None): if (item != None): self.selectedItem = item logging.info("pressEnter:: selectedItem = %s" % str(self.selectedItem)) action = self.__menuItems[self.selectedItem].getPythonTag('action') text = self.__menuItems[self.selectedItem].getPythonTag('text') logging.info("pressEnter:: action = %s" % str(action)) if (action != None): #function #self.hide() action(text) #call the function in 'action' def enter(self): """ Press the enter key to select the current menu item. """ self.pressEnter() UIItem.enter(self) def up(self): """Move one item up in the menu.""" newItem = self.selectedItem - 1 if (newItem < 0): newItem = len(self.__menuItems) - 1 self.select(newItem) UIItem.up(self) def down(self): """Move one item down in the menu.""" newItem = self.selectedItem + 1 if (newItem >= len(self.__menuItems)): newItem = 0 self.select(newItem) UIItem.down(self) def select(self, item): self.__menuItems[self.selectedItem]['text_fg'] = (1, 1, 1, 1) self.__menuItems[self.selectedItem]['text_bg'] = (0, 0, 0, 0) self.selectedItem = item self.__menuItems[self.selectedItem]['text_fg'] = (0, 0, 0.5, 1) self.__menuItems[self.selectedItem]['text_bg'] = (1, 1, 1, 1) def show(self): assert len(self.__menuItems) > 0 self.select(0) #make the first item selected if (self.__menuContainer.isHidden()): Sequence( Func(self.__menuContainer.setAlphaScale, 0.0), Func(self.__menuContainer.show), LerpFunctionInterval(self.__menuContainer.setAlphaScale, toData=1.0, fromData=0.0, duration=1.0)).start() def hide(self): self.ignoreAll() if (not self.__menuContainer.isHidden()): Sequence( LerpFunctionInterval(self.__menuContainer.setAlphaScale, toData=0.0, fromData=1.0, duration=1.0), Func(self.__menuContainer.hide), Func(self.__menuContainer.setAlphaScale, 1.0)).start() #now, hide all dialogs referred to: for i in self.__menuItems: a = i.getPythonTag('action') if (isinstance(a, Menu)): a.hide() def setParent(self, parent): self.__menuContainer.reparentTo(parent) def setPos(self, x, y, z): self.__menuContainer.setPos(x, y, z)
class NameValueList(UIItem): """ A NameValueList is a list of name and value pairs. This is commonly used in the settings screen where the user has a list of items and can edit them at any point in time. This is just a generic UI element to keep all of the data and logic within the game subsystem """ def __init__(self, items=[], lines_to_show=5): # items is a tuple consisting of (text name of item, selection values, current selection) self._items = [] self._lines_to_show = lines_to_show self.__menuContainer = DirectFrame( #text_pos=(0,1), pos=(-0.6, 0, 0.3), text_scale=1.5, text_align=TextNode.ALeft, text_fg=(0, 0, 0, 0), relief=DGG.FLAT, frameColor=(1, 1, 1, 0), #left,right,bottom,top frameSize=(-0.5, 0.5, -0.5, 0.5)) self.__menuContainer.hide() self.selectedItem = 0 self.editing = False self.blink = False self.blinkTask = None for item in items: self.addItem(item[0], item[1], item[2]) def setPos(self, x, y, z): self.__menuContainer.setPos(x, y, z) def addItem(self, text_name, selection_values, current_selection_idx=-1): """ Adds an item to the list display. text_name - The string representation of the list item selection_values - A list of available values for selection in this field current_selection - The currently selected item """ pos_name = Vec3(-0.47, 0, 0.4 - (len(self._items) * 0.15)) pos_value = Vec3(1.0, 0, 0.4 - (len(self._items) * 0.15)) length = len(self._items) selection_text = "" if current_selection_idx != -1: selection_text = str(selection_values[current_selection_idx]) name_display = DirectButton( text=text_name, command=None, extraArgs=[len(self._items)], text_align=TextNode.ALeft, scale=0.1, #left/right, forward/back, up/down pos=pos_name, text_fg=(1, 1, 1, 1), rolloverSound=None, clickSound=None, pressEffect=0, relief=None, textMayChange=True #text_font=base.fontLoader.load('Arial Bold.ttf') ) name_display.reparentTo(self.__menuContainer) value_display = DirectButton( text=str(selection_text), command=None, extraArgs=[len(self._items)], text_align=TextNode.ALeft, scale=0.1, #left/right, forward/back, up/down pos=pos_value, text_fg=(1, 1, 1, 1), rolloverSound=None, clickSound=None, pressEffect=0, relief=None, textMayChange=True #text_font=base.fontLoader.load('Arial Bold.ttf') ) value_display.reparentTo(self.__menuContainer) v = {} v['name'] = text_name v['name_display'] = name_display v['value_display'] = value_display v['selection_values'] = selection_values v['current_selection_idx'] = current_selection_idx self._items.append(v) def up(self): """Move one item up in the menu.""" if self.editing: current_item = self._items[self.selectedItem] current_item['current_selection_idx'] = ( current_item['current_selection_idx'] + 1) % len( current_item['selection_values']) current_item['value_display']['text'] = current_item[ 'selection_values'][current_item['current_selection_idx']] else: newItem = self.selectedItem - 1 if (newItem < 0): newItem = len(self._items) - 1 self.select(newItem) UIItem.up(self) def down(self): """Move one item down in the menu.""" if self.editing: current_item = self._items[self.selectedItem] current_item['current_selection_idx'] = ( current_item['current_selection_idx'] - 1) % len( current_item['selection_values']) current_item['value_display']['text'] = current_item[ 'selection_values'][current_item['current_selection_idx']] else: newItem = self.selectedItem + 1 if (newItem >= len(self._items)): newItem = 0 self.select(newItem) UIItem.down(self) def show(self): if (self.__menuContainer.isHidden()): Sequence( Func(self.__menuContainer.setAlphaScale, 0.0), Func(self.__menuContainer.show), LerpFunctionInterval(self.__menuContainer.setAlphaScale, toData=1.0, fromData=0.0, duration=1.0)).start() def hide(self): self.ignoreAll() if (not self.__menuContainer.isHidden()): Sequence( LerpFunctionInterval(self.__menuContainer.setAlphaScale, toData=0.0, fromData=1.0, duration=1.0), Func(self.__menuContainer.hide), Func(self.__menuContainer.setAlphaScale, 1.0)).start() def setParent(self, parent): self.__menuContainer.reparentTo(parent) def select(self, item): self._items[self.selectedItem]['name_display']['text_fg'] = (1, 1, 1, 1) self._items[self.selectedItem]['name_display']['text_bg'] = (0, 0, 0, 0) self._items[self.selectedItem]['value_display']['text_fg'] = (1, 1, 1, 1) self._items[self.selectedItem]['value_display']['text_bg'] = (0, 0, 0, 0) self.selectedItem = item self._items[self.selectedItem]['name_display']['text_fg'] = (0, 0, 0.5, 1) self._items[self.selectedItem]['name_display']['text_bg'] = (1, 1, 1, 1) self._items[self.selectedItem]['value_display']['text_fg'] = (0, 0, 0.5, 1) self._items[self.selectedItem]['value_display']['text_bg'] = (1, 1, 1, 1) def toggleEdit(self): self.editing = not self.editing if self.editing: # Start blinking self.blinkTask = base.taskMgr.doMethodLater( 0.4, self._doBlink, 'nvBlinkTask') else: # Stop blinking if self.blinkTask != None: base.taskMgr.remove(self.blinkTask) def _doBlink(self, task): self.blink = not self.blink if self.blink: self._items[self.selectedItem]['value_display']['text_fg'] = (0, 0, 0, 1) self._items[self.selectedItem]['value_display']['text_bg'] = (1, 1, 1, 1) else: self._items[self.selectedItem]['value_display']['text_fg'] = (1, 1, 1, 1) self._items[self.selectedItem]['value_display']['text_bg'] = (0, 0, 0, 0) return Task.again
class panda3dIOClass(DirectObject.DirectObject): # set gui key to None if you want to call toggleConsole from outside this class gui_key = PANDA3D_CONSOLE_TOGGLE_KEY autocomplete_key = PANDA3D_CONSOLE_AUTOCOMPLETE_KEY autohelp_key = PANDA3d_CONSOLE_AUTOHELP_KEY # change size of text and number of characters on one line # scale of frame ( must be small (0.0x) scale = PANDA3D_CONSOLE_SCALE # to define a special font, if loading fails the default font is used (without warning) font = PANDA3D_CONSOLE_FONT fontWidth = PANDA3D_CONSOLE_FONT_WIDTH # frame position and size (vertical & horizontal) h_pos = PANDA3D_CONSOLE_HORIZONTAL_POS h_size = PANDA3D_CONSOLE_HORIZONTAL_SIZE # v_size + v_pos should not exceed 2.0, else parts of the interface will not be visible # space above the frame ( must be below 2.0, best between 0.0 and 1.0 ) v_pos = PANDA3D_CONSOLE_VERTICAL_POS # vertical size of the frame ( must be at max 2.0, best between 0.5 and 2.0 ) v_size = PANDA3D_CONSOLE_VERTICAL_SIZE linelength = int((h_size / scale - 5) / fontWidth) print "max number of characters on a length:", linelength numlines = int(v_size / scale - 5) defaultTextColor = (0.0, 0.0, 0.0, 1.0) autoCompleteColor = (0.9, 0.9, 0.9, 1.0) def __init__(self, parent): self.parent = parent # line wrapper self.linewrap = textwrap.TextWrapper() self.linewrap.width = self.linelength # calculate window size left = (self.h_pos) / self.scale right = (self.h_pos + self.h_size) / self.scale bottom = (self.v_pos) / self.scale top = (self.v_pos + self.v_size) / self.scale # panda3d interface self.consoleFrame = DirectFrame( relief=DGG.GROOVE, frameColor=(200, 200, 200, 0.5), scale=self.scale, frameSize=(0, self.h_size / self.scale, 0, self.v_size / self.scale), ) self.windowEvent(base.win) # try to load the defined font fixedWidthFont = loader.loadFont(self.font) # if font is not valid use default font if not fixedWidthFont.isValid(): if self.font is None: print "pandaInteractiveConsole.py :: could not load the defined font %s" % str(self.font) fixedWidthFont = DGG.getDefaultFont() # text entry line self.consoleEntry = DirectEntry( self.consoleFrame, text="", command=self.onEnterPress, width=self.h_size / self.scale - 2, pos=(1, 0, 1.5), initialText="", numLines=1, focus=1, entryFont=fixedWidthFont, ) # output lines self.consoleOutputList = list() for i in xrange(self.numlines): label = OnscreenText( parent=self.consoleFrame, text="", pos=(1, -i + 3 + self.numlines), align=TextNode.ALeft, mayChange=1, scale=1.0, fg=self.defaultTextColor, ) label.setFont(fixedWidthFont) self.consoleOutputList.append(label) # list of the last commands of the user self.userCommandList = list() self.userCommandListLength = 100 for i in xrange(self.userCommandListLength): self.userCommandList.append("") self.userCommandPos = 0 # buffer for data self.textBuffer = list() self.textBufferLength = 1000 for i in xrange(self.textBufferLength): self.textBuffer.append(["", DEFAULT_COLOR]) self.textBufferPos = self.textBufferLength - self.numlines # toggle the window at least once to activate the events self.toggleConsole() self.toggleConsole() self.help() def help(self): # output some info text about this module infoTxt = """ ------ Panda3dConsole ------ - press F10 to toggle it on/off - use the usual copy, cut & paste keys - page_up : scrolls up - page_down : scrolls down - arrow_up : previous command - arrow_down : next command - BUGS : if you paste a to long text, the entry blocks FIX : use cut to remove the whole line""" # for line in infoTxt.split('\n'): # self.write( line, color=DEFAULT_COLOR ) return infoTxt # write a string to the panda3d console def write(self, printString, color=defaultTextColor): # remove not printable characters (which can be input by console input) printString = re.sub(r"[^%s]" % re.escape(string.printable[:95]), "", printString) splitLines = self.linewrap.wrap(printString) for line in splitLines: self.textBuffer.append([line, color]) self.textBuffer.pop(0) self.updateOutput() def updateOutput(self): for lineNumber in xrange(self.numlines): lineText, color = self.textBuffer[lineNumber + self.textBufferPos] self.consoleOutputList[lineNumber].setText(lineText) self.consoleOutputList[lineNumber]["fg"] = color # toggle the gui console def toggleConsole(self): self.consoleFrame.toggleVis() hidden = self.consoleFrame.isHidden() self.consoleEntry["focus"] != hidden if hidden: self.ignoreAll() self.accept(self.gui_key, self.toggleConsole) else: self.ignoreAll() self.accept("page_up", self.scroll, [-5]) self.accept("page_up-repeat", self.scroll, [-5]) self.accept("page_down", self.scroll, [5]) self.accept("page_down-repeat", self.scroll, [5]) self.accept("window-event", self.windowEvent) self.accept("arrow_up", self.scrollCmd, [1]) self.accept("arrow_down", self.scrollCmd, [-1]) self.accept(self.gui_key, self.toggleConsole) self.accept(self.autocomplete_key, self.autocomplete) self.accept(self.autohelp_key, self.autohelp) # accept v, c and x, where c & x copy's the whole console text # messenger.toggleVerbose() # for osx use ('meta') if sys.platform == "darwin": self.accept("meta", self.unfocus) self.accept("meta-up", self.focus) self.accept("meta-c", self.copy) self.accept("meta-x", self.cut) self.accept("meta-v", self.paste) # for windows use ('control') if sys.platform == "win32" or sys.platform == "linux2": self.accept("control", self.unfocus) self.accept("control-up", self.focus) self.accept("control-c", self.copy) self.accept("control-x", self.cut) self.accept("control-v", self.paste) def autocomplete(self): currentText = self.consoleEntry.get() currentPos = self.consoleEntry.guiItem.getCursorPosition() newText = self.parent.autocomplete(currentText, currentPos) if newText != currentText: self.consoleEntry.set(newText) self.consoleEntry.setCursorPosition(len(newText)) def autohelp(self): currentText = self.consoleEntry.get() currentPos = self.consoleEntry.guiItem.getCursorPosition() self.parent.autohelp(currentText, currentPos) def focus(self): self.consoleEntry["focus"] = 1 def unfocus(self): self.consoleEntry["focus"] = 0 def copy(self): copy = self.consoleEntry.get() clipboard.setText(copy) def paste(self): oldCursorPos = self.consoleEntry.guiItem.getCursorPosition() clipboardText = clipboard.getText() # compose new text line oldText = self.consoleEntry.get() newText = oldText[0:oldCursorPos] + clipboardText + oldText[oldCursorPos:] clipboardTextLines = newText.split(os.linesep) for i in xrange(len(clipboardTextLines) - 1): currentLine = clipboardTextLines[i] # we only want printable characters currentLine = re.sub(r"[^" + re.escape(string.printable[:95]) + "]", "", currentLine) # set new text and position self.consoleEntry.set(currentLine) self.onEnterPress(currentLine) currentLine = clipboardTextLines[-1] currentLine = re.sub(r"[^" + re.escape(string.printable[:95]) + "]", "", currentLine) self.consoleEntry.set(currentLine) self.consoleEntry.setCursorPosition(len(self.consoleEntry.get())) self.focus() def cut(self): clipboard.setText(self.consoleEntry.get()) self.consoleEntry.enterText("") self.focus() def scroll(self, step): self.textBufferPos += step self.textBufferPos = min(self.textBufferLength - self.numlines, max(0, self.textBufferPos)) self.updateOutput() def scrollCmd(self, step): oldCmdPos = self.userCommandPos self.userCommandPos += step self.userCommandPos = min(self.userCommandListLength - 1, max(0, self.userCommandPos)) self.userCommandList[oldCmdPos] = self.consoleEntry.get() newCmd = self.userCommandList[self.userCommandPos] self.consoleEntry.set(newCmd) self.consoleEntry.setCursorPosition(len(newCmd)) def onEnterPress(self, textEntered): # set to last message self.textBufferPos = self.textBufferLength - self.numlines # clear line self.consoleEntry.enterText("") self.focus() # add text entered to user command list & remove oldest entry self.userCommandList.insert(1, textEntered) self.userCommandList[0] = "" self.userCommandList.pop(-1) self.userCommandPos = 0 # call our parent self.parent.push(textEntered) def windowEvent(self, window): """ This is a special callback. It is called when the panda window is modified. """ wp = window.getProperties() width = wp.getXSize() / float(wp.getYSize()) height = wp.getYSize() / float(wp.getXSize()) if width > height: height = 1.0 else: width = 1.0 # aligned to center consolePos = Vec3(-self.h_size / 2, 0, -self.v_size / 2) # aligned to left bottom # consolePos = Vec3(-width+self.h_pos, 0, -height+self.v_pos) # aligned to right top # consolePos = Vec3(width-self.h_size, 0, height-self.v_size) # set position self.consoleFrame.setPos(consolePos)