Ejemplo n.º 1
0
 def set_title(self, title: str) -> 'Label':
     super(Label, self).set_title(title)
     if self._title_generator is not None:
         warn(
             f'{self.get_class_id()} title generator is not None, thus, the new'
             f' title "{title}" will be overridden after next update')
     return self
Ejemplo n.º 2
0
    def _check_title_color(self, background_menu: bool) -> None:
        """
        Performs title color and prints a warning if the color is similar to the
        background.

        :return: None
        """
        if background_menu and self._menu is not None:
            c_back = self._menu.get_theme().background_color
        else:
            c_back = self._background_color
        if not isinstance(c_back, VectorInstance):  # If is color
            return
        tol = 5
        c_dif_1 = abs(c_back[0] - self._font_color[0])
        c_dif_2 = abs(c_back[1] - self._font_color[1])
        c_dif_3 = abs(c_back[2] - self._font_color[2])
        if c_dif_1 < tol and c_dif_2 < tol and c_dif_3 < tol:
            warn(
                'title font color {0} is {3} to the {1} background color {2}, '
                'consider editing your Theme'.format(
                    self._font_color,
                    'menu' if background_menu else 'title',
                    c_back,
                    'equal' if c_dif_1 == c_dif_2 == c_dif_3 == 0 else 'similar'
                )
            )
Ejemplo n.º 3
0
    def _add_decor(self, decortype: int, prev: bool, data: Any) -> str:
        """
        Adds a decoration.

        :param decortype: Decoration type
        :param prev: To prev or post
        :param data: Data of the decoration
        :return: ID of the decoration
        """
        decor_id = uuid4()

        if prev:
            assert self._prev_enabled, 'prev decorators are not enabled'
            self._decor[DECOR_TYPE_PREV].append((decortype, decor_id, data))
            self._decor_prev_id.append(decor_id)
        else:
            assert self._post_enabled, 'post decorators are not enabled'
            self._decor[DECOR_TYPE_POST].append((decortype, decor_id, data))

        # Force surface cache update
        if hasattr(self._obj, 'force_menu_surface_cache_update'):
            self._obj.force_menu_surface_cache_update()

        # Forces cache update
        self._cache_needs_update[DECOR_TYPE_PREV if prev else DECOR_TYPE_POST] = True

        # Check sizes
        if self._total_decor() >= 300 and not self.cache:
            warn('cache is recommended if the total number of decorations exceeds 300')

        # Set automatically as enabled
        self._decor_enabled[decor_id] = True

        return decor_id
Ejemplo n.º 4
0
 def set_title(self, title: str) -> 'Label':
     super(Label, self).set_title(title)
     if self._title_generator is not None:
         warn(
             '{0} title generator is not None, thus, the new title "{1}" will be '
             'overridden after next update'.format(self.get_class_id(),
                                                   title))
     return self
Ejemplo n.º 5
0
    def generic_widget(self,
                       widget: 'Widget',
                       configure_defaults: bool = False) -> 'Widget':
        """
        Add generic widget to the Menu.

        .. note::

            The widget should be fully configured by the user: font, padding, etc.

        .. note::

            This is applied only to the base Menu (not the currently displayed,
            stored in ``_current`` pointer); for such behaviour apply to
            :py:meth:`pygame_menu.menu.Menu.get_current` object.

        .. warning::

            Unintended behaviours may happen while using this method, use only with
            caution; specially while creating nested submenus with buttons.

        :param widget: Widget to be added
        :param configure_defaults: Apply defaults widget configuration (for example, theme)
        :return: The added widget object
        :rtype: :py:class:`pygame_menu.widgets.Widget`
        """
        assert isinstance(widget, Widget)
        if widget.get_menu() is not None:
            raise ValueError(
                'widget to be added is already appended to another Menu')

        # Raise warning if adding button with Menu
        if isinstance(widget, pygame_menu.widgets.Button) and widget.to_menu:
            warn('prefer adding submenus using add_button method instead, '
                 'unintended behaviours may occur')

        # Configure widget
        if configure_defaults:
            self.configure_defaults_widget(widget)
        self._append_widget(widget)

        return widget
