Quellcode durchsuchen

rocket: Add sample showing some Panda3D uses of libRocket. (#24)

Ed Swartz vor 8 Jahren
Ursprung
Commit
34731cd2f4

BIN
samples/rocket-console/assets/Perfect DOS VGA 437.ttf


+ 38 - 0
samples/rocket-console/assets/console.rcss

@@ -0,0 +1,38 @@
+body
+{
+    font-family: "Perfect DOS VGA 437";
+    font-weight: normal;
+    font-style: normal;
+
+    // use all the allocated texture space
+    min-width: 100%;
+    min-height: 100%;
+
+    background-color: #000;
+}
+
+
+text#content
+{
+    z-index: 2;
+    font-size: 30px;
+
+    white-space: pre-wrap;
+
+    margin: auto;
+
+    text-align: left;
+    position: absolute;
+
+    // account for non-proportionality of our 1024x512
+    // buffer compared with VGA font proportions and
+    // wanting to center the screen with 40 columns
+    top: 16px;
+    left: 32px;
+
+    width: 100%;
+    height: 100%;
+
+    color: #888;
+
+}

+ 11 - 0
samples/rocket-console/assets/console.rml

@@ -0,0 +1,11 @@
+<rml>
+
+    <head>
+        <title>Administrative Console</title>
+        <link type="text/rcss" href="console.rcss"/>
+    </head>
+    <!-- events are bound strictly from code -->
+    <body >
+        <text id="content" />
+    </body>
+</rml>

+ 1 - 0
samples/rocket-console/assets/dos437.txt

@@ -0,0 +1 @@
+from www.dafont.com/perfect-dos-vga-437.font (info at http://zehfernando.com/2015/revisiting-vga-fonts/)

+ 54 - 0
samples/rocket-console/assets/loading.rml

@@ -0,0 +1,54 @@
+<rml>
+    <head >
+        <title>Main Menu</title>
+        <link type="text/template" href="window.rml" />
+        <style>
+            body
+            {
+                width: 400px;
+                height: 225px;
+
+                margin: auto;
+
+                background-color: #44f;
+            }
+
+
+            div#title_bar_content
+            {
+                font-size: 48;
+
+                //text-align: left;
+                position: absolute;
+                top: 40%;
+                //vertical-align: center;
+
+            }
+
+        </style>
+
+        <script>
+
+import _rocketcore as rocket
+
+# This handler overrides the 'onkeydown' handler from the template
+def OnKeyDown(event, document):
+    keyId = event.parameters['key_identifier']
+    if keyId in [ rocket.key_identifier.RETURN,
+                rocket.key_identifier.ESCAPE,
+                 rocket.key_identifier.SPACE ]:
+        FireClosing(document)
+
+# custom event
+def FireClosing(document):
+    document.DispatchEvent("aboutToClose", { }, False)
+
+    </script>
+
+    </head>
+    <body id='window' template="window" onclick='FireClosing(document)'>
+        <div id="title_bar_content" >
+            <label id="loadingLabel">Loading...</label>
+        </div>
+    </body>
+</rml>

+ 18 - 0
samples/rocket-console/assets/modenine.nfo

@@ -0,0 +1,18 @@
+ModeNine
+
+Based on Andrew Bulhak's ModeSeven, in turn inspired by the screen
+output of the BBC Micro.
+
+copyright: 
+(C) 1998 Andrew C. Bulhak
+(C) 2001 Graham H Freeman
+
+Freely Distributable. 
+
+All we ask is that this readme file must be included with the font package.
+Impresarios of free font sites and shovelware cd-roms, this means you!
+
+If you think this font is doovy, let us know at [email protected], and we
+might actually make more fonts. 
+
+Another fine Grudnuk Creations produkt | http://grudnuk.com/

BIN
samples/rocket-console/assets/modenine.ttf


BIN
samples/rocket-console/assets/monitor.egg.pz


+ 12 - 0
samples/rocket-console/assets/monitor.txt

@@ -0,0 +1,12 @@
+
+This is an edited version of this file from blendswap.com (http://www.blendswap.com/blends/view/74468).
+
+VERY IMPORTANT LICENSE INFORMATION:
+
+This file has been released by buzo under the following license:
+
+    Creative Commons Zero (Public Domain)
+
+You can use this model for any purposes according to the following conditions:
+
+    There are no requirements for CC-Zero licensed blends.

+ 44 - 0
samples/rocket-console/assets/rkt.rcss

@@ -0,0 +1,44 @@
+/*
+* Default styles for all the basic elements.
+*/
+
+div
+{
+    display: block;
+}
+
+p
+{
+    display: block;
+}
+
+h1
+{
+    display: block;
+}
+
+em
+{
+    font-style: italic;
+}
+
+strong
+{
+    font-weight: bold;
+}
+
+datagrid
+{
+    display: block;
+}
+
+select, dataselect, datacombo
+{
+    text-align: left;
+}
+
+tabset tabs
+{
+    display: block;
+}
+

+ 316 - 0
samples/rocket-console/assets/takeyga_kb.egg

@@ -0,0 +1,316 @@
+<CoordinateSystem> { Z-up } 
+<Material> takeyga_kb {
+  <Scalar> diffr { 0.800000 }
+  <Scalar> diffg { 0.800000 }
+  <Scalar> diffb { 0.800000 }
+  <Scalar> specr { 0.500000 }
+  <Scalar> specg { 0.500000 }
+  <Scalar> specb { 0.500000 }
+  <Scalar> shininess { 12.5 }
+  <Scalar> ambr { 1.000000 }
+  <Scalar> ambg { 1.000000 }
+  <Scalar> ambb { 1.000000 }
+  <Scalar> emitr { 0.000000 }
+  <Scalar> emitg { 0.000000 }
+  <Scalar> emitb { 0.000000 }
+}
+
+<Texture> Texture.001 {
+  "./tex/takeyga_kb_specular.dds"
+  <Scalar> envtype { MODULATE }
+  <Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> wrap { REPEAT }
+}
+
+<Texture> Tex {
+  "./tex/takeyga_kb_diffuse.dds"
+  <Scalar> envtype { MODULATE }
+  <Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> wrap { REPEAT }
+}
+
+<Texture> Texture {
+  "./tex/takeyga_kb_normal.dds"
+  <Scalar> envtype { NORMAL }
+  <Scalar> minfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> magfilter { LINEAR_MIPMAP_LINEAR }
+  <Scalar> wrap { REPEAT }
+}
+
+  <Group> Cube.001 {
+    <Transform> {
+      <Matrix4> {
+        0.08499996364116669 0.0 0.0 0.0 
+        0.0 0.23999987542629242 0.0 0.0 
+        0.0 0.0 0.02500000037252903 0.0 
+        0.0 0.0 0.0 1.0 
+      }
+    }
+    
+    <VertexPool> Cube.001 {
+    
+      <Vertex> 0 {0.082953 0.240000 -0.025000
+        <UV>  {
+          0.158203125 0.595703125 
+        }
+      }
+      <Vertex> 1 {0.082953 -0.240000 -0.025000
+        <UV>  {
+          0.98828125 0.6015625 
+        }
+      }
+      <Vertex> 2 {-0.085000 -0.240000 -0.025000
+        <UV>  {
+          0.98828125 0.986328125 
+        }
+      }
+      <Vertex> 3 {-0.085000 0.240000 -0.025000
+        <UV>  {
+          0.162109375 0.98828125 
+        }
+      }
+      <Vertex> 4 {0.073544 0.227744 -0.005546
+        <UV>  {
+          0.990234375 0.017578125 
+        }
+      }
+      <Vertex> 5 {-0.067932 0.223167 0.018996
+        <UV>  {
+          0.98046875 0.46875 
+        }
+      }
+      <Vertex> 6 {-0.067932 -0.223167 0.018996
+        <UV>  {
+          0.017578125 0.470703125 
+        }
+      }
+      <Vertex> 7 {0.073544 -0.227744 -0.005546
+        <UV>  {
+          0.01953125 0.013671875 
+        }
+      }
+      <Vertex> 8 {0.082372 0.237328 -0.015273
+        <UV>  {
+          0.9794921875 0.552734375 
+        }
+      }
+      <Vertex> 9 {0.073544 0.227744 -0.005546
+        <UV>  {
+          0.9765625 0.57421875 
+        }
+      }
+      <Vertex> 10 {0.073544 -0.227744 -0.005546
+        <UV>  {
+          0.025390625 0.57421875 
+        }
+      }
+      <Vertex> 11 {0.082372 -0.237328 -0.015273
+        <UV>  {
+          0.0234375 0.5537109375 
+        }
+      }
+      <Vertex> 12 {0.082372 -0.237328 -0.015273
+        <UV>  {
+          0.0703125 0.6123046875 
+        }
+      }
+      <Vertex> 13 {0.073544 -0.227744 -0.005546
+        <UV>  {
+          0.095703125 0.615234375 
+        }
+      }
+      <Vertex> 14 {-0.067932 -0.223167 0.018996
+        <UV>  {
+          0.13671875 0.97265625 
+        }
+      }
+      <Vertex> 15 {-0.080884 -0.235006 0.001122
+        <UV>  {
+          0.07421875 0.982421875 
+        }
+      }
+      <Vertex> 16 {-0.080884 -0.235006 0.001122
+        <UV>  {
+          0.0166015625 0.5361328125 
+        }
+      }
+      <Vertex> 17 {-0.067932 -0.223167 0.018996
+        <UV>  {
+          0.017578125 0.58203125 
+        }
+      }
+      <Vertex> 18 {-0.067932 0.223167 0.018996
+        <UV>  {
+          0.978515625 0.580078125 
+        }
+      }
+      <Vertex> 19 {-0.080884 0.235006 0.001122
+        <UV>  {
+          0.984375 0.5341796875 
+        }
+      }
+      <Vertex> 20 {0.082372 0.237328 -0.015273
+        <UV>  {
+          0.0703125 0.6123046875 
+        }
+      }
+      <Vertex> 21 {0.082953 0.240000 -0.025000
+        <UV>  {
+          0.044921875 0.609375 
+        }
+      }
+      <Vertex> 22 {-0.085000 0.240000 -0.025000
+        <UV>  {
+          0.01171875 0.9921875 
+        }
+      }
+      <Vertex> 23 {-0.080884 0.235006 0.001122
+        <UV>  {
+          0.07421875 0.982421875 
+        }
+      }
+      <Vertex> 24 {0.082953 0.240000 -0.025000
+        <UV>  {
+          0.982421875 0.53125 
+        }
+      }
+      <Vertex> 25 {0.082372 0.237328 -0.015273
+        <UV>  {
+          0.9794921875 0.552734375 
+        }
+      }
+      <Vertex> 26 {0.082372 -0.237328 -0.015273
+        <UV>  {
+          0.0234375 0.5537109375 
+        }
+      }
+      <Vertex> 27 {0.082953 -0.240000 -0.025000
+        <UV>  {
+          0.021484375 0.533203125 
+        }
+      }
+      <Vertex> 28 {0.082953 -0.240000 -0.025000
+        <UV>  {
+          0.044921875 0.609375 
+        }
+      }
+      <Vertex> 29 {0.082372 -0.237328 -0.015273
+        <UV>  {
+          0.0703125 0.6123046875 
+        }
+      }
+      <Vertex> 30 {-0.080884 -0.235006 0.001122
+        <UV>  {
+          0.07421875 0.982421875 
+        }
+      }
+      <Vertex> 31 {-0.085000 -0.240000 -0.025000
+        <UV>  {
+          0.01171875 0.9921875 
+        }
+      }
+      <Vertex> 32 {-0.085000 -0.240000 -0.025000
+        <UV>  {
+          0.015625 0.490234375 
+        }
+      }
+      <Vertex> 33 {-0.080884 -0.235006 0.001122
+        <UV>  {
+          0.0166015625 0.5361328125 
+        }
+      }
+      <Vertex> 34 {-0.080884 0.235006 0.001122
+        <UV>  {
+          0.984375 0.5341796875 
+        }
+      }
+      <Vertex> 35 {-0.085000 0.240000 -0.025000
+        <UV>  {
+          0.990234375 0.48828125 
+        }
+      }
+      <Vertex> 36 {0.073544 0.227744 -0.005546
+        <UV>  {
+          0.095703125 0.615234375 
+        }
+      }
+      <Vertex> 37 {0.082372 0.237328 -0.015273
+        <UV>  {
+          0.0703125 0.6123046875 
+        }
+      }
+      <Vertex> 38 {-0.080884 0.235006 0.001122
+        <UV>  {
+          0.07421875 0.982421875 
+        }
+      }
+      <Vertex> 39 {-0.067932 0.223167 0.018996
+        <UV>  {
+          0.13671875 0.97265625 
+        }
+      }}
+    
+    
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.000000 0.000000 -1.000000}
+      <VertexRef> { 0 1 2 3 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.508027 -0.000000 0.861341}
+      <VertexRef> { 4 5 6 7 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.966167 -0.000000 0.257917}
+      <VertexRef> { 8 9 10 11 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.028240 -0.996449 0.079322}
+      <VertexRef> { 12 13 14 15 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {-0.978036 0.000000 0.208436}
+      <VertexRef> { 16 17 18 19 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.001260 0.999752 0.022233}
+      <VertexRef> { 20 21 22 23 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.999846 -0.000000 0.017550}
+      <VertexRef> { 24 25 26 27 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.001259 -0.999752 0.022233}
+      <VertexRef> { 28 29 30 31 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {-0.998928 0.000000 0.046291}
+      <VertexRef> { 32 33 34 35 <Ref> { Cube.001 }} 
+    }
+    <Polygon> {
+      <TRef> { Tex }
+      <MRef> { takeyga_kb }
+      <Normal> {0.028241 0.996449 0.079322}
+      <VertexRef> { 36 37 38 39 <Ref> { Cube.001 }} 
+    }
+  }

BIN
samples/rocket-console/assets/tex/takeyga_kb_diffuse.dds


BIN
samples/rocket-console/assets/tex/takeyga_kb_normal.dds


BIN
samples/rocket-console/assets/tex/takeyga_kb_specular.dds


+ 56 - 0
samples/rocket-console/assets/window.rcss

@@ -0,0 +1,56 @@
+body
+{
+    font-family: "MODENINE";
+    font-weight: normal;
+    font-style: normal;
+    font-size: 15;
+
+}
+
+body.window
+{
+    padding-top: 43px;
+    padding-bottom: 20px;
+
+    min-width: 250px;
+
+    min-height: 135px;
+    max-height: 700px;
+
+}
+
+
+
+div#title_bar
+{
+    z-index: 1;
+
+    position: absolute;
+    top: 0px;
+    left: 0px;
+
+    text-align: center;
+
+    color: #fff;
+    background-color: #22f;
+}
+
+
+div#title_bar span
+{
+    padding-top: 17px;
+    padding-bottom: 48px;
+
+    font-size: 32;
+    font-weight: bold;
+
+    outline-font-effect: outline;
+    outline-width: 1px;
+    outline-color: black;
+}
+
+div#title_bar_content
+{
+    text-align: center;
+    color: #cff;
+}

+ 42 - 0
samples/rocket-console/assets/window.rml

@@ -0,0 +1,42 @@
+<template name="window" content="content">
+    <head>
+        <link type="text/rcss" href="rkt.rcss"/>
+        <link type="text/rcss" href="window.rcss"/>
+
+
+        <script>
+import _rocketcore as rocket
+
+def OnLoad(document):
+    print "Rocket document loaded"
+
+# event handlers from templates can be overridden in windows using them
+def OnKeyDown(event, document):
+    keyId = event.parameters['key_identifier']
+    print "Base keydown: unhandled key ",keyId
+
+        </script>
+    </head>
+
+
+    <body class="window" onload='OnLoad(document)' onkeydown='OnKeyDown(event, document)'>
+        <div id="title_bar">
+            <handle move_target="#document">
+            <span id="title">Rocket Sample</span>
+
+                <div id="title_bar_content">
+                </div>
+            </handle>
+        </div>
+        <div id="window">
+            <div id="content">
+            </div>
+        </div>
+
+        <!-- drag and drop of window -->
+        <handle size_target="#document"
+             style="position: absolute; width: 16px; height: 16px; bottom: 0px; right: 0px;">
+        </handle>
+    </body>
+
+</template>

+ 153 - 0
samples/rocket-console/console.py

@@ -0,0 +1,153 @@
+"""
+Simple console widget for rocket
+"""
+import sys, os.path
+
+# 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
+
+class Console(object):
+    def __init__(self, base, context, cols, rows, commandHandler):
+        self.base = base
+
+        self.context = context
+        self.loadFonts()
+        self.cols = cols
+        self.rows = rows
+        self.commandHandler = commandHandler
+
+        self.setupConsole()
+        self.allowEditing(True)
+
+    def getTextContainer(self):
+        return self.textEl
+
+    def setPrompt(self, prompt):
+        self.consolePrompt = prompt
+
+    def allowEditing(self, editMode):
+        self.editMode = editMode
+        if editMode:
+            self.input = ""
+            if not self.lastLine:
+                self.addLine("")
+            self.newEditLine()
+
+    def loadFonts(self):
+        rocket.LoadFontFace("Perfect DOS VGA 437.ttf")
+
+    def setupConsole(self):
+        self.document = self.context.LoadDocument("console.rml")
+        if not self.document:
+            raise AssertionError("did not find console.rml")
+
+        el = self.document.GetElementById('content')
+
+        self.textEl = el
+
+        # roundabout way of accessing the current object through rocket event...
+
+        # add attribute to let Rocket know about the receiver
+        self.context.console = self
+
+        # then reference through the string format (dunno how else to get the event...)
+        self.document.AddEventListener(
+            'keydown', 'document.context.console.handleKeyDown(event)', True)
+        self.document.AddEventListener(
+            'textinput', 'document.context.console.handleTextInput(event)', True)
+
+        self.consolePrompt = "C:\\>"
+
+        self.input = ""
+        self.lastLine = None
+
+        self.blinkState = False
+        self.queueBlinkCursor()
+
+        self.document.Show()
+
+    def queueBlinkCursor(self):
+        self.base.taskMgr.doMethodLater(0.2, self.blinkCursor, 'blinkCursor')
+
+    def blinkCursor(self, task):
+        self.blinkState = not self.blinkState
+        if self.editMode:
+            self.updateEditLine(self.input)
+        self.queueBlinkCursor()
+
+    def escape(self, text):
+        return text. \
+                replace('<', '&lt;'). \
+                replace('>', '&gt;'). \
+                replace('"', '&quot;')
+
+    def addLine(self, text):
+        curKids = list(self.textEl.child_nodes)
+        while len(curKids) >= self.rows:
+            self.textEl.RemoveChild(curKids[0])
+            curKids = curKids[1:]
+
+        line = self.document.CreateTextNode(self.escape(text) + '\n')
+        self.textEl.AppendChild(line)
+        self.lastLine = line
+
+    def addLines(self, lines):
+        for line in lines:
+            self.addLine(line)
+
+    def updateEditLine(self, newInput=''):
+        newText = self.consolePrompt + newInput
+        self.lastLine.text = self.escape(newText) + (self.blinkState and '_' or '')
+        self.input = newInput
+
+    def scroll(self):
+        self.blinkState = False
+        self.updateEditLine(self.input + '\n')
+
+    def handleKeyDown(self, event):
+        """
+        Handle control keys
+        """
+        keyId = event.parameters['key_identifier']
+        if not self.editMode:
+            if keyId == rocket.key_identifier.PAUSE:
+                if event.parameters['ctrl_key']:
+                    self.commandHandler(None)
+
+            return
+
+        if keyId == rocket.key_identifier.RETURN:
+            # emit line without cursor
+            self.scroll()
+
+            # handle command
+            self.commandHandler(self.input)
+
+            if self.editMode:
+                # start with new "command"
+                self.addLine(self.consolePrompt)
+                self.updateEditLine("")
+
+        elif keyId == rocket.key_identifier.BACK:
+            self.updateEditLine(self.input[0:-1])
+
+    def handleTextInput(self, event):
+        if not self.editMode:
+            return
+
+        # handle normal text character
+        data = event.parameters['data']
+        if 32 <= data < 128:
+            self.updateEditLine(self.input + chr(data))
+
+    def newEditLine(self):
+        self.addLine("")
+        self.updateEditLine()
+
+    def cls(self):
+        curKids = list(self.textEl.child_nodes)
+        for kid in curKids:
+            self.textEl.RemoveChild(kid)

+ 410 - 0
samples/rocket-console/main.py

@@ -0,0 +1,410 @@
+"""
+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()