[docs]classPanel2D(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_borderself._border_color=border_colorself._border_width=border_widthsuper(Panel2D,self).__init__(position=position)self.resize(size)self.alignment=alignself.color=colorself.opacity=opacityself.position=positionself._drag_offset=Nonedef_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()ifself.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),}forkeyinself.borders.keys():self.borders[key].color=self._border_colorself.add_element(self.borders[key],self.border_coords[key])forkeyinself.borders.keys():self.borders[key].on_left_mouse_button_pressed=self.left_button_pressedself.borders[key].on_left_mouse_button_dragged=self.left_button_draggedself.add_element(self.background,(0,0))# Add default events listener for this UI component.self.background.on_left_mouse_button_pressed=self.left_button_pressedself.background.on_left_mouse_button_dragged=self.left_button_draggeddef_get_actors(self):"""Get the actors composing this UI component."""actors=[]forelementinself._elements:actors+=element.actorsreturnactorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """forelementinself._elements:element.add_to_scene(scene)def_get_size(self):returnself.background.size
[docs]defresize(self,size):"""Set the panel size. Parameters ---------- size : (float, float) Panel size (width, height) in pixels. """self.background.resize(size)ifself.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)forelement,offsetinself.element_offsets:element.position=coords+offset
[docs]@warn_on_args_to_kwargs()defadd_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)ifnp.issubdtype(coords.dtype,np.floating):ifnp.any(coords<0)ornp.any(coords>1):raiseValueError("Normalized coordinates must be in [0,1].")coords=coords*self.sizeifanchor=="center":element.center=self.position+coordselifanchor=="position":element.position=self.position+coordselse:msg="Unknown anchor {}. Supported anchors are 'position'"" and 'center'."raiseValueError(msg)self._elements.append(element)offset=element.position-self.positionself.element_offsets.append((element,offset))
[docs]defremove_element(self,element):"""Remove a UI component from the panel. Parameters ---------- element : UI The UI item to be removed. """idx=self._elements.index(element)delself._elements[idx]delself.element_offsets[idx]
[docs]@warn_on_args_to_kwargs()defupdate_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]defleft_button_pressed(self,i_ren,_obj,panel2d_object):click_pos=np.array(i_ren.event.position)self._drag_offset=click_pos-self.positioni_ren.event.abort()# Stop propagating the event.
[docs]defre_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. """ifself.alignment=="left":passelifself.alignment=="right":self.position+=np.array(window_size_change)else:msg="You can only left-align or right-align objects in a panel."raiseValueError(msg)
[docs]defupdate_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),}forkeyinself.borders.keys():self.update_element(self.borders[key],self.border_coords[key])
@propertydefborder_color(self):sides=["left","right","top","bottom"]return[self.borders[side].colorforsideinsides]@border_color.setterdefborder_color(self,side_color):"""Set the color of a specific border Parameters ---------- side_color: Iterable Iterable to pack side, color values """side,color=side_colorifside.lower()notin["left","right","top","bottom"]:raiseValueError(f"{side} not a valid border side")self.borders[side].color=color@propertydefborder_width(self):sides=["left","right","top","bottom"]widths=[]forsideinsides:ifsidein["left","right"]:widths.append(self.borders[side].width)elifsidein["top","bottom"]:widths.append(self.borders[side].height)else:raiseValueError(f"{side} not a valid border side")returnwidths@border_width.setterdefborder_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_widthifside.lower()in["left","right"]:self.borders[side].width=border_widthelifside.lower()in["top","bottom"]:self.borders[side].height=border_widthelse:raiseValueError(f"{side} not a valid border side")
[docs]classTabPanel2D(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_panelself.panel_size=sizeself._text_size=(int(1.0*size[0]),size[1])super(TabPanel2D,self).__init__()self.title=titleself.panel.position=positionself.color=colordef_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."""returnself.panel.actors+self.content_panel.actorsdef_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=_coordsdef_get_size(self):returnself.panel.size
[docs]defresize(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)
@propertydefcolor(self):"""Return the background color of tab panel."""returnself.panel.color@color.setterdefcolor(self,color):"""Set background color of tab panel. Parameters ---------- color : list of 3 floats. """self.panel.color=color@propertydeftitle(self):"""Return the title of tab panel."""returnself.text_block.message@title.setterdeftitle(self,text):"""Set the title of tab panel. Parameters ---------- text : str New title for tab panel. """self.text_block.message=text@propertydeftitle_bold(self):"""Is the title of a tab panel bold."""returnself.text_block.bold@title_bold.setterdeftitle_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@propertydeftitle_color(self):"""Return the title color of tab panel."""returnself.text_block.color@title_color.setterdeftitle_color(self,color):"""Set the title color of tab panel. Parameters ---------- color : tuple New title color for tab panel. """self.text_block.color=color@propertydeftitle_font_size(self):"""Return the title font size of tab panel."""returnself.text_block.font_size@title_font_size.setterdeftitle_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@propertydeftitle_italic(self):"""Is the title of a tab panel italic."""returnself.text_block.italic@title_italic.setterdeftitle_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()defadd_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]defremove_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()defupdate_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]classTabUI(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_tabsself.parent_size=sizeself.content_size=(size[0],int(0.9*size[1]))self.draggable=draggableself.active_color=active_colorself.inactive_color=inactive_colorself.active_tab_idx=startup_tab_idself.collapsed=Trueself.tab_bar_pos=tab_bar_possuper(TabUI,self).__init__()self.position=positiondef_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=lambdaui:Noneself.on_collapse=lambdaui:Nonefor_inrange(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()ifself.active_tab_idxisnotNone:self.tabs[self.active_tab_idx].color=self.active_colorself.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.actorsfortab_panelinself.tabs:actors+=tab_panel.actorsreturnactorsdef_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)fortab_panelinself.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=_coordsdef_get_size(self):returnself.parent_panel.size
[docs]defupdate_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]))ifself.tab_bar_pos.lower()notin["top","bottom"]:warn("tab_bar_pos can only have value top/bottom",stacklevel=2)self.tab_bar_pos="top"ifself.tab_bar_pos.lower()=="top":tab_panel_pos=[0.0,0.9]elifself.tab_bar_pos.lower()=="bottom":tab_panel_pos=[0.0,0.0]fortab_panelinself.tabs:tab_panel.resize(self.tab_panel_size)tab_panel.content_panel.position=self.positioncontent_panel=tab_panel.content_panelifself.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=(lambdai_ren,_obj,_comp:i_ren.force_render)content_panel.background.on_left_mouse_button_dragged=(lambdai_ren,_obj,_comp:i_ren.force_render)tab_panel.text_block.on_left_mouse_button_clicked=self.select_tab_callbacktab_panel.panel.background.on_left_mouse_button_clicked=(self.select_tab_callback)tab_panel.text_block.on_right_mouse_button_clicked=self.collapse_tab_uitab_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)ifself.tab_bar_pos.lower()=="top":self.parent_panel.add_element(tab_panel.content_panel,(0.0,0.0))elifself.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]defselect_tab_callback(self,iren,_obj,_tab_comp):"""Handle events when a tab is selected."""foridx,tab_panelinenumerate(self.tabs):if(tab_panel.text_blockisnot_tab_compandtab_panel.panel.backgroundisnot_tab_comp):tab_panel.color=self.inactive_colortab_panel.content_panel.set_visibility(False)else:current_visibility=tab_panel.content_panel.actors[0].GetVisibility()ifnotcurrent_visibility:tab_panel.color=self.active_colorelse:tab_panel.color=self.inactive_colortab_panel.content_panel.set_visibility(notcurrent_visibility)self.active_tab_idx=idxself.collapsed=Falseself.on_change(self)iren.force_render()iren.event.abort()
[docs]defcollapse_tab_ui(self,iren,_obj,_tab_comp):"""Handle events when Tab UI is collapsed."""ifself.active_tab_idxisnotNone:active_tab_panel=self.tabs[self.active_tab_idx]active_tab_panel.color=self.inactive_coloractive_tab_panel.content_panel.set_visibility(False)self.active_tab_idx=Noneself.collapsed=Trueself.on_collapse(self)iren.force_render()iren.event.abort()
[docs]@warn_on_args_to_kwargs()defadd_element(self,tab_idx,element,coords,*,anchor="position"):"""Add element to content panel after checking its existence."""iftab_idx<self.nb_tabsandtab_idx>=0:self.tabs[tab_idx].add_element(element,coords,anchor=anchor)iftab_idx==self.active_tab_idx:element.set_visibility(True)else:raiseIndexError("Tab with index ""{} does not exist".format(tab_idx))
[docs]defremove_element(self,tab_idx,element):"""Remove element from content panel after checking its existence."""iftab_idx<self.nb_tabsandtab_idx>=0:self.tabs[tab_idx].remove_element(element)else:raiseIndexError("Tab with index ""{} does not exist".format(tab_idx))
[docs]@warn_on_args_to_kwargs()defupdate_element(self,tab_idx,element,coords,*,anchor="position"):"""Update element on content panel after checking its existence."""iftab_idx<self.nb_tabsandtab_idx>=0:self.tabs[tab_idx].update_element(element,coords,anchor=anchor)else:raiseIndexError("Tab with index ""{} does not exist".format(tab_idx))
[docs]defleft_button_pressed(self,i_ren,_obj,_sub_component):click_pos=np.array(i_ren.event.position)self._click_position=click_posi_ren.event.abort()# Stop propagating the event.
[docs]classImageContainer2D(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)returnabs(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]defresize(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]defscale(self,factor):"""Scale the image. Parameters ---------- factor : (float, float) Scaling factor (width, height) in pixels. """self.resize(self.size*factor)
[docs]defset_img(self,img):"""Modify the image used by the vtkTexturedActor2D. Parameters ---------- img : imageData """self.texture=set_input(self.texture,img)
[docs]classGridUI(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 defaultself.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_speedself.rotation_axis=rotation_axisforiteminself.container._items:actor=itemifcaptionsisNoneelseitem._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
def_setup(self):"""Set up this UI component and the events of its actor."""# Add default events listener to the VTK actor.foractorinself._actors:# self.handle_events(actor)ifself.rotation_axisisNone: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 tooself.add_callback(actor,"LeftButtonReleaseEvent",self.left_release_callback2)self.add_callback(actor,"MouseMoveEvent",self.mouse_move_callback2)# TODO: this is currently not runningself.add_callback(actor,"KeyPressEvent",self.key_press_callback)# self.on_key_press = self.key_press_callback2def_get_actors(self):"""Get the actors composing this UI component."""returnself._actorsdef_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]defresize(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