"""UI core module that describe UI abstract class."""
import abc
import numpy as np
from fury.decorators import warn_on_args_to_kwargs
from fury.interactor import CustomInteractorStyle
from fury.io import load_image
from fury.lib import (
Actor2D,
CellArray,
DiskSource,
FloatArray,
Points,
PolyData,
PolyDataMapper2D,
Polygon,
Property2D,
TextActor,
Texture,
TexturedActor2D,
)
from fury.utils import set_input
[docs]
class UI(object, metaclass=abc.ABCMeta):
"""An umbrella class for all UI elements.
While adding UI elements to the scene, we go over all the sub-elements
that come with it and add those to the scene automatically.
Attributes
----------
position : (float, float)
Absolute coordinates (x, y) of the lower-left corner of this
UI component.
center : (float, float)
Absolute coordinates (x, y) of the center of this UI component.
size : (int, int)
Width and height in pixels of this UI component.
on_left_mouse_button_pressed: function
Callback function for when the left mouse button is pressed.
on_left_mouse_button_released: function
Callback function for when the left mouse button is released.
on_left_mouse_button_clicked: function
Callback function for when clicked using the left mouse button
(i.e. pressed -> released).
on_left_mouse_double_clicked: function
Callback function for when left mouse button is double clicked
(i.e pressed -> released -> pressed -> released).
on_left_mouse_button_dragged: function
Callback function for when dragging using the left mouse button.
on_right_mouse_button_pressed: function
Callback function for when the right mouse button is pressed.
on_right_mouse_button_released: function
Callback function for when the right mouse button is released.
on_right_mouse_button_clicked: function
Callback function for when clicking using the right mouse button
(i.e. pressed -> released).
on_right_mouse_double_clicked: function
Callback function for when right mouse button is double clicked
(i.e pressed -> released -> pressed -> released).
on_right_mouse_button_dragged: function
Callback function for when dragging using the right mouse button.
on_middle_mouse_button_pressed: function
Callback function for when the middle mouse button is pressed.
on_middle_mouse_button_released: function
Callback function for when the middle mouse button is released.
on_middle_mouse_button_clicked: function
Callback function for when clicking using the middle mouse button
(i.e. pressed -> released).
on_middle_mouse_double_clicked: function
Callback function for when middle mouse button is double clicked
(i.e pressed -> released -> pressed -> released).
on_middle_mouse_button_dragged: function
Callback function for when dragging using the middle mouse button.
on_key_press: function
Callback function for when a keyboard key is pressed.
"""
@warn_on_args_to_kwargs()
def __init__(self, *, position=(0, 0)):
"""Init scene.
Parameters
----------
position : (float, float)
Absolute coordinates (x, y) of the lower-left corner of this
UI component.
"""
self._scene = object()
self._position = np.array([0, 0])
self._callbacks = []
self._setup() # Setup needed actors and sub UI components.
self.position = position
self.left_button_state = "released"
self.right_button_state = "released"
self.middle_button_state = "released"
self.on_left_mouse_button_pressed = lambda i_ren, obj, element: None
self.on_left_mouse_button_dragged = lambda i_ren, obj, element: None
self.on_left_mouse_button_released = lambda i_ren, obj, element: None
self.on_left_mouse_button_clicked = lambda i_ren, obj, element: None
self.on_left_mouse_double_clicked = lambda i_ren, obj, element: None
self.on_right_mouse_button_pressed = lambda i_ren, obj, element: None
self.on_right_mouse_button_released = lambda i_ren, obj, element: None
self.on_right_mouse_button_clicked = lambda i_ren, obj, element: None
self.on_right_mouse_double_clicked = lambda i_ren, obj, element: None
self.on_right_mouse_button_dragged = lambda i_ren, obj, element: None
self.on_middle_mouse_button_pressed = lambda i_ren, obj, element: None
self.on_middle_mouse_button_released = lambda i_ren, obj, element: None
self.on_middle_mouse_button_clicked = lambda i_ren, obj, element: None
self.on_middle_mouse_double_clicked = lambda i_ren, obj, element: None
self.on_middle_mouse_button_dragged = lambda i_ren, obj, element: None
self.on_key_press = lambda i_ren, obj, element: None
@abc.abstractmethod
def _setup(self):
"""Set up this UI component.
This is where you should create all your needed actors and sub UI
components.
"""
msg = "Subclasses of UI must implement `_setup(self)`."
raise NotImplementedError(msg)
@abc.abstractmethod
def _get_actors(self):
"""Get the actors composing this UI component."""
msg = "Subclasses of UI must implement `_get_actors(self)`."
raise NotImplementedError(msg)
@property
def actors(self):
"""Actors composing this UI component."""
return self._get_actors()
@abc.abstractmethod
def _add_to_scene(self, _scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
_scene : Scene
"""
msg = "Subclasses of UI must implement `_add_to_scene(self, scene)`."
raise NotImplementedError(msg)
[docs]
def add_to_scene(self, scene):
"""Allow UI objects to add their own props to the scene.
Parameters
----------
scene : scene
"""
self._add_to_scene(scene)
# Get a hold on the current interactor style.
iren = scene.GetRenderWindow().GetInteractor().GetInteractorStyle()
for callback in self._callbacks:
if not isinstance(iren, CustomInteractorStyle):
msg = (
"The ShowManager requires `CustomInteractorStyle` in"
" order to use callbacks."
)
raise TypeError(msg)
if callback[0] == self._scene:
iren.add_callback(iren, callback[1], callback[2], args=[self])
else:
# iren.add_callback(*callback, args=[self])
if len(callback) > 3:
iren.add_callback(*callback[:3], priority=callback[3], args=[self])
[docs]
@warn_on_args_to_kwargs()
def add_callback(self, prop, event_type, callback, *, priority=0):
"""Add a callback to a specific event for this UI component.
Parameters
----------
prop : vtkProp
The prop on which is callback is to be added.
event_type : string
The event code.
callback : function
The callback function.
priority : int
Higher number is higher priority.
"""
# Actually since we need an interactor style we will add the callback
# only when this UI component is added to the scene.
self._callbacks.append((prop, event_type, callback, priority))
@property
def position(self):
return self._position
@position.setter
def position(self, coords):
coords = np.asarray(coords)
self._set_position(coords)
self._position = coords
@abc.abstractmethod
def _set_position(self, _coords):
"""Position the lower-left corner of this UI component.
Parameters
----------
_coords: (float, float)
Absolute pixel coordinates (x, y).
"""
msg = "Subclasses of UI must implement `_set_position(self, coords)`."
raise NotImplementedError(msg)
@property
def size(self):
return np.asarray(self._get_size(), dtype=int)
@abc.abstractmethod
def _get_size(self):
msg = "Subclasses of UI must implement property `size`."
raise NotImplementedError(msg)
@property
def center(self):
return self.position + self.size / 2.0
@center.setter
def center(self, coords):
"""Position the center of this UI component.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
if not hasattr(self, "size"):
msg = "Subclasses of UI must implement the `size` property."
raise NotImplementedError(msg)
new_center = np.array(coords)
size = np.array(self.size)
new_lower_left_corner = new_center - size / 2.0
self.position = new_lower_left_corner
[docs]
def set_visibility(self, visibility):
"""Set visibility of this UI component."""
for actor in self.actors:
actor.SetVisibility(visibility)
[docs]
def handle_events(self, actor):
self.add_callback(
actor, "LeftButtonPressEvent", self.left_button_click_callback
)
self.add_callback(
actor, "LeftButtonReleaseEvent", self.left_button_release_callback
)
self.add_callback(
actor, "RightButtonPressEvent", self.right_button_click_callback
)
self.add_callback(
actor, "RightButtonReleaseEvent", self.right_button_release_callback
)
self.add_callback(
actor, "MiddleButtonPressEvent", self.middle_button_click_callback
)
self.add_callback(
actor, "MiddleButtonReleaseEvent", self.middle_button_release_callback
)
self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback)
self.add_callback(actor, "KeyPressEvent", self.key_press_callback)
[docs]
@staticmethod
def mouse_move_callback(i_ren, obj, self):
left_pressing_or_dragging = (
self.left_button_state == "pressing" or self.left_button_state == "dragging"
)
right_pressing_or_dragging = (
self.right_button_state == "pressing"
or self.right_button_state == "dragging"
)
middle_pressing_or_dragging = (
self.middle_button_state == "pressing"
or self.middle_button_state == "dragging"
)
if left_pressing_or_dragging:
self.left_button_state = "dragging"
self.on_left_mouse_button_dragged(i_ren, obj, self)
elif right_pressing_or_dragging:
self.right_button_state = "dragging"
self.on_right_mouse_button_dragged(i_ren, obj, self)
elif middle_pressing_or_dragging:
self.middle_button_state = "dragging"
self.on_middle_mouse_button_dragged(i_ren, obj, self)
[docs]
@staticmethod
def key_press_callback(i_ren, obj, self):
self.on_key_press(i_ren, obj, self)
[docs]
class Rectangle2D(UI):
"""A 2D rectangle sub-classed from UI."""
@warn_on_args_to_kwargs()
def __init__(self, *, size=(0, 0), position=(0, 0), color=(1, 1, 1), opacity=1.0):
"""Initialize a rectangle.
Parameters
----------
size : (int, int)
The size of the rectangle (width, height) in pixels.
position : (float, float)
Coordinates (x, y) of the lower-left corner of the rectangle.
color : (float, float, float)
Must take values in [0, 1].
opacity : float
Must take values in [0, 1].
"""
super(Rectangle2D, self).__init__(position=position)
self.color = color
self.opacity = opacity
self.resize(size)
def _setup(self):
"""Set up this UI component.
Creating the polygon actor used internally.
"""
# Setup four points
size = (1, 1)
self._points = Points()
self._points.InsertNextPoint(0, 0, 0)
self._points.InsertNextPoint(size[0], 0, 0)
self._points.InsertNextPoint(size[0], size[1], 0)
self._points.InsertNextPoint(0, size[1], 0)
# Create the polygon
polygon = Polygon()
polygon.GetPointIds().SetNumberOfIds(4) # make a quad
polygon.GetPointIds().SetId(0, 0)
polygon.GetPointIds().SetId(1, 1)
polygon.GetPointIds().SetId(2, 2)
polygon.GetPointIds().SetId(3, 3)
# Add the polygon to a list of polygons
polygons = CellArray()
polygons.InsertNextCell(polygon)
# Create a PolyData
self._polygonPolyData = PolyData()
self._polygonPolyData.SetPoints(self._points)
self._polygonPolyData.SetPolys(polygons)
# Create a mapper and actor
mapper = PolyDataMapper2D()
mapper = set_input(mapper, self._polygonPolyData)
self.actor = Actor2D()
self.actor.SetMapper(mapper)
# Add default events listener to the VTK actor.
self.handle_events(self.actor)
def _get_actors(self):
"""Get the actors composing this UI component."""
return [self.actor]
def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
scene.add(self.actor)
def _get_size(self):
# Get 2D coordinates of two opposed corners of the rectangle.
lower_left_corner = np.array(self._points.GetPoint(0)[:2])
upper_right_corner = np.array(self._points.GetPoint(2)[:2])
size = abs(upper_right_corner - lower_left_corner)
return size
@property
def width(self):
return self._points.GetPoint(2)[0]
@width.setter
def width(self, width):
self.resize((width, self.height))
@property
def height(self):
return self._points.GetPoint(2)[1]
@height.setter
def height(self, height):
self.resize((self.width, height))
[docs]
def resize(self, size):
"""Set the button size.
Parameters
----------
size : (float, float)
Button size (width, height) in pixels.
"""
self._points.SetPoint(0, 0, 0, 0.0)
self._points.SetPoint(1, size[0], 0, 0.0)
self._points.SetPoint(2, size[0], size[1], 0.0)
self._points.SetPoint(3, 0, size[1], 0.0)
self._polygonPolyData.SetPoints(self._points)
mapper = PolyDataMapper2D()
mapper = set_input(mapper, self._polygonPolyData)
self.actor.SetMapper(mapper)
def _set_position(self, coords):
"""Set the lower-left corner position of this UI component.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
self.actor.SetPosition(*coords)
@property
def color(self):
"""Get the rectangle's color."""
color = self.actor.GetProperty().GetColor()
return np.asarray(color)
@color.setter
def color(self, color):
"""Set the rectangle's color.
Parameters
----------
color : (float, float, float)
RGB. Must take values in [0, 1].
"""
self.actor.GetProperty().SetColor(*color)
@property
def opacity(self):
"""Get the rectangle's opacity."""
return self.actor.GetProperty().GetOpacity()
@opacity.setter
def opacity(self, opacity):
"""Set the rectangle's opacity.
Parameters
----------
opacity : float
Degree of transparency. Must be between [0, 1].
"""
self.actor.GetProperty().SetOpacity(opacity)
[docs]
class Disk2D(UI):
"""A 2D disk UI component."""
@warn_on_args_to_kwargs()
def __init__(
self,
outer_radius,
*,
inner_radius=0,
center=(0, 0),
color=(1, 1, 1),
opacity=1.0,
):
"""Initialize a 2D Disk.
Parameters
----------
outer_radius : int
Outer radius of the disk.
inner_radius : int, optional
Inner radius of the disk. A value > 0, makes a ring.
center : (float, float), optional
Coordinates (x, y) of the center of the disk.
color : (float, float, float), optional
Must take values in [0, 1].
opacity : float, optional
Must take values in [0, 1].
"""
super(Disk2D, self).__init__()
self.outer_radius = outer_radius
self.inner_radius = inner_radius
self.color = color
self.opacity = opacity
self.center = center
def _setup(self):
"""Setup this UI component.
Creating the disk actor used internally.
"""
# Setting up disk actor.
self._disk = DiskSource()
self._disk.SetRadialResolution(10)
self._disk.SetCircumferentialResolution(50)
self._disk.Update()
# Mapper
mapper = PolyDataMapper2D()
mapper = set_input(mapper, self._disk.GetOutputPort())
# Actor
self.actor = Actor2D()
self.actor.SetMapper(mapper)
# Add default events listener to the VTK actor.
self.handle_events(self.actor)
def _get_actors(self):
"""Get the actors composing this UI component."""
return [self.actor]
def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
scene.add(self.actor)
def _get_size(self):
diameter = 2 * self.outer_radius
size = (diameter, diameter)
return size
def _set_position(self, coords):
"""Set the lower-left corner position of this UI bounding box.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
# Disk actor are positioned with respect to their center.
self.actor.SetPosition(*coords + self.outer_radius)
@property
def color(self):
"""Get the color of this UI component."""
color = self.actor.GetProperty().GetColor()
return np.asarray(color)
@color.setter
def color(self, color):
"""Set the color of this UI component.
Parameters
----------
color : (float, float, float)
RGB. Must take values in [0, 1].
"""
self.actor.GetProperty().SetColor(*color)
@property
def opacity(self):
"""Get the opacity of this UI component."""
return self.actor.GetProperty().GetOpacity()
@opacity.setter
def opacity(self, opacity):
"""Set the opacity of this UI component.
Parameters
----------
opacity : float
Degree of transparency. Must be between [0, 1].
"""
self.actor.GetProperty().SetOpacity(opacity)
@property
def inner_radius(self):
return self._disk.GetInnerRadius()
@inner_radius.setter
def inner_radius(self, radius):
self._disk.SetInnerRadius(radius)
self._disk.Update()
@property
def outer_radius(self):
return self._disk.GetOuterRadius()
@outer_radius.setter
def outer_radius(self, radius):
self._disk.SetOuterRadius(radius)
self._disk.Update()
[docs]
class TextBlock2D(UI):
"""Wrap over the default vtkTextActor and helps setting the text.
Contains member functions for text formatting.
Attributes
----------
actor : :class:`vtkTextActor`
The text actor.
message : str
The initial text while building the actor.
position : (float, float)
(x, y) in pixels.
color : (float, float, float)
RGB: Values must be between 0-1.
bg_color : (float, float, float)
RGB: Values must be between 0-1.
font_size : int
Size of the text font.
font_family : str
Currently only supports Arial.
justification : str
left, right or center.
vertical_justification : str
bottom, middle or top.
bold : bool
Makes text bold.
italic : bool
Makes text italicised.
shadow : bool
Adds text shadow.
size : (int, int)
Size (width, height) in pixels of the text bounding box.
auto_font_scale : bool
Automatically scale font according to the text bounding box.
dynamic_bbox : bool
Automatically resize the bounding box according to the content.
"""
@warn_on_args_to_kwargs()
def __init__(
self,
*,
text="Text Block",
font_size=18,
font_family="Arial",
justification="left",
vertical_justification="bottom",
bold=False,
italic=False,
shadow=False,
size=None,
color=(1, 1, 1),
bg_color=None,
position=(0, 0),
auto_font_scale=False,
dynamic_bbox=False,
):
"""Init class instance.
Parameters
----------
text : str
The initial text while building the actor.
position : (float, float)
(x, y) in pixels.
color : (float, float, float)
RGB: Values must be between 0-1.
bg_color : (float, float, float)
RGB: Values must be between 0-1.
font_size : int
Size of the text font.
font_family : str
Currently only supports Arial.
justification : str
left, right or center.
vertical_justification : str
bottom, middle or top.
bold : bool
Makes text bold.
italic : bool
Makes text italicised.
shadow : bool
Adds text shadow.
size : (int, int)
Size (width, height) in pixels of the text bounding box.
auto_font_scale : bool, optional
Automatically scale font according to the text bounding box.
dynamic_bbox : bool, optional
Automatically resize the bounding box according to the content.
"""
self.boundingbox = [0, 0, 0, 0]
super(TextBlock2D, self).__init__(position=position)
self.scene = None
self.have_bg = bool(bg_color)
self.color = color
self.background_color = bg_color
self.font_family = font_family
self._justification = justification
self.bold = bold
self.italic = italic
self.shadow = shadow
self._vertical_justification = vertical_justification
self._dynamic_bbox = dynamic_bbox
self.auto_font_scale = auto_font_scale
self.message = text
self.font_size = font_size
if size is not None:
self.resize(size)
elif not self.dynamic_bbox:
# raise ValueError("TextBlock size is required as it is not dynamic.")
self.resize((0, 0))
def _setup(self):
self.actor = TextActor()
self.actor.GetPosition2Coordinate().SetCoordinateSystemToViewport()
self.background = Rectangle2D()
self.handle_events(self.actor)
[docs]
def resize(self, size):
"""Resize TextBlock2D.
Parameters
----------
size : (int, int)
Text bounding box size(width, height) in pixels.
"""
self.update_bounding_box(size=size)
def _get_actors(self):
"""Get the actors composing this UI component."""
return [self.actor] + self.background.actors
def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
scene.add(self.background, self.actor)
@property
def message(self):
"""Get message from the text.
Returns
-------
str
The current text message.
"""
return self.actor.GetInput()
@message.setter
def message(self, text):
"""Set the text message.
Parameters
----------
text : str
The message to be set.
"""
self.actor.SetInput(text)
if self.dynamic_bbox:
self.update_bounding_box()
@property
def font_size(self):
"""Get text font size.
Returns
-------
int
Text font size.
"""
return self.actor.GetTextProperty().GetFontSize()
@font_size.setter
def font_size(self, size):
"""Set font size.
Parameters
----------
size : int
Text font size.
"""
if not self.auto_font_scale:
self.actor.SetTextScaleModeToNone()
self.actor.GetTextProperty().SetFontSize(size)
if self.dynamic_bbox:
self.update_bounding_box()
@property
def font_family(self):
"""Get font family.
Returns
-------
str
Text font family.
"""
return self.actor.GetTextProperty().GetFontFamilyAsString()
@font_family.setter
def font_family(self, family="Arial"):
"""Set font family.
Currently Arial and Courier are supported.
Parameters
----------
family : str
The font family.
"""
if family == "Arial":
self.actor.GetTextProperty().SetFontFamilyToArial()
elif family == "Courier":
self.actor.GetTextProperty().SetFontFamilyToCourier()
else:
raise ValueError("Font not supported yet: {}.".format(family))
@property
def justification(self):
"""Get text justification.
Returns
-------
str
Text justification.
"""
return self._justification
@justification.setter
def justification(self, justification):
"""Justify text.
Parameters
----------
justification : str
Possible values are left, right, center.
"""
self._justification = justification
self.update_alignment()
@property
def vertical_justification(self):
"""Get text vertical justification.
Returns
-------
str
Text vertical justification.
"""
return self._vertical_justification
@vertical_justification.setter
def vertical_justification(self, vertical_justification):
"""Justify text vertically.
Parameters
----------
vertical_justification : str
Possible values are bottom, middle, top.
"""
self._vertical_justification = vertical_justification
self.update_alignment()
@property
def bold(self):
"""Return whether the text is bold.
Returns
-------
bool
Text is bold if True.
"""
return self.actor.GetTextProperty().GetBold()
@bold.setter
def bold(self, flag):
"""Bold/un-bold text.
Parameters
----------
flag : bool
Sets text bold if True.
"""
self.actor.GetTextProperty().SetBold(flag)
@property
def italic(self):
"""Return whether the text is italicised.
Returns
-------
bool
Text is italicised if True.
"""
return self.actor.GetTextProperty().GetItalic()
@italic.setter
def italic(self, flag):
"""Italicise/un-italicise text.
Parameters
----------
flag : bool
Italicises text if True.
"""
self.actor.GetTextProperty().SetItalic(flag)
@property
def shadow(self):
"""Return whether the text has shadow.
Returns
-------
bool
Text is shadowed if True.
"""
return self.actor.GetTextProperty().GetShadow()
@shadow.setter
def shadow(self, flag):
"""Add/remove text shadow.
Parameters
----------
flag : bool
Shadows text if True.
"""
self.actor.GetTextProperty().SetShadow(flag)
@property
def color(self):
"""Get text color.
Returns
-------
(float, float, float)
Returns text color in RGB.
"""
return self.actor.GetTextProperty().GetColor()
@color.setter
def color(self, color=(1, 0, 0)):
"""Set text color.
Parameters
----------
color : (float, float, float)
RGB: Values must be between 0-1.
"""
self.actor.GetTextProperty().SetColor(*color)
@property
def background_color(self):
"""Get background color.
Returns
-------
(float, float, float) or None
If None, there no background color.
Otherwise, background color in RGB.
"""
if not self.have_bg:
return None
return self.background.color
@background_color.setter
def background_color(self, color):
"""Set text color.
Parameters
----------
color : (float, float, float) or None
If None, remove background.
Otherwise, RGB values (must be between 0-1).
"""
if color is None:
# Remove background.
self.have_bg = False
self.background.set_visibility(False)
else:
self.have_bg = True
self.background.set_visibility(True)
self.background.color = color
@property
def auto_font_scale(self):
"""Return whether text font is automatically scaled.
Returns
-------
bool
Text is auto_font_scaled if True.
"""
return self._auto_font_scale
@auto_font_scale.setter
def auto_font_scale(self, flag):
"""Add/remove text auto_font_scale.
Parameters
----------
flag : bool
Automatically scales the text font if True.
"""
self._auto_font_scale = flag
if flag:
self.actor.SetTextScaleModeToProp()
self._justification = "left"
self.update_bounding_box(size=self.size)
else:
self.actor.SetTextScaleModeToNone()
@property
def dynamic_bbox(self):
"""Automatically resize the bounding box according to the content.
Returns
-------
bool
Bounding box is dynamic if True.
"""
return self._dynamic_bbox
@dynamic_bbox.setter
def dynamic_bbox(self, flag):
"""Add/remove dynamic_bbox.
Parameters
----------
flag : bool
The text bounding box is dynamic if True.
"""
self._dynamic_bbox = flag
if flag:
self.update_bounding_box()
[docs]
def update_alignment(self):
"""Update Text Alignment."""
text_property = self.actor.GetTextProperty()
updated_text_position = [0, 0]
if self.justification.lower() == "left":
text_property.SetJustificationToLeft()
updated_text_position[0] = self.boundingbox[0]
elif self.justification.lower() == "center":
text_property.SetJustificationToCentered()
updated_text_position[0] = (
self.boundingbox[0] + (self.boundingbox[2] - self.boundingbox[0]) // 2
)
elif self.justification.lower() == "right":
text_property.SetJustificationToRight()
updated_text_position[0] = self.boundingbox[2]
else:
msg = "Text can only be justified left, right and center."
raise ValueError(msg)
if self.vertical_justification.lower() == "bottom":
text_property.SetVerticalJustificationToBottom()
updated_text_position[1] = self.boundingbox[1]
elif self.vertical_justification.lower() == "middle":
text_property.SetVerticalJustificationToCentered()
updated_text_position[1] = (
self.boundingbox[1] + (self.boundingbox[3] - self.boundingbox[1]) // 2
)
elif self.vertical_justification.lower() == "top":
text_property.SetVerticalJustificationToTop()
updated_text_position[1] = self.boundingbox[3]
else:
msg = "Vertical justification must be: bottom, middle or top."
raise ValueError(msg)
self.actor.SetPosition(updated_text_position)
[docs]
def cal_size_from_message(self):
"""Calculate size of background according to the message it contains."""
lines = self.message.split("\n")
max_length = max(len(line) for line in lines)
return [max_length * self.font_size, len(lines) * self.font_size]
[docs]
@warn_on_args_to_kwargs()
def update_bounding_box(self, *, size=None):
"""Update Text Bounding Box.
Parameters
----------
size : (int, int) or None
If None, calculates bounding box.
Otherwise, uses the given size.
"""
if size is None:
size = self.cal_size_from_message()
self.boundingbox = [
self.position[0],
self.position[1],
self.position[0] + size[0],
self.position[1] + size[1],
]
self.background.resize(size)
if self.auto_font_scale:
self.actor.SetPosition2(
self.boundingbox[2] - self.boundingbox[0],
self.boundingbox[3] - self.boundingbox[1],
)
else:
self.update_alignment()
def _set_position(self, position):
"""Set text actor position.
Parameters
----------
position : (float, float)
The new position. (x, y) in pixels.
"""
self.actor.SetPosition(*position)
self.background.position = position
def _get_size(self):
bb_size = (
self.boundingbox[2] - self.boundingbox[0],
self.boundingbox[3] - self.boundingbox[1],
)
if self.dynamic_bbox or self.auto_font_scale or sum(bb_size):
return bb_size
return self.cal_size_from_message()