Module botroyale.gui.kex.widgets.app

App and associated widgets.

Generally, to use the GUI, one would initialize and then run an App:

app = XApp()
app.hook(func_to_call_every_frame, fps=20)
app.run()
Expand source code Browse git
"""App and associated widgets.

Generally, to use the GUI, one would initialize and then run an `App`:
```python
app = XApp()
app.hook(func_to_call_every_frame, fps=20)
app.run()
```
"""
from typing import Callable, Optional
from functools import partial
from .. import kivy as kv
from ..util import (
    XColor,
    XWindow,
    XWidget,
    consume_args,
    SimpleCallable,
    queue_around_frame,
)
from .layouts import XAnchor
from .uix import XLabel


class XOverlay(kv.FocusBehavior, XAnchor):
    """Overlay to be displayed on top of other widgets."""

    def __init__(self, **kwargs):
        """Initialize like an XAnchor."""
        super().__init__()
        self.make_bg(XColor(a=0.5))
        self.label = self.add(XLabel(**kwargs))
        self.label.set_size(x=500, y=150)
        self.label.make_bg(XColor.from_name("red", 0.15))


class XRoot(kv.FocusBehavior, XAnchor):
    """Root widget for the app, with FocusBehavior."""

    pass


class XApp(XWidget, kv.App):
    """See module documentation for details."""

    block_input = kv.BooleanProperty(False)
    """If all user input should be blocked."""

    def __init__(
        self,
        logger: Optional[Callable[[str], None]] = None,
        escape_exits: bool = False,
        enable_multitouch: bool = False,
        **kwargs,
    ):
        """Initialize the class.

        Args:
            logger: Logging function for XWidgets. Used internally for debugging.
            escape_exits: Enable exiting when the escape key is pressed.
            enable_multitouch: Enable multitouch.
        """
        super().__init__(**kwargs)
        XWindow.enable_escape_exit(escape_exits)
        self.root = XRoot()
        self.keyboard = kv.Window.request_keyboard(consume_args, self.root)
        self.__last_focused = None
        self.__overlay = None
        self.__enable_multitouch = enable_multitouch
        if not enable_multitouch:
            XWindow.disable_multitouch()
        self.root.bind(
            on_touch_down=self._is_touch_blocked,
            on_touch_up=self._is_touch_blocked,
            on_touch_move=self._is_touch_blocked,
        )

    def logger(self, message: str):
        """Log a message in the app's logger."""
        if self.logger:
            self.logger(message)

    def hook(self, func: Callable[[float], None], fps: float):
        """Schedule *func* to be called *fps* times per seconds."""
        kv.Clock.schedule_once(
            lambda *a: kv.Clock.schedule_interval(func, 1 / fps),
            0,
        )

    def open_settings(self, *args) -> False:
        """Overrides base class method to disable the builtin settings widget."""
        return False

    @property
    def mouse_pos(self) -> tuple[float, float]:
        """The current position of the mouse."""
        return XWindow.mouse_pos

    def add(self, *args, **kwargs):
        """Add a widget to the root widget."""
        return self.root.add(*args, **kwargs)

    @property
    def current_focus(self) -> kv.Widget:
        """The widget currently in focus."""
        return self.keyboard.target

    @property
    def overlay(self) -> Optional[XOverlay]:
        """The current overlay."""
        return self.__overlay

    def _is_touch_blocked(self, w, m):
        if self.block_input:
            return True
        if not self.__enable_multitouch:
            if hasattr(m, "multitouch_sim") and m.multitouch_sim:
                return True
        return False

    def __create_overlay(self, **kwargs):
        self.__last_focused = self.current_focus
        self.__overlay = XOverlay(**kwargs)
        self.__overlay.focus = True
        self.block_input = True
        self.add(self.__overlay)

    def __destroy_overlay(self, after: Optional[SimpleCallable] = None):
        if self.__last_focused is not None:
            self.__last_focused.focus = True
        self.root.remove_widget(self.__overlay)
        self.__overlay = None
        self.block_input = False
        if after is not None:
            after()

    def with_overlay(
        self,
        func: SimpleCallable,
        after: Optional[SimpleCallable] = None,
        **kwargs,
    ):
        """Queue a function with a temporary `XOverlay` that blocks input.

        Uses the `botroyale.gui.kex.util.queue_around_frame` decorator to draw
        a frame before calling the function, otherwise the added overlay will
        not be seen until execution is yielded to kivy's clock.

        Example usage:
        ```python
        with_overlay(
            func=lambda: my_func(arg1=True),
            text="my_func is executing...",
            after=lambda: print("finished executing my_func."),
        )
        ```

        Args:
            func: Callback to queue after adding the overlay.
            after: Optionally call after removing the overlay.
            kwargs: Keyword arguments for the XOverlay object.
        """
        if self.__overlay is not None:
            raise RuntimeError("Cannot create an overlay when one already exists.")
        queue_around_frame(
            func,
            before=partial(self.__create_overlay, **kwargs),
            after=partial(self.__destroy_overlay, after),
        )()

