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]
class Slider(pytermgui.widgets.base.Widget):
 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.
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': {'l', '\x1b[C', '+', '\x06'}, 'decrease': {'\x02', '-', 'h', '\x1b[D'}}

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:
 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.

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