472 lines
15 KiB
Python
472 lines
15 KiB
Python
"""OnscreenText module: contains the OnscreenText class"""
|
|
|
|
__all__ = ['OnscreenText', 'Plain', 'ScreenTitle', 'ScreenPrompt', 'NameConfirm', 'BlackOnWhite']
|
|
|
|
from panda3d.core import *
|
|
from . import DirectGuiGlobals as DGG
|
|
import sys
|
|
|
|
## 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
|
|
BlackOnWhite = 5
|
|
|
|
class OnscreenText(NodePath):
|
|
|
|
def __init__(self, text = '',
|
|
style = Plain,
|
|
pos = (0, 0),
|
|
roll = 0,
|
|
scale = None,
|
|
fg = None,
|
|
bg = None,
|
|
shadow = None,
|
|
shadowOffset = (0.04, 0.04),
|
|
frame = None,
|
|
align = None,
|
|
wordwrap = None,
|
|
drawOrder = None,
|
|
decal = 0,
|
|
font = None,
|
|
parent = None,
|
|
sort = 0,
|
|
mayChange = True,
|
|
direction = None):
|
|
"""
|
|
Make a text node from string, put it into the 2d sg and set it
|
|
up with all the indicated parameters.
|
|
|
|
The parameters are as follows:
|
|
|
|
text: the actual text to display. This may be omitted and
|
|
specified later via setText() if you don't have it
|
|
available, but it is better to specify it up front.
|
|
|
|
style: one of the pre-canned style parameters defined at the
|
|
head of this file. This sets up the default values for
|
|
many of the remaining parameters if they are
|
|
unspecified; however, a parameter may still be specified
|
|
to explicitly set it, overriding the pre-canned style.
|
|
|
|
pos: the x, y position of the text on the screen.
|
|
|
|
scale: the size of the text. This may either be a single
|
|
float (and it will usually be a small number like 0.07)
|
|
or it may be a 2-tuple of floats, specifying a different
|
|
x, y scale.
|
|
|
|
fg: the (r, g, b, a) foreground color of the text. This is
|
|
normally a 4-tuple of floats or ints.
|
|
|
|
bg: the (r, g, b, a) background color of the text. If the
|
|
fourth value, a, is nonzero, a card is created to place
|
|
behind the text and set to the given color.
|
|
|
|
shadow: the (r, g, b, a) color of the shadow behind the text.
|
|
If the fourth value, a, is nonzero, a little drop shadow
|
|
is created and placed behind the text.
|
|
|
|
frame: the (r, g, b, a) color of the frame drawn around the
|
|
text. If the fourth value, a, is nonzero, a frame is
|
|
created around the text.
|
|
|
|
align: one of TextNode.ALeft, TextNode.ARight, or TextNode.ACenter.
|
|
|
|
wordwrap: either the width to wordwrap the text at, or None
|
|
to specify no automatic word wrapping.
|
|
|
|
drawOrder: the drawing order of this text with respect to
|
|
all other things in the 'fixed' bin within render2d.
|
|
The text will actually use drawOrder through drawOrder +
|
|
2.
|
|
|
|
decal: if this is True, the text is decalled onto its
|
|
background card. Useful when the text will be parented
|
|
into the 3-D scene graph.
|
|
|
|
font: the font to use for the text.
|
|
|
|
parent: the NodePath to parent the text to initially.
|
|
|
|
mayChange: pass true if the text or its properties may need
|
|
to be changed at runtime, false if it is static once
|
|
created (which leads to better memory optimization).
|
|
|
|
direction: this can be set to 'ltr' or 'rtl' to override the
|
|
direction of the text.
|
|
"""
|
|
if parent == None:
|
|
parent = aspect2d
|
|
|
|
# make a text node
|
|
textNode = TextNode('')
|
|
self.textNode = textNode
|
|
|
|
# We ARE a node path. Initially, we're an empty node path.
|
|
NodePath.__init__(self)
|
|
|
|
# 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)
|
|
if align == None:
|
|
align = TextNode.ACenter
|
|
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)
|
|
if align == None:
|
|
align = TextNode.ACenter
|
|
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)
|
|
if align == None:
|
|
align = TextNode.ACenter
|
|
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)
|
|
if align == None:
|
|
align = TextNode.ACenter
|
|
elif style == BlackOnWhite:
|
|
scale = scale or 0.1
|
|
fg = fg or (0, 0, 0, 1)
|
|
bg = bg or (1, 1, 1, 1)
|
|
shadow = shadow or (0, 0, 0, 0)
|
|
frame = frame or (0, 0, 0, 0)
|
|
if align == None:
|
|
align = TextNode.ACenter
|
|
else:
|
|
raise ValueError
|
|
|
|
if not isinstance(scale, tuple):
|
|
# 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
|
|
self.__roll = roll
|
|
self.__wordwrap = wordwrap
|
|
|
|
if decal:
|
|
textNode.setCardDecal(1)
|
|
|
|
if font == None:
|
|
font = DGG.getDefaultFont()
|
|
|
|
textNode.setFont(font)
|
|
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.
|
|
# Can't use the *shadow interface because it might be a VBase4.
|
|
#textNode.setShadowColor(*shadow)
|
|
textNode.setShadowColor(shadow[0], shadow[1], shadow[2], shadow[3])
|
|
textNode.setShadow(*shadowOffset)
|
|
|
|
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)
|
|
|
|
if direction is not None:
|
|
if isinstance(direction, str):
|
|
direction = direction.lower()
|
|
if direction == 'rtl':
|
|
direction = TextProperties.D_rtl
|
|
elif direction == 'ltr':
|
|
direction = TextProperties.D_ltr
|
|
else:
|
|
raise ValueError('invalid direction')
|
|
textNode.setDirection(direction)
|
|
|
|
# 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.
|
|
self.updateTransformMat()
|
|
|
|
if drawOrder != None:
|
|
textNode.setBin('fixed')
|
|
textNode.setDrawOrder(drawOrder)
|
|
|
|
self.setText(text)
|
|
if not text:
|
|
# If we don't have any text, assume we'll be changing it later.
|
|
self.mayChange = 1
|
|
else:
|
|
self.mayChange = mayChange
|
|
|
|
# Ok, now update the node.
|
|
if not self.mayChange:
|
|
# If we aren't going to change the text later, we can
|
|
# throw away the TextNode.
|
|
self.textNode = textNode.generate()
|
|
|
|
self.isClean = 0
|
|
|
|
# Set ourselves up as the NodePath that points to this node.
|
|
self.assign(parent.attachNewNode(self.textNode, sort))
|
|
|
|
def cleanup(self):
|
|
self.textNode = None
|
|
if self.isClean == 0:
|
|
self.isClean = 1
|
|
self.removeNode()
|
|
|
|
def destroy(self):
|
|
self.cleanup()
|
|
|
|
def freeze(self):
|
|
pass
|
|
|
|
def thaw(self):
|
|
pass
|
|
|
|
# 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.
|
|
|
|
def setDecal(self, decal):
|
|
self.textNode.setCardDecal(decal)
|
|
|
|
def getDecal(self):
|
|
return self.textNode.getCardDecal()
|
|
|
|
decal = property(getDecal, setDecal)
|
|
|
|
def setFont(self, font):
|
|
self.textNode.setFont(font)
|
|
|
|
def getFont(self):
|
|
return self.textNode.getFont()
|
|
|
|
font = property(getFont, setFont)
|
|
|
|
def clearText(self):
|
|
self.textNode.clearText()
|
|
|
|
def setText(self, text):
|
|
if sys.version_info >= (3, 0):
|
|
assert not isinstance(text, bytes)
|
|
self.unicodeText = True
|
|
else:
|
|
self.unicodeText = isinstance(text, unicode)
|
|
|
|
if self.unicodeText:
|
|
self.textNode.setWtext(text)
|
|
else:
|
|
self.textNode.setText(text)
|
|
|
|
def appendText(self, text):
|
|
if sys.version_info >= (3, 0):
|
|
assert not isinstance(text, bytes)
|
|
self.unicodeText = True
|
|
else:
|
|
self.unicodeText = isinstance(text, unicode)
|
|
|
|
if self.unicodeText:
|
|
self.textNode.appendWtext(text)
|
|
else:
|
|
self.textNode.appendText(text)
|
|
|
|
def getText(self):
|
|
if self.unicodeText:
|
|
return self.textNode.getWtext()
|
|
else:
|
|
return self.textNode.getText()
|
|
|
|
text = property(getText, setText)
|
|
|
|
def setX(self, x):
|
|
self.setPos(x, self.__pos[1])
|
|
|
|
def setY(self, y):
|
|
self.setPos(self.__pos[0], y)
|
|
|
|
def setPos(self, x, y):
|
|
"""setPos(self, float, float)
|
|
Position the onscreen text in 2d screen space
|
|
"""
|
|
self.__pos = (x, y)
|
|
self.updateTransformMat()
|
|
|
|
def getPos(self):
|
|
return self.__pos
|
|
|
|
pos = property(getPos, setPos)
|
|
|
|
def setRoll(self, roll):
|
|
"""setRoll(self, float)
|
|
Rotate the onscreen text around the screen's normal
|
|
"""
|
|
self.__roll = roll
|
|
self.updateTransformMat()
|
|
|
|
def getRoll(self):
|
|
return self.__roll
|
|
|
|
roll = property(getRoll, setRoll)
|
|
|
|
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, tuple):
|
|
self.__scale = sx
|
|
else:
|
|
self.__scale = (sx, sx)
|
|
else:
|
|
self.__scale = (sx, sy)
|
|
self.updateTransformMat()
|
|
|
|
def updateTransformMat(self):
|
|
assert(isinstance(self.textNode, TextNode))
|
|
mat = (
|
|
Mat4.scaleMat(Vec3.rfu(self.__scale[0], 1, self.__scale[1])) *
|
|
Mat4.rotateMat(self.__roll, Vec3.back()) *
|
|
Mat4.translateMat(Point3.rfu(self.__pos[0], 0, self.__pos[1]))
|
|
)
|
|
self.textNode.setTransform(mat)
|
|
|
|
def getScale(self):
|
|
return self.__scale
|
|
|
|
scale = property(getScale, setScale)
|
|
|
|
def setWordwrap(self, wordwrap):
|
|
self.__wordwrap = wordwrap
|
|
|
|
if wordwrap:
|
|
self.textNode.setWordwrap(wordwrap)
|
|
else:
|
|
self.textNode.clearWordwrap()
|
|
|
|
def getWordwrap(self):
|
|
return self.__wordwrap
|
|
|
|
wordwrap = property(getWordwrap, setWordwrap)
|
|
|
|
def __getFg(self):
|
|
return self.textNode.getTextColor()
|
|
|
|
def setFg(self, fg):
|
|
self.textNode.setTextColor(fg[0], fg[1], fg[2], fg[3])
|
|
|
|
fg = property(__getFg, setFg)
|
|
|
|
def __getBg(self):
|
|
if self.textNode.hasCard():
|
|
return self.textNode.getCardColor()
|
|
else:
|
|
return LColor(0)
|
|
|
|
def setBg(self, bg):
|
|
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()
|
|
|
|
bg = property(__getBg, setBg)
|
|
|
|
def __getShadow(self):
|
|
return self.textNode.getShadowColor()
|
|
|
|
def setShadow(self, shadow):
|
|
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.04, 0.04)
|
|
else:
|
|
# Otherwise, remove the shadow.
|
|
self.textNode.clearShadow()
|
|
|
|
shadow = property(__getShadow, setShadow)
|
|
|
|
def __getFrame(self):
|
|
return self.textNode.getFrameColor()
|
|
|
|
def setFrame(self, frame):
|
|
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()
|
|
|
|
frame = property(__getFrame, setFrame)
|
|
|
|
def configure(self, option=None, **kw):
|
|
# These is for compatibility with DirectGui functions
|
|
if not self.mayChange:
|
|
print('OnscreenText.configure: mayChange == 0')
|
|
return
|
|
for option, value in kw.items():
|
|
# Use option string to access setter function
|
|
try:
|
|
setter = getattr(self, 'set' + option[0].upper() + option[1:])
|
|
if setter == self.setPos:
|
|
setter(value[0], value[1])
|
|
else:
|
|
setter(value)
|
|
except AttributeError:
|
|
print('OnscreenText.configure: invalid option: %s' % option)
|
|
|
|
# Allow index style references
|
|
def __setitem__(self, key, value):
|
|
self.configure(*(), **{key: value})
|
|
|
|
def cget(self, option):
|
|
# Get current configuration setting.
|
|
# This is for compatibility with DirectGui functions
|
|
getter = getattr(self, 'get' + option[0].upper() + option[1:])
|
|
return getter()
|
|
|
|
def __getAlign(self):
|
|
return self.textNode.getAlign()
|
|
|
|
def setAlign(self, align):
|
|
self.textNode.setAlign(align)
|
|
|
|
align = property(__getAlign, setAlign)
|
|
|
|
# Allow index style refererences
|
|
__getitem__ = cget
|
|
|