# Draggable
![](http://img.youtube.com/vi/CjiRZjiSqgA/0.jpg)
[Youtube][youtube]
[README(Japanese)](README_jp.md)
Inspired by:
* [drag_n_drop][drag_n_drop] (`Draggable` is based on this, so please read its documentation first to get the basic idea of this one)
* [Flutter][flutter]
This flower adds a drag and drop functionality to layouts and widgets. There are 3
main components used to have drag and drop:
- The `KXDraggableBehavior`. An equivalent of drag_n_drop's
`DraggableObjectBehavior`.
- The `KXReorderableBehavior`. An equivalent of drag_n_drop's
`DraggableLayoutBehavior`.
- The `KXDroppableBehavior`. An equivalent of Flutter's `DragTarget`.
From now on, I use the term `droppable` to refer both `KXReorderableBehavior` and `KXDroppableBehavior`, and use the term `draggable` to refer `KXDraggableBehavior`.
## Installation
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.2
pip install "kivy_garden.draggable>=0.2,<0.3"
```
## Main differences from drag_n_drop
- Drag is triggered by a long-press. More precisely, when a finger of the user
dropped inside a draggable, if the finger stays for `draggable.drag_timeout`
milli seconds without traveling more than `draggable.drag_distance` pixels, it will
be recognized as a dragging gesture.
- Droppables can handle multiple drags simultaneously.
- Drag can be canceled by calling `draggable.drag_cancel()`.
- Nested `KXReorderableBehavior` is not officially supported. It may or may
not work depending on how `drag_classes` and `drag_cls` are set.
## Flow
Once a drag has started, it will go through the following path.
```mermaid
stateDiagram-v2
state cancelled? <<choice>>
state on_a_droppable? <<choice>>
state listed? <<choice>>
state accepted? <<choice>>
[*] --> on_drag_start
on_drag_start --> cancelled?
cancelled? --> on_a_droppable?: User lifted their finger up
cancelled? --> on_drag_cancel: 'draggable.cancel()' was called before the user lifts their finger up
on_a_droppable? --> listed?: Finger was on a droppable
on_a_droppable? --> on_drag_fail: not on a droppable
droppable_is_set: 'ctx.droppable' is set to the droppable
listed? --> droppable_is_set: 'draggable.drag_cls' was listed in the 'droppable.drag_classes'
listed? --> on_drag_fail: not listed
droppable_is_set --> accepted?
accepted? --> on_drag_succeed: Droppable accepted the drag ('droppable.accepts_drag()' returned True.)
accepted? --> on_drag_fail
on_drag_cancel --> on_drag_end
on_drag_fail --> on_drag_end
on_drag_succeed --> on_drag_end
on_drag_end --> [*]
```
## Cancellation
When your app switches a scene, you may want to cancel all the ongoing drags.
`ongoing_drags()` and `draggable.drag_cancel()` are what you want.
```python
from kivy_garden.draggable import ongoing_drags
def cancel_all_ongoing_drags():
for draggable in ongoing_drags():
draggable.drag_cancel()
```
## Using other widgets as an emitter
Let's say you are creating a card game, and there is a deck on the screen.
Say, you want the deck to emit a card when the user drops a finger on it,
and want the card to follow the finger until the user lifts it up.
In this situation, a widget that triggers a drag and a widget that is dragged are different.
You can implement it as follows:
```python
class Card(KXDraggableBehavior, Widget):
pass
class Deck(Widget):
def on_touch_down(self, touch):
if self.collide_point(*touch.opos):
Card(...).start_dragging_from_others_touch(self, touch)
```
## Customization
What draggables do `on_drag_succeed` / `on_drag_fail` / `on_drag_cancel` are completely customizable.
For example, by default, when a drag fails, the draggable will go back to where it came from with little animation.
This is because the default handler of `on_drag_fail` is implemented as follows:
```python
class KXDraggableBehavior:
async def on_drag_fail(self, touch, ctx):
await ak.animate(
self, duration=.1,
x=ctx.original_pos_win[0],
y=ctx.original_pos_win[1],
)
restore_widget_state(self, ctx.original_state)
```
If you don't need the animation, and want the draggable to go back instantly, overwrite the handler as follows:
```python
class MyDraggable(KXDraggableBehavior, Widget):
def on_drag_fail(self, touch, ctx):
restore_widget_state(self, ctx.original_state)
```
Or if you want the draggable to not go back, and want it to stay the current position, overwrite the handler as follows:
```python
class MyDraggable(KXDraggableBehavior, Widget):
def on_drag_fail(self, touch, ctx):
pass
```
Another example: when a drag succeed, the draggable will become a child of droppable, by default.
If you don't like it, and want the draggable to fade-out,
overwrite the handler as follows:
```python
class MyDraggable(KXDraggableBehavior, Widget):
async def on_drag_succeed(self, touch, ctx):
import asynckivy
await asynckivy.animate(self, opacity=0)
self.parent.remove_widget(self)
```
Just like that, you have free rein to change those behaviors.
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 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,
its code will be a part of dragging process and is guaranteed to be finished before ``on_drag_end`` gets fired.
## License
This software is released under the terms of the MIT License.
[drag_n_drop]:https://github.com/kivy-garden/drag_n_drop
[flutter]:https://api.flutter.dev/flutter/widgets/Draggable-class.html
[youtube]:https://www.youtube.com/playlist?list=PLNdhqAjzeEGiepWKfP43Dh7IWqn3cQtpQ
Raw data
{
"_id": null,
"home_page": "https://github.com/kivy-garden/draggable",
"name": "kivy-garden-draggable",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8.1,<4.0.0",
"maintainer_email": "",
"keywords": "kivy",
"author": "Natt\u014dsai Mit\u014d",
"author_email": "flow4re2c@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/53/ef/e9077c4c0ad3f941dfc26706afbc494d444eacfcd4fc012f58adb7d5a5c4/kivy_garden_draggable-0.2.0.tar.gz",
"platform": null,
"description": "# Draggable\n\n![](http://img.youtube.com/vi/CjiRZjiSqgA/0.jpg) \n[Youtube][youtube] \n[README(Japanese)](README_jp.md) \n\nInspired by:\n\n* [drag_n_drop][drag_n_drop] (`Draggable` is based on this, so please read its documentation first to get the basic idea of this one)\n* [Flutter][flutter]\n\nThis flower adds a drag and drop functionality to layouts and widgets. There are 3\nmain components used to have drag and drop:\n\n- The `KXDraggableBehavior`. An equivalent of drag_n_drop's\n `DraggableObjectBehavior`.\n- The `KXReorderableBehavior`. An equivalent of drag_n_drop's\n `DraggableLayoutBehavior`.\n- The `KXDroppableBehavior`. An equivalent of Flutter's `DragTarget`.\n\nFrom now on, I use the term `droppable` to refer both `KXReorderableBehavior` and `KXDroppableBehavior`, and use the term `draggable` to refer `KXDraggableBehavior`.\n\n## Installation\n\nIt's recommended to pin the minor version, because if it changed, it means some important breaking changes occurred.\n\n```\npoetry add kivy_garden.draggable@~0.2\npip install \"kivy_garden.draggable>=0.2,<0.3\"\n```\n\n## Main differences from drag_n_drop\n\n- Drag is triggered by a long-press. More precisely, when a finger of the user\n dropped inside a draggable, if the finger stays for `draggable.drag_timeout`\n milli seconds without traveling more than `draggable.drag_distance` pixels, it will\n be recognized as a dragging gesture.\n- Droppables can handle multiple drags simultaneously.\n- Drag can be canceled by calling `draggable.drag_cancel()`.\n- Nested `KXReorderableBehavior` is not officially supported. It may or may\n not work depending on how `drag_classes` and `drag_cls` are set.\n\n## Flow\n\nOnce a drag has started, it will go through the following path.\n\n```mermaid\nstateDiagram-v2\n state cancelled? <<choice>>\n state on_a_droppable? <<choice>>\n state listed? <<choice>>\n state accepted? <<choice>>\n\n [*] --> on_drag_start\n on_drag_start --> cancelled?\n cancelled? --> on_a_droppable?: User lifted their finger up\n cancelled? --> on_drag_cancel: 'draggable.cancel()' was called before the user lifts their finger up\n\n on_a_droppable? --> listed?: Finger was on a droppable\n on_a_droppable? --> on_drag_fail: not on a droppable\n\n droppable_is_set: 'ctx.droppable' is set to the droppable\n listed? --> droppable_is_set: 'draggable.drag_cls' was listed in the 'droppable.drag_classes'\n listed? --> on_drag_fail: not listed\n\n droppable_is_set --> accepted?\n accepted? --> on_drag_succeed: Droppable accepted the drag ('droppable.accepts_drag()' returned True.)\n accepted? --> on_drag_fail\n\n on_drag_cancel --> on_drag_end\n on_drag_fail --> on_drag_end\n on_drag_succeed --> on_drag_end\n\n on_drag_end --> [*]\n```\n\n## Cancellation\n\nWhen your app switches a scene, you may want to cancel all the ongoing drags.\n`ongoing_drags()` and `draggable.drag_cancel()` are what you want.\n\n```python\nfrom kivy_garden.draggable import ongoing_drags\n\ndef cancel_all_ongoing_drags():\n for draggable in ongoing_drags():\n draggable.drag_cancel()\n```\n\n## Using other widgets as an emitter\n\nLet's say you are creating a card game, and there is a deck on the screen.\nSay, you want the deck to emit a card when the user drops a finger on it,\nand want the card to follow the finger until the user lifts it up.\nIn this situation, a widget that triggers a drag and a widget that is dragged are different.\nYou can implement it as follows:\n\n```python\nclass Card(KXDraggableBehavior, Widget):\n pass\n\n\nclass Deck(Widget):\n def on_touch_down(self, touch):\n if self.collide_point(*touch.opos):\n Card(...).start_dragging_from_others_touch(self, touch)\n```\n\n## Customization\n\nWhat draggables do `on_drag_succeed` / `on_drag_fail` / `on_drag_cancel` are completely customizable.\nFor example, by default, when a drag fails, the draggable will go back to where it came from with little animation.\nThis is because the default handler of `on_drag_fail` is implemented as follows:\n\n```python\nclass KXDraggableBehavior:\n async def on_drag_fail(self, touch, ctx):\n await ak.animate(\n self, duration=.1,\n x=ctx.original_pos_win[0],\n y=ctx.original_pos_win[1],\n )\n restore_widget_state(self, ctx.original_state)\n```\n\nIf you don't need the animation, and want the draggable to go back instantly, overwrite the handler as follows:\n\n```python\nclass MyDraggable(KXDraggableBehavior, Widget):\n def on_drag_fail(self, touch, ctx):\n restore_widget_state(self, ctx.original_state)\n```\n\nOr if you want the draggable to not go back, and want it to stay the current position, overwrite the handler as follows:\n\n```python\nclass MyDraggable(KXDraggableBehavior, Widget):\n def on_drag_fail(self, touch, ctx):\n pass\n```\n\nAnother example: when a drag succeed, the draggable will become a child of droppable, by default.\nIf you don't like it, and want the draggable to fade-out,\noverwrite the handler as follows:\n\n```python\nclass MyDraggable(KXDraggableBehavior, Widget):\n async def on_drag_succeed(self, touch, ctx):\n import asynckivy\n await asynckivy.animate(self, opacity=0)\n self.parent.remove_widget(self)\n```\n\nJust like that, you have free rein to change those behaviors.\nBut note that **only the default handler of `on_drag_succeed` and `on_drag_fail`\ncan be an async function. Those two only.**\n\nYou might say \"What's the point of implementing a default handler as an async function,\nwhen you can just launch any number of tasks from a regular function by using ``asynckivy.start()``?\".\nWell, if you use ``asynckivy.start()``, that task will run independently from the dragging process,\nwhich means the draggable might fire ``on_drag_end`` and might start another drag while the task is still running.\nIf a default handler is an async function,\nits code will be a part of dragging process and is guaranteed to be finished before ``on_drag_end`` gets fired.\n\n## License\n\nThis software is released under the terms of the MIT License.\n\n[drag_n_drop]:https://github.com/kivy-garden/drag_n_drop\n[flutter]:https://api.flutter.dev/flutter/widgets/Draggable-class.html\n[youtube]:https://www.youtube.com/playlist?list=PLNdhqAjzeEGiepWKfP43Dh7IWqn3cQtpQ\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Drag & Drop Extension for Kivy",
"version": "0.2.0",
"project_urls": {
"Homepage": "https://github.com/kivy-garden/draggable",
"Repository": "https://github.com/kivy-garden/draggable"
},
"split_keywords": [
"kivy"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "507de5309612b42728c31e3933a40907114c18fbe325a5c29f5df40727754181",
"md5": "941a11639d4007ddcb25fb3367ccd41e",
"sha256": "d3d7fc715dcb7da7b8d3c4a4f64d7e4ac0b6cb1c62b467875e5fafe1170610f7"
},
"downloads": -1,
"filename": "kivy_garden_draggable-0.2.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "941a11639d4007ddcb25fb3367ccd41e",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8.1,<4.0.0",
"size": 9250,
"upload_time": "2023-08-28T11:11:44",
"upload_time_iso_8601": "2023-08-28T11:11:44.141393Z",
"url": "https://files.pythonhosted.org/packages/50/7d/e5309612b42728c31e3933a40907114c18fbe325a5c29f5df40727754181/kivy_garden_draggable-0.2.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "53efe9077c4c0ad3f941dfc26706afbc494d444eacfcd4fc012f58adb7d5a5c4",
"md5": "4e44fe99c608f5b4bc5594624f1b738a",
"sha256": "e43be691c204dc8b09da34441a919c3a7de7b623cdfa0e727840dbff8efcb976"
},
"downloads": -1,
"filename": "kivy_garden_draggable-0.2.0.tar.gz",
"has_sig": false,
"md5_digest": "4e44fe99c608f5b4bc5594624f1b738a",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8.1,<4.0.0",
"size": 10228,
"upload_time": "2023-08-28T11:11:45",
"upload_time_iso_8601": "2023-08-28T11:11:45.533879Z",
"url": "https://files.pythonhosted.org/packages/53/ef/e9077c4c0ad3f941dfc26706afbc494d444eacfcd4fc012f58adb7d5a5c4/kivy_garden_draggable-0.2.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-08-28 11:11:45",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "kivy-garden",
"github_project": "draggable",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "kivy-garden-draggable"
}