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