import numpy as np
import pygfx
from ..utils.enums import RenderQueue
class MeshMasks:
"""Used set the x0, x1, y0, y1 positions of the plane mesh"""
x0 = np.array(
[
[False, False, False],
[True, False, False],
[False, False, False],
[True, False, False],
]
)
x1 = np.array(
[
[True, False, False],
[False, False, False],
[True, False, False],
[False, False, False],
]
)
y0 = np.array(
[
[False, True, False],
[False, True, False],
[False, False, False],
[False, False, False],
]
)
y1 = np.array(
[
[False, False, False],
[False, False, False],
[False, True, False],
[False, True, False],
]
)
masks = MeshMasks
[docs]
class TextBox:
def __init__(
self,
font_size: int = 12,
text_color: str | pygfx.Color | tuple = "w",
background_color: str | pygfx.Color | tuple = (0.1, 0.1, 0.3, 0.95),
outline_color: str | pygfx.Color | tuple = (0.8, 0.8, 1.0, 1.0),
padding: tuple[float, float] = (5, 5),
):
"""
Create a Textbox
Parameters
----------
font_size: int, default 12
text font size
text_color: str | pygfx.Color | tuple, default "w"
text color, interpretable by pygfx.Color
background_color: str | pygfx.Color | tuple, default (0.1, 0.1, 0.3, 0.95),
background color, interpretable by pygfx.Color
outline_color: str | pygfx.Color | tuple, default (0.8, 0.8, 1.0, 1.0)
outline color, interpretable by pygfx.Color
padding: (float, float), default (5, 5)
the amount of pixels in (x, y) by which to extend the rectangle behind the text
"""
# text object
self._text = pygfx.Text(
text="",
font_size=font_size,
screen_space=False, # these are added to the overlay render pass so it will actually be in screen space!
anchor="bottom-left",
material=pygfx.TextMaterial(
alpha_mode="blend",
aa=True,
render_queue=RenderQueue.overlay,
color=text_color,
depth_write=False,
depth_test=False,
pick_write=False,
),
)
# plane for the background of the text object
geometry = pygfx.plane_geometry(1, 1)
material = pygfx.MeshBasicMaterial(
alpha_mode="blend",
render_queue=RenderQueue.overlay,
color=background_color,
depth_write=False,
depth_test=False,
)
self._plane = pygfx.Mesh(geometry, material)
# line to outline the plane mesh
self._line = pygfx.Line(
geometry=pygfx.Geometry(
positions=np.array(
[
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
],
dtype=np.float32,
)
),
material=pygfx.LineThinMaterial(
alpha_mode="blend",
render_queue=RenderQueue.overlay,
thickness=1.0,
color=outline_color,
depth_write=False,
depth_test=False,
),
)
# Plane gets rendered before text and line
self._plane.render_order = -1
self._fpl_world_object = pygfx.Group()
self._fpl_world_object.add(self._plane, self._text, self._line)
# padded to bbox so the background box behind the text extends a bit further
# making the text easier to read
self._padding = np.zeros(shape=(2, 3), dtype=np.float32)
self.padding = padding
# position of the tooltip in screen space
self._position = np.array([0.0, 0.0])
@property
def position(self) -> np.ndarray:
"""position of the tooltip in screen space"""
return self._position
@property
def font_size(self):
"""Get or set font size"""
return self._text.font_size
@font_size.setter
def font_size(self, size: float):
self._text.font_size = size
@property
def text_color(self):
"""Get or set text color using a str or RGB(A) array"""
return self._text.material.color
@text_color.setter
def text_color(self, color: str | tuple | list | np.ndarray):
self._text.material.color = color
@property
def background_color(self):
"""Get or set background color using a str or RGB(A) array"""
return self._plane.material.color
@background_color.setter
def background_color(self, color: str | tuple | list | np.ndarray):
self._plane.material.color = color
@property
def outline_color(self):
"""Get or set outline color using a str or RGB(A) array"""
return self._line.material.color
@outline_color.setter
def outline_color(self, color: str | tuple | list | np.ndarray):
self._line.material.color = color
@property
def padding(self) -> np.ndarray:
"""
Get or set the background padding in number of pixels.
The padding defines the number of pixels around the tooltip text that the background is extended by.
"""
return self.padding[0, :2].copy()
@padding.setter
def padding(self, padding_xy: tuple[float, float]):
self._padding[0, :2] = padding_xy
self._padding[1, :2] = -np.asarray(padding_xy)
@property
def visible(self) -> bool:
"""get or set the visibility"""
return self._fpl_world_object.visible
@visible.setter
def visible(self, visible: bool):
self._fpl_world_object.visible = visible
[docs]
def display(self, position: tuple[float, float], info: str):
"""
display at the given position in screen space
Parameters
----------
position: (x, y)
position in screen space
info: str
tooltip text to display
"""
# set the text and top left position of the tooltip
self.visible = True
self._text.set_text(info)
self._draw_tooltip(position)
self._position[:] = position
def _draw_tooltip(self, pos: tuple[float, float]):
"""
Sets the positions of the world objects so it's draw at the given position
Parameters
----------
pos: [float, float]
position in screen space
"""
if np.array_equal(self.position, pos):
return
# need to flip due to inverted y
x, y = pos[0], pos[1]
# put the tooltip slightly to the top right of the cursor positoin
x += 8
y -= 8
self._text.world.position = (x, -y, 0)
bbox = self._text.get_world_bounding_box() - self._padding
[[x0, y0, _], [x1, y1, _]] = bbox
self._plane.geometry.positions.data[masks.x0] = x0
self._plane.geometry.positions.data[masks.x1] = x1
self._plane.geometry.positions.data[masks.y0] = y0
self._plane.geometry.positions.data[masks.y1] = y1
self._plane.geometry.positions.update_range()
# line points
pts = [[x0, y0], [x0, y1], [x1, y1], [x1, y0], [x0, y0]]
self._line.geometry.positions.data[:, :2] = pts
self._line.geometry.positions.update_range()
[docs]
def clear(self, *args):
"""clear the text box and make it invisible"""
self._text.set_text("")
self._fpl_world_object.visible = False