Ejemplo n.º 6
0
    def __init__(self, **kwargs) -> None:

        # Menu general
        self.background_color = self._get(kwargs, 'background_color',
                                          'color_image', (220, 220, 220))
        self.focus_background_color = self._get(kwargs,
                                                'focus_background_color',
                                                'color', (0, 0, 0, 180))
        self.fps = self._get(kwargs, 'fps', NumberInstance, 30)
        self.readonly_color = self._get(kwargs, 'readonly_color', 'color',
                                        (120, 120, 120))
        self.readonly_selected_color = self._get(kwargs,
                                                 'readonly_selected_color',
                                                 'color', (190, 190, 190))
        self.selection_color = self._get(kwargs, 'selection_color', 'color',
                                         (255, 255, 255))
        self.surface_clear_color = self._get(kwargs, 'surface_clear_color',
                                             'color', (0, 0, 0))

        # Cursor/Text gathering
        self.cursor_color = self._get(kwargs, 'cursor_color', 'color',
                                      (0, 0, 0))
        self.cursor_selection_color = self._get(kwargs,
                                                'cursor_selection_color',
                                                'color', (30, 30, 30, 120))
        self.cursor_switch_ms = self._get(kwargs, 'cursor_switch_ms',
                                          NumberInstance, 750)

        # Menubar/Title
        self.title = self._get(kwargs, 'title', bool, True)
        self.title_background_color = self._get(kwargs,
                                                'title_background_color',
                                                'color', (70, 70, 70))
        self.title_bar_modify_scrollarea = self._get(
            kwargs, 'title_bar_modify_scrollarea', bool, True)
        self.title_bar_style = self._get(kwargs, 'title_bar_style', int,
                                         MENUBAR_STYLE_ADAPTIVE)
        self.title_close_button = self._get(kwargs, 'title_close_button', bool,
                                            True)
        self.title_close_button_background_color = self._get(
            kwargs, 'title_close_button_background_color', 'color',
            (255, 255, 255))
        self.title_close_button_cursor = self._get(
            kwargs, 'title_close_button_cursor', 'cursor')
        self.title_fixed = self._get(kwargs, 'title_fixed', bool, True)
        self.title_floating = self._get(kwargs, 'title_floating', bool, False)
        self.title_font = self._get(kwargs, 'title_font', 'font',
                                    FONT_OPEN_SANS)
        self.title_font_antialias = self._get(kwargs, 'title_font_antialias',
                                              bool, True)
        self.title_font_color = self._get(kwargs, 'title_font_color', 'color',
                                          (220, 220, 220))
        self.title_font_shadow = self._get(kwargs, 'title_font_shadow', bool,
                                           False)
        self.title_font_shadow_color = self._get(kwargs,
                                                 'title_font_shadow_color',
                                                 'color', (0, 0, 0))
        self.title_font_shadow_offset = self._get(kwargs,
                                                  'title_font_shadow_offset',
                                                  int, 2)
        self.title_font_shadow_position = self._get(
            kwargs, 'title_font_shadow_position', 'position',
            POSITION_NORTHWEST)
        self.title_font_size = self._get(kwargs, 'title_font_size', int, 40)
        self.title_offset = self._get(kwargs, 'title_offset', 'tuple2',
                                      (5, -1))
        self.title_updates_pygame_display = self._get(
            kwargs, 'title_updates_pygame_display', bool, False)

        # ScrollArea
        self.scrollarea_outer_margin = self._get(kwargs,
                                                 'scrollarea_outer_margin',
                                                 'tuple2', (0, 0))
        self.scrollarea_position = self._get(kwargs, 'scrollarea_position',
                                             str, POSITION_SOUTHEAST)

        # ScrollBar
        self.scrollbar_color = self._get(kwargs, 'scrollbar_color', 'color',
                                         (235, 235, 235))
        self.scrollbar_cursor = self._get(kwargs, 'scrollbar_cursor', 'cursor')
        self.scrollbar_shadow = self._get(kwargs, 'scrollbar_shadow', bool,
                                          False)
        self.scrollbar_shadow_color = self._get(kwargs,
                                                'scrollbar_shadow_color',
                                                'color', (0, 0, 0))
        self.scrollbar_shadow_offset = self._get(kwargs,
                                                 'scrollbar_shadow_offset',
                                                 int, 2)
        self.scrollbar_shadow_position = self._get(
            kwargs, 'scrollbar_shadow_position', 'position',
            POSITION_NORTHWEST)
        self.scrollbar_slider_color = self._get(kwargs,
                                                'scrollbar_slider_color',
                                                'color', (200, 200, 200))
        self.scrollbar_slider_hover_color = self._get(
            kwargs, 'scrollbar_slider_hover_color', 'color', (170, 170, 170))
        self.scrollbar_slider_pad = self._get(kwargs, 'scrollbar_slider_pad',
                                              NumberInstance, 0)
        self.scrollbar_thick = self._get(kwargs, 'scrollbar_thick', int, 20)

        # Generic widget themes
        self.widget_alignment = self._get(kwargs, 'widget_alignment',
                                          'alignment', ALIGN_CENTER)
        self.widget_background_color = self._get(kwargs,
                                                 'widget_background_color',
                                                 'color_image_none')
        self.widget_background_inflate = self._get(kwargs,
                                                   'background_inflate',
                                                   'tuple2int', (0, 0))
        self.widget_background_inflate_to_selection = self._get(
            kwargs, 'widget_background_inflate_to_selection', bool, False)
        self.widget_border_color = self._get(kwargs, 'widget_border_color',
                                             'color', (0, 0, 0))
        self.widget_border_inflate = self._get(kwargs, 'widget_border_inflate',
                                               'tuple2int', (0, 0))
        self.widget_border_position = self._get(kwargs,
                                                'widget_border_position',
                                                'position_vector',
                                                WIDGET_FULL_BORDER)
        self.widget_border_width = self._get(kwargs, 'widget_border_width',
                                             int, 0)
        self.widget_box_arrow_color = self._get(kwargs,
                                                'widget_box_arrow_color',
                                                'color', (150, 150, 150))
        self.widget_box_arrow_margin = self._get(kwargs,
                                                 'widget_box_arrow_margin',
                                                 'tuple3int', (5, 5, 0))
        self.widget_box_background_color = self._get(
            kwargs, 'widget_box_background_color', 'color', (255, 255, 255))
        self.widget_box_border_color = self._get(kwargs,
                                                 'widget_box_border_color',
                                                 'color', (0, 0, 0))
        self.widget_box_border_width = self._get(kwargs,
                                                 'widget_box_border_width',
                                                 int, 1)
        self.widget_box_inflate = self._get(kwargs, 'widget_box_inflate',
                                            'tuple2int', (0, 0))
        self.widget_box_margin = self._get(kwargs, 'widget_box_margin',
                                           'tuple2', (25, 0))
        self.widget_cursor = self._get(kwargs, 'widget_cursor', 'cursor',
                                       CURSOR_ARROW)
        self.widget_font = self._get(kwargs, 'widget_font', 'font',
                                     FONT_OPEN_SANS)
        self.widget_font_antialias = self._get(kwargs, 'widget_font_antialias',
                                               bool, True)
        self.widget_font_background_color = self._get(
            kwargs,
            'widget_font_background_color',
            'color_none',
        )
        self.widget_font_background_color_from_menu = self._get(
            kwargs, 'widget_font_background_color_from_menu', bool, False)
        self.widget_font_color = self._get(kwargs, 'widget_font_color',
                                           'color', (70, 70, 70))
        self.widget_font_shadow = self._get(kwargs, 'widget_font_shadow', bool,
                                            False)
        self.widget_font_shadow_color = self._get(kwargs,
                                                  'widget_font_shadow_color',
                                                  'color', (0, 0, 0))
        self.widget_font_shadow_offset = self._get(
            kwargs, 'widget_font_shadow_offset', int, 2)
        self.widget_font_shadow_position = self._get(
            kwargs, 'widget_font_shadow_position', 'position',
            POSITION_NORTHWEST)
        self.widget_font_size = self._get(kwargs, 'widget_font_size', int, 30)
        self.widget_margin = self._get(kwargs, 'widget_margin', 'tuple2',
                                       (0, 0))
        self.widget_offset = self._get(kwargs, 'widget_offset', 'tuple2',
                                       (0, 0))
        self.widget_padding = self._get(kwargs, 'widget_padding',
                                        PaddingInstance, (4, 8))
        self.widget_selection_effect = self._get(
            kwargs, 'widget_selection_effect', Selection,
            HighlightSelection(margin_x=0, margin_y=0))
        self.widget_tab_size = self._get(kwargs, 'widget_tab_size', int, 4)
        self.widget_url_color = self._get(kwargs, 'widget_url_color', 'color',
                                          (6, 69, 173))

        # Compatibility check
        if kwargs.get('menubar_close_button', None) is not None:
            warn('menubar_close_button has been moved to title_close_button. '
                 'This alert will be removed in v4.1')
            self.title_close_button = self._get(kwargs, 'menubar_close_button',
                                                bool)

        # Upon this, no more kwargs should exist, raise exception if there's more
        for invalid_keyword in kwargs.keys():
            raise ValueError(
                'parameter Theme.{} does not exist'.format(invalid_keyword))

        # Test purpose only, if True disables any validation
        self._disable_validation = False
