"""OnscreenText module: contains the OnscreenText class.
See the :ref:`onscreentext` page in the programming manual for explanation of
this class.
"""
__all__ = ['OnscreenText', 'Plain', 'ScreenTitle', 'ScreenPrompt', 'NameConfirm', 'BlackOnWhite']
from panda3d.core import *
from . import DirectGuiGlobals as DGG
import warnings
## 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
[docs]class OnscreenText(NodePath):
[docs] 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.
Parameters:
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 is None:
from direct.showbase import ShowBaseGlobal
parent = ShowBaseGlobal.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 is 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 is 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 is 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 is 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 is 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 is 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 is not 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))
[docs] def cleanup(self):
self.textNode = None
if self.isClean == 0:
self.isClean = 1
self.removeNode()
[docs] def destroy(self):
self.cleanup()
[docs] def freeze(self):
pass
[docs] 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.
[docs] def setDecal(self, decal):
self.textNode.setCardDecal(decal)
[docs] def getDecal(self):
return self.textNode.getCardDecal()
decal = property(getDecal, setDecal)
[docs] def setFont(self, font):
self.textNode.setFont(font)
[docs] def getFont(self):
return self.textNode.getFont()
font = property(getFont, setFont)
[docs] def clearText(self):
self.textNode.clearText()
[docs] def setText(self, text):
assert not isinstance(text, bytes)
self.textNode.setWtext(text)
[docs] def appendText(self, text):
assert not isinstance(text, bytes)
self.textNode.appendWtext(text)
[docs] def getText(self):
return self.textNode.getWtext()
text = property(getText, setText)
[docs] def setTextX(self, x):
"""
.. versionadded:: 1.10.8
"""
self.setTextPos(x, self.__pos[1])
[docs] def setX(self, x):
"""
.. deprecated:: 1.11.0
Use `.setTextX()` method instead.
"""
if __debug__:
warnings.warn("Use `.setTextX()` method instead.", DeprecationWarning, stacklevel=2)
self.setTextPos(x, self.__pos[1])
[docs] def setTextY(self, y):
"""
.. versionadded:: 1.10.8
"""
self.setTextPos(self.__pos[0], y)
[docs] def setY(self, y):
"""
.. deprecated:: 1.11.0
Use `.setTextY()` method instead.
"""
if __debug__:
warnings.warn("Use `.setTextY()` method instead.", DeprecationWarning, stacklevel=2)
self.setTextPos(self.__pos[0], y)
[docs] def setTextPos(self, x, y=None):
"""
Position the onscreen text in 2d screen space
.. versionadded:: 1.10.8
"""
if y is None:
self.__pos = tuple(x)
else:
self.__pos = (x, y)
self.updateTransformMat()
[docs] def getTextPos(self):
"""
.. versionadded:: 1.10.8
"""
return self.__pos
text_pos = property(getTextPos, setTextPos)
[docs] def setPos(self, x, y):
"""setPos(self, float, float)
Position the onscreen text in 2d screen space
.. deprecated:: 1.11.0
Use `.setTextPos()` method or `.text_pos` property instead.
"""
if __debug__:
warnings.warn("Use `.setTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
self.__pos = (x, y)
self.updateTransformMat()
[docs] def getPos(self):
"""
.. deprecated:: 1.11.0
Use `.getTextPos()` method or `.text_pos` property instead.
"""
if __debug__:
warnings.warn("Use `.getTextPos()` method or `.text_pos` property instead.", DeprecationWarning, stacklevel=2)
return self.__pos
pos = property(getPos)
[docs] def setTextR(self, r):
"""setTextR(self, float)
Rotates the text around the screen's normal.
.. versionadded:: 1.10.8
"""
self.__roll = -r
self.updateTransformMat()
[docs] def getTextR(self):
return -self.__roll
text_r = property(getTextR, setTextR)
[docs] def setRoll(self, roll):
"""setRoll(self, float)
Rotate the onscreen text around the screen's normal.
.. deprecated:: 1.11.0
Use ``setTextR(-roll)`` instead (note the negated sign).
"""
if __debug__:
warnings.warn("Use ``setTextR(-roll)`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
self.__roll = roll
self.updateTransformMat()
[docs] def getRoll(self):
"""
.. deprecated:: 1.11.0
Use ``-getTextR()`` instead (note the negated sign).
"""
if __debug__:
warnings.warn("Use ``-getTextR()`` instead (note the negated sign).", DeprecationWarning, stacklevel=2)
return self.__roll
roll = property(getRoll, setRoll)
[docs] def setTextScale(self, sx, sy = None):
"""setTextScale(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.
.. versionadded:: 1.10.8
"""
if sy is None:
if isinstance(sx, tuple):
self.__scale = sx
else:
self.__scale = (sx, sx)
else:
self.__scale = (sx, sy)
self.updateTransformMat()
[docs] def getTextScale(self):
"""
.. versionadded:: 1.10.8
"""
return self.__scale
text_scale = property(getTextScale, setTextScale)
[docs] 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.
.. deprecated:: 1.11.0
Use `.setTextScale()` method or `.text_scale` property instead.
"""
if __debug__:
warnings.warn("Use `.setTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
if sy is None:
if isinstance(sx, tuple):
self.__scale = sx
else:
self.__scale = (sx, sx)
else:
self.__scale = (sx, sy)
self.updateTransformMat()
[docs] def getScale(self):
"""
.. deprecated:: 1.11.0
Use `.getTextScale()` method or `.text_scale` property instead.
"""
if __debug__:
warnings.warn("Use `.getTextScale()` method or `.text_scale` property instead.", DeprecationWarning, stacklevel=2)
return self.__scale
scale = property(getScale, setScale)
[docs] 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)
[docs] def setWordwrap(self, wordwrap):
self.__wordwrap = wordwrap
if wordwrap:
self.textNode.setWordwrap(wordwrap)
else:
self.textNode.clearWordwrap()
[docs] def getWordwrap(self):
return self.__wordwrap
wordwrap = property(getWordwrap, setWordwrap)
def __getFg(self):
return self.textNode.getTextColor()
[docs] 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)
[docs] 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()
[docs] 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()
[docs] 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)
[docs] 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:
if option == 'pos':
self.setTextPos(value[0], value[1])
elif option == 'roll':
self.setTextR(-value)
elif option == 'scale':
self.setTextScale(value)
elif option == 'x':
self.setTextX(value)
elif option == 'y':
self.setTextY(value)
else:
setter = getattr(self, 'set' + option[0].upper() + option[1:])
setter(value)
except AttributeError:
print('OnscreenText.configure: invalid option: %s' % option)
# Allow index style references
def __setitem__(self, key, value):
self.configure(*(), **{key: value})
[docs] def cget(self, option):
# Get current configuration setting.
# This is for compatibility with DirectGui functions
if option == 'pos':
return self.__pos
elif option == 'roll':
return self.__roll
elif option == 'scale':
return self.__scale
elif option == 'x':
return self.__pos[0]
elif option == 'y':
return self.__pos[1]
getter = getattr(self, 'get' + option[0].upper() + option[1:])
return getter()
def __getAlign(self):
return self.textNode.getAlign()
[docs] def setAlign(self, align):
self.textNode.setAlign(align)
align = property(__getAlign, setAlign)
# Allow index style refererences
__getitem__ = cget