Package: kivy-garden-draggable
DraggableYoutubeREADME(Japanese)Inspired by:drag_n_drop(Draggableis based on this, so please read its documentation first to get the basic idea of this one)FlutterThis flower adds a drag and drop functionality to layouts and widgets. There are 3
main components used to have drag and drop:TheKXDraggableBehavior. An equivalent of drag_n_drop'sDraggableObjectBehavior.TheKXReorderableBehavior. An equivalent of drag_n_drop'sDraggableLayoutBehavior.TheKXDroppableBehavior. An equivalent of Flutter'sDragTarget.From now on, I use the termdroppableto refer bothKXReorderableBehaviorandKXDroppableBehavior, and use the termdraggableto referKXDraggableBehavior.InstallationIt'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_dropDrag is triggered by a long-press. More precisely, when a finger of the user
dropped inside a draggable, if the finger stays fordraggable.drag_timeoutmilli seconds without traveling more thandraggable.drag_distancepixels, it will
be recognized as a dragging gesture.Droppables can handle multiple drags simultaneously.Drag can be canceled by callingdraggable.drag_cancel().NestedKXReorderableBehavioris not officially supported. It may or may
not work depending on howdrag_classesanddrag_clsare set.FlowOnce a drag has started, it will go through the following path.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 --> [*]CancellationWhen your app switches a scene, you may want to cancel all the ongoing drags.ongoing_drags()anddraggable.drag_cancel()are what you want.fromkivy_garden.draggableimportongoing_dragsdefcancel_all_ongoing_drags():fordraggableinongoing_drags():draggable.drag_cancel()Using other widgets as an emitterLet'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:classCard(KXDraggableBehavior,Widget):passclassDeck(Widget):defon_touch_down(self,touch):ifself.collide_point(*touch.opos):Card(...).start_dragging_from_others_touch(self,touch)CustomizationWhat draggables doon_drag_succeed/on_drag_fail/on_drag_cancelare 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 ofon_drag_failis implemented as follows:classKXDraggableBehavior:asyncdefon_drag_fail(self,touch,ctx):awaitak.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:classMyDraggable(KXDraggableBehavior,Widget):defon_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:classMyDraggable(KXDraggableBehavior,Widget):defon_drag_fail(self,touch,ctx):passAnother 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:classMyDraggable(KXDraggableBehavior,Widget):asyncdefon_drag_succeed(self,touch,ctx):importasynckivyawaitasynckivy.animate(self,opacity=0)self.parent.remove_widget(self)Just like that, you have free rein to change those behaviors.
But note thatonly the default handler ofon_drag_succeedandon_drag_failcan 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 usingasynckivy.start()?".
Well, if you useasynckivy.start(), that task will run independently from the dragging process,
which means the draggable might fireon_drag_endand 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 beforeon_drag_endgets fired.LicenseThis software is released under the terms of the MIT License.