API Reference¶

anim_attrs(obj, *, duration=1.0, step=0, transition='linear', **animated_properties)¶

Animates attibutes of any object.

import types

obj = types.SimpleNamespace(x=0, size=(200, 300))
await anim_attrs(obj, x=100, size=(400, 400))

Warning

Unlike kivy.animation.Animation, this one does not support dictionary-type and nested-sequence.

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

Added in version 0.6.1.

Changed in version 0.9.0: The output_seq_type parameter was removed.

anim_attrs_abbr(obj, *, d=1.0, s=0, t='linear', **animated_properties)¶

anim_attrs() cannot animate attributes named step, duration and transition but this one can.

Added in version 0.6.1.

Changed in version 0.9.0: The output_seq_type parameter was removed.

async anim_with_ratio(*, base, step=0)¶

Returns an async iterator that yields the elapsed time since the start of the iteration, divided by base.

async for p in anim_with_ratio(base=3):
    print(p)

The code above is equivalent to the following:

with sleep_freq() as sleep:
    base = 3
    total_elapsed_time = 0.
    while True:
        total_elapsed_time += await sleep()
        p = total_elapsed_time / base
        print(p)

Use kivy.animation.AnimationTransition for non-linear curves.

from kivy.animation import AnimationTransition

in_cubic = AnimationTransition.in_cubic

async for p in anim_with_ratio(base=...):
    p = in_cubic(p)
    print(p)

Added in version 0.6.1.

Changed in version 0.7.0: The duration parameter was replaced with base. The loop no longer ends on its own.

class block_touch_events(event_dispatcher, *, filter=<function block_touch_events.<lambda>>)¶
with block_touch_events(widget):
    ...

Returns a context manager that blocks all touch events that meet both of the following criteria:

  • The touch is not currently grabbed by any widget. (i.e. touch.grab_current is None)

  • The touch is inside the widget’s bounding box. (i.e. widget.collide_point(*touch.pos))

Basically equivalent to the following:

def f(w, t):
    return t.grab_current is None and w.collide_point(*t.pos)
with (
    suppress_event(widget, 'on_touch_down', filter=f),
    suppress_event(widget, 'on_touch_move', filter=f),
    suppress_event(widget, 'on_touch_up', filter=f),
):
    ...

Added in version 0.10.0.

cancel_managed_tasks(*__)¶

Cancels all tasks started with managed_start().

Usually, you do not need to call this function directly, as it is automatically called when an EventLoop.on_stop event fires. However, you might need to call it manually in unit tests because the EventLoop.on_stop event wouldn’t be triggered in each test case.

Added in version 0.10.0.

async event(event_dispatcher, event_name, *, filter=None, stop_dispatching=False)¶

Returns an Awaitable that can be used to wait for:

  • a Kivy event to occur.

  • a Kivy property’s value to change.

# 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:

# 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:

This only works for events not for properties.

class event_freq(event_dispatcher, event_name, *, filter=None, stop_dispatching=False)¶

When handling a frequently occurring event, such as on_touch_move, the following kind of code might cause performance issues:

__, touch = await event(widget, 'on_touch_down')

# This loop registers and unregisters an event handler on every iteration.
while True:
    await event(widget, 'on_touch_move', filter=lambda w, t: t is touch)
    ...

If that happens, try the following code instead. It might resolve the issue:

__, touch = await event(widget, 'on_touch_down')

with event_freq(widget, "on_touch_move", filter=lambda w, t: t is touch) as on_touch_move:
    while True:
        await on_touch_move()
        ...

When listening for an on_touch_move event, you will often also want to listen for an on_touch_up event, which leads to deeply nested code:

__, touch = await event(widget, "on_touch_down")

def is_the_same_touch(w, t, touch=touch):
    return t is touch
async with move_on_when(event(widget, "on_touch_up", filter=is_the_same_touch)):
    with event_freq(widget, "on_touch_move", filter=is_the_same_touch) as on_touch_move:
        while True:
            await on_touch_move()
            ...

To mitigate this, event_freq can also be used as an async context manager, making the above code less nested:

async with (
    move_on_when(event(widget, "on_touch_up", filter=is_the_same_touch)),
    event_freq(widget, "on_touch_move", filter=is_the_same_touch) as on_touch_move,
):
    while True:
        await on_touch_move()
        ...

Added in version 0.7.1.

Changed in version 0.9.0: The free_to_await parameter was added.

