[MouseFox logo]
The MouseFox Project
Join The Community Discord Server
[Discord logo]
Edit on GitHub

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)
class XHotkeyController(kvex.widgets.widget.XWidget, kivy.uix.widget.Widget):
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.

XHotkeyController( *, name: str = 'Unnamed', logger: Callable[[str], Any] = <built-in function print>, log_press: bool = False, log_release: bool = False, log_register: bool = False, log_bind: bool = False, log_callback: bool = False, log_active: bool = False, honor_numlock: bool = True)
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.
active

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.

@staticmethod
def humanize_hotkey(hotkey: ~HotkeyFormat, /) -> str:
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.

def register( self, control: str, hotkey: ~HotkeyFormat, /, bind: Optional[Callable] = None):
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.

def bind( self, control: str, callback: Callable, *, allow_overwrite: bool = True):
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.

def set_active(self, path: Optional[str], /):
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.

def set_active_callback(self, path: str, callback: Callable, /):
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())
def is_active(self, control: str, /) -> bool:
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.

def invoke(self, control: str, /) -> Any:
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.
def debug(self, *args, **kwargs):
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