kvex.widgets.hotkeycontroller
XHotkeyController.
For simple use cases, use XHotkeyController.register
to register and bind your
hotkeys. For more advanced use cases, the XHotkeyController class provides a way to set
which controls are active using a tree.
Hotkeys
Hotkeys are represented using strings with a simple format: modifiers then key name. The modifiers are as follows: '^' ctrl, '!' alt, '+' shift, '#' super/meta. E.g. 'g', 'f1', '^+ s'.
Set XHotkeyController.log_press
to see key names as they are pressed.
Control tree
A control can be named with a path using dot (.
) notation. For example a control named
'root.app.login' will be the 'login' control with the path 'root.app'.
The XHotkeyController.active
property determines which controls are active based on
their paths. A control is active when it's path is part of the active
path. E.g. the
active path 'root.app' will enable controls 'root.app.login' and 'root.quit' but not
'root.debug.log' and not 'root.app.game.move'. See also XHotkeyController.is_active
.
1"""XHotkeyController. 2 3For simple use cases, use `XHotkeyController.register` to register and bind your 4hotkeys. For more advanced use cases, the XHotkeyController class provides a way to set 5which controls are active using a tree. 6 7### Hotkeys 8Hotkeys are represented using strings with a simple format: modifiers then key name. The 9modifiers are as follows: '^' ctrl, '!' alt, '+' shift, '#' super/meta. E.g. 'g', 'f1', 10'^+ s'. 11 12Set `XHotkeyController.log_press` to see key names as they are pressed. 13 14### Control tree 15A control can be named with a path using dot (`.`) notation. For example a control named 16'root.app.login' will be the 'login' control with the path 'root.app'. 17 18The `XHotkeyController.active` property determines which controls are active based on 19their paths. A control is active when it's path is part of the `active` path. E.g. the 20active path 'root.app' will enable controls 'root.app.login' and 'root.quit' but not 21'root.debug.log' and not 'root.app.game.move'. See also `XHotkeyController.is_active`. 22""" 23 24from typing import TypeVar, Callable, Any, Optional 25import collections 26from .. import kivy as kv 27from .widget import XWidget 28 29 30KEYCODE_TEXT = {v: k for k, v in kv.Keyboard.keycodes.items()} 31HotkeyFormat = TypeVar("HotkeyFormat", bound=str) 32"""A type alias for a string formatted as either: `f'{modifiers} {key}'` or `key`.""" 33MODIFIER_SORT = "^!+#" 34KEY2MOD = { 35 "ctrl": "^", 36 "alt-gr": "!", 37 "alt": "!", 38 "shift": "+", 39 "super": "#", 40 "meta": "#", 41 "control": "^", 42 "lctrl": "^", 43 "rctrl": "^", 44 "lalt": "!", 45 "ralt": "!", 46 "lshift": "+", 47 "rshift": "+", 48 "numlock": "", 49 "capslock": "", 50} 51MOD2KEY = { 52 "^": "ctrl", 53 "!": "alt", 54 "+": "shift", 55 "#": "super", 56} 57 58 59def _to_hotkey_format( 60 key_name: str, 61 modifiers: list[str], 62 /, 63 *, 64 honor_numlock: bool = True, 65) -> HotkeyFormat: 66 """Convert a combination of keys to a standard string format.""" 67 if ( 68 honor_numlock 69 and "numlock" in modifiers 70 and key_name.startswith("numpad") 71 and len(key_name) == 7 72 ): 73 key_name = key_name[-1] 74 assert isinstance(int(key_name), int) # Sanity check that we have a number key 75 # Remove duplicate modifiers 76 modifiers = {KEY2MOD[mod] for mod in modifiers} 77 modifiers -= {""} 78 # Remove modifier if it is the main key being pressed 79 # e.g. when key_name == "lctrl" then "ctrl" will be in modifiers 80 keyname_as_mod = KEY2MOD.get(key_name) 81 if keyname_as_mod: 82 modifiers -= {keyname_as_mod} 83 # No space required in HotkeyFormat if no modifiers 84 if len(modifiers) == 0: 85 return key_name 86 # Order of modifiers should be consistent 87 sorted_modifiers = sorted(modifiers, key=lambda x: MODIFIER_SORT.index(x)) 88 # Return the HotkeyFormat 89 mod_str = "".join(sorted_modifiers) 90 return f"{mod_str} {key_name}" 91 92 93def _fix_modifier_order(k: str) -> str: 94 if " " not in k: 95 return k 96 mods, key = k.split(" ") 97 sorted_mods = "".join(sorted(mods, key=lambda x: MODIFIER_SORT.index(x))) 98 return f"{sorted_mods} {key}" 99 100 101class XHotkeyController(XWidget, kv.Widget): 102 """See module documentation for details.""" 103 104 _active_path = "" 105 106 def _get_active(self): 107 return self._active_path 108 109 def _set_active(self, path: Optional[str], /): 110 if path == self._active_path: 111 return False 112 assert path is None or isinstance(path, str) 113 if path in self.registered_controls: 114 raise RuntimeError( 115 f"Cannot set to control {path!r} as active." 116 f" Try setting the parent path instead." 117 ) 118 self._active_path = path 119 if self.log_active: 120 self.logger(f"{self.name!r} active path: {path!r}") 121 callback = self._active_path_callbacks.get(path) 122 if callback: 123 callback() 124 return True 125 126 active = kv.AliasProperty(_get_active, _set_active) 127 """Currently active path. 128 129 Setting a non-empty string will filter active controls. Setting None will disable 130 all controls, and setting an empty string will enable all controls. See module 131 documentation for details. 132 """ 133 134 def __init__( 135 self, 136 *, 137 name: str = "Unnamed", 138 logger: Callable[[str], Any] = print, 139 log_press: bool = False, 140 log_release: bool = False, 141 log_register: bool = False, 142 log_bind: bool = False, 143 log_callback: bool = False, 144 log_active: bool = False, 145 honor_numlock: bool = True, 146 ): 147 """Initialize the class. 148 149 Args: 150 logger: Function to pass logging messages. 151 name: Controller name, used for logging. 152 log_press: Log key presses. 153 log_release: Log key releases. 154 log_register: Log control registration. 155 log_bind: Log control binding. 156 log_callback: Log control events. 157 log_active: Log active path. 158 honor_numlock: Convert numpad keys to numbers when numlock is 159 enabled. 160 """ 161 self.registered_controls: set[str] = set() 162 self._callbacks: dict[str, Callable] = dict() 163 self._hotkeys: dict[str, set[str]] = collections.defaultdict(set) 164 self._active_path_callbacks: dict[str, Callable] = dict() 165 self.name = name 166 self.logger: Callable = logger 167 self.log_press: bool = log_press 168 self.log_release: bool = log_release 169 self.log_register: bool = log_register 170 self.log_bind: bool = log_bind 171 self.log_callback: bool = log_callback 172 self.log_active: bool = log_active 173 self.honor_numlock: bool = honor_numlock 174 kv.Window.bind(on_key_down=self._on_key_down, on_key_up=self._on_key_up) 175 176 @staticmethod 177 def humanize_hotkey(hotkey: HotkeyFormat, /) -> str: 178 """Generate a human-readable string from a HotkeyFormat.""" 179 mods, key = hotkey.split(" ") if " " in hotkey else ([], hotkey) 180 dstr = [MOD2KEY[mod] for mod in mods] 181 dstr.append(key) 182 return " + ".join(dstr) 183 184 def register( 185 self, 186 control: str, 187 hotkey: HotkeyFormat, 188 /, 189 bind: Optional[Callable] = None, 190 ): 191 """Register a control with a hotkey. 192 193 Can be used multiple times on the same control or hotkey. 194 """ 195 if self.log_register: 196 self.logger(f"{self.name!r} registering {control=} with {hotkey=}") 197 self._hotkeys[hotkey].add(control) 198 self.registered_controls.add(control) 199 if bind: 200 self.bind(control, bind) 201 202 def bind(self, control: str, callback: Callable, *, allow_overwrite: bool = True): 203 """Bind a control to a callback.""" 204 if not allow_overwrite and control in self._callbacks: 205 raise KeyError(f"Control {control!r} already bound.") 206 if control not in self.registered_controls: 207 raise RuntimeError( 208 f"Cannot bind to unregistered control {control!r}." 209 f" Use XHotkeyController.register()." 210 ) 211 if self.log_bind: 212 self.logger(f"{self.name!r} binding {control=} to {callback=}") 213 self._callbacks[control] = callback 214 215 def set_active(self, path: Optional[str], /): 216 """Set the currently active path. 217 218 Passing None will disable all controls. Passing an empty string will 219 enable all controls. See module documentation for details. 220 """ 221 self.active_path = path 222 223 def set_active_callback(self, path: str, callback: Callable, /): 224 """Set a callback for when a path is activated. 225 226 Equivalent to: 227 ```python3 228 def callback(): 229 if active_path == path: 230 ... 231 232 controller.bind(active=lambda *args: callback()) 233 ``` 234 """ 235 self._active_path_callbacks[path] = callback 236 237 def is_active(self, control: str, /) -> bool: 238 """Check if a control is active. See module documentation for details.""" 239 if self.active is None: 240 return False 241 if self.active == "": 242 return True 243 control_path = ".".join(control.split(".")[:-1]) 244 return self.active.startswith(control_path) 245 246 def invoke(self, control: str, /) -> Any: 247 """Invoke a control's callback. 248 249 Returns: 250 Return value of the control's callback. 251 252 Raises: 253 `MissingCallbackError` if control has no callback. 254 """ 255 callback = self._callbacks.get(control) 256 if not callback: 257 raise MissingCallbackError(f"{control!r} has no callback.") 258 if self.log_callback: 259 self.logger(f"{self.name!r} invoking {control!r} control: {callback}") 260 return callback() 261 262 def _on_key_down( 263 self, 264 window: kv.Window, 265 key: int, 266 scancode: int, 267 codepoint: str, 268 modifiers: list[str], 269 ): 270 key_name = KEYCODE_TEXT.get(key, "") 271 kf = _to_hotkey_format(key_name, modifiers, honor_numlock=self.honor_numlock) 272 if self.log_press: 273 self.logger( 274 f"{self.name!r} pressed: {kf!r}" 275 f" ({key=} {scancode=} {codepoint=} {modifiers=})" 276 ) 277 paths = self._hotkeys.get(kf, set()) 278 consumed = False 279 for path in paths: 280 # Skip if path is not active 281 if not self.is_active(path): 282 continue 283 # Try invoking 284 try: 285 self.invoke(path) 286 except MissingCallbackError: 287 continue 288 consumed = True 289 break 290 return consumed 291 292 def _on_key_up(self, window: kv.Window, key: int, scancode: int): 293 if self.log_release: 294 key_name = KEYCODE_TEXT.get(key, "") 295 self.logger(f"{self.name!r} released: {key_name!r}") 296 297 def debug(self, *args, **kwargs): 298 """Send debug info to logger.""" 299 active_controls = [] 300 for path in sorted(self.registered_controls): 301 active = " ACTIVE" if self.is_active(path) else "INACTIVE" 302 callback = self._callbacks.get(path) 303 bound = f"\n -> {callback}" if callback else "" 304 active_controls.append(f" {active} {repr(path):<50}{bound}") 305 strs = [ 306 repr(self), 307 f"Active path: {self.active!r}", 308 "Bound hotkeys:", 309 *( 310 f" {repr(hk):>20} {control!r}" 311 for hk, control in self._hotkeys.items() 312 ), 313 "Active controls:", 314 *active_controls, 315 ] 316 self.logger("\n".join(strs)) 317 318 def __repr__(self): 319 """Repr.""" 320 return f"<{self.__class__.__qualname__} '{self.name}' @ {id(self):x}>" 321 322 323class MissingCallbackError(Exception): 324 """Called when trying to invoke a control without a callback.""" 325 326 pass 327 328 329__all__ = ( 330 "XHotkeyController", 331)
102class XHotkeyController(XWidget, kv.Widget): 103 """See module documentation for details.""" 104 105 _active_path = "" 106 107 def _get_active(self): 108 return self._active_path 109 110 def _set_active(self, path: Optional[str], /): 111 if path == self._active_path: 112 return False 113 assert path is None or isinstance(path, str) 114 if path in self.registered_controls: 115 raise RuntimeError( 116 f"Cannot set to control {path!r} as active." 117 f" Try setting the parent path instead." 118 ) 119 self._active_path = path 120 if self.log_active: 121 self.logger(f"{self.name!r} active path: {path!r}") 122 callback = self._active_path_callbacks.get(path) 123 if callback: 124 callback() 125 return True 126 127 active = kv.AliasProperty(_get_active, _set_active) 128 """Currently active path. 129 130 Setting a non-empty string will filter active controls. Setting None will disable 131 all controls, and setting an empty string will enable all controls. See module 132 documentation for details. 133 """ 134 135 def __init__( 136 self, 137 *, 138 name: str = "Unnamed", 139 logger: Callable[[str], Any] = print, 140 log_press: bool = False, 141 log_release: bool = False, 142 log_register: bool = False, 143 log_bind: bool = False, 144 log_callback: bool = False, 145 log_active: bool = False, 146 honor_numlock: bool = True, 147 ): 148 """Initialize the class. 149 150 Args: 151 logger: Function to pass logging messages. 152 name: Controller name, used for logging. 153 log_press: Log key presses. 154 log_release: Log key releases. 155 log_register: Log control registration. 156 log_bind: Log control binding. 157 log_callback: Log control events. 158 log_active: Log active path. 159 honor_numlock: Convert numpad keys to numbers when numlock is 160 enabled. 161 """ 162 self.registered_controls: set[str] = set() 163 self._callbacks: dict[str, Callable] = dict() 164 self._hotkeys: dict[str, set[str]] = collections.defaultdict(set) 165 self._active_path_callbacks: dict[str, Callable] = dict() 166 self.name = name 167 self.logger: Callable = logger 168 self.log_press: bool = log_press 169 self.log_release: bool = log_release 170 self.log_register: bool = log_register 171 self.log_bind: bool = log_bind 172 self.log_callback: bool = log_callback 173 self.log_active: bool = log_active 174 self.honor_numlock: bool = honor_numlock 175 kv.Window.bind(on_key_down=self._on_key_down, on_key_up=self._on_key_up) 176 177 @staticmethod 178 def humanize_hotkey(hotkey: HotkeyFormat, /) -> str: 179 """Generate a human-readable string from a HotkeyFormat.""" 180 mods, key = hotkey.split(" ") if " " in hotkey else ([], hotkey) 181 dstr = [MOD2KEY[mod] for mod in mods] 182 dstr.append(key) 183 return " + ".join(dstr) 184 185 def register( 186 self, 187 control: str, 188 hotkey: HotkeyFormat, 189 /, 190 bind: Optional[Callable] = None, 191 ): 192 """Register a control with a hotkey. 193 194 Can be used multiple times on the same control or hotkey. 195 """ 196 if self.log_register: 197 self.logger(f"{self.name!r} registering {control=} with {hotkey=}") 198 self._hotkeys[hotkey].add(control) 199 self.registered_controls.add(control) 200 if bind: 201 self.bind(control, bind) 202 203 def bind(self, control: str, callback: Callable, *, allow_overwrite: bool = True): 204 """Bind a control to a callback.""" 205 if not allow_overwrite and control in self._callbacks: 206 raise KeyError(f"Control {control!r} already bound.") 207 if control not in self.registered_controls: 208 raise RuntimeError( 209 f"Cannot bind to unregistered control {control!r}." 210 f" Use XHotkeyController.register()." 211 ) 212 if self.log_bind: 213 self.logger(f"{self.name!r} binding {control=} to {callback=}") 214 self._callbacks[control] = callback 215 216 def set_active(self, path: Optional[str], /): 217 """Set the currently active path. 218 219 Passing None will disable all controls. Passing an empty string will 220 enable all controls. See module documentation for details. 221 """ 222 self.active_path = path 223 224 def set_active_callback(self, path: str, callback: Callable, /): 225 """Set a callback for when a path is activated. 226 227 Equivalent to: 228 ```python3 229 def callback(): 230 if active_path == path: 231 ... 232 233 controller.bind(active=lambda *args: callback()) 234 ``` 235 """ 236 self._active_path_callbacks[path] = callback 237 238 def is_active(self, control: str, /) -> bool: 239 """Check if a control is active. See module documentation for details.""" 240 if self.active is None: 241 return False 242 if self.active == "": 243 return True 244 control_path = ".".join(control.split(".")[:-1]) 245 return self.active.startswith(control_path) 246 247 def invoke(self, control: str, /) -> Any: 248 """Invoke a control's callback. 249 250 Returns: 251 Return value of the control's callback. 252 253 Raises: 254 `MissingCallbackError` if control has no callback. 255 """ 256 callback = self._callbacks.get(control) 257 if not callback: 258 raise MissingCallbackError(f"{control!r} has no callback.") 259 if self.log_callback: 260 self.logger(f"{self.name!r} invoking {control!r} control: {callback}") 261 return callback() 262 263 def _on_key_down( 264 self, 265 window: kv.Window, 266 key: int, 267 scancode: int, 268 codepoint: str, 269 modifiers: list[str], 270 ): 271 key_name = KEYCODE_TEXT.get(key, "") 272 kf = _to_hotkey_format(key_name, modifiers, honor_numlock=self.honor_numlock) 273 if self.log_press: 274 self.logger( 275 f"{self.name!r} pressed: {kf!r}" 276 f" ({key=} {scancode=} {codepoint=} {modifiers=})" 277 ) 278 paths = self._hotkeys.get(kf, set()) 279 consumed = False 280 for path in paths: 281 # Skip if path is not active 282 if not self.is_active(path): 283 continue 284 # Try invoking 285 try: 286 self.invoke(path) 287 except MissingCallbackError: 288 continue 289 consumed = True 290 break 291 return consumed 292 293 def _on_key_up(self, window: kv.Window, key: int, scancode: int): 294 if self.log_release: 295 key_name = KEYCODE_TEXT.get(key, "") 296 self.logger(f"{self.name!r} released: {key_name!r}") 297 298 def debug(self, *args, **kwargs): 299 """Send debug info to logger.""" 300 active_controls = [] 301 for path in sorted(self.registered_controls): 302 active = " ACTIVE" if self.is_active(path) else "INACTIVE" 303 callback = self._callbacks.get(path) 304 bound = f"\n -> {callback}" if callback else "" 305 active_controls.append(f" {active} {repr(path):<50}{bound}") 306 strs = [ 307 repr(self), 308 f"Active path: {self.active!r}", 309 "Bound hotkeys:", 310 *( 311 f" {repr(hk):>20} {control!r}" 312 for hk, control in self._hotkeys.items() 313 ), 314 "Active controls:", 315 *active_controls, 316 ] 317 self.logger("\n".join(strs)) 318 319 def __repr__(self): 320 """Repr.""" 321 return f"<{self.__class__.__qualname__} '{self.name}' @ {id(self):x}>"
See module documentation for details.
135 def __init__( 136 self, 137 *, 138 name: str = "Unnamed", 139 logger: Callable[[str], Any] = print, 140 log_press: bool = False, 141 log_release: bool = False, 142 log_register: bool = False, 143 log_bind: bool = False, 144 log_callback: bool = False, 145 log_active: bool = False, 146 honor_numlock: bool = True, 147 ): 148 """Initialize the class. 149 150 Args: 151 logger: Function to pass logging messages. 152 name: Controller name, used for logging. 153 log_press: Log key presses. 154 log_release: Log key releases. 155 log_register: Log control registration. 156 log_bind: Log control binding. 157 log_callback: Log control events. 158 log_active: Log active path. 159 honor_numlock: Convert numpad keys to numbers when numlock is 160 enabled. 161 """ 162 self.registered_controls: set[str] = set() 163 self._callbacks: dict[str, Callable] = dict() 164 self._hotkeys: dict[str, set[str]] = collections.defaultdict(set) 165 self._active_path_callbacks: dict[str, Callable] = dict() 166 self.name = name 167 self.logger: Callable = logger 168 self.log_press: bool = log_press 169 self.log_release: bool = log_release 170 self.log_register: bool = log_register 171 self.log_bind: bool = log_bind 172 self.log_callback: bool = log_callback 173 self.log_active: bool = log_active 174 self.honor_numlock: bool = honor_numlock 175 kv.Window.bind(on_key_down=self._on_key_down, on_key_up=self._on_key_up)
Initialize the class.
Arguments:
- logger: Function to pass logging messages.
- name: Controller name, used for logging.
- log_press: Log key presses.
- log_release: Log key releases.
- log_register: Log control registration.
- log_bind: Log control binding.
- log_callback: Log control events.
- log_active: Log active path.
- honor_numlock: Convert numpad keys to numbers when numlock is enabled.
Currently active path.
Setting a non-empty string will filter active controls. Setting None will disable all controls, and setting an empty string will enable all controls. See module documentation for details.
177 @staticmethod 178 def humanize_hotkey(hotkey: HotkeyFormat, /) -> str: 179 """Generate a human-readable string from a HotkeyFormat.""" 180 mods, key = hotkey.split(" ") if " " in hotkey else ([], hotkey) 181 dstr = [MOD2KEY[mod] for mod in mods] 182 dstr.append(key) 183 return " + ".join(dstr)
Generate a human-readable string from a HotkeyFormat.
185 def register( 186 self, 187 control: str, 188 hotkey: HotkeyFormat, 189 /, 190 bind: Optional[Callable] = None, 191 ): 192 """Register a control with a hotkey. 193 194 Can be used multiple times on the same control or hotkey. 195 """ 196 if self.log_register: 197 self.logger(f"{self.name!r} registering {control=} with {hotkey=}") 198 self._hotkeys[hotkey].add(control) 199 self.registered_controls.add(control) 200 if bind: 201 self.bind(control, bind)
Register a control with a hotkey.
Can be used multiple times on the same control or hotkey.
203 def bind(self, control: str, callback: Callable, *, allow_overwrite: bool = True): 204 """Bind a control to a callback.""" 205 if not allow_overwrite and control in self._callbacks: 206 raise KeyError(f"Control {control!r} already bound.") 207 if control not in self.registered_controls: 208 raise RuntimeError( 209 f"Cannot bind to unregistered control {control!r}." 210 f" Use XHotkeyController.register()." 211 ) 212 if self.log_bind: 213 self.logger(f"{self.name!r} binding {control=} to {callback=}") 214 self._callbacks[control] = callback
Bind a control to a callback.
216 def set_active(self, path: Optional[str], /): 217 """Set the currently active path. 218 219 Passing None will disable all controls. Passing an empty string will 220 enable all controls. See module documentation for details. 221 """ 222 self.active_path = path
Set the currently active path.
Passing None will disable all controls. Passing an empty string will enable all controls. See module documentation for details.
224 def set_active_callback(self, path: str, callback: Callable, /): 225 """Set a callback for when a path is activated. 226 227 Equivalent to: 228 ```python3 229 def callback(): 230 if active_path == path: 231 ... 232 233 controller.bind(active=lambda *args: callback()) 234 ``` 235 """ 236 self._active_path_callbacks[path] = callback
Set a callback for when a path is activated.
Equivalent to:
def callback():
if active_path == path:
...
controller.bind(active=lambda *args: callback())
238 def is_active(self, control: str, /) -> bool: 239 """Check if a control is active. See module documentation for details.""" 240 if self.active is None: 241 return False 242 if self.active == "": 243 return True 244 control_path = ".".join(control.split(".")[:-1]) 245 return self.active.startswith(control_path)
Check if a control is active. See module documentation for details.
247 def invoke(self, control: str, /) -> Any: 248 """Invoke a control's callback. 249 250 Returns: 251 Return value of the control's callback. 252 253 Raises: 254 `MissingCallbackError` if control has no callback. 255 """ 256 callback = self._callbacks.get(control) 257 if not callback: 258 raise MissingCallbackError(f"{control!r} has no callback.") 259 if self.log_callback: 260 self.logger(f"{self.name!r} invoking {control!r} control: {callback}") 261 return callback()
Invoke a control's callback.
Returns:
Return value of the control's callback.
Raises:
MissingCallbackError
if control has no callback.
298 def debug(self, *args, **kwargs): 299 """Send debug info to logger.""" 300 active_controls = [] 301 for path in sorted(self.registered_controls): 302 active = " ACTIVE" if self.is_active(path) else "INACTIVE" 303 callback = self._callbacks.get(path) 304 bound = f"\n -> {callback}" if callback else "" 305 active_controls.append(f" {active} {repr(path):<50}{bound}") 306 strs = [ 307 repr(self), 308 f"Active path: {self.active!r}", 309 "Bound hotkeys:", 310 *( 311 f" {repr(hk):>20} {control!r}" 312 for hk, control in self._hotkeys.items() 313 ), 314 "Active controls:", 315 *active_controls, 316 ] 317 self.logger("\n".join(strs))
Send debug info to logger.
Inherited Members
- 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
- add_widget
- remove_widget
- 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
- unbind
- fbind
- funbind
- unbind_uid
- get_property_observers
- events
- dispatch
- dispatch_generic
- dispatch_children
- setter
- getter
- property
- properties
- create_property
- apply_property