"""UI container module."""
from warnings import warn
import numpy as np
from fury.actor import grid
from fury.decorators import warn_on_args_to_kwargs
from fury.io import load_image
from fury.lib import (
CellArray,
FloatArray,
Points,
PolyData,
PolyDataMapper2D,
Property2D,
Texture,
TexturedActor2D,
)
from fury.ui.core import UI, Rectangle2D, TextBlock2D
from fury.utils import rotate, set_input
[docs]
class Panel2D(UI):
"""A 2D UI Panel.
Can contain one or more UI elements.
Attributes
----------
alignment : [left, right]
Alignment of the panel with respect to the overall screen.
"""
@warn_on_args_to_kwargs()
def __init__(
self,
size,
*,
position=(0, 0),
color=(0.1, 0.1, 0.1),
opacity=0.7,
align="left",
border_color=(1, 1, 1),
border_width=0,
has_border=False,
):
"""Init class instance.
Parameters
----------
size : (int, int)
Size (width, height) in pixels of the panel.
position : (float, float)
Absolute coordinates (x, y) of the lower-left corner of the panel.
color : (float, float, float)
Must take values in [0, 1].
opacity : float
Must take values in [0, 1].
align : [left, right]
Alignment of the panel with respect to the overall screen.
border_color: (float, float, float), optional
Must take values in [0, 1].
border_width: float, optional
width of the border
has_border: bool, optional
If the panel should have borders.
"""
self.has_border = has_border
self._border_color = border_color
self._border_width = border_width
super(Panel2D, self).__init__(position=position)
self.resize(size)
self.alignment = align
self.color = color
self.opacity = opacity
self.position = position
self._drag_offset = None
def _setup(self):
"""Setup this UI component.
Create the background (Rectangle2D) of the panel.
Create the borders (Rectangle2D) of the panel.
"""
self._elements = []
self.element_offsets = []
self.background = Rectangle2D()
if self.has_border:
self.borders = {
"left": Rectangle2D(),
"right": Rectangle2D(),
"top": Rectangle2D(),
"bottom": Rectangle2D(),
}
self.border_coords = {
"left": (0.0, 0.0),
"right": (1.0, 0.0),
"top": (0.0, 1.0),
"bottom": (0.0, 0.0),
}
for key in self.borders.keys():
self.borders[key].color = self._border_color
self.add_element(self.borders[key], self.border_coords[key])
for key in self.borders.keys():
self.borders[
key
].on_left_mouse_button_pressed = self.left_button_pressed
self.borders[
key
].on_left_mouse_button_dragged = self.left_button_dragged
self.add_element(self.background, (0, 0))
# Add default events listener for this UI component.
self.background.on_left_mouse_button_pressed = self.left_button_pressed
self.background.on_left_mouse_button_dragged = self.left_button_dragged
def _get_actors(self):
"""Get the actors composing this UI component."""
actors = []
for element in self._elements:
actors += element.actors
return actors
def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
for element in self._elements:
element.add_to_scene(scene)
def _get_size(self):
return self.background.size
[docs]
def resize(self, size):
"""Set the panel size.
Parameters
----------
size : (float, float)
Panel size (width, height) in pixels.
"""
self.background.resize(size)
if self.has_border:
self.borders["left"].resize(
(self._border_width, size[1] + self._border_width)
)
self.borders["right"].resize(
(self._border_width, size[1] + self._border_width)
)
self.borders["top"].resize(
(self.size[0] + self._border_width, self._border_width)
)
self.borders["bottom"].resize(
(self.size[0] + self._border_width, self._border_width)
)
self.update_border_coords()
def _set_position(self, coords):
"""Set the lower-left corner position of this UI component.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
coords = np.array(coords)
for element, offset in self.element_offsets:
element.position = coords + offset
[docs]
def set_visibility(self, visibility):
for element in self._elements:
element.set_visibility(visibility)
@property
def color(self):
return self.background.color
@color.setter
def color(self, color):
self.background.color = color
@property
def opacity(self):
return self.background.opacity
@opacity.setter
def opacity(self, opacity):
self.background.opacity = opacity
[docs]
@warn_on_args_to_kwargs()
def add_element(self, element, coords, *, anchor="position"):
"""Add a UI component to the panel.
The coordinates represent an offset from the lower left corner of the
panel.
Parameters
----------
element : UI
The UI item to be added.
coords : (float, float) or (int, int)
If float, normalized coordinates are assumed and they must be
between [0,1].
If int, pixels coordinates are assumed and it must fit within the
panel's size.
"""
coords = np.array(coords)
if np.issubdtype(coords.dtype, np.floating):
if np.any(coords < 0) or np.any(coords > 1):
raise ValueError("Normalized coordinates must be in [0,1].")
coords = coords * self.size
if anchor == "center":
element.center = self.position + coords
elif anchor == "position":
element.position = self.position + coords
else:
msg = "Unknown anchor {}. Supported anchors are 'position'" " and 'center'."
raise ValueError(msg)
self._elements.append(element)
offset = element.position - self.position
self.element_offsets.append((element, offset))
[docs]
def remove_element(self, element):
"""Remove a UI component from the panel.
Parameters
----------
element : UI
The UI item to be removed.
"""
idx = self._elements.index(element)
del self._elements[idx]
del self.element_offsets[idx]
[docs]
@warn_on_args_to_kwargs()
def update_element(self, element, coords, *, anchor="position"):
"""Update the position of a UI component in the panel.
Parameters
----------
element : UI
The UI item to be updated.
coords : (float, float) or (int, int)
New coordinates.
If float, normalized coordinates are assumed and they must be
between [0,1].
If int, pixels coordinates are assumed and it must fit within the
panel's size.
"""
self.remove_element(element)
self.add_element(element, coords, anchor=anchor)
[docs]
def re_align(self, window_size_change):
"""Re-organise the elements in case the window size is changed.
Parameters
----------
window_size_change : (int, int)
New window size (width, height) in pixels.
"""
if self.alignment == "left":
pass
elif self.alignment == "right":
self.position += np.array(window_size_change)
else:
msg = "You can only left-align or right-align objects in a panel."
raise ValueError(msg)
[docs]
def update_border_coords(self):
"""Update the coordinates of the borders"""
self.border_coords = {
"left": (0.0, 0.0),
"right": (1.0, 0.0),
"top": (0.0, 1.0),
"bottom": (0.0, 0.0),
}
for key in self.borders.keys():
self.update_element(self.borders[key], self.border_coords[key])
@property
def border_color(self):
sides = ["left", "right", "top", "bottom"]
return [self.borders[side].color for side in sides]
@border_color.setter
def border_color(self, side_color):
"""Set the color of a specific border
Parameters
----------
side_color: Iterable
Iterable to pack side, color values
"""
side, color = side_color
if side.lower() not in ["left", "right", "top", "bottom"]:
raise ValueError(f"{side} not a valid border side")
self.borders[side].color = color
@property
def border_width(self):
sides = ["left", "right", "top", "bottom"]
widths = []
for side in sides:
if side in ["left", "right"]:
widths.append(self.borders[side].width)
elif side in ["top", "bottom"]:
widths.append(self.borders[side].height)
else:
raise ValueError(f"{side} not a valid border side")
return widths
@border_width.setter
def border_width(self, side_width):
"""Set the border width of a specific border
Parameters
----------
side_width: Iterable
Iterable to pack side, width values
"""
side, border_width = side_width
if side.lower() in ["left", "right"]:
self.borders[side].width = border_width
elif side.lower() in ["top", "bottom"]:
self.borders[side].height = border_width
else:
raise ValueError(f"{side} not a valid border side")
[docs]
class TabPanel2D(UI):
"""Render content within a Tab.
Attributes
----------
content_panel: :class: 'Panel2D'
Hold all the content UI components.
text_block: :class: 'TextBlock2D'
Renders the title of the tab.
"""
@warn_on_args_to_kwargs()
def __init__(
self,
*,
position=(0, 0),
size=(100, 100),
title="New Tab",
color=(0.5, 0.5, 0.5),
content_panel=None,
):
"""Init class instance.
Parameters
----------
position : (float, float)
Absolute coordinates (x, y) of the lower-left corner of the
UI component
size : (int, int)
Width and height of the pixels of this UI component.
title : str
Renders the title for Tab panel.
color : list of 3 floats
Background color of tab panel.
content_panel : Panel2D
Panel consisting of the content UI elements.
"""
self.content_panel = content_panel
self.panel_size = size
self._text_size = (int(1.0 * size[0]), size[1])
super(TabPanel2D, self).__init__()
self.title = title
self.panel.position = position
self.color = color
def _setup(self):
"""Setup this UI component.
Create parent panel.
Create Text to hold tab information.
Create Button to close tab.
"""
self.panel = Panel2D(size=self.panel_size)
self.text_block = TextBlock2D(size=self._text_size, color=(0, 0, 0))
self.panel.add_element(self.text_block, (0, 0))
def _get_actors(self):
"""Get the actors composing this UI component."""
return self.panel.actors + self.content_panel.actors
def _add_to_scene(self, _scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
self.panel.add_to_scene(_scene)
self.content_panel.add_to_scene(_scene)
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.panel.position = _coords
def _get_size(self):
return self.panel.size
[docs]
def resize(self, size):
"""Resize Tab panel.
Parameters
----------
size : (int, int)
New width and height in pixels.
"""
self._text_size = (int(0.7 * size[0]), size[1])
self._button_size = (int(0.3 * size[0]), size[1])
self.panel.resize(size)
self.text_block.resize(self._text_size)
@property
def color(self):
"""Return the background color of tab panel."""
return self.panel.color
@color.setter
def color(self, color):
"""Set background color of tab panel.
Parameters
----------
color : list of 3 floats.
"""
self.panel.color = color
@property
def title(self):
"""Return the title of tab panel."""
return self.text_block.message
@title.setter
def title(self, text):
"""Set the title of tab panel.
Parameters
----------
text : str
New title for tab panel.
"""
self.text_block.message = text
@property
def title_bold(self):
"""Is the title of a tab panel bold."""
return self.text_block.bold
@title_bold.setter
def title_bold(self, bold):
"""Determine if the text title of a tab panel must be bold.
Parameters
----------
bold : bool
Bold property for a text title in a tab panel.
"""
self.text_block.bold = bold
@property
def title_color(self):
"""Return the title color of tab panel."""
return self.text_block.color
@title_color.setter
def title_color(self, color):
"""Set the title color of tab panel.
Parameters
----------
color : tuple
New title color for tab panel.
"""
self.text_block.color = color
@property
def title_font_size(self):
"""Return the title font size of tab panel."""
return self.text_block.font_size
@title_font_size.setter
def title_font_size(self, font_size):
"""Set the title font size of tab panel.
Parameters
----------
font_size : int
New title font size for tab panel.
"""
self.text_block.font_size = font_size
@property
def title_italic(self):
"""Is the title of a tab panel italic."""
return self.text_block.italic
@title_italic.setter
def title_italic(self, italic):
"""Determine if the text title of a tab panel must be italic.
Parameters
----------
italic : bool
Italic property for a text title in a tab panel.
"""
self.text_block.italic = italic
[docs]
@warn_on_args_to_kwargs()
def add_element(self, element, coords, *, anchor="position"):
"""Add a UI component to the content panel.
The coordinates represent an offset from the lower left corner of the
panel.
Parameters
----------
element : UI
The UI item to be added.
coords : (float, float) or (int, int)
If float, normalized coordinates are assumed and they must be
between [0,1].
If int, pixels coordinates are assumed and it must fit within the
panel's size.
"""
element.set_visibility(False)
self.content_panel.add_element(element, coords, anchor=anchor)
[docs]
def remove_element(self, element):
"""Remove a UI component from the content panel.
Parameters
----------
element : UI
The UI item to be removed.
"""
self.content_panel.remove_element(element)
[docs]
@warn_on_args_to_kwargs()
def update_element(self, element, coords, *, anchor="position"):
"""Update the position of a UI component in the content panel.
Parameters
----------
element : UI
The UI item to be updated.
coords : (float, float) or (int, int)
New coordinates.
If float, normalized coordinates are assumed and they must be
between [0,1].
If int, pixels coordinates are assumed and it must fit within the
panel's size.
"""
self.content_panel.update_element(element, coords, anchor="position")
[docs]
class TabUI(UI):
"""UI element to add multiple panels within a single window.
Attributes
----------
tabs: :class: List of 'TabPanel2D'
Stores all the instances of 'TabPanel2D' that renders the contents.
"""
@warn_on_args_to_kwargs()
def __init__(
self,
*,
position=(0, 0),
size=(100, 100),
nb_tabs=1,
active_color=(1, 1, 1),
inactive_color=(0.5, 0.5, 0.5),
draggable=False,
startup_tab_id=None,
tab_bar_pos="top",
):
"""Init class instance.
Parameters
----------
position : (float, float)
Absolute coordinates (x, y) of the lower-left corner of this
UI component.
size : (int, int)
Width and height in pixels of this UI component.
nb_tabs : int
Number of tabs to be renders.
active_color : tuple of 3 floats.
Background color of active tab panel.
inactive_color : tuple of 3 floats.
Background color of inactive tab panels.
draggable : bool
Whether the UI element is draggable or not.
startup_tab_id : int, optional
Tab to be activated and uncollapsed on startup.
by default None is activated/ all collapsed.
tab_bar_pos : str, optional
Position of the Tab Bar in the panel
"""
self.tabs = []
self.nb_tabs = nb_tabs
self.parent_size = size
self.content_size = (size[0], int(0.9 * size[1]))
self.draggable = draggable
self.active_color = active_color
self.inactive_color = inactive_color
self.active_tab_idx = startup_tab_id
self.collapsed = True
self.tab_bar_pos = tab_bar_pos
super(TabUI, self).__init__()
self.position = position
def _setup(self):
"""Setup this UI component.
Create parent panel.
Create tab panels.
"""
self.parent_panel = Panel2D(self.parent_size, opacity=0.0)
# Offer some standard hooks to the user.
self.on_change = lambda ui: None
self.on_collapse = lambda ui: None
for _ in range(self.nb_tabs):
content_panel = Panel2D(size=self.content_size)
content_panel.set_visibility(False)
tab_panel = TabPanel2D(content_panel=content_panel)
self.tabs.append(tab_panel)
self.update_tabs()
if self.active_tab_idx is not None:
self.tabs[self.active_tab_idx].color = self.active_color
self.tabs[self.active_tab_idx].content_panel.set_visibility(True)
def _get_actors(self):
"""Get the actors composing this UI component."""
actors = []
actors += self.parent_panel.actors
for tab_panel in self.tabs:
actors += tab_panel.actors
return actors
def _add_to_scene(self, _scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
self.parent_panel.add_to_scene(_scene)
for tab_panel in self.tabs:
tab_panel.add_to_scene(_scene)
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.parent_panel.position = _coords
def _get_size(self):
return self.parent_panel.size
[docs]
def update_tabs(self):
"""Update position, size and callbacks for tab panels."""
self.tab_panel_size = (self.size[0] // self.nb_tabs, int(0.1 * self.size[1]))
if self.tab_bar_pos.lower() not in ["top", "bottom"]:
warn("tab_bar_pos can only have value top/bottom", stacklevel=2)
self.tab_bar_pos = "top"
if self.tab_bar_pos.lower() == "top":
tab_panel_pos = [0.0, 0.9]
elif self.tab_bar_pos.lower() == "bottom":
tab_panel_pos = [0.0, 0.0]
for tab_panel in self.tabs:
tab_panel.resize(self.tab_panel_size)
tab_panel.content_panel.position = self.position
content_panel = tab_panel.content_panel
if self.draggable:
tab_panel.panel.background.on_left_mouse_button_pressed = (
self.left_button_pressed
)
content_panel.background.on_left_mouse_button_pressed = (
self.left_button_pressed
)
tab_panel.text_block.on_left_mouse_button_pressed = (
self.left_button_pressed
)
tab_panel.panel.background.on_left_mouse_button_dragged = (
self.left_button_dragged
)
content_panel.background.on_left_mouse_button_dragged = (
self.left_button_dragged
)
tab_panel.text_block.on_left_mouse_button_dragged = (
self.left_button_dragged
)
else:
tab_panel.panel.background.on_left_mouse_button_dragged = (
lambda i_ren, _obj, _comp: i_ren.force_render
)
content_panel.background.on_left_mouse_button_dragged = (
lambda i_ren, _obj, _comp: i_ren.force_render
)
tab_panel.text_block.on_left_mouse_button_clicked = self.select_tab_callback
tab_panel.panel.background.on_left_mouse_button_clicked = (
self.select_tab_callback
)
tab_panel.text_block.on_right_mouse_button_clicked = self.collapse_tab_ui
tab_panel.panel.background.on_right_mouse_button_clicked = (
self.collapse_tab_ui
)
tab_panel.content_panel.resize(self.content_size)
self.parent_panel.add_element(tab_panel, tab_panel_pos)
if self.tab_bar_pos.lower() == "top":
self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.0))
elif self.tab_bar_pos.lower() == "bottom":
self.parent_panel.add_element(tab_panel.content_panel, (0.0, 0.1))
tab_panel_pos[0] += 1 / self.nb_tabs
[docs]
def select_tab_callback(self, iren, _obj, _tab_comp):
"""Handle events when a tab is selected."""
for idx, tab_panel in enumerate(self.tabs):
if (
tab_panel.text_block is not _tab_comp
and tab_panel.panel.background is not _tab_comp
):
tab_panel.color = self.inactive_color
tab_panel.content_panel.set_visibility(False)
else:
current_visibility = tab_panel.content_panel.actors[0].GetVisibility()
if not current_visibility:
tab_panel.color = self.active_color
else:
tab_panel.color = self.inactive_color
tab_panel.content_panel.set_visibility(not current_visibility)
self.active_tab_idx = idx
self.collapsed = False
self.on_change(self)
iren.force_render()
iren.event.abort()
[docs]
def collapse_tab_ui(self, iren, _obj, _tab_comp):
"""Handle events when Tab UI is collapsed."""
if self.active_tab_idx is not None:
active_tab_panel = self.tabs[self.active_tab_idx]
active_tab_panel.color = self.inactive_color
active_tab_panel.content_panel.set_visibility(False)
self.active_tab_idx = None
self.collapsed = True
self.on_collapse(self)
iren.force_render()
iren.event.abort()
[docs]
@warn_on_args_to_kwargs()
def add_element(self, tab_idx, element, coords, *, anchor="position"):
"""Add element to content panel after checking its existence."""
if tab_idx < self.nb_tabs and tab_idx >= 0:
self.tabs[tab_idx].add_element(element, coords, anchor=anchor)
if tab_idx == self.active_tab_idx:
element.set_visibility(True)
else:
raise IndexError("Tab with index " "{} does not exist".format(tab_idx))
[docs]
def remove_element(self, tab_idx, element):
"""Remove element from content panel after checking its existence."""
if tab_idx < self.nb_tabs and tab_idx >= 0:
self.tabs[tab_idx].remove_element(element)
else:
raise IndexError("Tab with index " "{} does not exist".format(tab_idx))
[docs]
@warn_on_args_to_kwargs()
def update_element(self, tab_idx, element, coords, *, anchor="position"):
"""Update element on content panel after checking its existence."""
if tab_idx < self.nb_tabs and tab_idx >= 0:
self.tabs[tab_idx].update_element(element, coords, anchor=anchor)
else:
raise IndexError("Tab with index " "{} does not exist".format(tab_idx))
[docs]
class ImageContainer2D(UI):
"""A 2D container to hold an image.
Currently Supports:
- png and jpg/jpeg images
Attributes
----------
size: (float, float)
Image size (width, height) in pixels.
img : ImageData
The image loaded from the specified path.
"""
@warn_on_args_to_kwargs()
def __init__(self, img_path, *, position=(0, 0), size=(100, 100)):
"""Init class instance.
Parameters
----------
img_path : string
URL or local path of the image
position : (float, float), optional
Absolute coordinates (x, y) of the lower-left corner of the image.
size : (int, int), optional
Width and height in pixels of the image.
"""
super(ImageContainer2D, self).__init__(position=position)
self.img = load_image(img_path, as_vtktype=True)
self.set_img(self.img)
self.resize(size)
def _get_size(self):
lower_left_corner = self.texture_points.GetPoint(0)
upper_right_corner = self.texture_points.GetPoint(2)
size = np.array(upper_right_corner) - np.array(lower_left_corner)
return abs(size[:2])
def _setup(self):
"""Setup this UI Component.
Return an image as a 2D actor with a specific position.
Returns
-------
:class:`vtkTexturedActor2D`
"""
self.texture_polydata = PolyData()
self.texture_points = Points()
self.texture_points.SetNumberOfPoints(4)
polys = CellArray()
polys.InsertNextCell(4)
polys.InsertCellPoint(0)
polys.InsertCellPoint(1)
polys.InsertCellPoint(2)
polys.InsertCellPoint(3)
self.texture_polydata.SetPolys(polys)
tc = FloatArray()
tc.SetNumberOfComponents(2)
tc.SetNumberOfTuples(4)
tc.InsertComponent(0, 0, 0.0)
tc.InsertComponent(0, 1, 0.0)
tc.InsertComponent(1, 0, 1.0)
tc.InsertComponent(1, 1, 0.0)
tc.InsertComponent(2, 0, 1.0)
tc.InsertComponent(2, 1, 1.0)
tc.InsertComponent(3, 0, 0.0)
tc.InsertComponent(3, 1, 1.0)
self.texture_polydata.GetPointData().SetTCoords(tc)
texture_mapper = PolyDataMapper2D()
texture_mapper = set_input(texture_mapper, self.texture_polydata)
image = TexturedActor2D()
image.SetMapper(texture_mapper)
self.texture = Texture()
image.SetTexture(self.texture)
image_property = Property2D()
image_property.SetOpacity(1.0)
image.SetProperty(image_property)
self.actor = image
# Add default events listener to the VTK actor.
self.handle_events(self.actor)
def _get_actors(self):
"""Return the actors that compose 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)
[docs]
def resize(self, size):
"""Resize the image.
Parameters
----------
size : (float, float)
image size (width, height) in pixels.
"""
# Update actor.
self.texture_points.SetPoint(0, 0, 0, 0.0)
self.texture_points.SetPoint(1, size[0], 0, 0.0)
self.texture_points.SetPoint(2, size[0], size[1], 0.0)
self.texture_points.SetPoint(3, 0, size[1], 0.0)
self.texture_polydata.SetPoints(self.texture_points)
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)
[docs]
def scale(self, factor):
"""Scale the image.
Parameters
----------
factor : (float, float)
Scaling factor (width, height) in pixels.
"""
self.resize(self.size * factor)
[docs]
def set_img(self, img):
"""Modify the image used by the vtkTexturedActor2D.
Parameters
----------
img : imageData
"""
self.texture = set_input(self.texture, img)
[docs]
class GridUI(UI):
"""Add actors in a grid and interact with them individually."""
@warn_on_args_to_kwargs()
def __init__(
self,
actors,
*,
captions=None,
caption_offset=(0, -100, 0),
cell_padding=0,
cell_shape="rect",
aspect_ratio=16 / 9.0,
dim=None,
rotation_speed=1,
rotation_axis=(0, 1, 0),
):
# TODO: add rotation axis None by default
self.container = grid(
actors,
captions=captions,
caption_offset=caption_offset,
cell_padding=cell_padding,
cell_shape=cell_shape,
aspect_ratio=aspect_ratio,
dim=dim,
)
self._actors = []
self._actors_dict = {}
self.rotation_speed = rotation_speed
self.rotation_axis = rotation_axis
for item in self.container._items:
actor = item if captions is None else item._items[0]
self._actors.append(actor)
self._actors_dict[actor] = {"x": -np.inf, "y": -np.inf}
super(GridUI, self).__init__(position=(0, 0, 0))
def _get_size(self):
return
[docs]
@staticmethod
def left_click_callback(istyle, _obj, _what):
istyle.trackball_actor.OnLeftButtonDown()
istyle.force_render()
istyle.event.abort()
[docs]
@staticmethod
def left_release_callback(istyle, _obj, _what):
istyle.trackball_actor.OnLeftButtonUp()
istyle.force_render()
istyle.event.abort()
[docs]
@staticmethod
def mouse_move_callback(istyle, _obj, _what):
istyle.trackball_actor.OnMouseMove()
istyle.force_render()
istyle.event.abort()
[docs]
@staticmethod
def left_click_callback2(istyle, obj, self):
rx, ry, rz = self.rotation_axis
clockwise_rotation = np.array([self.rotation_speed, rx, ry, rz])
rotate(obj, clockwise_rotation)
istyle.force_render()
istyle.event.abort()
[docs]
@staticmethod
def left_release_callback2(istyle, _obj, _what):
istyle.force_render()
istyle.event.abort()
[docs]
@staticmethod
def mouse_move_callback2(istyle, obj, self):
if self._actors_dict[obj]["y"] == -np.inf:
iren = istyle.GetInteractor()
event_pos = iren.GetEventPosition()
self._actors_dict[obj]["y"] = event_pos[1]
else:
iren = istyle.GetInteractor()
event_pos = iren.GetEventPosition()
rx, ry, rz = self.rotation_axis
if event_pos[1] >= self._actors_dict[obj]["y"]:
clockwise_rotation = np.array([-self.rotation_speed, rx, ry, rz])
rotate(obj, clockwise_rotation)
else:
anti_clockwise_rotation = np.array([self.rotation_speed, rx, ry, rz])
rotate(obj, anti_clockwise_rotation)
self._actors_dict[obj]["y"] = event_pos[1]
istyle.force_render()
istyle.event.abort()
ANTICLOCKWISE_ROTATION_Y = np.array([-10, 0, 1, 0])
CLOCKWISE_ROTATION_Y = np.array([10, 0, 1, 0])
ANTICLOCKWISE_ROTATION_X = np.array([-10, 1, 0, 0])
CLOCKWISE_ROTATION_X = np.array([10, 1, 0, 0])
[docs]
def key_press_callback(self, istyle, obj, _what):
has_changed = False
if istyle.event.key == "Left":
has_changed = True
for a in self._actors:
rotate(a, self.ANTICLOCKWISE_ROTATION_Y)
elif istyle.event.key == "Right":
has_changed = True
for a in self._actors:
rotate(a, self.CLOCKWISE_ROTATION_Y)
elif istyle.event.key == "Up":
has_changed = True
for a in self._actors:
rotate(a, self.ANTICLOCKWISE_ROTATION_X)
elif istyle.event.key == "Down":
has_changed = True
for a in self._actors:
rotate(a, self.CLOCKWISE_ROTATION_X)
if has_changed:
istyle.force_render()
def _setup(self):
"""Set up this UI component and the events of its actor."""
# Add default events listener to the VTK actor.
for actor in self._actors:
# self.handle_events(actor)
if self.rotation_axis is None:
self.add_callback(
actor, "LeftButtonPressEvent", self.left_click_callback
)
self.add_callback(
actor, "LeftButtonReleaseEvent", self.left_release_callback
)
self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback)
else:
self.add_callback(
actor, "LeftButtonPressEvent", self.left_click_callback2
)
# TODO: possibly add this too
self.add_callback(
actor, "LeftButtonReleaseEvent", self.left_release_callback2
)
self.add_callback(actor, "MouseMoveEvent", self.mouse_move_callback2)
# TODO: this is currently not running
self.add_callback(actor, "KeyPressEvent", self.key_press_callback)
# self.on_key_press = self.key_press_callback2
def _get_actors(self):
"""Get the actors composing this UI component."""
return self._actors
def _add_to_scene(self, scene):
"""Add all subcomponents or VTK props that compose this UI component.
Parameters
----------
scene : scene
"""
self.container.add_to_scene(scene)
[docs]
def resize(self, size):
"""Resize the button.
Parameters
----------
size : (float, float)
Button size (width, height) in pixels.
"""
# Update actor.
pass
def _set_position(self, coords):
"""Set the lower-left corner position of this UI component.
Parameters
----------
coords: (float, float)
Absolute pixel coordinates (x, y).
"""
# coords = (0, 0, 0)
pass
# self.actor.SetPosition(*coords)
# self.container.SetPosition(*coords)