Ejemplo n.º 7
0
    def add_row(
            self,
            cells: Union[ColumnInputType, 'Widget'],
            cell_align: Optional[str] = None,
            cell_border_color: Optional[ColorInputType] = None,
            cell_border_position: Optional[WidgetBorderPositionType] = None,
            cell_border_width: Optional[int] = None,
            cell_font: Optional[FontType] = None,
            cell_font_color: Optional[ColorInputType] = None,
            cell_font_size: Optional[int] = None,
            cell_padding: PaddingType = None,
            cell_vertical_position: Optional[str] = None,
            row_background_color: Optional[ColorInputType] = None) -> 'Frame':
        """
        Add row to table.

        .. note::

            By default, if ``None`` each cell style uses the table defaults "cell"
            styles.

        .. note::

            By default, the cell font is the same as the table font style.

        .. warning::

            Currently, only static widgets work properly, that is, widgets that
            does not accept events. Buttons or TextInputs can be added, but them
            will not accept any event, also, these widgets cannot be selected.
            That's because the Table architecture relies on Frame widget.

        :param cells: Cells to add. This can be a tuple or list of widgets, string, numbers, boolean values or images. Also, a Frame row can be added
        :param cell_align: Horizontal align of each cell. See :py:mod:`pygame_menu.locals`
        :param cell_border_color: Border color of each cell
        :param cell_border_position: Border position of each cell. Valid only: north, south, east, and west. See :py:mod:`pygame_menu.locals`
        :param cell_border_width: Border width in px of each cell
        :param cell_font: Font name or path
        :param cell_font_color: Font color
        :param cell_font_size: Font size
        :param cell_padding: Padding of each cell according to CSS rules. General shape: (top, right, bottom, left)
        :param cell_vertical_position: Vertical position of each cell. Only valid: north, center, and south. See :py:mod:`pygame_menu.locals`
        :param row_background_color: Row background color
        :return:
        """
        assert self.configured, 'table must be configured before adding rows'

        # Use defaults
        if cell_align is None:
            cell_align = self.default_cell_align
        if cell_border_color is None:
            cell_border_color = self.default_cell_border_color
        if cell_border_position is None:
            cell_border_position = self.default_cell_border_position
        if cell_border_width is None:
            cell_border_width = self.default_cell_border_width
        if cell_font is None:
            cell_font = self._font_name
        if cell_font_color is None:
            cell_font_color = self._font_color
        if cell_font_size is None:
            cell_font_size = self._font_size
        if cell_padding is None:
            cell_padding = self.default_cell_padding
        if cell_vertical_position is None:
            cell_vertical_position = self.default_cell_vertical_position
        if row_background_color is None:
            row_background_color = self.default_row_background_color

        # If cells is a previous table row
        if isinstance(cells, Frame) and cells.has_attribute('is_row'):
            row_cells = list(cells.get_widgets(unpack_subframes=False))
            cells.clear()
            cells = row_cells
        if isinstance(cells, Widget):
            cells = [cells]

        assert isinstance(cells, VectorInstance)

        # Check cell styles
        self._check_cell_style(align=cell_align,
                               background_color=row_background_color,
                               border_color=cell_border_color,
                               border_position=cell_border_position,
                               border_width=cell_border_width,
                               padding=cell_padding,
                               vertical_position=cell_vertical_position)
        cell_padding = parse_padding(cell_padding)
        if cell_border_color is not None:
            cell_border_color = assert_color(cell_border_color)
        if isinstance(cell_border_position, str):
            cell_border_position = [cell_border_position]

        # Check positioning
        if cell_border_width == 0:
            cell_border_position = WIDGET_BORDER_POSITION_NONE

        if row_background_color is not None:
            row_background_color = assert_color(row_background_color)

        # Create frame row
        row = Frame(1,
                    1,
                    ORIENTATION_HORIZONTAL,
                    frame_id=self._id + '+cell-row-' + uuid4(short=True))
        row._accepts_scrollarea = False
        row._accepts_title = False
        row._menu_can_be_none_pack = True
        row._update__repr___(self)
        row.configured = True
        row.relax()
        row.set_background_color(row_background_color)
        row.set_menu(self._menu)
        row.set_scrollarea(self._scrollarea)
        row.set_attribute('is_row')
        row.set_controls(joystick=self._joystick_enabled,
                         mouse=self._mouse_enabled,
                         touchscreen=self._touchscreen_enabled,
                         keyboard=self._keyboard_enabled)
        # row.set_frame(self) This cannot be executed as row is packed within

        # Create widgets
        row_cells: List['Widget'] = []
        cell: 'Widget'
        j = 0

        for c in cells:
            cell_widget_type = False

            if isinstance(c, (str, int, float, bool)):
                cell = Label(c,
                             label_id=self._id + '+cell-label-' +
                             uuid4(short=True))
                cell.set_font(
                    antialias=self._font_antialias,
                    background_color=None,
                    color=cell_font_color,
                    font=cell_font,
                    font_size=cell_font_size,
                    readonly_color=self._font_readonly_color,
                    readonly_selected_color=self._font_readonly_selected_color,
                    selected_color=self._font_selected_color)
                cell.set_padding(0)
                cell.set_tab_size(self._tab_size)

            elif isinstance(c, BaseImage):
                cell = Image(c,
                             image_id=self._id + '+cell-image-' +
                             uuid4(short=True))

            elif isinstance(c, pygame.Surface):
                cell = SurfaceWidget(c,
                                     surface_id=self._id + '+cell-surface-' +
                                     uuid4(short=True))

            else:
                assert isinstance(c, Widget)
                assert c != self, f'{self.get_class_id()} cannot be appended to itself'

                # Check if Frame not recursive
                if isinstance(c, Frame):
                    print(self, c.get_widgets())
                    assert self not in c.get_widgets(unpack_subframes_include_frame=True), \
                        f'{self.get_class_id()} cannot be packed within {c.get_class_id()},' \
                        f' recursive packing is not allowed (Table is within Frame' \
                        f' to be inserted as row cell)'

                cell = c
                if c._accept_events:
                    cell_widget_type = True
                    warn(
                        f'{self.get_class_id()} does not accept events in current'
                        f' pygame-menu v{ver}; thus appended cell row widget '
                        f'{c.get_class_id()} (pos {j}) would not work properly, '
                        f'as it will ignore all inputs. Also, widgets within Tables'
                        f' cannot be selected. Consider Tables as visual-only')
                # self._append_menu_update_frame(self)

            # Configure cell
            cell.set_attribute('accept_events', cell_widget_type)
            cell.set_attribute('align', cell_align)
            cell.set_attribute('background_color', row_background_color)
            cell.set_attribute('border_color', cell_border_color)
            cell.set_attribute('border_position', cell_border_position)
            cell.set_attribute('border_width', cell_border_width)
            cell.set_attribute('column', j + 1)
            cell.set_attribute('padding', cell_padding)
            cell.set_attribute('row', len(self._rows) + 1)
            cell.set_attribute('row_frame', row)
            cell.set_attribute('table', self)
            cell.set_attribute('vertical_position', cell_vertical_position)
            cell.set_float(False)
            cell._update__repr___(self)
            cell.configured = True

            # If cell is within a menu, remove from it
            if cell.get_menu() is not None:
                try:
                    cell.get_menu().remove_widget(cell)
                except ValueError:
                    pass

            # Check the cell frame is None
            assert cell.get_frame() != self, \
                f'{cell.get_class_id()} cannot be added as it already exists in table'
            assert cell.get_frame() is None, \
                f'{cell.get_class_id()} is already packed in ' \
                f'{cell.get_frame().get_class_id()}, it cannot be added to {self.get_class_id()}'

            # If cell is frame and scrollable
            if isinstance(cell, Frame):
                self._append_menu_update_frame(cell)

            # Add to cells
            row_cells.append(cell)
            j += 1

        # Pack cells to row
        for c in row_cells:
            row.pack(c)

        # Pack rows to self
        super(Table, self).pack(row)
        self._rows.append(row)

        # Update size
        self._update_row_sizing()
        self._update_event_widgets()

        return row
