I'm trying to combine functionality from KivyMD MDCardSwipe and kivy_garden drag_n_drop. MDCardSwipe allows you to swipe a card to the side to reveal an underlying widget -- in my case with a trash icon and delete function -- and drag_n_drop allows you to drag widgets around to reorder them within a layout. Ideally I want swipe-able cards that I can rearrange by dragging.
I have created a class DragCard shown below that inherits from both MDCardSwipe, and DraggableObjectBehavior. When I order this way the MDCardSwipe functionality works, but the DraggableObjectBehavior does not. When I reverse the order, MDCardSwipe no longer works but DraggableObjectBehavior does.
MDCardSwipe has a relatively small area within which the swipe must begin. I want MDCardSwipe to receive the touch if it's within this area, and to pass the touch on to DraggableObjectBehavior if the touch is outside said area.
Any help would be greatly appreciated!
.py file:
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.card import MDCardSwipe
from kivy.uix.widget import Widget
from kivy_garden.drag_n_drop import DraggableLayoutBehavior, DraggableObjectBehavior
from kivy.properties import StringProperty
class DraggableBoxLayout(DraggableLayoutBehavior, BoxLayout):
def __init__(self, **kwargs):
super().__init__(
spacer_widget = MySpacer(),
spacer_props = {'size_hint_y': None, 'height' : 150 },
**kwargs)
def compare_pos_to_widget(self, widget, pos):
if self.orientation == 'vertical':
return 'before' if pos[1] >= widget.center_y else 'after'
return 'before' if pos[0] < widget.center_x else 'after'
def handle_drag_release(self, index, drag_widget):
self.add_widget(drag_widget, index)
class DragCard(MDCardSwipe, DraggableObjectBehavior):
text = StringProperty()
def initiate_drag(self):
# during a drag, we remove the widget from the original location
self.parent.remove_widget(self)
class MySpacer(Widget):
"""Widget inserted at the location where the dragged widget may be
dropped to show where it'll be dropped.
"""
pass
class MainApp(MDApp):
def build(self):
self.theme_cls.theme_style = 'Dark'
self.theme_cls.primary_palette = "Cyan"
Window.size = (375, 740)
return Builder.load_file("drag.kv")
def on_start(self):
pass
if __name__ == '__main__':
MainApp().run()
.kv file:
#:kivy 2.0.0
BoxLayout:
orientation: 'vertical'
DraggableBoxLayout:
drag_classes: ['card']
orientation: 'vertical'
DragCard:
text: 'A'
drag_cls: 'card'
DragCard:
text: 'B'
drag_cls: 'card'
DragCard:
text: 'C'
drag_cls: 'card'
DragCard:
text: 'D'
drag_cls: 'card'
DragCard:
text: 'E'
drag_cls: 'card'
DragCard:
text: 'F'
drag_cls: 'card'
DragCard:
text: 'G'
drag_cls: 'card'
DragCard:
text: 'H'
drag_cls: 'card'
<DragCard>:
size_hint_y: None
height: 150
MDCardSwipeLayerBox:
padding: "8dp"
MDIconButton:
icon: "trash-can"
pos_hint: {"center_y": .5}
# on_release: root.delete_template()
MDCardSwipeFrontBox:
Label:
text: root.text
pos_hint: {'center_x': .5, 'center_y': .5}
halign: 'center'
valign: 'center'
font_style: 'H3'
<MySpacer>:
canvas:
Color:
rgba: [.95, .57, .26, 1]
Rectangle:
size: self.size
pos: self.pos
The following code appears to do what you want. The on_touch_down()
method of DragCard
calls the on_touch_down()
method of DraggableObjectBehavior
or MDCardSwipe
depending on the location of the touch
on the DragCard
. The other touch
methods determine which method to call based in the grab_current
property of the touch
(which is set by DraggableObjectBehavior
):
class DragCard(DraggableObjectBehavior, MDCardSwipe):
text = StringProperty()
def initiate_drag(self):
# during a drag, we remove the widget from the original location
self.parent.remove_widget(self)
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.x > self.center_x:
return DraggableObjectBehavior.on_touch_down(self, touch)
else:
return MDCardSwipe.on_touch_down(self, touch)
def on_touch_up(self, touch):
if touch.grab_current == self:
return DraggableObjectBehavior.on_touch_up(self, touch)
else:
return MDCardSwipe.on_touch_up(self, touch)
def on_touch_move(self, touch):
if touch.grab_current == self:
return DraggableObjectBehavior.on_touch_move(self, touch)
else:
return MDCardSwipe.on_touch_move(self, touch)