Classes

class XApp (logger: Optional[Callable[[str], None]] = None, escape_exits: bool = False, enable_multitouch: bool = False, **kwargs)

See module documentation for details.

Initialize the class.

Args
-----=
logger
Logging function for XWidgets. Used internally for debugging.
escape_exits
Enable exiting when the escape key is pressed.
enable_multitouch
Enable multitouch.
Expand source code Browse git
class XApp(XWidget, kv.App):
    """See module documentation for details."""

    block_input = kv.BooleanProperty(False)
    """If all user input should be blocked."""

    def __init__(
        self,
        logger: Optional[Callable[[str], None]] = None,
        escape_exits: bool = False,
        enable_multitouch: bool = False,
        **kwargs,
    ):
        """Initialize the class.

        Args:
            logger: Logging function for XWidgets. Used internally for debugging.
            escape_exits: Enable exiting when the escape key is pressed.
            enable_multitouch: Enable multitouch.
        """
        super().__init__(**kwargs)
        XWindow.enable_escape_exit(escape_exits)
        self.root = XRoot()
        self.keyboard = kv.Window.request_keyboard(consume_args, self.root)
        self.__last_focused = None
        self.__overlay = None
        self.__enable_multitouch = enable_multitouch
        if not enable_multitouch:
            XWindow.disable_multitouch()
        self.root.bind(
            on_touch_down=self._is_touch_blocked,
            on_touch_up=self._is_touch_blocked,
            on_touch_move=self._is_touch_blocked,
        )

    def logger(self, message: str):
        """Log a message in the app's logger."""
        if self.logger:
            self.logger(message)

    def hook(self, func: Callable[[float], None], fps: float):
        """Schedule *func* to be called *fps* times per seconds."""
        kv.Clock.schedule_once(
            lambda *a: kv.Clock.schedule_interval(func, 1 / fps),
            0,
        )

    def open_settings(self, *args) -> False:
        """Overrides base class method to disable the builtin settings widget."""
        return False

    @property
    def mouse_pos(self) -> tuple[float, float]:
        """The current position of the mouse."""
        return XWindow.mouse_pos

    def add(self, *args, **kwargs):
        """Add a widget to the root widget."""
        return self.root.add(*args, **kwargs)

    @property
    def current_focus(self) -> kv.Widget:
        """The widget currently in focus."""
        return self.keyboard.target

    @property
    def overlay(self) -> Optional[XOverlay]:
        """The current overlay."""
        return self.__overlay

    def _is_touch_blocked(self, w, m):
        if self.block_input:
            return True
        if not self.__enable_multitouch:
            if hasattr(m, "multitouch_sim") and m.multitouch_sim:
                return True
        return False

    def __create_overlay(self, **kwargs):
        self.__last_focused = self.current_focus
        self.__overlay = XOverlay(**kwargs)
        self.__overlay.focus = True
        self.block_input = True
        self.add(self.__overlay)

    def __destroy_overlay(self, after: Optional[SimpleCallable] = None):
        if self.__last_focused is not None:
            self.__last_focused.focus = True
        self.root.remove_widget(self.__overlay)
        self.__overlay = None
        self.block_input = False
        if after is not None:
            after()

    def with_overlay(
        self,
        func: SimpleCallable,
        after: Optional[SimpleCallable] = None,
        **kwargs,
    ):
        """Queue a function with a temporary `XOverlay` that blocks input.

        Uses the `botroyale.gui.kex.util.queue_around_frame` decorator to draw
        a frame before calling the function, otherwise the added overlay will
        not be seen until execution is yielded to kivy's clock.

        Example usage:
        ```python
        with_overlay(
            func=lambda: my_func(arg1=True),
            text="my_func is executing...",
            after=lambda: print("finished executing my_func."),
        )
        ```

        Args:
            func: Callback to queue after adding the overlay.
            after: Optionally call after removing the overlay.
            kwargs: Keyword arguments for the XOverlay object.
        """
        if self.__overlay is not None:
            raise RuntimeError("Cannot create an overlay when one already exists.")
        queue_around_frame(
            func,
            before=partial(self.__create_overlay, **kwargs),
            after=partial(self.__destroy_overlay, after),
        )()

Ancestors

  • XWidget
  • kivy.app.App
  • kivy._event.EventDispatcher
  • kivy._event.ObjectWithUid

Subclasses

Instance variables

var block_input

If all user input should be blocked.

var current_focus : kivy.uix.widget.Widget

The widget currently in focus.

