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