Ejemplo n.º 8
0
    def set_sound(self,
                  sound_type: str,
                  sound_file: Optional[Union[str, 'Path']],
                  volume: float = 0.5,
                  loops: int = 0,
                  maxtime: NumberType = 0,
                  fade_ms: NumberType = 0) -> bool:
        """
        Link a sound file to a sound type.

        :param sound_type: Sound type
        :param sound_file: Sound file. If ``None`` disable the given sound type
        :param volume: Volume of the sound, from ``0.0`` to ``1.0``
        :param loops: Loops of the sound
        :param maxtime: Max playing time of the sound
        :param fade_ms: Fading ms
        :return: The status of the sound load, ``True`` if the sound was loaded
        """
        assert isinstance(sound_type, str)
        assert isinstance(sound_file, (str, type(None), Path))
        assert isinstance(volume, NumberInstance)
        assert isinstance(loops, int)
        assert isinstance(maxtime, NumberInstance)
        assert isinstance(fade_ms, NumberInstance)
        assert loops >= 0, 'loops count must be equal or greater than zero'
        assert maxtime >= 0, 'maxtime must be equal or greater than zero'
        assert fade_ms >= 0, 'fade_ms must be equal or greater than zero'
        assert 1 >= volume >= 0, 'volume must be between 0 and 1'

        # Check sound type is correct
        if sound_type not in SOUND_TYPES:
            raise ValueError('sound type not valid, check the manual')

        # If file is none disable the sound
        if sound_file is None:
            self._sound[sound_type] = {}
            return False

        # Check the file exists
        sound_file = str(sound_file)
        if not path.isfile(sound_file):
            raise IOError('sound file "{0}" does not exist'.format(sound_file))

        # Load the sound
        try:
            # noinspection PyTypeChecker
            sound_data = mixer.Sound(file=sound_file)
        except pygame_error:
            warn(
                'the sound file "{0}" could not be loaded, it has been disabled'
                ''.format(sound_file))
            self._sound[sound_type] = {}
            return False
        except NotImplementedError:
            warn(
                'mixer module is not available on the current System, thus, the '
                'sound file "{0}" could not be loaded'.format(sound_file))
            self._sound[sound_type] = {}
            return False

        # Configure the sound
        sound_data.set_volume(float(volume))

        # Store the sound
        self._sound[sound_type] = {
            'fade_ms': fade_ms,
            'file': sound_data,
            'length': sound_data.get_length(),
            'loops': loops,
            'maxtime': maxtime,
            'path': sound_file,
            'type': sound_type,
            'volume': volume
        }
        return True