Expand source code Browse git
@property
def current_focus(self) -> kv.Widget:
    """The widget currently in focus."""
    return self.keyboard.target
var mouse_pos : tuple[float, float]

The current position of the mouse.

Expand source code Browse git
@property
def mouse_pos(self) -> tuple[float, float]:
    """The current position of the mouse."""
    return XWindow.mouse_pos
var overlay : Optional[XOverlay]

The current overlay.

Expand source code Browse git
@property
def overlay(self) -> Optional[XOverlay]:
    """The current overlay."""
    return self.__overlay

Methods

def add(self, *args, **kwargs)

Add a widget to the root widget.

Expand source code Browse git
def add(self, *args, **kwargs):
    """Add a widget to the root widget."""
    return self.root.add(*args, **kwargs)
def hook(self, func: Callable[[float], None], fps: float)

Schedule func to be called fps times per seconds.

Expand source code Browse git
def hook(self, func: Callable[[float], None], fps: float):
    """Schedule *func* to be called *fps* times per seconds."""
    kv.Clock.schedule_once(
        lambda *a: kv.Clock.schedule_interval(func, 1 / fps),
        0,
    )
def logger(self, message: str)

Log a message in the app's logger.

Expand source code Browse git
def logger(self, message: str):
    """Log a message in the app's logger."""
    if self.logger:
        self.logger(message)
def open_settings(self, *args)

Overrides base class method to disable the builtin settings widget.

Expand source code Browse git
def open_settings(self, *args) -> False:
    """Overrides base class method to disable the builtin settings widget."""
    return False
def with_overlay(self, func: Callable[[], None], after: Optional[Callable[[], None]] = None, **kwargs)

Queue a function with a temporary XOverlay that blocks input.

Uses the queue_around_frame() decorator to draw a frame before calling the function, otherwise the added overlay will not be seen until execution is yielded to kivy's clock.

Example usage:

with_overlay(
    func=lambda: my_func(arg1=True),
    text="my_func is executing...",
    after=lambda: print("finished executing my_func."),
)
Args
-----=
func
Callback to queue after adding the overlay.
after
Optionally call after removing the overlay.
kwargs
Keyword arguments for the XOverlay object.
Expand source code Browse git
def with_overlay(
    self,
    func: SimpleCallable,
    after: Optional[SimpleCallable] = None,
    **kwargs,
):
    """Queue a function with a temporary `XOverlay` that blocks input.

    Uses the `botroyale.gui.kex.util.queue_around_frame` decorator to draw
    a frame before calling the function, otherwise the added overlay will
    not be seen until execution is yielded to kivy's clock.

    Example usage:
    ```python
    with_overlay(
        func=lambda: my_func(arg1=True),
        text="my_func is executing...",
        after=lambda: print("finished executing my_func."),
    )
    ```

    Args:
        func: Callback to queue after adding the overlay.
        after: Optionally call after removing the overlay.
        kwargs: Keyword arguments for the XOverlay object.
    """
    if self.__overlay is not None:
        raise RuntimeError("Cannot create an overlay when one already exists.")
    queue_around_frame(
        func,
        before=partial(self.__create_overlay, **kwargs),
        after=partial(self.__destroy_overlay, after),
    )()

Inherited members

class XOverlay (**kwargs)

Overlay to be displayed on top of other widgets.

Initialize like an XAnchor.

Expand source code Browse git
class XOverlay(kv.FocusBehavior, XAnchor):
    """Overlay to be displayed on top of other widgets."""

    def __init__(self, **kwargs):
        """Initialize like an XAnchor."""
        super().__init__()
        self.make_bg(XColor(a=0.5))
        self.label = self.add(XLabel(**kwargs))
        self.label.set_size(x=500, y=150)
        self.label.make_bg(XColor.from_name("red", 0.15))

Ancestors

  • kivy.uix.behaviors.focus.FocusBehavior
  • XAnchor
  • XWidget
  • kivy.uix.anchorlayout.AnchorLayout
  • kivy.uix.layout.Layout
  • kivy.uix.widget.Widget
  • kivy.uix.widget.WidgetBase
  • kivy._event.EventDispatcher
  • kivy._event.ObjectWithUid

Inherited members

class XRoot (**kwargs)

Root widget for the app, with FocusBehavior.

Expand source code Browse git
class XRoot(kv.FocusBehavior, XAnchor):
    """Root widget for the app, with FocusBehavior."""

    pass

Ancestors

  • kivy.uix.behaviors.focus.FocusBehavior
  • XAnchor
  • XWidget
  • kivy.uix.anchorlayout.AnchorLayout
  • kivy.uix.layout.Layout
  • kivy.uix.widget.Widget
  • kivy.uix.widget.WidgetBase
  • kivy._event.EventDispatcher
  • kivy._event.ObjectWithUid

Inherited members