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

kvex.util

Kvex utilities.

  1"""Kvex utilities."""
  2
  3from typing import Optional, Any, Callable, Iterable
  4from functools import partial, wraps
  5import os
  6import sys
  7from . import kivy as kv
  8
  9
 10def queue_around_frame(
 11    func,
 12    before: Optional[Callable] = None,
 13    after: Optional[Callable] = None,
 14):
 15    """Decorator for queuing functions before and after drawing frames.
 16
 17    Used for performing GUI operations before and after functions that will
 18    block code execution for a significant period of time. Functions that would
 19    otherwise freeze the GUI without feedback can be wrapped with this decorator
 20    to give user feedback.
 21
 22    The following order of operations will be queued:
 23
 24    1. Call *before*
 25    2. Draw GUI frame
 26    3. Call the wrapped function
 27    4. Call *after*
 28
 29    ### Example usage:
 30    ```python
 31    @queue(
 32        before=lambda: print("Drawing GUI frame then executing function..."),
 33        after=lambda: print("Done executing..."),
 34    )
 35    def do_sleep():
 36        time.sleep(2)
 37    ```
 38    """
 39
 40    @wraps(func)
 41    def wrapper(*args, **kwargs):
 42        if before is not None:
 43            before()
 44        wrapped = partial(func, *args, **kwargs)
 45        kv.Clock.schedule_once(lambda dt: _call_with_after(wrapped, after), 0.05)
 46
 47    return wrapper
 48
 49
 50def _call_with_after(func: Callable, after: Optional[Callable] = None):
 51    func()
 52    if after is not None:
 53        # In order to schedule properly, we must tick or else all the time spent
 54        # calling func will be counted as time waited on kivy's clock schedule.
 55        kv.Clock.tick()
 56        kv.Clock.schedule_once(lambda dt: after(), 0.05)
 57
 58
 59def center_sprite(
 60    pos: tuple[float, float],
 61    size: tuple[float, float],
 62) -> tuple[int, int]:
 63    """Given *size* and center position *pos*, return the bottom left corner."""
 64    assert len(pos) == 2 and len(size) == 2
 65    return int(pos[0] - (size[0] / 2)), int(pos[1] - (size[1] / 2))
 66
 67
 68def text_texture(text, **kwargs):
 69    """Create a label texture using kivy.core.Label."""
 70    label = kv.CoreLabel(text=text, **kwargs)
 71    label.refresh()
 72    return label.texture
 73
 74
 75def from_atlas(name: str, /) -> str:
 76    """Get a path to a sprite name from the `defaulttheme` atlas."""
 77    return f"atlas://data/images/defaulttheme/{name}"
 78
 79
 80def restart_script(*args, **kwargs):
 81    """Restart the Python script. Ignores all arguments."""
 82    os.execl(sys.executable, sys.executable, *sys.argv)
 83
 84
 85def consume_args(*args, **kwargs):
 86    """Empty function that will consume all arguments passed to it."""
 87    pass
 88
 89
 90def placeholder(
 91    *wrapper_args,
 92    verbose: bool = False,
 93    returns: Any = None,
 94    **wrapper_kwargs,
 95) -> Callable:
 96    """Create a function That consumes all arguments and prints them."""
 97
 98    def placeholder_inner(*args, **kwargs):
 99        print(
100            f"Placeholder function {wrapper_args}{wrapper_kwargs} : "
101            f"{args}{kwargs} -> {returns=}"
102        )
103        return returns
104
105    return placeholder_inner
106
107
108create_trigger = kv.Clock.create_trigger
109schedule_once = kv.Clock.schedule_once
110schedule_interval = kv.Clock.schedule_interval
111
112
113def snooze_trigger(ev: "kivy.clock.ClockEvent"):  # noqa: F821
114    """Cancel and reschedule a ClockEvent."""
115    if ev.is_triggered:
116        ev.cancel()
117    ev()
118
119
120_metrics_dp = kv.metrics.dp
121_metrics_sp = kv.metrics.sp
122
123
124def _str2pixels(s) -> float:
125    vstr = str(s)
126    if vstr.endswith("dp"):
127        return _metrics_dp(vstr[:-2])
128    if vstr.endswith("sp"):
129        return _metrics_sp(vstr[:-2])
130    raise ValueError(f"Unkown format: {s!r} (please use 'dp' or 'sp')")
131
132
133def sp2pixels(value: float | str | Iterable[float | str]) -> float | list[float]:
134    """Convert values in 'sp', 'dp', or pixels to pixels.
135
136    Useful when wishing to convert values to pixels but it is unknown if they are given
137    in pixels or 'sp' format.
138    """
139    if isinstance(value, int) or isinstance(value, float):
140        return value
141    if isinstance(value, str):
142        return _str2pixels(value)
143    # Handle as iterable
144    values = []
145    append = values.append
146    for v in value:
147        if isinstance(v, int) or isinstance(v, float):
148            append(v)
149        elif isinstance(v, str):
150            append(_str2pixels(v))
151        else:
152            m = f"Expected float or str, instead got: {v!r} {type(v)}"
153            raise ValueError(m)
154    return values
155
156
157__all__ = (
158    "center_sprite",
159    "text_texture",
160    "from_atlas",
161    "restart_script",
162    "placeholder",
163    "consume_args",
164    "create_trigger",
165    "schedule_once",
166    "schedule_interval",
167    "snooze_trigger",
168    "queue_around_frame",
169    "sp2pixels",
170)
def center_sprite(pos: tuple[float, float], size: tuple[float, float]) -> tuple[int, int]:
60def center_sprite(
61    pos: tuple[float, float],
62    size: tuple[float, float],
63) -> tuple[int, int]:
64    """Given *size* and center position *pos*, return the bottom left corner."""
65    assert len(pos) == 2 and len(size) == 2
66    return int(pos[0] - (size[0] / 2)), int(pos[1] - (size[1] / 2))

