"""
pygame-menu
https://github.com/ppizarror/pygame-menu
RANGE SLIDER
Slider bar between one/two numeric ranges.
"""
__all__ = [
# Class
'RangeSlider',
'RangeSliderManager',
# Input types
'RangeSliderRangeValueType',
'RangeSliderValueFormatType',
'RangeSliderValueType'
]
import math
import pygame
import pygame_menu
from abc import ABC
from pygame_menu.locals import POSITION_NORTH, POSITION_SOUTH
from pygame_menu.font import FontType, assert_font
from pygame_menu.locals import FINGERUP, FINGERDOWN, FINGERMOTION
from pygame_menu.utils import check_key_pressed_valid, assert_color, assert_vector, \
make_surface, get_finger_pos, assert_position, parse_padding
from pygame_menu.widgets.core.widget import Widget, WidgetTransformationNotImplemented, \
AbstractWidgetManager
from pygame_menu._types import Any, CallbackType, Union, List, Tuple, Optional, \
ColorType, NumberType, Tuple2IntType, NumberInstance, ColorInputType, \
EventVectorType, Vector2NumberType, VectorType, PaddingType, Tuple4IntType, \
Callable, Dict
RangeSliderRangeValueType = Union[Vector2NumberType, VectorType]
RangeSliderValueFormatType = Callable[[NumberType], str]
RangeSliderValueType = Union[NumberType, Vector2NumberType]
# noinspection PyMissingOrEmptyDocstring
[docs]class RangeSlider(Widget):
"""
Range slider widget. Offers 1 or 2 sliders for defining a unique value or
a range of numeric ones.
If the state of the widget changes the ``onchange`` callback is called. The
state can change by pressing LEFT/RIGHT, or by mouse/touch events.
.. code-block:: python
onchange(range_value, **kwargs)
If pressing return (apply) on the widget:
.. code-block:: python
onreturn(range_value, **kwargs)
.. note::
RangeSlider only accepts translation transformation.
:param title: Range slider title
:param rangeslider_id: RangeSlider ID
:param default_value: Default range value, can accept a number or a tuple/list of 2 elements (min, max). If a single number is provided the rangeslider only accepts 1 value, if 2 are provided, the range is enabled (2 values)
:param range_values: Tuple/list of 2 elements of min/max values of the range slider. Also range can accept a list of numbers, in which case the values of the range slider will be discrete. List must be sorted
:param range_width: Width of the range in px
:param increment: Increment of the value if using left/right keys; used only if the range values are not discrete
:param onchange: Callback when changing the value of the range slider
:param onreturn: Callback when pressing return (apply) on the range slider
:param onselect: Function when selecting the widget
:param range_box_color: Color of the range box between the sliders
:param range_box_color_readonly: Color of the range box if widget in readonly state
:param range_box_enabled: Enables a range box between two sliders
:param range_box_height_factor: Height of the range box (factor of the range title height)
:param range_box_single_slider: Enables range box if there's only 1 slider instead of 2
:param range_line_color: Color of the range line
:param range_line_height: Height of the range line in px
:param range_margin: Range margin on x-axis and y-axis (x, y) from title in px
:param range_text_value_color: Color of the range values text
:param range_text_value_enabled: Enables the range values text
:param range_text_value_font: Font of the ranges value. If ``None`` the same font as the widget is used
:param range_text_value_font_height: Height factor of the range value font (factor of the range title height)
:param range_text_value_margin_f: Margin of the range text values (factor of the range title height)
:param range_text_value_position: Position of the range text values, can be NORTH or SOUTH. See :py:mod:`pygame_menu.locals`
:param range_text_value_tick_color: Color of the range text value tick
:param range_text_value_tick_enabled: Range text value tick enabled
:param range_text_value_tick_hfactor: Height factor of the range text value tick (factor of the range title height)
:param range_text_value_tick_number: Number of range value text, the values are placed uniformly distributed
:param range_text_value_tick_thick: Thickness of the range text value tick in px
:param repeat_keys: Enable key repeat
:param repeat_keys_initial_ms: Time in milliseconds before keys are repeated when held in milliseconds
:param repeat_keys_interval_ms: Interval between key press repetition when held in milliseconds
:param slider_color: Slider color
:param slider_height_factor: Height of the slider (factor of the range title height)
:param slider_sel_highlight_color: Color of the selected slider highlight box effect
:param slider_sel_highlight_enabled: Selected slider is highlighted
:param slider_sel_highlight_thick: Thickness of the selected slider highlight
:param slider_selected_color: Selected slider color
:param slider_text_value_bgcolor: Background color of the value text on each slider
:param slider_text_value_color: Color of value text on each slider
:param slider_text_value_enabled: Enables a value text on each slider
:param slider_text_value_font: Font of the slider value. If ``None`` the same font as the widget is used
:param slider_text_value_font_height: Height factor of the slider font (factor of the range title height)
:param slider_text_value_margin_f: Margin of the slider text values (factor of the range title height)
:param slider_text_value_padding: Padding of the slider text values
:param slider_text_value_position: Position of the slider text values, can be NORTH or SOUTH. See :py:mod:`pygame_menu.locals`
:param slider_text_value_triangle: Draws a triangle between slider text value and slider
:param slider_thickness: Slider thickness in px
:param slider_vmargin: Vertical margin of the slider (factor of the range title height)
:param value_format: Function that format the value and returns a string that is used in the range and slider text
:param args: Optional arguments for callbacks
:param kwargs: Optional keyword arguments
"""
_font_range_value: Optional['pygame.font.Font']
_font_slider_value: Optional['pygame.font.Font']
_increment: NumberType
_increment_shift_factor: float
_keyrepeat: bool
_keyrepeat_counters: Dict[int, int]
_keyrepeat_initial_interval_ms: NumberType
_keyrepeat_interval_ms: NumberType
_range_box: 'pygame.Surface'
_range_box_color: ColorType
_range_box_color_readonly: ColorType
_range_box_enabled: bool
_range_box_height: int
_range_box_height_factor: NumberType
_range_box_pos: Tuple2IntType
_range_box_single_slider: bool
_range_line: 'pygame.Surface'
_range_line_color: ColorType
_range_line_height: int
_range_line_pos: Tuple2IntType
_range_margin: Tuple2IntType
_range_pos: Tuple2IntType
_range_text_value_color: ColorType
_range_text_value_enabled: bool
_range_text_value_font: Optional[FontType]
_range_text_value_font_height: NumberType
_range_text_value_margin: int
_range_text_value_margin_factor: NumberType
_range_text_value_position: str
_range_text_value_surfaces: List['pygame.Surface']
_range_text_value_surfaces_pos: List[Tuple2IntType]
_range_text_value_tick_color: ColorType
_range_text_value_tick_enabled: bool
_range_text_value_tick_height: int
_range_text_value_tick_height_factor: NumberType
_range_text_value_tick_number: int
_range_text_value_tick_surfaces: List['pygame.Surface']
_range_text_value_tick_surfaces_pos: List[Tuple2IntType]
_range_text_value_tick_thickness: int
_range_values: RangeSliderRangeValueType
_range_width: int
_scrolling: bool # Slider is scrolling
_selected_mouse: bool
_single: bool # Range single or double
_slider: List['pygame.Surface']
_slider_color: ColorType
_slider_height: int
_slider_height_factor: NumberType
_slider_pos: Tuple[Tuple2IntType, Tuple2IntType]
_slider_selected: Tuple[bool, bool]
_slider_selected_color: ColorType
_slider_selected_highlight_color: ColorType
_slider_selected_highlight_enabled: bool
_slider_selected_highlight_thickness: int
_slider_text_value_bgcolor: ColorType
_slider_text_value_color: ColorType
_slider_text_value_enabled: bool
_slider_text_value_font: Optional[FontType]
_slider_text_value_font_height: NumberType
_slider_text_value_margin: int
_slider_text_value_margin_factor: NumberType
_slider_text_value_padding: Tuple4IntType
_slider_text_value_position: str
_slider_text_value_surfaces: List['pygame.Surface']
_slider_text_value_surfaces_pos: List[Tuple2IntType]
_slider_text_value_triangle: bool
_slider_text_value_vmargin: int
_slider_thickness: int
_slider_vmargin: NumberType
_value: List[NumberType] # Public value of the slider, generated from the hidden
_value_format: RangeSliderValueFormatType
_value_hidden: List[NumberType] # Hidden value of the slider, modified by events
def __init__(
self,
title: Any,
rangeslider_id: str = '',
default_value: RangeSliderValueType = 0,
range_values: RangeSliderRangeValueType = (0, 1),
range_width: int = 150,
increment: NumberType = 0.1,
onchange: CallbackType = None,
onreturn: CallbackType = None,
onselect: CallbackType = None,
range_box_color: ColorInputType = (6, 119, 206, 170),
range_box_color_readonly: ColorInputType = (200, 200, 200, 170),
range_box_enabled: bool = True,
range_box_height_factor: NumberType = 0.45,
range_box_single_slider: bool = False,
range_line_color: ColorInputType = (100, 100, 100),
range_line_height: int = 2,
range_margin: Tuple2IntType = (25, 0),
range_text_value_color: ColorInputType = (80, 80, 80),
range_text_value_enabled: bool = True,
range_text_value_font: Optional[FontType] = None,
range_text_value_font_height: NumberType = 0.4,
range_text_value_margin_f: NumberType = 0.8,
range_text_value_position: str = POSITION_SOUTH,
range_text_value_tick_color: ColorInputType = (60, 60, 60),
range_text_value_tick_enabled: bool = True,
range_text_value_tick_hfactor: NumberType = 0.35,
range_text_value_tick_number: int = 2,
range_text_value_tick_thick: int = 1,
repeat_keys: bool = True,
repeat_keys_initial_ms: NumberType = 400,
repeat_keys_interval_ms: NumberType = 50,
slider_color: ColorInputType = (120, 120, 120),
slider_height_factor: NumberType = 0.7,
slider_sel_highlight_color: ColorInputType = (0, 0, 0),
slider_sel_highlight_enabled: bool = True,
slider_sel_highlight_thick: int = 1,
slider_selected_color: ColorInputType = (180, 180, 180),
slider_text_value_bgcolor: ColorInputType = (140, 140, 140),
slider_text_value_color: ColorInputType = (0, 0, 0),
slider_text_value_enabled: bool = True,
slider_text_value_font: Optional[FontType] = None,
slider_text_value_font_height: NumberType = 0.4,
slider_text_value_margin_f: NumberType = 1,
slider_text_value_padding: PaddingType = (0, 4),
slider_text_value_position: str = POSITION_NORTH,
slider_text_value_triangle: bool = True,
slider_thickness: int = 15,
slider_vmargin: NumberType = 0,
value_format: RangeSliderValueFormatType = lambda x: str(round(x, 3)),
*args,
**kwargs
) -> None:
super(RangeSlider, self).__init__(
args=args,
kwargs=kwargs,
onchange=onchange,
onreturn=onreturn,
onselect=onselect,
title=title,
widget_id=rangeslider_id
)
# Check ranges
assert_vector(range_values, 0)
assert len(range_values) >= 2, \
'range values length must be equal or greater than 2'
if len(range_values) == 2:
assert range_values[1] > range_values[0], \
'range values must be increasing and different numeric values'
else:
for i in range(len(range_values) - 1):
assert range_values[i] < range_values[i + 1], \
'range values list must be ordered and different'
assert isinstance(range_width, int)
assert range_width > 0, 'range width must be greater than zero'
# Check default value
if not isinstance(default_value, NumberInstance):
assert_vector(default_value, 2)
assert default_value[0] < default_value[1], \
'default value vector must be ordered and different (min,max)'
assert default_value[0] >= range_values[0], \
f'minimum default value ({default_value[0]}) must be equal or ' \
f'greater than minimum value of the range ({range_values[0]})'
assert default_value[1] <= range_values[-1], \
f'maximum default value ({default_value[1]}) must be lower or ' \
f'equal than maximum value of the range ({range_values[-1]})'
default_value = tuple(default_value)
else:
assert range_values[0] <= default_value <= range_values[-1], \
f'default value ({default_value}) must be between minimum and maximum' \
f' of the range values ({range_values[0]}, {range_values[-1]}), that ' \
f'is, it must satisfy {range_values[0]}<={default_value}<={range_values[-1]}'
# If range is discrete, check default value within list
if len(range_values) > 2:
if not isinstance(default_value, NumberInstance):
assert default_value[0] in range_values, \
f'min default value ({default_value[0]}) must be within range'
assert default_value[1] in range_values, \
f'max default value ({default_value[1]}) must be within range'
else:
assert default_value in range_values, \
f'default value ({default_value}) must be within range values'
# Check increment
assert isinstance(increment, NumberInstance)
assert increment >= 0, 'increment must be equal or greater than zero'
# Check fonts
if range_text_value_font is not None:
assert_font(range_text_value_font)
assert isinstance(range_text_value_font_height, NumberInstance)
assert 0 < range_text_value_font_height, \
'range text value font height must be greater than zero'
if slider_text_value_font is not None:
assert_font(slider_text_value_font)
assert isinstance(slider_text_value_font_height, NumberInstance)
assert 0 < slider_text_value_font_height, \
'slider text value font height must be greater than zero'
# Check colors
range_box_color = assert_color(range_box_color)
range_box_color_readonly = assert_color(range_box_color_readonly)
range_line_color = assert_color(range_line_color)
range_text_value_color = assert_color(range_text_value_color)
range_text_value_tick_color = assert_color(range_text_value_tick_color)
slider_color = assert_color(slider_color)
slider_selected_color = assert_color(slider_selected_color)
slider_sel_highlight_color = assert_color(slider_sel_highlight_color)
slider_text_value_bgcolor = assert_color(slider_text_value_bgcolor)
slider_text_value_color = assert_color(slider_text_value_color)
# Check dimensions and sizes
assert isinstance(range_box_height_factor, NumberInstance)
assert 0 < range_box_height_factor, \
'height factor must be greater than zero'
assert isinstance(range_text_value_margin_f, NumberInstance)
assert 0 < range_text_value_margin_f, \
'height factor must be greater than zero'
assert isinstance(slider_text_value_margin_f, NumberInstance)
assert 0 < slider_text_value_margin_f, \
'height factor must be greater than zero'
assert isinstance(slider_height_factor, NumberInstance)
assert 0 < slider_height_factor, \
'height factor must be greater than zero'
assert isinstance(slider_vmargin, NumberInstance)
slider_text_value_padding = parse_padding(slider_text_value_padding)
assert isinstance(slider_thickness, int)
assert slider_thickness > 0, \
'slider thickness must be greater than zero'
assert isinstance(range_line_height, int)
assert range_line_height >= 0, \
'range line height must be equal or greater than zero'
assert_vector(range_margin, 2, int)
assert isinstance(range_text_value_tick_number, int)
if range_text_value_enabled:
assert range_text_value_tick_number >= 2, \
'number of range value must be equal or greater than 2'
assert isinstance(range_text_value_tick_thick, int)
assert range_text_value_tick_thick >= 1, \
'range text tick thickness must be equal or greater than 1 px'
assert isinstance(slider_sel_highlight_thick, int)
assert slider_sel_highlight_thick >= 1, \
'selected highlight thickness must be equal or greater than 1 px'
# Check positions
assert_position(range_text_value_position)
assert range_text_value_position in (POSITION_NORTH, POSITION_SOUTH), \
'range text value position must be north or south'
assert_position(slider_text_value_position)
assert slider_text_value_position in (POSITION_NORTH, POSITION_SOUTH), \
'slider text value position must be north or south'
# Check boolean
assert isinstance(range_box_enabled, bool)
assert isinstance(range_box_single_slider, bool)
assert isinstance(range_text_value_enabled, bool)
assert isinstance(range_text_value_enabled, bool)
assert isinstance(slider_sel_highlight_enabled, bool)
assert isinstance(slider_text_value_enabled, bool)
assert isinstance(slider_text_value_triangle, bool)
# Check the value format function
assert callable(value_format)
assert isinstance(value_format(0), str), \
'value_format must be a function that accepts only 1 argument ' \
'(value) and must return a string'
# Check keyrepeat
assert isinstance(repeat_keys, bool)
assert isinstance(repeat_keys_initial_ms, NumberInstance)
assert isinstance(repeat_keys_interval_ms, NumberInstance)
assert repeat_keys_initial_ms > 0, \
'repeat keys initial ms cannot be lower or equal than zero'
assert repeat_keys_interval_ms > 0, \
'repeat keys interval ms cannot be lower or equal than zero'
# Single value
single = isinstance(default_value, NumberInstance)
# Convert default value to list
if isinstance(default_value, NumberInstance):
default_value = [default_value, 0]
else:
default_value = [default_value[0], default_value[1]]
# Store properties
self._clock = pygame.time.Clock()
self._default_value = tuple(default_value)
self._increment = increment
self._increment_shift_factor = 0.5
self._keyrepeat = repeat_keys
self._keyrepeat_counters = {} # {event.key: (counter_int, event.unicode)} (look for "***")
self._keyrepeat_initial_interval_ms = repeat_keys_initial_ms
self._keyrepeat_interval_ms = repeat_keys_interval_ms
self._range_box_color = range_box_color
self._range_box_color_readonly = range_box_color_readonly
self._range_box_enabled = range_box_enabled
self._range_box_height_factor = range_box_height_factor
self._range_box_single_slider = range_box_single_slider
self._range_line_color = range_line_color
self._range_line_height = range_line_height
self._range_margin = range_margin
self._range_pos = (0, 0)
self._range_text_value_color = range_text_value_color
self._range_text_value_enabled = range_text_value_enabled
self._range_text_value_font = range_text_value_font
self._range_text_value_font_height = range_text_value_font_height
self._range_text_value_margin = 0
self._range_text_value_margin_factor = range_text_value_margin_f
self._range_text_value_position = range_text_value_position
self._range_text_value_tick_color = range_text_value_tick_color
self._range_text_value_tick_enabled = range_text_value_tick_enabled
self._range_text_value_tick_height = 0
self._range_text_value_tick_height_factor = range_text_value_tick_hfactor
self._range_text_value_tick_number = range_text_value_tick_number
self._range_text_value_tick_thickness = range_text_value_tick_thick
self._range_values = tuple(range_values)
self._range_width = range_width
self._scrolling = False
self._selected_mouse = False
self._single = single
self._slider_color = slider_color
self._slider_height = 0
self._slider_height_factor = slider_height_factor
self._slider_selected = (True, False)
self._slider_selected_color = slider_selected_color
self._slider_selected_highlight_color = slider_sel_highlight_color
self._slider_selected_highlight_enabled = slider_sel_highlight_enabled
self._slider_selected_highlight_thickness = slider_sel_highlight_thick
self._slider_text_value_bgcolor = slider_text_value_bgcolor
self._slider_text_value_color = slider_text_value_color
self._slider_text_value_enabled = slider_text_value_enabled
self._slider_text_value_font = slider_text_value_font
self._slider_text_value_font_height = slider_text_value_font_height
self._slider_text_value_margin_factor = slider_text_value_margin_f
self._slider_text_value_padding = slider_text_value_padding
self._slider_text_value_position = slider_text_value_position
self._slider_text_value_triangle = slider_text_value_triangle
self._slider_text_value_vmargin = 0
self._slider_thickness = slider_thickness
self._slider_vmargin = slider_vmargin
self._value = default_value
self._value_format = value_format
self._value_hidden = default_value.copy() # Used when dragging mouse on discrete range
[docs] def value_changed(self) -> bool:
if self._single:
return self.get_value() != self._default_value[0]
return self.get_value() != self._default_value
[docs] def reset_value(self) -> 'Widget':
if self._single:
self.set_value(self._default_value[0])
else:
self.set_value(self._default_value)
return self
[docs] def set_value(self, value: RangeSliderValueType) -> None:
if self._single:
assert isinstance(value, NumberInstance)
# noinspection PyTypeChecker
assert self._range_values[0] <= value <= self._range_values[-1], \
f'value ({value}) must be within range {self._range_values[0]} <=' \
f' {value} <= {self._range_values[1]}'
if len(self._range_values) > 2:
assert value in self._range_values, \
'value must be between range values discrete list'
value = [value, 0]
else:
assert_vector(value, 2)
assert self._range_values[0] <= value[0], \
'value must be equal or greater than minimum range value'
assert value[1] <= self._range_values[-1], \
'value must be lower or equal than maximum range value'
assert value[0] < value[1], 'value vector must be ordered'
if len(self._range_values) > 2:
assert value[0] in self._range_values
assert value[1] in self._range_values
value = [value[0], value[1]]
self._value = value
self._value_hidden = self._value.copy()
self._render()
def scale(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
def resize(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
def set_max_width(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
def set_max_height(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
def rotate(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
def flip(self, *args, **kwargs) -> 'RangeSlider':
raise WidgetTransformationNotImplemented()
[docs] def get_value(self) -> Union[NumberType, Tuple[NumberType, NumberType]]:
if self._single:
return self._value[0]
return self._value[0], self._value[1]
def _apply_font(self) -> None:
if self._range_text_value_font is None:
self._range_text_value_font = self._font_name
if self._slider_text_value_font is None:
self._slider_text_value_font = self._font_name
self._font_range_value = pygame_menu.font.get_font(
self._range_text_value_font,
int(self._range_text_value_font_height * self._font_size)
)
self._font_slider_value = pygame_menu.font.get_font(
self._slider_text_value_font,
int(self._slider_text_value_font_height * self._font_size)
)
# Compute the height
height = self._font_render_string('TEST').get_height()
self._range_box_height = int(height * self._range_box_height_factor)
self._slider_height = int(height * self._slider_height_factor)
self._range_text_value_margin = int(height * self._range_text_value_margin_factor)
self._range_text_value_tick_height = int(height * self._range_text_value_tick_height_factor)
self._slider_text_value_margin = int(height * self._slider_text_value_margin_factor)
def _draw(self, surface: 'pygame.Surface') -> None:
# Draw title
surface.blit(self._surface, self._rect.topleft)
# Draw range line
surface.blit(self._range_line, (self._range_line_pos[0] + self._rect.x, self._range_line_pos[1] + self._rect.y))
# Draw range values and ticks
for i in range(len(self._range_text_value_surfaces)):
if self._range_text_value_enabled:
surface.blit(self._range_text_value_surfaces[i],
(self._rect.x + self._range_text_value_surfaces_pos[i][0],
self._rect.y + self._range_text_value_surfaces_pos[i][1]))
if self._range_text_value_tick_enabled:
tick_i = self._range_text_value_tick_surfaces[i]
surface.blit(tick_i,
(self._rect.x + self._range_text_value_tick_surfaces_pos[i][0],
self._rect.y + self._range_text_value_tick_surfaces_pos[i][1]))
# Draw range box
if self._range_box_enabled and (not self._single or self._range_box_single_slider):
surface.blit(self._range_box,
(self._range_box_pos[0] + self._rect.x, self._range_box_pos[1] + self._rect.y))
# Draw sliders
surface.blit(self._slider[0], (self._slider_pos[0][0] + self._rect.x, self._slider_pos[0][1] + self._rect.y))
if not self._single:
surface.blit(self._slider[1],
(self._slider_pos[1][0] + self._rect.x, self._slider_pos[1][1] + self._rect.y))
# Draw slider highlighted
if self._slider_selected[0] and not self.readonly and self.is_selected():
pygame.draw.rect(
surface, self._slider_selected_highlight_color,
self._get_slider_inflate_rect(0), self._slider_selected_highlight_thickness
)
if self._slider_selected[1] and not self.readonly and self.is_selected():
pygame.draw.rect(
surface, self._slider_selected_highlight_color,
self._get_slider_inflate_rect(1), self._slider_selected_highlight_thickness
)
[docs] def draw_after_if_selected(self, surface: Optional['pygame.Surface']) -> 'RangeSlider':
super(RangeSlider, self).draw_after_if_selected(surface)
self.last_surface = surface
# Draw slider value
if self._slider_text_value_enabled and not self.readonly:
surface.blit(
self._slider_text_value_surfaces[0],
(self._slider_text_value_surfaces_pos[0][0] + self._rect.x,
self._slider_text_value_surfaces_pos[0][1] + self._rect.y)
)
if not self._single:
surface.blit(
self._slider_text_value_surfaces[1],
(self._slider_text_value_surfaces_pos[1][0] + self._rect.x,
self._slider_text_value_surfaces_pos[1][1] + self._rect.y)
)
return self
def _get_slider_inflate_rect(
self,
pos: int,
inflate: Optional[Tuple2IntType] = None,
to_real_position: bool = False,
to_absolute_position: bool = False,
real_position_visible: bool = True
) -> 'pygame.Rect':
"""
Return the slider inflate rect.
:param pos: Which slider 0/1
:param inflate: Inflate in x, y
:param to_real_position: Transform the widget rect to real coordinates. Used by events
:param to_absolute_position: Transform the widget rect to absolute coordinates. Used by events
:param real_position_visible: Return only the visible width/height if ``to_real_position=True``
:return: Slider inflated rect
"""
if inflate is None:
inflate = (0, 0)
s = self._slider[pos]
rect = s.get_rect()
rect.x += self._slider_pos[pos][0] + self._rect.x
rect.y += self._slider_pos[pos][1] + self._rect.y
rect = pygame.Rect(
int(rect.x - inflate[0] / 2),
int(rect.y - inflate[1] / 2),
int(rect.width + inflate[0]),
int(rect.height + inflate[1])
)
if self._scrollarea is not None:
assert not (to_real_position and to_absolute_position), \
'real and absolute positions cannot be True at the same time'
if to_real_position:
rect = self._scrollarea.to_real_position(rect, visible=real_position_visible)
elif to_absolute_position:
rect = self._scrollarea.to_absolute_position(rect)
return rect
def _render(self) -> Optional[bool]:
if not hasattr(self, '_font_range_value'):
return False
elif not self._render_hash_changed(self._selected, self._title, self._visible, self.readonly,
self._range_values, self._slider_selected, self._value[0],
self._value[1], self._scrolling, self._selected_mouse):
return True
# Create basic title
self._surface = self._render_string(self._title, self.get_font_color_status())
self._rect.width, self._rect.height = self._surface.get_size()
self._range_pos = (self._rect.width + self._range_margin[0], int(self._rect.height / 2) + self._range_margin[1])
# Create slider
sel_s = self._slider_selected[0] and self._selected and not self.readonly, \
self._slider_selected[1] and self._selected and not self.readonly
self._slider = [
make_surface(self._slider_thickness, self._slider_height,
fill_color=(self._slider_color if not self.readonly else self._font_readonly_color)
if not sel_s[0] else self._slider_selected_color),
make_surface(self._slider_thickness, self._slider_height,
fill_color=(self._slider_color if not self.readonly else self._font_readonly_color)
if not sel_s[1] else self._slider_selected_color)
]
slider_vm = int(self._slider_vmargin * self._rect.height) # Vertical margin
if self._single:
self._slider_pos = (
(self._range_pos[0] + self._get_pos_range(self._value[0], self._slider[0]),
self._range_pos[1] + slider_vm - int(self._slider_height / 2)),
(0, 0)
)
else:
self._slider_pos = (
(self._range_pos[0] + self._get_pos_range(self._value[0], self._slider[0]),
self._range_pos[1] + slider_vm - int(self._slider_height / 2)),
(self._range_pos[0] + self._get_pos_range(self._value[1], self._slider[1]),
self._range_pos[1] + slider_vm - int(self._slider_height / 2))
)
# Create the range line
self._range_line = make_surface(self._range_width, self._range_line_height, fill_color=self._range_line_color)
self._range_line_pos = (self._range_pos[0], int(self._range_pos[1] - self._range_line_height / 2))
# Create the range font surfaces
range_values: List[NumberType] = []
if len(self._range_values) == 2:
d_val = (self._range_values[1] - self._range_values[0]) \
/ (self._range_text_value_tick_number - 1)
v_i = self._range_values[0]
for i in range(self._range_text_value_tick_number):
range_values.append(v_i)
v_i += d_val
else:
for i in self._range_values:
range_values.append(i)
# Create surfaces for each range value text & position + ticks
self._range_text_value_surfaces = []
self._range_text_value_surfaces_pos = []
self._range_text_value_tick_surfaces = []
self._range_text_value_tick_surfaces_pos = []
range_value_pos = 1 if self._range_text_value_position == POSITION_SOUTH else -1
for i in range_values:
s = self._font_range_value.render(
self._value_format(i), self._font_antialias,
self._range_text_value_color if not self.readonly else self._font_readonly_color)
self._range_text_value_surfaces.append(s)
s_x = self._range_pos[0] + self._get_pos_range(i, s)
s_y = self._range_pos[1] + self._range_text_value_margin / 2 * range_value_pos
self._range_text_value_surfaces_pos.append((int(s_x), int(s_y)))
# Create the tick
s_tick = make_surface(self._range_text_value_tick_thickness,
self._range_text_value_tick_height,
fill_color=self._range_text_value_tick_color)
self._range_text_value_tick_surfaces.append(s_tick)
t_x = self._range_pos[0] + self._get_pos_range(i)
t_y = self._range_pos[1] - s_tick.get_height() / 2
self._range_text_value_tick_surfaces_pos.append((int(t_x), int(t_y)))
# Computes the height of the ranges value text for modifying the rect box sizing
range_values_size = 0, 0 # Stores sizing
if self._range_text_value_enabled:
s = self._range_text_value_surfaces[0]
range_values_size = (int(s.get_width() * 0.7),
int(self._range_text_value_surfaces_pos[0][1] + s.get_height() * 0.9))
# Create the range box surface
if not self._single or self._range_box_single_slider:
r_pos = 0 if self._single else self._get_pos_range(self._value[0])
r_width = self._get_distance_between_sliders()
self._range_box = make_surface(
max(0, r_width), self._range_box_height,
fill_color=self._range_box_color if not self.readonly else self._range_box_color_readonly)
self._range_box_pos = (self._range_pos[0] + r_pos,
self._range_pos[1] - int(self._range_box.get_height() / 2))
# Create the slider values
self._slider_text_value_surfaces = []
self._slider_text_value_surfaces_pos = []
for v in self._value:
t = self._font_slider_value.render(self._value_format(v), self._font_antialias,
self._slider_text_value_color) # Value text
st = make_surface(
t.get_width() + self._slider_text_value_padding[1] + self._slider_text_value_padding[3],
t.get_height() + self._slider_text_value_padding[0] + self._slider_text_value_padding[2],
fill_color=self._slider_text_value_bgcolor)
st.blit(t, (
self._slider_text_value_padding[1],
self._slider_text_value_padding[0] + self._slider_text_value_vmargin))
# Create surface that considers st and the triangle
tri_height = int(self._slider_text_value_margin / 2) - int(self._slider_height / 2)
if tri_height and self._slider_text_value_triangle:
st_root = make_surface(st.get_width(), st.get_height() + tri_height)
st_root.blit(st, (0, 0))
mid = st.get_width() / 2
hig = st.get_height()
w = tri_height / math.sqrt(3)
tri_poly = ((mid - w, hig), (mid + w, hig), (mid, hig + tri_height))
pygame.draw.polygon(st_root, self._slider_text_value_bgcolor, tri_poly)
else:
st_root = st
self._slider_text_value_surfaces.append(st_root)
st_x = self._range_pos[0] + self._get_pos_range(v, st)
st_y = self._range_pos[1] - int(self._slider_text_value_margin / 2) - st.get_height()
self._slider_text_value_surfaces_pos.append((st_x, st_y))
# Update maximum rect height
self._rect.height = max(self._rect.height, self._slider_height, self._range_line_height, range_values_size[1])
self._rect.height += self._range_margin[1]
self._rect.width += self._range_width + self._range_margin[0] + range_values_size[0]
# Finals
self.force_menu_surface_update()
def _get_pos_range(self, value: NumberType, surface: Optional['pygame.Surface'] = None) -> int:
"""
Return the position of the surface within range slider.
:param value: Value
:param surface: Surface or None
:return: Position in px
"""
sw = surface.get_width() / 2 if surface is not None else 0
if len(self._range_values) == 2:
d = float(value - self._range_values[0]) / (self._range_values[1] - self._range_values[0])
return int(d * self._range_width - sw)
# Find the nearest position
n, t = self._find_nearest_discrete_range(value), len(self._range_values)
return int((float(n) / (t - 1)) * self._range_width - sw)
def _get_distance_between_sliders(self) -> int:
"""
Returns the distance between both sliders.
:return: Distance in px
"""
if not self._single:
return self._get_pos_range(self._value[1]) - self._get_pos_range(self._value[0])
return self._get_pos_range(self._value[0])
def _find_nearest_discrete_range(self, value: NumberType) -> int:
"""
Return the nearest position of value from range discrete list.
:param value: Value to find
:return: Position of the list
"""
n = 0 # Position of the nearest value
m = math.inf # Maximum distance
t = len(self._range_values) # Number of values
for j in range(t):
k = abs(self._range_values[j] - value)
if k < m:
m = k # Update max
n = j
return n
def _update_value(self, delta: NumberType) -> bool:
"""
Updates the value of the active slider by delta.
:param delta: Delta value
:return: True if value changed
"""
if self.readonly:
return False
old_value = self._value.copy()
old_value_hidden = self._value_hidden.copy()
slider_idx = 0 if self._slider_selected[0] else 1
self._value_hidden[slider_idx] += delta
if self._value_hidden[0] >= self._value_hidden[1] and not self._single:
self._value_hidden = old_value_hidden
else:
self._value_hidden[slider_idx] = max(
self._range_values[0], min(self._range_values[-1], self._value_hidden[slider_idx]))
# Update real value
if len(self._range_values) == 2:
self._value = self._value_hidden
else:
# Find nearest
val_index_nearest = self._find_nearest_discrete_range(self._value_hidden[slider_idx])
self._value[slider_idx] = self._range_values[val_index_nearest]
if self._value[0] >= self._value[1] and not self._single:
self._value = old_value
changed = old_value_hidden != self._value_hidden
if changed:
self.change()
return changed
def _blur(self) -> None:
self._selected_mouse = False
def _focus(self) -> None:
self._selected_mouse = False
def _left_right(self, event, left: bool) -> bool:
"""
Process left and right event keys.
:param event: Event
:param left: ``True`` if left, right otherwise
:return: ``True`` if updated
"""
self._value_hidden = self._value.copy() # Update hidden to real value
if event.key not in self._keyrepeat_counters:
self._keyrepeat_counters[event.key] = 0
keys_pressed = pygame.key.get_pressed()
# If not discrete, apply delta as increment
if len(self._range_values) == 2:
mod = 1 if not (keys_pressed[pygame.K_LSHIFT] or keys_pressed[pygame.K_RSHIFT]) \
else self._increment_shift_factor
if left:
mod *= -1
delta = self._increment * mod
# If discrete, find the current index and subtract +-1, then find the delta
# as the difference between two states
else:
if not self._single:
slider_idx = 0 if self._slider_selected[0] else 1
else:
slider_idx = 0
current_val_idx = self._find_nearest_discrete_range(self._value[slider_idx])
new_val_idx = current_val_idx
if left:
new_val_idx = max(0, new_val_idx - 1)
else:
new_val_idx = min(len(self._range_values) - 1, new_val_idx + 1)
delta = self._range_values[new_val_idx] - self._range_values[current_val_idx]
if self._update_value(delta):
self._sound.play_key_add()
return True
return False
def _test_get_pos_value(self, value: NumberType, dx: int = 0, dy: int = 0) -> Tuple2IntType:
"""
Return the position of the value in real coordinates, used for testing.
:param value: Value to get position
:param dx: Delta for x position in px
:param dy: Delta for y position in px
:return: (x, y) position
"""
rect = self.get_rect(to_real_position=True, apply_padding=False)
rect.x += self._range_pos[0] + self._get_pos_range(value)
rect.y += self._range_pos[1]
return rect.x + dx, rect.y + dy
[docs] def update(self, events: EventVectorType) -> bool:
self.apply_update_callbacks(events)
self._clock.tick(60)
if self.readonly or not self.is_visible():
self._readonly_check_mouseover(events)
return False
# Get time clock
time_clock = self._clock.get_time()
updated = False
events = self._merge_events(events) # Extend events with custom events
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.left(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 self._left_right(event, True):
return True
# Right button
elif (
keydown and self._ctrl.right(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 self._left_right(event, False):
return True
# Press enter
elif (
keydown and self._ctrl.apply(event, self) or
joy_button_down and self._ctrl.joy_select(event, self)
):
self.apply()
return True
# Tab, switch active slider
elif keydown and self._ctrl.tab(event, self):
if self._single:
continue
self._slider_selected = (False, True) if self._slider_selected[0] else (True, False)
return True
# Releases key
elif event.type == pygame.KEYUP and self._keyboard_enabled:
if event.key in self._keyrepeat_counters:
del self._keyrepeat_counters[event.key]
# User clicks the slider rect
elif (
event.type == pygame.MOUSEBUTTONDOWN and self._mouse_enabled or
event.type == FINGERDOWN and self._touchscreen_enabled and self._menu is not None
):
event_pos = get_finger_pos(self._menu, event)
# Check which slider is clicked
rect_slider_0 = self._get_slider_inflate_rect(0, to_real_position=True)
rect_slider_1 = self._get_slider_inflate_rect(1, to_real_position=True)
rc_1 = rect_slider_0.collidepoint(*event_pos)
rc_2 = rect_slider_1.collidepoint(*event_pos)
old_slider_selected = self._slider_selected
if not self._single:
# Check sliders does not collide each other
dist_sliders = self._get_distance_between_sliders()
sliders_intersect = dist_sliders <= (self._slider_thickness - 1)
if rc_1 and not sliders_intersect:
self._slider_selected = (True, False)
elif rc_2 and not sliders_intersect:
self._slider_selected = (False, True)
if old_slider_selected != self._slider_selected:
updated = True
self._render()
# Check if slider is clicked
self._scrolling = bool(rc_1 or rc_2)
self._selected_mouse = True
# User releases the mouse
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
):
event_pos = get_finger_pos(self._menu, event)
# If collides and not scroll, update the value to the clicked position
rect = self.get_rect(to_real_position=True, apply_padding=False)
if rect.collidepoint(*event_pos) and not self._scrolling and self._selected_mouse:
mouse_x, _ = event_pos
topleft, _ = rect.topleft
# Distance from title
dist = mouse_x - (topleft + self._range_pos[0])
# Current slider active value
val = self._value[0] if self._single else self._value[0 if self._slider_selected[0] else 1]
val_px = self._get_pos_range(val)
delta = (self._range_values[-1] - self._range_values[0]) * (dist - val_px) / self._range_width
if delta != 0 and dist >= 0: # If clicked true value
if self._update_value(delta):
updated = True
self._selected_mouse = False
# Disables scrolling
if self._scrolling:
self._scrolling = False
self._value_hidden = self._value.copy()
updated = True
# User scrolls clicked slider
elif self._scrolling and self._selected_mouse and (
event.type == pygame.MOUSEMOTION and self._mouse_enabled and hasattr(event, 'rel') or
event.type == FINGERMOTION and self._touchscreen_enabled and self._menu is not None
):
rel = event.rel[0] if event.type == pygame.MOUSEMOTION else (
event.dx * 2 * self._menu.get_window_size()[0])
delta = (self._range_values[-1] - self._range_values[0]) * rel / self._range_width
# Check mouse position
mx, my = event.pos if event.type == pygame.MOUSEMOTION else get_finger_pos(self._menu, event)
rect = self.get_rect(to_real_position=True, apply_padding=False)
# Compute position of mouse within valid range
dist_x = mx - (rect.x + self._range_pos[0])
dist_y = my - rect.y
# Get current slider pos if not single
x_max_min = 0, self._range_width
if not self._single:
slider_idx = 0 if self._slider_selected[0] else 1
slider_pos = self._get_pos_range(self._value[slider_idx])
x_max_min = slider_pos - 1, slider_pos
# Check slider within rect
if delta < 0:
in_x = -self._slider_height <= dist_x <= x_max_min[1]
else:
in_x = x_max_min[0] <= dist_x <= self._range_width + self._slider_height
in_y = 0 <= dist_y <= rect.height
# Checks mouse changed position and within rect position
if delta != 0 and in_x and in_y:
if self._update_value(delta):
updated = True
# Update key counters
if self._keyrepeat:
for key in self._keyrepeat_counters:
self._keyrepeat_counters[key] += time_clock # Update clock
# Generate new key events if enough time has passed:
if self._keyrepeat_counters[key] >= self._keyrepeat_initial_interval_ms:
self._keyrepeat_counters[key] = self._keyrepeat_initial_interval_ms - self._keyrepeat_interval_ms
self._add_event(pygame.event.Event(pygame.KEYDOWN, key=key))
return updated
class RangeSliderManager(AbstractWidgetManager, ABC):
"""
RangeSlider manager.
"""
def range_slider(
self,
title: str,
default: RangeSliderValueType,
range_values: RangeSliderRangeValueType,
increment: Optional[NumberType] = None,
onchange: CallbackType = None,
onreturn: CallbackType = None,
onselect: Optional[Callable[[bool, 'Widget', 'pygame_menu.Menu'], Any]] = None,
rangeslider_id: str = '',
value_format: RangeSliderValueFormatType = lambda x: str(round(x, 3)),
width: int = 150,
**kwargs
) -> 'pygame_menu.widgets.RangeSlider':
"""
Add a range slider to the Menu: Offers 1 or 2 sliders for defining a unique
value or a range of numeric ones.
If the state of the widget changes the ``onchange`` callback is called. The
state can change by pressing LEFT/RIGHT, or by mouse/touch events.
.. code-block:: python
onchange(range_value, **kwargs)
If pressing return (apply) on the widget:
.. code-block:: python
onreturn(range_value, **kwargs)
If ``onselect`` is defined, the callback is executed as follows, where
``selected`` is a boolean representing the selected status:
.. code-block:: python
onselect(selected, widget, menu)
kwargs (Optional)
- ``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)
- ``range_box_color_readonly`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the range box if widget in readonly state
- ``range_box_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the range box between the sliders
- ``range_box_enabled`` (bool) - Enables a range box between two sliders
- ``range_box_height_factor`` (int, float) - Height of the range box (factor of the range title height)
- ``range_box_single_slider`` (bool) - Enables range box if there's only 1 slider instead of 2
- ``range_line_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the range line
- ``range_line_height`` (int) - Height of the range line in px
- ``range_margin`` (tuple, list) - Range margin on x-axis and y-axis (x, y) from title in px
- ``range_text_value_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the range values text
- ``range_text_value_enabled`` (bool) - Enables the range values text
- ``range_text_value_font_height`` (int, float) - Height factor of the range value font (factor of the range title height)
- ``range_text_value_font`` (str, :py:class:`pathlib.Path`, :py:class:`pygame.font.Font`) - Font of the ranges value. If ``None`` the same font as the widget is used
- ``range_text_value_margin_f`` (int, float) - Margin of the range text values (factor of the range title height)
- ``range_text_value_position`` (str) - Position of the range text values, can be NORTH or SOUTH. See :py:mod:`pygame_menu.locals`
- ``range_text_value_tick_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the range text value tick
- ``range_text_value_tick_enabled`` (bool) - Range text value tick enabled
- ``range_text_value_tick_hfactor`` (bool) Height factor of the range text value tick (factor of the range title height)
- ``range_text_value_tick_number`` (int) - Number of range value text, the values are placed uniformly distributed
- ``range_text_value_tick_thick`` (int) - Thickness of the range text value tick in px
- ``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
- ``repeat_keys`` (bool) - Enable key repeat. ``True`` by default
- ``repeat_keys_initial_ms`` (int) - Time in milliseconds before keys are repeated when held in milliseconds. ``400`` by default
- ``repeat_keys_interval_ms`` (int) - Interval between key press repetition when held in milliseconds. ``50`` by default
- ``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
- ``slider_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Slider color
- ``slider_height_factor`` (int, float) - Height of the slider (factor of the range title height)
- ``slider_sel_highlight_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of the selected slider highlight box effect
- ``slider_sel_highlight_enabled`` (bool) - Selected slider is highlighted
- ``slider_sel_highlight_thick`` (int) - Thickness of the selected slider highlight
- ``slider_selected_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Selected slider color
- ``slider_text_value_bgcolor`` (tuple, list, str, int, :py:class:`pygame.Color`) - Background color of the value text on each slider
- ``slider_text_value_color`` (tuple, list, str, int, :py:class:`pygame.Color`) - Color of value text on each slider
- ``slider_text_value_enabled`` (bool) - Enables a value text on each slider
- ``slider_text_value_font_height`` (int, float) - Height factor of the slider font (factor of the range title height)
- ``slider_text_value_font`` (str, :py:class:`pathlib.Path`, :py:class:`pygame.font.Font`) - Font of the slider value. If ``None`` the same font as the widget is used
- ``slider_text_value_margin_f`` (int, float) - Margin of the slider text values (factor of the range title height)
- ``slider_text_value_padding`` (int, float, tuple, list) – Padding of the slider text values
- ``slider_text_value_position`` (str) - Position of the slider text values, can be NORTH or SOUTH. See :py:mod:`pygame_menu.locals`
- ``slider_text_value_triangle`` (bool) - Draws a triangle between slider text value and slider
- ``slider_thickness`` (int) - Slider thickness in px
- ``slider_vmargin`` (int, float) - Vertical margin of the slider (factor of the range title height)
- ``tab_size`` (int) – Width of a tab character
.. 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 behaviour apply to
:py:meth:`pygame_menu.menu.Menu.get_current` object.
.. warning::
Be careful with kwargs collision. Consider that all optional documented
kwargs keys are removed from the object.
:param title: Title of the range slider
:param default: Default range value, can accept a number or a tuple/list of 2 elements (min, max). If a single number is provided the rangeslider only accepts 1 value, if 2 are provided, the range is enabled (2 values)
:param range_values: Tuple/list of 2 elements of min/max values of the range slider. Also range can accept a list of numbers, in which case the values of the range slider will be discrete. List must be sorted
:param increment: Increment of the value if using left/right keys; used only if the range values are not discrete
:param onchange: Callback executed when changing the value of the range slider
:param onreturn: Callback executed when pressing return (apply) on the range slider
:param onselect: Callback executed when selecting the widget
:param rangeslider_id: ID of the range slider
:param value_format: Function that format the value and returns a string that is used in the range and slider text
:param width: Width of the range in px
:param kwargs: Optional keyword arguments
:return: Widget object
:rtype: :py:class:`pygame_menu.widgets.RangeSlider`
"""
assert_vector(range_values, 0)
if len(range_values) == 2:
assert isinstance(increment, NumberInstance), \
'increment must be defined if the range values are not discrete'
else:
if increment is None:
increment = 1
# Filter widget attributes to avoid passing them to the callbacks
attributes = self._filter_widget_attributes(kwargs)
range_margin = kwargs.pop('range_margin', self._theme.widget_box_margin)
range_line_color = kwargs.pop('range_line_color', self._theme.widget_font_color)
range_text_value_color = kwargs.pop('range_text_value_color', self._theme.widget_font_color)
range_text_value_font_height = kwargs.pop('range_text_value_font_height', 0.6)
range_text_value_tick_hfactor = kwargs.pop('range_text_value_tick_hfactor', 0.5)
slider_text_value_font_height = kwargs.pop('slider_text_value_font_height', 0.6)
widget = RangeSlider(
title=title,
rangeslider_id=rangeslider_id,
default_value=default,
range_values=range_values,
range_width=width,
increment=increment,
onchange=onchange,
onreturn=onreturn,
onselect=onselect,
range_line_color=range_line_color,
range_margin=range_margin,
range_text_value_color=range_text_value_color,
range_text_value_font_height=range_text_value_font_height,
range_text_value_tick_hfactor=range_text_value_tick_hfactor,
slider_text_value_font_height=slider_text_value_font_height,
value_format=value_format,
**kwargs
)
self._configure_widget(widget=widget, **attributes)
self._append_widget(widget)
return widget