Source code for pygame_menu.themes

"""
pygame-menu
https://github.com/ppizarror/pygame-menu

THEMES
Theme class and predefined themes.
"""

from __future__ import annotations

__all__ = [
    # Main class
    "Theme",
    # Custom themes
    "THEME_BLUE",
    "THEME_DARK",
    "THEME_DEFAULT",
    "THEME_GREEN",
    "THEME_ORANGE",
    "THEME_SOLARIZED",
    # Colors
    "TRANSPARENT_COLOR",
]

import copy
from typing import TYPE_CHECKING, Any

from pygame_menu._scrollarea import get_scrollbars_from_position
from pygame_menu._types import (
    ColorInputType,
    ColorType,
    CursorType,
    NumberInstance,
    NumberType,
    PaddingInstance,
    PaddingType,
    Tuple2IntType,
    Tuple2NumberType,
    Tuple3IntType,
    VectorInstance,
)
from pygame_menu.baseimage import BaseImage
from pygame_menu.font import FONT_OPEN_SANS, FontType, assert_font
from pygame_menu.locals import (
    ALIGN_CENTER,
    CURSOR_ARROW,
    POSITION_NORTHWEST,
    POSITION_SOUTHEAST,
)
from pygame_menu.utils import (
    assert_alignment,
    assert_color,
    assert_cursor,
    assert_position,
    assert_position_vector,
    assert_vector,
    format_color,
)
from pygame_menu.widgets import (
    MENUBAR_STYLE_ADAPTIVE,
    MENUBAR_STYLE_NONE,
    MENUBAR_STYLE_SIMPLE,
    MENUBAR_STYLE_TITLE_ONLY,
    MENUBAR_STYLE_TITLE_ONLY_DIAGONAL,
    MENUBAR_STYLE_UNDERLINE,
    MENUBAR_STYLE_UNDERLINE_TITLE,
    HighlightSelection,
    NoneSelection,
)
from pygame_menu.widgets.core import Selection
from pygame_menu.widgets.core.widget import (
    WIDGET_FULL_BORDER,
    WIDGET_SHADOW_TYPE_ELLIPSE,
    WIDGET_SHADOW_TYPE_RECTANGULAR,
    WidgetBorderPositionType,
)

if TYPE_CHECKING:
    import pygame_menu

TRANSPARENT_COLOR: ColorType = (0, 0, 0, 0)


def _check_menubar_style(style: int) -> bool:
    """
    Check menubar style.

    :param style: Style
    :return: ``True`` if correct
    """
    return style in (
        MENUBAR_STYLE_ADAPTIVE,
        MENUBAR_STYLE_SIMPLE,
        MENUBAR_STYLE_TITLE_ONLY,
        MENUBAR_STYLE_TITLE_ONLY_DIAGONAL,
        MENUBAR_STYLE_NONE,
        MENUBAR_STYLE_UNDERLINE,
        MENUBAR_STYLE_UNDERLINE_TITLE,
    )


