kvex.widgets.inputpanel
Panel of simple input widgets.
1"""Panel of simple input widgets.""" 2 3from dataclasses import dataclass, field 4from typing import Any, Optional, Callable 5import functools 6from .. import kivy as kv 7from .. import util 8from .layouts import XAnchor, XDBox, XBox, XCurtain 9from .label import XLabel 10from .button import XButton 11from .input import XInput 12from .checkbox import XCheckBox 13from .spinner import XSpinner 14 15 16HEIGHT_UNIT = "40sp" 17 18 19@dataclass 20class XInputPanelWidget: 21 """Dataclass to configure a specific `XInputPanel` widget.""" 22 23 label: str 24 """Label text.""" 25 widget: str = "str" 26 """Widget type, one of `INPUT_WIDGET_TYPES`.""" 27 default: Any = None 28 """Default value of the input widget.""" 29 orientation: str = "horizontal" 30 """Orientation between label and input widget.""" 31 showing: bool = True 32 """If widget should be showing.""" 33 label_hint: float = 1 34 """Size hint of the label relative to the input widget.""" 35 italic: bool = True 36 """Italicized label.""" 37 bold: bool = False 38 """Enboldened label.""" 39 halign: Optional[str] = None 40 """Label text horizontal alignment.""" 41 choices: list = field(default_factory=list) 42 """Used by choice widgets.""" 43 44 45class XInputPanel(XDBox): 46 """A widget containing arbitrary input widgets. 47 48 Intended for forms or configuration user input. 49 """ 50 51 reset_text = kv.StringProperty("Reset defaults") 52 """Text for the reset button, leave empty to hide.""" 53 invoke_text = kv.StringProperty("Send") 54 """Text to show on the invoke button, leave empty to hide.""" 55 fill_button = kv.BooleanProperty(False) 56 """Fill reset or invoke button horizontally even if only one is visible.""" 57 58 def __init__( 59 self, 60 widgets: dict[str, XInputPanelWidget], 61 /, 62 **kwargs, 63 ): 64 """Initialize the class. 65 66 Args: 67 widgets: Dictionary of names to widgets. 68 """ 69 kwargs = dict(padding=("10sp", 0)) | kwargs 70 self._controls = controls = XBox() 71 super().__init__(**kwargs) 72 main_box = self 73 self.widgets: dict[str, BaseInputWidget] = dict() 74 self._curtains: dict[str, XCurtain] = dict() 75 # Widgets 76 self._reset_btn = XButton(text=self.reset_text, on_release=self.reset_defaults) 77 self._invoke_btn = XButton(text=self.invoke_text, on_release=self._do_invoke) 78 # Input Widgets 79 for name, w in widgets.items(): 80 iw_cls = INPUT_WIDGET_CLASSES[w.widget] 81 input_widget = iw_cls(w, self._do_values, self._do_invoke) 82 curtain = XCurtain(content=input_widget, showing=w.showing) 83 curtain.set_size(y=input_widget.height) 84 self.widgets[name] = input_widget 85 self._curtains[name] = curtain 86 main_box.add_widget(curtain) 87 # Controls 88 controls = XBox() 89 if self.reset_text: 90 controls.add_widget(XAnchor.wrap(self._reset_btn, padding=("5dp", 0))) 91 if self.invoke_text: 92 controls.add_widget(XAnchor.wrap(self._invoke_btn, padding=("5dp", 0))) 93 if len(controls.children) == 1: 94 controls.set_size(hx=1 if self.fill_button else 0.5) 95 controls = XAnchor.wrap(controls) 96 controls.set_size(y=HEIGHT_UNIT) 97 if len(controls.children) > 0: 98 main_box.add_widget(controls) 99 # Bindings 100 self.bind( 101 reset_text=self._on_reset_text, 102 invoke_text=self._on_invoke_text, 103 ) 104 self.register_event_type("on_invoke") 105 self.register_event_type("on_values") 106 107 def on_fill_button(self, w, fill: bool): 108 """Adjust control buttons frame size hint.""" 109 if len(self._controls.children) == 1: 110 self._controls.set_size(hx=1 if fill else 0.5) 111 else: 112 self._controls.set_size(hx=1) 113 114 def get_value(self, widget_name: str, /) -> Any: 115 """Get a value by name.""" 116 widget = self.widgets[widget_name] 117 return widget.get_value() 118 119 def get_values(self) -> dict[str, Any]: 120 """Get all values.""" 121 return {name: iw.get_value() for name, iw in self.widgets.items()} 122 123 def reset_defaults(self, *args, **kwargs): 124 """Reset all values to their defaults.""" 125 for iw in self.widgets.values(): 126 iw.set_value() 127 128 def set_focus(self, widget_name: str, /): 129 """Focus a widget by name.""" 130 widget = self.widgets[widget_name] 131 widget.set_focus() 132 133 def set_enabled(self, widget_name: str, set_as: bool = True, /): 134 """Enable or disable a widget by name.""" 135 widget = self.widgets[widget_name] 136 widget.set_enabled(set_as) 137 138 def set_showing(self, widget_name: str, set_as: bool = True, /): 139 """Show or hide a widget by name.""" 140 curtain = self._curtains[widget_name] 141 curtain.showing = set_as 142 143 def on_invoke(self, values: dict): 144 """Triggered when the invoke button is pressed or otherwise sent by user.""" 145 pass 146 147 def on_values(self, values: dict): 148 """Triggered when any of the values change.""" 149 pass 150 151 def _do_invoke(self, *args): 152 self.dispatch("on_invoke", self.get_values()) 153 154 def _do_values(self, *args): 155 self.dispatch("on_values", self.get_values()) 156 157 def _on_reset_text(self, w, text): 158 self._reset_btn.text = text 159 160 def _on_invoke_text(self, w, text): 161 self._invoke_btn.text = text 162 163 164class BaseInputWidget(XBox): 165 def __init__(self, w: XInputPanelWidget, on_value: Callable, on_invoke: Callable): 166 assert w.widget == self.wtype 167 self.specification = w 168 super().__init__(orientation=w.orientation) 169 default_halign = "right" if w.orientation == "horizontal" else "center" 170 # Build 171 self.label = XLabel( 172 text=w.label, 173 padding=(10, 5), 174 italic=w.italic, 175 bold=w.bold, 176 halign=w.halign or default_halign, 177 ) 178 self.widget = self._get_widget(w, on_value, on_invoke) 179 assert self.widget is not None 180 # Assemble 181 height = util.sp2pixels(HEIGHT_UNIT) * (1 + (w.orientation == "vertical")) 182 self.set_size(y=height) 183 self.label.set_size(hx=w.label_hint if w.orientation == "horizontal" else 1) 184 self.add_widgets(self.label, self.widget) 185 186 def set_enabled(self, set_as: Optional[bool] = None, /): 187 if set_as is None: 188 set_as = not self.label.disabled 189 self.label.disabled = set_as 190 191 def set_focus(self): 192 pass 193 194 195class StringInputWidget(BaseInputWidget): 196 wtype = "str" 197 _entry_class = XInput 198 _text_default = "" 199 _password = False 200 201 def _get_widget( 202 self, 203 w: XInputPanelWidget, 204 on_value: Callable, 205 on_invoke: Callable, 206 ): 207 self._entry = self._entry_class( 208 text=str(w.default or self._text_default), 209 password=self._password, 210 select_on_focus=True, 211 ) 212 self._entry.bind(text=on_value, on_text_validate=on_invoke) 213 return self._entry 214 215 def get_value(self) -> str: 216 return self._entry.text 217 218 def set_value(self, value: Optional[str] = None, /): 219 if value is None: 220 value = self.specification.default or self._text_default 221 self._entry.text = value 222 223 def set_enabled(self, set_as: Optional[bool] = None, /): 224 super().set_enabled(set_as) 225 if set_as is None: 226 set_as = not self._entry.disabled 227 self._entry.disabled = set_as 228 229 def set_focus(self): 230 self._entry.focus = True 231 self._entry.select_all() 232 233 234class BooleanInputWidget(BaseInputWidget): 235 wtype = "bool" 236 237 def _get_widget( 238 self, 239 w: XInputPanelWidget, 240 on_value: Callable, 241 on_invoke: Callable, 242 ): 243 self._checkbox = XCheckBox(active=w.default or False) 244 self._checkbox.bind(active=on_value) 245 if w.orientation == "vertical": 246 return self._checkbox 247 self._checkbox.set_size(x=HEIGHT_UNIT) 248 frame = XAnchor(anchor_x="left") 249 frame.add_widget(self._checkbox) 250 return frame 251 252 def get_value(self) -> bool: 253 return self._checkbox.active 254 255 def set_value(self, value: Optional[bool] = None, /): 256 if value is None: 257 value = self.specification.default 258 self._checkbox.active = value 259 260 def set_enabled(self, set_as: Optional[bool] = None, /): 261 super().set_enabled(set_as) 262 if set_as is None: 263 set_as = not self._checkbox.disabled 264 self._checkbox.disabled = set_as 265 266 def set_focus(self): 267 self._checkbox.focus = True 268 269 270class IntInputWidget(StringInputWidget): 271 wtype = "int" 272 _entry_class = functools.partial(XInput, input_filter="int") 273 _text_default = "0" 274 275 def get_value(self) -> int: 276 return int(self._entry.text or 0) 277 278 def set_value(self, value: Optional[int] = None, /): 279 if value is None: 280 value = self.specification.default or 0 281 self._entry.text = str(value) 282 283 284class FloatInputWidget(StringInputWidget): 285 wtype = "float" 286 _entry_class = functools.partial(XInput, input_filter="float") 287 _text_default = "0" 288 289 def get_value(self) -> float: 290 return float(self._entry.text or 0) 291 292 def set_value(self, value: Optional[float] = None, /): 293 if value is None: 294 value = self.specification.default or 0 295 self._entry.text = str(value) 296 297 298class PasswordInputWidget(StringInputWidget): 299 wtype = "password" 300 _password = True 301 302 303class ChoiceInputWidget(BaseInputWidget): 304 wtype = "choice" 305 306 def _get_widget( 307 self, 308 w: XInputPanelWidget, 309 on_value: Callable, 310 on_invoke: Callable, 311 ): 312 self._spinner = XSpinner( 313 text=w.default or "", 314 values=w.choices, 315 text_autoupdate=True, 316 ) 317 self._spinner.bind(text=on_value) 318 return self._spinner 319 320 def get_value(self) -> bool: 321 return self._spinner.text 322 323 def set_value(self, value: Optional[str] = None, /): 324 if value is None: 325 value = self.specification.default or "" 326 self._spinner.text = value 327 328 def set_enabled(self, set_as: Optional[bool] = None, /): 329 super().set_enabled(set_as) 330 if set_as is None: 331 set_as = not self._spinner.disabled 332 self._spinner.disabled = set_as 333 334 335INPUT_WIDGET_CLASSES: dict[str, BaseInputWidget] = dict( 336 str=StringInputWidget, 337 bool=BooleanInputWidget, 338 int=IntInputWidget, 339 float=FloatInputWidget, 340 password=PasswordInputWidget, 341 choice=ChoiceInputWidget, 342) 343INPUT_WIDGET_TYPES = tuple(INPUT_WIDGET_CLASSES.keys()) 344"""Input widget types.""" 345 346 347__all__ = ( 348 "XInputPanel", 349 "XInputPanelWidget", 350 "INPUT_WIDGET_TYPES", 351)
46class XInputPanel(XDBox): 47 """A widget containing arbitrary input widgets. 48 49 Intended for forms or configuration user input. 50 """ 51 52 reset_text = kv.StringProperty("Reset defaults") 53 """Text for the reset button, leave empty to hide.""" 54 invoke_text = kv.StringProperty("Send") 55 """Text to show on the invoke button, leave empty to hide.""" 56 fill_button = kv.BooleanProperty(False) 57 """Fill reset or invoke button horizontally even if only one is visible.""" 58 59 def __init__( 60 self, 61 widgets: dict[str, XInputPanelWidget], 62 /, 63 **kwargs, 64 ): 65 """Initialize the class. 66 67 Args: 68 widgets: Dictionary of names to widgets. 69 """ 70 kwargs = dict(padding=("10sp", 0)) | kwargs 71 self._controls = controls = XBox() 72 super().__init__(**kwargs) 73 main_box = self 74 self.widgets: dict[str, BaseInputWidget] = dict() 75 self._curtains: dict[str, XCurtain] = dict() 76 # Widgets 77 self._reset_btn = XButton(text=self.reset_text, on_release=self.reset_defaults) 78 self._invoke_btn = XButton(text=self.invoke_text, on_release=self._do_invoke) 79 # Input Widgets 80 for name, w in widgets.items(): 81 iw_cls = INPUT_WIDGET_CLASSES[w.widget] 82 input_widget = iw_cls(w, self._do_values, self._do_invoke) 83 curtain = XCurtain(content=input_widget, showing=w.showing) 84 curtain.set_size(y=input_widget.height) 85 self.widgets[name] = input_widget 86 self._curtains[name] = curtain 87 main_box.add_widget(curtain) 88 # Controls 89 controls = XBox() 90 if self.reset_text: 91 controls.add_widget(XAnchor.wrap(self._reset_btn, padding=("5dp", 0))) 92 if self.invoke_text: 93 controls.add_widget(XAnchor.wrap(self._invoke_btn, padding=("5dp", 0))) 94 if len(controls.children) == 1: 95 controls.set_size(hx=1 if self.fill_button else 0.5) 96 controls = XAnchor.wrap(controls) 97 controls.set_size(y=HEIGHT_UNIT) 98 if len(controls.children) > 0: 99 main_box.add_widget(controls) 100 # Bindings 101 self.bind( 102 reset_text=self._on_reset_text, 103 invoke_text=self._on_invoke_text, 104 ) 105 self.register_event_type("on_invoke") 106 self.register_event_type("on_values") 107 108 def on_fill_button(self, w, fill: bool): 109 """Adjust control buttons frame size hint.""" 110 if len(self._controls.children) == 1: 111 self._controls.set_size(hx=1 if fill else 0.5) 112 else: 113 self._controls.set_size(hx=1) 114 115 def get_value(self, widget_name: str, /) -> Any: 116 """Get a value by name.""" 117 widget = self.widgets[widget_name] 118 return widget.get_value() 119 120 def get_values(self) -> dict[str, Any]: 121 """Get all values.""" 122 return {name: iw.get_value() for name, iw in self.widgets.items()} 123 124 def reset_defaults(self, *args, **kwargs): 125 """Reset all values to their defaults.""" 126 for iw in self.widgets.values(): 127 iw.set_value() 128 129 def set_focus(self, widget_name: str, /): 130 """Focus a widget by name.""" 131 widget = self.widgets[widget_name] 132 widget.set_focus() 133 134 def set_enabled(self, widget_name: str, set_as: bool = True, /): 135 """Enable or disable a widget by name.""" 136 widget = self.widgets[widget_name] 137 widget.set_enabled(set_as) 138 139 def set_showing(self, widget_name: str, set_as: bool = True, /): 140 """Show or hide a widget by name.""" 141 curtain = self._curtains[widget_name] 142 curtain.showing = set_as 143 144 def on_invoke(self, values: dict): 145 """Triggered when the invoke button is pressed or otherwise sent by user.""" 146 pass 147 148 def on_values(self, values: dict): 149 """Triggered when any of the values change.""" 150 pass 151 152 def _do_invoke(self, *args): 153 self.dispatch("on_invoke", self.get_values()) 154 155 def _do_values(self, *args): 156 self.dispatch("on_values", self.get_values()) 157 158 def _on_reset_text(self, w, text): 159 self._reset_btn.text = text 160 161 def _on_invoke_text(self, w, text): 162 self._invoke_btn.text = text
A widget containing arbitrary input widgets.
Intended for forms or configuration user input.
XInputPanel( widgets: dict[str, kvex.widgets.inputpanel.XInputPanelWidget], /, **kwargs)
59 def __init__( 60 self, 61 widgets: dict[str, XInputPanelWidget], 62 /, 63 **kwargs, 64 ): 65 """Initialize the class. 66 67 Args: 68 widgets: Dictionary of names to widgets. 69 """ 70 kwargs = dict(padding=("10sp", 0)) | kwargs 71 self._controls = controls = XBox() 72 super().__init__(**kwargs) 73 main_box = self 74 self.widgets: dict[str, BaseInputWidget] = dict() 75 self._curtains: dict[str, XCurtain] = dict() 76 # Widgets 77 self._reset_btn = XButton(text=self.reset_text, on_release=self.reset_defaults) 78 self._invoke_btn = XButton(text=self.invoke_text, on_release=self._do_invoke) 79 # Input Widgets 80 for name, w in widgets.items(): 81 iw_cls = INPUT_WIDGET_CLASSES[w.widget] 82 input_widget = iw_cls(w, self._do_values, self._do_invoke) 83 curtain = XCurtain(content=input_widget, showing=w.showing) 84 curtain.set_size(y=input_widget.height) 85 self.widgets[name] = input_widget 86 self._curtains[name] = curtain 87 main_box.add_widget(curtain) 88 # Controls 89 controls = XBox() 90 if self.reset_text: 91 controls.add_widget(XAnchor.wrap(self._reset_btn, padding=("5dp", 0))) 92 if self.invoke_text: 93 controls.add_widget(XAnchor.wrap(self._invoke_btn, padding=("5dp", 0))) 94 if len(controls.children) == 1: 95 controls.set_size(hx=1 if self.fill_button else 0.5) 96 controls = XAnchor.wrap(controls) 97 controls.set_size(y=HEIGHT_UNIT) 98 if len(controls.children) > 0: 99 main_box.add_widget(controls) 100 # Bindings 101 self.bind( 102 reset_text=self._on_reset_text, 103 invoke_text=self._on_invoke_text, 104 ) 105 self.register_event_type("on_invoke") 106 self.register_event_type("on_values")
Initialize the class.
Arguments:
- widgets: Dictionary of names to widgets.
def
get_value(self, widget_name: str, /) -> Any:
115 def get_value(self, widget_name: str, /) -> Any: 116 """Get a value by name.""" 117 widget = self.widgets[widget_name] 118 return widget.get_value()
Get a value by name.
def
get_values(self) -> dict[str, typing.Any]:
120 def get_values(self) -> dict[str, Any]: 121 """Get all values.""" 122 return {name: iw.get_value() for name, iw in self.widgets.items()}
Get all values.
def
reset_defaults(self, *args, **kwargs):
124 def reset_defaults(self, *args, **kwargs): 125 """Reset all values to their defaults.""" 126 for iw in self.widgets.values(): 127 iw.set_value()
Reset all values to their defaults.
def
set_focus(self, widget_name: str, /):
129 def set_focus(self, widget_name: str, /): 130 """Focus a widget by name.""" 131 widget = self.widgets[widget_name] 132 widget.set_focus()
Focus a widget by name.
def
set_enabled(self, widget_name: str, set_as: bool = True, /):
134 def set_enabled(self, widget_name: str, set_as: bool = True, /): 135 """Enable or disable a widget by name.""" 136 widget = self.widgets[widget_name] 137 widget.set_enabled(set_as)
Enable or disable a widget by name.
def
set_showing(self, widget_name: str, set_as: bool = True, /):
139 def set_showing(self, widget_name: str, set_as: bool = True, /): 140 """Show or hide a widget by name.""" 141 curtain = self._curtains[widget_name] 142 curtain.showing = set_as
Show or hide a widget by name.
def
on_invoke(self, values: dict):
144 def on_invoke(self, values: dict): 145 """Triggered when the invoke button is pressed or otherwise sent by user.""" 146 pass
Triggered when the invoke button is pressed or otherwise sent by user.
Inherited Members
- kivy.uix.gridlayout.GridLayout
- spacing
- padding
- cols
- rows
- col_default_width
- row_default_height
- col_force_default
- row_force_default
- cols_minimum
- rows_minimum
- minimum_width
- minimum_height
- minimum_size
- orientation
- get_max_widgets
- on_children
- do_layout
- kivy.uix.layout.Layout
- remove_widget
- layout_hint_with_bounds
- kivy.uix.widget.Widget
- proxy_ref
- apply_class_lang_rules
- collide_point
- collide_widget
- on_motion
- on_touch_down
- on_touch_move
- on_touch_up
- on_kv_post
- clear_widgets
- register_for_motion_event
- unregister_for_motion_event
- export_to_png
- export_as_image
- get_root_window
- get_parent_window
- walk
- walk_reverse
- to_widget
- to_window
- to_parent
- to_local
- get_window_matrix
- x
- y
- width
- height
- pos
- size
- get_right
- set_right
- right
- get_top
- set_top
- top
- get_center_x
- set_center_x
- center_x
- get_center_y
- set_center_y
- center_y
- center
- cls
- children
- parent
- size_hint_x
- size_hint_y
- size_hint
- pos_hint
- size_hint_min_x
- size_hint_min_y
- size_hint_min
- size_hint_max_x
- size_hint_max_y
- size_hint_max
- ids
- opacity
- on_opacity
- canvas
- get_disabled
- set_disabled
- inc_disabled
- dec_disabled
- disabled
- motion_filter
- kivy._event.EventDispatcher
- register_event_type
- unregister_event_types
- unregister_event_type
- is_event_type
- bind
- unbind
- fbind
- funbind
- unbind_uid
- get_property_observers
- events
- dispatch
- dispatch_generic
- dispatch_children
- setter
- getter
- property
- properties
- create_property
- apply_property
@dataclass
class
XInputPanelWidget:
20@dataclass 21class XInputPanelWidget: 22 """Dataclass to configure a specific `XInputPanel` widget.""" 23 24 label: str 25 """Label text.""" 26 widget: str = "str" 27 """Widget type, one of `INPUT_WIDGET_TYPES`.""" 28 default: Any = None 29 """Default value of the input widget.""" 30 orientation: str = "horizontal" 31 """Orientation between label and input widget.""" 32 showing: bool = True 33 """If widget should be showing.""" 34 label_hint: float = 1 35 """Size hint of the label relative to the input widget.""" 36 italic: bool = True 37 """Italicized label.""" 38 bold: bool = False 39 """Enboldened label.""" 40 halign: Optional[str] = None 41 """Label text horizontal alignment.""" 42 choices: list = field(default_factory=list) 43 """Used by choice widgets."""
Dataclass to configure a specific XInputPanel
widget.
INPUT_WIDGET_TYPES = ('str', 'bool', 'int', 'float', 'password', 'choice')
Input widget types.