"""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)