Given size and center position pos, return the bottom left corner.

def text_texture(text, **kwargs):
69def text_texture(text, **kwargs):
70    """Create a label texture using kivy.core.Label."""
71    label = kv.CoreLabel(text=text, **kwargs)
72    label.refresh()
73    return label.texture

Create a label texture using kivy.core.Label.

def from_atlas(name: str, /) -> str:
76def from_atlas(name: str, /) -> str:
77    """Get a path to a sprite name from the `defaulttheme` atlas."""
78    return f"atlas://data/images/defaulttheme/{name}"

Get a path to a sprite name from the defaulttheme atlas.

def restart_script(*args, **kwargs):
81def restart_script(*args, **kwargs):
82    """Restart the Python script. Ignores all arguments."""
83    os.execl(sys.executable, sys.executable, *sys.argv)

Restart the Python script. Ignores all arguments.

def placeholder( *wrapper_args, verbose: bool = False, returns: Any = None, **wrapper_kwargs) -> Callable:
 91def placeholder(
 92    *wrapper_args,
 93    verbose: bool = False,
 94    returns: Any = None,
 95    **wrapper_kwargs,
 96) -> Callable:
 97    """Create a function That consumes all arguments and prints them."""
 98
 99    def placeholder_inner(*args, **kwargs):
100        print(
101            f"Placeholder function {wrapper_args}{wrapper_kwargs} : "
102            f"{args}{kwargs} -> {returns=}"
103        )
104        return returns
105
106    return placeholder_inner

Create a function That consumes all arguments and prints them.

def consume_args(*args, **kwargs):
86def consume_args(*args, **kwargs):
87    """Empty function that will consume all arguments passed to it."""
88    pass

Empty function that will consume all arguments passed to it.

def create_trigger(unknown):

CyClockBase.create_trigger(self, callback, timeout=0, interval=False, release_ref=True) -> ClockEvent Create a Trigger event. It is thread safe but not __del__ or __dealloc__ safe (see schedule_del_safe()). Check module documentation for more information.

    To cancel the event before it is executed, call `ClockEvent.cancel()`
    on the returned event.
    To schedule it again, simply call the event (``event()``) and it'll be safely
    rescheduled if it isn't already scheduled.

    :Parameters:

        `callback`: callable
            The callback to execute from kivy. It takes a single parameter - the
            current elapsed kivy time.
        `timeout`: float
            How long to wait before calling the callback.
        `interval`: bool
            Whether the callback should be called once (False) or repeatedly
            with a period of ``timeout`` (True) like `schedule_interval()`.
        `release_ref`: bool
            If True, the default, then if ``callback``
            is a class method and the object has no references to it, then
            the object may be garbage collected and the callbacks won't be called.
            If False, the clock keeps a reference to the object preventing it
            from being garbage collected - so it will be called.

    :returns:

        A `ClockEvent` instance. To schedule the callback of this
        instance, you can call it.

    *New in version 1.0.5.*

    *Changed in version 1.10.0:*
    ``interval`` has been added.


    *Changed in version 2.0.0:*
    ``release_ref`` has been added.