Ejemplo n.º 9
0
    def __init__(self,
                 allowedchanges: int = AUDIO_ALLOW_CHANNELS_CHANGE
                 | AUDIO_ALLOW_FREQUENCY_CHANGE,
                 buffer: int = 4096,
                 channels: int = 2,
                 devicename: str = '',
                 force_init: bool = False,
                 frequency: int = 22050,
                 size: int = -16,
                 sound_id: str = '',
                 uniquechannel: bool = True) -> None:
        super(Sound, self).__init__(object_id=sound_id)

        assert isinstance(allowedchanges, int)
        assert isinstance(buffer, int)
        assert isinstance(channels, int)
        assert isinstance(devicename, str)
        assert isinstance(force_init, bool)
        assert isinstance(frequency, int)
        assert isinstance(size, int)
        assert isinstance(uniquechannel, bool)

        assert buffer > 0, 'buffer size must be greater than zero'
        assert channels > 0, 'channels must be greater than zero'
        assert frequency > 0, 'frequency must be greater than zero'

        # Check if mixer is init
        mixer_missing = 'MissingModule' in str(type(mixer))
        if mixer_missing:
            warn('pygame mixer module could not be found, NotImplementedError'
                 'has been raised. Sound support is disabled')

        # Initialize sounds if not initialized
        if not mixer_missing and \
                ((mixer.get_init() is None and not SOUND_INITIALIZED[0]) or
                 force_init):

            # Set sound as initialized globally
            SOUND_INITIALIZED[0] = True

            # Check pygame version
            version_major, _, version_minor = pygame_version

            # noinspection PyBroadException
            try:
                # <= 1.9.4
                if version_major == 1 and version_minor <= 4:
                    mixer.init(frequency=frequency,
                               size=size,
                               channels=channels,
                               buffer=buffer)

                # <2.0.0 & >= 1.9.5
                elif version_major == 1 and version_minor > 4:  # lgtm [py/redundant-comparison]
                    mixer.init(frequency=frequency,
                               size=size,
                               channels=channels,
                               buffer=buffer,
                               devicename=devicename)

                # >= 2.0.0
                elif version_major > 1:
                    mixer.init(frequency=frequency,
                               size=size,
                               channels=channels,
                               buffer=buffer,
                               devicename=devicename,
                               allowedchanges=allowedchanges)

            except Exception as e:
                warn('sound error: ' + str(e))
            except pygame_error as e:
                warn('sound engine could not be initialized, pygame error: ' +
                     str(e))

        # Store mixer configs
        self._mixer_configs = {
            'allowedchanges': allowedchanges,
            'buffer': buffer,
            'channels': channels,
            'devicename': devicename,
            'frequency': frequency,
            'size': size
        }

        # Channel where a sound is played
        self._channel = None
        self._uniquechannel = uniquechannel

        # Sound dict
        self._sound = {}
        for sound in SOUND_TYPES:
            self._sound[sound] = {}

        # Last played song
        self._last_play = ''
        self._last_time = 0
