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.
#   locked: bool

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

#   value: float

Returns the value of this Slider.

Returns

A floating point number between 0.0 and 1.0.

#   def handle_key(self, key: str) -> bool:
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.

#   def handle_mouse(self, event: pytermgui.ansi_interface.MouseEvent) -> bool:
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.

#   def get_lines(self) -> list[str]:
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.