pytermgui.widgets.interactive.slider
This module contains the Slider
class.
View Source
0"""This module contains the `Slider` class.""" 1 2from __future__ import annotations 3from typing import Any, Callable 4 5from ...ansi_interface import MouseAction, MouseEvent 6from ...input import keys 7from ...regex import real_length 8from .. import styles as w_styles 9from ..base import Widget 10 11FILLED_SELECTED_STYLE = w_styles.MarkupFormatter("[72]{item}") 12FILLED_UNSELECTED_STYLE = w_styles.MarkupFormatter("[247]{item}") 13UNFILLED_STYLE = w_styles.MarkupFormatter("[240]{item}") 14 15 16class Slider(Widget): # pylint: disable=too-many-instance-attributes 17 """A Widget to display & configure scalable data. 18 19 By default, this Widget will act like a slider you might find in a 20 settings page, allowing percentage-based selection of magnitude. 21 Using `WindowManager` it can even be dragged around by the user using 22 the mouse. 23 """ 24 25 locked: bool 26 """Disallow mouse input, hide cursor and lock current state""" 27 28 chars = {"cursor": "", "fill": "", "rail": "━", "delimiter": ["[", "]"]} 29 30 styles = w_styles.StyleManager( 31 delimiter=UNFILLED_STYLE, 32 filled=FILLED_UNSELECTED_STYLE, 33 cursor=FILLED_SELECTED_STYLE, 34 filled_selected=FILLED_SELECTED_STYLE, 35 unfilled=UNFILLED_STYLE, 36 unfilled_selected=UNFILLED_STYLE, 37 ) 38 39 keys = { 40 "increase": {keys.RIGHT, keys.CTRL_F, "l", "+"}, 41 "decrease": {keys.LEFT, keys.CTRL_B, "h", "-"}, 42 } 43 44 def __init__( 45 self, 46 onchange: Callable[[float], Any] | None = None, 47 locked: bool = False, 48 **attrs: Any, 49 ) -> None: 50 """Initializes a Slider. 51 52 Args: 53 onchange: The callable called every time the value 54 is updated. 55 locked: Whether this Slider should accept value changes. 56 """ 57 58 self._value = 0.0 59 60 super().__init__(**attrs) 61 self._selectables_length = 1 62 63 self.is_locked = locked 64 self.onchange = onchange 65 66 @property 67 def value(self) -> float: 68 """Returns the value of this Slider. 69 70 Returns: 71 A floating point number between 0.0 and 1.0. 72 """ 73 74 return self._value 75 76 @value.setter 77 def value(self, new: float) -> None: 78 """Updates the value.""" 79 80 if self.is_locked: 81 return 82 83 self._value = max(0.0, min(new, 1.0)) 84 85 if self.onchange is not None: 86 self.onchange(self._value) 87 88 def handle_key(self, key: str) -> bool: 89 """Moves the slider cursor.""" 90 91 if self.execute_binding(key): 92 return True 93 94 if key in self.keys["increase"]: 95 self.value += 0.1 96 return True 97 98 if key in self.keys["decrease"]: 99 self.value -= 0.1 100 return True 101 102 return False 103 104 def handle_mouse(self, event: MouseEvent) -> bool: 105 """Moves the slider cursor.""" 106 107 delimiter = self._get_char("delimiter")[0] 108 109 if event.action in [MouseAction.LEFT_CLICK, MouseAction.LEFT_DRAG]: 110 offset = event.position[0] - self.pos[0] + 1 - real_length(delimiter) 111 self.value = max(0, min(offset / self.width, 1.0)) 112 return True 113 114 return False 115 116 def get_lines(self) -> list[str]: 117 """Gets slider lines.""" 118 119 rail = self._get_char("rail") 120 cursor = self._get_char("cursor") or rail 121 delimiters = self._get_char("delimiter") 122 123 assert isinstance(delimiters, list) 124 assert isinstance(cursor, str) 125 assert isinstance(rail, str) 126 127 cursor = self._get_style("cursor")(cursor) 128 unfilled = self.styles.unfilled(rail) 129 130 if self.selected_index is None: 131 filled = self.styles.filled(rail) 132 else: 133 filled = self.styles.filled_selected(rail) 134 135 for i, char in enumerate(delimiters): 136 delimiters[i] = self.styles.filled_selected(char) 137 138 for i, delimiter in enumerate(delimiters): 139 delimiters[i] = self.styles.delimiter(delimiter) 140 141 width = self.width - real_length("".join(delimiters)) 142 count = width * self.value - 1 143 144 chars = [delimiters[0]] 145 146 for i in range(width): 147 if i == count and not self.is_locked and self.selected_index is not None: 148 chars.append(cursor) 149 continue 150 151 if i <= count: 152 chars.append(filled) 153 continue 154 155 chars.append(unfilled) 156 157 chars.append(delimiters[1]) 158 line = "".join(chars) 159 self.width = real_length(line) 160 161 return [line]
View Source
17class Slider(Widget): # pylint: disable=too-many-instance-attributes 18 """A Widget to display & configure scalable data. 19 20 By default, this Widget will act like a slider you might find in a 21 settings page, allowing percentage-based selection of magnitude. 22 Using `WindowManager` it can even be dragged around by the user using 23 the mouse. 24 """ 25 26 locked: bool 27 """Disallow mouse input, hide cursor and lock current state""" 28 29 chars = {"cursor": "", "fill": "", "rail": "━", "delimiter": ["[", "]"]} 30 31 styles = w_styles.StyleManager( 32 delimiter=UNFILLED_STYLE, 33 filled=FILLED_UNSELECTED_STYLE, 34 cursor=FILLED_SELECTED_STYLE, 35 filled_selected=FILLED_SELECTED_STYLE, 36 unfilled=UNFILLED_STYLE, 37 unfilled_selected=UNFILLED_STYLE, 38 ) 39 40 keys = { 41 "increase": {keys.RIGHT, keys.CTRL_F, "l", "+"}, 42 "decrease": {keys.LEFT, keys.CTRL_B, "h", "-"}, 43 } 44 45 def __init__( 46 self, 47 onchange: Callable[[float], Any] | None = None, 48 locked: bool = False, 49 **attrs: Any, 50 ) -> None: 51 """Initializes a Slider. 52 53 Args: 54 onchange: The callable called every time the value 55 is updated. 56 locked: Whether this Slider should accept value changes. 57 """ 58 59 self._value = 0.0 60 61 super().__init__(**attrs) 62 self._selectables_length = 1 63 64 self.is_locked = locked 65 self.onchange = onchange 66 67 @property 68 def value(self) -> float: 69 """Returns the value of this Slider. 70 71 Returns: 72 A floating point number between 0.0 and 1.0. 73 """ 74 75 return self._value 76 77 @value.setter 78 def value(self, new: float) -> None: 79 """Updates the value.""" 80 81 if self.is_locked: 82 return 83 84 self._value = max(0.0, min(new, 1.0)) 85 86 if self.onchange is not None: 87 self.onchange(self._value) 88 89 def handle_key(self, key: str) -> bool: 90 """Moves the slider cursor.""" 91 92 if self.execute_binding(key): 93 return True 94 95 if key in self.keys["increase"]: 96 self.value += 0.1 97 return True 98 99 if key in self.keys["decrease"]: 100 self.value -= 0.1 101 return True 102 103 return False 104 105 def handle_mouse(self, event: MouseEvent) -> bool: 106 """Moves the slider cursor.""" 107 108 delimiter = self._get_char("delimiter")[0] 109 110 if event.action in [MouseAction.LEFT_CLICK, MouseAction.LEFT_DRAG]: 111 offset = event.position[0] - self.pos[0] + 1 - real_length(delimiter) 112 self.value = max(0, min(offset / self.width, 1.0)) 113 return True 114 115 return False 116 117 def get_lines(self) -> list[str]: 118 """Gets slider lines.""" 119 120 rail = self._get_char("rail") 121 cursor = self._get_char("cursor") or rail 122 delimiters = self._get_char("delimiter") 123 124 assert isinstance(delimiters, list) 125 assert isinstance(cursor, str) 126 assert isinstance(rail, str) 127 128 cursor = self._get_style("cursor")(cursor) 129 unfilled = self.styles.unfilled(rail) 130 131 if self.selected_index is None: 132 filled = self.styles.filled(rail) 133 else: 134 filled = self.styles.filled_selected(rail) 135 136 for i, char in enumerate(delimiters): 137 delimiters[i] = self.styles.filled_selected(char) 138 139 for i, delimiter in enumerate(delimiters): 140 delimiters[i] = self.styles.delimiter(delimiter) 141 142 width = self.width - real_length("".join(delimiters)) 143 count = width * self.value - 1 144 145 chars = [delimiters[0]] 146 147 for i in range(width): 148 if i == count and not self.is_locked and self.selected_index is not None: 149 chars.append(cursor) 150 continue 151 152 if i <= count: 153 chars.append(filled) 154 continue 155 156 chars.append(unfilled) 157 158 chars.append(delimiters[1]) 159 line = "".join(chars) 160 self.width = real_length(line) 161 162 return [line]
A Widget to display & configure scalable data.
By default, this Widget will act like a slider you might find in a
settings page, allowing percentage-based selection of magnitude.
Using WindowManager
it can even be dragged around by the user using
the mouse.
#  
Slider(
onchange: Optional[Callable[[float], Any]] = None,
locked: bool = False,
**attrs: Any
)
View Source
45 def __init__( 46 self, 47 onchange: Callable[[float], Any] | None = None, 48 locked: bool = False, 49 **attrs: Any, 50 ) -> None: 51 """Initializes a Slider. 52 53 Args: 54 onchange: The callable called every time the value 55 is updated. 56 locked: Whether this Slider should accept value changes. 57 """ 58 59 self._value = 0.0 60 61 super().__init__(**attrs) 62 self._selectables_length = 1 63 64 self.is_locked = locked 65 self.onchange = onchange
Initializes a Slider.
Args
- onchange: The callable called every time the value is updated.
- locked: Whether this Slider should accept value changes.
Disallow mouse input, hide cursor and lock current state
#  
chars: dict[str, typing.Union[typing.List[str], str]] = {'cursor': '', 'fill': '', 'rail': '━', 'delimiter': ['[', ']']}
Default characters for this class
#  
styles = {'delimiter': StyleCall(obj=None, method=MarkupFormatter(markup='[240]{item}', ensure_strip=False, _markup_cache={})), 'filled': StyleCall(obj=None, method=MarkupFormatter(markup='[247]{item}', ensure_strip=False, _markup_cache={})), 'cursor': StyleCall(obj=None, method=MarkupFormatter(markup='[72]{item}', ensure_strip=False, _markup_cache={})), 'filled_selected': StyleCall(obj=None, method=MarkupFormatter(markup='[72]{item}', ensure_strip=False, _markup_cache={})), 'unfilled': StyleCall(obj=None, method=MarkupFormatter(markup='[240]{item}', ensure_strip=False, _markup_cache={})), 'unfilled_selected': StyleCall(obj=None, method=MarkupFormatter(markup='[240]{item}', ensure_strip=False, _markup_cache={}))}
Default styles for this class
#  
keys: dict[str, set[str]] = {'increase': {'\x06', '+', '\x1b[C', 'l'}, 'decrease': {'-', '\x02', '\x1b[D', 'h'}}
Groups of keys that are used in handle_key
Returns the value of this Slider.
Returns
A floating point number between 0.0 and 1.0.
View Source
89 def handle_key(self, key: str) -> bool: 90 """Moves the slider cursor.""" 91 92 if self.execute_binding(key): 93 return True 94 95 if key in self.keys["increase"]: 96 self.value += 0.1 97 return True 98 99 if key in self.keys["decrease"]: 100 self.value -= 0.1 101 return True 102 103 return False
Moves the slider cursor.
View Source
105 def handle_mouse(self, event: MouseEvent) -> bool: 106 """Moves the slider cursor.""" 107 108 delimiter = self._get_char("delimiter")[0] 109 110 if event.action in [MouseAction.LEFT_CLICK, MouseAction.LEFT_DRAG]: 111 offset = event.position[0] - self.pos[0] + 1 - real_length(delimiter) 112 self.value = max(0, min(offset / self.width, 1.0)) 113 return True 114 115 return False
Moves the slider cursor.
View Source
117 def get_lines(self) -> list[str]: 118 """Gets slider lines.""" 119 120 rail = self._get_char("rail") 121 cursor = self._get_char("cursor") or rail 122 delimiters = self._get_char("delimiter") 123 124 assert isinstance(delimiters, list) 125 assert isinstance(cursor, str) 126 assert isinstance(rail, str) 127 128 cursor = self._get_style("cursor")(cursor) 129 unfilled = self.styles.unfilled(rail) 130 131 if self.selected_index is None: 132 filled = self.styles.filled(rail) 133 else: 134 filled = self.styles.filled_selected(rail) 135 136 for i, char in enumerate(delimiters): 137 delimiters[i] = self.styles.filled_selected(char) 138 139 for i, delimiter in enumerate(delimiters): 140 delimiters[i] = self.styles.delimiter(delimiter) 141 142 width = self.width - real_length("".join(delimiters)) 143 count = width * self.value - 1 144 145 chars = [delimiters[0]] 146 147 for i in range(width): 148 if i == count and not self.is_locked and self.selected_index is not None: 149 chars.append(cursor) 150 continue 151 152 if i <= count: 153 chars.append(filled) 154 continue 155 156 chars.append(unfilled) 157 158 chars.append(delimiters[1]) 159 line = "".join(chars) 160 self.width = real_length(line) 161 162 return [line]
Gets slider lines.