Source code for pygame_menu.widgets.widget.rangeslider

"""
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