From b17a81eeecc41908d26fbc53d978311b80e48f36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natt=C5=8Dsai=20Mit=C5=8D?= Date: Mon, 28 Aug 2023 18:38:35 +0900 Subject: [PATCH 1/3] remove temp_grab_current (private api) --- src/kivy_garden/draggable/_impl.py | 7 +++++-- src/kivy_garden/draggable/_utils.py | 16 +--------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/kivy_garden/draggable/_impl.py b/src/kivy_garden/draggable/_impl.py index 26b8a24..3abcb31 100644 --- a/src/kivy_garden/draggable/_impl.py +++ b/src/kivy_garden/draggable/_impl.py @@ -19,7 +19,7 @@ import asynckivy as ak from ._utils import ( - temp_transform, temp_grab_current, _create_spacer, + temp_transform, _create_spacer, save_widget_state, restore_widget_state, ) @@ -228,10 +228,13 @@ async def _treat_a_touch_as_a_drag(self, touch, *, do_transform=False, touch_rec async def _simulate_a_normal_touch(self, touch, *, do_transform=False, do_touch_up=False): # simulate 'on_touch_down' - with temp_grab_current(touch): + original = touch.grab_current + try: touch.grab_current = None with temp_transform(touch, self.parent.to_widget) if do_transform else nullcontext(): super().on_touch_down(touch) + finally: + touch.grab_current = original if not do_touch_up: return diff --git a/src/kivy_garden/draggable/_utils.py b/src/kivy_garden/draggable/_utils.py index 23215a3..3122b83 100644 --- a/src/kivy_garden/draggable/_utils.py +++ b/src/kivy_garden/draggable/_utils.py @@ -1,5 +1,5 @@ __all__ = ( - 'temp_transform', 'temp_grab_current', + 'temp_transform', 'save_widget_state', 'restore_widget_state', 'save_widget_location', 'restore_widget_location', ) @@ -24,20 +24,6 @@ def __exit__(self, *args): self._touch.pop() -class temp_grab_current: - __slots__ = ('_touch', '_original', ) - - def __init__(self, touch): - self._touch = touch - self._original = touch.grab_current - - def __enter__(self): - pass - - def __exit__(self, *args): - self._touch.grab_current = self._original - - _shallow_copyable_property_names = ( 'x', 'y', 'width', 'height', 'size_hint_x', 'size_hint_y', From b405e18dcad7e3941f8eaf55766ac53d9bf2f212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natt=C5=8Dsai=20Mit=C5=8D?= Date: Mon, 28 Aug 2023 19:00:13 +0900 Subject: [PATCH 2/3] refactor --- examples/reacting_to_entering_and_leaving.py | 12 +++++------- examples/reorderable_stacklayout.py | 7 +++---- src/kivy_garden/draggable/_impl.py | 10 ++++------ 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/examples/reacting_to_entering_and_leaving.py b/examples/reacting_to_entering_and_leaving.py index 5d7fc6c..f51da33 100644 --- a/examples/reacting_to_entering_and_leaving.py +++ b/examples/reacting_to_entering_and_leaving.py @@ -1,4 +1,4 @@ -from kivy.utils import reify +from functools import cached_property from kivy.properties import NumericProperty from kivy.app import App from kivy.lang import Builder @@ -95,7 +95,7 @@ class ReactiveDroppableBehavior(KXDroppableBehavior): ''' __events__ = ('on_drag_enter', 'on_drag_leave', ) - @reify + @cached_property def __ud_key(self): return 'ReactiveDroppableBehavior.' + str(self.uid) @@ -107,9 +107,7 @@ def on_touch_move(self, touch): if drag_cls is not None: touch_ud[ud_key] = None if drag_cls in self.drag_classes: - # Start watching the touch. Use ``ak.or_()`` so that ``_watch_touch()`` will be automatically - # cancelled when the drag is cancelled. - ak.start(ak.or_( + ak.start(ak.wait_any( self._watch_touch(touch), ak.event(touch.ud['kivyx_draggable'], 'on_drag_end'), )) @@ -122,8 +120,8 @@ async def _watch_touch(self, touch): self.dispatch('on_drag_enter', touch, ctx, draggable) try: - async with ak.watch_touch(self, touch) as is_touch_move: - while await is_touch_move(): + async with ak.watch_touch(self, touch) as in_progress: + while await in_progress(): if not collide_point(*touch.pos): return finally: diff --git a/examples/reorderable_stacklayout.py b/examples/reorderable_stacklayout.py index 37fbe25..db699e2 100644 --- a/examples/reorderable_stacklayout.py +++ b/examples/reorderable_stacklayout.py @@ -1,8 +1,7 @@ ''' -When you want to make ``StackLayout`` re-orderable, you may want to disable -the ``size_hint`` of each child, or may want to limit the maximum size of -each child, otherwise the layout may be messed up. You can confirm it by -commenting/uncommenting the ``SampleApp.on_start()``'s body. +When you want to make a :class:`kivy.uix.stacklayout.StackLayout` re-orderable, you may want to disable the +``size_hint`` of each child, or may want to limit the maximum size of each child, otherwise the layout will be messed +up. You can confirm that by commenting/uncommenting the part of ``SampleApp.on_start()``. ''' from kivy.lang import Builder diff --git a/src/kivy_garden/draggable/_impl.py b/src/kivy_garden/draggable/_impl.py index 3abcb31..1de2ff9 100644 --- a/src/kivy_garden/draggable/_impl.py +++ b/src/kivy_garden/draggable/_impl.py @@ -140,7 +140,7 @@ def start_dragging_from_others_touch(self, receiver: Widget, touch): --------- * ``receiver`` ... The widget that received the ``touch``. - * ``touch`` ... A touch that is going to drag me. + * ``touch`` ... The touch that is going to drag me. ''' if touch.time_end != -1: return @@ -370,9 +370,7 @@ def on_touch_move(self, touch): if drag_cls is not None: touch_ud[ud_key] = None if drag_cls in self.drag_classes: - # Start watching the touch. Use ``ak.or_()`` so that ``_watch_touch()`` will be automatically - # cancelled when the drag is cancelled. - ak.start(ak.or_( + ak.start(ak.wait_any( self._watch_touch(touch), ak.event(touch.ud['kivyx_draggable'], 'on_drag_end'), )) @@ -395,8 +393,8 @@ async def _watch_touch(self, touch): touch_ud['kivyx_drag_ctx'].original_state, ignore_parent=True) add_widget(spacer) - async with ak.watch_touch(self, touch) as is_touch_move: - while await is_touch_move(): + async with ak.watch_touch(self, touch) as in_progress: + while await in_progress(): x, y = touch.pos if collide_point(x, y): new_idx = get_drop_insertion_index_move(x, y, spacer) From 8ef59de14088af0efea45cfd3b45ca637aa00805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Natt=C5=8Dsai=20Mit=C5=8D?= Date: Mon, 28 Aug 2023 20:05:25 +0900 Subject: [PATCH 3/3] publish 0.2.0 --- README.md | 6 +++--- README_jp.md | 54 ++++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 7b38cdb..25361ea 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ From now on, I use the term `droppable` to refer both `KXReorderableBehavior` an It's recommended to pin the minor version, because if it changed, it means some important breaking changes occurred. ``` -poetry add kivy_garden.draggable@~0.1 -pip install "kivy_garden.draggable>=0.1,<0.2" +poetry add kivy_garden.draggable@~0.2 +pip install "kivy_garden.draggable>=0.2,<0.3" ``` ## Main differences from drag_n_drop @@ -156,7 +156,7 @@ But note that **only the default handler of `on_drag_succeed` and `on_drag_fail` can be an async function. Those two only.** You might say "What's the point of implementing a default handler as an async function, -when you can just launch any number of tasks from regular function by using ``asynckivy.start()``?". +when you can just launch any number of tasks from a regular function by using ``asynckivy.start()``?". Well, if you use ``asynckivy.start()``, that task will run independently from the dragging process, which means the draggable might fire ``on_drag_end`` and might start another drag while the task is still running. If a default handler is an async function, diff --git a/README_jp.md b/README_jp.md index be7fb61..1480dff 100644 --- a/README_jp.md +++ b/README_jp.md @@ -3,7 +3,7 @@ ![](http://img.youtube.com/vi/CjiRZjiSqgA/0.jpg) [Youtube][youtube] -`kivy_garden.draggable`はdrag&dropの機能を実現するための拡張機能で以下の三つの部品で構成される。 +`kivy_garden.draggable`はdrag&dropの機能を実現するための拡張機能で以下の三つの部品で構成されます。 - `KXDraggableBehavior` ... dragできるようにしたいwidgetが継承すべきclass - `KXDroppableBehavior`と`KXReorderableBehavior` ... dragされているwidgetを受け入れられるようにしたいwidgetが継承すべきclass @@ -15,17 +15,18 @@ このmoduleのminor versionが変わった時は何らかの重要な互換性の無い変更が加えられた可能性が高いので、使う際はminor versionまでを固定してください。 ``` -poetry add kivy_garden.draggable@~0.1 -pip install "kivy_garden.draggable>=0.1,<0.2" +poetry add kivy_garden.draggable@~0.2 +pip install "kivy_garden.draggable>=0.2,<0.3" ``` ## dragが始まる条件 -dragは長押しによって引き起こされる。より具体的には利用者の指がdraggable内に降りてから`draggable.drag_distance`pixel以上動かずに`draggable.drag_timeout`ミリ秒以上指が離れなかった場合のみ引き起こされる。このためscroll操作(指がすぐさま動き出す)やtap動作(指がすぐに離れる)として誤認されにくい。 +dragは長押しによって引き起こされます。より具体的には利用者の指がdraggable内に降りてから`draggable.drag_distance`pixel以上動かずに`draggable.drag_timeout`ミリ秒以上指が離れなかった場合のみ引き起こされます。 +このためscroll操作(指がすぐさま動き出す)やtap動作(指がすぐに離れる)として誤認されにくいです。 ## dragが始まった後の処理の流れ -ユーザーがdraggableの上に指を降ろしてdragが始まった後の流れは以下のようになる。 +ユーザーがdraggableの上に指を降ろしてdragが始まった後の流れは以下のようになります。 ```mermaid stateDiagram-v2 @@ -60,9 +61,9 @@ stateDiagram-v2 ## 受け入れるdragの選別 図に書かれているように利用者の指が離れた時にdragが受け入れられるか否かの判断がなされ、 -指がdroppableの上じゃない所で離れた場合や`draggable.drag_cls`が`droppable.drag_classes`に含まれていない場合はまず即drag失敗となる。 +指がdroppableの上じゃない所で離れた場合や`draggable.drag_cls`が`droppable.drag_classes`に含まれていない場合はまず即drag失敗となります。 -その選別をくぐり抜けたdraggableは`droppable.accepts_drag()`へ渡され、そこでdragが受け入れられるか否かの最終判断が下される。例えばmethodが +その選別をくぐり抜けたdraggableは`droppable.accepts_drag()`へ渡され、そこでdragが受け入れられるか否かの最終判断が下されます。例えばmethodが ```python class MyDroppable(KXDroppableBehavior, Widget): @@ -70,16 +71,16 @@ class MyDroppable(KXDroppableBehavior, Widget): return not self.children ``` -という風に実装されていたら、このdroppableは自分が子を持っている間は例え適切な`drag_cls`を持つdraggableであっても受け付けない。 +という風に実装されていたら、このdroppableは自分が子を持っている間は例え適切な`drag_cls`を持つdraggableであっても受け付けません。 ## dragの中止 -アプリが次のシーンに移りたい時にまだdrag中のwidgetがあると不都合かもれしない。そのような事態に備えて +アプリが次のシーンに移りたい時にまだdrag中のwidgetがあると不都合かもしれません。そのような事態に備えて - 現在進行中のdragを列挙する`ongoing_drags()`と -- dragを中止する`draggable.drag_cancel()`がある。 +- dragを中止する`draggable.drag_cancel()`があります。 -これらを用いる事で以下のように進行中のdragを全て中止できる。 +これらを用いる事で以下のように進行中のdragを全て中止できます。 ```python from kivy_garden.draggable import ongoing_drags @@ -92,17 +93,17 @@ def cancel_all_ongoing_drags(): ## dragを引き起こすwidgetとdragされるwidgetを別にする 上で述べたようにdragはdraggableを長押しすることで引き起こされるので、 -dragを引き起こすwidgetとdragされるwidgetは基本同じである。 -でも例えばcard gameを作っているとして画面上に山札があったとする。 -そしてdrag操作によって山札から札を引けるようにしたいとする。 +dragを引き起こすwidgetとdragされるwidgetは基本同じです。 +でも例えばcard gameを作っているとして画面上に山札があったとして +drag操作によって山札から札を引けるようにしたいとします。 具体的には利用者が山札に指を触れた時に札を作り出し、 -そのまま指の動きに沿って札を追わせたいとする。 +そのまま指の動きに沿って札を追わせたいとします。 このような - dragを引き起こすwidget(山札)と - dragされるwidget(山札から引かれた札) -が別である状況では`draggable.start_dragging_from_others_touch()`が使える。 +が別である状況では`draggable.start_dragging_from_others_touch()`が使えます。 ```python class Card(KXDraggableBehavior, Widget): @@ -117,7 +118,8 @@ class Deck(Widget): ## 自由に振る舞いを変える -dragが失敗/成功/中止した時に何をするかは完全にあなたに委ねられている。例えばdrag失敗時は既定ではアニメーションしながら元の場所に戻るが、これをアニメーション無しで瞬時に戻したいなら以下のようにdefault handlerを上書きすれば良い。 +dragが失敗/成功/中止した時に何をするかは完全にあなたに委ねられています。 +例えばdrag失敗時は既定ではアニメーションしながら元の場所に戻りますが、これをアニメーション無しで瞬時に戻したいなら以下のようにdefault handlerを上書きすれば良いです。 ```python class MyDraggable(KXDraggableBehavior, Widget): @@ -125,7 +127,7 @@ class MyDraggable(KXDraggableBehavior, Widget): restore_widget_state(self, ctx.original_state) ``` -また何もせずにその場に残って欲しいなら以下のようにすれば良い。 +また何もせずにその場に残って欲しいなら以下のようにすれば良いです。 ```python class MyDraggable(KXDraggableBehavior, Widget): @@ -133,7 +135,7 @@ class MyDraggable(KXDraggableBehavior, Widget): pass ``` -成功時も同様で、既定では受け入れてくれたdroppableの子widgetになるように実装されているが以下のようにすれば子widgetにはならずに現在の位置で徐々に透明になって消える事になる。 +成功時も同様で、既定では受け入れてくれたdroppableの子widgetになるように実装されていますが以下のようにすると子widgetにはならずに現在の位置で徐々に透明になって消える事になります。 ```python import asynckivy as ak @@ -144,17 +146,17 @@ class MyDraggable(KXDraggableBehavior, Widget): self.parent.remove_widget(self) ``` -このようにdefault handlerを上書きすることで自由に振るまいを変えられる。 -ただし**async関数になれるのは`on_drag_succeed`と`on_drag_fail`のdefault handlerだけ**なので注意されたし。 +このようにdefault handlerを上書きすることで自由に振るまいを変えられます。 +ただし**async関数になれるのは`on_drag_succeed`と`on_drag_fail`のdefault handlerだけ**なので注意してください。 ここで - default handlerをasync関数にするのと - default handlerは普通の関数のままにしておいて内部で`asynckivy.start()`を用いてasync関数を立ち上げるのと -の違いについて説明する。 -前者ではasync関数のcodeがdrag処理の間に挟み込まれ、codeが`on_drag_end`が起こるより前に完遂される事が保証されるのに対し、 -後者ではcodeがdrag処理とは独立して進むので`on_drag_end`が起こるより前に完了する保証はない。 +の違いについて説明します。 +前者ではasync関数のcodeがdrag処理の間に挟み込まれcodeが`on_drag_end`が起こるより前に完遂される事が保証されるのに対し、 +後者ではcodeがdrag処理とは独立して進むので`on_drag_end`が起こるより前に完了する保証はありません。 なのでもし上の`on_drag_succeed`の例を後者のやり方で実装すると ```python @@ -170,8 +172,8 @@ class MyDraggable(KXDraggableBehavior, Widget): ``` `ak.animate()`の進行中にdragが完了し、そこで利用者が再び指を触れたことで次のdragが始まり、 -その最中に`self.parent.remove_widget(self)`が実行されてdraggableが親widgetから切り離されてしまうなんて事が起こりうる。 -なので **drag完了前に完遂させたい非同期処理があるのなら必ず前者の方法を使うべし**。 +その最中に`self.parent.remove_widget(self)`が実行されてdraggableが親widgetから切り離されてしまうなんて事が起こりえます。 +なので **drag完了前に完遂させたい非同期処理があるのなら必ず前者の方法を使ってください**。 ## その他 diff --git a/pyproject.toml b/pyproject.toml index d27f0a7..588f739 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "kivy-garden-draggable" -version = "0.2.0.dev1" +version = "0.2.0" description = "Drag & Drop Extension for Kivy" authors = ["Nattōsai Mitō "] license = "MIT"