Changed in version 0.11.0:

  • This can be used as either a synchronous or an asynchronous context manager. Prefer the synchronous form, as it has less overhead.

  • The free_to_await parameter was removed. You can treat it as if it were always set to True.

async interpolate(start, end, *, duration=1.0, step=0, transition='linear') AsyncIterator¶

Interpolates between the values start and end in an async-manner. Inspired by wasabi2d’s interpolate.

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

async interpolate_seq(start, end, *, duration, step=0, transition='linear') AsyncIterator¶

Same as interpolate() except this one is for sequence types.

async for v in interpolate_seq([0, 50], [100, 100], duration=1, step=0.3):
    print(v)

elapsed time

output

0

[0, 50]

0.3

[30, 65]

0.6

[60, 80]

0.9

[90, 95]

1.2 sec

[100, 100]

Added in version 0.7.0.

Changed in version 0.9.0: The output_type parameter was removed. The iterator now always yields a list.

managed_start(aw: Awaitable | Task, /) Task¶

A task started with this function will be automatically cancelled when an EventLoop.on_stop event fires, if it is still running. This prevents the task from being cancelled by the garbage collector, ensuring more reliable cleanup. You should always use this function instead of calling asynckivy.start directly, except when writing unit tests.

task = managed_start(async_func(...))

Added in version 0.7.1.

Changed in version 0.10.0: Uses EventLoop.on_stop instead of App.on_stop.

move_on_after(seconds: float) AbstractAsyncContextManager[Task]¶

Returns an async context manager that applies a time limit to its code block, like trio.move_on_after() does.

async with move_on_after(seconds) as timeout_tracker:
    ...
if timeout_tracker.finished:
    print("The code block was interrupted due to a timeout")
else:
    print("The code block exited gracefully.")

Added in version 0.6.1.

async n_frames(n: int)¶

Waits for a specified number of frames to elapse.

await n_frames(2)

If you want to wait for one frame, asynckivy.sleep() is preferable for a performance reason.

await sleep(0)
rest_of_touch_events(widget, touch, *, stop_dispatching=False, grab=True)¶

Returns an async context manager that helps to await both on_touch_move and on_touch_up events at the same time.

async with rest_of_touch_events(widget, touch) as on_touch_move:
    while True:
        await on_touch_move()
        print("touch moved")
print("touch ended")
Parameters:
  • grab – If set to False, this API will not rely on touch.grab(), which means there is no guarantee that all events from the given touch will be delivered to the widget, as documented in grabbing-touch-events. If the corresponding on_touch_up event is not delivered, the await on_touch_move() line will wait indefinitely for it. Do not set this to False unless you know what you are doing.

  • stop_dispatching – Whether to stop dispatching non-grabbed touch events corresponding to the given touch. (Grabbed touch events are always stopped if the grab is True, and are never stopped if the grab is False.) For details, see event-bubbling.

Added in version 0.9.1.

Changed in version 0.11.0:

  • The free_to_await parameter was removed. You can treat it as if it were always set to True.

  • The API renamed from rest_of_touch_events_cm to rest_of_touch_events. The original rest_of_touch_events was removed.

async run_in_executor(executor: ThreadPoolExecutor, func)¶

Runs a function within a concurrent.futures.ThreadPoolExecutor, and waits for the completion of the function.

executor = ThreadPoolExecutor()
...
return_value = await run_in_executor(executor, func)

See I/O in AsyncKivy for details.

Warning

When the caller Task is cancelled, the func will be left running if it has already started, which violates “structured concurrency”.

async run_in_thread(func, *, daemon=None)¶

Creates a new thread, runs a function within it, then waits for the completion of that function.

return_value = await run_in_thread(func)

See I/O in AsyncKivy for details.

Warning

When the caller Task is cancelled, the func will be left running, which violates “structured concurrency”.

sandwich_canvas(target: kivy.graphics.Canvas, top_bun: kivy.graphics.Instruction, bottom_bun: kivy.graphics.Instruction, *, canvas_layer: Literal['inner', 'outer', 'inner_outer'] = 'inner')¶

Returns a context manager that sandwiches the target’s graphics instructions between the top_bun and bottom_bun.

# The text of this label is drawn 20 pixels to the right of its original position.
with sandwich_canvas(label.canvas, Translate(20, 0), Translate(-20, 0)):
    ...

The canvas_layer parameter controls where top_bun and bottom_bun are inserted within the target canvas. If set to “inner” (the default), they are inserted into the outer side of the inner canvas:

# ... represents existing instructions

Widget:
    canvas.before:
        ...
    canvas:
        top_bun
        ...
        bottom_bun
    canvas.after:
        ...

If set to “outer”, they are inserted into the outer side of the outer canvas:

Widget:
    canvas.before:
        top_bun
        ...
    canvas:
        ...
    canvas.after:
        ...
        bottom_bun

If set to “inner_outer”, they are inserted into the inner side of the outer canvas:

Widget:
    canvas.before:
        ...
        top_bun
    canvas:
        ...
    canvas.after:
        bottom_bun
        ...

Added in version 0.10.0.

sleep(duration)¶

An async form of kivy.clock.Clock.schedule_once().

dt = await sleep(5)  # wait for 5 seconds
sleep_free(duration)¶

An async form of kivy.clock.Clock.schedule_once_free().

dt = await sleep_free(5)  # wait for 5 seconds
class sleep_freq(step=0)¶

An async form of kivy.clock.Clock.schedule_interval(). The following callback-style code:

def callback(dt):
    print(dt)
    if some_condition:
        return False

Clock.schedule_interval(callback, 0.1)

is equivalent to the following async-style code:

with sleep_freq(0.1) as sleep:
    while True:
        dt = await sleep()
        print(dt)
        if some_condition:
            break

Changed in version 0.9.0:

  • The API was made public again.

  • The free_to_await parameter was added.

Changed in version 0.11.0:

  • This can be used as either a synchronous or an asynchronous context manager. Prefer the synchronous form, as it has less overhead.

  • The free_to_await parameter was removed. You can treat it as if it were always set to True.

class smooth_attr(target: tuple[kivy.event.EventDispatcher, str], follower: tuple[Any, str], *, speed=10.0, min_diff=kivy.metrics.dp)¶

Makes an attribute smoothly follow another.

import types

widget = Widget(x=0)
obj = types.SimpleNamespace(xx=100)

# 'obj.xx' will smoothly follow 'widget.x'.
smooth_attr(target=(widget, 'x'), follower=(obj, 'xx'))

To make its effect temporary, use it with a with-statement:

# The effect lasts only within the with-block.
with smooth_attr(...):
    ...

A key feature of this API is that if the target value changes while being followed, the follower automatically adjusts to the new value.

Parameters:
  • target –

    Must be a numeric or numeric sequence type property, that is, one of the following:

  • speed – The speed coefficient for following. A larger value results in faster following.

  • min_diff – If the difference between the target and the follower is less than this value, the follower will instantly jump to the target’s value. When the target is a ColorProperty, you most likely want to set this to a very small value, such as 0.01. Defaults to dp(2).

Added in version 0.8.0.

stencil_mask(widget, *, canvas_layer: Literal['inner', 'outer', 'inner_outer'] = 'inner') Iterator[kivy.graphics.InstructionGroup]¶

Returns a context manager that allows restricting the drawing area of a specified widget to an arbitrary shape.

with stencil_mask(widget) as drawable_area:
    ...

The most common use case would be to confine drawing to the widget’s own area, which can be achieved as follows:

from kivy.graphics import Rectangle
import asynckivy as ak

rect = Rectangle()
with (
    ak.sync_attr(from_=(widget, 'pos'), to_=(rect, 'pos')),  # A
    ak.sync_attr(from_=(widget, 'size'), to_=(rect, 'size')),
    ak.stencil_mask(widget) as drawable_area,
):
    drawable_area.add(rect)
    ...

Since this use case is so common, stencil_widget_mask() is provided as a shorthand. Also, note that if the widget is a relative-type widget and the canvas_layer is not “outer”, line A above must be removed.

Parameters:

canvas_layer – Controls which part of the widget’s canvas is affected by the restriction. See sandwich_canvas() for details.

Added in version 0.9.1.

Changed in version 0.10.0: The use_outer_canvas parameter was replaced with the canvas_layer parameter.

stencil_widget_mask(widget, *, canvas_layer='inner', relative=False) Iterator[kivy.graphics.InstructionGroup]¶

Returns a context manager that restricts the drawing area to the widget’s own area.

with stencil_widget_mask(widget):
    ...
Parameters:
  • relative – Must be set to True if the widget is a relative-type widget.

  • canvas_layer – Controls which part of the widget’s canvas is affected by the restriction. See sandwich_canvas() for details.

Added in version 0.9.1.

Changed in version 0.10.0: The use_outer_canvas parameter was replaced with the canvas_layer parameter.

