"""Text2D -- screen-pinned text drawn through the one 2D text builder (P3a)."""
from ..descriptors import Property
from ..properties import Colour
from .node2d import Node2D
[docs]
class Text2D(Node2D):
"""Screen-pinned text for HUD / UI. Works in both 2D and 3D modes.
Text2D draws through the ONE 2D text builder (``renderer.draw_text``): the
item path emits a native kerned MSDF ``GLYPH`` item, and the ``Draw2D``
``_draw_recursive`` walk (editor play-mode / web) appends a matching ``TEXT``
op -- both from the same layout, so they render identically.
The text is **screen-pinned**: it draws in screen space (``screen_space=True``)
at its ``position`` (the inherited :class:`Node2D` ``Vec2``), so it stays fixed
while a Camera2D pans the world -- the usual HUD behaviour.
``font_scale`` is a multiplier on the 16px logical em (``scale=1`` -> 16 logical
px).
Common port patterns are first-class node Properties:
* ``outline`` / ``outline_colour`` -- a readable halo on any background.
* ``rect`` -- position + align inside a box instead of at a point.
* ``fit_to_width`` -- shrink ``font_scale`` so the text fits ``rect`` width.
"""
text = Property("", hint="Text to display")
font_scale = Property(1.0, range=(0.1, 100.0), hint="Font size (multiplier on 16px em)")
colour = Colour((1.0, 1.0, 1.0, 1.0))
# Horizontal anchor for ``position`` / box alignment inside ``rect``: "left"
# puts position.x at the text's left edge, "centre" centres on it, "right" puts
# it at the right edge. Each line is measured by the builder, so centring is
# exact.
align = Property("left", enum=["left", "centre", "right"], hint="Horizontal anchor")
# Outline halo: a 4-direction offset copy in
# ``outline_colour`` under the glyphs. ``outline`` is in glyph-em units (~0.08
# is a typical 1-2px halo); 0 disables it (the common case, zero added cost).
outline = Property(0.0, range=(0.0, 0.5), hint="Outline thickness (em units; 0 = none)")
outline_colour = Colour((0.0, 0.0, 0.0, 1.0))
# Box mode: when set, the text is positioned + aligned inside this rect
# (x, y, w, h) in screen pixels instead of anchored at ``position``.
rect = Property(None, hint="(x, y, w, h) box to align text inside (None = point at position)")
# Shrink font_scale so the text fits the box (or position.x..+width) horizontally.
fit_to_width = Property(False, hint="Shrink font_scale so text fits the rect/box width")
[docs]
def on_draw(self, renderer) -> None:
if not self.text:
return
ox, oy = getattr(self.tree, "overlay_offset", (0.0, 0.0)) if self.tree is not None else (0.0, 0.0)
fc = self.colour
if any(c > 1.0 for c in fc[:3]): # normalise 0-255 int colours
fc = tuple(c / 255.0 for c in fc)
oc = self.outline_colour
if any(c > 1.0 for c in oc[:3]):
oc = tuple(c / 255.0 for c in oc)
box = self.rect
if box is not None:
rect = (box[0] + ox, box[1] + oy, box[2], box[3])
renderer.draw_text(
self.text, rect=rect, scale=self.font_scale, colour=fc,
alignment=self.align, fit_to_width=self.fit_to_width,
outline=self.outline, outline_colour=oc, screen_space=True,
)
else:
pos = self.position
renderer.draw_text(
self.text, (pos.x + ox, pos.y + oy), scale=self.font_scale, colour=fc,
alignment=self.align, fit_to_width=self.fit_to_width,
outline=self.outline, outline_colour=oc, screen_space=True,
)