def add_line( self, pos1: Tuple2NumberType, pos2: Tuple2NumberType, color: ColorInputType, width: int = 1, prev: bool = True, **kwargs ) -> str: """ Adds a line. .. note:: Consider ``(0, 0)`` coordinates as the center of the object. kwargs (Optional) - ``use_center_positioning`` Uses object center position as *(0, 0)*. ``True`` by default :param pos1: Position 1 (x1, y1) :param pos2: Position 2 (x2, y2) :param color: Line color :param width: Line width in px :param prev: If ``True`` draw previous the object, else draws post :param kwargs: Optional keyword arguments :return: ID of the decoration """ assert_vector(pos1, 2) assert_vector(pos2, 2) color = assert_color(color) assert isinstance(width, int) and width >= 1 length = math.sqrt(math.pow(pos1[0] - pos2[0], 2) + math.pow(pos1[1] - pos2[1], 2)) assert length > 0, 'line cannot be zero-length' return self._add_decor(DECORATION_LINE, prev, ((tuple(pos1), tuple(pos2)), color, width, kwargs))
def __init__( self, image_path: Union[str, 'BaseImage', 'Path', 'BytesIO'], angle: NumberType = 0, image_id: str = '', onselect: CallbackType = None, scale: Tuple2NumberType = (1, 1), scale_smooth: bool = True ) -> None: assert isinstance(image_path, (str, Path, BaseImage, BytesIO)) assert isinstance(image_id, str) assert isinstance(angle, NumberInstance) assert isinstance(scale_smooth, bool) assert_vector(scale, 2) super(Image, self).__init__( onselect=onselect, widget_id=image_id ) if isinstance(image_path, BaseImage): self._image = image_path else: self._image = BaseImage(image_path) self._image.rotate(angle) self._image.scale(scale[0], scale[1], smooth=scale_smooth)
def middle_rect_click(rect: Union['pygame_menu.widgets.Widget', 'pygame.Rect', Tuple2NumberType], menu: Optional['pygame_menu.Menu'] = None, evtype: int = pygame.MOUSEBUTTONUP, inlist: bool = True, rel: Tuple2IntType = (0, 0), button: int = 3, delta: Tuple2IntType = (0, 0), testmode: bool = True) -> EventListType: """ Return event clicking the middle of a given rect. :param rect: Widget, Rect object, or Tuple :param menu: Menu object :param evtype: event type, it can be MOUSEBUTTONUP, MOUSEBUTTONDOWN, MOUSEMOTION, FINGERUP, FINGERDOWN, FINGERMOTION :param inlist: If ``True`` return the event within a list :param rel: Rel position (relative movement) :param button: Which button presses, ``1`` to ``3`` are the main buttons; ``4`` and ``5`` is the wheel :param delta: Add tuple to rect position :param testmode: Event is in test mode :return: Event """ assert isinstance(button, int) and button > 0 assert_vector(rel, 2, int) assert_vector(delta, 2, int) if isinstance(rect, pygame_menu.widgets.Widget): x, y = rect.get_rect(to_real_position=True, apply_padding=False, render=True).center menu = rect.get_menu() elif isinstance(rect, pygame.Rect): x, y = rect.center elif isinstance(rect, tuple): x, y = rect elif isinstance(rect, list): x, y = rect[0], rect[1] else: raise ValueError('unknown rect type') if evtype == FINGERDOWN or evtype == FINGERUP or evtype == FINGERMOTION: assert menu is not None, \ 'menu cannot be none if FINGERDOWN, FINGERUP, or FINGERMOTION' display = menu.get_window_size() evt = pygame.event.Event( evtype, { 'button': button, 'rel': rel, 'test': testmode, 'x': (x + delta[0]) / display[0], 'y': (y + delta[1]) / display[1] }) if inlist: evt = [evt] return evt return PygameEventUtils.mouse_click(x=x + delta[0], y=y + delta[1], inlist=inlist, evtype=evtype, rel=rel, button=button, testmode=testmode)
def get_at( self, pos: Tuple2NumberType, ignore_alpha: bool = False ) -> Union[Tuple3IntType, Tuple4IntType]: """ Get the color from a certain position in image on x-axis and y-axis (x, y). ``get_at`` return a copy of the RGBA Color value at the given pixel. If the Surface has no per pixel alpha, then the alpha value will always be ``255`` (opaque). If the pixel position is outside the area of the Surface an ``IndexError`` exception will be raised. Getting and setting pixels one at a time is generally too slow to be used in a game or realtime situation. It is better to use methods which operate on many pixels at a time like with the blit, fill and draw methods - or by using pygame.surfarraypygame module for accessing surface pixel data using array interfaces/pygame.PixelArraypygame object for direct pixel access of surfaces. :param pos: Position on x-axis and y-axis (x, y) in px :param ignore_alpha: If ``True`` returns only the three main channels :return: Color """ assert_vector(pos, 2) color = self._surface.get_at(pos) if ignore_alpha: return color[0], color[1], color[2] return color
def mouse_click(x: NumberType, y: NumberType, inlist: bool = True, evtype: int = pygame.MOUSEBUTTONUP, rel: Tuple2IntType = (0, 0), button: int = 3, testmode: bool = True, update_mouse: bool = False) -> EventListType: """ Generate a mouse click event. :param x: X coordinate in px :param y: Y coordinate in px :param inlist: Return event in a list :param evtype: event type, it can be MOUSEBUTTONUP or MOUSEBUTTONDOWN :param rel: Rel position (relative movement) :param button: Which button presses, ``1`` to ``3`` are the main buttons; ``4`` and ``5`` is the wheel :param testmode: Event is in test mode :param update_mouse: If ``True`` updates the mouse position :return: Event """ assert isinstance(button, int) and button > 0 assert_vector(rel, 2, int) event_obj = pygame.event.Event(evtype, { 'button': button, 'pos': (x, y), 'rel': rel, 'test': testmode }) if update_mouse: print('set mouse position', (x, y)) pygame.mouse.set_pos((x, y)) if inlist: event_obj = [event_obj] return event_obj
def __init__(self, margin_left: NumberType, margin_right: NumberType, margin_top: NumberType, margin_bottom: NumberType, arrow_size: Tuple2IntType = (10, 15), arrow_vertical_offset: NumberType = 0, blink_ms: NumberType = 0) -> None: super(ArrowSelection, self).__init__(margin_left=margin_left, margin_right=margin_right, margin_top=margin_top, margin_bottom=margin_bottom) assert_vector(arrow_size, 2, int) assert isinstance(arrow_vertical_offset, NumberInstance) assert isinstance(blink_ms, int) assert arrow_size[0] > 0 and arrow_size[ 1] > 0, 'arrow size must be greater than zero' assert blink_ms >= 0, 'blinking milliseconds must be greater than or equal to zero' self._arrow_vertical_offset = int(arrow_vertical_offset) self._arrow_size = (arrow_size[0], arrow_size[1]) self._blink_ms = blink_ms self._blink_time = 0 self._blink_status = True self._last_widget = None
def set_drawing_offset(self, offset: Vector2NumberType) -> 'BaseImage': """ Set the image drawing offset. :param offset: Drawing offset tuple on x-axis and y-axis (x, y) in px :return: Self reference """ assert_vector(offset, 2) self._drawing_offset = (int(offset[0]), int(offset[1])) return self
def set_at(self, pos: Tuple2NumberType, color: ColorInputType) -> 'BaseImage': """ Set the color of pixel on x-axis and y-axis (x, y). :param pos: Position on x-axis and y-axis (x, y) in px :param color: Color :return: Self reference """ assert_vector(pos, 2) self._surface.set_at(pos, assert_color(color)) return self
def set_at( self, pos: Tuple2NumberType, color: Union['pygame.Color', str, List[int], ColorType]) -> 'BaseImage': """ Set the color of the *(x, y)* pixel. :param pos: Position in *(x, y)* :param color: Color :return: Self reference """ assert_vector(pos, 2) self._surface.set_at(pos, color) return self
def add_line(self, pos1: Tuple2NumberType, pos2: Tuple2NumberType, color: ColorType, width: int = 1, prev: bool = True) -> str: """ Adds a line. :param pos1: Position 1 *(x1, y1)* :param pos2: Position 2 *(x2, y2)* :param color: Line color :param width: Line width in px :param prev: If ``True`` draw previous the object, else draws post :return: ID of the decoration """ assert_vector(pos1, 2) assert_vector(pos2, 2) assert_color(color) assert isinstance(width, int) and width >= 1 return self._add_decor(DECORATION_LINE, prev, ((tuple(pos1), tuple(pos2)), color, width))
def touch_click(x: NumberType, y: NumberType, inlist: bool = True, evtype: int = FINGERUP, rel: Tuple2IntType = (0, 0), normalize: bool = True, menu: Union['pygame_menu.Menu', None] = None, testmode: bool = True) -> EventListType: """ Generate a mouse click event. :param x: X coordinate :param y: Y coordinate :param inlist: Return event in a list :param evtype: Event type, it can be FINGERUP, FINGERDOWN or FINGERMOTION :param rel: Rel position (relative movement) :param normalize: Normalize event position :param menu: Menu reference :param testmode: Event is in test mode :return: Event """ assert isinstance(x, NumberInstance) assert isinstance(y, NumberInstance) assert_vector(rel, 2, int) if normalize: assert menu is not None, \ 'menu reference must be provided if normalize is used (related to touch events)' display_size = menu.get_window_size() x /= display_size[0] y /= display_size[1] event_obj = pygame.event.Event(evtype, { 'x': x, 'y': y, 'rel': rel, 'test': testmode }) if inlist: event_obj = [event_obj] return event_obj
def draw( self, surface: 'pygame.Surface', area: Optional['pygame.Rect'] = None, position: Tuple2IntType = (0, 0) ) -> 'BaseImage': """ Draw the image in a given surface. :param surface: Pygame surface object :param area: Area to draw; if ``None`` the image will be drawn on entire surface :param position: Position to draw on x-axis and y-axis (x, y) in px :return: Self reference """ assert isinstance(surface, pygame.Surface) assert isinstance(area, (pygame.Rect, type(None))) assert_vector(position, 2, int) if area is None: area = surface.get_rect() # Compute offset based on drawing offset + drawing position px, py = self._get_position_delta() if self._drawing_mode != IMAGE_MODE_SIMPLE: px = 0 py = 0 offx = self._drawing_offset[0] - px offy = self._drawing_offset[1] - py if self._drawing_mode == IMAGE_MODE_FILL: # Check if exists the transformed surface if area.width == self._last_transform[0] and area.height == self._last_transform[1] and \ self._last_transform[2] is not None: surf = self._last_transform[2] else: # Transform scale if self.smooth_scaling and self._surface.get_bitsize() > 8: surf = pygame.transform.smoothscale(self._surface, (area.width, area.height)) else: surf = pygame.transform.scale(self._surface, (area.width, area.height)) self._last_transform = (area.width, area.height, surf) surface.blit( surf, ( offx + position[0], offy + position[1] )) elif self._drawing_mode == IMAGE_MODE_REPEAT_X: w = self._surface.get_width() times = int(math.ceil(float(area.width) / w)) assert times > 0, \ 'invalid size, width must be greater than zero' for x in range(times): surface.blit( self._surface, ( x * w + offx + position[0], offy + position[1] ), area ) elif self._drawing_mode == IMAGE_MODE_REPEAT_Y: h = self._surface.get_height() times = int(math.ceil(float(area.height) / h)) assert times > 0, \ 'invalid size, height must be greater than zero' for y in range(times): surface.blit( self._surface, ( 0 + offx + position[0], y * h + offy + position[1] ), area ) elif self._drawing_mode == IMAGE_MODE_REPEAT_XY: w, h = self._surface.get_size() timesx = int(math.ceil(float(area.width) / w)) timesy = int(math.ceil(float(area.height) / h)) assert timesx > 0 and timesy > 0, \ 'invalid size, width and height must be greater than zero' for x in range(timesx): for y in range(timesy): surface.blit( self._surface, ( x * w + offx + position[0], y * h + offy + position[1] ), area ) elif self._drawing_mode == IMAGE_MODE_CENTER: sw, hw = area.width, area.height # Window w, h = self._surface.get_size() # Image surface.blit( self._surface, ( int(float(sw - w) / 2 + offx + position[0]), int(float(hw - h) / 2 + offy + position[1]) ), area ) elif self._drawing_mode == IMAGE_MODE_SIMPLE: surface.blit( self._surface, ( offx + position[0], offy + position[1] ), area ) return self
def __init__(self, title: Any, items: Union[List[Tuple[Any, ...]], List[str]], dropselect_id: str = '', default: Optional[Union[int, List[int]]] = None, max_selected: int = 0, onchange: CallbackType = None, onreturn: CallbackType = None, onselect: CallbackType = None, open_middle: bool = False, placeholder: str = 'Select an option', placeholder_add_to_selection_box: bool = True, placeholder_selected: str = '{0} selected', selection_box_arrow_color: ColorInputType = (150, 150, 150), selection_box_arrow_margin: Tuple3IntType = (5, 5, 0), selection_box_bgcolor: ColorInputType = (255, 255, 255), selection_box_border_color: ColorInputType = (150, 150, 150), selection_box_border_width: int = 1, selection_box_height: int = 3, selection_box_inflate: Tuple2IntType = (0, 0), selection_box_margin: Tuple2NumberType = (25, 0), selection_box_text_margin: int = 5, selection_box_width: int = 0, selection_infinite: bool = False, selection_option_active_bgcolor: ColorInputType = (188, 227, 244), selection_option_active_font_color: ColorInputType = (0, 0, 0), selection_option_border_color: ColorInputType = (220, 220, 220), selection_option_border_width: int = 1, selection_option_cursor: CursorInputType = None, selection_option_font: Optional[FontType] = None, selection_option_font_color: ColorInputType = (0, 0, 0), selection_option_font_size: Optional[int] = None, selection_option_padding: PaddingType = 5, selection_option_selected_bgcolor: ColorInputType = (142, 247, 141), selection_option_selected_box: bool = True, selection_option_selected_box_border: int = 1, selection_option_selected_box_color: ColorInputType = (150, 150, 150), selection_option_selected_box_height: float = 0.5, selection_option_selected_box_margin: Tuple3IntType = (0, 5, 0), selection_option_selected_font_color: ColorInputType = (0, 0, 0), *args, **kwargs) -> None: super(DropSelectMultiple, self).__init__( dropselect_id=dropselect_id, items=items, onchange=onchange, onreturn=onreturn, onselect=onselect, open_middle=open_middle, placeholder=placeholder, placeholder_add_to_selection_box=placeholder_add_to_selection_box, selection_box_arrow_color=selection_box_arrow_color, selection_box_arrow_margin=selection_box_arrow_margin, selection_box_bgcolor=selection_box_bgcolor, selection_box_border_color=selection_box_border_color, selection_box_border_width=selection_box_border_width, selection_box_height=selection_box_height, selection_box_inflate=selection_box_inflate, selection_box_margin=selection_box_margin, selection_box_text_margin=selection_box_text_margin, selection_box_width=selection_box_width, selection_infinite=selection_infinite, selection_option_border_color=selection_option_border_color, selection_option_border_width=selection_option_border_width, selection_option_cursor=selection_option_cursor, selection_option_font=selection_option_font, selection_option_font_color=selection_option_font_color, selection_option_font_size=selection_option_font_size, selection_option_padding=selection_option_padding, selection_option_selected_bgcolor=selection_option_selected_bgcolor, selection_option_selected_font_color= selection_option_selected_font_color, title=title, **kwargs) # Asserts assert isinstance(placeholder_selected, str) assert isinstance(selection_option_selected_box, bool) assert isinstance(selection_option_selected_box_border, int) and \ selection_option_selected_box_border > 0 assert_vector(selection_option_selected_box_margin, 3, int) assert isinstance(selection_option_selected_box_height, (int, float)) assert 0 < selection_option_selected_box_height <= 1, 'height factor must be between 0 and 1' assert isinstance(max_selected, int) and max_selected >= 0 # Configure parent self._args = args or [] self._close_on_apply = False self._max_selected = max_selected self._selection_option_left_space = True self._selection_option_left_space_height_factor = selection_option_selected_box_height self._selection_option_left_space_margin = selection_option_selected_box_margin # Set style self._placeholder_selected = placeholder_selected self._selection_option_active_bgcolor = assert_color( selection_option_active_bgcolor) self._selection_option_active_font_color = assert_color( selection_option_active_font_color) self._selection_option_selected_box = selection_option_selected_box self._selection_option_selected_box_color = assert_color( selection_option_selected_box_color) self._selection_option_selected_box_width = selection_option_selected_box_border self.set_default_value(default)
def __init__( self, title: Any, progressbar_id: str = '', default: NumberType = 0, width: int = 150, onselect: CallbackType = None, box_background_color: ColorInputType = (255, 255, 255), box_border_color: ColorInputType = (0, 0, 0), box_border_width: int = 1, box_margin: Tuple2IntType = (25, 0), box_progress_color: ColorInputType = (0, 255, 0), box_progress_padding: PaddingType = (1, 1), progress_text_align: str = ALIGN_CENTER, progress_text_enabled: bool = True, progress_text_font: Optional[FontType] = None, progress_text_font_color: ColorInputType = (0, 0, 0), progress_text_font_hfactor: float = 0.8, progress_text_format: ProgressBarTextFormatType = lambda x: str(round(x, 1)), progress_text_margin: Tuple2IntType = (0, 0), progress_text_placeholder: str = '{0} %', *args, **kwargs ) -> None: super(ProgressBar, self).__init__( args=args, kwargs=kwargs, onselect=onselect, title=title, widget_id=progressbar_id ) # Check the value assert isinstance(default, NumberInstance) assert 0 <= default <= 100, 'default value must range from 0 to 100' # Check fonts if progress_text_font is not None: assert_font(progress_text_font) assert isinstance(progress_text_font_hfactor, NumberInstance) assert progress_text_font_hfactor > 0, \ 'progress text font height factor must be greater than zero' # Check colors box_background_color = assert_color(box_background_color) box_border_color = assert_color(box_border_color) box_progress_color = assert_color(box_progress_color) progress_text_font_color = assert_color(progress_text_font_color) # Check dimensions and sizes assert isinstance(box_border_width, int) assert box_border_width >= 0, \ 'box border width must be equal or greater than zero' assert_vector(box_margin, 2, int) assert_vector(progress_text_margin, 2, int) assert isinstance(width, int) assert width > 0, 'width must be greater than zero' box_progress_padding = parse_padding(box_progress_padding) self._box_progress_padding = box_progress_padding # Check progress text assert isinstance(progress_text_enabled, bool) assert is_callable(progress_text_format) assert isinstance(progress_text_format(0), str) assert isinstance(progress_text_placeholder, str) assert_alignment(progress_text_align) # Store properties self._default_value = default self._box_background_color = box_background_color self._box_border_color = box_border_color self._box_border_width = box_border_width self._box_margin = box_margin self._box_progress_color = box_progress_color self._progress = default self._progress_text_align = progress_text_align self._progress_text_enabled = progress_text_enabled self._progress_text_font = progress_text_font self._progress_text_font_color = progress_text_font_color self._progress_text_font_height = 0 self._progress_text_font_height_factor = progress_text_font_hfactor self._progress_text_format = progress_text_format self._progress_text_margin = progress_text_margin self._progress_text_placeholder = progress_text_placeholder self._width = width
def pack( self, widget: Union['Widget', List['Widget'], Tuple['Widget', ...]], alignment: str = _locals.ALIGN_LEFT, vertical_position: str = _locals.POSITION_NORTH, margin: Vector2NumberType = (0, 0) ) -> Union['Widget', List['Widget'], Tuple['Widget', ...], Any]: """ Packs widget in the frame line. To pack a widget it has to be already appended to Menu, and the Menu must be the same as the frame. Packing is added to the same line, for example if three LEFT widgets are added: .. code-block:: python <frame horizontal> frame.pack(W1, alignment=ALIGN_LEFT, vertical_position=POSITION_NORTH) frame.pack(W2, alignment=ALIGN_LEFT, vertical_position=POSITION_CENTER) frame.pack(W3, alignment=ALIGN_LEFT, vertical_position=POSITION_SOUTH) ---------------- |W1 | | W2 | | W3 | ---------------- Another example: .. code-block:: python <frame horizontal> frame.pack(W1, alignment=ALIGN_LEFT) frame.pack(W2, alignment=ALIGN_CENTER) frame.pack(W3, alignment=ALIGN_RIGHT) ---------------- |W1 W2 W3| ---------------- .. code-block:: python <frame vertical> frame.pack(W1, alignment=ALIGN_LEFT) frame.pack(W2, alignment=ALIGN_CENTER) frame.pack(W3, alignment=ALIGN_RIGHT) -------- |W1 | | W2 | | W3| -------- .. note:: Frame does not consider previous widget margin. For such purpose, use ``margin`` pack parameter. .. note:: It is recommended to force menu rendering after packing all widgets. .. note:: Packing applies a virtual translation to the widget, previous translation is not modified. .. note:: Widget floating is also considered within frames. If a widget is floating, it does not add any size to the respective positioning. :param widget: Widget to be packed :param alignment: Widget alignment :param vertical_position: Vertical position of the widget within frame. See :py:mod:`pygame_menu.locals` :param margin: (left, top) margin of added widget in px. It overrides the previous widget margin :return: Added widget references """ assert self._menu is not None, \ 'frame menu must be set before packing widgets' if isinstance(widget, (tuple, list)): for w in widget: self.pack(widget=w, alignment=alignment, vertical_position=vertical_position) return widget assert isinstance(widget, Widget) if isinstance(widget, Frame): assert widget.get_menu() is not None, \ '{0} menu cannot be None'.format(widget.get_class_id()) assert widget.get_id() not in self._widgets.keys(), \ '{0} already exists in {1}'.format(widget.get_class_id(), self.get_class_id()) assert widget.get_menu() == self._menu or widget.get_menu() is None, \ 'widget menu to be added to frame must be in same menu as frame, or it can have any Menu instance' assert widget.get_frame() is None, \ '{0} is already packed in {1}'.format(widget.get_class_id(), widget.get_frame().get_class_id()) assert_alignment(alignment) assert vertical_position in (_locals.POSITION_NORTH, _locals.POSITION_CENTER, _locals.POSITION_SOUTH), \ 'vertical position must be NORTH, CENTER, or SOUTH' assert_vector(margin, 2) assert widget.configured, 'widget must be configured before packing' if widget.get_margin() != (0, 0) and self._pack_margin_warning: msg = '{0} margin should be (0, 0) if packed, but received {1}; {2}.pack() does not consider ' \ 'previous widget margin. Set frame._pack_margin_warning=False to hide this warning' \ ''.format(widget.get_class_id(), widget.get_margin(), self.get_class_id()) warnings.warn(msg) if isinstance(widget, Frame): widget.update_indices() widget.set_frame(self) widget.set_margin(*margin) if self._frame_scrollarea is not None: widget.set_scrollarea(self._frame_scrollarea) self._sort_menu_scrollable_frames() else: widget.set_scrollarea(self._scrollarea) self._widgets[widget.get_id()] = widget self._widgets_props[widget.get_id()] = (alignment, vertical_position) # Sort widgets to keep selection order menu_widgets = self._menu._widgets if widget.get_menu() is not None and widget in menu_widgets: self._menu._validate_frame_widgetmove = False widgets_list = list(self._widgets.values()) # Move frame to last if len(self._widgets) > 1: wlast = widgets_list[-2] # -1 is the last added for i in range(2, len(self._widgets)): if wlast.get_menu() is None and len(self._widgets) > 2: wlast = widgets_list[-(i + 1)] else: break # Check for last if wlast is frame while True: if not (isinstance(wlast, Frame) and wlast.get_indices() != (-1, -1)) or wlast.get_menu() is None: break wlast = menu_widgets[wlast.last_index] if wlast.get_menu() == self._menu: self._menu.move_widget_index(self, wlast, render=False) # Swap self._menu.move_widget_index(widget, self, render=False) if isinstance(widget, Frame): reverse = menu_widgets.index(widget) == len(menu_widgets) - 1 widgs = widget.get_widgets(unpack_subframes_include_frame=True, reverse=reverse) for w in widgs: if w.get_menu() is None: continue self._menu.move_widget_index(w, self, render=False) if len(widgs) >= 1: swap_target = widgs[-1] if not reverse: swap_target = widgs[0] menu_widgets.remove(widget) menu_widgets.insert(menu_widgets.index(swap_target), widget) # Move widget to first menu_widgets.remove(self) for k in range(len(widgets_list)): if widgets_list[k].get_menu() == self._menu: menu_widgets.insert(menu_widgets.index(widgets_list[k]), self) break self._menu._validate_frame_widgetmove = True # Update control widget if self._control_widget is None: self._control_widget = widget self._control_widget_last_pos = self._control_widget.get_position( ) if isinstance(widget, Frame): self._has_frames = True # Update menu selected widget self._menu.move_widget_index(None, update_selected_index=True) # Render is mandatory as it modifies row/column layout try: self.update_position() self._menu._render() except _FrameSizeException: self.unpack(widget) raise # Request scroll if widget is selected if widget.is_selected(): widget.scroll_to_widget() widget.scroll_to_widget() return widget
def _get(params: Dict[str, Any], key: str, allowed_types: Optional[Union[Type, str, List[Type], Tuple[Type, ...]]] = None, default: Any = None) -> Any: """ Return a value from a dictionary. Custom types (str) - alignment – pygame-menu alignment (locals) - callable – Is callable type, same as ``"function"`` - color – Check color - color_image – Color or :py:class:`pygame_menu.baseimage.BaseImage` - color_image_none – Color, :py:class:`pygame_menu.baseimage.BaseImage`, or None - color_none – Color or None - cursor – Cursor object (pygame) - font – Font type - image – Value must be ``BaseImage`` - none – None only - position – pygame-menu position (locals) - position_vector – pygame-menu position (str or vector) - tuple2 – Only valid numeric tuples ``(x, y)`` or ``[x, y]`` - tuple2int – Only valid integer tuples ``(x, y)`` or ``[x, y]`` - tuple3 – Only valid numeric tuples ``(x, y, z)`` or ``[x, y, z]`` - tuple3int – Only valid integer tuples ``(x, y, z)`` or ``[x, y, z]`` - type – Type-class (bool, str, etc...) :param params: Parameters dictionary :param key: Key to look for :param allowed_types: List of allowed types :param default: Default value to return :return: The value associated to the key """ value = params.pop(key, default) if allowed_types is not None: other_types = [] # Contain other types to check from if not isinstance(allowed_types, VectorInstance): allowed_types = (allowed_types, ) for val_type in allowed_types: if val_type == 'alignment': assert_alignment(value) elif val_type == callable or val_type == 'function' or val_type == 'callable': assert is_callable(value), \ 'value must be callable type' elif val_type == 'color': value = assert_color(value) elif val_type == 'color_image': if not isinstance(value, BaseImage): value = assert_color(value) elif val_type == 'color_image_none': if not (value is None or isinstance(value, BaseImage)): value = assert_color(value) elif val_type == 'color_none': if value is not None: value = assert_color(value) elif val_type == 'cursor': assert_cursor(value) elif val_type == 'font': assert_font(value) elif val_type == 'image': assert isinstance(value, BaseImage), \ 'value must be BaseImage type' elif val_type == 'none': assert value is None elif val_type == 'position': assert_position(value) elif val_type == 'position_vector': assert_position_vector(value) elif val_type == 'type': assert isinstance(value, type), \ 'value is not type-class' elif val_type == 'tuple2': assert_vector(value, 2) elif val_type == 'tuple2int': assert_vector(value, 2, int) elif val_type == 'tuple3': assert_vector(value, 3) elif val_type == 'tuple3int': assert_vector(value, 3, int) else: # Unknown type assert isinstance(val_type, type), \ 'allowed type "{0}" is not a type-class'.format(val_type) other_types.append(val_type) # Check other types if len(other_types) > 0: others = tuple(other_types) assert isinstance(value, others), \ 'Theme.{} type shall be in {} types (got {})'.format(key, others, type(value)) return value
def __init__(self, title: Any, toggleswitch_id: str = '', default_state: int = 0, infinite: bool = False, onchange: CallbackType = None, onselect: CallbackType = None, slider_color: ColorInputType = (255, 255, 255), slider_height_factor: NumberType = 1, slider_thickness: int = 25, slider_vmargin: NumberType = 0, state_color: Tuple[ColorInputType, ...] = ((178, 178, 178), (117, 185, 54)), state_text: Tuple[str, ...] = ('Off', 'On'), state_text_font: Optional[FontType] = None, state_text_font_color: Tuple[ColorInputType, ...] = ((255, 255, 255), (255, 255, 255)), state_text_font_size: Optional[int] = None, state_text_position: Tuple2NumberType = (0.5, 0.5), state_values: Tuple[Any, ...] = (False, True), state_width: Union[Tuple[int, ...], int] = 150, switch_border_color: ColorInputType = (40, 40, 40), switch_border_width: int = 1, switch_height: NumberType = 1.25, switch_margin: Tuple2NumberType = (25, 0), *args, **kwargs) -> None: super(ToggleSwitch, self).__init__(args=args, kwargs=kwargs, onchange=onchange, onselect=onselect, title=title, widget_id=toggleswitch_id) # Asserts assert isinstance(default_state, int) assert isinstance(state_values, tuple) assert isinstance(infinite, bool) self._total_states = len(state_values) assert 2 <= self._total_states, 'the minimum number of states is 2' assert 0 <= default_state < self._total_states, 'invalid default state value' if state_text_font is not None: assert_font(state_text_font) assert isinstance(state_text_font_size, (int, type(None))) if state_text_font_size is not None: assert state_text_font_size > 0, 'state text font size must be equal or greater than zero' assert_vector(state_text_position, 2) switch_border_color = assert_color(switch_border_color) assert isinstance(switch_border_width, int) and switch_border_width >= 0, \ 'border width must be equal or greater than zero' slider_color = assert_color(slider_color) assert slider_height_factor > 0, 'slider height factor cannot be negative' assert slider_thickness >= 0, 'slider thickness cannot be negative' assert isinstance(slider_vmargin, NumberInstance) assert_vector(switch_margin, 2) assert isinstance(switch_height, NumberInstance) and switch_height > 0, \ 'switch height factor cannot be zero or negative' assert isinstance(state_color, tuple) and len(state_color) == self._total_states new_state_color = [] for c in state_color: new_state_color.append(assert_color(c)) state_color = tuple(new_state_color) assert isinstance(state_text, tuple) and len(state_text) == self._total_states for c in state_text: assert isinstance(c, str), 'all states text must be string-type' assert isinstance( state_text_font_color, tuple) and len(state_text_font_color) == self._total_states new_state_text_font_color = [] for c in state_text_font_color: new_state_text_font_color.append(assert_color(c)) state_text_font_color = tuple(new_state_text_font_color) self._switch_width = 0 if isinstance(state_width, NumberInstance): state_width = [state_width] assert_vector(state_width, self._total_states - 1, int) for i in range(len(state_width)): assert isinstance(state_width[i], int), 'each state width must be an integer' assert state_width[ i] > 0, 'each state width must be greater than zero' self._switch_width += state_width[i] # Store properties self._switch_border_color = switch_border_color self._switch_border_width = switch_border_width self._infinite = infinite self._slider_color = slider_color self._slider_height_factor = slider_height_factor self._slider_thickness = slider_thickness self._slider_vmargin = slider_vmargin self._state = default_state self._state_color = state_color self._state_text = state_text self._state_text_font = state_text_font self._state_text_font_color = state_text_font_color self._state_text_font_size = state_text_font_size self._state_text_position = state_text_position self._state_values = state_values self._state_width = state_width self._switch_height_factor = float(switch_height) self._switch_margin = switch_margin # Compute state width accum self._state_width_accum = [0] accum = 0 for w in self._state_width: accum += w self._state_width_accum.append(accum - self._slider_thickness - 2 * self._switch_border_width) # Inner properties self._slider_height = 0 self._slider_pos = (0, 0) # to add to (rect.x, rect.y) self._state_font = None self._switch_font_rendered = [] # Stores font render for each state self._switch_height = 0 self._switch_pos = (0, 0) # horizontal pos, and delta to title
def _get(params: Dict[str, Any], key: str, allowed_types: Optional[Union[Type, str, List[Type], Tuple[Type, ...]]] = None, default: Any = None) -> Any: """ Return a value from a dictionary. Custom types (str) - alignment pygame-menu alignment (locals) - callable Is callable type, same as ``'function'`` - color Check color - color_image Color or :py:class:`pygame_menu.baseimage.BaseImage` - color_image_none Color, :py:class:`pygame_menu.baseimage.BaseImage`, or None - color_none Color or None - image Value must be ``BaseImage`` - none None only - position pygame-menu position (locals)} - type Type-class (bool, str, etc...) - tuple2 Only valid numeric tuples ``(x,y)`` or ``[x,y]`` - tuple3 Only valid numeric tuples ``(x,y,z)`` or ``[x,y,z]`` :param params: Parameters dictionary :param key: Key to look for :param allowed_types: List of allowed types :param default: Default value to return :return: The value associated to the key """ value = params.pop(key, default) if allowed_types is not None: other_types = [] # Contain other types to check from if not isinstance(allowed_types, (tuple, list)): allowed_types = (allowed_types,) for valtype in allowed_types: if valtype == 'alignment': _utils.assert_alignment(value) elif valtype == callable or valtype == 'function' or valtype == 'callable': assert _utils.is_callable(value), 'value must be callable type' elif valtype == 'color': _utils.assert_color(value) elif valtype == 'color_image': if isinstance(value, BaseImage): return value _utils.assert_color(value) elif valtype == 'color_image_none': if value is None or isinstance(value, BaseImage): return value _utils.assert_color(value) elif valtype == 'color_none': if value is None: return value _utils.assert_color(value) elif valtype == 'image': assert isinstance(value, BaseImage), 'value must be BaseImage type' elif valtype == 'none': assert value is None elif valtype == 'position': _utils.assert_position(value) elif valtype == 'type': assert isinstance(value, type), 'value is not type-class' elif valtype == 'tuple2': _utils.assert_vector(value, 2) elif valtype == 'tuple3': _utils.assert_vector(value, 3) else: # Unknown type assert isinstance(valtype, type), \ 'allowed type "{0}" is not a type-class'.format(valtype) other_types.append(valtype) # Check other types if len(other_types) > 0: others = tuple(other_types) msg = 'Theme.{} type shall be in {} types (got {})'.format(key, others, type(value)) assert isinstance(value, others), msg return value
def update_cell_style( self, column: Union[int, Vector2IntType], row: Union[int, Vector2IntType], align: Optional[str] = None, background_color: Optional[ColorInputType] = None, border_color: Optional[ColorInputType] = None, border_position: Optional[WidgetBorderPositionType] = None, border_width: Optional[int] = None, font: Optional[FontType] = None, font_color: Optional[ColorInputType] = None, font_size: Optional[int] = None, padding: Optional[PaddingType] = None, vertical_position: Optional[str] = None ) -> Union['Widget', List['Widget']]: """ Update cell style. If a parameter is ``None`` the default cell property will be used. :param column: Cell column position (counting from 1). If -1 update all column from the given row. Also, a 2-item list/tuple is accepted (from, to), ``to=-1`` is also accepted (last) :param row: Cell row position (counting from 1). If ``-1`` update all rows from the given column. Also, a 2-item list/tuple is accepted (from, to), ``to=-1`` is also accepted (last) :param align: Horizontal align of each cell. See :py:mod:`pygame_menu.locals` :param background_color: Background color :param border_color: Border color of each cell :param border_position: Border position of each cell. Valid only: north, south, east, and west. See :py:mod:`pygame_menu.locals` :param border_width: Border width in px of each cell :param font: Font name or path :param font_color: Font color :param font_size: Font size :param padding: Cell padding according to CSS rules. General shape: (top, right, bottom, left) :param vertical_position: Vertical position of each cell. Only valid: north, center, and south. See :py:mod:`pygame_menu.locals` :return: Cell widget """ if row == -1 or isinstance(row, VectorInstance): max_rows = len(self._rows) if row == -1: row = [] for i in range(max_rows): row.append(i + 1) else: assert_vector(row, 2, int) row_k = list(row) if row_k[1] == -1: row_k[1] = len(self._rows) assert 1 <= row_k[0] <= row_k[1] <= max_rows, \ f'(from, to) of rows vector must be increasing and between 1-{max_rows}' row = [row_k[0]] for i in range(row_k[1] - row_k[0]): row.append(row_k[0] + (i + 1)) if isinstance(column, VectorInstance) and column != [1, -1]: assert self.is_rectangular(), \ f'only rectangular tables (same number of columns for each row) ' \ f'accept a variable column different than -1 or [1, -1], but ' \ f'received "{column}"' updated_wid = [] for i in row: w = self.update_cell_style(column=column, row=i, align=align, background_color=background_color, border_color=border_color, border_position=border_position, border_width=border_width, font=font, font_color=font_color, font_size=font_size, padding=padding, vertical_position=vertical_position) if not isinstance(w, list): w = [w] for k in w: updated_wid.append(k) return updated_wid if column == -1 or isinstance(column, VectorInstance): assert isinstance(row, int) and 1 <= row <= len(self._rows), \ f'row index ({row}) cannot exceed the number of rows ({len(self._rows)})' max_columns = self._rows[row - 1].get_total_packed() if column == -1: column = [] for i in range(max_columns): column.append(i + 1) else: assert_vector(column, 2, int) column_k = list(column) if column_k[1] == -1: column_k[1] = max_columns assert 1 <= column_k[0] <= column_k[1] <= max_columns, \ f'(from, to) of column vector must be increasing and between 1-{max_columns} for row {row}' column = [column_k[0]] for i in range(column_k[1] - column_k[0]): column.append(column_k[0] + (i + 1)) updated_wid = [] for i in column: w = self.update_cell_style(column=i, row=row, align=align, background_color=background_color, border_color=border_color, border_position=border_position, border_width=border_width, font=font, font_color=font_color, font_size=font_size, padding=padding, vertical_position=vertical_position) if not isinstance(w, list): w = [w] for k in w: updated_wid.append(k) return updated_wid cell = self.get_cell(column, row) r = self._rows[row - 1] if align is None: align = cell.get_attribute('align') if background_color is None: background_color = cell.get_attribute('background_color') if border_color is None: border_color = cell.get_attribute('border_color') if border_position is None: border_position = cell.get_attribute('border_position') if border_width is None: border_width = cell.get_attribute('border_width') if padding is None: padding = cell.get_attribute('padding') if vertical_position is None: vertical_position = cell.get_attribute('vertical_position') self._check_cell_style(align=align, background_color=background_color, border_color=border_color, border_position=border_position, border_width=border_width, padding=padding, vertical_position=vertical_position) if background_color is not None: background_color = assert_color(background_color) if border_color is not None: border_color = assert_color(border_color) padding = parse_padding(padding) # Update background color if background_color != r._background_color: cell.set_background_color(background_color) else: cell.set_background_color(None) # Update font if font_color is None: font_color = cell._font_color assert_color(font_color) if font is None: font = cell._font_name assert_font(font) if font_size is None: font_size = cell._font_size try: cell.update_font({'color': font_color, 'name': font}) except AssertionError: pass try: if isinstance(font_size, int) and font_size > 0: cell.update_font({'size': font_size}) except AssertionError: pass if isinstance(border_position, str): border_position = [border_position] # Update cell cell.set_attribute('align', align) cell.set_attribute('background_color', background_color) cell.set_attribute('border_color', border_color) cell.set_attribute('border_position', border_position) cell.set_attribute('border_width', border_width) cell.set_attribute('padding', padding) cell.set_attribute('vertical_position', vertical_position) self._update_row_sizing() self._render() self.force_menu_surface_update() return cell
def _filter_widget_attributes(self, kwargs: Dict) -> Dict[str, Any]: attributes = {} # align align = kwargs.pop('align', self._theme.widget_alignment) assert isinstance(align, str) attributes['align'] = align # background_color background_is_color = False background_color = kwargs.pop('background_color', self._theme.widget_background_color) if background_color is not None: if isinstance(background_color, pygame_menu.BaseImage): pass else: background_color = assert_color(background_color) background_is_color = True attributes['background_color'] = background_color # background_inflate background_inflate = kwargs.pop('background_inflate', self._theme.widget_background_inflate) if background_inflate == 0: background_inflate = (0, 0) assert_vector(background_inflate, 2, int) assert background_inflate[0] >= 0 and background_inflate[1] >= 0, \ 'both background inflate components must be equal or greater than zero' attributes['background_inflate'] = background_inflate # border_color border_color = kwargs.pop('border_color', self._theme.widget_border_color) if border_color is not None: border_color = assert_color(border_color) attributes['border_color'] = border_color # border_inflate border_inflate = kwargs.pop('border_inflate', self._theme.widget_border_inflate) if border_inflate == 0: border_inflate = (0, 0) assert_vector(border_inflate, 2, int) assert isinstance(border_inflate[0], int) and border_inflate[0] >= 0 assert isinstance(border_inflate[1], int) and border_inflate[1] >= 0 attributes['border_inflate'] = border_inflate # border_position border_position = kwargs.pop('border_position', self._theme.widget_border_position) assert_position_vector(border_position) attributes['border_position'] = border_position # border_width border_width = kwargs.pop('border_width', self._theme.widget_border_width) assert isinstance(border_width, int) and border_width >= 0 attributes['border_width'] = border_width # cursor cursor = kwargs.pop('cursor', self._theme.widget_cursor) assert_cursor(cursor) attributes['cursor'] = cursor # floating status float_ = kwargs.pop('float', False) assert isinstance(float_, bool) attributes['float'] = float_ float_origin_position = kwargs.pop('float_origin_position', False) assert isinstance(float_origin_position, bool) attributes['float_origin_position'] = float_origin_position # font_antialias attributes['font_antialias'] = self._theme.widget_font_antialias # font_background_color font_background_color = kwargs.pop( 'font_background_color', self._theme.widget_font_background_color) if font_background_color is None and \ self._theme.widget_font_background_color_from_menu and \ not background_is_color: if not isinstance(self._theme.background_color, pygame_menu.BaseImage): font_background_color = assert_color( self._theme.background_color) attributes['font_background_color'] = font_background_color # font_color font_color = kwargs.pop('font_color', self._theme.widget_font_color) attributes['font_color'] = assert_color(font_color) # font_name font_name = kwargs.pop('font_name', self._theme.widget_font) assert_font(font_name) attributes['font_name'] = font_name # font_shadow font_shadow = kwargs.pop('font_shadow', self._theme.widget_font_shadow) assert isinstance(font_shadow, bool) attributes['font_shadow'] = font_shadow # font_shadow_color font_shadow_color = kwargs.pop('font_shadow_color', self._theme.widget_font_shadow_color) attributes['font_shadow_color'] = assert_color(font_shadow_color) # font_shadow_offset font_shadow_offset = kwargs.pop('font_shadow_offset', self._theme.widget_font_shadow_offset) assert isinstance(font_shadow_offset, int) attributes['font_shadow_offset'] = font_shadow_offset # font_shadow_position font_shadow_position = kwargs.pop( 'font_shadow_position', self._theme.widget_font_shadow_position) assert isinstance(font_shadow_position, str) attributes['font_shadow_position'] = font_shadow_position # font_size font_size = kwargs.pop('font_size', self._theme.widget_font_size) assert isinstance(font_size, int) assert font_size > 0, 'font size must be greater than zero' attributes['font_size'] = font_size # margin margin = kwargs.pop('margin', self._theme.widget_margin) if margin == 0: margin = (0, 0) assert_vector(margin, 2) attributes['margin'] = margin # padding padding = kwargs.pop('padding', self._theme.widget_padding) assert isinstance(padding, PaddingInstance) attributes['padding'] = padding # readonly_color readonly_color = kwargs.pop('readonly_color', self._theme.readonly_color) attributes['readonly_color'] = assert_color(readonly_color) # readonly_selected_color readonly_selected_color = kwargs.pop( 'readonly_selected_color', self._theme.readonly_selected_color) attributes['readonly_selected_color'] = assert_color( readonly_selected_color) # selection_color selection_color = kwargs.pop('selection_color', self._theme.selection_color) attributes['selection_color'] = assert_color(selection_color) # selection_effect selection_effect = kwargs.pop('selection_effect', self._theme.widget_selection_effect) if selection_effect is None: selection_effect = pygame_menu.widgets.NoneSelection() else: selection_effect = selection_effect.copy() assert isinstance(selection_effect, pygame_menu.widgets.core.Selection) selection_effect.set_color(attributes['selection_color']) attributes['selection_effect'] = selection_effect # shadow attributes['shadow_aa'] = kwargs.pop('shadow_aa', self._theme.widget_shadow_aa) attributes['shadow_color'] = kwargs.pop( 'shadow_color', self._theme.widget_shadow_color) attributes['shadow_radius'] = kwargs.pop( 'shadow_radius', self._theme.widget_shadow_radius) attributes['shadow_type'] = kwargs.pop('shadow_type', self._theme.widget_shadow_type) attributes['shadow_width'] = kwargs.pop( 'shadow_width', self._theme.widget_shadow_width) # tab_size attributes['tab_size'] = kwargs.pop('tab_size', self._theme.widget_tab_size) return attributes
def __init__(self, title: Any, items: Union[List[Tuple[Any, ...]], List[str]], selector_id: str = '', default: int = 0, onchange: CallbackType = None, onreturn: CallbackType = None, onselect: CallbackType = None, style: SelectorStyleType = SELECTOR_STYLE_CLASSIC, style_fancy_arrow_color: ColorInputType = (160, 160, 160), style_fancy_arrow_margin: Tuple3IntType = (5, 5, 0), style_fancy_bgcolor: ColorInputType = (180, 180, 180), style_fancy_bordercolor: ColorInputType = (0, 0, 0), style_fancy_borderwidth: int = 1, style_fancy_box_inflate: Tuple2IntType = (0, 0), style_fancy_box_margin: Tuple2NumberType = (25, 0), *args, **kwargs) -> None: assert isinstance(items, list) assert isinstance(selector_id, str) assert isinstance(default, int) assert style in (SELECTOR_STYLE_CLASSIC, SELECTOR_STYLE_FANCY), \ 'invalid selector style' # Check items list check_selector_items(items) assert default >= 0, \ 'default position must be equal or greater than zero' assert default < len(items), \ 'default position should be lower than number of values' assert isinstance(selector_id, str), 'id must be a string' assert isinstance(default, int), 'default must be an integer' # Check fancy style style_fancy_arrow_color = assert_color(style_fancy_arrow_color) assert_vector(style_fancy_arrow_margin, 3, int) assert_vector(style_fancy_box_margin, 2) style_fancy_bgcolor = assert_color(style_fancy_bgcolor) style_fancy_bordercolor = assert_color(style_fancy_bordercolor) assert isinstance(style_fancy_borderwidth, int) and style_fancy_borderwidth >= 0 assert_vector(style_fancy_box_inflate, 2, int) assert style_fancy_box_inflate[0] >= 0 and style_fancy_box_inflate[1] >= 0, \ 'box inflate must be equal or greater than zero on both axis' super(Selector, self).__init__(onchange=onchange, onreturn=onreturn, onselect=onselect, title=title, widget_id=selector_id, args=args, kwargs=kwargs) self._index = 0 self._items = items.copy() self._sformat = '' self._style = style self._title_size = 0 # Store fancy style self._style_fancy_arrow_color = style_fancy_arrow_color self._style_fancy_arrow_margin = style_fancy_arrow_margin self._style_fancy_bgcolor = style_fancy_bgcolor self._style_fancy_bordercolor = style_fancy_bordercolor self._style_fancy_borderwidth = style_fancy_borderwidth self._style_fancy_box_inflate = style_fancy_box_inflate self._style_fancy_box_margin = (int(style_fancy_box_margin[0]), int(style_fancy_box_margin[1])) # Apply default item default %= len(self._items) for k in range(0, default): self._right() # Last configs self.set_sformat('{0}< {1} >') self.set_default_value(default)
def pack( self, widget: Union['Widget', List['Widget'], Tuple['Widget', ...]], alignment: str = _locals.ALIGN_LEFT, vertical_position: PackPositionTypes = _locals.POSITION_NORTH, margin: Vector2NumberType = (0, 0) ) -> Union['Widget', List['Widget'], Tuple['Widget', ...]]: """ Packs widget in the frame line. To pack a widget it has to be already appended to Menu, and the Menu must be the same as the frame. Packing is added to the same line, for example if three LEFT widgets are added: .. code-block:: python <frame horizontal> frame.pack(W1, alignment=ALIGN_LEFT, vertical_position=POSITION_NORTH) frame.pack(W2, alignment=ALIGN_LEFT, vertical_position=POSITION_CENTER) frame.pack(W3, alignment=ALIGN_LEFT, vertical_position=POSITION_SOUTH) ---------------- |W1 | | W2 | | W3 | ---------------- Another example: .. code-block:: python <frame horizontal> frame.pack(W1, alignment=ALIGN_LEFT) frame.pack(W2, alignment=ALIGN_CENTER) frame.pack(W3, alignment=ALIGN_RIGHT) ---------------- |W1 W2 W3| ---------------- .. code-block:: python <frame vertical> frame.pack(W1, alignment=ALIGN_LEFT) frame.pack(W2, alignment=ALIGN_CENTER) frame.pack(W3, alignment=ALIGN_RIGHT) -------- |W1 | | W2 | | W3| -------- .. note:: It is recommended to force menu rendering after packing all widgets. :param widget: Widget to be packed :param alignment: Widget alignment :param vertical_position: Vertical position of the widget :param margin: *(left, top)* margin of added widget in px. It overrides the previous widget margin :return: Added widget reference """ menu = self.get_menu() assert menu is not None, \ 'menu must be set before packing widgets' if isinstance(widget, (list, tuple)): for w in widget: self.pack(widget=w, alignment=alignment, vertical_position=vertical_position) return widget assert isinstance(widget, Widget) assert widget.get_id() not in self._widgets.keys(), \ 'widget already in frame' assert widget.get_menu() == menu, \ 'widget menu to be added to frame must be in same menu as frame' assert widget.get_frame() is None, \ 'widget already is in another frame' assert_alignment(alignment) assert vertical_position in (_locals.POSITION_NORTH, _locals.POSITION_CENTER, _locals.POSITION_SOUTH), \ 'vertical position must be NORTH, CENTER, or SOUTH' assert widget._translate[0] == 0 and widget._translate[1] == 0, \ 'widget cannot have a previous translation if appended. Frame overrides translation' assert_vector(margin, 2) widget.set_frame(self) widget.set_float() widget.set_margin(*margin) self._widgets[widget.get_id()] = (widget, alignment, vertical_position) # Notify menu and sort widgets to keep selection order # noinspection PyProtectedMember menu_widgets = menu._widgets frame_index = menu_widgets.index(self) widgt_index = menu_widgets.index(widget) assert widgt_index > frame_index, 'widget cannot be appended before frame' menu_widgets.pop(widgt_index) menu_widgets.insert(frame_index, widget) if widget.is_selected(): widget.select(False) menu.select_widget(widget) if self._control_widget is None: self._control_widget = widget self._control_widget_last_pos = self._control_widget.get_position() # Render is mandatory as it modifies row/column layout try: menu.render() except _FrameSizeException: self.unpack(widget) raise self.update_indices() return widget