historical/toontown-classic.git/panda/samples/rocket-console/main.py

411 lines
13 KiB
Python
Raw Normal View History

2024-01-16 11:20:27 -06:00
"""
Show how to use libRocket in Panda3D.
"""
import sys
from panda3d.core import loadPrcFile, loadPrcFileData, Point3,Vec4, Mat4, LoaderOptions # @UnusedImport
from panda3d.core import DirectionalLight, AmbientLight, PointLight
from panda3d.core import Texture, PNMImage
from panda3d.core import PandaSystem
import random
from direct.interval.LerpInterval import LerpHprInterval, LerpPosInterval, LerpFunc
from direct.showbase.ShowBase import ShowBase
# workaround: https://www.panda3d.org/forums/viewtopic.php?t=10062&p=99697#p99054
#from panda3d import rocket
import _rocketcore as rocket
from panda3d.rocket import RocketRegion, RocketInputHandler
loadPrcFileData("", "model-path $MAIN_DIR/assets")
import console
global globalClock
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.win.setClearColor(Vec4(0.2, 0.2, 0.2, 1))
self.disableMouse()
self.render.setShaderAuto()
dlight = DirectionalLight('dlight')
alight = AmbientLight('alight')
dlnp = self.render.attachNewNode(dlight)
alnp = self.render.attachNewNode(alight)
dlight.setColor((0.8, 0.8, 0.5, 1))
alight.setColor((0.2, 0.2, 0.2, 1))
dlnp.setHpr(0, -60, 0)
self.render.setLight(dlnp)
self.render.setLight(alnp)
# Put lighting on the main scene
plight = PointLight('plight')
plnp = self.render.attachNewNode(plight)
plnp.setPos(0, 0, 10)
self.render.setLight(plnp)
self.render.setLight(alnp)
self.loadRocketFonts()
self.loadingTask = None
#self.startModelLoadingAsync()
self.startModelLoading()
self.inputHandler = RocketInputHandler()
self.mouseWatcher.attachNewNode(self.inputHandler)
self.openLoadingDialog()
def loadRocketFonts(self):
""" Load fonts referenced from e.g. 'font-family' RCSS directives.
Note: the name of the font as used in 'font-family'
is not always the same as the filename;
open the font in your OS to see its display name.
"""
rocket.LoadFontFace("modenine.ttf")
def startModelLoading(self):
self.monitorNP = None
self.keyboardNP = None
self.loadingError = False
self.taskMgr.doMethodLater(1, self.loadModels, 'loadModels')
def loadModels(self, task):
self.monitorNP = self.loader.loadModel("monitor")
self.keyboardNP = self.loader.loadModel("takeyga_kb")
def startModelLoadingAsync(self):
"""
NOTE: this seems to invoke a few bugs (crashes, sporadic model
reading errors, etc) so is disabled for now...
"""
self.monitorNP = None
self.keyboardNP = None
self.loadingError = False
# force the "loading" to take some time after the first run...
options = LoaderOptions()
options.setFlags(options.getFlags() | LoaderOptions.LFNoCache)
def gotMonitorModel(model):
if not model:
self.loadingError = True
self.monitorNP = model
self.loader.loadModel("monitor", loaderOptions=options, callback=gotMonitorModel)
def gotKeyboardModel(model):
if not model:
self.loadingError = True
self.keyboardNP = model
self.loader.loadModel("takeyga_kb", loaderOptions=options, callback=gotKeyboardModel)
def openLoadingDialog(self):
self.userConfirmed = False
self.windowRocketRegion = RocketRegion.make('pandaRocket', self.win)
self.windowRocketRegion.setActive(1)
self.windowRocketRegion.setInputHandler(self.inputHandler)
self.windowContext = self.windowRocketRegion.getContext()
self.loadingDocument = self.windowContext.LoadDocument("loading.rml")
if not self.loadingDocument:
raise AssertionError("did not find loading.rml")
self.loadingDots = 0
el = self.loadingDocument.GetElementById('loadingLabel')
self.loadingText = el.first_child
self.stopLoadingTime = globalClock.getFrameTime() + 3
self.loadingTask = self.taskMgr.add(self.cycleLoading, 'doc changer')
# note: you may encounter errors like 'KeyError: 'document'"
# when invoking events using methods from your own scripts with this
# obvious code:
#
# self.loadingDocument.AddEventListener('aboutToClose',
# self.onLoadingDialogDismissed, True)
#
# A workaround is to define callback methods in standalone Python
# files with event, self, and document defined to None.
#
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
#
# Or, use this indirection technique to work around the problem,
# by publishing the app into the context, then accessing it through
# the document's context...
self.windowContext.app = self
self.loadingDocument.AddEventListener('aboutToClose',
'document.context.app.handleAboutToClose()', True)
self.loadingDocument.Show()
def handleAboutToClose(self):
self.userConfirmed = True
if self.monitorNP and self.keyboardNP:
self.onLoadingDialogDismissed()
def attachCustomRocketEvent(self, document, rocketEventName, pandaHandler, once=False):
# handle custom event
# note: you may encounter errors like 'KeyError: 'document'"
# when invoking events using methods from your own scripts with this
# obvious code:
#
# self.loadingDocument.AddEventListener('aboutToClose',
# self.onLoadingDialogDismissed, True)
#
# see https://www.panda3d.org/forums/viewtopic.php?f=4&t=16412
# this technique converts Rocket events to Panda3D events
pandaEvent = 'panda.' + rocketEventName
document.AddEventListener(
rocketEventName,
"messenger.send('" + pandaEvent + "', [event])")
if once:
self.acceptOnce(pandaEvent, pandaHandler)
else:
self.accept(pandaEvent, pandaHandler)
def cycleLoading(self, task):
"""
Update the "loading" text in the initial window until
the user presses Space, Enter, or Escape or clicks (see loading.rxml)
or sufficient time has elapsed (self.stopLoadingTime).
"""
text = self.loadingText
now = globalClock.getFrameTime()
if self.monitorNP and self.keyboardNP:
text.text = "Ready"
if now > self.stopLoadingTime or self.userConfirmed:
self.onLoadingDialogDismissed()
return task.done
elif self.loadingError:
text.text = "Assets not found"
else:
count = 5
intv = int(now * 4) % count # @UndefinedVariable
text.text = "Loading" + ("." * (1+intv)) + (" " * (2 - intv))
return task.cont
def onLoadingDialogDismissed(self):
""" Once a models are loaded, stop 'loading' and proceed to 'start' """
if self.loadingDocument:
if self.loadingTask:
self.taskMgr.remove(self.loadingTask)
self.loadingTask = None
self.showStarting()
def fadeOut(self, element, time):
""" Example updating RCSS attributes from code
by modifying the 'color' RCSS attribute to slowly
change from solid to transparent.
element: the Rocket element whose style to modify
time: time in seconds for fadeout
"""
# get the current color from RCSS effective style
color = element.style.color
# convert to RGBA form
prefix = color[:color.rindex(',')+1].replace('rgb(', 'rgba(')
def updateAlpha(t):
# another way of setting style on a specific element
attr = 'color: ' + prefix + str(int(t)) +');'
element.SetAttribute('style', attr)
alphaInterval = LerpFunc(updateAlpha,
duration=time,
fromData=255,
toData=0,
blendType='easeIn')
return alphaInterval
def showStarting(self):
""" Models are loaded, so update the dialog,
fade out, then transition to the console. """
self.loadingText.text = 'Starting...'
alphaInterval = self.fadeOut(self.loadingText, 0.5)
alphaInterval.setDoneEvent('fadeOutFinished')
def fadeOutFinished():
if self.loadingDocument:
self.loadingDocument.Close()
self.loadingDocument = None
self.createConsole()
self.accept('fadeOutFinished', fadeOutFinished)
alphaInterval.start()
def createConsole(self):
""" Create the in-world console, which displays
a RocketRegion in a GraphicsBuffer, which appears
in a Texture on the monitor model. """
self.monitorNP.reparentTo(self.render)
self.monitorNP.setScale(1.5)
self.keyboardNP.reparentTo(self.render)
self.keyboardNP.setHpr(-90, 0, 15)
self.keyboardNP.setScale(20)
self.placeItems()
self.setupRocketConsole()
# re-enable mouse
mat=Mat4(self.camera.getMat())
mat.invertInPlace()
self.mouseInterfaceNode.setMat(mat)
self.enableMouse()
def placeItems(self):
self.camera.setPos(0, -20, 0)
self.camera.setHpr(0, 0, 0)
self.monitorNP.setPos(0, 0, 1)
self.keyboardNP.setPos(0, -5, -2.5)
def setupRocketConsole(self):
"""
Place a new rocket window onto a texture
bound to the front of the monitor.
"""
self.win.setClearColor(Vec4(0.5, 0.5, 0.8, 1))
faceplate = self.monitorNP.find("**/Faceplate")
assert faceplate
mybuffer = self.win.makeTextureBuffer("Console Buffer", 1024, 512)
tex = mybuffer.getTexture()
tex.setMagfilter(Texture.FTLinear)
tex.setMinfilter(Texture.FTLinear)
faceplate.setTexture(tex, 1)
self.rocketConsole = RocketRegion.make('console', mybuffer)
self.rocketConsole.setInputHandler(self.inputHandler)
self.consoleContext = self.rocketConsole.getContext()
self.console = console.Console(self, self.consoleContext, 40, 13, self.handleCommand)
self.console.addLine("Panda DOS")
self.console.addLine("type 'help'")
self.console.addLine("")
self.console.allowEditing(True)
def handleCommand(self, command):
if command is None:
# hack for Ctrl-Break
self.spewInProgress = False
self.console.addLine("*** break ***")
self.console.allowEditing(True)
return
command = command.strip()
if not command:
return
tokens = [x.strip() for x in command.split(' ')]
command = tokens[0].lower()
if command == 'help':
self.console.addLines([
"Sorry, this is utter fakery.",
"You won't get much more",
"out of this simulation unless",
"you program it yourself. :)"
])
elif command == 'dir':
self.console.addLines([
"Directory of C:\\:",
"HELP COM 72 05-06-2015 14:07",
"DIR COM 121 05-06-2015 14:11",
"SPEW COM 666 05-06-2015 15:02",
" 2 Files(s) 859 Bytes.",
" 0 Dirs(s) 7333 Bytes free.",
""])
elif command == 'cls':
self.console.cls()
elif command == 'echo':
self.console.addLine(' '.join(tokens[1:]))
elif command == 'ver':
self.console.addLine('Panda DOS v0.01 in Panda3D ' + PandaSystem.getVersionString())
elif command == 'spew':
self.startSpew()
elif command == 'exit':
self.console.setPrompt("System is shutting down NOW!")
self.terminateMonitor()
else:
self.console.addLine("command not found")
def startSpew(self):
self.console.allowEditing(False)
self.console.addLine("LINE NOISE 1.0")
self.console.addLine("")
self.spewInProgress = True
# note: spewage always occurs in 'doMethodLater';
# time.sleep() would be pointless since the whole
# UI would be frozen during the wait.
self.queueSpew(2)
def queueSpew(self, delay=0.1):
self.taskMgr.doMethodLater(delay, self.spew, 'spew')
def spew(self, task):
# generate random spewage, just like on TV!
if not self.spewInProgress:
return
def randchr():
return chr(int(random.random() < 0.25 and 32 or random.randint(32, 127)))
line = ''.join([randchr() for _ in range(40) ])
self.console.addLine(line)
self.queueSpew()
def terminateMonitor(self):
alphaInterval = self.fadeOut(self.console.getTextContainer(), 2)
alphaInterval.setDoneEvent('fadeOutFinished')
def fadeOutFinished():
sys.exit(0)
self.accept('fadeOutFinished', fadeOutFinished)
alphaInterval.start()
app = MyApp()
app.run()