[docs] class Theme: """ Class defining the visual rendering of menus and widgets. .. note:: All colors must be defined with a tuple of 3 or 4 numbers in the formats: - (R, G, B) - (R, G, B, A) Red (R), Green (G), and Blue (B) must be numbers between ``0`` and ``255``. A means the alpha channel (opacity), if ``0`` the color is transparent, ``100`` means opaque. .. note:: Themes only modify visual behavior of the Menu. For other options like rows/columns, enabling or disabling overflow, position, or Menu width/height see Menu parameters. :param background_color: Menu background color :type background_color: tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage` :param border_color: Menu border color. If border is an image, it will be split in 9 tiles to use top, left, bottom, right, and the corners :type border_color: tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage`, None :param border_width: Border width in px. Used only if ``border_color`` is not an image :type border_width: int :param cursor_color: Cursor color (used in some text-gathering widgets like ``TextInput``) :type cursor_color: tuple, list, str, int, :py:class:`pygame.Color` :param cursor_selection_color: Color of the text selection if the cursor is enabled on certain widgets :type cursor_selection_color: tuple, list, str, int, :py:class:`pygame.Color` :param cursor_switch_ms: Interval of cursor switch between off and on status :type cursor_switch_ms: int, float :param focus_background_color: Color of the widget focus, this must be a tuple of 4 elements (R, G, B, A) :type focus_background_color: tuple, list, str, int, :py:class:`pygame.Color` :param fps: Menu max fps (frames per second). If ``0`` there's no limit :type fps: int, float :param readonly_color: Color of the widget in readonly mode :type readonly_color: tuple, list, str, int, :py:class:`pygame.Color` :param readonly_selected_color: Color of the selected widget in readonly mode :type readonly_selected_color: tuple, list, str, int, :py:class:`pygame.Color` :param scrollarea_outer_margin: Outer ScrollArea margin in px; the tuple is added to computed ScrollArea width/height, it can add a margin to bottom/right scrolls after widgets. If value less than ``1`` use percentage of width/height. It cannot be a negative value :type scrollarea_outer_margin: tuple, list :param scrollarea_position: Position of ScrollArea scrollbars. See :py:mod:`pygame_menu.locals` :type scrollarea_position: str :param scrollbar_color: Scrollbars color :type scrollbar_color: tuple, list, str, int, :py:class:`pygame.Color` :param scrollbar_cursor: Scrollbar cursor if mouse is placed over. If ``None`` the scrollbar don't change the cursor :type scrollbar_cursor: int, :py:class:`pygame.cursors.Cursor`, None :param scrollbar_shadow: Indicate if a shadow is drawn on each scrollbar :type scrollbar_shadow: bool :param scrollbar_shadow_color: Color of the scrollbar shadow :type scrollbar_shadow_color: tuple, list, str, int, :py:class:`pygame.Color` :param scrollbar_shadow_offset: Offset of the scrollbar shadow :type scrollbar_shadow_offset: int :param scrollbar_shadow_position: Position of the scrollbar shadow. See :py:mod:`pygame_menu.locals` :type scrollbar_shadow_position: str :param scrollbar_slider_color: Color of the sliders :type scrollbar_slider_color: tuple, list, str, int, :py:class:`pygame.Color` :param scrollbar_slider_hover_color: Color of the slider if hovered or clicked :type scrollbar_slider_hover_color: tuple, list, str, int, :py:class:`pygame.Color` :param scrollbar_slider_pad: Space between slider and scrollbars borders :type scrollbar_slider_pad: int, float :param scrollbar_thick: Scrollbar thickness in px :type scrollbar_thick: int :param selection_color: Color of the selected widget. It updates both selected font and ``widget_selection_effect`` color :type selection_color: tuple, list, str, int, :py:class:`pygame.Color` :param surface_clear_color: Surface clear color before applying background function :type surface_clear_color: tuple, list, str, int, :py:class:`pygame.Color` :param title: Title is enabled/disabled. If disabled the object is ``hidden`` :type title: bool :param title_background_color: Title background color :type title_background_color: tuple, list, str, int, :py:class:`pygame.Color` :param title_bar_modify_scrollarea: If ``True`` title bar modifies the scrollbars of the scrollarea depending on the style :type title_bar_modify_scrollarea: bool :param title_bar_style: Style of the title, use :py:class:`pygame_menu.widgets.MenuBar` widget styles :type title_bar_style: int :param title_close_button: Draw a back-box button on header to close the Menu. If user moves through nested submenus this buttons turns to a back-arrow :type title_close_button: bool :param title_close_button_background_color: Title back-box background color :type title_close_button_background_color: tuple, list, str, int, :py:class:`pygame.Color` :param title_close_button_cursor: Cursor applied over title close button :type title_close_button_cursor: int, :py:class:`pygame.cursors.Cursor`, None :param title_fixed: If ``True`` title is drawn over the scrollarea, forcing widget surface area to be drawn behind the title :type title_fixed: bool :param title_floating: If ``True`` title don't contribute height to the Menu. Thus, scroll uses full menu width/height :type title_floating: bool :param title_font: Title font :type title_font: str, :py:class:`pygame.font.Font`, :py:class:`pathlib.Path` :param title_font_antialias: Title font renders with antialiasing :type title_font_antialias: bool :param title_font_color: Title font color :type title_font_color: tuple, list, str, int, :py:class:`pygame.Color` :param title_font_shadow: Enable title font shadow :type title_font_shadow: bool :param title_font_shadow_color: Title font shadow color :type title_font_shadow_color: tuple, list, str, int, :py:class:`pygame.Color` :param title_font_shadow_offset: Offset of title font shadow in px :type title_font_shadow_offset: int :param title_font_shadow_position: Position of the title font shadow. See :py:mod:`pygame_menu.locals` :type title_font_shadow_position: str :param title_font_size: Font size of the title :type title_font_size: int :param title_offset: Offset (x-position, y-position) of title in px :type title_offset: tuple, list :param title_updates_pygame_display: If ``True`` the menu title updates See :py:mod:`pygame.display.caption` automatically on draw :type title_updates_pygame_display: bool :param widget_alignment: Widget default `alignment <https://pygame-menu.readthedocs.io/en/latest/_source/themes.html#alignment>`_. See :py:mod:`pygame_menu.locals` :type widget_alignment: str :param widget_alignment_ignore_scrollbar_thickness: Widget positioning ignores the scrollbar thickness. If ``True``, the widgets only consider the menu size, ignoring the thickness of the visible scrollbars :type widget_alignment_ignore_scrollbar_thickness: bool :param widget_background_color: Background color of a widget, it can be a color, ``None`` (transparent), or a BaseImage object. Background fills the entire widget + the padding :type widget_background_color: tuple, list, str, int, :py:class:`pygame.Color`, :py:class:`pygame_menu.baseimage.BaseImage`, None :param widget_background_inflate: Inflate background on x-axis and y-axis (x, y) in px. By default, it uses the highlight margin. This parameter is visual only. For modifying widget size use padding instead :type widget_background_inflate: tuple, list :param widget_background_inflate_to_selection: If ``True`` widget will inflate to match selection effect margin and overrides ``widget_background_inflate`` :type widget_background_inflate_to_selection: bool :param widget_border_color: Widget border color :type widget_border_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_border_inflate: Widget inflate size on x-axis and y-axis (x, y) in px. These values cannot be negative :type widget_border_inflate: tuple, list :param widget_border_position: 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` :type widget_border_position: str, tuple, list :param widget_border_width: Widget border width in px. If ``0`` the border is disabled. Border width don't contribute to the widget width/height, it's visual-only :type widget_border_width: int :param widget_box_arrow_color: Widget box arrow color, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_arrow_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_box_arrow_margin: Widget box arrow margin (left, right, vertical) in px, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_arrow_margin: tuple :param widget_box_background_color: Widget box background color, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_background_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_box_border_color: Widget box border color, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_border_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_box_border_width: Widget box border width in px, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_border_width: int :param widget_box_inflate: Widget box inflate on x-axis and y-axis (x, y) in px, used by some widgets such as DropSelect, Fancy Selector, etc. :type widget_box_inflate: tuple, list :param widget_box_margin: Box margin on x-axis and y-axis (x, y) in px :type widget_box_margin: tuple, list :param widget_cursor: Widget cursor if mouse is placed over. If ``None`` the widget don't change the cursor :type widget_cursor: int, :py:class:`pygame.cursors.Cursor`, None :param widget_font: Widget font path or name :type widget_font: str, :py:class:`pygame.font.Font`, :py:class:`pathlib.Path` :param widget_font_antialias: Widget font renders with antialiasing :type widget_font_antialias: bool :param widget_font_background_color: Widget font background color. If ``None`` the value will be the same as ``background_color`` if it is a color object and if ``widget_font_background_color_from_menu`` is ``True`` and ``widget_background_color`` is ``None`` :type widget_font_background_color: tuple, list, str, int, :py:class:`pygame.Color`, None :param widget_font_background_color_from_menu: Use Menu background color as font background color. Disabled by default :type widget_font_background_color_from_menu: bool :param widget_font_color: Color of the font :type widget_font_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_font_shadow: Indicate if the widget font shadow is enabled :type widget_font_shadow: bool :param widget_font_shadow_color: Color of the widget font shadow :type widget_font_shadow_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_font_shadow_offset: Offset of the widget font shadow in px :type widget_font_shadow_offset: int :param widget_font_shadow_position: Position of the widget font shadow. See :py:mod:`pygame_menu.locals` :type widget_font_shadow_position: str :param widget_font_size: Font size :type widget_font_size: int :param widget_margin: Horizontal and vertical margin of each element in Menu in px :type widget_margin: tuple, list :param widget_padding: Padding of the widget according to CSS rules. It can be a single digit, or a tuple of 2, 3, or 4 elements. Padding modifies widget width/height :type widget_padding: int, float, tuple, list :param widget_offset: x-axis and y-axis (x, y) offset of widgets within Menu in px respect to top-left corner. If value less than ``1`` use percentage of width/height. It cannot be a negative value :type widget_offset: tuple, list :param widget_selection_effect: Widget selection effect object. This is visual-only, the selection properties does not affect widget height/width :type widget_selection_effect: :py:class:`pygame_menu.widgets.core.Selection`, None :param widget_shadow_aa: Widget shadow antialiasing factor, default is x4 :type widget_shadow_aa: int :param widget_shadow_color: The color of the widget shadow :type widget_shadow_color: tuple, list, str, int, :py:class:`pygame.Color` :param widget_shadow_radius: The border radius of the widget shadow :type widget_shadow_radius: int :param widget_shadow_type: The shadow type, it can be 'rectangular' or 'ellipse' :type widget_shadow_type: str :param widget_shadow_width: The width of the shadow in px :type widget_shadow_width: int :param widget_tab_size: Widget tab size :type widget_tab_size: int :param widget_url_color: Color of url text links :type widget_url_color: tuple, list, str, int, :py:class:`pygame.Color` """ _disable_validation: bool background_color: ColorType | BaseImage border_color: ColorType | BaseImage cursor_color: ColorType cursor_selection_color: ColorType cursor_switch_ms: NumberType focus_background_color: ColorType fps: NumberType readonly_color: ColorType readonly_selected_color: ColorType scrollarea_outer_margin: Tuple2NumberType scrollarea_position: str scrollbar_color: ColorType scrollbar_cursor: CursorType # type: ignore scrollbar_shadow: bool scrollbar_shadow_color: ColorType scrollbar_shadow_offset: int scrollbar_shadow_position: str scrollbar_slider_color: ColorType scrollbar_slider_hover_color: ColorType scrollbar_slider_pad: NumberType scrollbar_thick: int selection_color: ColorType surface_clear_color: ColorType title: bool title_background_color: ColorType title_bar_modify_scrollarea: bool title_bar_style: int title_close_button: bool title_close_button_background_color: ColorType title_close_button_cursor: CursorType # type: ignore title_fixed: bool title_floating: bool title_font: FontType title_font_antialias: bool title_font_color: ColorType title_font_shadow: bool title_font_shadow_color: ColorType title_font_shadow_offset: int title_font_shadow_position: str title_font_size: int title_offset: Tuple2NumberType title_updates_pygame_display: bool widget_alignment: str widget_alignment_ignore_scrollbar_thickness: bool widget_background_color: ColorType | BaseImage | None widget_background_inflate: Tuple2IntType widget_background_inflate_to_selection: bool widget_border_color: ColorType widget_border_inflate: Tuple2IntType widget_border_position: WidgetBorderPositionType widget_border_width: int widget_box_arrow_color: ColorType widget_box_arrow_margin: Tuple3IntType widget_box_background_color: ColorType widget_box_border_color: ColorType widget_box_border_width: int widget_box_inflate: Tuple2IntType widget_box_margin: Tuple2NumberType widget_cursor: CursorType # type: ignore widget_font: FontType widget_font_antialias: str widget_font_background_color: ColorType | None widget_font_background_color_from_menu: bool widget_font_color: ColorType widget_font_shadow: bool widget_font_shadow_color: ColorType widget_font_shadow_offset: NumberType widget_font_shadow_position: str widget_font_size: int widget_margin: Tuple2NumberType widget_offset: Tuple2NumberType widget_padding: PaddingType widget_selection_effect: pygame_menu.widgets.core.Selection widget_shadow_aa: int widget_shadow_color: ColorType widget_shadow_radius: int widget_shadow_type: str widget_shadow_width: int widget_tab_size: int widget_url_color: ColorType def __init__(self, **kwargs) -> None: # Menu general self.background_color = self._get( kwargs, "background_color", "color_image", (220, 220, 220) ) self.border_color = self._get(kwargs, "border_color", "color_image_none") self.border_width = self._get(kwargs, "border_width", int, 0) 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_alignment_ignore_scrollbar_thickness = self._get( kwargs, "widget_alignment_ignore_scrollbar_thickness", bool, False ) 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_shadow_aa = self._get(kwargs, "widget_shadow_aa", int, 4) self.widget_shadow_color = self._get( kwargs, "widget_shadow_color", "color", (0, 0, 0) ) self.widget_shadow_radius = self._get(kwargs, "widget_shadow_radius", int, 0) self.widget_shadow_type = self._get( kwargs, "widget_shadow_type", str, WIDGET_SHADOW_TYPE_RECTANGULAR ) self.widget_shadow_width = self._get(kwargs, "widget_shadow_width", int, 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) ) # Upon this, no more kwargs should exist, raise exception if there's more for invalid_keyword in kwargs.keys(): raise ValueError(f"parameter Theme.{invalid_keyword} does not exist") # Test purpose only, if True disables any validation self._disable_validation = False
[docs] def validate(self) -> Theme: """ Validate the values of the theme. If there's an invalid parameter throws an ``AssertionError``. This function also converts all lists to tuples. This is done because lists are mutable. :return: Self reference """ if self._disable_validation: return self # Boolean asserts assert isinstance(self.scrollbar_shadow, bool) assert isinstance(self.title_bar_modify_scrollarea, bool) assert isinstance(self.title_close_button, bool) assert isinstance(self.title_font_antialias, bool) assert isinstance(self.title_font_shadow, bool) assert isinstance(self.widget_font_antialias, bool) assert isinstance(self.widget_font_background_color_from_menu, bool) assert isinstance(self.widget_font_shadow, bool) # Value type checks assert_alignment(self.widget_alignment) assert_cursor(self.scrollbar_cursor) assert_cursor(self.title_close_button_cursor) assert_cursor(self.widget_cursor) assert_font(self.title_font) assert_font(self.widget_font) assert_position(self.scrollbar_shadow_position) assert_position(self.title_font_shadow_position) assert_position(self.widget_font_shadow_position) assert_position_vector(self.widget_border_position) assert _check_menubar_style(self.title_bar_style) assert get_scrollbars_from_position(self.scrollarea_position) is not None # Check selection effect if None if self.widget_selection_effect is None: self.widget_selection_effect = NoneSelection() assert isinstance(self.border_width, int) assert isinstance(self.cursor_switch_ms, NumberInstance) assert isinstance(self.fps, NumberInstance) assert isinstance(self.scrollbar_shadow_offset, int) assert isinstance(self.scrollbar_slider_pad, NumberInstance) assert isinstance(self.scrollbar_thick, int) assert isinstance(self.title, bool) assert isinstance(self.title_fixed, bool) assert isinstance(self.title_floating, bool) assert isinstance(self.title_font_shadow_offset, int) assert isinstance(self.title_font_size, int) assert isinstance(self.title_updates_pygame_display, bool) assert isinstance(self.widget_background_inflate_to_selection, bool) assert isinstance(self.widget_border_width, int) assert isinstance(self.widget_box_border_width, int) assert isinstance(self.widget_font_shadow_offset, int) assert isinstance(self.widget_font_size, int) assert isinstance(self.widget_padding, PaddingInstance) assert isinstance(self.widget_selection_effect, Selection) assert isinstance(self.widget_shadow_aa, int) assert isinstance(self.widget_shadow_radius, int) assert isinstance(self.widget_shadow_width, int) assert isinstance(self.widget_tab_size, int) # Format colors, this converts all color lists to tuples automatically, # if it is an image, return the same object self.background_color = self._format_color_opacity(self.background_color) self.border_color = self._format_color_opacity(self.border_color, none=True) self.cursor_color = self._format_color_opacity(self.cursor_color) self.cursor_selection_color = self._format_color_opacity( self.cursor_selection_color ) self.focus_background_color = self._format_color_opacity( self.focus_background_color ) self.readonly_color = self._format_color_opacity(self.readonly_color) self.readonly_selected_color = self._format_color_opacity( self.readonly_selected_color ) self.scrollbar_color = self._format_color_opacity(self.scrollbar_color) self.scrollbar_shadow_color = self._format_color_opacity( self.scrollbar_shadow_color ) self.scrollbar_slider_color = self._format_color_opacity( self.scrollbar_slider_color ) self.scrollbar_slider_hover_color = self._format_color_opacity( self.scrollbar_slider_hover_color ) self.selection_color = self._format_color_opacity(self.selection_color) self.surface_clear_color = self._format_color_opacity(self.surface_clear_color) self.title_background_color = self._format_color_opacity( self.title_background_color ) self.title_close_button_background_color = self._format_color_opacity( self.title_close_button_background_color ) self.title_font_color = self._format_color_opacity(self.title_font_color) self.title_font_shadow_color = self._format_color_opacity( self.title_font_shadow_color ) self.widget_background_color = self._format_color_opacity( self.widget_background_color, none=True ) self.widget_border_color = self._format_color_opacity(self.widget_border_color) self.widget_box_arrow_color = self._format_color_opacity( self.widget_box_arrow_color ) self.widget_box_background_color = self._format_color_opacity( self.widget_box_background_color ) self.widget_box_border_color = self._format_color_opacity( self.widget_box_border_color ) self.widget_font_background_color = self._format_color_opacity( self.widget_font_background_color, none=True ) self.widget_font_color = self._format_color_opacity(self.widget_font_color) self.widget_font_shadow_color = self._format_color_opacity( self.widget_font_shadow_color ) self.widget_shadow_color = self._format_color_opacity(self.widget_shadow_color) self.widget_url_color = self._format_color_opacity(self.widget_url_color) # List to tuple self.scrollarea_outer_margin = self._vec_to_tuple( # type: ignore self.scrollarea_outer_margin, 2, NumberInstance ) self.title_offset = self._vec_to_tuple(self.title_offset, 2, NumberInstance) # type: ignore self.widget_background_inflate = self._vec_to_tuple( # type: ignore self.widget_background_inflate, 2, int ) self.widget_border_inflate = self._vec_to_tuple( # type: ignore self.widget_border_inflate, 2, int ) self.widget_box_arrow_margin = self._vec_to_tuple( # type: ignore self.widget_box_arrow_margin, 3, int ) self.widget_box_inflate = self._vec_to_tuple(self.widget_box_inflate, 2, int) # type: ignore self.widget_box_margin = self._vec_to_tuple( # type: ignore self.widget_box_margin, 2, NumberInstance ) self.widget_margin = self._vec_to_tuple(self.widget_margin, 2, NumberInstance) # type: ignore if isinstance(self.widget_padding, VectorInstance): self.widget_padding = self._vec_to_tuple(self.widget_padding) assert 2 <= len(self.widget_padding) <= 4, ( "widget padding tuple length must be 2, 3 or 4" ) for p in self.widget_padding: assert isinstance(p, NumberInstance), ( "each padding element must be numeric (integer or float)" ) assert p >= 0, "all padding elements must be equal or greater than zero" else: assert self.widget_padding >= 0, "padding cannot be a negative number" self.widget_offset = self._vec_to_tuple(self.widget_offset, 2, NumberInstance) # type: ignore # Check sizes assert self.border_width >= 0, "border width must be equal or greater than zero" assert ( self.scrollarea_outer_margin[0] >= 0 and self.scrollarea_outer_margin[1] >= 0 ), "scroll area outer margin must be equal or greater than zero on both axis" assert self.widget_offset[0] >= 0 and self.widget_offset[1] >= 0, ( "widget offset must be equal or greater than zero" ) assert ( self.widget_background_inflate[0] >= 0 and self.widget_background_inflate[1] >= 0 ), "widget background inflate must be equal or greater than zero on both axis" assert ( self.widget_border_inflate[0] >= 0 and self.widget_border_inflate[1] >= 0 ), "widget border inflate must be equal or greater than zero on both axis" assert self.widget_box_inflate[0] >= 0 and self.widget_box_inflate[1] >= 0, ( "widget box inflate inflate must be equal or greater than zero on both axis" ) assert self.widget_shadow_width >= 0 and self.widget_shadow_radius >= 0, ( "widget shadow width and radius must be equal or greater than zero" ) # Check shadow type assert self.widget_shadow_type in ( WIDGET_SHADOW_TYPE_ELLIPSE, WIDGET_SHADOW_TYPE_RECTANGULAR, ), "widget shadow type must be ellipse or rectangular" assert self.cursor_switch_ms > 0, "cursor switch ms must be greater than zero" assert self.fps >= 0, "fps must be equal or greater than zero" assert self.scrollbar_shadow_offset > 0, ( "scrollbar shadow offset must be greater than zero" ) assert self.scrollbar_slider_pad >= 0, ( "slider pad must be equal or greater than zero" ) assert self.scrollbar_thick > 0, "scrollbar thickness must be greater than zero" assert self.title_font_size > 0, "title font size must be greater than zero" assert self.widget_border_width >= 0, ( "widget border width must be equal or greater than zero" ) assert self.widget_box_border_width >= 0, ( "widget border box width must be equal or greater than zero" ) assert self.widget_font_shadow_offset > 0, ( "widget font shadow offset must be greater than zero" ) assert self.widget_font_size > 0, "widget font size must be greater than zero" assert self.widget_shadow_aa >= 1, ( "widget shadow antialiasing must be equal or greater than one" ) assert self.widget_tab_size >= 0, ( "widget tab size must be equal or greater than zero" ) # Color asserts assert self.focus_background_color[3] != 0, ( "focus background color cannot be fully transparent, suggested opacity between 1 and 255" ) return self
[docs] def set_background_color_opacity(self, opacity: float) -> Theme: """ Modify the Menu background color with given opacity. :param opacity: Opacity value, from ``0`` (transparent) to ``1`` (opaque) :return: Self reference """ self.background_color = assert_color(self.background_color) assert isinstance(opacity, NumberInstance) assert 0 <= opacity <= 1, ( "opacity must be a number between 0 (transparent) and 1 (opaque)" ) self.background_color = ( self.background_color[0], self.background_color[1], self.background_color[2], int(float(opacity) * 255), ) return self
@staticmethod def _vec_to_tuple( obj: tuple | list, check_length: int = 0, check_instance: type = Any ) -> tuple: """ Return a tuple from a list or tuple object. :param obj: Object :param check_length: Check length if not zero :return: Tuple """ if isinstance(obj, tuple): v = obj elif isinstance(obj, list): v = tuple(obj) else: if check_length == 0: raise ValueError("object is not a vector") raise ValueError(f"object is not a vector of length {check_length}") if check_length > 0: if len(v) != check_length: raise ValueError(f"object is not a vector of length {check_length}") if check_instance is not Any: for i in v: assert isinstance(i, check_instance), ( f"{i} element of tuple {v} is not {check_instance} instance" ) return v
[docs] def copy(self) -> Theme: """ Creates a deep copy of the object. :return: Copied theme """ self.validate() return copy.deepcopy(self)
def __copy__(self) -> Theme: """ Copy method. :return: Copied theme """ return self.copy() @staticmethod def _format_color_opacity( color: ColorInputType | BaseImage | None, none: bool = False ) -> ColorType | BaseImage | None: """ Adds opacity to a 3 channel color. (R,G,B) -> (R,G,B,A) if the color has not an alpha channel. Also updates the opacity to a number between ``0`` (transparent) and ``255`` (opaque). Color may be an Image, so if this is the case return the same object. - If the color is a list, return a tuple. - If the color is ``None``, return ``None`` if ``None`` is True. :param color: Color object :param none: If ``True`` Color can be ``None`` :return: Color in the same format """ if isinstance(color, BaseImage): return color elif color is None and none: return color color = format_color(color) if isinstance(color, VectorInstance): assert_color(color) if len(color) == 4: if isinstance(color, tuple): return color return tuple(color) # type: ignore elif len(color) == 3: color = color[0], color[1], color[2], 255 else: raise ValueError( f"invalid color type {color}, only tuple or list are valid" ) return color @staticmethod def _get( params: dict[str, Any], key: str, allowed_types: type | str | list[type] | tuple[type, ...] | None = 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 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), ( f'allowed type "{val_type}" is not a type-class' ) other_types.append(val_type) # Check other types if len(other_types) > 0: others = tuple(other_types) assert isinstance(value, others), ( f"Theme.{key} type shall be in {others} types (got {type(value)})" ) return value
THEME_DEFAULT = Theme() THEME_DARK = Theme( background_color=(40, 41, 35), cursor_color=(255, 255, 255), cursor_selection_color=(80, 80, 80, 120), scrollbar_color=(39, 41, 42), scrollbar_slider_color=(65, 66, 67), scrollbar_slider_hover_color=(90, 89, 88), selection_color=(255, 255, 255), title_background_color=(47, 48, 51), title_font_color=(215, 215, 215), widget_font_color=(200, 200, 200), ) THEME_BLUE = Theme( background_color=(228, 230, 246), scrollbar_shadow=True, scrollbar_slider_color=(150, 200, 230), scrollbar_slider_hover_color=(123, 173, 202), scrollbar_slider_pad=2, selection_color=(100, 62, 132), title_background_color=(62, 149, 195), title_font_color=(228, 230, 246), title_font_shadow=True, widget_font_color=(61, 170, 220), ) THEME_GREEN = Theme( background_color=(186, 214, 177), scrollbar_slider_color=(125, 121, 114), scrollbar_slider_hover_color=(100, 96, 90), scrollbar_slider_pad=2, selection_color=(125, 121, 114), title_background_color=(125, 121, 114), title_font_color=(228, 230, 246), widget_font_color=(255, 255, 255), ) THEME_ORANGE = Theme( background_color=(228, 100, 36), selection_color=(255, 255, 255), title_background_color=(170, 65, 50), widget_font_color=(0, 0, 0), widget_font_size=30, ) THEME_SOLARIZED = Theme( background_color=(239, 231, 211), cursor_color=(0, 0, 0), cursor_selection_color=(146, 160, 160, 120), selection_color=(207, 62, 132), title_background_color=(4, 47, 58), title_font_color=(38, 158, 151), widget_font_color=(102, 122, 130), )