def schedule_once(unknown):

CyClockBase.schedule_once(self, callback, timeout=0) -> ClockEvent Schedule an event in seconds. If is unspecified or 0, the callback will be called after the next frame is rendered. See create_trigger() for advanced scheduling and more details.

    To cancel the event before it is executed, call `ClockEvent.cancel()`
    on the returned event.
    If the callback is a class method, a weakref to the object is created and it
    may be garbage collected if there's no other reference to the object.

    :returns:

        A `ClockEvent` instance. As opposed to
        `create_trigger()` which only creates the trigger event, this
        method also schedules it.

    *Changed in version 1.0.5:*
    If the timeout is -1, the callback will be called before the next
    frame (at `tick_draw()`).
def schedule_interval(unknown):

CyClockBase.schedule_interval(self, callback, timeout) -> ClockEvent Schedule an event to be called every seconds. See create_trigger() for advanced scheduling and more details.

    To cancel the event before it is executed, call `ClockEvent.cancel()`
    on the returned event.
    If the callback is a class method, a weakref to the object is created and it
    may be garbage collected if there's no other reference to the object.

    :returns:

        A `ClockEvent` instance. As opposed to
        `create_trigger()` which only creates the trigger event, this
        method also schedules it.
def snooze_trigger(ev: kivy._clock.ClockEvent):
114def snooze_trigger(ev: "kivy.clock.ClockEvent"):  # noqa: F821
115    """Cancel and reschedule a ClockEvent."""
116    if ev.is_triggered:
117        ev.cancel()
118    ev()

Cancel and reschedule a ClockEvent.

def queue_around_frame( func, before: Optional[Callable] = None, after: Optional[Callable] = None):
11def queue_around_frame(
12    func,
13    before: Optional[Callable] = None,
14    after: Optional[Callable] = None,
15):
16    """Decorator for queuing functions before and after drawing frames.
17
18    Used for performing GUI operations before and after functions that will
19    block code execution for a significant period of time. Functions that would
20    otherwise freeze the GUI without feedback can be wrapped with this decorator
21    to give user feedback.
22
23    The following order of operations will be queued:
24
25    1. Call *before*
26    2. Draw GUI frame
27    3. Call the wrapped function
28    4. Call *after*
29
30    ### Example usage:
31    ```python
32    @queue(
33        before=lambda: print("Drawing GUI frame then executing function..."),
34        after=lambda: print("Done executing..."),
35    )
36    def do_sleep():
37        time.sleep(2)
38    ```
39    """
40
41    @wraps(func)
42    def wrapper(*args, **kwargs):
43        if before is not None:
44            before()
45        wrapped = partial(func, *args, **kwargs)
46        kv.Clock.schedule_once(lambda dt: _call_with_after(wrapped, after), 0.05)
47
48    return wrapper

Decorator for queuing functions before and after drawing frames.

Used for performing GUI operations before and after functions that will block code execution for a significant period of time. Functions that would otherwise freeze the GUI without feedback can be wrapped with this decorator to give user feedback.

The following order of operations will be queued:

  1. Call before
  2. Draw GUI frame
  3. Call the wrapped function
  4. Call after

Example usage:

@queue(
    before=lambda: print("Drawing GUI frame then executing function..."),
    after=lambda: print("Done executing..."),
)
def do_sleep():
    time.sleep(2)
def sp2pixels(value: Union[float, str, Iterable[float | str]]) -> float | list[float]:
134def sp2pixels(value: float | str | Iterable[float | str]) -> float | list[float]:
135    """Convert values in 'sp', 'dp', or pixels to pixels.
136
137    Useful when wishing to convert values to pixels but it is unknown if they are given
138    in pixels or 'sp' format.
139    """
140    if isinstance(value, int) or isinstance(value, float):
141        return value
142    if isinstance(value, str):
143        return _str2pixels(value)
144    # Handle as iterable
145    values = []
146    append = values.append
147    for v in value:
148        if isinstance(v, int) or isinstance(v, float):
149            append(v)
150        elif isinstance(v, str):
151            append(_str2pixels(v))
152        else:
153            m = f"Expected float or str, instead got: {v!r} {type(v)}"
154            raise ValueError(m)
155    return values

Convert values in 'sp', 'dp', or pixels to pixels.

Useful when wishing to convert values to pixels but it is unknown if they are given in pixels or 'sp' format.