Source code for pygame_menu.widgets.widget.dropselect

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

DROPSELECT
Drop select widget. This is similar to HTML selects, it can contain many items
(options) to select. The selection is unique.
"""

from __future__ import annotations

__all__ = ["DropSelect", "DropSelectManager"]

import math
from abc import ABC
from typing import TYPE_CHECKING, Any

import pygame

from pygame_menu._types import (
    CallbackType,
    ColorInputType,
    ColorType,
    CursorInputType,
    CursorType,
    EventVectorType,
    NumberInstance,
    NumberType,
    PaddingInstance,
    PaddingType,
    Tuple2IntType,
    Tuple2NumberType,
    Tuple3IntType,
    Tuple4IntType,
)
from pygame_menu.font import FontType, assert_font, get_font
from pygame_menu.locals import (
    FINGERDOWN,
    FINGERUP,
    ORIENTATION_VERTICAL,
    POSITION_NORTHWEST,
    POSITION_SOUTHEAST,
)
from pygame_menu.utils import (
    assert_color,
    assert_cursor,
    assert_position,
    assert_vector,
    check_key_pressed_valid,
    get_finger_pos,
    make_surface,
    parse_padding,
    uuid4,
)
from pygame_menu.widgets.core.widget import (
    AbstractWidgetManager,
    Widget,
    WidgetTransformationNotImplemented,
)
from pygame_menu.widgets.widget.button import Button
from pygame_menu.widgets.widget.frame import Frame
from pygame_menu.widgets.widget.selector import check_selector_items

if TYPE_CHECKING:
    from collections.abc import Callable

    import pygame_menu


# noinspection PyProtectedMember
[docs] class DropSelect(Widget): """ Drop select is a selector within a Frame. This drops a vertical frame if requested. Drop select can contain selectable items (options), only 1 can be selected. The items of the DropSelect are: .. code-block:: python items = [('Item1', a, b, c...), ('Item2', d, e, f...)] The callbacks receive the current selected item, its index in the list, the associated arguments, and all unknown keyword arguments, where ``selected_item=widget.get_value()`` and ``selected_index=widget.get_index()``: .. code-block:: python onchange((selected_item, selected_index), a, b, c..., **kwargs) onreturn((selected_item, selected_index), a, b, c..., **kwargs) For example, if ``selected_index=0`` then ``selected_item=('Item1', a, b, c...)``. .. note:: DropSelect only accepts translation transformation. :param title: Drop select title :param items: Items of the drop select :param dropselect_id: ID of the drop select :param default: Index of default item to display. If ``None`` no item is selected :param onchange: Callback when changing the drop select item :param onreturn: Callback when pressing return (apply) on the selected item :param onselect: Function when selecting the widget :param open_middle: If ``True`` the selection box is opened in the middle of the menu :param placeholder: Text shown if no option is selected yet :param placeholder_add_to_selection_box: If ``True`` adds the placeholder button to the selection box :param scrollbar_color: Scrollbar color :param scrollbar_cursor: Cursor of the scrollbars if mouse is placed over. By default, is ``None`` :param scrollbar_shadow: Indicate if a shadow is drawn on each scrollbar :param scrollbar_shadow_color: Color of the shadow of each scrollbar :param scrollbar_shadow_offset: Offset of the scrollbar shadow in px :param scrollbar_shadow_position: Position of the scrollbar shadow. See :py:mod:`pygame_menu.locals` :param scrollbar_slider_color: Color of the sliders :param scrollbar_slider_hover_color: Color of the slider if hovered or clicked :param scrollbar_slider_pad: Space between slider and scrollbars borders in px :param scrollbar_thick: Scrollbar thickness in px :param scrollbars: Scrollbar position. See :py:mod:`pygame_menu.locals` :param selection_box_arrow_color: Selection box arrow color :param selection_box_arrow_margin: Selection box arrow margin (left, right, vertical) in px :param selection_box_bgcolor: Selection box background color :param selection_box_border_color: Selection box border color :param selection_box_border_width: Selection box border width :param selection_box_height: Selection box height, counted as how many options are packed before showing scroll :param selection_box_inflate: Selection box inflate on x-axis and y-axis (x, y) in px :param selection_box_margin: Selection box on x-axis and y-axis (x, y) margin from title in px :param selection_box_text_margin: Selection box text margin (left) in px :param selection_box_width: Selection box width in px. If ``0`` compute automatically to fit placeholder :param selection_infinite: If ``True`` selection can rotate through bottom/top :param selection_option_border_color: Option border color :param selection_option_border_width: Option border width :param selection_option_cursor: Option cursor. If ``None`` use the same cursor as the widget :param selection_option_font: Option font. If ``None`` use the same font as the widget :param selection_option_font_color: Option font color :param selection_option_font_size: Option font size. If ``None`` use the 100% of the widget font size :param selection_option_padding: Selection padding. See padding styling :param selection_option_selected_bgcolor: Selected option background color :param selection_option_selected_font_color: Selected option font color :param kwargs: Optional keyword arguments """ _drop_frame: Frame | None _index: int _items: list[tuple[Any, ...]] | list[str] _open_bottom: bool _open_middle: bool _opened: bool _option_buttons: list[Button] _option_font: pygame.font.Font | None _placeholder: str _placeholder_add_to_selection_box: bool _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 _scrollbars: str _selection_box_arrow_color: ColorType _selection_box_arrow_margin: Tuple3IntType _selection_box_bgcolor: ColorType _selection_box_border_color: ColorType _selection_box_border_width: int _selection_box_height: int _selection_box_inflate: Tuple2IntType _selection_box_margin: Tuple2IntType _selection_box_text_margin: int _selection_box_width: int _selection_infinite: bool _selection_option_border_color: ColorType _selection_option_border_width: int _selection_option_cursor: CursorType # type: ignore _selection_option_font_style: dict[str, Any] _selection_option_left_space: bool _selection_option_left_space_height_factor: float _selection_option_left_space_margin: Tuple3IntType _selection_option_padding: Tuple4IntType _selection_option_selected_bgcolor: ColorType _title_size: Tuple2IntType def __init__( self, title: Any, items: list[tuple[Any, ...]] | list[str], close_on_apply: bool = True, dropselect_id: str = "", default: int | None = None, 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, scrollbar_color: ColorInputType = (235, 235, 235), scrollbar_cursor: CursorInputType = None, # type: ignore scrollbar_shadow: bool = False, scrollbar_shadow_color: ColorInputType = (0, 0, 0), scrollbar_shadow_offset: int = 2, scrollbar_shadow_position: str = POSITION_NORTHWEST, scrollbar_slider_color: ColorInputType = (200, 200, 200), scrollbar_slider_hover_color: ColorInputType = (170, 170, 170), scrollbar_slider_pad: NumberType = 0, scrollbar_thick: int = 20, scrollbars: str = POSITION_SOUTHEAST, 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_border_color: ColorInputType = (220, 220, 220), selection_option_border_width: int = 1, selection_option_cursor: CursorInputType = None, # type: ignore selection_option_font: FontType | None = None, selection_option_font_color: ColorInputType = (0, 0, 0), selection_option_font_size: int | None = None, selection_option_left_space: bool = False, selection_option_left_space_height_factor: float = 1.0, selection_option_padding: PaddingType = 5, selection_option_selected_bgcolor: ColorInputType = (188, 227, 244), selection_option_selected_font_color: ColorInputType = (0, 0, 0), *args, **kwargs, ) -> None: assert isinstance(default, (int, type(None))) assert isinstance(dropselect_id, str) assert isinstance(items, list) assert isinstance(open_middle, bool) assert isinstance(placeholder, str) assert isinstance(placeholder_add_to_selection_box, bool) # Check items list check_selector_items(items) if default is None: default = -1 assert default >= -1, "default position must be equal or greater than zero" assert default < len(items), ( "default position should be lower than number of values" ) assert isinstance(dropselect_id, str), "id must be a string" assert isinstance(default, int), "default must be an integer" # Check styling assert isinstance(scrollbar_shadow, bool) assert isinstance(scrollbar_shadow_offset, int) and scrollbar_shadow_offset >= 0 assert ( isinstance(scrollbar_slider_pad, NumberInstance) and scrollbar_slider_pad >= 0 ) assert isinstance(scrollbar_thick, int) and scrollbar_thick >= 0 assert ( isinstance(selection_box_border_width, int) and selection_box_border_width >= 0 ) assert isinstance(selection_box_height, int) and selection_box_height >= 1 assert ( isinstance(selection_box_text_margin, int) and selection_box_text_margin >= 0 ) assert isinstance(selection_box_width, int) and selection_box_width >= 0 assert isinstance(selection_infinite, bool) assert ( isinstance(selection_option_border_width, int) and selection_option_border_width >= 0 ) assert isinstance(selection_option_padding, PaddingInstance) assert_cursor(scrollbar_cursor) assert_cursor(selection_option_cursor) assert_position(scrollbar_shadow_position) assert_position(scrollbars) assert_vector(selection_box_arrow_margin, 3, int) assert_vector(selection_box_inflate, 2, int) assert_vector(selection_box_margin, 2) scrollbar_color = assert_color(scrollbar_color) scrollbar_shadow_color = assert_color(scrollbar_shadow_color) scrollbar_slider_color = assert_color(scrollbar_slider_color) scrollbar_slider_hover_color = assert_color(scrollbar_slider_hover_color) selection_box_arrow_color = assert_color(selection_box_arrow_color) selection_box_bgcolor = assert_color(selection_box_bgcolor) selection_box_border_color = assert_color(selection_box_border_color) selection_option_border_color = assert_color(selection_option_border_color) selection_option_font_color = assert_color(selection_option_font_color) selection_option_selected_bgcolor = assert_color( selection_option_selected_bgcolor ) selection_option_selected_font_color = assert_color( selection_option_selected_font_color ) if selection_option_font is not None: assert_font(selection_option_font) if selection_option_font_size is not None: assert ( isinstance(selection_option_font_size, int) and selection_option_font_size > 0 ) super().__init__( onchange=onchange, onreturn=onreturn, onselect=onselect, title=title, widget_id=dropselect_id, args=args, kwargs=kwargs, accept_events=True, selection_effect_draw_post=False, ) self._close_on_apply = close_on_apply self._default_value = default self._drop_frame = None self._index = default self._items = items.copy() self._open_bottom = True self._open_middle = open_middle self._placeholder = placeholder self._placeholder_add_to_selection_box = placeholder_add_to_selection_box self._title_size = (0, 0) # If True adds a space equals to the height of the option at left, used for # drawing some options (for example, ticks, boxes, etc.) self._selection_option_left_space = selection_option_left_space self._selection_option_left_space_height_factor = ( selection_option_left_space_height_factor ) self._selection_option_left_space_margin = (0, 0, 0) # left, right, top # Style self._option_font = None self._scrollbar_color = scrollbar_color self._scrollbar_cursor = scrollbar_cursor self._scrollbar_shadow = scrollbar_shadow self._scrollbar_shadow_color = scrollbar_shadow_color self._scrollbar_shadow_offset = scrollbar_shadow_offset self._scrollbar_shadow_position = scrollbar_shadow_position self._scrollbar_slider_color = scrollbar_slider_color self._scrollbar_slider_hover_color = scrollbar_slider_hover_color self._scrollbar_slider_pad = scrollbar_slider_pad self._scrollbar_thick = scrollbar_thick self._scrollbars = scrollbars self._selection_box_arrow_color = selection_box_arrow_color self._selection_box_arrow_margin = selection_box_arrow_margin self._selection_box_bgcolor = selection_box_bgcolor self._selection_box_border_color = selection_box_border_color self._selection_box_border_width = selection_box_border_width self._selection_box_height = selection_box_height self._selection_box_inflate = selection_box_inflate self._selection_box_margin = ( int(selection_box_margin[0]), int(selection_box_margin[1]), ) self._selection_box_text_margin = selection_box_text_margin self._selection_box_width = selection_box_width self._selection_infinite = selection_infinite self._selection_option_border_color = selection_option_border_color self._selection_option_border_width = selection_option_border_width self._selection_option_cursor = selection_option_cursor self._selection_option_padding = parse_padding(selection_option_padding) self._selection_option_selected_bgcolor = selection_option_selected_bgcolor self._selection_option_font_style = { "color": selection_option_font_color, "color_selected": selection_option_selected_font_color, "name": selection_option_font, "size": selection_option_font_size, }
[docs] def set_default_value(self, index: int) -> DropSelect: self._default_value = index return self
def _apply_font(self) -> None: # Compute title size title_render = self._font.size(self._title) w = int( title_render[0] + self._selection_box_margin[0] - self._selection_box_inflate[0] / 2 ) h = int( title_render[1] + self._selection_box_inflate[1] / 2 - self._selection_box_border_width ) self._title_size = (w, h) # Load option font if self._selection_option_font_style["name"] is None: self._selection_option_font_style["name"] = self._font_name if self._selection_option_font_style["size"] is None: self._selection_option_font_style["size"] = int(self._font_size) self._option_font = get_font( self._selection_option_font_style["name"], self._selection_option_font_style["size"], ) if self._selection_box_width == 0: f = self._render_option_string(self._placeholder) h = self._render_string( self._title, self.get_font_color_status() ).get_height() self._selection_box_width = int( f.get_width() + self._selection_box_arrow_margin[0] + self._selection_box_arrow_margin[1] + h - h / 4 + 2 * self._selection_box_border_width ) def _make_selection_drop(self) -> DropSelect: """ Make the selection drop box. This function creates the box UI and adds the selection options. Should be called after the widget is configured. :return: Self reference """ from pygame_menu._scrollarea import get_scrollbars_from_position if not self.configured: raise RuntimeError( f"{self.get_class_id()} must be configured before creating selection drop" ) # Create options buttons total_height: int = 0 max_height: int = 0 frame_width: int = self._selection_box_width + self._selection_box_inflate[0] self._option_buttons = [] # Add placeholder button if self._placeholder_add_to_selection_box: self._items.insert(0, (self._placeholder, -1)) for opt_id in range(len(self._items)): option = self._items[opt_id] btn = Button( option[0], onreturn=self._click_option, index=opt_id - (1 if self._placeholder_add_to_selection_box else 0), button_id=self._id + "+option-" + uuid4(short=True), ) btn.set_background_color(color=self._selection_box_bgcolor) btn.set_border( width=self._selection_option_border_width, color=self._selection_option_border_color, ) btn.set_controls( joystick=False, # Only drop select controls the joystick behavior mouse=self._mouse_enabled, touchscreen=self._touchscreen_enabled, keyboard=False, # Only drop select controls the keyboard behavior ) btn.set_cursor( # This feature does not work properly cursor=self._selection_option_cursor ) font_c = self._selection_option_font_style["color"] if self._placeholder_add_to_selection_box: font_color = font_c if opt_id != 0 else self._font_readonly_color else: font_color = font_c btn.set_font( antialias=self._font_antialias, background_color=None, color=font_color, font=self._selection_option_font_style["name"], font_size=self._selection_option_font_style["size"], readonly_color=self._font_readonly_color, readonly_selected_color=self._font_readonly_selected_color, selected_color=self._font_selected_color, ) btn.set_padding(padding=self._selection_option_padding) btn.add_self_to_kwargs("btn") btn.set_tab_size(self._tab_size) btn.configured = True btn.set_menu(self._menu) btn._update__repr___(self) self._option_buttons.append(btn) bh = btn.get_height() - self._selection_option_border_width if self._selection_option_left_space and not ( self._placeholder_add_to_selection_box and opt_id == 0 ): prev_pad = btn._padding # top, right, bottom, left prev_pad_t: Tuple4IntType = btn._padding_transform dh = int( btn.get_height(apply_padding=False) * self._selection_option_left_space_height_factor ) btn.set_attribute("left_space_height", dh) m = self._selection_option_left_space_margin btn._padding = ( prev_pad[0], prev_pad[1], prev_pad[2], prev_pad[3] + dh + m[0] + m[1], ) btn._padding_transform = ( prev_pad_t[0], prev_pad_t[1], prev_pad_t[2], prev_pad_t[3] + dh + m[0] + m[1], ) total_height += bh if opt_id + 1 <= self._selection_box_height: max_height += bh max_width = frame_width if total_height != max_height: max_width -= self._scrollbar_thick frame_width -= self._scrollbar_thick total_height -= self._selection_box_border_width max_height -= self._selection_box_border_width elif total_height > 0: total_height += self._selection_box_border_width max_height += self._selection_box_border_width for btn in self._option_buttons: max_width = max( max_width, btn.get_width() - self._selection_option_border_width ) # Update options rect delta width for btn in self._option_buttons: btn._rect_size_delta = (max_width - btn.get_width(), 0) # Pop placeholder if self._placeholder_add_to_selection_box: self._items.pop(0) placeholder_button = self._option_buttons.pop(0) else: placeholder_button = None # Unpack previous frame (if exists) if self._drop_frame is not None: self._drop_frame.set_menu(None) # Create frame self._drop_frame = Frame( max_width, max(total_height, 1), ORIENTATION_VERTICAL, frame_id=self._id + "+frame-" + uuid4(short=True), ) self._drop_frame._accepts_title = False self._drop_frame._menu_can_be_none_pack = True self._drop_frame.hide() self._drop_frame.set_background_color(color=self._selection_box_bgcolor) self._drop_frame.set_border( width=self._selection_box_border_width, color=self._selection_box_border_color, ) self._drop_frame.set_scrollarea(self._scrollarea) self._drop_frame.relax() self._drop_frame.configured = True self._drop_frame.set_tab_size(self._tab_size) self._drop_frame._update__repr___(self) self._drop_frame.set_controls( joystick=self._joystick_enabled, mouse=self._mouse_enabled, touchscreen=self._touchscreen_enabled, keyboard=self._keyboard_enabled, ) if total_height > 0: # Menu is needed while creating the scrollarea, as that reference # is later passed to the scrollbars, which need menu for touchscreen # events self._drop_frame._menu = self._menu self._drop_frame.make_scrollarea( max_width=frame_width, max_height=max_height, scrollarea_color=self._selection_box_arrow_color, scrollbar_color=self._scrollbar_color, scrollbar_cursor=self._scrollbar_cursor, scrollbar_shadow_color=self._scrollbar_shadow_color, scrollbar_shadow_offset=self._scrollbar_shadow_offset, scrollbar_shadow_position=self._scrollbar_shadow_position, scrollbar_shadow=self._scrollbar_shadow, scrollbar_slider_color=self._scrollbar_slider_color, scrollbar_slider_hover_color=self._scrollbar_slider_hover_color, scrollbar_slider_pad=self._scrollbar_slider_pad, scrollbar_thick=self._scrollbar_thick, scrollbars=get_scrollbars_from_position(self._scrollbars), ) # Menu is formerly set in frame. That method adds the frame to update frames # depending on if frame is scrollable or not self._drop_frame.set_menu(self._menu) self._drop_frame.set_scrollarea(self._scrollarea) if self._frame is not None: self._drop_frame.set_frame(self._frame) # Set sizing properties if total_height > 0: add_scrollbar = self._scrollbar_thick if max_width != frame_width else 0 border_w = ( self._selection_box_border_width if total_height != max_height else 0 ) self._drop_frame.set_attribute( "height", max_height + add_scrollbar - border_w ) self._drop_frame.set_attribute("width", frame_width) else: self._drop_frame.set_attribute("height", 0) self._drop_frame.set_attribute("width", 0) if self._placeholder_add_to_selection_box: placeholder_button.hide() margin_width = ( self._selection_box_border_width if total_height == max_height else 0 ) self._drop_frame.set_attribute("extra_margin", margin_width) self._drop_frame.set_attribute("placeholder_button", placeholder_button) # Pack options if self._placeholder_add_to_selection_box: self._drop_frame.pack(placeholder_button) for opt in self._option_buttons: self._drop_frame.pack(opt, margin=(0, -self._selection_option_border_width)) # Update options if index is defined if self._index != -1: self.set_value(self._index) return self def _append_to_menu(self) -> None: self._make_selection_drop()
[docs] def on_remove_from_menu(self) -> DropSelect: if self._drop_frame is not None: self._drop_frame.set_menu(None) return self
[docs] def hide(self) -> DropSelect: super().hide() if self._drop_frame is not None: self._drop_frame.hide() return self
[docs] def show(self) -> DropSelect: super().show() if self.active: self._toggle_drop() return self
[docs] def scrollh(self, value: NumberType) -> DropSelect: """ Scroll drop frame to horizontal value. :param value: Horizontal scroll value, if ``0`` scroll to left; ``1`` scroll to right :return: Self reference """ if self._drop_frame is not None: self._drop_frame.scrollh(value) return self
[docs] def scrollv(self, value: NumberType) -> DropSelect: """ Scroll drop frame to vertical value. :param value: Vertical scroll value, if ``0`` scroll to top; ``1`` scroll to bottom :return: Self reference """ if self._drop_frame is not None: self._drop_frame.scrollv(value) return self
[docs] def get_scroll_value_percentage(self, orientation: str) -> float: """ Get the scroll value in percentage, if ``0`` the scroll is at top/left, ``1`` bottom/right. .. note:: If ScrollArea does not contain such orientation scroll, or frame is not scrollable, ``-1`` is returned. :param orientation: Orientation. See :py:mod:`pygame_menu.locals` :return: Value from ``0`` to ``1`` """ if self._drop_frame is not None: return self._drop_frame.get_scroll_value_percentage(orientation) return -1
[docs] def set_scrollarea(self, scrollarea: pygame_menu._scrollarea.ScrollArea) -> None: super().set_scrollarea(scrollarea) if self._drop_frame is not None: self._drop_frame.set_scrollarea(scrollarea)
[docs] def set_frame(self, frame: pygame_menu.widgets.Frame) -> DropSelect: super().set_frame(frame) if self._drop_frame is not None: self._drop_frame.set_frame(frame) return self
def _click_option(self, index: int, btn: Button) -> None: """ Function triggered after option has been selected or clicked. :param index: Option index within list """ btn.set_attribute("ignore_scroll_to_widget") prev_index = self._index self.set_value(index) if self._index != prev_index and self._index != -1: self.change(*self._items[self._index][1:]) if self._close_on_apply: self.active = False if self._drop_frame is not None: self._drop_frame.hide() btn.remove_attribute("ignore_scroll_to_widget")
[docs] def set_position(self, x: NumberType, y: NumberType) -> DropSelect: super().set_position(x, y) if self._drop_frame is not None: x = self._rect.x y = self._rect.y if not self._open_middle: if self._open_bottom: self._drop_frame.set_position( x + self._title_size[0], y + self._title_size[1] + self.get_attribute("delta_title_height", 0), ) else: self._drop_frame.set_position( x + self._title_size[0], y - self._drop_frame.get_attribute("height") + self._drop_frame.get_attribute("extra_margin"), ) else: self._drop_frame.set_position(*self._compute_position_middle()) for w in self._option_buttons: w._set_position_relative_to_frame() if self._placeholder_add_to_selection_box: placeholder_button: Button = self._drop_frame.get_attribute( "placeholder_button" ) placeholder_button._set_position_relative_to_frame() self._drop_frame.update_position() return self
def scale(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def resize(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def set_max_width(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def set_max_height(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def rotate(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def flip(self, *args, **kwargs) -> DropSelect: raise WidgetTransformationNotImplemented() def _draw(self, surface: pygame.Surface) -> None: surface.blit(self._surface, self._rect.topleft)
[docs] def draw_after_if_selected(self, surface: pygame.Surface | None) -> DropSelect: super().draw_after_if_selected(surface) if self.active and self.is_visible(): self._check_drop_made() if self._open_middle: # Draw drop frame in menu widgets surface assert self._menu is not None, "middle position need menu reference" surface = self._menu._widgets_surface self._drop_frame.draw(surface) self.last_surface = surface return self
def _render_option_string(self, text: str) -> pygame.Surface: """ Render option string surface. :param text: Text to render :return: Option string surface """ color = self._selection_option_font_style["color"] if self.readonly or self._index == -1: color = self._font_readonly_color text = text.replace("\t", " " * self._tab_size) return self._option_font.render(text, self._font_antialias, color) def _get_current_selected_text(self) -> str: """ Return the current selected text. :return: Text """ if self._index == -1: current_selected = self._placeholder else: current_selected = self.get_value()[0][0] return current_selected def _render(self) -> bool | None: if self._option_font is None: return None scroll_v: int = 0 menu_height: int = ( 0 if self._menu is None else self._menu.get_height(widget=True) ) current_selected = self._get_current_selected_text() if not self._render_hash_changed( current_selected, self._selected, self._visible, self._index, self.readonly, self.active, self._open_bottom, scroll_v, menu_height, self._open_middle, len(self._items), self._rect.x, self._rect.y, ): return True title = self._render_string(self._title, self.get_font_color_status()) current = self._render_option_string(current_selected) # Compute virtual inflate to be applied to current rect vi = 0 # Virtual inflate if current.get_height() < title.get_height(): vi = (title.get_height() - current.get_height()) / 2 current_rect_bg = current.get_rect() current_rect_bg.x += title.get_width() + self._selection_box_margin[0] current_rect_bg.y += int( (self._selection_box_inflate[1]) / 2 + vi + self._selection_box_margin[1] ) current_rect_bg.width = self._selection_box_width current_rect_bg = current_rect_bg.inflate( ( int(self._selection_box_inflate[0]), int(self._selection_box_inflate[1] + 2 * vi), ) ) # Compute delta title if height is lower than selection box h = title.get_height() delta_title_height = max(int(math.floor((current_rect_bg.height - h) / 2)), 0) # Create arrows arrow = pygame.Rect( int( title.get_width() + self._selection_box_margin[0] + self._selection_box_width - h ), int( self._selection_box_arrow_margin[2] + (self._selection_box_inflate[1] + vi) / 4 + delta_title_height + self._selection_box_margin[1] ), h, h, ) w = h + self._selection_box_arrow_margin[1] # Check which direction it should open (top, bottom) self._open_bottom = True if self._drop_frame is not None and self._scrollarea is not None: assert self.get_scrollarea().get_world() is not None, ( "scrollarea world is None. The widget have been appended to the Menu?" ) rect = self._rect.copy() rect.width = self._selection_box_width rect.y += delta_title_height rect.x += self._title_size[0] rect.height += self._drop_frame.get_attribute("height") if rect.width != 0 and rect.height != 0: rect_clipped = self._scrollarea.get_world_rect().clip(rect) if rect.height != rect_clipped.height: self._open_bottom = False # Create frame if ( self._drop_frame is not None and self._open_middle and self._menu is not None ): self._drop_frame.set_scrollarea(self._menu.get_scrollarea()) self._drop_frame._frame = None if not self._menu._mouse_motion_selection: self.force_menu_draw_focus = True else: self.force_menu_draw_focus = False delta_title_height_dh = ( math.ceil((current_rect_bg.height - h) / 2) + 2 * self._selection_box_border_width ) if delta_title_height != 0: self.set_attribute("delta_title_height", delta_title_height_dh) arrow_up = ( ( int(arrow.right - w + h / 2 - h / 16), int(arrow.centery - h / 6 - h / 20), ), ( int(arrow.right - w + h / 2 + h / 4 - h / 16), int(arrow.centery + h / 4 - h / 20), ), (int(arrow.right - w + h - h / 16), int(arrow.centery - h / 6 - h / 20)), ) arrow_down = ( ( int(arrow.right - w + h / 2 - h / 16), int(arrow.centery + h / 4 - h / 20), ), ( int(arrow.right - w + h / 2 + h / 4 - h / 16), int(arrow.centery - h / 6 - h / 20), ), (int(arrow.right - w + h - h / 16), int(arrow.centery + h / 4 - h / 20)), ) if not self._open_bottom: if not self.active: arrow_right_pos = arrow_down else: arrow_right_pos = arrow_up else: if not self.active: arrow_right_pos = arrow_up else: arrow_right_pos = arrow_down self._surface = make_surface( title.get_width() + self._selection_box_margin[0] + self._selection_box_width + self._selection_box_inflate[0] / 2 + self._selection_box_border_width, max( title.get_height() + self._selection_box_inflate[1], current_rect_bg.height - self.active * self._selection_box_border_width, ), ) self._surface.blit( title, (0, int(self._selection_box_inflate[1] / 2 + delta_title_height)) ) pygame.draw.rect(self._surface, self._selection_box_bgcolor, current_rect_bg) pygame.draw.rect( self._surface, self._selection_box_border_color, current_rect_bg, self._selection_box_border_width, ) # Crop current max width cropped_current_w = ( self._selection_box_width - self._selection_box_arrow_margin[0] - self._selection_box_arrow_margin[1] - h / 2 - h / 16 - self._selection_box_text_margin ) assert cropped_current_w > 0, ( "there is no left space for text width, try increasing selection_box_width size" ) new_current = make_surface(cropped_current_w, current.get_height()) new_current.blit(current, (0, 0)) # new_current.fill((0, 0, 0)) self._surface.blit( new_current, ( int( title.get_width() + self._selection_box_margin[0] + self._selection_box_text_margin ), int( self._selection_box_inflate[1] / 2 + vi - 1 + self._selection_box_margin[1] ), ), ) if len(self._items) > 0: pygame.draw.polygon( self._surface, self._selection_box_arrow_color, arrow_right_pos ) self._rect.width, self._rect.height = self._surface.get_size() self.force_menu_surface_update() return True
[docs] def get_index(self) -> int: """ Get selected index. :return: Selected index """ return self._index
[docs] def get_value(self) -> tuple[tuple[Any, ...] | str, int]: """ Return the current value of the selected index. :return: Value and index as a tuple, (value, index) """ if self._index == -1: raise ValueError("any item has been selected yet as index is -1") return self._items[self._index], self._index
[docs] def value_changed(self) -> bool: return self._index != self._default_value
def _down(self) -> None: """ Move current selection down. """ if self.readonly: return None elif not self._items: return None elif not self.active: return self._toggle_drop() elif self._index == -1: self.set_value(len(self._items) - 1) else: if self._selection_infinite: self.set_value((self._index - 1) % len(self._items)) else: prev = self._index new = max(0, self._index - 1) if prev == new: return None self.set_value(new) self.change(*self._items[self._index][1:]) self._sound.play_key_add() return None def _up(self) -> None: """ Move current selection up. """ if self.readonly: return None elif not self._items: return None elif not self.active: return self._toggle_drop() elif self._index == -1: self.set_value(0) else: if self._selection_infinite: self.set_value((self._index + 1) % len(self._items)) else: prev = self._index new = min(self._index + 1, len(self._items) - 1) if prev == new: return None self.set_value(new) self.change(*self._items[self._index][1:]) self._sound.play_key_add() return None
[docs] def set_value(self, item: str | int, process_index: bool = False) -> None: """ Set the current value of the widget, selecting the item that matches the text if ``item`` is a string, or the index if ``item`` is an integer. This method raises ``ValueError`` if no item found. For example, if widget item list is ``[['a', 0], ['b', 1], ['a', 2]]``: - *widget*.set_value('a') -> Widget selects the first item (index 0) - *widget*.set_value(2) -> Widget selects the third item (index 2) .. note:: This method does not trigger any event (change). :param item: The item to select, either a string or an integer index. :param process_index: Ignored in this class; used by subclasses. """ if isinstance(item, str): found = False for i in self._items: if i[0] == item: self._index = self._items.index(i) found = True break if not found: raise ValueError(f'no value "{item}" found in drop select') elif isinstance(item, int): assert -1 <= item < len(self._items), ( "item index must be greater than zero and lower than the number " "of items on the drop select" ) self._index = item # Base class ignores process_index # (DropSelectMultiple overrides and uses it) # Update options background selection for b_ind_x in range(len(self._option_buttons)): btn = self._option_buttons[b_ind_x] if b_ind_x == self._index: btn.set_background_color(self._selection_option_selected_bgcolor) btn.update_font( {"color": self._selection_option_font_style["color_selected"]} ) if not self._drop_frame.has_attribute("ignorescroll"): btn.scroll_to_widget(scroll_parent=False) else: btn.set_background_color(self._selection_box_bgcolor) btn.update_font({"color": self._selection_option_font_style["color"]}) # Force render self._render()
[docs] def update_items(self, items: list[tuple[Any, ...]] | list[str]) -> None: """ Update drop select items. .. note:: If the length of the list is different from the previous one, the new index of the select will be the ``-1``, that is, same as the unselected status. :param items: New drop select items; format ``[('Item1', a, b, c...), ('Item2', d, e, f...)]`` """ assert isinstance(items, list) if len(items) > 0: check_selector_items(items) if ( self._index != -1 and len(items) != len(self._items) or (self._index + 1) > len(items) ): self._index = -1 self._default_value = -1 self._items = items.copy() if self._drop_frame is not None: self._drop_frame.set_menu(None) self._drop_frame = None self.active = False self._make_selection_drop()
def _check_drop_made(self) -> None: """ Checks if drop selection has been made. """ if self._drop_frame is None: raise _SelectionDropNotMadeException( f"selection drop has not been made yet. Call {self.get_class_id()}" f"._make_selection_drop() for avoiding this exception" )
[docs] def get_items(self) -> list[tuple[Any, ...]] | list[str]: """ Return a copy of the select items. :return: Select items list """ return self._items.copy()
def _toggle_drop(self) -> None: """ Open drop selection. """ self._check_drop_made() if not self._selected: return elif not self._items: return self.active = not self.active if self.active: if self._drop_frame is not None: self._drop_frame.show() self.scroll_to_widget(scroll_parent=False) else: if self._drop_frame is not None: self._drop_frame.hide() if self.active and self._index != -1: self.set_value(self._index) def _compute_position_middle(self, add_offset: bool = True) -> Tuple2IntType: """ Compute box position if position is in the middle. :param add_offset: Adds offset :return: Position """ assert self._menu is not None, ( "menu cannot be none if the position is in middle (open_middle)" ) self._check_drop_made() if add_offset: offx, offy = self._menu.get_scrollarea().get_offsets() else: offx, offy = 0, 0 w, h = self._menu.get_size() h -= self._menu.get_menubar().get_height() bw, bh = self._selection_box_width, self._drop_frame.get_attribute("height") assert w >= bw, ( f"selection box width ({bw}) cannot be greater than menu width ({w})" ) assert h >= bh, ( f"selection box height ({bh}) cannot be greater than menu height ({h})" ) x = (w - bw) / 2 + offx y = (h - bh) / 2 + offy return x, y
[docs] def get_focus_rect(self) -> pygame.Rect: self._check_drop_made() rect = self.get_rect(apply_padding=False, to_real_position=True) if self.active: rect.width = self._selection_box_width rect.x += self._title_size[0] rect.height += self._drop_frame.get_attribute( "height" ) - self._drop_frame.get_attribute("extra_margin") if not self._open_bottom: rect.y -= self._drop_frame.get_attribute( "height" ) - self._drop_frame.get_attribute("extra_margin") if self._open_middle: x, y = self._compute_position_middle(add_offset=False) rect.x = int(x + self._menu.get_position()[0]) rect.y = int(y + self._menu.get_scrollarea().get_position()[1]) rect.height -= self._rect.height else: rect.width -= self._selection_box_border_width return rect
def _drop_frame_scrolling(self) -> bool: """ Checks if drop frame is scrolling. :return: ``True`` if scrolling """ if ( self._drop_frame is None or self._drop_frame.get_scrollarea(inner=True) is None ): return False return self._drop_frame.get_scrollarea(inner=True).is_scrolling()
[docs] def update(self, events: EventVectorType) -> bool: self.apply_update_callbacks(events) if self.readonly or not self.is_visible(): self._readonly_check_mouseover(events) return False # Check scroll self._check_drop_made() updated = self._drop_frame.update(events) if updated: return True for event in events: if event.type == pygame.KEYDOWN: # Check key is valid if self._ignores_keyboard_nonphysical() and not check_key_pressed_valid( event ): continue # Check mouse over self._check_mouseover(event) # Events keydown = self._keyboard_enabled and event.type == pygame.KEYDOWN joy_hatmotion = self._joystick_enabled and event.type == pygame.JOYHATMOTION joy_axismotion = ( self._joystick_enabled and event.type == pygame.JOYAXISMOTION ) joy_button_down = ( self._joystick_enabled and event.type == pygame.JOYBUTTONDOWN ) # Left button if ( keydown and self._ctrl.move_down(event, self) or joy_hatmotion and self._ctrl.joy_left(event, self) or joy_axismotion and self._ctrl.joy_axis_x_left(event, self) ): if not self.active: continue self._down() return True # Right button elif ( keydown and self._ctrl.move_up(event, self) or joy_hatmotion and self._ctrl.joy_right(event, self) or joy_axismotion and self._ctrl.joy_axis_x_right(event, self) ): if not self.active: continue self._up() return True # Press enter elif ( keydown and self._ctrl.apply(event, self) or joy_button_down and self._ctrl.joy_select(event, self) ): if self.active and self._index >= 0: self._sound.play_key_add() self.apply(*self._items[self._index][1:]) if not ( self.active and (not self._close_on_apply and self._index != -1) ): self._toggle_drop() return True # Press keys which active the drop but not apply elif keydown and self._ctrl.tab(event, self): self._toggle_drop() return True # Close the selection elif keydown and ( self._ctrl.escape(event, self) or self._ctrl.back(event, self) ): if self.active: self._toggle_drop() return True # Click on dropselect; don't consider the mouse wheel (button 4 & 5) elif self.active and ( event.type == pygame.MOUSEBUTTONDOWN and self._mouse_enabled and event.button in (1, 2, 3) or event.type == FINGERDOWN and self._touchscreen_enabled and self._menu is not None and not self._drop_frame_scrolling() ): event_pos = get_finger_pos(self._menu, event) if self._drop_frame.get_rect( apply_padding=False, to_real_position=True ).collidepoint(*event_pos): return True # Click on dropselect; don't consider the mouse wheel (button 4 & 5) elif ( event.type == pygame.MOUSEBUTTONUP and self._mouse_enabled and event.button in (1, 2, 3) or event.type == FINGERUP and self._touchscreen_enabled and self._menu is not None and not self._drop_frame_scrolling() ): # Check for mouse clicks within if self.active: for btn in self._option_buttons: btn.set_attribute("ignore_scroll_to_widget") updated = btn.update([event]) try: btn.remove_attribute("ignore_scroll_to_widget") except IndexError: pass if updated: return True # Get event position based on input type event_pos = get_finger_pos(self._menu, event) # If collides rect = self.get_rect(to_real_position=True, apply_padding=False) if rect.collidepoint(*event_pos): # Check if mouse collides left or right as percentage, use only X coordinate mouse_x, _ = event_pos topleft, _ = rect.topleft dist = mouse_x - ( topleft + self._title_size[0] ) # Distance from title if dist > 0: # User clicked the options, not title self._toggle_drop() return True else: if self.active and not self.get_focus_rect().collidepoint( *event_pos ): self._toggle_drop() return True # Check mousemove # elif event.type == pygame.MOUSEMOTION: # for btn in self._option_buttons: # btn._check_mouseover(event) return False
class _SelectionDropNotMadeException(Exception): """ Exception thrown if drop selection has not been made. """ pass class DropSelectManager(AbstractWidgetManager, ABC): """ DropSelect manager. """ def dropselect( self, title: Any, items: list[tuple[Any, ...]] | list[str], default: int | None = None, dropselect_id: str = "", onchange: CallbackType = None, onreturn: CallbackType = None, onselect: Callable[[bool, Widget, pygame_menu.Menu], Any] | None = None, open_middle: bool = False, placeholder: str = "Select an option", placeholder_add_to_selection_box: bool = True, **kwargs, ) -> pygame_menu.widgets.DropSelect: """ Add a dropselect to the Menu: Drop select is a selector within a Frame. This drops a vertical frame if requested. Drop select can contain selectable items (options), but only one can be selected. The items of the DropSelect are: .. code-block:: python items = [('Item1', a, b, c...), ('Item2', d, e, f...)] The callbacks receive the current selected item, its index in the list, the associated arguments, and all unknown keyword arguments, where ``selected_item=widget.get_value()`` and ``selected_index=widget.get_index()``: .. code-block:: python onchange((selected_item, selected_index), a, b, c..., **kwargs) onreturn((selected_item, selected_index), a, b, c..., **kwargs) For example, if ``selected_index=0`` then ``selected_item=('Item1', a, b, c...)``. 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) - ``align`` (str) – Widget `alignment <https://pygame-menu.readthedocs.io/en/latest/_source/themes.html#alignment>`_ - ``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 - ``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 - ``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 kwargs for modifying selection box/option style (Optional) - ``scrollbar_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Scrollbar color - ``scrollbar_cursor`` (int, :py:class:`pygame.cursors.Cursor`, None) – Cursor of the scrollbars if the mouse is placed over - ``scrollbar_shadow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the shadow of each scrollbar - ``scrollbar_shadow_offset`` (int) – Offset of the scrollbar shadow in px - ``scrollbar_shadow_position`` (str) – Position of the scrollbar shadow. See :py:mod:`pygame_menu.locals` - ``scrollbar_shadow`` (bool) – Indicate if a shadow is drawn on each scrollbar - ``scrollbar_slider_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the sliders - ``scrollbar_slider_hover_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Color of the slider if hovered or clicked - ``scrollbar_slider_pad`` (int, float) – Space between slider and scrollbars borders in px - ``scrollbar_thick`` (int) – Scrollbar thickness in px - ``scrollbars`` (str) – Scrollbar position. See :py:mod:`pygame_menu.locals` - ``selection_box_arrow_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Selection box arrow color - ``selection_box_arrow_margin`` (tuple) – Selection box arrow margin (left, right, vertical) in px - ``selection_box_bgcolor`` (tuple, list, str, int, :py:class:`pygame.Color`) – Selection box background color - ``selection_box_border_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Selection box border color - ``selection_box_border_width`` (int) – Selection box border width - ``selection_box_height`` (int) – Number of the options which can be packed before showing scroll - ``selection_box_inflate`` (tuple) – Selection box inflate on x-axis and y-axis (x, y) in px - ``selection_box_margin`` (tuple, list) – Selection box on x-axis and y-axis (x, y) margin from title in px - ``selection_box_text_margin`` (int) – Left margin of the text inside the selection box in px - ``selection_box_width`` (int) – Selection box width in px. If ``0`` compute automatically to fit placeholder - ``selection_infinite`` (bool) – If ``True`` selection can rotate through bottom/top - ``selection_option_border_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Option border color - ``selection_option_border_width`` (int) – Option border width - ``selection_option_font_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Option font color - ``selection_option_font_size`` (int, None) – Option font size. If ``None`` use the 75% of the widget font size - ``selection_option_font`` (str, :py:class:`pathlib.Path`, :py:class:`pygame.font.Font`) – Option font. If ``None`` use the same font as the widget - ``selection_option_padding`` (int, float, tuple, list ) – Selection padding. See padding styling - ``selection_option_selected_bgcolor`` (tuple, list, str, int, :py:class:`pygame.Color`) – Selected option background color - ``selection_option_selected_font_color`` (tuple, list, str, int, :py:class:`pygame.Color`) – Selected option font color .. note:: All theme-related optional kwargs use the default Menu theme if not defined. .. note:: This is applied only to the base Menu (not the currently displayed, stored in ``_current`` pointer); for such behavior 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: Drop select title :param items: Item list of the drop select; format ``[('Item1', a, b, c...), ('Item2', d, e, f...)]`` :param default: Index of default item to display. If ``None`` no item is selected :param dropselect_id: ID of the dropselect :param onchange: Callback when changing the drop select item :param onreturn: Callback when pressing return (apply) on the selected item :param onselect: Function when selecting the widget :param open_middle: If ``True`` the selection box is opened in the middle of the menu :param placeholder: Text shown if no option is selected yet :param placeholder_add_to_selection_box: If ``True`` adds the placeholder button to the selection box :param kwargs: Optional keyword arguments :return: Widget object :rtype: :py:class:`pygame_menu.widgets.DropSelect` """ # Filter widget attributes to avoid passing them to the callbacks attributes = self._filter_widget_attributes(kwargs) # Get selection box properties selection_box_arrow_color = kwargs.pop( "selection_box_arrow_color", self._theme.widget_box_arrow_color ) selection_box_arrow_margin = kwargs.pop( "selection_box_arrow_margin", self._theme.widget_box_arrow_margin ) selection_box_bgcolor = kwargs.pop( "selection_box_bgcolor", self._theme.widget_box_background_color ) selection_box_border_color = kwargs.pop( "selection_box_border_color", self._theme.widget_box_border_color ) selection_box_border_width = kwargs.pop( "selection_box_border_width", self._theme.widget_box_border_width ) selection_box_height = kwargs.pop("selection_box_height", 3) selection_box_inflate = kwargs.pop( "selection_box_inflate", self._theme.widget_border_inflate ) selection_box_margin = kwargs.pop( "selection_box_margin", self._theme.widget_box_margin ) selection_box_text_margin = kwargs.pop( "selection_box_text_margin", self._theme.widget_box_arrow_margin[0] ) selection_box_width = kwargs.pop("selection_box_width", 0) selection_infinite = kwargs.pop("selection_infinite", False) selection_option_border_color = kwargs.pop( "selection_option_border_color", self._theme.scrollbar_color ) selection_option_border_width = kwargs.pop( "selection_option_border_width", self._theme.widget_box_border_width ) # selection_option_cursor = kwargs.pop('selection_option_cursor', None) selection_option_font = kwargs.pop("selection_option_font", None) selection_option_font_color = kwargs.pop( "selection_option_font_color", (0, 0, 0) ) selection_option_font_size = kwargs.pop("selection_option_font_size", None) selection_option_padding = kwargs.pop("selection_option_padding", (2, 5)) selection_option_selected_bgcolor = kwargs.pop( "selection_option_selected_bgcolor", (188, 227, 244) ) selection_option_selected_font_color = kwargs.pop( "selection_option_selected_font_color", (0, 0, 0) ) # Get selection box scrollbar properties scrollbar_color = kwargs.pop("scrollbar_color", self._theme.scrollbar_color) scrollbar_cursor = kwargs.pop("scrollbar_cursor", self._theme.scrollbar_cursor) scrollbar_shadow_color = kwargs.pop( "scrollbar_shadow_color", self._theme.scrollbar_shadow_color ) scrollbar_shadow_offset = kwargs.pop( "scrollbar_shadow_offset", self._theme.scrollbar_shadow_offset ) scrollbar_shadow_position = kwargs.pop( "scrollbar_shadow_position", self._theme.scrollbar_shadow_position ) scrollbar_shadow = kwargs.pop("scrollbar_shadow", self._theme.scrollbar_shadow) scrollbar_slider_color = kwargs.pop( "scrollbar_slider_color", self._theme.scrollbar_slider_color ) scrollbar_slider_hover_color = kwargs.pop( "scrollbar_slider_hover_color", self._theme.scrollbar_slider_hover_color ) scrollbar_slider_pad = kwargs.pop( "scrollbar_slider_pad", self._theme.scrollbar_slider_pad ) scrollbar_thick = kwargs.pop("scrollbar_thick", self._theme.scrollbar_thick) scrollbars = kwargs.pop("scrollbars", self._theme.scrollarea_position) # Convert to tuple if enabled if isinstance(selection_box_margin, NumberInstance): selection_box_margin = (selection_box_margin, selection_box_margin) widget = DropSelect( default=default, 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, scrollbar_color=scrollbar_color, scrollbar_cursor=scrollbar_cursor, scrollbar_shadow=scrollbar_shadow, scrollbar_shadow_color=scrollbar_shadow_color, scrollbar_shadow_offset=scrollbar_shadow_offset, scrollbar_shadow_position=scrollbar_shadow_position, scrollbar_slider_color=scrollbar_slider_color, scrollbar_slider_hover_color=scrollbar_slider_hover_color, scrollbar_slider_pad=scrollbar_slider_pad, scrollbar_thick=scrollbar_thick, scrollbars=scrollbars, 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, ) self._configure_widget(widget=widget, **attributes) self._append_widget(widget) return widget