class suppress_event(event_dispatcher, event_name, *, filter=<function suppress_event.<lambda>>)¶

Returns a context manager that prevents the callback functions (including the default handler) bound to an event from being called.

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.

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')
class sync_attr(from_: tuple[kivy.event.EventDispatcher, str], to_: tuple[Any, str])¶

Creates one-directional binding between attributes.

import types

widget = Widget(x=100)
obj = types.SimpleNamespace()

sync_attr(from_=(widget, 'x'), to_=(obj, 'xx'))
assert obj.xx == 100  # synchronized
widget.x = 10
assert obj.xx == 10  # synchronized
obj.xx = 20
assert widget.x == 10  # but not the other way around

To make its effect temporary, use it with a with-statement:

# The effect lasts only within the with-block.
with sync_attr(...):
    ...

This can be particularly useful when combined with transform().

from kivy.graphics import Rotate

async def rotate_widget(widget, *, angle=360.):
    rotate = Rotate()
    with (
        transform(widget) as ig,
        sync_attr(from_=(widget, 'center'), to_=(rotate, 'origin')),
    ):
        ig.add(rotate)
        await anim_attrs(rotate, angle=angle)

Added in version 0.6.1.

Changed in version 0.8.0: The context manager now applies its effect upon creation, rather than when its __enter__() method is called, and __enter__() no longer performs any action.

Additionally, the context manager now assigns the from_ value to the to_ upon creation:

with sync_attr((widget, 'x'), (obj, 'xx')):
    assert widget.x == obj.xx
class sync_attrs(from_: tuple[kivy.event.EventDispatcher, str], *to_)¶

When multiple sync_attr calls take the same from_ argument, they can be merged into a single sync_attrs call. For instance, the following code:

with sync_attr((widget, 'x'), (obj1, 'x')), sync_attr((widget, 'x'), (obj2, 'xx')):
    ...

can be replaced with the following one:

with sync_attrs((widget, 'x'), (obj1, 'x'), (obj2, 'xx')):
    ...

This can be particularly useful when combined with transform().

from kivy.graphics import Rotate, Scale

async def scale_and_rotate_widget(widget, *, scale=2.0, angle=360.):
    rotate = Rotate()
    scale = Scale()
    with (
        transform(widget) as ig,
        sync_attrs((widget, 'center'), (rotate, 'origin'), (scale, 'origin')),
    ):
        ig.add(rotate)
        ig.add(scale)
        await wait_all(
            anim_attrs(rotate, angle=angle),
            anim_attrs(scale, x=scale, y=scale),
        )

Added in version 0.6.1.

Changed in version 0.8.0: The context manager now applies its effect upon creation, rather than when its __enter__() method is called, and __enter__() no longer performs any action.

Additionally, the context manager now assigns the from_ value to the to_ upon creation:

with sync_attrs((widget, 'x'), (obj, 'xx')):
    assert widget.x is obj.xx
transform(widget, *, canvas_layer: Literal['inner', 'outer', 'inner_outer'] = 'inner') Iterator[kivy.graphics.InstructionGroup]¶

Returns a context manager that helps apply transformations to the given widget.

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)
Parameters:

canvas_layer – Controls which part of the widget’s canvas is affected by the transformation. See sandwich_canvas() for details.

Changed in version 0.10.0: The use_outer_canvas parameter was replaced with the canvas_layer parameter.

visibility_aware_touch_events(widget, touch, *, stop_dispatching=False)¶

(experimental) rest_of_touch_events() with awareness of whether the touch is currently within the widget’s visible area. This can be useful when a widget is clipped by other widgets and you need to know whether the touch is inside the portion that is actually visible.

__, touch = await event(widget, "on_touch_down")
was_inside = widget.collide_point(*touch.pos)

async with visibility_aware_touch_events(widget, touch) as on_touch_move:
    while True:
        is_inside = await on_touch_move()
        if is_inside:
            if was_inside:
                print("Touch moved while staying within the visible area")
            else:
                print("Touch moved from outside to inside the visible area")
        else:
            if was_inside:
                print("Touch moved from inside to outside the visible area")
            else:
                print("Touch moved while staying outside the visible area")
        was_inside = is_inside
print("Touch ended.")

Warning

Since ScrollView does not dispatch touch events to its children for touches that start outside it, this API will not work properly if a ScrollView is in the target widget’s parent hierarchy and the touch starts outside the ScrollView.

Added in version 0.11.0.