diff --git a/direct/src/directdevices/DirectJoybox.py b/direct/src/directdevices/DirectJoybox.py index 531b7118c8..c82201a933 100644 --- a/direct/src/directdevices/DirectJoybox.py +++ b/direct/src/directdevices/DirectJoybox.py @@ -55,7 +55,7 @@ class DirectJoybox(PandaObject): self.refCS = direct.cameraControl.coaMarker self.tempCS = direct.group.attachNewNode('JoyboxTempCS') # Text object to display current mode - self.readout = OnscreenText.OnscreenText( '', -0.9, -0.95 ) + self.readout = OnscreenText.OnscreenText( pos = (-0.9, -0.95) ) # List of functions to cycle through self.modeList = [self.joeMode, self.driveMode, self.orbitMode] # Pick initial mode diff --git a/direct/src/directtools/DirectSession.py b/direct/src/directtools/DirectSession.py index 93fdb8ed55..14854f37f3 100644 --- a/direct/src/directtools/DirectSession.py +++ b/direct/src/directtools/DirectSession.py @@ -45,7 +45,7 @@ class DirectSession(PandaObject): self.ancestry = [] self.ancestryIndex = 0 - self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 ) + self.readout = OnscreenText.OnscreenText( pos = (0.1, -0.95) ) # Make sure readout is never lit or drawn in wireframe useDirectRenderStyle(self.readout) # self.readout.textNode.setCardColor(0.5, 0.5, 0.5, 0.5) diff --git a/direct/src/gui/ForceAcknowledge.py b/direct/src/gui/ForceAcknowledge.py index ca5a8d9c6d..22cc240646 100644 --- a/direct/src/gui/ForceAcknowledge.py +++ b/direct/src/gui/ForceAcknowledge.py @@ -51,14 +51,9 @@ class ForceAcknowledge(StateData.StateData): return None # create a message - self.text = OnscreenText.OnscreenText("", 0.0, 0.25) - self.text.freeze() - self.text.node().setAlign(0) - self.text.node().setTextColor(0.0, 0.0, 0.0, 1.0) - self.text.node().setFrameColor(1.0, 1.0, 1.0, 1.0) - self.text.setScale(0.08) - self.text.thaw() - + self.text = OnscreenText.OnscreenText(parent = hidden, + scale = 0.08, + pos = (0.0, 0.25)) # create a button self.okButton = Button.Button("ForceAcknowledge", "OK") self.okButton.setPos(0.0, -0.5) @@ -79,9 +74,6 @@ class ForceAcknowledge(StateData.StateData): self.exit() # GUI - self.text.removeNode() - self.text.cleanup() - self.text = None self.okButton.cleanup() self.okButton = None self.isLoaded = 0 diff --git a/direct/src/gui/Label.py b/direct/src/gui/Label.py index 6aa0422b3a..dbc8294588 100644 --- a/direct/src/gui/Label.py +++ b/direct/src/gui/Label.py @@ -88,6 +88,9 @@ def textLabelAndText(string, style, elif style == ScrollItem: pass + else: + raise ValueError + # Don't set the text until the very last thing, so the TextNode # has minimal work to do (even though it's frozen). diff --git a/direct/src/gui/OnscreenText.py b/direct/src/gui/OnscreenText.py index ded74da4da..670f54a740 100644 --- a/direct/src/gui/OnscreenText.py +++ b/direct/src/gui/OnscreenText.py @@ -2,75 +2,240 @@ from PandaObject import * from GuiGlobals import * +import types + +## These are the styles of text we might commonly see. They set the +## overall appearance of the text according to one of a number of +## pre-canned styles. You can further customize the appearance of the +## text by specifying individual parameters as well. +Plain = 1 +ScreenTitle = 2 +ScreenPrompt = 3 +NameConfirm = 4 class OnscreenText(PandaObject, NodePath): - def __init__(self, string, x=0.0, y=0.0, font=getDefaultFont()): - """__init__(self, string, float=0.0, float=0.0, - font=GuiGlobals.getDefaultFont()) - Make a text node from string, put it into the 2d sg and place - it at x, y in screen space + def __init__(self, text = '', + style = Plain, + pos = (0, 0), + scale = None, + fg = None, + bg = None, + shadow = None, + frame = None, + align = None, + wordwrap = None, + drawOrder = getDefaultDrawOrder(), + font = getDefaultFont(), + parent = aspect2d): + """__init__(self, ...) + + Make a text node from string, put it into the 2d sg and set it + up with all the indicated parameters. + """ - # become one with our NodePath-ness - NodePath.__init__(self) # make a text node - self.textNode = textNode = TextNode() + textNode = TextNode() + self.textNode = textNode + + # We ARE the node path that references this text node. + NodePath.__init__(self, parent.attachNewNode(textNode)) + + # Choose the default parameters according to the selected + # style. + if style == Plain: + scale = scale or 0.07 + fg = fg or (0, 0, 0, 1) + bg = bg or (0, 0, 0, 0) + shadow = shadow or (0, 0, 0, 0) + frame = frame or (0, 0, 0, 0) + align = align or TMALIGNCENTER + + elif style == ScreenTitle: + scale = scale or 0.15 + fg = fg or (1, 0.2, 0.2, 1) + bg = bg or (0, 0, 0, 0) + shadow = shadow or (0, 0, 0, 1) + frame = frame or (0, 0, 0, 0) + align = align or TMALIGNCENTER + + elif style == ScreenPrompt: + scale = scale or 0.1 + fg = fg or (1, 1, 0, 1) + bg = bg or (0, 0, 0, 0) + shadow = shadow or (0, 0, 0, 1) + frame = frame or (0, 0, 0, 0) + align = align or TMALIGNCENTER + + elif style == NameConfirm: + scale = scale or 0.1 + fg = fg or (0, 1, 0, 1) + bg = bg or (0, 0, 0, 0) + shadow = shadow or (0, 0, 0, 0) + frame = frame or (0, 0, 0, 0) + align = align or TMALIGNCENTER + + else: + raise ValueError + + if not isinstance(scale, types.TupleType): + # If the scale is already a tuple, it's a 2-d (x,y) scale. + # Otherwise, it's a uniform scale--make it a tuple. + scale = (scale, scale) + + # Save some of the parameters for posterity. + self.scale = scale + self.pos = pos + # Freeze the node while we set all the properties textNode.freeze() - textNode.setBillboard(0) - textNode.setTextColor(0.0, 0.0, 0.0, 1.0) - textNode.setCardColor(1.0, 1.0, 1.0, 1.0) - textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1) - # textNode.setFrameColor(0.0, 0.0, 0.0, 1.0) - # textNode.setFrameAsMargin(0.15, 0.15, 0.15, 0.15) textNode.setFont(font) - textNode.setText(string) - textNode.clearCardBorder() - textNode.clearFrame() + textNode.setTextColor(fg[0], fg[1], fg[2], fg[3]) + textNode.setAlign(align) + + if wordwrap: + textNode.setWordwrap(wordwrap) + + if bg[3] != 0: + # If we have a background color, create a card. + textNode.setCardColor(bg[0], bg[1], bg[2], bg[3]) + textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1) + + if shadow[3] != 0: + # If we have a shadow color, create a shadow. + textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3]) + textNode.setShadow(0.03, 0.03) + + if frame[3] != 0: + # If we have a frame color, create a frame. + textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3]) + textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1) + + # Create a transform for the text for our scale and position. + # We'd rather do it here, on the text itself, rather than on + # our NodePath, so we have one fewer transforms in the scene + # graph. + mat = Mat4.scaleMat(scale[0], 1, scale[1]) * Mat4.translateMat(pos[0], 0, pos[1]) + textNode.setTransform(mat) + + textNode.setBin('fixed') + textNode.setDrawOrder(drawOrder) + + textNode.setText(text) + # Ok, now update the node textNode.thaw() - - # put the text node into the 2d scene graph - textNodePath = render2d.attachNewNode(textNode) - - # we ARE this node path - self.assign(textNodePath) - - # position ourselves - self.setPos(x, y) - - self.setScale(0.069) self.isClean = 0 - return None + def __del__(self): + # Make sure the node is removed when we delete the + # OnscreenText object. This means we don't have to explicitly + # remove an OnscreenText object; it can do it by itself. + # Maybe this will be too confusing because we *do* have to + # explicitly remove other kinds of onscreen objects. + self.cleanup() + def cleanup(self): """cleanup(self) """ self.textNode = None if self.isClean == 0: self.isClean = 1 + if self.hasArcs(): + self.removeNode() NodePath.__del__(self) - return None def freeze(self): self.textNode.freeze() def thaw(self): self.textNode.thaw() - - def setText(self, string): - """setText(self, string) - Set the text of the onscreen text - """ - self.node().setText(string) + + # Allow changing of several of the parameters after the text has + # been created. These should be used with caution; it is better + # to set all the parameters up front. These functions are + # primarily intended for interactive placement of the initial + # text, and for those rare occasions when you actually want to + # change a text's property after it has been created. + + # If you need to change several properties at the same time at + # runtime, you should call freeze() first and thaw() afterward. + + def setText(self, text): + self.textNode.setText(text) def setPos(self, x, y): """setPos(self, float, float) Position the onscreen text in 2d screen space """ - # render2d has x across and z up - NodePath.setPos(self, x, 0.0, y) + self.pos = (x, y) + mat = Mat4.scaleMat(self.scale[0], 1, self.scale[1]) * Mat4.translateMat(self.pos[0], 0, self.pos[1]) + self.textNode.setTransform(mat) - def setColor(self, color): - self.textNode.setCardColor(color[0],color[1],color[2],color[3]) + def getPos(self): + return self.pos + + def setScale(self, sx, sy = None): + """setScale(self, float, float) + Scale the text in 2d space. You may specify either a single + uniform scale, or two scales, or a tuple of two scales. + """ + + if sy == None: + if isinstance(sx, types.TupleType): + self.scale = sx + else: + self.scale = (sx, sx) + else: + self.scale = (sx, sy) + mat = Mat4.scaleMat(self.scale[0], 1, self.scale[1]) * Mat4.translateMat(self.pos[0], 0, self.pos[1]) + self.textNode.setTransform(mat) + + def getScale(self): + if self.scale[0] == self.scale[1]: + return self.scale[0] + else: + return self.scale + + def setWordwrap(self, wordwrap): + if wordwrap: + self.textNode.setWordwrap(wordwrap) + else: + self.textNode.clearWordwrap() + + def setFg(self, fg): + self.textNode.setTextColor(fg[0], fg[1], fg[2], fg[3]) + + def setBg(self, bg): + self.textNode.freeze() + if bg[3] != 0: + # If we have a background color, create a card. + self.textNode.setCardColor(bg[0], bg[1], bg[2], bg[3]) + self.textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1) + else: + # Otherwise, remove the card. + self.textNode.clearCard() + self.textNode.thaw() + + def setShadow(self, shadow): + self.textNode.freeze() + if shadow[3] != 0: + # If we have a shadow color, create a shadow. + self.textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3]) + self.textNode.setShadow(0.03, 0.03) + else: + # Otherwise, remove the shadow. + self.textNode.clearShadow() + self.textNode.thaw() + + def setFrame(self, frame): + self.textNode.freeze() + if frame[3] != 0: + # If we have a frame color, create a frame. + self.textNode.setFrameColor(frame[0], frame[1], frame[2], frame[3]) + self.textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1) + else: + # Otherwise, remove the frame. + self.textNode.clearFrame() + self.textNode.thaw() diff --git a/direct/src/leveleditor/LevelEditor.py b/direct/src/leveleditor/LevelEditor.py index cc7106ecf1..676c34ec06 100644 --- a/direct/src/leveleditor/LevelEditor.py +++ b/direct/src/leveleditor/LevelEditor.py @@ -2838,7 +2838,7 @@ class LevelStyleManager: # Attach the color chips to the new menu and adjust sizes for i in range (numItems): # Create the node and set its color - node = OnscreenText(' ', 0.0, 0.0) + node = OnscreenText(' ') node.setColor(colorList[i]) bounds = node.getBounds() center = bounds.getCenter() @@ -2944,7 +2944,7 @@ class LevelStyleManager: for i in range (numItems): # Create onscreen text node for each item if (textList[i] != None): - node = OnscreenText(str(textList[i]),0,0) + node = OnscreenText(str(textList[i])) else: node = None if node: @@ -2952,10 +2952,12 @@ class LevelStyleManager: bounds = node.getBounds() center = bounds.getCenter() center = center * (sf * node.getScale()[0]) + node.freeze() node.setPos(radius * math.cos(i * angle) - center[0], ((radius * aspectRatio * math.sin(i * angle)) - center[2])) node.setScale(node.getScale() * sf) + node.thaw() # Add it to the newMenu node.reparentTo(newMenu) # Scale the whole shebang down by 0.5