Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit doc #152

Merged
merged 2 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions sphinx/notes-ja.rst
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,28 @@ async操作が禁じられている場所
...
async for __ in async_iterator: # 駄目
...


-------------------
プログラムの構造
-------------------

理想を言えばプログラムは一つだけ"root"タスクを持ち、他のタスクはその子や子孫であるべきです。
そうする事でタスクの集合が一本の木で表され、プログラムの構造が明確になるからです。

asynckivyでそれを実現するには :func:`asynckivy.start` の呼び出しを一度だけにし、他のタスクは全て
:external+asyncgui:doc:`structured-concurrency-ja` を通して作ります。

.. code-block::

import asynckivy as ak

class YourApp(App):
def on_start(self):
self._root_task = ak.start(self.main())

def on_stop(self):
self._root_task.cancel()

async def main(self):
...
25 changes: 25 additions & 0 deletions sphinx/notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,28 @@ Here is a list of them:
...
async for __ in async_iterator: # NOT ALLOWED
...


-------------------------------
How to structure your program
-------------------------------

Ultimately, your program should have only one "root" task, with all other tasks as its children or descendants.
This is something that Trio forces you to do but asynckivy does not.

You can achieve this by calling :func:`asynckivy.start` only once in your program,
and spawning all other tasks through the :external+asyncgui:doc:`structured-concurrency` APIs.

.. code-block::

import asynckivy as ak

class YourApp(App):
def on_start(self):
self._root_task = ak.start(self.main())

def on_stop(self):
self._root_task.cancel()

async def main(self):
...
31 changes: 17 additions & 14 deletions src/asynckivy/_touch.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,37 +126,40 @@ async def touch_up_event(widget, touch, *, stop_dispatching=False, timeout=1.) -
'''
*(experimental state)*

Returns an awaitable that can be used to wait for the ``on_touch_up`` event of the given ``touch`` to occur.
Returns an awaitable that waits for an ``on_touch_up`` event to occur for the specified ``touch`` on the given
``widget``.

.. 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.
This API "grabs" the touch, making it behave differently from the following example, which does not grab.

.. 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.
Typically, this API is used with :any:`asyncgui.move_on_when`.

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.
.. code-block::

__, touch = await event(widget, 'on_touch_down', stop_dispatching=True)
touch_move_event = partial(
event, widget, 'on_touch_move', stop_dispatching=True,
filter=lambda w, t: t is touch and t.grab_current is w)

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.
async with move_on_when(touch_up_event(widget, touch)):
# This code block will be cancelled when the touch ends.
while True:
await touch_move_event()
...

.. _Kivy does not guarantee: https://kivy.org/doc/stable/guide/inputs.html#grabbing-touch-events
An advantage of the code above, compared to :any:`rest_of_touch_events`, is that it allows you to
perform other async operations inside the with-block.
'''
touch.grab(widget)
try:
Expand Down
31 changes: 27 additions & 4 deletions src/asynckivy/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,13 @@ class sync_attr:
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)
rotate = Rotate(origin=widget.center)
with (
transform(widget) as ig,
sync_attr(from_=(widget, 'center'), to_=(rotate, 'origin')),
):
ig.add(rotate)
await anim_attrs(rotate, angle=angle)

.. versionadded:: 0.6.1
'''
Expand Down Expand Up @@ -235,6 +238,26 @@ class sync_attrs:
with sync_attrs((widget, 'x'), (obj1, 'x'), (obj2, 'xx')):
...

This can be particularly useful when combined with :func:`transform`.

.. code-block::

from kivy.graphics import Rotate, Scale

async def scale_and_rotate_widget(widget, *, scale=2.0, angle=360.):
rotate = Rotate(origin=widget.center)
scale = Scale(origin=widget.center)
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),
)

.. versionadded:: 0.6.1
'''
__slots__ = ('_from', '_to', '_bind_uid', )
Expand Down
Loading