working condition
This commit is contained in:
parent
417e54da96
commit
511e0b0379
517 changed files with 29187 additions and 32696 deletions
42
kivy_venv/lib/python3.11/site-packages/asynckivy/__init__.py
Normal file
42
kivy_venv/lib/python3.11/site-packages/asynckivy/__init__.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
__all__ = (
|
||||
'MotionEventAlreadyEndedError',
|
||||
'anim_attrs',
|
||||
'anim_attrs_abbr',
|
||||
'anim_with_dt',
|
||||
'anim_with_dt_et',
|
||||
'anim_with_dt_et_ratio',
|
||||
'anim_with_et',
|
||||
'anim_with_ratio',
|
||||
'animate',
|
||||
'create_texture_from_text',
|
||||
'event',
|
||||
'fade_transition',
|
||||
'interpolate',
|
||||
'move_on_after',
|
||||
'n_frames',
|
||||
'repeat_sleeping',
|
||||
'rest_of_touch_events',
|
||||
'run_in_executor',
|
||||
'run_in_thread',
|
||||
'sleep',
|
||||
'sleep_free',
|
||||
'suppress_event',
|
||||
'sync_attr',
|
||||
'sync_attrs',
|
||||
'touch_up_event',
|
||||
'transform',
|
||||
'watch_touch',
|
||||
)
|
||||
|
||||
from asyncgui import *
|
||||
from ._exceptions import MotionEventAlreadyEndedError
|
||||
from ._sleep import sleep, sleep_free, repeat_sleeping, move_on_after
|
||||
from ._event import event
|
||||
from ._anim_with_xxx import anim_with_dt, anim_with_et, anim_with_ratio, anim_with_dt_et, anim_with_dt_et_ratio
|
||||
from ._animation import animate
|
||||
from ._anim_attrs import anim_attrs, anim_attrs_abbr
|
||||
from ._interpolate import interpolate, fade_transition
|
||||
from ._touch import watch_touch, rest_of_touch_events, rest_of_touch_moves, touch_up_event
|
||||
from ._threading import run_in_executor, run_in_thread
|
||||
from ._n_frames import n_frames
|
||||
from ._utils import transform, suppress_event, create_texture_from_text, sync_attr, sync_attrs
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
114
kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_attrs.py
Normal file
114
kivy_venv/lib/python3.11/site-packages/asynckivy/_anim_attrs.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
__all__ = ('anim_attrs', 'anim_attrs_abbr', )
|
||||
import typing as T
|
||||
import types
|
||||
from functools import partial
|
||||
import kivy.clock
|
||||
from kivy.animation import AnimationTransition
|
||||
import asyncgui
|
||||
|
||||
|
||||
def _update(setattr, zip, min, obj, duration, transition, output_seq_type, anim_params, task, p_time, dt):
|
||||
time = p_time[0] + dt
|
||||
p_time[0] = time
|
||||
|
||||
# calculate progression
|
||||
progress = min(1., time / duration)
|
||||
t = transition(progress)
|
||||
|
||||
# apply progression on obj
|
||||
for attr_name, org_value, slope, is_seq in anim_params:
|
||||
if is_seq:
|
||||
new_value = output_seq_type(
|
||||
slope_elem * t + org_elem
|
||||
for org_elem, slope_elem in zip(org_value, slope)
|
||||
)
|
||||
setattr(obj, attr_name, new_value)
|
||||
else:
|
||||
setattr(obj, attr_name, slope * t + org_value)
|
||||
|
||||
# time to stop ?
|
||||
if progress >= 1.:
|
||||
task._step()
|
||||
return False
|
||||
|
||||
|
||||
_update = partial(_update, setattr, zip, min)
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def _anim_attrs(
|
||||
obj, duration, step, transition, output_seq_type, animated_properties,
|
||||
getattr=getattr, isinstance=isinstance, tuple=tuple, str=str, partial=partial, native_seq_types=(tuple, list),
|
||||
zip=zip, Clock=kivy.clock.Clock, AnimationTransition=AnimationTransition,
|
||||
_update=_update, _current_task=asyncgui._current_task, _sleep_forever=asyncgui._sleep_forever, /):
|
||||
if isinstance(transition, str):
|
||||
transition = getattr(AnimationTransition, transition)
|
||||
|
||||
# get current values & calculate slopes
|
||||
anim_params = tuple(
|
||||
(
|
||||
org_value := getattr(obj, attr_name),
|
||||
is_seq := isinstance(org_value, native_seq_types),
|
||||
(
|
||||
org_value := tuple(org_value),
|
||||
slope := tuple(goal_elem - org_elem for goal_elem, org_elem in zip(goal_value, org_value)),
|
||||
) if is_seq else (slope := goal_value - org_value),
|
||||
) and (attr_name, org_value, slope, is_seq, )
|
||||
for attr_name, goal_value in animated_properties.items()
|
||||
)
|
||||
|
||||
try:
|
||||
clock_event = Clock.schedule_interval(
|
||||
partial(_update, obj, duration, transition, output_seq_type, anim_params, (yield _current_task)[0][0],
|
||||
[0., ]),
|
||||
step,
|
||||
)
|
||||
yield _sleep_forever
|
||||
finally:
|
||||
clock_event.cancel()
|
||||
|
||||
|
||||
def anim_attrs(obj, *, duration=1.0, step=0, transition=AnimationTransition.linear, output_seq_type=tuple,
|
||||
**animated_properties) -> T.Awaitable:
|
||||
'''
|
||||
Animates attibutes of any object.
|
||||
|
||||
.. code-block::
|
||||
|
||||
import types
|
||||
|
||||
obj = types.SimpleNamespace(x=0, size=(200, 300))
|
||||
await anim_attrs(obj, x=100, size=(400, 400))
|
||||
|
||||
The ``output_seq_type`` parameter:
|
||||
|
||||
.. code-block::
|
||||
|
||||
obj = types.SimpleNamespace(size=(200, 300))
|
||||
await anim_attrs(obj, size=(400, 400), output_seq_type=list)
|
||||
assert type(obj.size) is list
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike :class:`kivy.animation.Animation`, this one does not support dictionary-type and nested-sequence.
|
||||
|
||||
.. code-block::
|
||||
|
||||
await anim_attrs(obj, pos_hint={'x': 1.}) # not supported
|
||||
await anim_attrs(obj, nested_sequence=[[10, 20, ]]) # not supported
|
||||
|
||||
await anim_attrs(obj, color=(1, 0, 0, 1), pos=(100, 200)) # OK
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
return _anim_attrs(obj, duration, step, transition, output_seq_type, animated_properties)
|
||||
|
||||
|
||||
def anim_attrs_abbr(obj, *, d=1.0, s=0, t=AnimationTransition.linear, output_seq_type=tuple,
|
||||
**animated_properties) -> T.Awaitable:
|
||||
'''
|
||||
:func:`anim_attrs` cannot animate attributes named ``step``, ``duration`` and ``transition`` but this one can.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
return _anim_attrs(obj, d, s, t, output_seq_type, animated_properties)
|
|
@ -0,0 +1,146 @@
|
|||
__all__ = (
|
||||
'anim_with_dt', 'anim_with_et', 'anim_with_ratio', 'anim_with_dt_et', 'anim_with_dt_et_ratio',
|
||||
)
|
||||
|
||||
from ._sleep import repeat_sleeping
|
||||
|
||||
|
||||
async def anim_with_dt(*, step=0):
|
||||
'''
|
||||
An async form of :meth:`kivy.clock.Clock.schedule_interval`. The following callback-style code:
|
||||
|
||||
.. code-block::
|
||||
|
||||
def callback(dt):
|
||||
print(dt)
|
||||
if some_condition:
|
||||
return False
|
||||
|
||||
Clock.schedule_interval(callback, 0.1)
|
||||
|
||||
is equivalent to the following async-style code:
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for dt in anim_with_dt(step=0.1):
|
||||
print(dt)
|
||||
if some_condition:
|
||||
break
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
async with repeat_sleeping(step=step) as sleep:
|
||||
while True:
|
||||
yield await sleep()
|
||||
|
||||
|
||||
async def anim_with_et(*, step=0):
|
||||
'''
|
||||
Same as :func:`anim_with_dt` except this one generates the total elapsed time of the loop instead of the elapsed
|
||||
time between frames.
|
||||
|
||||
.. code-block::
|
||||
|
||||
timeout = 3.0
|
||||
async for et in anim_with_et(...):
|
||||
...
|
||||
if et > timeout:
|
||||
break
|
||||
|
||||
You can calculate ``et`` by yourself if you want to:
|
||||
|
||||
.. code-block::
|
||||
|
||||
et = 0.
|
||||
timeout = 3.0
|
||||
async for dt in anim_with_dt(...):
|
||||
et += dt
|
||||
...
|
||||
if et > timeout:
|
||||
break
|
||||
|
||||
which should be as performant as the former.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
et = 0.
|
||||
async with repeat_sleeping(step=step) as sleep:
|
||||
while True:
|
||||
et += await sleep()
|
||||
yield et
|
||||
|
||||
|
||||
async def anim_with_dt_et(*, step=0):
|
||||
'''
|
||||
:func:`anim_with_dt` and :func:`anim_with_et` combined.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for dt, et in anim_with_dt_et(...):
|
||||
...
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
et = 0.
|
||||
async with repeat_sleeping(step=step) as sleep:
|
||||
while True:
|
||||
dt = await sleep()
|
||||
et += dt
|
||||
yield dt, et
|
||||
|
||||
|
||||
async def anim_with_ratio(*, duration=1., step=0):
|
||||
'''
|
||||
Same as :func:`anim_with_et` except this one generates the total progression ratio of the loop.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for p in anim_with_ratio(duration=3.0):
|
||||
print(p * 100, "%")
|
||||
|
||||
If you want to progress at a non-consistant rate, :class:`kivy.animation.AnimationTransition` may be helpful.
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivy.animation import AnimationTransition
|
||||
|
||||
in_cubic = AnimationTransition.in_cubic
|
||||
|
||||
async for p in anim_with_ratio(duration=3.0):
|
||||
p = in_cubic(p)
|
||||
print(p * 100, "%")
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
async with repeat_sleeping(step=step) as sleep:
|
||||
if not duration:
|
||||
await sleep()
|
||||
yield 1.0
|
||||
return
|
||||
et = 0.
|
||||
while et < duration:
|
||||
et += await sleep()
|
||||
yield et / duration
|
||||
|
||||
|
||||
async def anim_with_dt_et_ratio(*, duration=1., step=0):
|
||||
'''
|
||||
:func:`anim_with_dt`, :func:`anim_with_et` and :func:`anim_with_ratio` combined.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for dt, et, p in anim_with_dt_et_ratio(...):
|
||||
...
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
async with repeat_sleeping(step=step) as sleep:
|
||||
if not duration:
|
||||
dt = await sleep()
|
||||
yield dt, dt, 1.0
|
||||
return
|
||||
et = 0.
|
||||
while et < duration:
|
||||
dt = await sleep()
|
||||
et += dt
|
||||
yield dt, et, et / duration
|
115
kivy_venv/lib/python3.11/site-packages/asynckivy/_animation.py
Normal file
115
kivy_venv/lib/python3.11/site-packages/asynckivy/_animation.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
__all__ = ('animate', )
|
||||
import typing as T
|
||||
import types
|
||||
from functools import partial
|
||||
from kivy.clock import Clock
|
||||
from kivy.animation import AnimationTransition
|
||||
from asyncgui import _sleep_forever, _current_task
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def animate(obj, *, duration=1.0, step=0, transition=AnimationTransition.linear, **animated_properties) -> T.Awaitable:
|
||||
'''
|
||||
Animates attibutes of any object. This is basically an async form of :class:`kivy.animation.Animation`.
|
||||
|
||||
.. code-block::
|
||||
|
||||
import types
|
||||
|
||||
obj = types.SimpleNamespace(x=0, size=(200, 300, ))
|
||||
await animate(obj, x=100, size=(400, 400))
|
||||
|
||||
Kivy has two compound animations, :class:`kivy.animation.Sequence` and :class:`kivy.animation.Parallel`.
|
||||
You can achieve the same functionality as them in asynckivy as follows:
|
||||
|
||||
.. code-block::
|
||||
|
||||
import asynckivy as ak
|
||||
|
||||
async def sequential_animation(widget):
|
||||
await ak.animate(widget, x=100)
|
||||
await ak.animate(widget, x=0)
|
||||
|
||||
async def parallel_animation(widget):
|
||||
await ak.wait_all(
|
||||
ak.animate(widget, x=100),
|
||||
ak.animate(widget, y=100, duration=2),
|
||||
)
|
||||
|
||||
.. deprecated:: 0.6.1
|
||||
|
||||
This will be removed before version 1.0.0.
|
||||
Use :func:`asynckivy.anim_attrs` or :func:`asynckivy.anim_attrs_abbr` instead.
|
||||
'''
|
||||
if not duration:
|
||||
for key, value in animated_properties.items():
|
||||
setattr(obj, key, value)
|
||||
return
|
||||
if isinstance(transition, str):
|
||||
transition = getattr(AnimationTransition, transition)
|
||||
|
||||
# get current values
|
||||
properties = {}
|
||||
for key, value in animated_properties.items():
|
||||
original_value = getattr(obj, key)
|
||||
if isinstance(original_value, (tuple, list)):
|
||||
original_value = original_value[:]
|
||||
elif isinstance(original_value, dict):
|
||||
original_value = original_value.copy()
|
||||
properties[key] = (original_value, value)
|
||||
|
||||
try:
|
||||
clock_event = Clock.schedule_interval(
|
||||
partial(_update, obj, duration, transition, properties, (yield _current_task)[0][0], [0., ]),
|
||||
step,
|
||||
)
|
||||
yield _sleep_forever
|
||||
finally:
|
||||
clock_event.cancel()
|
||||
|
||||
|
||||
def _calculate(isinstance, list, tuple, dict, range, len, a, b, t):
|
||||
'''The logic of this function is identical to 'kivy.animation.Animation._calculate()'
|
||||
'''
|
||||
if isinstance(a, list) or isinstance(a, tuple):
|
||||
if isinstance(a, list):
|
||||
tp = list
|
||||
else:
|
||||
tp = tuple
|
||||
return tp([_calculate(a[x], b[x], t) for x in range(len(a))])
|
||||
elif isinstance(a, dict):
|
||||
d = {}
|
||||
for x in a:
|
||||
if x not in b:
|
||||
# User requested to animate only part of the dict.
|
||||
# Copy the rest
|
||||
d[x] = a[x]
|
||||
else:
|
||||
d[x] = _calculate(a[x], b[x], t)
|
||||
return d
|
||||
else:
|
||||
return (a * (1. - t)) + (b * t)
|
||||
|
||||
|
||||
def _update(setattr, _calculate, obj, duration, transition, properties, task, p_time, dt):
|
||||
time = p_time[0] + dt
|
||||
p_time[0] = time
|
||||
|
||||
# calculate progression
|
||||
progress = min(1., time / duration)
|
||||
t = transition(progress)
|
||||
|
||||
# apply progression on obj
|
||||
for key, values in properties.items():
|
||||
a, b = values
|
||||
value = _calculate(a, b, t)
|
||||
setattr(obj, key, value)
|
||||
|
||||
# time to stop ?
|
||||
if progress >= 1.:
|
||||
task._step()
|
||||
return False
|
||||
|
||||
|
||||
_calculate = partial(_calculate, isinstance, list, tuple, dict, range, len)
|
||||
_update = partial(_update, setattr, _calculate)
|
57
kivy_venv/lib/python3.11/site-packages/asynckivy/_event.py
Normal file
57
kivy_venv/lib/python3.11/site-packages/asynckivy/_event.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
__all__ = ('event', )
|
||||
|
||||
import typing as T
|
||||
import types
|
||||
from functools import partial
|
||||
from asyncgui import _current_task, _sleep_forever
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def event(event_dispatcher, event_name, *, filter=None, stop_dispatching=False) -> T.Awaitable[tuple]:
|
||||
'''
|
||||
Returns an awaitable that can be used to wait for:
|
||||
|
||||
* a Kivy event to occur.
|
||||
* a Kivy property's value to change.
|
||||
|
||||
.. code-block::
|
||||
|
||||
# Wait for a button to be pressed.
|
||||
await event(button, 'on_press')
|
||||
|
||||
# Wait for an 'on_touch_down' event to occur.
|
||||
__, touch = await event(widget, 'on_touch_down')
|
||||
|
||||
# Wait for 'widget.x' to change.
|
||||
__, x = await ak.event(widget, 'x')
|
||||
|
||||
|
||||
The ``filter`` parameter:
|
||||
|
||||
.. code-block::
|
||||
|
||||
# Wait for an 'on_touch_down' event to occur inside a widget.
|
||||
__, touch = await event(widget, 'on_touch_down', filter=lambda w, t: w.collide_point(*t.opos))
|
||||
|
||||
# Wait for 'widget.x' to become greater than 100.
|
||||
if widget.x <= 100:
|
||||
await event(widget, 'x', filter=lambda __, x: x > 100)
|
||||
|
||||
The ``stop_dispatching`` parameter:
|
||||
|
||||
It only works for events not for properties.
|
||||
See :ref:`kivys-event-system` for details.
|
||||
'''
|
||||
task = (yield _current_task)[0][0]
|
||||
bind_id = event_dispatcher.fbind(event_name, partial(_callback, filter, task, stop_dispatching))
|
||||
assert bind_id # check if binding succeeded
|
||||
try:
|
||||
return (yield _sleep_forever)[0]
|
||||
finally:
|
||||
event_dispatcher.unbind_uid(event_name, bind_id)
|
||||
|
||||
|
||||
def _callback(filter, task, stop_dispatching, *args, **kwargs):
|
||||
if (filter is None) or filter(*args, **kwargs):
|
||||
task._step(*args)
|
||||
return stop_dispatching
|
|
@ -0,0 +1,27 @@
|
|||
__all__ = (
|
||||
'MotionEventAlreadyEndedError',
|
||||
)
|
||||
|
||||
|
||||
class MotionEventAlreadyEndedError(Exception):
|
||||
'''
|
||||
This error occurs when an already-ended touch is passed to an asynckivy API that expects an ongoing touch.
|
||||
For instance:
|
||||
|
||||
.. code-block::
|
||||
:emphasize-lines: 4
|
||||
|
||||
import asynckivy as ak
|
||||
|
||||
class MyWidget(Widget):
|
||||
def on_touch_up(self, touch): # not 'on_touch_down', oops!
|
||||
ak.start(self.handle_touch(touch))
|
||||
return True
|
||||
|
||||
async def handle_touch(self, touch):
|
||||
try:
|
||||
async for __ in ak.rest_of_touch_events(widget, touch):
|
||||
...
|
||||
except ak.MotionEventAlreadyEndedError:
|
||||
...
|
||||
'''
|
|
@ -0,0 +1,75 @@
|
|||
__all__ = ('interpolate', 'fade_transition', )
|
||||
import typing as T
|
||||
from contextlib import asynccontextmanager
|
||||
from kivy.animation import AnimationTransition
|
||||
|
||||
from ._anim_with_xxx import anim_with_ratio
|
||||
|
||||
|
||||
linear = AnimationTransition.linear
|
||||
|
||||
|
||||
async def interpolate(start, end, *, duration=1.0, step=0, transition=linear) -> T.AsyncIterator:
|
||||
'''
|
||||
Interpolates between the values ``start`` and ``end`` in an async-manner.
|
||||
Inspired by wasabi2d's interpolate_.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for v in interpolate(0, 100, duration=1.0, step=.3):
|
||||
print(int(v))
|
||||
|
||||
============ ======
|
||||
elapsed time output
|
||||
============ ======
|
||||
0 sec 0
|
||||
0.3 sec 30
|
||||
0.6 sec 60
|
||||
0.9 sec 90
|
||||
**1.2 sec** 100
|
||||
============ ======
|
||||
|
||||
.. _interpolate: https://wasabi2d.readthedocs.io/en/stable/coros.html#clock.coro.interpolate
|
||||
'''
|
||||
if isinstance(transition, str):
|
||||
transition = getattr(AnimationTransition, transition)
|
||||
|
||||
slope = end - start
|
||||
yield transition(0.) * slope + start
|
||||
async for p in anim_with_ratio(step=step, duration=duration):
|
||||
if p >= 1.0:
|
||||
break
|
||||
yield transition(p) * slope + start
|
||||
yield transition(1.) * slope + start
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def fade_transition(*widgets, duration=1.0, step=0) -> T.AsyncContextManager:
|
||||
'''
|
||||
Returns an async context manager that:
|
||||
|
||||
* fades-out the given widgets on ``__aenter__``.
|
||||
* fades-in the given widgets on ``__aexit__``.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with fade_transition(widget1, widget2):
|
||||
...
|
||||
|
||||
The ``widgets`` don't have to be actual Kivy widgets.
|
||||
Anything that has an attribute named ``opacity`` would work.
|
||||
'''
|
||||
half_duration = duration / 2.
|
||||
org_opas = tuple(w.opacity for w in widgets)
|
||||
try:
|
||||
async for p in anim_with_ratio(duration=half_duration, step=step):
|
||||
p = 1.0 - p
|
||||
for w, o in zip(widgets, org_opas):
|
||||
w.opacity = p * o
|
||||
yield
|
||||
async for p in anim_with_ratio(duration=half_duration, step=step):
|
||||
for w, o in zip(widgets, org_opas):
|
||||
w.opacity = p * o
|
||||
finally:
|
||||
for w, o in zip(widgets, org_opas):
|
||||
w.opacity = o
|
|
@ -0,0 +1,43 @@
|
|||
__all__ = ('n_frames', )
|
||||
|
||||
import typing as T
|
||||
import types
|
||||
from kivy.clock import Clock
|
||||
from asyncgui import _current_task, _sleep_forever
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def n_frames(n: int) -> T.Awaitable:
|
||||
'''
|
||||
Waits for a specified number of frames.
|
||||
|
||||
.. code-block::
|
||||
|
||||
await n_frames(2)
|
||||
|
||||
If you want to wait for one frame, :func:`asynckivy.sleep` is preferable for a performance reason.
|
||||
|
||||
.. code-block::
|
||||
|
||||
await sleep(0)
|
||||
'''
|
||||
if n < 0:
|
||||
raise ValueError(f"Waiting for {n} frames doesn't make sense.")
|
||||
if not n:
|
||||
return
|
||||
|
||||
task = (yield _current_task)[0][0]
|
||||
|
||||
def callback(dt):
|
||||
nonlocal n
|
||||
n -= 1
|
||||
if not n:
|
||||
task._step()
|
||||
return False
|
||||
|
||||
clock_event = Clock.schedule_interval(callback, 0)
|
||||
|
||||
try:
|
||||
yield _sleep_forever
|
||||
finally:
|
||||
clock_event.cancel()
|
125
kivy_venv/lib/python3.11/site-packages/asynckivy/_sleep.py
Normal file
125
kivy_venv/lib/python3.11/site-packages/asynckivy/_sleep.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
__all__ = ('sleep', 'sleep_free', 'repeat_sleeping', 'move_on_after', )
|
||||
|
||||
import typing as T
|
||||
import types
|
||||
|
||||
from kivy.clock import Clock
|
||||
from asyncgui import _current_task, _sleep_forever, move_on_when, Task, Cancelled
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def sleep(duration) -> T.Awaitable[float]:
|
||||
'''
|
||||
An async form of :meth:`kivy.clock.Clock.schedule_once`.
|
||||
|
||||
.. code-block::
|
||||
|
||||
dt = await sleep(5) # wait for 5 seconds
|
||||
'''
|
||||
task = (yield _current_task)[0][0]
|
||||
clock_event = Clock.create_trigger(task._step, duration, False, False)
|
||||
clock_event()
|
||||
|
||||
try:
|
||||
return (yield _sleep_forever)[0][0]
|
||||
except Cancelled:
|
||||
clock_event.cancel()
|
||||
raise
|
||||
|
||||
|
||||
@types.coroutine
|
||||
def sleep_free(duration) -> T.Awaitable[float]:
|
||||
'''
|
||||
An async form of :meth:`kivy.clock.Clock.schedule_once_free`.
|
||||
|
||||
.. code-block::
|
||||
|
||||
dt = await sleep_free(5) # wait for 5 seconds
|
||||
'''
|
||||
task = (yield _current_task)[0][0]
|
||||
clock_event = Clock.create_trigger_free(task._step, duration, False, False)
|
||||
clock_event()
|
||||
|
||||
try:
|
||||
return (yield _sleep_forever)[0][0]
|
||||
except Cancelled:
|
||||
clock_event.cancel()
|
||||
raise
|
||||
|
||||
|
||||
class repeat_sleeping:
|
||||
'''
|
||||
Returns an async context manager that provides an efficient way to repeat sleeping.
|
||||
|
||||
When there is a piece of code like this:
|
||||
|
||||
.. code-block::
|
||||
|
||||
while True:
|
||||
await sleep(0)
|
||||
...
|
||||
|
||||
it can be translated to:
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with repeat_sleeping(step=0) as sleep:
|
||||
while True:
|
||||
await sleep()
|
||||
...
|
||||
|
||||
The latter is more suitable for situations requiring frequent sleeps, such as moving an object in every frame.
|
||||
|
||||
**Restriction**
|
||||
|
||||
You are not allowed to perform any kind of async operations inside the with-block except you can
|
||||
``await`` the return value of the function that is bound to the identifier of the as-clause.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with repeat_sleeping(step=0) as sleep:
|
||||
await sleep() # OK
|
||||
await something_else # NOT ALLOWED
|
||||
async with async_context_manager: # NOT ALLOWED
|
||||
...
|
||||
async for __ in async_iterator: # NOT ALLOWED
|
||||
...
|
||||
'''
|
||||
|
||||
__slots__ = ('_step', '_trigger', )
|
||||
|
||||
@types.coroutine
|
||||
def _sleep(_f=_sleep_forever):
|
||||
return (yield _f)[0][0]
|
||||
|
||||
def __init__(self, *, step=0):
|
||||
self._step = step
|
||||
|
||||
@types.coroutine
|
||||
def __aenter__(self, _sleep=_sleep) -> T.Awaitable[T.Callable[[], T.Awaitable[float]]]:
|
||||
task = (yield _current_task)[0][0]
|
||||
self._trigger = Clock.create_trigger(task._step, self._step, True, False)
|
||||
self._trigger()
|
||||
return _sleep
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
self._trigger.cancel()
|
||||
|
||||
|
||||
def move_on_after(seconds: float) -> T.AsyncContextManager[Task]:
|
||||
'''
|
||||
Returns an async context manager that applies a time limit to its code block,
|
||||
like :func:`trio.move_on_after` does.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with move_on_after(seconds) as bg_task:
|
||||
...
|
||||
if bg_task.finished:
|
||||
print("The code block was interrupted due to a timeout")
|
||||
else:
|
||||
print("The code block exited gracefully.")
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
return move_on_when(sleep(seconds))
|
|
@ -0,0 +1,64 @@
|
|||
__all__ = ('run_in_thread', 'run_in_executor', )
|
||||
import typing as T
|
||||
from threading import Thread
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from kivy.clock import Clock
|
||||
import asyncgui
|
||||
|
||||
|
||||
def _wrapper(func, ev):
|
||||
ret = None
|
||||
exc = None
|
||||
try:
|
||||
ret = func()
|
||||
except Exception as e:
|
||||
exc = e
|
||||
finally:
|
||||
Clock.schedule_once(lambda __: ev.fire(ret, exc))
|
||||
|
||||
|
||||
async def run_in_thread(func, *, daemon=None) -> T.Awaitable:
|
||||
'''
|
||||
Creates a new thread, runs a function within it, then waits for the completion of that function.
|
||||
|
||||
.. code-block::
|
||||
|
||||
return_value = await run_in_thread(func)
|
||||
|
||||
See :ref:`io-in-asynckivy` for details.
|
||||
'''
|
||||
ev = asyncgui.AsyncEvent()
|
||||
Thread(
|
||||
name='asynckivy.run_in_thread',
|
||||
target=_wrapper, daemon=daemon, args=(func, ev, ),
|
||||
).start()
|
||||
ret, exc = (await ev.wait())[0]
|
||||
if exc is not None:
|
||||
raise exc
|
||||
return ret
|
||||
|
||||
|
||||
async def run_in_executor(executor: ThreadPoolExecutor, func) -> T.Awaitable:
|
||||
'''
|
||||
Runs a function within a :class:`concurrent.futures.ThreadPoolExecutor`, and waits for the completion of the
|
||||
function.
|
||||
|
||||
.. code-block::
|
||||
|
||||
executor = ThreadPoolExecutor()
|
||||
...
|
||||
return_value = await run_in_executor(executor, func)
|
||||
|
||||
See :ref:`io-in-asynckivy` for details.
|
||||
'''
|
||||
ev = asyncgui.AsyncEvent()
|
||||
future = executor.submit(_wrapper, func, ev)
|
||||
try:
|
||||
ret, exc = (await ev.wait())[0]
|
||||
except asyncgui.Cancelled:
|
||||
future.cancel()
|
||||
raise
|
||||
assert future.done()
|
||||
if exc is not None:
|
||||
raise exc
|
||||
return ret
|
196
kivy_venv/lib/python3.11/site-packages/asynckivy/_touch.py
Normal file
196
kivy_venv/lib/python3.11/site-packages/asynckivy/_touch.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
__all__ = ('watch_touch', 'rest_of_touch_moves', 'rest_of_touch_events', 'touch_up_event', )
|
||||
|
||||
import typing as T
|
||||
import types
|
||||
from functools import partial
|
||||
from asyncgui import wait_any, current_task
|
||||
from ._exceptions import MotionEventAlreadyEndedError
|
||||
from ._sleep import sleep
|
||||
from ._event import event
|
||||
|
||||
|
||||
class watch_touch:
|
||||
'''
|
||||
Returns an async context manager that provides an easy way to handle touch events.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with watch_touch(widget, touch) as in_progress:
|
||||
while await in_progress():
|
||||
print('on_touch_move')
|
||||
print('on_touch_up')
|
||||
|
||||
The ``await in_progress()`` waits for either an ``on_touch_move`` event or an ``on_touch_up`` event to occur, and
|
||||
returns True or False respectively when they occurred.
|
||||
|
||||
**Caution**
|
||||
|
||||
* You are not allowed to perform any kind of async operations inside the with-block except ``await in_progress()``.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async with watch_touch(widget, touch) as in_progress:
|
||||
await in_progress() # OK
|
||||
await something_else # NOT ALLOWED
|
||||
async with async_context_manager: # NOT ALLOWED
|
||||
...
|
||||
async for __ in async_iterator: # NOT ALLOWED
|
||||
...
|
||||
|
||||
* If the ``widget`` is the type of widget that grabs touches by itself, such as :class:`kivy.uix.button.Button`,
|
||||
you probably want to set the ``stop_dispatching`` parameter to True in most cases.
|
||||
* There are widgets/behaviors that can simulate a touch (e.g. :class:`kivy.uix.scrollview.ScrollView`,
|
||||
:class:`kivy.uix.behaviors.DragBehavior` and ``kivy_garden.draggable.KXDraggableBehavior``).
|
||||
If many such widgets are in the parent stack of the ``widget``, this API might mistakenly raise a
|
||||
:exc:`asynckivy.MotionEventAlreadyEndedError`. If that happens, increase the ``timeout`` parameter.
|
||||
'''
|
||||
__slots__ = ('_widget', '_touch', '_stop_dispatching', '_timeout', '_uid_up', '_uid_move', '_no_cleanup', )
|
||||
|
||||
def __init__(self, widget, touch, *, stop_dispatching=False, timeout=1.):
|
||||
self._widget = widget
|
||||
self._touch = touch
|
||||
self._stop_dispatching = stop_dispatching
|
||||
self._timeout = timeout
|
||||
self._no_cleanup = False
|
||||
|
||||
def _on_touch_up_sd(step, touch, w, t):
|
||||
if t is touch:
|
||||
if t.grab_current is w:
|
||||
t.ungrab(w)
|
||||
step(False)
|
||||
return True
|
||||
|
||||
def _on_touch_move_sd(step, touch, w, t):
|
||||
if t is touch:
|
||||
if t.grab_current is w:
|
||||
step(True)
|
||||
return True
|
||||
|
||||
def _on_touch_up(step, touch, w, t):
|
||||
if t.grab_current is w and t is touch:
|
||||
t.ungrab(w)
|
||||
step(False)
|
||||
return True
|
||||
|
||||
def _on_touch_move(step, touch, w, t):
|
||||
if t.grab_current is w and t is touch:
|
||||
step(True)
|
||||
return True
|
||||
|
||||
_callbacks = ((_on_touch_up_sd, _on_touch_move_sd, ), (_on_touch_up, _on_touch_move, ), )
|
||||
del _on_touch_up, _on_touch_move, _on_touch_up_sd, _on_touch_move_sd
|
||||
|
||||
@staticmethod
|
||||
@types.coroutine
|
||||
def _true_if_touch_move_false_if_touch_up() -> bool:
|
||||
return (yield lambda step_coro: None)[0][0]
|
||||
|
||||
@staticmethod
|
||||
@types.coroutine
|
||||
def _always_false() -> bool:
|
||||
return False
|
||||
yield # just to make this function a generator function
|
||||
|
||||
async def __aenter__(self) -> T.Awaitable[T.Callable[[], T.Awaitable[bool]]]:
|
||||
touch = self._touch
|
||||
widget = self._widget
|
||||
if touch.time_end != -1:
|
||||
# `on_touch_up` might have been already fired so we need to find out it actually was or not.
|
||||
tasks = await wait_any(
|
||||
sleep(self._timeout),
|
||||
event(widget, 'on_touch_up', filter=lambda w, t: t is touch),
|
||||
)
|
||||
if tasks[0].finished:
|
||||
raise MotionEventAlreadyEndedError(f"MotionEvent(uid={touch.uid}) has already ended")
|
||||
self._no_cleanup = True
|
||||
return self._always_false
|
||||
step = (await current_task())._step
|
||||
on_touch_up, on_touch_move = self._callbacks[not self._stop_dispatching]
|
||||
touch.grab(widget)
|
||||
self._uid_up = widget.fbind('on_touch_up', partial(on_touch_up, step, touch))
|
||||
self._uid_move = widget.fbind('on_touch_move', partial(on_touch_move, step, touch))
|
||||
assert self._uid_up
|
||||
assert self._uid_move
|
||||
return self._true_if_touch_move_false_if_touch_up
|
||||
|
||||
async def __aexit__(self, *args):
|
||||
if self._no_cleanup:
|
||||
return
|
||||
w = self._widget
|
||||
self._touch.ungrab(w)
|
||||
w.unbind_uid('on_touch_up', self._uid_up)
|
||||
w.unbind_uid('on_touch_move', self._uid_move)
|
||||
|
||||
|
||||
async def touch_up_event(widget, touch, *, stop_dispatching=False, timeout=1.) -> T.Awaitable:
|
||||
'''
|
||||
*(experimental state)*
|
||||
|
||||
Returns an awaitable that can be used to wait for the ``on_touch_up`` event of the given ``touch`` to occur.
|
||||
|
||||
.. code-block::
|
||||
|
||||
__, touch = await event(widget, 'on_touch_down')
|
||||
...
|
||||
await touch_up_event(widget, touch)
|
||||
|
||||
You might wonder what the differences are compared to the code below.
|
||||
|
||||
.. code-block::
|
||||
:emphasize-lines: 3
|
||||
|
||||
__, touch = await event(widget, 'on_touch_down')
|
||||
...
|
||||
await event(widget, 'on_touch_up', filter=lambda w, t: t is touch)
|
||||
|
||||
The latter has two problems:
|
||||
If the ``on_touch_up`` event of the ``touch`` occurred before the highlighted line,
|
||||
the execution will halt indefinitely at that point.
|
||||
Even if the event didn't occur before that line, the execution still might halt because
|
||||
`Kivy does not guarantee`_ that all touch events are delivered to all widgets.
|
||||
|
||||
This API takes care of both problems in the same way as :func:`watch_touch`.
|
||||
If the ``on_touch_up`` event has already occurred, it raises a :exc:`MotionEventAlreadyEndedError` exception.
|
||||
And it grabs/ungrabs the ``touch`` so that it won't miss any touch events.
|
||||
|
||||
Needless to say, if you want to wait for both ``on_touch_move`` and ``on_touch_up`` events at the same time,
|
||||
use :func:`watch_touch` or :func:`rest_of_touch_events` instead.
|
||||
|
||||
.. _Kivy does not guarantee: https://kivy.org/doc/stable/guide/inputs.html#grabbing-touch-events
|
||||
'''
|
||||
touch.grab(widget)
|
||||
try:
|
||||
awaitable = event(
|
||||
widget, 'on_touch_up', stop_dispatching=stop_dispatching,
|
||||
filter=lambda w, t: t.grab_current is w and t is touch,
|
||||
)
|
||||
if touch.time_end == -1:
|
||||
await awaitable
|
||||
else:
|
||||
tasks = await wait_any(sleep(timeout), awaitable)
|
||||
if tasks[0].finished:
|
||||
raise MotionEventAlreadyEndedError(f"MotionEvent(uid={touch.uid}) has already ended")
|
||||
finally:
|
||||
touch.ungrab(widget)
|
||||
|
||||
|
||||
async def rest_of_touch_events(widget, touch, *, stop_dispatching=False, timeout=1.) -> T.AsyncIterator[None]:
|
||||
'''
|
||||
Returns an async iterator that iterates the number of times ``on_touch_move`` occurs,
|
||||
and ends the iteration when ``on_touch_up`` occurs.
|
||||
|
||||
.. code-block::
|
||||
|
||||
async for __ in rest_of_touch_events(widget, touch):
|
||||
print('on_touch_move')
|
||||
print('on_touch_up')
|
||||
|
||||
This is a wrapper for :class:`watch_touch`. Although this one, I believe, is more intuitive than
|
||||
:class:`watch_touch`, it has a couple of disadvantages - see :ref:`the-problem-with-async-generators`.
|
||||
'''
|
||||
async with watch_touch(widget, touch, stop_dispatching=stop_dispatching, timeout=timeout) as in_progress:
|
||||
while await in_progress():
|
||||
yield
|
||||
|
||||
|
||||
rest_of_touch_moves = rest_of_touch_events
|
256
kivy_venv/lib/python3.11/site-packages/asynckivy/_utils.py
Normal file
256
kivy_venv/lib/python3.11/site-packages/asynckivy/_utils.py
Normal file
|
@ -0,0 +1,256 @@
|
|||
__all__ = ('transform', 'suppress_event', 'create_texture_from_text', 'sync_attr', 'sync_attrs', )
|
||||
import typing as T
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
|
||||
from kivy.event import EventDispatcher
|
||||
from kivy.graphics import PushMatrix, PopMatrix, InstructionGroup
|
||||
from kivy.graphics.texture import Texture
|
||||
from kivy.core.text import Label as CoreLabel
|
||||
from kivy.core.text.markup import MarkupLabel as CoreMarkupLabel
|
||||
|
||||
|
||||
@contextmanager
|
||||
def transform(widget, *, use_outer_canvas=False) -> T.ContextManager[InstructionGroup]:
|
||||
'''
|
||||
Returns a context manager that sandwiches the ``widget``'s existing canvas instructions between
|
||||
a :class:`kivy.graphics.PushMatrix` and a :class:`kivy.graphics.PopMatrix`, and inserts an
|
||||
:class:`kivy.graphics.InstructionGroup` right next to the ``PushMatrix``. Those three instructions will be removed
|
||||
when the context manager exits.
|
||||
|
||||
This may be useful when you want to animate a widget.
|
||||
|
||||
**Usage**
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivy.graphics import Rotate
|
||||
|
||||
async def rotate_widget(widget, *, angle=360.):
|
||||
with transform(widget) as ig: # <- InstructionGroup
|
||||
ig.add(rotate := Rotate(origin=widget.center))
|
||||
await anim_attrs(rotate, angle=angle)
|
||||
|
||||
If the position or size of the ``widget`` changes during the animation, you might need :class:`sync_attr`.
|
||||
|
||||
**The use_outer_canvas parameter**
|
||||
|
||||
While the context manager is active, the content of the widget's canvas would be:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# ... represents existing instructions
|
||||
|
||||
Widget:
|
||||
canvas.before:
|
||||
...
|
||||
canvas:
|
||||
PushMatrix
|
||||
InstructionGroup
|
||||
...
|
||||
PopMatrix
|
||||
canvas.after:
|
||||
...
|
||||
|
||||
but if ``use_outer_canvas`` is True, it would be:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Widget:
|
||||
canvas.before:
|
||||
PushMatrix
|
||||
InstructionGroup
|
||||
...
|
||||
canvas:
|
||||
...
|
||||
canvas.after:
|
||||
...
|
||||
PopMatrix
|
||||
'''
|
||||
|
||||
c = widget.canvas
|
||||
if use_outer_canvas:
|
||||
before = c.before
|
||||
after = c.after
|
||||
push_mat_idx = 0
|
||||
ig_idx = 1
|
||||
else:
|
||||
c.before # ensure 'canvas.before' exists
|
||||
|
||||
# Index starts from 1 because 'canvas.before' is sitting at index 0 and we usually want it to remain first.
|
||||
# See https://github.com/kivy/kivy/issues/7945 for details.
|
||||
push_mat_idx = 1
|
||||
ig_idx = 2
|
||||
before = after = c
|
||||
|
||||
push_mat = PushMatrix()
|
||||
ig = InstructionGroup()
|
||||
pop_mat = PopMatrix()
|
||||
|
||||
before.insert(push_mat_idx, push_mat)
|
||||
before.insert(ig_idx, ig)
|
||||
after.add(pop_mat)
|
||||
try:
|
||||
yield ig
|
||||
finally:
|
||||
after.remove(pop_mat)
|
||||
before.remove(ig)
|
||||
before.remove(push_mat)
|
||||
|
||||
|
||||
class suppress_event:
|
||||
'''
|
||||
Returns a context manager that prevents the callback functions (including the default handler) bound to an event
|
||||
from being called.
|
||||
|
||||
.. code-block::
|
||||
:emphasize-lines: 4
|
||||
|
||||
from kivy.uix.button import Button
|
||||
|
||||
btn = Button()
|
||||
btn.bind(on_press=lambda __: print("pressed"))
|
||||
with suppress_event(btn, 'on_press'):
|
||||
btn.dispatch('on_press')
|
||||
|
||||
The above code prints nothing because the callback function won't be called.
|
||||
|
||||
Strictly speaking, this context manager doesn't prevent all callback functions from being called.
|
||||
It only prevents the callback functions that were bound to an event before the context manager enters.
|
||||
Thus, the following code prints ``pressed``.
|
||||
|
||||
.. code-block::
|
||||
:emphasize-lines: 5
|
||||
|
||||
from kivy.uix.button import Button
|
||||
|
||||
btn = Button()
|
||||
with suppress_event(btn, 'on_press'):
|
||||
btn.bind(on_press=lambda __: print("pressed"))
|
||||
btn.dispatch('on_press')
|
||||
|
||||
.. warning::
|
||||
|
||||
You need to be careful when you suppress an ``on_touch_xxx`` event.
|
||||
See :ref:`kivys-event-system` for details.
|
||||
'''
|
||||
__slots__ = ('_dispatcher', '_name', '_bind_uid', '_filter', )
|
||||
|
||||
def __init__(self, event_dispatcher, event_name, *, filter=lambda *args, **kwargs: True):
|
||||
self._dispatcher = event_dispatcher
|
||||
self._name = event_name
|
||||
self._filter = filter
|
||||
|
||||
def __enter__(self):
|
||||
self._bind_uid = self._dispatcher.fbind(self._name, self._filter)
|
||||
|
||||
def __exit__(self, *args):
|
||||
self._dispatcher.unbind_uid(self._name, self._bind_uid)
|
||||
|
||||
|
||||
def create_texture_from_text(*, markup=False, **label_kwargs) -> Texture:
|
||||
'''
|
||||
.. code-block::
|
||||
|
||||
from kivy.metrics import sp
|
||||
|
||||
texture = create_texture_from_text(
|
||||
text='Hello',
|
||||
font_size=sp(50),
|
||||
font_name='Roboto',
|
||||
color=(1, 0, 0, 1),
|
||||
)
|
||||
|
||||
The keyword arguments are similar to :external:kivy:doc:`api-kivy.uix.label` 's.
|
||||
'''
|
||||
core_cls = CoreMarkupLabel if markup else CoreLabel
|
||||
core = core_cls(**label_kwargs)
|
||||
core.refresh()
|
||||
return core.texture
|
||||
|
||||
|
||||
class sync_attr:
|
||||
'''
|
||||
Returns a context manager that creates one-directional binding between attributes.
|
||||
|
||||
.. code-block::
|
||||
|
||||
import types
|
||||
|
||||
widget = Widget()
|
||||
obj = types.SimpleNamespace()
|
||||
|
||||
with sync_attr(from_=(widget, 'x'), to_=(obj, 'xx')):
|
||||
widget.x = 10
|
||||
assert obj.xx == 10 # synchronized
|
||||
obj.xx = 20
|
||||
assert widget.x == 10 # but not the other way around
|
||||
|
||||
This can be particularly useful when combined with :func:`transform`.
|
||||
|
||||
.. code-block::
|
||||
|
||||
from kivy.graphics import Rotate
|
||||
|
||||
async def rotate_widget(widget, *, angle=360.):
|
||||
with transform(widget) as ig:
|
||||
ig.add(rotate := Rotate(origin=widget.center))
|
||||
with sync_attr(from_=(widget, 'center'), to_=(rotate, 'origin')):
|
||||
await anim_attrs(rotate, angle=angle)
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
__slots__ = ('_from', '_to', '_bind_uid', )
|
||||
|
||||
def __init__(self, from_: T.Tuple[EventDispatcher, str], to_: T.Tuple[T.Any, str]):
|
||||
self._from = from_
|
||||
self._to = to_
|
||||
|
||||
def _sync(setattr, obj, attr_name, event_dispatcher, new_value):
|
||||
setattr(obj, attr_name, new_value)
|
||||
|
||||
def __enter__(self, partial=partial, sync=partial(_sync, setattr)):
|
||||
self._bind_uid = self._from[0].fbind(self._from[1], partial(sync, *self._to))
|
||||
|
||||
def __exit__(self, *args):
|
||||
self._from[0].unbind_uid(self._from[1], self._bind_uid)
|
||||
|
||||
del _sync
|
||||
|
||||
|
||||
class sync_attrs:
|
||||
'''
|
||||
When multiple :class:`sync_attr` calls take the same ``from_`` argument, they can be merged into a single
|
||||
:class:`sync_attrs` call. For instance, the following code:
|
||||
|
||||
.. code-block::
|
||||
|
||||
with sync_attr((widget, 'x'), (obj1, 'x')), sync_attr((widget, 'x'), (obj2, 'xx')):
|
||||
...
|
||||
|
||||
can be replaced with the following one:
|
||||
|
||||
.. code-block::
|
||||
|
||||
with sync_attrs((widget, 'x'), (obj1, 'x'), (obj2, 'xx')):
|
||||
...
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
'''
|
||||
__slots__ = ('_from', '_to', '_bind_uid', )
|
||||
|
||||
def __init__(self, from_: T.Tuple[EventDispatcher, str], *to_):
|
||||
self._from = from_
|
||||
self._to = to_
|
||||
|
||||
def _sync(setattr, to_, event_dispatcher, new_value):
|
||||
for obj, attr_name in to_:
|
||||
setattr(obj, attr_name, new_value)
|
||||
|
||||
def __enter__(self, partial=partial, sync=partial(_sync, setattr)):
|
||||
self._bind_uid = self._from[0].fbind(self._from[1], partial(sync, self._to))
|
||||
|
||||
def __exit__(self, *args):
|
||||
self._from[0].unbind_uid(self._from[1], self._bind_uid)
|
||||
|
||||
del _sync
|
20
kivy_venv/lib/python3.11/site-packages/asynckivy/vanim.py
Normal file
20
kivy_venv/lib/python3.11/site-packages/asynckivy/vanim.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
__all__ = (
|
||||
'dt', 'delta_time',
|
||||
'et', 'elapsed_time',
|
||||
'dt_et', 'delta_time_elapsed_time',
|
||||
'progress',
|
||||
'dt_et_progress', 'delta_time_elapsed_time_progress',
|
||||
)
|
||||
|
||||
import warnings
|
||||
from . import _anim_with_xxx
|
||||
|
||||
|
||||
warnings.warn("The 'vanim' module is deprecated. Use 'asynckivy.anim_with_xxx' instead.")
|
||||
|
||||
|
||||
delta_time = dt = _anim_with_xxx.anim_with_dt
|
||||
elapsed_time = et = _anim_with_xxx.anim_with_et
|
||||
progress = _anim_with_xxx.anim_with_ratio
|
||||
delta_time_elapsed_time = dt_et = _anim_with_xxx.anim_with_dt_et
|
||||
delta_time_elapsed_time_progress = dt_et_progress = _anim_with_xxx.anim_with_dt_et_ratio
|
Loading…
Add table
Add a link
Reference in a new issue