[docs]classTextBox2D(UI):"""An editable 2D text box that behaves as a UI component. Currently supports: - Basic text editing. - Cursor movements. - Single and multi-line text boxes. - Pre text formatting (text needs to be formatted beforehand). Attributes ---------- text : str The current text state. actor : :class:`vtkActor2d` The text actor. width : int The number of characters in a single line of text. height : int The number of lines in the textbox. window_left : int Left limit of visible text in the textbox. window_right : int Right limit of visible text in the textbox. caret_pos : int Position of the caret in the text. init : bool Flag which says whether the textbox has just been initialized. """@warn_on_args_to_kwargs()def__init__(self,width,height,*,text="Enter Text",position=(100,10),color=(0,0,0),font_size=18,font_family="Arial",justification="left",bold=False,italic=False,shadow=False,):"""Init this UI element. Parameters ---------- width : int The number of characters in a single line of text. height : int The number of lines in the textbox. 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. font_size : int Size of the text font. font_family : str Currently only supports Arial. justification : str left, right or center. bold : bool Makes text bold. italic : bool Makes text italicised. shadow : bool Adds text shadow. """super(TextBox2D,self).__init__(position=position)self.message=textself.text.message=textself.text.font_size=font_sizeself.text.font_family=font_familyself.text.justification=justificationself.text.bold=boldself.text.italic=italicself.text.shadow=shadowself.text.color=colorself.text.background_color=(1,1,1)self.width=widthself.height=heightself.window_left=0self.window_right=0self.caret_pos=0self.init=Trueself.off_focus=lambdaui:Nonedef_setup(self):"""Setup this UI component. Create the TextBlock2D component used for the textbox. """self.text=TextBlock2D(dynamic_bbox=True)# Add default events listener for this UI component.self.text.on_left_mouse_button_pressed=self.left_button_pressself.text.on_key_press=self.key_pressdef_get_actors(self):"""Get the actors composing this UI component."""returnself.text.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.text.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.text.position=coordsdef_get_size(self):returnself.text.size
[docs]defset_message(self,message):"""Set custom text to textbox. Parameters ---------- message: str The custom message to be set. """self.message=messageself.text.message=messageself.init=Falseself.window_right=len(self.message)self.window_left=0self.caret_pos=self.window_right
[docs]defwidth_set_text(self,text):"""Add newlines to text where necessary. This is needed for multi-line text boxes. Parameters ---------- text : str The final text to be formatted. Returns ------- str A multi line formatted text. """multi_line_text=""fori,tinenumerate(text):multi_line_text+=tif(i+1)%self.width==0:multi_line_text+="\n"returnmulti_line_text.rstrip("\n")
[docs]defhandle_character(self,key,key_char):"""Handle button events. # TODO: Need to handle all kinds of characters like !, +, etc. Parameters ---------- character : str """ifkey.lower()=="return":self.render_text(show_caret=False)self.off_focus(self)returnTrueelifkey_char!=""andkey_charinprintable:self.add_character(key_char)ifkey.lower()=="backspace":self.remove_character()elifkey.lower()=="left":self.move_left()elifkey.lower()=="right":self.move_right()self.render_text()returnFalse
[docs]defmove_caret_right(self):"""Move the caret towards right."""self.caret_pos=min(self.caret_pos+1,len(self.message))
[docs]defmove_caret_left(self):"""Move the caret towards left."""self.caret_pos=max(self.caret_pos-1,0)
[docs]defright_move_right(self):"""Move right boundary of the text window right-wards."""ifself.window_right<=len(self.message):self.window_right+=1
[docs]defright_move_left(self):"""Move right boundary of the text window left-wards."""ifself.window_right>0:self.window_right-=1
[docs]defleft_move_right(self):"""Move left boundary of the text window right-wards."""ifself.window_left<=len(self.message):self.window_left+=1
[docs]defleft_move_left(self):"""Move left boundary of the text window left-wards."""ifself.window_left>0:self.window_left-=1
[docs]defadd_character(self,character):"""Insert a character into the text and moves window and caret. Parameters ---------- character : str """iflen(character)>1andcharacter.lower()!="space":returnifcharacter.lower()=="space":character=" "self.message=(self.message[:self.caret_pos]+character+self.message[self.caret_pos:])self.move_caret_right()ifself.window_right-self.window_left==self.height*self.width-1:self.left_move_right()self.right_move_right()
[docs]defremove_character(self):"""Remove a character and moves window and caret accordingly."""ifself.caret_pos==0:returnself.message=(self.message[:self.caret_pos-1]+self.message[self.caret_pos:])self.move_caret_left()iflen(self.message)<self.height*self.width-1:self.right_move_left()ifself.window_right-self.window_left==self.height*self.width-1:ifself.window_left>0:self.left_move_left()self.right_move_left()
[docs]defmove_left(self):"""Handle left button press."""self.move_caret_left()ifself.caret_pos==self.window_left-1:ifself.window_right-self.window_left==self.height*self.width-1:self.left_move_left()self.right_move_left()
[docs]defmove_right(self):"""Handle right button press."""self.move_caret_right()ifself.caret_pos==self.window_right+1:ifself.window_right-self.window_left==self.height*self.width-1:self.left_move_right()self.right_move_right()
[docs]defshowable_text(self,show_caret):"""Chop out text to be shown on the screen. Parameters ---------- show_caret : bool Whether or not to show the caret. """ifshow_caret:ret_text=(self.message[:self.caret_pos]+"_"+self.message[self.caret_pos:])else:ret_text=self.messageret_text=ret_text[self.window_left:self.window_right+1]returnret_text
[docs]@warn_on_args_to_kwargs()defrender_text(self,*,show_caret=True):"""Render text after processing. Parameters ---------- show_caret : bool Whether or not to show the caret. """text=self.showable_text(show_caret)iftext=="":text="Enter Text"self.text.message=self.width_set_text(text)
[docs]defedit_mode(self):"""Turn on edit mode."""ifself.init:self.message=""self.init=Falseself.caret_pos=0self.render_text()
[docs]defleft_button_press(self,i_ren,_obj,_textbox_object):"""Handle left button press for textbox. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _textbox_object: :class:`TextBox2D` """i_ren.add_active_prop(self.text.actor)self.edit_mode()i_ren.force_render()
[docs]defkey_press(self,i_ren,_obj,_textbox_object):"""Handle Key press for textboxself. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _textbox_object: :class:`TextBox2D` """key=i_ren.event.keykey_char=i_ren.event.key_charis_done=self.handle_character(key,key_char)ifis_done:i_ren.remove_active_prop(self.text.actor)i_ren.force_render()
[docs]classLineSlider2D(UI):"""A 2D Line Slider. A sliding handle on a line with a percentage indicator. Attributes ---------- line_width : int Width of the line on which the disk will slide. length : int Length of the slider. track : :class:`Rectangle2D` The line on which the slider's handle moves. handle : :class:`Disk2D` The moving part of the slider. text : :class:`TextBlock2D` The text that shows percentage. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. default_color : (float, float, float) Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. """@warn_on_args_to_kwargs()def__init__(self,*,center=(0,0),initial_value=50,min_value=0,max_value=100,length=200,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,font_size=16,orientation="horizontal",text_alignment="",text_template="{value:.1f} ({ratio:.0%})",shape="disk",):"""Init this UI element. Parameters ---------- center : (float, float) Center of the slider's center. initial_value : float Initial value of the slider. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. length : int Length of the slider. line_width : int Width of the line on which the disk will slide. inner_radius : int Inner radius of the handles (if disk). outer_radius : int Outer radius of the handles (if disk). handle_side : int Side length of the handles (if square). font_size : int Size of the text to display alongside the slider (pt). orientation : str horizontal or vertical text_alignment : str define text alignment on a slider. Left (default)/ right for the vertical slider or top/bottom (default) for an horizontal slider. text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`. If callable, this instance of `:class:LineSlider2D` will be passed as argument to the text template function. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.shape=shapeself.orientation=orientation.lower().strip()self.align_dict={"horizontal":["top","bottom"],"vertical":["left","right"],}self.default_color=(1,1,1)self.active_color=(0,0,1)self.alignment=text_alignment.lower()super(LineSlider2D,self).__init__()ifself.orientation=="horizontal":self.alignment="bottom"ifnotself.alignmentelseself.alignmentself.track.width=lengthself.track.height=line_widthelifself.orientation=="vertical":self.alignment="left"ifnotself.alignmentelseself.alignmentself.track.width=line_widthself.track.height=lengthelse:raiseValueError("Unknown orientation")ifself.alignmentnotinself.align_dict[self.orientation]:raiseValueError("Unknown alignment: choose from '{}' or '{}'".format(*self.align_dict[self.orientation]))ifshape=="disk":self.handle.inner_radius=inner_radiusself.handle.outer_radius=outer_radiuselifshape=="square":self.handle.width=handle_sideself.handle.height=handle_sideself.center=centerself.min_value=min_valueself.max_value=max_valueself.text.font_size=font_sizeself.text_template=text_template# Offer some standard hooks to the user.self.on_change=lambdaui:Noneself.on_value_changed=lambdaui:Noneself.on_moving_slider=lambdaui:Noneself.value=initial_valueself.update()def_setup(self):"""Setup this UI component. Create the slider's track (Rectangle2D), the handle (Disk2D) and the text (TextBlock2D). """# Slider's trackself.track=Rectangle2D()self.track.color=(1,0,0)# Slider's handleifself.shape=="disk":self.handle=Disk2D(outer_radius=1)elifself.shape=="square":self.handle=Rectangle2D(size=(1,1))self.handle.color=self.default_color# Slider Textself.text=TextBlock2D(justification="center",vertical_justification="top")# Add default events listener for this UI component.self.track.on_left_mouse_button_pressed=self.track_click_callbackself.track.on_left_mouse_button_dragged=self.handle_move_callbackself.track.on_left_mouse_button_released=self.handle_release_callbackself.handle.on_left_mouse_button_dragged=self.handle_move_callbackself.handle.on_left_mouse_button_released=self.handle_release_callbackdef_get_actors(self):"""Get the actors composing this UI component."""returnself.track.actors+self.handle.actors+self.text.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handle.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):# Consider the handle's size when computing the slider's size.width=Noneheight=Noneifself.orientation=="horizontal":width=self.track.width+self.handle.size[0]height=max(self.track.height,self.handle.size[1])else:width=max(self.track.width,self.handle.size[0])height=self.track.height+self.handle.size[1]returnnp.array([width,height])def_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# Offset the slider line by the handle's radius.track_position=coords+self.handle.size/2.0ifself.orientation=="horizontal":# Offset the slider line height by half the slider line width.track_position[1]-=self.track.size[1]/2.0else:# Offset the slider line width by half the slider line height.track_position[0]+=self.track.size[0]/2.0self.track.position=track_positionself.handle.position=self.handle.position.astype(float)self.handle.position+=coords-self.position# Position the text below the handle.ifself.orientation=="horizontal":align=35ifself.alignment=="top"else-10self.text.position=(self.handle.center[0],self.handle.position[1]+align,)else:align=70ifself.alignment=="right"else-35self.text.position=(self.handle.position[0]+align,self.handle.center[1]+2,)@propertydefbottom_y_position(self):returnself.track.position[1]@propertydeftop_y_position(self):returnself.track.position[1]+self.track.size[1]@propertydefleft_x_position(self):returnself.track.position[0]@propertydefright_x_position(self):returnself.track.position[0]+self.track.size[0]
[docs]defset_position(self,position):"""Set the disk's position. Parameters ---------- position : (float, float) The absolute position of the disk (x, y). """# Move slider disk.ifself.orientation=="horizontal":x_position=position[0]x_position=max(x_position,self.left_x_position)x_position=min(x_position,self.right_x_position)self.handle.center=(x_position,self.track.center[1])else:y_position=position[1]y_position=max(y_position,self.bottom_y_position)y_position=min(y_position,self.top_y_position)self.handle.center=(self.track.center[0],y_position)self.update()# Update information.
[docs]defformat_text(self):"""Return formatted text to display along the slider."""ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(ratio=self.ratio,value=self.value)
[docs]defupdate(self):"""Update the slider."""# Compute the ratio determined by the position of the slider disk.disk_position_x=Nonedisk_position_y=Noneifself.orientation=="horizontal":length=float(self.right_x_position-self.left_x_position)length=np.round(length,decimals=6)iflength!=self.track.width:raiseValueError("Disk position outside the slider line")disk_position_x=self.handle.center[0]self._ratio=(disk_position_x-self.left_x_position)/lengthelse:length=float(self.top_y_position-self.bottom_y_position)iflength!=self.track.height:raiseValueError("Disk position outside the slider line")disk_position_y=self.handle.center[1]self._ratio=(disk_position_y-self.bottom_y_position)/length# Compute the selected value considering min_value and max_value.value_range=self.max_value-self.min_valueself._value=self.min_value+self.ratio*value_range# Update text.text=self.format_text()self.text.message=text# Move the text below the slider's handle.ifself.orientation=="horizontal":self.text.position=(disk_position_x,self.text.position[1])else:self.text.position=(self.text.position[0],disk_position_y)self.on_change(self)
[docs]deftrack_click_callback(self,i_ren,_vtkactor,_slider):"""Update disk position and grab the focus. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineSlider2D` """position=i_ren.event.positionself.set_position(position)self.on_moving_slider(self)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_move_callback(self,i_ren,_vtkactor,_slider):"""Handle movement. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` """self.handle.color=self.active_colorposition=i_ren.event.positionself.set_position(position)self.on_moving_slider(self)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,_vtkactor,_slider):"""Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor slider : :class:`LineSlider2D` """self.handle.color=self.default_colori_ren.force_render()
[docs]classLineDoubleSlider2D(UI):"""A 2D Line Slider with two sliding rings. Useful for setting min and max values for something. Currently supports: - Setting positions of both disks. Attributes ---------- line_width : int Width of the line on which the disk will slide. length : int Length of the slider. track : :class:`vtkActor` The line on which the handles move. handles : [:class:`vtkActor`, :class:`vtkActor`] The moving slider disks. text : [:class:`TextBlock2D`, :class:`TextBlock2D`] The texts that show the values of the disks. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. default_color : (float, float, float) Color of the handles when in unpressed state. active_color : (float, float, float) Color of the handles when they are pressed. """@warn_on_args_to_kwargs()def__init__(self,*,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,center=(450,300),length=200,initial_values=(0,100),min_value=0,max_value=100,font_size=16,text_template="{value:.1f}",orientation="horizontal",shape="disk",):"""Init this UI element. Parameters ---------- line_width : int Width of the line on which the disk will slide. inner_radius : int Inner radius of the handles (if disk). outer_radius : int Outer radius of the handles (if disk). handle_side : int Side length of the handles (if square). center : (float, float) Center of the slider. length : int Length of the slider. initial_values : (float, float) Initial values of the two handles. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. font_size : int Size of the text to display alongside the slider (pt). text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`. If callable, this instance of `:class:LineDoubleSlider2D` will be passed as argument to the text template function. orientation : str horizontal or vertical shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.shape=shapeself.default_color=(1,1,1)self.active_color=(0,0,1)self.orientation=orientation.lower()super(LineDoubleSlider2D,self).__init__()ifself.orientation=="horizontal":self.track.width=lengthself.track.height=line_widthelifself.orientation=="vertical":self.track.width=line_widthself.track.height=lengthelse:raiseValueError("Unknown orientation")self.center=centerifshape=="disk":self.handles[0].inner_radius=inner_radiusself.handles[0].outer_radius=outer_radiusself.handles[1].inner_radius=inner_radiusself.handles[1].outer_radius=outer_radiuselifshape=="square":self.handles[0].width=handle_sideself.handles[0].height=handle_sideself.handles[1].width=handle_sideself.handles[1].height=handle_sideself.min_value=min_valueself.max_value=max_valueself.text[0].font_size=font_sizeself.text[1].font_size=font_sizeself.text_template=text_template# Offer some standard hooks to the user.self.on_change=lambdaui:Noneself.on_value_changed=lambdaui:Noneself.on_moving_slider=lambdaui:None# Setting the handle positions will also update everything.self._values=[initial_values[0],initial_values[1]]self._ratio=[None,None]self.left_disk_value=initial_values[0]self.right_disk_value=initial_values[1]self.bottom_disk_value=initial_values[0]self.top_disk_value=initial_values[1]def_setup(self):"""Setup this UI component. Create the slider's track (Rectangle2D), the handles (Disk2D) and the text (TextBlock2D). """# Slider's trackself.track=Rectangle2D()self.track.color=(1,0,0)# Handlesself.handles=[]ifself.shape=="disk":self.handles.append(Disk2D(outer_radius=1))self.handles.append(Disk2D(outer_radius=1))elifself.shape=="square":self.handles.append(Rectangle2D(size=(1,1)))self.handles.append(Rectangle2D(size=(1,1)))self.handles[0].color=self.default_colorself.handles[1].color=self.default_color# Slider Textself.text=[TextBlock2D(justification="center",vertical_justification="top"),TextBlock2D(justification="center",vertical_justification="top"),]# Add default events listener for this UI component.self.track.on_left_mouse_button_dragged=self.handle_move_callbackself.handles[0].on_left_mouse_button_dragged=self.handle_move_callbackself.handles[1].on_left_mouse_button_dragged=self.handle_move_callbackself.handles[0].on_left_mouse_button_released=self.handle_release_callbackself.handles[1].on_left_mouse_button_released=self.handle_release_callbackdef_get_actors(self):"""Get the actors composing this UI component."""return(self.track.actors+self.handles[0].actors+self.handles[1].actors+self.text[0].actors+self.text[1].actors)def_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handles[0].add_to_scene(scene)self.handles[1].add_to_scene(scene)self.text[0].add_to_scene(scene)self.text[1].add_to_scene(scene)def_get_size(self):# Consider the handle's size when computing the slider's size.width=Noneheight=Noneifself.orientation=="horizontal":width=self.track.width+self.handles[0].size[0]height=max(self.track.height,self.handles[0].size[1])else:width=max(self.track.width,self.handles[0].size[0])height=self.track.height+self.handles[0].size[1]returnnp.array([width,height])def_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """# Offset the slider line by the handle's radius.track_position=coordsifself.orientation=="horizontal":# Offset the slider line height by half the slider line width.track_position[1]-=self.track.size[1]/2.0else:# Offset the slider line width by half the slider line height.track_position[0]-=self.track.size[0]/2.0self.track.position=track_positionself.handles[0].position=self.handles[0].position.astype(float)self.handles[1].position=self.handles[1].position.astype(float)self.handles[0].position+=coords-self.positionself.handles[1].position+=coords-self.positionifself.orientation=="horizontal":# Position the text below the handles.self.text[0].position=(self.handles[0].center[0],self.handles[0].position[1]-10,)self.text[1].position=(self.handles[1].center[0],self.handles[1].position[1]-10,)else:# Position the text to the left of the handles.self.text[0].position=(self.handles[0].center[0]-35,self.handles[0].position[1],)self.text[1].position=(self.handles[1].center[0]-35,self.handles[1].position[1],)@propertydefbottom_y_position(self):returnself.track.position[1]@propertydeftop_y_position(self):returnself.track.position[1]+self.track.size[1]@propertydefleft_x_position(self):returnself.track.position[0]@propertydefright_x_position(self):returnself.track.position[0]+self.track.size[0]
[docs]defvalue_to_ratio(self,value):"""Convert the value of a disk to the ratio. Parameters ---------- value : float """value_range=self.max_value-self.min_valuereturn(value-self.min_value)/value_rangeifvalue_rangeelse0
[docs]defratio_to_coord(self,ratio):"""Convert the ratio to the absolute coordinate. Parameters ---------- ratio : float """ifself.orientation=="horizontal":returnself.left_x_position+ratio*self.track.widthreturnself.bottom_y_position+ratio*self.track.height
[docs]defcoord_to_ratio(self,coord):"""Convert the x coordinate of a disk to the ratio. Parameters ---------- coord : float """ifself.orientation=="horizontal":return(coord-self.left_x_position)/float(self.track.width)return(coord-self.bottom_y_position)/float(self.track.height)
[docs]defratio_to_value(self,ratio):"""Convert the ratio to the value of the disk. Parameters ---------- ratio : float """value_range=self.max_value-self.min_valuereturnself.min_value+ratio*value_range
[docs]defset_position(self,position,disk_number):"""Set the disk's position. Parameters ---------- position : (float, float) The absolute position of the disk (x, y). disk_number : int The index of disk being moved. """ifself.orientation=="horizontal":x_position=position[0]ifdisk_number==0andx_position>=self.handles[1].center[0]:x_position=self.ratio_to_coord(self.value_to_ratio(self._values[1]-1))ifdisk_number==1andx_position<=self.handles[0].center[0]:x_position=self.ratio_to_coord(self.value_to_ratio(self._values[0]+1))x_position=max(x_position,self.left_x_position)x_position=min(x_position,self.right_x_position)self.handles[disk_number].center=(x_position,self.track.center[1])else:y_position=position[1]ifdisk_number==0andy_position>=self.handles[1].center[1]:y_position=self.ratio_to_coord(self.value_to_ratio(self._values[1]-1))ifdisk_number==1andy_position<=self.handles[0].center[1]:y_position=self.ratio_to_coord(self.value_to_ratio(self._values[0]+1))y_position=max(y_position,self.bottom_y_position)y_position=min(y_position,self.top_y_position)self.handles[disk_number].center=(self.track.center[0],y_position)self.update(disk_number)
@propertydefbottom_disk_value(self):"""Return the value of the bottom disk."""returnself._values[0]@bottom_disk_value.setterdefbottom_disk_value(self,bottom_disk_value):"""Set the value of the bottom disk. Parameters ---------- bottom_disk_value : float New value for the bottom disk. """self.bottom_disk_ratio=self.value_to_ratio(bottom_disk_value)@propertydeftop_disk_value(self):"""Return the value of the top disk."""returnself._values[1]@top_disk_value.setterdeftop_disk_value(self,top_disk_value):"""Set the value of the top disk. Parameters ---------- top_disk_value : float New value for the top disk. """self.top_disk_ratio=self.value_to_ratio(top_disk_value)@propertydefleft_disk_value(self):"""Return the value of the left disk."""returnself._values[0]@left_disk_value.setterdefleft_disk_value(self,left_disk_value):"""Set the value of the left disk. Parameters ---------- left_disk_value : float New value for the left disk. """self.left_disk_ratio=self.value_to_ratio(left_disk_value)self.on_value_changed(self)@propertydefright_disk_value(self):"""Return the value of the right disk."""returnself._values[1]@right_disk_value.setterdefright_disk_value(self,right_disk_value):"""Set the value of the right disk. Parameters ---------- right_disk_value : float New value for the right disk. """self.right_disk_ratio=self.value_to_ratio(right_disk_value)self.on_value_changed(self)@propertydefbottom_disk_ratio(self):"""Return the ratio of the bottom disk."""returnself._ratio[0]@bottom_disk_ratio.setterdefbottom_disk_ratio(self,bottom_disk_ratio):"""Set the ratio of the bottom disk. Parameters ---------- bottom_disk_ratio : float New ratio for the bottom disk. """position_x=self.ratio_to_coord(bottom_disk_ratio)position_y=self.ratio_to_coord(bottom_disk_ratio)self.set_position((position_x,position_y),0)@propertydeftop_disk_ratio(self):"""Return the ratio of the top disk."""returnself._ratio[1]@top_disk_ratio.setterdeftop_disk_ratio(self,top_disk_ratio):"""Set the ratio of the top disk. Parameters ---------- top_disk_ratio : float New ratio for the top disk. """position_x=self.ratio_to_coord(top_disk_ratio)position_y=self.ratio_to_coord(top_disk_ratio)self.set_position((position_x,position_y),1)@propertydefleft_disk_ratio(self):"""Return the ratio of the left disk."""returnself._ratio[0]@left_disk_ratio.setterdefleft_disk_ratio(self,left_disk_ratio):"""Set the ratio of the left disk. Parameters ---------- left_disk_ratio : float New ratio for the left disk. """position_x=self.ratio_to_coord(left_disk_ratio)position_y=self.ratio_to_coord(left_disk_ratio)self.set_position((position_x,position_y),0)@propertydefright_disk_ratio(self):"""Return the ratio of the right disk."""returnself._ratio[1]@right_disk_ratio.setterdefright_disk_ratio(self,right_disk_ratio):"""Set the ratio of the right disk. Parameters ---------- right_disk_ratio : float New ratio for the right disk. """position_x=self.ratio_to_coord(right_disk_ratio)position_y=self.ratio_to_coord(right_disk_ratio)self.set_position((position_x,position_y),1)
[docs]defformat_text(self,disk_number):"""Return formatted text to display along the slider. Parameters ---------- disk_number : int Index of the disk. """ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(value=self._values[disk_number])
[docs]defupdate(self,disk_number):"""Update the slider. Parameters ---------- disk_number : int Index of the disk to be updated. """# Compute the ratio determined by the position of the slider disk.ifself.orientation=="horizontal":self._ratio[disk_number]=self.coord_to_ratio(self.handles[disk_number].center[0])else:self._ratio[disk_number]=self.coord_to_ratio(self.handles[disk_number].center[1])# Compute the selected value considering min_value and max_value.self._values[disk_number]=self.ratio_to_value(self._ratio[disk_number])# Update text.text=self.format_text(disk_number)self.text[disk_number].message=textifself.orientation=="horizontal":self.text[disk_number].position=(self.handles[disk_number].center[0],self.text[disk_number].position[1],)else:self.text[disk_number].position=(self.text[disk_number].position[0],self.handles[disk_number].center[1],)self.on_change(self)
[docs]defhandle_move_callback(self,i_ren,vtkactor,_slider):"""Handle movement. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineDoubleSlider2D` """position=i_ren.event.positionifvtkactor==self.handles[0].actors[0]:self.set_position(position,0)self.handles[0].color=self.active_colorelifvtkactor==self.handles[1].actors[0]:self.set_position(position,1)self.handles[1].color=self.active_colorself.on_moving_slider(self)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,vtkactor,_slider):"""Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`LineDoubleSlider2D` """ifvtkactor==self.handles[0].actors[0]:self.handles[0].color=self.default_colorelifvtkactor==self.handles[1].actors[0]:self.handles[1].color=self.default_colori_ren.force_render()
[docs]classRingSlider2D(UI):"""A disk slider. A disk moves along the boundary of a ring. Goes from 0-360 degrees. Attributes ---------- mid_track_radius: float Distance from the center of the slider to the middle of the track. previous_value: float Value of Rotation of the actor before the current value. track : :class:`Disk2D` The circle on which the slider's handle moves. handle : :class:`Disk2D` The moving part of the slider. text : :class:`TextBlock2D` The text that shows percentage. default_color : (float, float, float) Color of the handle when in unpressed state. active_color : (float, float, float) Color of the handle when it is pressed. """@warn_on_args_to_kwargs()def__init__(self,*,center=(0,0),initial_value=180,min_value=0,max_value=360,slider_inner_radius=40,slider_outer_radius=44,handle_inner_radius=0,handle_outer_radius=10,font_size=16,text_template="{ratio:.0%}",):"""Init this UI element. Parameters ---------- center : (float, float) Position (x, y) of the slider's center. initial_value : float Initial value of the slider. min_value : float Minimum value of the slider. max_value : float Maximum value of the slider. slider_inner_radius : int Inner radius of the base disk. slider_outer_radius : int Outer radius of the base disk. handle_outer_radius : int Outer radius of the slider's handle. handle_inner_radius : int Inner radius of the slider's handle. font_size : int Size of the text to display alongside the slider (pt). text_template : str, callable If str, text template can contain one or multiple of the replacement fields: `{value:}`, `{ratio:}`, `{angle:}`. If callable, this instance of `:class:RingSlider2D` will be passed as argument to the text template function. """self.default_color=(1,1,1)self.active_color=(0,0,1)super(RingSlider2D,self).__init__()self.track.inner_radius=slider_inner_radiusself.track.outer_radius=slider_outer_radiusself.handle.inner_radius=handle_inner_radiusself.handle.outer_radius=handle_outer_radiusself.center=centerself.min_value=min_valueself.max_value=max_valueself.text.font_size=font_sizeself.text_template=text_template# Offer some standard hooks to the user.self.on_change=lambdaui:Noneself.on_value_changed=lambdaui:Noneself.on_moving_slider=lambdaui:Noneself._value=initial_valueself.value=initial_valueself._previous_value=initial_valueself._angle=0self._ratio=self.angle/TWO_PIdef_setup(self):"""Setup this UI component. Create the slider's circle (Disk2D), the handle (Disk2D) and the text (TextBlock2D). """# Slider's track.self.track=Disk2D(outer_radius=1)self.track.color=(1,0,0)# Slider's handle.self.handle=Disk2D(outer_radius=1)self.handle.color=self.default_color# Slider Textself.text=TextBlock2D(justification="center",vertical_justification="middle")# Add default events listener for this UI component.self.track.on_left_mouse_button_pressed=self.track_click_callbackself.track.on_left_mouse_button_dragged=self.handle_move_callbackself.track.on_left_mouse_button_released=self.handle_release_callbackself.handle.on_left_mouse_button_dragged=self.handle_move_callbackself.handle.on_left_mouse_button_released=self.handle_release_callbackdef_get_actors(self):"""Get the actors composing this UI component."""returnself.track.actors+self.handle.actors+self.text.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.track.add_to_scene(scene)self.handle.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):returnself.track.size+self.handle.sizedef_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.track.position=coords+self.handle.size/2.0self.handle.position+=coords-self.position# Position the text in the center of the slider's track.self.text.position=coords+self.size/2.0@propertydefmid_track_radius(self):return(self.track.inner_radius+self.track.outer_radius)/2.0@propertydefvalue(self):returnself._value@value.setterdefvalue(self,value):value_range=self.max_value-self.min_valueself.ratio=(value-self.min_value)/value_rangeifvalue_rangeelse0self.on_value_changed(self)@propertydefprevious_value(self):returnself._previous_value@propertydefratio(self):returnself._ratio@ratio.setterdefratio(self,ratio):self.angle=ratio*TWO_PI@propertydefangle(self):"""Return Angle (in rad) the handle makes with x-axis."""returnself._angle@angle.setterdefangle(self,angle):self._angle=angle%TWO_PI# Wraparoundself.update()
[docs]defformat_text(self):"""Return formatted text to display along the slider."""ifcallable(self.text_template):returnself.text_template(self)returnself.text_template.format(ratio=self.ratio,value=self.value,angle=np.rad2deg(self.angle))
[docs]defupdate(self):"""Update the slider."""# Compute the ratio determined by the position of the slider disk.self._ratio=self.angle/TWO_PI# Compute the selected value considering min_value and max_value.value_range=self.max_value-self.min_valueself._previous_value=self.valueself._value=self.min_value+self.ratio*value_range# Update text disk actor.x=self.mid_track_radius*np.cos(self.angle)+self.center[0]y=self.mid_track_radius*np.sin(self.angle)+self.center[1]self.handle.center=(x,y)# Update text.text=self.format_text()self.text.message=textself.on_change(self)# Call hook.
[docs]defmove_handle(self,click_position):"""Move the slider's handle. Parameters ---------- click_position: (float, float) Position of the mouse click. """x,y=np.array(click_position)-self.centerangle=np.arctan2(y,x)ifangle<0:angle+=TWO_PIself.angle=angle
[docs]deftrack_click_callback(self,i_ren,_obj,_slider):"""Update disk position and grab the focus. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """click_position=i_ren.event.positionself.move_handle(click_position=click_position)self.on_moving_slider(self)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_move_callback(self,i_ren,_obj,_slider):"""Move the slider's handle. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """click_position=i_ren.event.positionself.handle.color=self.active_colorself.move_handle(click_position=click_position)self.on_moving_slider(self)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defhandle_release_callback(self,i_ren,_obj,_slider):"""Change color when handle is released. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor _slider : :class:`RingSlider2D` """self.handle.color=self.default_colori_ren.force_render()
[docs]classRangeSlider(UI):"""A set of a LineSlider2D and a LineDoubleSlider2D. The double slider is used to set the min and max value for the LineSlider2D Attributes ---------- range_slider_center : (float, float) Center of the LineDoubleSlider2D object. value_slider_center : (float, float) Center of the LineSlider2D object. range_slider : :class:`LineDoubleSlider2D` The line slider which sets the min and max values value_slider : :class:`LineSlider2D` The line slider which sets the value """@warn_on_args_to_kwargs()def__init__(self,*,line_width=5,inner_radius=0,outer_radius=10,handle_side=20,range_slider_center=(450,400),value_slider_center=(450,300),length=200,min_value=0,max_value=100,font_size=16,range_precision=1,orientation="horizontal",value_precision=2,shape="disk",):"""Init this class instance. Parameters ---------- line_width : int Width of the slider tracks inner_radius : int Inner radius of the handles. outer_radius : int Outer radius of the handles. handle_side : int Side length of the handles (if square). range_slider_center : (float, float) Center of the LineDoubleSlider2D object. value_slider_center : (float, float) Center of the LineSlider2D object. length : int Length of the sliders. min_value : float Minimum value of the double slider. max_value : float Maximum value of the double slider. font_size : int Size of the text to display alongside the sliders (pt). range_precision : int Number of decimal places to show the min and max values set. orientation : str horizontal or vertical value_precision : int Number of decimal places to show the value set on slider. shape : string Describes the shape of the handle. Currently supports 'disk' and 'square'. """self.min_value=min_valueself.max_value=max_valueself.inner_radius=inner_radiusself.outer_radius=outer_radiusself.handle_side=handle_sideself.length=lengthself.line_width=line_widthself.font_size=font_sizeself.shape=shapeself.orientation=orientation.lower()self.range_slider_text_template="{value:."+str(range_precision)+"f}"self.value_slider_text_template="{value:."+str(value_precision)+"f}"self.range_slider_center=range_slider_centerself.value_slider_center=value_slider_centersuper(RangeSlider,self).__init__()def_setup(self):"""Setup this UI component."""self.range_slider=LineDoubleSlider2D(line_width=self.line_width,inner_radius=self.inner_radius,outer_radius=self.outer_radius,handle_side=self.handle_side,center=self.range_slider_center,length=self.length,min_value=self.min_value,max_value=self.max_value,initial_values=(self.min_value,self.max_value),font_size=self.font_size,shape=self.shape,orientation=self.orientation,text_template=self.range_slider_text_template,)self.value_slider=LineSlider2D(line_width=self.line_width,length=self.length,inner_radius=self.inner_radius,outer_radius=self.outer_radius,handle_side=self.handle_side,center=self.value_slider_center,min_value=self.min_value,max_value=self.max_value,initial_value=(self.min_value+self.max_value)/2,font_size=self.font_size,shape=self.shape,orientation=self.orientation,text_template=self.value_slider_text_template,)# Add default events listener for this UI component.self.range_slider.handles[0].on_left_mouse_button_dragged=self.range_slider_handle_move_callbackself.range_slider.handles[1].on_left_mouse_button_dragged=self.range_slider_handle_move_callbackdef_get_actors(self):"""Get the actors composing this UI component."""returnself.range_slider.actors+self.value_slider.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.range_slider.add_to_scene(scene)self.value_slider.add_to_scene(scene)def_get_size(self):returnself.range_slider.size+self.value_slider.sizedef_set_position(self,coords):pass
[docs]defrange_slider_handle_move_callback(self,i_ren,obj,_slider):"""Update range_slider's handles. Parameters ---------- i_ren : :class:`CustomInteractorStyle` obj : :class:`vtkActor` The picked actor _slider : :class:`RangeSlider` """position=i_ren.event.positionifobj==self.range_slider.handles[0].actors[0]:self.range_slider.handles[0].color=self.range_slider.active_colorself.range_slider.set_position(position,0)self.value_slider.min_value=self.range_slider.left_disk_valueself.value_slider.update()elifobj==self.range_slider.handles[1].actors[0]:self.range_slider.handles[1].color=self.range_slider.active_colorself.range_slider.set_position(position,1)self.value_slider.max_value=self.range_slider.right_disk_valueself.value_slider.update()i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]classOption(UI):"""A set of a Button2D and a TextBlock2D to act as a single option for checkboxes and radio buttons. Clicking the button toggles its checked/unchecked status. Attributes ---------- label : str The label for the option. font_size : int Font Size of the label. """@warn_on_args_to_kwargs()def__init__(self,label,*,position=(0,0),font_size=18,checked=False):"""Init this class instance. Parameters ---------- label : str Text to be displayed next to the option's button. position : (float, float) Absolute coordinates (x, y) of the lower-left corner of the button of the option. font_size : int Font size of the label. checked : bool, optional Boolean value indicates the initial state of the option """self.label=labelself.font_size=font_sizeself.checked=checkedself.button_size=(font_size*1.2,font_size*1.2)self.button_label_gap=10super(Option,self).__init__(position=position)# Offer some standard hooks to the user.self.on_change=lambdaobj:Nonedef_setup(self):"""Setup this UI component."""# Option's buttonself.button_icons=[]self.button_icons.append(("unchecked",read_viz_icons(fname="stop2.png")))self.button_icons.append(("checked",read_viz_icons(fname="checkmark.png")))self.button=Button2D(icon_fnames=self.button_icons,size=self.button_size)self.text=TextBlock2D(text=self.label,font_size=self.font_size)# Display initial stateifself.checked:self.button.set_icon_by_name("checked")# Add callbacksself.button.on_left_mouse_button_clicked=self.toggleself.text.on_left_mouse_button_clicked=self.toggledef_get_actors(self):"""Get the actors composing this UI component."""returnself.button.actors+self.text.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.button.add_to_scene(scene)self.text.add_to_scene(scene)def_get_size(self):width=self.button.size[0]+self.button_label_gap+self.text.size[0]height=max(self.button.size[1],self.text.size[1])returnnp.array([width,height])def_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """num_newlines=self.label.count("\n")self.button.position=coords+(0,num_newlines*self.font_size*0.5)offset=(self.button.size[0]+self.button_label_gap,0)self.text.position=coords+offset
[docs]classCheckbox(UI):"""A 2D set of :class:'Option' objects. Multiple options can be selected. Attributes ---------- labels : list(string) List of labels of each option. options : dict(Option) Dictionary of all the options in the checkbox set. padding : float Distance between two adjacent options """@warn_on_args_to_kwargs()def__init__(self,labels,*,checked_labels=(),padding=1,font_size=18,font_family="Arial",position=(0,0),):"""Init this class instance. Parameters ---------- labels : list(str) List of labels of each option. checked_labels: list(str), optional List of labels that are checked on setting up. padding : float, optional The distance between two adjacent options font_size : int, optional Size of the text font. font_family : str, optional Currently only supports Arial. position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the button of the first option. """self.labels=list(reversed(list(labels)))self._padding=paddingself._font_size=font_sizeself.font_family=font_familyself.checked_labels=list(checked_labels)super(Checkbox,self).__init__(position=position)self.on_change=lambdacheckbox:Nonedef_setup(self):"""Setup this UI component."""self.options=OrderedDict()button_y=self.position[1]forlabelinself.labels:option=Option(label=label,font_size=self.font_size,position=(self.position[0],button_y),checked=(labelinself.checked_labels),)line_spacing=option.text.actor.GetTextProperty().GetLineSpacing()button_y=(button_y+self.font_size*(label.count("\n")+1)*(line_spacing+0.1)+self.padding)self.options[label]=option# Set callbackoption.on_change=self._handle_option_changedef_get_actors(self):"""Get the actors composing this UI component."""actors=[]foroptioninself.options.values():actors=actors+option.actorsreturnactorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """foroptioninself.options.values():option.add_to_scene(scene)def_get_size(self):option_width,option_height=self.options.values()[0].get_size()height=len(self.labels)*(option_height+self.padding)-self.paddingreturnnp.asarray([option_width,height])def_handle_option_change(self,option):"""Update whenever an option changes. Parameters ---------- option : :class:`Option` """ifoption.checked:self.checked_labels.append(option.label)else:self.checked_labels.remove(option.label)self.on_change(self)def_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """button_y=coords[1]foroption_no,optioninenumerate(self.options.values()):option.position=(coords[0],button_y)line_spacing=option.text.actor.GetTextProperty().GetLineSpacing()button_y=(button_y+self.font_size*(self.labels[option_no].count("\n")+1)*(line_spacing+0.1)+self.padding)@propertydeffont_size(self):"""Gets the font size of text."""returnself._font_size@propertydefpadding(self):"""Get the padding between options."""returnself._padding
[docs]classRadioButton(Checkbox):"""A 2D set of :class:'Option' objects. Only one option can be selected. Attributes ---------- labels : list(string) List of labels of each option. options : dict(Option) Dictionary of all the options in the checkbox set. padding : float Distance between two adjacent options """@warn_on_args_to_kwargs()def__init__(self,labels,checked_labels,*,padding=1,font_size=18,font_family="Arial",position=(0,0),):"""Init class instance. Parameters ---------- labels : list(str) List of labels of each option. checked_labels: list(str), optional List of labels that are checked on setting up. padding : float, optional The distance between two adjacent options font_size : int, optional Size of the text font. font_family : str, optional Currently only supports Arial. position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the button of the first option. """iflen(checked_labels)>1:err_msg="Only one option can be pre-selected for radio buttons."raiseValueError(err_msg)super(RadioButton,self).__init__(labels=labels,position=position,padding=padding,font_size=font_size,font_family=font_family,checked_labels=checked_labels,)def_handle_option_change(self,option):foroption_inself.options.values():option_.deselect()option.select()self.checked_labels=[option.label]self.on_change(self)
[docs]classComboBox2D(UI):"""UI element to create drop-down menus. Attributes ---------- selection_box: :class: 'TextBox2D' Display selection and placeholder text. drop_down_button: :class: 'Button2D' Button to show or hide menu. drop_down_menu: :class: 'ListBox2D' Container for item list. """@warn_on_args_to_kwargs()def__init__(self,*,items=None,position=(0,0),size=(300,200),placeholder="Choose selection...",draggable=True,selection_text_color=(0,0,0),selection_bg_color=(1,1,1),menu_text_color=(0.2,0.2,0.2),selected_color=(0.9,0.6,0.6),unselected_color=(0.6,0.6,0.6),scroll_bar_active_color=(0.6,0.2,0.2),scroll_bar_inactive_color=(0.9,0.0,0.0),menu_opacity=1.0,reverse_scrolling=False,font_size=20,line_spacing=1.4,):"""Init class Instance. Parameters ---------- items: list(string) List of items to be displayed as choices. 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. placeholder : str Holds the default text to be displayed. draggable: {True, False} Whether the UI element is draggable or not. selection_text_color : tuple of 3 floats Color of the selected text to be displayed. selection_bg_color : tuple of 3 floats Background color of the selection text. menu_text_color : tuple of 3 floats. Color of the options displayed in drop down menu. selected_color : tuple of 3 floats. Background color of the selected option in drop down menu. unselected_color : tuple of 3 floats. Background color of the unselected option in drop down menu. scroll_bar_active_color : tuple of 3 floats. Color of the scrollbar when in active use. scroll_bar_inactive_color : tuple of 3 floats. Color of the scrollbar when inactive. reverse_scrolling: {True, False} If True, scrolling up will move the list of files down. font_size: int The font size of selected text in pixels. line_spacing: float Distance between drop down menu's items in pixels. """ifitemsisNone:items=[]self.items=items.copy()self.font_size=font_sizeself.reverse_scrolling=reverse_scrollingself.line_spacing=line_spacingself.panel_size=sizeself._selection=placeholderself._menu_visibility=Falseself._selection_ID=Noneself.draggable=draggableself.sel_text_color=selection_text_colorself.sel_bg_color=selection_bg_colorself.menu_txt_color=menu_text_colorself.selected_color=selected_colorself.unselected_color=unselected_colorself.scroll_active_color=scroll_bar_active_colorself.scroll_inactive_color=scroll_bar_inactive_colorself.menu_opacity=menu_opacity# Define subcomponent sizes.self.text_block_size=(int(0.9*size[0]),int(0.1*size[1]))self.drop_menu_size=(int(0.9*size[0]),int(0.7*size[1]))self.drop_button_size=(int(0.1*size[0]),int(0.1*size[1]))self._icon_files=[("left",read_viz_icons(fname="circle-left.png")),("down",read_viz_icons(fname="circle-down.png")),]super(ComboBox2D,self).__init__()self.position=positiondef_setup(self):"""Setup this UI component. Create the ListBox filled with empty slots (ListBoxItem2D). Create TextBox with placeholder text. Create Button for toggling drop down menu. """self.selection_box=TextBlock2D(size=self.text_block_size,color=self.sel_text_color,bg_color=self.sel_bg_color,text=self._selection,)self.drop_down_button=Button2D(icon_fnames=self._icon_files,size=self.drop_button_size)self.drop_down_menu=ListBox2D(values=self.items,multiselection=False,font_size=self.font_size,line_spacing=self.line_spacing,text_color=self.menu_txt_color,selected_color=self.selected_color,unselected_color=self.unselected_color,scroll_bar_active_color=self.scroll_active_color,scroll_bar_inactive_color=self.scroll_inactive_color,background_opacity=self.menu_opacity,reverse_scrolling=self.reverse_scrolling,size=self.drop_menu_size,)self.drop_down_menu.set_visibility(False)self.panel=Panel2D(self.panel_size,opacity=0.0)self.panel.add_element(self.selection_box,(0.001,0.7))self.panel.add_element(self.drop_down_button,(0.8,0.7))self.panel.add_element(self.drop_down_menu,(0,0))ifself.draggable:self.drop_down_button.on_left_mouse_button_dragged=(self.left_button_dragged)self.drop_down_menu.panel.background.on_left_mouse_button_dragged=(self.left_button_dragged)self.selection_box.on_left_mouse_button_dragged=self.left_button_draggedself.selection_box.background.on_left_mouse_button_dragged=(self.left_button_dragged)self.drop_down_button.on_left_mouse_button_pressed=(self.left_button_pressed)self.drop_down_menu.panel.background.on_left_mouse_button_pressed=(self.left_button_pressed)self.selection_box.on_left_mouse_button_pressed=self.left_button_pressedself.selection_box.background.on_left_mouse_button_pressed=(self.left_button_pressed)else:self.panel.background.on_left_mouse_button_dragged=(lambdai_ren,_obj,_comp:i_ren.force_render)self.drop_down_menu.panel.background.on_left_mouse_button_dragged=(lambdai_ren,_obj,_comp:i_ren.force_render)# Handle mouse wheel events on the slots.forslotinself.drop_down_menu.slots:slot.add_callback(slot.textblock.actor,"LeftButtonPressEvent",self.select_option_callback,)slot.add_callback(slot.background.actor,"LeftButtonPressEvent",self.select_option_callback,)self.drop_down_button.on_left_mouse_button_clicked=self.menu_toggle_callback# Offer some standard hooks to the user.self.on_change=lambdaui:Nonedef_get_actors(self):"""Get the actors composing this UI component."""returnself.panel.actors
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=coordsself.panel.position=(self.panel.position[0],self.panel.position[1]-self.drop_menu_size[1],)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.selection_box.font_size=self.font_sizedef_get_size(self):returnself.panel.size@propertydefselected_text(self):returnself._selection@propertydefselected_text_index(self):returnself._selection_ID
[docs]defappend_item(self,*items):"""Append additional options to the menu. Parameters ---------- items : n-d list, n-d tuple, Number or str Additional options. """foriteminitems:ifisinstance(item,(list,tuple)):# Useful when n-d lists/tuples are used.self.append_item(*item)elifisinstance(item,(str,Number)):self.items.append(str(item))else:raiseTypeError("Invalid item instance {}".format(type(item)))self.drop_down_menu.update_scrollbar()ifnotself._menu_visibility:self.drop_down_menu.scroll_bar.set_visibility(False)
[docs]defselect_option_callback(self,i_ren,_obj,listboxitem):"""Select the appropriate option Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor listboxitem: :class:`ListBoxItem2D` """# Set the Text of TextBlock2D to the text of listboxitemself._selection=listboxitem.elementself._selection_ID=self.items.index(self._selection)self.selection_box.message=self._selectionclip_overflow(self.selection_box,self.selection_box.background.size[0])self.drop_down_menu.set_visibility(False)self._menu_visibility=Falseself.drop_down_button.next_icon()self.on_change(self)i_ren.force_render()i_ren.event.abort()
[docs]defmenu_toggle_callback(self,i_ren,_vtkactor,_combobox):"""Toggle visibility of drop down menu list. Parameters ---------- i_ren : :class:`CustomInteractorStyle` vtkactor : :class:`vtkActor` The picked actor combobox : :class:`ComboBox2D` """self._menu_visibility=notself._menu_visibilityself.drop_down_menu.set_visibility(self._menu_visibility)self.drop_down_button.next_icon()i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[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]classListBox2D(UI):"""UI component that allows the user to select items from a list. Attributes ---------- on_change: function Callback function for when the selected items have changed. """@warn_on_args_to_kwargs()def__init__(self,values,*,position=(0,0),size=(100,300),multiselection=True,reverse_scrolling=False,font_size=20,line_spacing=1.4,text_color=(0.2,0.2,0.2),selected_color=(0.9,0.6,0.6),unselected_color=(0.6,0.6,0.6),scroll_bar_active_color=(0.6,0.2,0.2),scroll_bar_inactive_color=(0.9,0.0,0.0),background_opacity=1.0,):"""Init class instance. Parameters ---------- values: list of objects Values used to populate this listbox. Objects must be castable to string. 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. multiselection: {True, False} Whether multiple values can be selected at once. reverse_scrolling: {True, False} If True, scrolling up will move the list of files down. font_size: int The font size in pixels. line_spacing: float Distance between listbox's items in pixels. text_color : tuple of 3 floats selected_color : tuple of 3 floats unselected_color : tuple of 3 floats scroll_bar_active_color : tuple of 3 floats scroll_bar_inactive_color : tuple of 3 floats background_opacity : float """self.view_offset=0self.slots=[]self.selected=[]self.panel_size=sizeself.font_size=font_sizeself.line_spacing=line_spacingself.slot_height=int(self.font_size*self.line_spacing)self.text_color=text_colorself.selected_color=selected_colorself.unselected_color=unselected_colorself.background_opacity=background_opacity# self.panel.resize(size)self.values=valuesself.multiselection=multiselectionself.last_selection_idx=0self.reverse_scrolling=reverse_scrollingsuper(ListBox2D,self).__init__()denom=len(self.values)-self.nb_slotsifnotdenom:denom+=1self.scroll_step_size=(self.slot_height*self.nb_slots-self.scroll_bar.height)/denomself.scroll_bar_active_color=scroll_bar_active_colorself.scroll_bar_inactive_color=scroll_bar_inactive_colorself.scroll_bar.color=self.scroll_bar_inactive_colorself.scroll_bar.opacity=self.background_opacityself.position=positionself.scroll_init_position=0self.update()# Offer some standard hooks to the user.self.on_change=lambda:Nonedef_setup(self):"""Setup this UI component. Create the ListBox (Panel2D) filled with empty slots (ListBoxItem2D). """self.margin=10size=self.panel_sizefont_size=self.font_size# Calculating the number of slots.self.nb_slots=int((size[1]-2*self.margin)//self.slot_height)# This panel facilitates adding slots at the right position.self.panel=Panel2D(size=size,color=(1,1,1))# Add a scroll barscroll_bar_height=(self.nb_slots*(size[1]-2*self.margin)/len(self.values))self.scroll_bar=Rectangle2D(size=(int(size[0]/20),scroll_bar_height))iflen(self.values)<=self.nb_slots:self.scroll_bar.set_visibility(False)self.scroll_bar.height=0self.panel.add_element(self.scroll_bar,size-self.scroll_bar.size-self.margin)# Initialisation of empty text actorsself.slot_width=(size[0]-self.scroll_bar.size[0]-2*self.margin-self.margin)x=self.marginy=size[1]-self.marginfor_inrange(self.nb_slots):y-=self.slot_heightitem=ListBoxItem2D(list_box=self,size=(self.slot_width,self.slot_height),text_color=self.text_color,selected_color=self.selected_color,unselected_color=self.unselected_color,background_opacity=self.background_opacity,)item.textblock.font_size=font_sizeself.slots.append(item)self.panel.add_element(item,(x,y+self.margin))# Add default events listener for this UI component.self.scroll_bar.on_left_mouse_button_pressed=self.scroll_click_callbackself.scroll_bar.on_left_mouse_button_released=self.scroll_release_callbackself.scroll_bar.on_left_mouse_button_dragged=self.scroll_drag_callback# Handle mouse wheel events on the panel.up_event="MouseWheelForwardEvent"down_event="MouseWheelBackwardEvent"ifself.reverse_scrolling:up_event,down_event=down_event,up_event# Swap eventsself.add_callback(self.panel.background.actor,up_event,self.up_button_callback)self.add_callback(self.panel.background.actor,down_event,self.down_button_callback)# Handle mouse wheel events on the slots.forslotinself.slots:self.add_callback(slot.background.actor,up_event,self.up_button_callback)self.add_callback(slot.background.actor,down_event,self.down_button_callback)self.add_callback(slot.textblock.actor,up_event,self.up_button_callback)self.add_callback(slot.textblock.actor,down_event,self.down_button_callback)
def_get_actors(self):"""Get the actors composing this UI component."""returnself.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)forslotinself.slots:clip_overflow(slot.textblock,self.slot_width)def_get_size(self):returnself.panel.sizedef_set_position(self,coords):"""Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.panel.position=coords
[docs]defup_button_callback(self,i_ren,_obj,_list_box):"""Pressing up button scrolls up in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box: :class:`ListBox2D` """ifself.view_offset>0:self.view_offset-=1self.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]+self.scroll_step_size,)self.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position),)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defdown_button_callback(self,i_ren,_obj,_list_box):"""Pressing down button scrolls down in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box: :class:`ListBox2D` """view_end=self.view_offset+self.nb_slotsifview_end<len(self.values):self.view_offset+=1self.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]-self.scroll_step_size,)self.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position),)i_ren.force_render()i_ren.event.abort()# Stop propagating the event.
[docs]defscroll_click_callback(self,i_ren,_obj,_rect_obj):"""Callback to change the color of the bar when it is clicked. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _rect_obj: :class:`Rectangle2D` """self.scroll_bar.color=self.scroll_bar_active_colorself.scroll_init_position=i_ren.event.position[1]i_ren.force_render()i_ren.event.abort()
[docs]defscroll_release_callback(self,i_ren,_obj,_rect_obj):"""Callback to change the color of the bar when it is released. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor rect_obj: :class:`Rectangle2D` """self.scroll_bar.color=self.scroll_bar_inactive_colori_ren.force_render()
[docs]defscroll_drag_callback(self,i_ren,_obj,_rect_obj):"""Drag scroll bar in the combo box. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor rect_obj: :class:`Rectangle2D` """position=i_ren.event.positionoffset=int((position[1]-self.scroll_init_position)/self.scroll_step_size)ifoffset>0andself.view_offset>0:offset=min(offset,self.view_offset)elifoffset<0and(self.view_offset+self.nb_slots<len(self.values)):offset=min(-offset,len(self.values)-self.nb_slots-self.view_offset)offset=-offsetelse:returnself.view_offset-=offsetself.update()scroll_bar_idx=self.panel._elements.index(self.scroll_bar)self.scroll_bar.center=(self.scroll_bar.center[0],self.scroll_bar.center[1]+offset*self.scroll_step_size,)self.scroll_init_position+=offset*self.scroll_step_sizeself.panel.element_offsets[scroll_bar_idx]=(self.scroll_bar,(self.scroll_bar.position-self.panel.position),)i_ren.force_render()i_ren.event.abort()
[docs]defupdate(self):"""Refresh listbox's content."""view_start=self.view_offsetview_end=view_start+self.nb_slotsvalues_to_show=self.values[view_start:view_end]# Populate slots according to the view.fori,choiceinenumerate(values_to_show):slot=self.slots[i]slot.element=choiceifslot.textblock.sceneisnotNone:clip_overflow(slot.textblock,self.slot_width)slot.set_visibility(True)ifslot.size[1]!=self.slot_height:slot.resize((self.slot_width,self.slot_height))ifslot.elementinself.selected:slot.select()else:slot.deselect()# Flush remaining slots.forslotinself.slots[len(values_to_show):]:slot.element=Noneslot.set_visibility(False)slot.resize((self.slot_width,0))slot.deselect()
[docs]defupdate_scrollbar(self):"""Change the scroll-bar height when the values in the listbox change """self.scroll_bar.set_visibility(True)self.scroll_bar.height=(self.nb_slots*(self.panel_size[1]-2*self.margin)/len(self.values))self.scroll_step_size=(self.slot_height*self.nb_slots-self.scroll_bar.height)/(len(self.values)-self.nb_slots)self.panel.update_element(self.scroll_bar,self.panel_size-self.scroll_bar.size-self.margin)iflen(self.values)<=self.nb_slots:self.scroll_bar.set_visibility(False)self.scroll_bar.height=0
[docs]@warn_on_args_to_kwargs()defselect(self,item,*,multiselect=False,range_select=False):"""Select the item. Parameters ---------- item: ListBoxItem2D's object Item to select. multiselect: {True, False} If True and multiselection is allowed, the item is added to the selection. Otherwise, the selection will only contain the provided item unless range_select is True. range_select: {True, False} If True and multiselection is allowed, all items between the last selected item and the current one will be added to the selection. Otherwise, the selection will only contain the provided item unless multi_select is True. """selection_idx=self.values.index(item.element)ifself.multiselectionandrange_select:self.clear_selection()step=1ifselection_idx>=self.last_selection_idxelse-1foriinrange(self.last_selection_idx,selection_idx+step,step):self.selected.append(self.values[i])elifself.multiselectionandmultiselect:ifitem.elementinself.selected:self.selected.remove(item.element)else:self.selected.append(item.element)self.last_selection_idx=selection_idxelse:self.clear_selection()self.selected.append(item.element)self.last_selection_idx=selection_idxself.on_change()# Call hook.self.update()
[docs]classListBoxItem2D(UI):"""The text displayed in a listbox."""@warn_on_args_to_kwargs()def__init__(self,list_box,size,*,text_color=(1.0,0.0,0.0),selected_color=(0.4,0.4,0.4),unselected_color=(0.9,0.9,0.9),background_opacity=1.0,):"""Init ListBox Item instance. Parameters ---------- list_box : :class:`ListBox` The ListBox reference this text belongs to. size : tuple of 2 ints The size of the listbox item. text_color : tuple of 3 floats unselected_color : tuple of 3 floats selected_color : tuple of 3 floats background_opacity : float """super(ListBoxItem2D,self).__init__()self._element=Noneself.list_box=list_boxself.background.resize(size)self.background_opacity=background_opacityself.selected=Falseself.text_color=text_colorself.textblock.color=self.text_colorself.selected_color=selected_colorself.unselected_color=unselected_colorself.background.opacity=self.background_opacityself.deselect()def_setup(self):"""Setup this UI component. Create the ListBoxItem2D with its background (Rectangle2D) and its label (TextBlock2D). """self.background=Rectangle2D()self.textblock=TextBlock2D(justification="left",vertical_justification="middle")# Add default events listener for this UI component.self.add_callback(self.textblock.actor,"LeftButtonPressEvent",self.left_button_clicked)self.add_callback(self.background.actor,"LeftButtonPressEvent",self.left_button_clicked)def_get_actors(self):"""Get the actors composing this UI component."""returnself.background.actors+self.textblock.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.background.add_to_scene(scene)self.textblock.add_to_scene(scene)def_get_size(self):returnself.background.sizedef_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.textblock.position=coords# Center background underneath the text.position=coordsself.background.position=(position[0],position[1]-self.background.size[1]/2.0,)
[docs]defleft_button_clicked(self,i_ren,_obj,_list_box_item):"""Handle left click for this UI element. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _list_box_item: :class:`ListBoxItem2D` """multiselect=i_ren.event.ctrl_keyrange_select=i_ren.event.shift_keyself.list_box.select(item=self,multiselect=multiselect,range_select=range_select)i_ren.force_render()
[docs]classFileMenu2D(UI):"""A menu to select files in the current folder. Can go to new folder, previous folder and select multiple files. Attributes ---------- extensions: ['extension1', 'extension2', ....] To show all files, extensions=["*"] or [""] List of extensions to be shown as files. listbox : :class: 'ListBox2D' Container for the menu. """@warn_on_args_to_kwargs()def__init__(self,directory_path,*,extensions=None,position=(0,0),size=(100,300),multiselection=True,reverse_scrolling=False,font_size=20,line_spacing=1.4,):"""Init class instance. Parameters ---------- extensions: list(string) List of extensions to be shown as files. directory_path: string Path of the directory where this dialog should open. 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. multiselection: {True, False} Whether multiple values can be selected at once. reverse_scrolling: {True, False} If True, scrolling up will move the list of files down. font_size: int The font size in pixels. line_spacing: float Distance between listbox's items in pixels. """self.font_size=font_sizeself.multiselection=multiselectionself.reverse_scrolling=reverse_scrollingself.line_spacing=line_spacingself.extensions=extensionsor["*"]self.current_directory=directory_pathself.menu_size=sizeself.directory_contents=[]super(FileMenu2D,self).__init__()self.position=positionself.set_slot_colors()def_setup(self):"""Setup this UI component. Create the ListBox (Panel2D) filled with empty slots (ListBoxItem2D). """self.directory_contents=self.get_all_file_names()content_names=[x[0]forxinself.directory_contents]self.listbox=ListBox2D(values=content_names,multiselection=self.multiselection,font_size=self.font_size,line_spacing=self.line_spacing,reverse_scrolling=self.reverse_scrolling,size=self.menu_size,)self.add_callback(self.listbox.scroll_bar.actor,"MouseMoveEvent",self.scroll_callback)# Handle mouse wheel events on the panel.up_event="MouseWheelForwardEvent"down_event="MouseWheelBackwardEvent"ifself.reverse_scrolling:up_event,down_event=down_event,up_event# Swap eventsself.add_callback(self.listbox.panel.background.actor,up_event,self.scroll_callback)self.add_callback(self.listbox.panel.background.actor,down_event,self.scroll_callback)# Handle mouse wheel events on the slots.forslotinself.listbox.slots:self.add_callback(slot.background.actor,up_event,self.scroll_callback)self.add_callback(slot.background.actor,down_event,self.scroll_callback)self.add_callback(slot.textblock.actor,up_event,self.scroll_callback)self.add_callback(slot.textblock.actor,down_event,self.scroll_callback)slot.add_callback(slot.textblock.actor,"LeftButtonPressEvent",self.directory_click_callback,)slot.add_callback(slot.background.actor,"LeftButtonPressEvent",self.directory_click_callback,)def_get_actors(self):"""Get the actors composing this UI component."""returnself.listbox.actors
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.listbox.position=coordsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self.listbox.add_to_scene(scene)def_get_size(self):returnself.listbox.size
[docs]defget_all_file_names(self):"""Get file and directory names. Returns ------- all_file_names: list((string, {"directory", "file"})) List of all file and directory names as string. """all_file_names=[]directory_names=self.get_directory_names()fordirectory_nameindirectory_names:all_file_names.append((directory_name,"directory"))file_names=self.get_file_names()forfile_nameinfile_names:all_file_names.append((file_name,"file"))returnall_file_names
[docs]defget_directory_names(self):"""Find names of all directories in the current_directory Returns ------- directory_names: list(string) List of all directory names as string. """# A list of directory names in the current directorydirectory_names=[]for_,dirnames,_inos.walk(self.current_directory):directory_names+=dirnamesbreakdirectory_names.sort(key=lambdas:s.lower())directory_names.insert(0,"../")returndirectory_names
[docs]defget_file_names(self):"""Find names of all files in the current_directory Returns ------- file_names: list(string) List of all file names as string. """# A list of file names with extension in the current directoryfiles=[]for_,_,finos.walk(self.current_directory):files+=fbreakfile_names=[]if"*"inself.extensionsor""inself.extensions:file_names=fileselse:forextinself.extensions:forfileinfiles:iffile.endswith("."+ext):file_names.append(file)file_names.sort(key=lambdas:s.lower())returnfile_names
[docs]defset_slot_colors(self):"""Set the text color of the slots based on the type of element they show. Blue for directories and green for files. """foridx,slotinenumerate(self.listbox.slots):list_idx=min(self.listbox.view_offset+idx,len(self.directory_contents)-1)ifself.directory_contents[list_idx][1]=="directory":slot.textblock.color=(0,0.6,0)elifself.directory_contents[list_idx][1]=="file":slot.textblock.color=(0,0,0.7)
[docs]defscroll_callback(self,i_ren,_obj,_filemenu_item):"""Handle scroll and change the slot text colors. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor _filemenu_item: :class:`FileMenu2D` """self.set_slot_colors()i_ren.force_render()i_ren.event.abort()
[docs]defdirectory_click_callback(self,i_ren,_obj,listboxitem):"""Handle the move into a directory if it has been clicked. Parameters ---------- i_ren: :class:`CustomInteractorStyle` obj: :class:`vtkActor` The picked actor listboxitem: :class:`ListBoxItem2D` """if(listboxitem.element,"directory")inself.directory_contents:new_directory_path=os.path.join(self.current_directory,listboxitem.element)ifos.access(new_directory_path,os.R_OK):self.current_directory=new_directory_pathself.directory_contents=self.get_all_file_names()content_names=[x[0]forxinself.directory_contents]self.listbox.clear_selection()self.listbox.values=content_namesself.listbox.view_offset=0self.listbox.update()self.listbox.update_scrollbar()self.set_slot_colors()i_ren.force_render()i_ren.event.abort()
[docs]classDrawShape(UI):"""Create and Manage 2D Shapes."""@warn_on_args_to_kwargs()def__init__(self,shape_type,*,drawpanel=None,position=(0,0)):"""Init this UI element. Parameters ---------- shape_type : string Type of shape to be created. drawpanel : DrawPanel, optional Reference to the main canvas on which it is drawn. position : (float, float), optional (x, y) in pixels. """self.shape=Noneself.shape_type=shape_type.lower()self.drawpanel=drawpanelself.max_size=Noneself.rotation=0super(DrawShape,self).__init__(position=position)self.shape.color=np.random.random(3)def_setup(self):"""Setup this UI component. Create a Shape. """ifself.shape_type=="line":self.shape=Rectangle2D(size=(3,3))elifself.shape_type=="quad":self.shape=Rectangle2D(size=(3,3))elifself.shape_type=="circle":self.shape=Disk2D(outer_radius=2)else:raiseIOError("Unknown shape type: {}.".format(self.shape_type))self.shape.on_left_mouse_button_pressed=self.left_button_pressedself.shape.on_left_mouse_button_dragged=self.left_button_draggedself.shape.on_left_mouse_button_released=self.left_button_releaseddef_get_actors(self):"""Get the actors composing this UI component."""returnself.shapedef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self._scene=sceneself.shape.add_to_scene(scene)def_get_size(self):returnself.shape.sizedef_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """ifself.shape_type=="circle":self.shape.center=coordselse:self.shape.position=coords
[docs]defupdate_shape_position(self,center_position):"""Update the center position on the canvas. Parameters ---------- center_position: (float, float) Absolute pixel coordinates (x, y). """new_center=self.clamp_position(center=center_position)self.drawpanel.canvas.update_element(self,new_center,anchor="center")self.cal_bounding_box()
@propertydefcenter(self):returnself._bounding_box_min+self._bounding_box_size//2@center.setterdefcenter(self,coords):"""Position the center of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """new_center=np.array(coords)new_lower_left_corner=new_center-self._bounding_box_size//2self.position=new_lower_left_corner+self._bounding_box_offsetself.cal_bounding_box()@propertydefis_selected(self):returnself._is_selected@is_selected.setterdefis_selected(self,value):ifself.drawpanelandvalue:self.drawpanel.current_shape=selfself._is_selected=valueself.selection_change()
[docs]defrotate(self,angle):"""Rotate the vertices of the UI component using specific angle. Parameters ---------- angle: float Value by which the vertices are rotated in radian. """ifself.shape_type=="circle":returnpoints_arr=vertices_from_actor(self.shape.actor)new_points_arr=rotate_2d(points_arr,angle)set_polydata_vertices(self.shape._polygonPolyData,new_points_arr)update_actor(self.shape.actor)self.cal_bounding_box()
[docs]defcal_bounding_box(self):"""Calculate the min, max position and the size of the bounding box."""vertices=self.position+vertices_from_actor(self.shape.actor)[:,:-1](self._bounding_box_min,self._bounding_box_max,self._bounding_box_size,)=cal_bounding_box_2d(vertices)self._bounding_box_offset=self.position-self._bounding_box_min
[docs]@warn_on_args_to_kwargs()defclamp_position(self,*,center=None):"""Clamp the given center according to the DrawPanel canvas. Parameters ---------- center : (float, float) (x, y) in pixels. Returns ------- new_center: ndarray(int) New center for the shape. """center=self.centerifcenterisNoneelsecenternew_center=np.clip(center,self._bounding_box_size//2,self.drawpanel.canvas.size-self._bounding_box_size//2,)returnnew_center.astype(int)
[docs]defresize(self,size):"""Resize the UI."""ifself.shape_type=="line":hyp=np.hypot(size[0],size[1])self.shape.resize((hyp,3))self.rotate(angle=np.arctan2(size[1],size[0]))elifself.shape_type=="quad":self.shape.resize(size)elifself.shape_type=="circle":hyp=np.hypot(size[0],size[1])ifself.max_sizeandhyp>self.max_size:hyp=self.max_sizeself.shape.outer_radius=hypself.cal_bounding_box()
[docs]defremove(self):"""Remove the Shape and all related actors."""self._scene.rm(self.shape.actor)self.drawpanel.rotation_slider.set_visibility(False)
[docs]classDrawPanel(UI):"""The main Canvas(Panel2D) on which everything would be drawn."""@warn_on_args_to_kwargs()def__init__(self,*,size=(400,400),position=(0,0),is_draggable=False):"""Init this UI element. Parameters ---------- size : (int, int), optional Width and height in pixels of this UI component. position : (float, float), optional (x, y) in pixels. is_draggable : bool, optional Whether the background canvas will be draggble or not. """self.panel_size=sizesuper(DrawPanel,self).__init__(position=position)self.is_draggable=is_draggableself.current_mode=Noneifis_draggable:self.current_mode="selection"self.shape_list=[]self.current_shape=Nonedef_setup(self):"""Setup this UI component. Create a Canvas(Panel2D). """self.canvas=Panel2D(size=self.panel_size)self.canvas.background.on_left_mouse_button_pressed=self.left_button_pressedself.canvas.background.on_left_mouse_button_dragged=self.left_button_dragged# Todo# Convert mode_data into a private variable and make it read-only# Then add the ability to insert user-defined modemode_data={"selection":["selection.png","selection-pressed.png"],"line":["line.png","line-pressed.png"],"quad":["quad.png","quad-pressed.png"],"circle":["circle.png","circle-pressed.png"],"delete":["delete.png","delete-pressed.png"],}padding=5# Todo# Add this size to __init__mode_panel_size=(len(mode_data)*35+2*padding,40)self.mode_panel=Panel2D(size=mode_panel_size,color=(0.5,0.5,0.5))btn_pos=np.array([0,0])formode,fnameinmode_data.items():icon_files=[]icon_files.append((mode,read_viz_icons(style="new_icons",fname=fname[0])))icon_files.append((mode+"-pressed",read_viz_icons(style="new_icons",fname=fname[1])))btn=Button2D(icon_fnames=icon_files)defmode_selector(i_ren,_obj,btn):self.current_mode=btn.icon_names[0]i_ren.force_render()btn.on_left_mouse_button_pressed=mode_selectorself.mode_panel.add_element(btn,btn_pos+padding)btn_pos[0]+=btn.size[0]+paddingself.canvas.add_element(self.mode_panel,(0,-mode_panel_size[1]))self.mode_text=TextBlock2D(text="Select appropriate drawing mode using below icon")self.canvas.add_element(self.mode_text,(0.0,1.0))self.rotation_slider=RingSlider2D(initial_value=0,text_template="{angle:5.1f}°")self.rotation_slider.set_visibility(False)defrotate_shape(slider):angle=slider.valueprevious_angle=slider.previous_valuerotation_angle=angle-previous_anglecurrent_center=self.current_shape.centerself.current_shape.rotate(np.deg2rad(rotation_angle))self.current_shape.rotation=slider.valueself.current_shape.update_shape_position(current_center-self.canvas.position)self.rotation_slider.on_moving_slider=rotate_shapedef_get_actors(self):"""Get the actors composing this UI component."""returnself.canvas.actorsdef_add_to_scene(self,scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- scene : scene """self._scene=sceneself.canvas.add_to_scene(scene)def_get_size(self):returnself.canvas.sizedef_set_position(self,coords):"""Set the lower-left corner position of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.canvas.position=coords+[0,self.mode_panel.size[1]]slider_position=self.canvas.position+[self.canvas.size[0]-self.rotation_slider.size[0]/2,self.rotation_slider.size[1]/2,]self.rotation_slider.center=slider_position
[docs]defresize(self,size):"""Resize the UI."""pass
[docs]defcal_min_boundary_distance(self,position):"""Calculate minimum distance between the current position and canvas boundary. Parameters ---------- position: (float,float) current position of the shape. Returns ------- float Minimum distance from the boundary. """distance_list=[]# calculate distance from element to left and lower boundarydistance_list.extend(position-self.canvas.position)# calculate distance from element to upper and right boundarydistance_list.extend(self.canvas.position+self.canvas.size-position)returnmin(distance_list)
[docs]defdraw_shape(self,shape_type,current_position):"""Draw the required shape at the given position. Parameters ---------- shape_type: string Type of shape - line, quad, circle. current_position: (float,float) Lower left corner position for the shape. """shape=DrawShape(shape_type=shape_type,drawpanel=self,position=current_position)ifshape_type=="circle":shape.max_size=self.cal_min_boundary_distance(current_position)self.shape_list.append(shape)self._scene.add(shape)self.canvas.add_element(shape,current_position-self.canvas.position)self.update_shape_selection(shape)
[docs]defresize_shape(self,current_position):"""Resize the shape. Parameters ---------- current_position: (float,float) Lower left corner position for the shape. """self.current_shape=self.shape_list[-1]size=current_position-self.current_shape.positionself.current_shape.resize(size)
[docs]defshow_rotation_slider(self):"""Display the RingSlider2D to allow rotation of shape from the center."""self._scene.rm(*self.rotation_slider.actors)self.rotation_slider.add_to_scene(self._scene)self.rotation_slider.set_visibility(True)
[docs]defupdate_button_icons(self,current_mode):"""Update the button icon. Parameters ---------- current_mode: string Current mode of the UI. """forbtninself.mode_panel._elements[1:]:ifbtn.icon_names[0]==current_mode:btn.next_icon()elifbtn.current_icon_id==1:btn.next_icon()
[docs]defclamp_mouse_position(self,mouse_position):"""Restrict the mouse position to the canvas boundary. Parameters ---------- mouse_position: (float,float) Current mouse position. Returns ------- list(float) New clipped position. """returnnp.clip(mouse_position,self.canvas.position,self.canvas.position+self.canvas.size,)
[docs]classPlaybackPanel(UI):"""A playback controller that can do essential functionalities. such as play, pause, stop, and seek. """@warn_on_args_to_kwargs()def__init__(self,*,loop=False,position=(0,0),width=None):self._width=widthifwidthisnotNoneelse900self._auto_width=widthisNoneself._position=positionsuper(PlaybackPanel,self).__init__(position=position)self._playing=Falseself._loop=Noneself.loop()ifloopelseself.play_once()self._speed=1# callback functionsself.on_play_pause_toggle=lambdastate:Noneself.on_play=lambda:Noneself.on_pause=lambda:Noneself.on_stop=lambda:Noneself.on_loop_toggle=lambdais_looping:Noneself.on_progress_bar_changed=lambdax:Noneself.on_speed_up=lambdax:Noneself.on_slow_down=lambdax:Noneself.on_speed_changed=lambdax:Noneself._set_position(position)def_setup(self):"""Setup this Panel component."""self.time_text=TextBlock2D()self.speed_text=TextBlock2D(text="1",font_size=21,color=(0.2,0.2,0.2),bold=True,justification="center",vertical_justification="middle",)self.panel=Panel2D(size=(190,30),color=(1,1,1),align="right",has_border=True,border_color=(0,0.3,0),border_width=2,)play_pause_icons=[("play",read_viz_icons(fname="play3.png")),("pause",read_viz_icons(fname="pause2.png")),]loop_icons=[("once",read_viz_icons(fname="checkmark.png")),("loop",read_viz_icons(fname="infinite.png")),]self._play_pause_btn=Button2D(icon_fnames=play_pause_icons)self._loop_btn=Button2D(icon_fnames=loop_icons)self._stop_btn=Button2D(icon_fnames=[("stop",read_viz_icons(fname="stop2.png"))])self._speed_up_btn=Button2D(icon_fnames=[("plus",read_viz_icons(fname="plus.png"))],size=(15,15))self._slow_down_btn=Button2D(icon_fnames=[("minus",read_viz_icons(fname="minus.png"))],size=(15,15))self._progress_bar=LineSlider2D(initial_value=0,orientation="horizontal",min_value=0,max_value=100,text_alignment="top",length=590,text_template="",line_width=9,)start=0.04w=0.2self.panel.add_element(self._play_pause_btn,(start,0.04))self.panel.add_element(self._stop_btn,(start+w,0.04))self.panel.add_element(self._loop_btn,(start+2*w,0.04))self.panel.add_element(self._slow_down_btn,(start+0.63,0.3))self.panel.add_element(self.speed_text,(start+0.78,0.45))self.panel.add_element(self._speed_up_btn,(start+0.86,0.3))defplay_pause_toggle(i_ren,_obj,_button):self._playing=notself._playingifself._playing:self.play()else:self.pause()self.on_play_pause_toggle(self._playing)i_ren.force_render()defstop(i_ren,_obj,_button):self.stop()i_ren.force_render()defspeed_up(i_ren,_obj,_button):inc=10**np.floor(np.log10(self.speed))self.speed=round(self.speed+inc,13)self.on_speed_up(self._speed)self.on_speed_changed(self._speed)i_ren.force_render()defslow_down(i_ren,_obj,_button):dec=10**np.floor(np.log10(self.speed-self.speed/10))self.speed=round(self.speed-dec,13)self.on_slow_down(self._speed)self.on_speed_changed(self._speed)i_ren.force_render()defloop_toggle(i_ren,_obj,_button):self._loop=notself._loopifself._loop:self.loop()else:self.play_once()self.on_loop_toggle(self._loop)i_ren.force_render()# using the adapters created aboveself._play_pause_btn.on_left_mouse_button_pressed=play_pause_toggleself._stop_btn.on_left_mouse_button_pressed=stopself._loop_btn.on_left_mouse_button_pressed=loop_toggleself._speed_up_btn.on_left_mouse_button_pressed=speed_upself._slow_down_btn.on_left_mouse_button_pressed=slow_downdefon_progress_change(slider):t=slider.valueself.on_progress_bar_changed(t)self.current_time=tself._progress_bar.on_moving_slider=on_progress_changeself.current_time=0
[docs]defplay(self):"""Play the playback"""self._playing=Trueself._play_pause_btn.set_icon_by_name("pause")self.on_play()
[docs]defstop(self):"""Stop the playback"""self._playing=Falseself._play_pause_btn.set_icon_by_name("play")self.on_stop()
[docs]defpause(self):"""Pause the playback"""self._playing=Falseself._play_pause_btn.set_icon_by_name("play")self.on_pause()
[docs]defloop(self):"""Set repeating mode to loop."""self._loop=Trueself._loop_btn.set_icon_by_name("loop")
[docs]defplay_once(self):"""Set repeating mode to repeat once."""self._loop=Falseself._loop_btn.set_icon_by_name("once")
@propertydeffinal_time(self):"""Set final progress slider time value. Returns ------- float Final time for the progress slider. """returnself._progress_bar.max_value@final_time.setterdeffinal_time(self,t):"""Set final progress slider time value. Parameters ---------- t: float Final time for the progress slider. """self._progress_bar.max_value=t@propertydefcurrent_time(self):"""Get current time of the progress slider. Returns ------- float Progress slider current value. """returnself._progress_bar.value@current_time.setterdefcurrent_time(self,t):"""Set progress slider value. Parameters ---------- t: float Current time to be set. """self._progress_bar.value=tself.current_time_str=t@propertydefcurrent_time_str(self):"""Returns current time as a string. Returns ------- str Current time formatted as a string in the form:`HH:MM:SS`. """returnself.time_text.message@current_time_str.setterdefcurrent_time_str(self,t):"""Set time counter. Parameters ---------- t: float Time to be set in the time_text counter. Notes ----- This should only be used when the `current_value` is not being set since setting`current_value` automatically sets this property as well. """t=np.clip(t,0,self.final_time)ifself.final_time<3600:m,s=divmod(t,60)t_str=r"%02d:%05.2f"%(m,s)else:m,s=divmod(t,60)h,m=divmod(m,60)t_str=r"%02d:%02d:%02d"%(h,m,s)self.time_text.message=t_str@propertydefspeed(self):"""Returns current speed. Returns ------- str Current time formatted as a string in the form:`HH:MM:SS`. """returnself._speed@speed.setterdefspeed(self,speed):"""Set time counter. Parameters ---------- speed: float Speed value to be set in the speed_text counter. """ifspeed<=0:speed=0.01self._speed=speedspeed_str=f"{speed}".strip("0").rstrip(".")self.speed_text.font_size=21if0.01<=speed<100else14self.speed_text.message=speed_str
def_get_actors(self):"""Get the actors composing this UI component."""returnself.panel.actors+self._progress_bar.actors+self.time_text.actorsdef_add_to_scene(self,_scene):"""Add all subcomponents or VTK props that compose this UI component. Parameters ---------- _scene : scene """defresize_cbk(caller,ev):ifself._auto_width:width=_scene.GetSize()[0]ifwidth==self.width:returnself._width=widthself._set_position(self.position)self._progress_bar.value=self._progress_bar.value_scene.AddObserver(Command.StartEvent,resize_cbk)self.panel.add_to_scene(_scene)self._progress_bar.add_to_scene(_scene)self.time_text.add_to_scene(_scene)@propertydefwidth(self):"""Return the width of the PlaybackPanel Returns ------- float The width of the PlaybackPanel. """returnself._width@width.setterdefwidth(self,width):"""Set width of the PlaybackPanel. Parameters ---------- width: float The width of the whole panel. If set to None, The width will be the same as the window's width. """self._width=widthifwidthisnotNoneelse900self._auto_width=widthisNoneself._set_position(self.position)def_set_position(self,_coords):x,y=self.positionwidth=self.widthself.panel.position=(x+5,y+5)progress_length=max(width-310-x,1.0)self._progress_bar.track.width=progress_lengthself._progress_bar.center=(x+215+progress_length/2,y+20)self.time_text.position=(x+225+progress_length,y+10)def_get_size(self):returnself.panel.size+self._progress_bar.size+self.time_text.size
[docs]classCard2D(UI):"""Card element to show image and related text Attributes ---------- image: :class: 'ImageContainer2D' Renders the image on the card. title_box: :class: 'TextBlock2D' Displays the title on card. body_box: :class: 'TextBLock2D' Displays the body text. """@warn_on_args_to_kwargs()def__init__(self,image_path,*,body_text="",draggable=True,title_text="",padding=10,position=(0,0),size=(400,400),image_scale=0.5,bg_color=(0.5,0.5,0.5),bg_opacity=1,title_color=(0.0,0.0,0.0),body_color=(0.0,0.0,0.0),border_color=(1.0,1.0,1.0),border_width=0,maintain_aspect=False,):"""Parameters ---------- image_path: str Path of the image, supports png and jpg/jpeg images body_text: str, optional Card body text draggable: Bool, optional If the card should be draggable title_text: str, optional Card title text padding: int, optional Padding between image, title, body position : (float, float), optional Absolute coordinates (x, y) of the lower-left corner of the UI component size : (int, int), optional Width and height of the pixels of this UI component. image_scale: float, optional fraction of size taken by the image (between 0 , 1) bg_color: (float, float, float), optional Background color of card bg_opacity: float, optional Background opacity title_color: (float, float, float), optional Title text color body_color: (float, float, float), optional Body text color border_color: (float, float, float), optional Border color border_width: int, optional Width of the border maintain_aspect: bool, optional If the image should be scaled to maintain aspect ratio """self.image_path=image_pathself._basename=os.path.basename(self.image_path)self._extension=self._basename.split(".")[-1]ifself._extensionnotin["jpg","jpeg","png"]:raiseUnidentifiedImageError(f"Image extension {self._extension} not supported")self.body_text=body_textself.title_text=title_textself.draggable=draggableself.card_size=sizeself.padding=paddingself.title_color=[np.clip(value,0,1)forvalueintitle_color]self.body_color=[np.clip(value,0,1)forvalueinbody_color]self.bg_color=[np.clip(value,0,1)forvalueinbg_color]self.border_color=[np.clip(value,0,1)forvalueinborder_color]self.bg_opacity=bg_opacityself.text_scale=np.clip(1-image_scale,0,1)self.image_scale=np.clip(image_scale,0,1)self.maintain_aspect=maintain_aspectifself.maintain_aspect:self._true_image_size=Image.open(urlopen(self.image_path)).sizeself._image_size=(self.card_size[0],self.card_size[1]*self.image_scale)self.border_width=border_widthself.has_border=bool(border_width)super(Card2D,self).__init__()self.position=positionifself.maintain_aspect:self._new_size=(self._true_image_size[0],self._true_image_size[1]//self.image_scale,)self.resize(self._new_size)else:self.resize(size)def_setup(self):"""Setup this UI component Create the image. Create the title and body. Create a Panel2D widget to hold image, title, body. """self.image=ImageContainer2D(img_path=self.image_path,size=self._image_size)self.body_box=TextBlock2D(text=self.body_text,color=self.body_color)self.title_box=TextBlock2D(text=self.title_text,bold=True,color=self.title_color)self.panel=Panel2D(self.card_size,color=self.bg_color,opacity=self.bg_opacity,border_color=self.border_color,border_width=self.border_width,has_border=self.has_border,)self.panel.add_element(self.image,(0.0,0.0))self.panel.add_element(self.title_box,(0.0,0.0))self.panel.add_element(self.body_box,(0.0,0.0))ifself.draggable:self.panel.background.on_left_mouse_button_dragged=(self.left_button_dragged)self.panel.background.on_left_mouse_button_pressed=(self.left_button_pressed)self.image.on_left_mouse_button_dragged=self.left_button_draggedself.image.on_left_mouse_button_pressed=self.left_button_pressedelse:self.panel.background.on_left_mouse_button_dragged=(lambdai_ren,_obj,_comp:i_ren.force_render)def_get_actors(self):"""Get the actors composing this UI component."""returnself.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)ifself.size[0]<=200:clip_overflow(self.body_box,self.size[0]-2*self.padding)else:wrap_overflow(self.body_box,self.size[0]-2*self.padding)wrap_overflow(self.title_box,self.size[0]-2*self.padding)def_get_size(self):returnself.panel.size
def_set_position(self,_coords):"""Position the lower-left corner of this UI component. Parameters ---------- coords: (float, float) Absolute pixel coordinates (x, y). """self.panel.position=_coords@propertydefcolor(self):"""Returns the background color of card."""returnself.panel.color@color.setterdefcolor(self,color):"""Sets background color of card. Parameters ---------- color : list of 3 floats. """self.panel.color=color@propertydefbody(self):"""Returns the body text of the card."""returnself.body_box.message@body.setterdefbody(self,text):self.body_box.message=text@propertydeftitle(self):"""Returns the title text of the card"""returnself.title_box.message@title.setterdeftitle(self,text):self.title_box.message=text
[docs]classSpinBox(UI):"""SpinBox UI."""@warn_on_args_to_kwargs()def__init__(self,*,position=(350,400),size=(300,100),padding=10,panel_color=(1,1,1),min_val=0,max_val=100,initial_val=50,step=1,max_column=10,max_line=2,):"""Init this UI element. Parameters ---------- position : (int, int), optional Absolute coordinates (x, y) of the lower-left corner of this UI component. size : (int, int), optional Width and height in pixels of this UI component. padding : int, optional Distance between TextBox and Buttons. panel_color : (float, float, float), optional Panel color of SpinBoxUI. min_val: int, optional Minimum value of SpinBoxUI. max_val: int, optional Maximum value of SpinBoxUI. initial_val: int, optional Initial value of SpinBoxUI. step: int, optional Step value of SpinBoxUI. max_column: int, optional Max number of characters in a line. max_line: int, optional Max number of lines in the textbox. """self.panel_size=sizeself.padding=paddingself.panel_color=panel_colorself.min_val=min_valself.max_val=max_valself.step=stepself.max_column=max_columnself.max_line=max_linesuper(SpinBox,self).__init__(position=position)self.value=initial_valself.resize(size)self.on_change=lambdaui:Nonedef_setup(self):"""Setup this UI component. Create the SpinBoxUI with Background (Panel2D) and InputBox (TextBox2D) and Increment,Decrement Button (Button2D). """self.panel=Panel2D(size=self.panel_size,color=self.panel_color)self.textbox=TextBox2D(width=self.max_column,height=self.max_line)self.textbox.text.dynamic_bbox=Falseself.textbox.text.auto_font_scale=Trueself.increment_button=Button2D(icon_fnames=[("up",read_viz_icons(fname="circle-up.png"))])self.decrement_button=Button2D(icon_fnames=[("down",read_viz_icons(fname="circle-down.png"))])self.panel.add_element(self.textbox,(0,0))self.panel.add_element(self.increment_button,(0,0))self.panel.add_element(self.decrement_button,(0,0))# Adding button click callbacksself.increment_button.on_left_mouse_button_pressed=self.increment_callbackself.decrement_button.on_left_mouse_button_pressed=self.decrement_callbackself.textbox.off_focus=self.textbox_update_value
def_get_actors(self):"""Get the actors composing this UI component."""returnself.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)def_get_size(self):returnself.panel.sizedef_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.center=coords
[docs]defvalidate_value(self,value):"""Validate and convert the given value into integer. Parameters ---------- value : str Input value received from the textbox. Returns ------- int If valid return converted integer else the previous value. """ifvalue.isnumeric():returnint(value)returnself.value
[docs]defincrement(self):"""Increment the current value by the step."""current_val=self.validate_value(self.textbox.message)self.value=current_val+self.stepself.on_change(self)
[docs]defdecrement(self):"""Decrement the current value by the step."""current_val=self.validate_value(self.textbox.message)self.value=current_val-self.stepself.on_change(self)