pytermgui.widgets.interactive.input_field
This module contains the InputField
class.
1"""This module contains the `InputField` class.""" 2 3from __future__ import annotations 4from typing import Any 5 6import string 7 8from ...ansi_interface import MouseAction, MouseEvent 9from ...input import keys 10from ...enums import HorizontalAlignment 11from ...regex import real_length 12from .. import styles as w_styles 13from ..base import Label 14 15 16class InputField(Label): 17 """An element to display user input 18 19 This class does NOT read input. To use this widget, send it 20 user data gathered by `pytermgui.input.getch` or other means. 21 22 Args: 23 value: The default value of this InputField. 24 prompt: Text to display to the left of the field. 25 expect: Type object that all input should match. This type 26 is called on each new key, and if a `ValueError` is 27 raised the key is discarded. The `value` attribute 28 is also converted using this type. 29 30 Example of usage: 31 32 ```python3 33 import pytermgui as ptg 34 35 field = ptg.InputField() 36 37 root = ptg.Container( 38 "[210 bold]This is an InputField!", 39 field, 40 ) 41 42 while True: 43 key = getch() 44 45 # Send key to field 46 field.handle_key(key) 47 root.print() 48 ``` 49 """ 50 51 styles = w_styles.StyleManager( 52 value=w_styles.FOREGROUND, 53 cursor=w_styles.MarkupFormatter("[inverse]{item}"), 54 fill=w_styles.MarkupFormatter("[@243]{item}"), 55 ) 56 57 is_bindable = True 58 59 def __init__( 60 self, 61 value: str = "", 62 prompt: str = "", 63 expect: type | None = None, 64 **attrs: Any, 65 ) -> None: 66 """Initialize object""" 67 68 super().__init__(prompt + value, **attrs) 69 70 self.parent_align = HorizontalAlignment.LEFT 71 72 self.value = value 73 self.prompt = prompt 74 self.cursor = real_length(self.value) 75 self.expect = expect 76 77 @property 78 def selectables_length(self) -> int: 79 """Get length of selectables in object""" 80 81 return 1 82 83 @property 84 def cursor(self) -> int: 85 """Get cursor""" 86 87 return self._cursor 88 89 @cursor.setter 90 def cursor(self, value: int) -> None: 91 """Set cursor as an always-valid value""" 92 93 self._cursor = max(0, min(value, real_length(str(self.value)))) 94 95 def handle_key(self, key: str) -> bool: 96 """Handle keypress, return True if success, False if failure""" 97 98 def _run_callback() -> None: 99 """Call callback if `keys.ANY_KEY` is bound""" 100 101 if keys.ANY_KEY in self._bindings: 102 method, _ = self._bindings[keys.ANY_KEY] 103 method(self, key) 104 105 if self.execute_binding(key): 106 return True 107 108 if key == keys.TAB: 109 return False 110 111 if key == keys.BACKSPACE and self.cursor > 0: 112 self.value = str(self.value) 113 left = self.value[: self.cursor - 1] 114 right = self.value[self.cursor :] 115 self.value = left + right 116 117 self.cursor -= 1 118 119 _run_callback() 120 121 elif key in [keys.LEFT, keys.CTRL_B]: 122 self.cursor -= 1 123 124 elif key in [keys.RIGHT, keys.CTRL_F]: 125 self.cursor += 1 126 127 # Ignore unhandled non-printing keys 128 elif key == keys.ENTER or key not in string.printable: 129 return False 130 131 # Add character 132 else: 133 if self.expect is not None: 134 try: 135 self.expect(key) 136 except ValueError: 137 return False 138 139 self.value = str(self.value) 140 141 left = self.value[: self.cursor] + key 142 right = self.value[self.cursor :] 143 144 self.value = left + right 145 self.cursor += len(key) 146 _run_callback() 147 148 if self.expect is not None and self.value != "": 149 self.value = self.expect(self.value) 150 151 return True 152 153 def handle_mouse(self, event: MouseEvent) -> bool: 154 """Handle mouse events""" 155 156 # Ignore mouse release events 157 if event.action is MouseAction.RELEASE: 158 return True 159 160 # Set cursor to mouse location 161 if event.action is MouseAction.LEFT_CLICK: 162 self.cursor = event.position[0] - self.pos[0] 163 return True 164 165 return super().handle_mouse(event) 166 167 def get_lines(self) -> list[str]: 168 """Get lines of object""" 169 170 # Cache value to be reset later 171 old = self.value 172 173 # Stringify value in case `expect` is set 174 self.value = str(self.value) 175 176 # Create sides separated by cursor 177 left = self.styles.fill(self.value[: self.cursor]) 178 right = self.styles.fill(self.value[self.cursor + 1 :]) 179 180 # Assign cursor character 181 if self.selected_index is None: 182 if len(self.value) <= self.cursor: 183 cursor_char = "" 184 185 else: 186 cursor_char = self.styles.fill(self.value[self.cursor]) 187 188 # These errors are really weird, as there is nothing different with 189 # styles.cursor compared to others, which pass just fine. 190 elif len(self.value) > self.cursor: 191 cursor_char = self.styles.cursor( # pylint: disable=not-callable 192 self.value[self.cursor] 193 ) 194 195 else: 196 cursor_char = self.styles.cursor(" ") # pylint: disable=not-callable 197 198 # Set new value, get lines using it 199 self.value = self.prompt 200 201 if len(self.prompt) > 0: 202 self.value += " " 203 204 self.value += left + cursor_char + right 205 206 lines = super().get_lines() 207 208 # Set old value 209 self.value = old 210 211 return [ 212 line + self.styles.fill((self.width - real_length(line)) * " ") 213 for line in lines 214 ]
17class InputField(Label): 18 """An element to display user input 19 20 This class does NOT read input. To use this widget, send it 21 user data gathered by `pytermgui.input.getch` or other means. 22 23 Args: 24 value: The default value of this InputField. 25 prompt: Text to display to the left of the field. 26 expect: Type object that all input should match. This type 27 is called on each new key, and if a `ValueError` is 28 raised the key is discarded. The `value` attribute 29 is also converted using this type. 30 31 Example of usage: 32 33 ```python3 34 import pytermgui as ptg 35 36 field = ptg.InputField() 37 38 root = ptg.Container( 39 "[210 bold]This is an InputField!", 40 field, 41 ) 42 43 while True: 44 key = getch() 45 46 # Send key to field 47 field.handle_key(key) 48 root.print() 49 ``` 50 """ 51 52 styles = w_styles.StyleManager( 53 value=w_styles.FOREGROUND, 54 cursor=w_styles.MarkupFormatter("[inverse]{item}"), 55 fill=w_styles.MarkupFormatter("[@243]{item}"), 56 ) 57 58 is_bindable = True 59 60 def __init__( 61 self, 62 value: str = "", 63 prompt: str = "", 64 expect: type | None = None, 65 **attrs: Any, 66 ) -> None: 67 """Initialize object""" 68 69 super().__init__(prompt + value, **attrs) 70 71 self.parent_align = HorizontalAlignment.LEFT 72 73 self.value = value 74 self.prompt = prompt 75 self.cursor = real_length(self.value) 76 self.expect = expect 77 78 @property 79 def selectables_length(self) -> int: 80 """Get length of selectables in object""" 81 82 return 1 83 84 @property 85 def cursor(self) -> int: 86 """Get cursor""" 87 88 return self._cursor 89 90 @cursor.setter 91 def cursor(self, value: int) -> None: 92 """Set cursor as an always-valid value""" 93 94 self._cursor = max(0, min(value, real_length(str(self.value)))) 95 96 def handle_key(self, key: str) -> bool: 97 """Handle keypress, return True if success, False if failure""" 98 99 def _run_callback() -> None: 100 """Call callback if `keys.ANY_KEY` is bound""" 101 102 if keys.ANY_KEY in self._bindings: 103 method, _ = self._bindings[keys.ANY_KEY] 104 method(self, key) 105 106 if self.execute_binding(key): 107 return True 108 109 if key == keys.TAB: 110 return False 111 112 if key == keys.BACKSPACE and self.cursor > 0: 113 self.value = str(self.value) 114 left = self.value[: self.cursor - 1] 115 right = self.value[self.cursor :] 116 self.value = left + right 117 118 self.cursor -= 1 119 120 _run_callback() 121 122 elif key in [keys.LEFT, keys.CTRL_B]: 123 self.cursor -= 1 124 125 elif key in [keys.RIGHT, keys.CTRL_F]: 126 self.cursor += 1 127 128 # Ignore unhandled non-printing keys 129 elif key == keys.ENTER or key not in string.printable: 130 return False 131 132 # Add character 133 else: 134 if self.expect is not None: 135 try: 136 self.expect(key) 137 except ValueError: 138 return False 139 140 self.value = str(self.value) 141 142 left = self.value[: self.cursor] + key 143 right = self.value[self.cursor :] 144 145 self.value = left + right 146 self.cursor += len(key) 147 _run_callback() 148 149 if self.expect is not None and self.value != "": 150 self.value = self.expect(self.value) 151 152 return True 153 154 def handle_mouse(self, event: MouseEvent) -> bool: 155 """Handle mouse events""" 156 157 # Ignore mouse release events 158 if event.action is MouseAction.RELEASE: 159 return True 160 161 # Set cursor to mouse location 162 if event.action is MouseAction.LEFT_CLICK: 163 self.cursor = event.position[0] - self.pos[0] 164 return True 165 166 return super().handle_mouse(event) 167 168 def get_lines(self) -> list[str]: 169 """Get lines of object""" 170 171 # Cache value to be reset later 172 old = self.value 173 174 # Stringify value in case `expect` is set 175 self.value = str(self.value) 176 177 # Create sides separated by cursor 178 left = self.styles.fill(self.value[: self.cursor]) 179 right = self.styles.fill(self.value[self.cursor + 1 :]) 180 181 # Assign cursor character 182 if self.selected_index is None: 183 if len(self.value) <= self.cursor: 184 cursor_char = "" 185 186 else: 187 cursor_char = self.styles.fill(self.value[self.cursor]) 188 189 # These errors are really weird, as there is nothing different with 190 # styles.cursor compared to others, which pass just fine. 191 elif len(self.value) > self.cursor: 192 cursor_char = self.styles.cursor( # pylint: disable=not-callable 193 self.value[self.cursor] 194 ) 195 196 else: 197 cursor_char = self.styles.cursor(" ") # pylint: disable=not-callable 198 199 # Set new value, get lines using it 200 self.value = self.prompt 201 202 if len(self.prompt) > 0: 203 self.value += " " 204 205 self.value += left + cursor_char + right 206 207 lines = super().get_lines() 208 209 # Set old value 210 self.value = old 211 212 return [ 213 line + self.styles.fill((self.width - real_length(line)) * " ") 214 for line in lines 215 ]
An element to display user input
This class does NOT read input. To use this widget, send it
user data gathered by pytermgui.input.getch
or other means.
Args
- value: The default value of this InputField.
- prompt: Text to display to the left of the field.
- expect: Type object that all input should match. This type
is called on each new key, and if a
ValueError
is raised the key is discarded. Thevalue
attribute is also converted using this type.
Example of usage:
import pytermgui as ptg
field = ptg.InputField()
root = ptg.Container(
"[210 bold]This is an InputField!",
field,
)
while True:
key = getch()
# Send key to field
field.handle_key(key)
root.print()
InputField( value: str = '', prompt: str = '', expect: type | None = None, **attrs: Any)
60 def __init__( 61 self, 62 value: str = "", 63 prompt: str = "", 64 expect: type | None = None, 65 **attrs: Any, 66 ) -> None: 67 """Initialize object""" 68 69 super().__init__(prompt + value, **attrs) 70 71 self.parent_align = HorizontalAlignment.LEFT 72 73 self.value = value 74 self.prompt = prompt 75 self.cursor = real_length(self.value) 76 self.expect = expect
Initialize object
styles = {'value': StyleCall(obj=None, method=<function <lambda>>), 'cursor': StyleCall(obj=None, method=MarkupFormatter(markup='[inverse]{item}', ensure_strip=False, _markup_cache={})), 'fill': StyleCall(obj=None, method=MarkupFormatter(markup='[@243]{item}', ensure_strip=False, _markup_cache={}))}
Default styles for this class
def
handle_key(self, key: str) -> bool:
96 def handle_key(self, key: str) -> bool: 97 """Handle keypress, return True if success, False if failure""" 98 99 def _run_callback() -> None: 100 """Call callback if `keys.ANY_KEY` is bound""" 101 102 if keys.ANY_KEY in self._bindings: 103 method, _ = self._bindings[keys.ANY_KEY] 104 method(self, key) 105 106 if self.execute_binding(key): 107 return True 108 109 if key == keys.TAB: 110 return False 111 112 if key == keys.BACKSPACE and self.cursor > 0: 113 self.value = str(self.value) 114 left = self.value[: self.cursor - 1] 115 right = self.value[self.cursor :] 116 self.value = left + right 117 118 self.cursor -= 1 119 120 _run_callback() 121 122 elif key in [keys.LEFT, keys.CTRL_B]: 123 self.cursor -= 1 124 125 elif key in [keys.RIGHT, keys.CTRL_F]: 126 self.cursor += 1 127 128 # Ignore unhandled non-printing keys 129 elif key == keys.ENTER or key not in string.printable: 130 return False 131 132 # Add character 133 else: 134 if self.expect is not None: 135 try: 136 self.expect(key) 137 except ValueError: 138 return False 139 140 self.value = str(self.value) 141 142 left = self.value[: self.cursor] + key 143 right = self.value[self.cursor :] 144 145 self.value = left + right 146 self.cursor += len(key) 147 _run_callback() 148 149 if self.expect is not None and self.value != "": 150 self.value = self.expect(self.value) 151 152 return True
Handle keypress, return True if success, False if failure
def
handle_mouse(self, event: pytermgui.ansi_interface.MouseEvent) -> bool:
154 def handle_mouse(self, event: MouseEvent) -> bool: 155 """Handle mouse events""" 156 157 # Ignore mouse release events 158 if event.action is MouseAction.RELEASE: 159 return True 160 161 # Set cursor to mouse location 162 if event.action is MouseAction.LEFT_CLICK: 163 self.cursor = event.position[0] - self.pos[0] 164 return True 165 166 return super().handle_mouse(event)
Handle mouse events
def
get_lines(self) -> list[str]:
168 def get_lines(self) -> list[str]: 169 """Get lines of object""" 170 171 # Cache value to be reset later 172 old = self.value 173 174 # Stringify value in case `expect` is set 175 self.value = str(self.value) 176 177 # Create sides separated by cursor 178 left = self.styles.fill(self.value[: self.cursor]) 179 right = self.styles.fill(self.value[self.cursor + 1 :]) 180 181 # Assign cursor character 182 if self.selected_index is None: 183 if len(self.value) <= self.cursor: 184 cursor_char = "" 185 186 else: 187 cursor_char = self.styles.fill(self.value[self.cursor]) 188 189 # These errors are really weird, as there is nothing different with 190 # styles.cursor compared to others, which pass just fine. 191 elif len(self.value) > self.cursor: 192 cursor_char = self.styles.cursor( # pylint: disable=not-callable 193 self.value[self.cursor] 194 ) 195 196 else: 197 cursor_char = self.styles.cursor(" ") # pylint: disable=not-callable 198 199 # Set new value, get lines using it 200 self.value = self.prompt 201 202 if len(self.prompt) > 0: 203 self.value += " " 204 205 self.value += left + cursor_char + right 206 207 lines = super().get_lines() 208 209 # Set old value 210 self.value = old 211 212 return [ 213 line + self.styles.fill((self.width - real_length(line)) * " ") 214 for line in lines 215 ]
Get lines of object