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)
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.
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.
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.
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.
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.
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.
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.
CyClockBase.schedule_once(self, callback, timeout=0) -> ClockEvent
Schedule an event in 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()`).
CyClockBase.schedule_interval(self, callback, timeout) -> ClockEvent
Schedule an event to be called every 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.
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.
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:
- Call before
- Draw GUI frame
- Call the wrapped function
- 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)
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.