Ejemplo n.º 10
0
    def button(self,
               title: Any,
               action: Optional[Union['pygame_menu.Menu', '_events.MenuAction',
                                      Callable, int]] = None,
               *args,
               **kwargs) -> 'pygame_menu.widgets.Button':
        """
        Adds a button to the Menu.

        The arguments and unknown keyword arguments are passed to the action, if
        it's a callable object:

        .. code-block:: python

            action(*args)

        If ``accept_kwargs=True`` then the ``**kwargs`` are also unpacked on action
        call:

        .. code-block:: python

            action(*args, **kwargs)

        If ``onselect`` is defined, the callback is executed as follows, where
        ``selected`` is a boolean representing the selected status:

        .. code-block:: python

            onselect(selected, widget, menu)

        kwargs (Optional)
            - ``accept_kwargs``                 (bool) – Button action accepts ``**kwargs`` if it's a callable object (function-type), ``False`` by default
            - ``align``                         (str) – Widget `alignment <https://pygame-menu.readthedocs.io/en/latest/_source/themes.html#alignment>`_
            - ``back_count``                    (int) – Number of menus to go back if action is :py:data:`pygame_menu.events.BACK` event, default is ``1``
            - ``background_color``              (tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage`) – Color of the background. ``None`` for no-color
            - ``background_inflate``            (tuple, list) – Inflate background on x-axis and y-axis (x, y) in px
            - ``border_color``                  (tuple, list, str, int, :py:class:`pygame.Color`) – Widget border color. ``None`` for no-color
            - ``border_inflate``                (tuple, list) – Widget border inflate on x-axis and y-axis (x, y) in px
            - ``border_position``               (str, tuple, list) – Widget border positioning. It can be a single position, or a tuple/list of positions. Only are accepted: north, south, east, and west. See :py:mod:`pygame_menu.locals`
            - ``border_width``                  (int) – Border width in px. If ``0`` disables the border
            - ``button_id``                     (str) – Widget ID
            - ``cursor``                        (int, :py:class:`pygame.cursors.Cursor`, None) – Cursor of the widget if the mouse is placed over
            - ``float``                         (bool) - If ``True`` the widget don't contribute width/height to the Menu widget positioning computation, and don't add one unit to the rows
            - ``float_origin_position``         (bool) - If ``True`` the widget position is set to the top-left position of the Menu if the widget is floating
            - ``font_background_color``         (tuple, list, str, int, :py:class:`pygame.Color`, None) – Widget font background color
            - ``font_color``                    (tuple, list, str, int, :py:class:`pygame.Color`) – Widget font color
            - ``font_name``                     (str, :py:class:`pathlib.Path`, :py:class:`pygame.font.Font`) – Widget font path
            - ``font_shadow_color``             (tuple, list, str, int, :py:class:`pygame.Color`) – Font shadow color
            - ``font_shadow_offset``            (int) – Font shadow offset in px
            - ``font_shadow_position``          (str) – Font shadow position, see locals for position
            - ``font_shadow``                   (bool) – Font shadow is enabled or disabled
            - ``font_size``                     (int) – Font size of the widget
            - ``margin``                        (tuple, list) – Widget (left, bottom) margin in px
            - ``onselect``                      (callable, None) – Callback executed when selecting the widget
            - ``padding``                       (int, float, tuple, list) – Widget padding according to CSS rules. General shape: (top, right, bottom, left)
            - ``readonly_color``                (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode
            - ``readonly_selected_color``       (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget if readonly mode and is selected
            - ``selection_color``               (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the selected widget; only affects the font color
            - ``selection_effect``              (:py:class:`pygame_menu.widgets.core.Selection`) – Widget selection effect
            - ``shadow_color``                  (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the widget shadow
            - ``shadow_radius``                 (int) - Border radius of the shadow
            - ``shadow_type``                   (str) - Shadow type, it can be ``'rectangular'`` or ``'ellipse'``
            - ``shadow_width``                  (int) - Width of the shadow. If ``0`` the shadow is disabled
            - ``tab_size``                      (int) – Width of a tab character
            - ``underline_color``               (tuple, list, str, int, :py:class:`pygame.Color`, None) – Color of the underline. If ``None`` use the same color of the text
            - ``underline_offset``              (int) – Vertical offset in px. ``2`` by default
            - ``underline_width``               (int) – Underline width in px. ``2`` by default
            - ``underline``                     (bool) – Enables text underline, using a properly placed decoration. ``False`` by default

        .. note::

            All theme-related optional kwargs use the default Menu theme if not defined.

        .. note::

            Using ``action=None`` is the same as using ``action=pygame_menu.events.NONE``.

        .. note::

            This is applied only to the base Menu (not the currently displayed,
            stored in ``_current`` pointer); for such behaviour apply to
            :py:meth:`pygame_menu.menu.Menu.get_current` object.

        .. warning::

            Be careful with kwargs collision. Consider that all optional documented
            kwargs keys are removed from the object.

        :param title: Title of the button
        :param action: Action of the button, can be a Menu, an event, or a function
        :param args: Additional arguments used by a function
        :param kwargs: Optional keyword arguments
        :return: Widget object
        :rtype: :py:class:`pygame_menu.widgets.Button`
        """
        total_back = kwargs.pop('back_count', 1)
        assert isinstance(total_back, int) and 1 <= total_back

        # Get ID
        button_id = kwargs.pop('button_id', '')
        assert isinstance(button_id, str), 'id must be a string'

        # Accept kwargs
        accept_kwargs = kwargs.pop('accept_kwargs', False)
        assert isinstance(accept_kwargs, bool)

        # Onselect callback
        onselect = kwargs.pop('onselect', None)

        # Filter widget attributes to avoid passing them to the callbacks
        attributes = self._filter_widget_attributes(kwargs)

        # Button underline
        underline = kwargs.pop('underline', False)
        underline_color = kwargs.pop('underline_color',
                                     attributes['font_color'])
        underline_offset = kwargs.pop('underline_offset', 1)
        underline_width = kwargs.pop('underline_width', 1)

        # Change action if certain events
        if action == _events.PYGAME_QUIT or action == _events.PYGAME_WINDOWCLOSE:
            action = _events.EXIT
        elif action is None:
            action = _events.NONE

        # If element is a Menu
        if isinstance(action, type(self._menu)):
            # Check for recursive
            if action == self._menu or action.in_submenu(self._menu,
                                                         recursive=True):
                raise ValueError(
                    f'{action.get_class_id()} title "{action.get_title()}" is '
                    f'already on submenu structure, recursive menus lead to '
                    f'unexpected behaviours. For returning to previous menu'
                    f'use pygame_menu.events.BACK event defining an optional '
                    f'back_count number of menus to return from, default is 1')

            widget = Button(title, button_id, self._menu._open, action)
            widget.to_menu = True

        # If element is a MenuAction
        elif action == _events.BACK:  # Back to Menu
            widget = Button(title, button_id, self._menu.reset, total_back)

        elif action == _events.CLOSE:  # Close Menu
            widget = Button(title, button_id, self._menu._close)

        elif action == _events.EXIT:  # Exit program
            widget = Button(title, button_id, self._menu._exit)

        elif action == _events.NONE:  # None action
            widget = Button(title, button_id)

        elif action == _events.RESET:  # Back to Top Menu
            widget = Button(title, button_id, self._menu.full_reset)

        # If element is a function or callable
        elif callable(action):
            if not accept_kwargs:
                widget = Button(title, button_id, action, *args)
            else:
                widget = Button(title, button_id, action, *args, **kwargs)

        else:
            raise ValueError('action must be a Menu, a MenuAction (event), a '
                             'function (callable), or None')

        # Configure and add the button
        if not accept_kwargs:
            try:
                self._check_kwargs(kwargs)
            except ValueError:
                warn('button cannot accept kwargs. If you want to use kwargs '
                     'options set accept_kwargs=True')
                raise

        self._configure_widget(widget=widget, **attributes)
        if underline:
            widget.add_underline(underline_color, underline_offset,
                                 underline_width)
        widget.set_selection_callback(onselect)
        self._append_widget(widget)

        # Add to submenu
        if widget.to_menu:
            self._add_submenu(action, widget)

        return widget