Version

Quick search

Table Of Contents

Source code for kivy.uix.behaviors.drag

"""
Drag Behavior
=============

The :class:`~kivy.uix.behaviors.drag.DragBehavior`
`mixin <https://en.wikipedia.org/wiki/Mixin>`_ class provides Drag behavior.
When combined with a widget, dragging in the rectangle defined by the
:attr:`~kivy.uix.behaviors.drag.DragBehavior.drag_rectangle` will drag the
widget.

Example
-------

The following example creates a draggable label::

    from kivy.uix.label import Label
    from kivy.app import App
    from kivy.uix.behaviors import DragBehavior
    from kivy.lang import Builder

    # You could also put the following in your kv file...
    kv = '''
    <DragLabel>:
        # Define the properties for the DragLabel
        drag_rectangle: self.x, self.y, self.width, self.height
        drag_timeout: 10000000
        drag_distance: 0

    FloatLayout:
        # Define the root widget
        DragLabel:
            size_hint: 0.25, 0.2
            text: 'Drag me'
    '''


    class DragLabel(DragBehavior, Label):
        pass


    class TestApp(App):
        def build(self):
            return Builder.load_string(kv)

    TestApp().run()

"""

__all__ = ('DragBehavior', )

from kivy.clock import Clock
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.config import Config
from kivy.metrics import sp
from functools import partial

# When we are generating documentation, Config doesn't exist
_scroll_timeout = _scroll_distance = 0
if Config:
    _scroll_timeout = Config.getint('widgets', 'scroll_timeout')
    _scroll_distance = Config.getint('widgets', 'scroll_distance')


[docs]class DragBehavior(object): ''' The DragBehavior `mixin <https://en.wikipedia.org/wiki/Mixin>`_ provides Drag behavior. When combined with a widget, dragging in the rectangle defined by :attr:`drag_rectangle` will drag the widget. Please see the :mod:`drag behaviors module <kivy.uix.behaviors.drag>` documentation for more information. .. versionadded:: 1.8.0 ''' drag_distance = NumericProperty(_scroll_distance) '''Distance to move before dragging the :class:`DragBehavior`, in pixels. As soon as the distance has been traveled, the :class:`DragBehavior` will start to drag, and no touch event will be dispatched to the children. It is advisable that you base this value on the dpi of your target device's screen. :attr:`drag_distance` is a :class:`~kivy.properties.NumericProperty` and defaults to the `scroll_distance` as defined in the user :class:`~kivy.config.Config` (20 pixels by default). ''' drag_timeout = NumericProperty(_scroll_timeout) '''Timeout allowed to trigger the :attr:`drag_distance`, in milliseconds. If the user has not moved :attr:`drag_distance` within the timeout, dragging will be disabled, and the touch event will be dispatched to the children. :attr:`drag_timeout` is a :class:`~kivy.properties.NumericProperty` and defaults to the `scroll_timeout` as defined in the user :class:`~kivy.config.Config` (55 milliseconds by default). ''' drag_rect_x = NumericProperty(0) '''X position of the axis aligned bounding rectangle where dragging is allowed (in window coordinates). :attr:`drag_rect_x` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' drag_rect_y = NumericProperty(0) '''Y position of the axis aligned bounding rectangle where dragging is allowed (in window coordinates). :attr:`drag_rect_Y` is a :class:`~kivy.properties.NumericProperty` and defaults to 0. ''' drag_rect_width = NumericProperty(100) '''Width of the axis aligned bounding rectangle where dragging is allowed. :attr:`drag_rect_width` is a :class:`~kivy.properties.NumericProperty` and defaults to 100. ''' drag_rect_height = NumericProperty(100) '''Height of the axis aligned bounding rectangle where dragging is allowed. :attr:`drag_rect_height` is a :class:`~kivy.properties.NumericProperty` and defaults to 100. ''' drag_rectangle = ReferenceListProperty(drag_rect_x, drag_rect_y, drag_rect_width, drag_rect_height) '''Position and size of the axis aligned bounding rectangle where dragging is allowed. :attr:`drag_rectangle` is a :class:`~kivy.properties.ReferenceListProperty` of (:attr:`drag_rect_x`, :attr:`drag_rect_y`, :attr:`drag_rect_width`, :attr:`drag_rect_height`) properties. ''' def __init__(self, **kwargs): self._drag_touch = None super(DragBehavior, self).__init__(**kwargs) def _get_uid(self, prefix='sv'): return '{0}.{1}'.format(prefix, self.uid) def on_touch_down(self, touch): xx, yy, w, h = self.drag_rectangle x, y = touch.pos if not self.collide_point(x, y): touch.ud[self._get_uid('svavoid')] = True return super(DragBehavior, self).on_touch_down(touch) if self._drag_touch or ('button' in touch.profile and touch.button.startswith('scroll')) or\ not ((xx < x <= xx + w) and (yy < y <= yy + h)): return super(DragBehavior, self).on_touch_down(touch) # no mouse scrolling, so the user is going to drag with this touch. self._drag_touch = touch uid = self._get_uid() touch.grab(self) touch.ud[uid] = { 'mode': 'unknown', 'dx': 0, 'dy': 0} Clock.schedule_once(self._change_touch_mode, self.drag_timeout / 1000.) return True def on_touch_move(self, touch): if self._get_uid('svavoid') in touch.ud or\ self._drag_touch is not touch: return super(DragBehavior, self).on_touch_move(touch) or\ self._get_uid() in touch.ud if touch.grab_current is not self: return True uid = self._get_uid() ud = touch.ud[uid] mode = ud['mode'] if mode == 'unknown': ud['dx'] += abs(touch.dx) ud['dy'] += abs(touch.dy) if ud['dx'] > sp(self.drag_distance): mode = 'drag' if ud['dy'] > sp(self.drag_distance): mode = 'drag' ud['mode'] = mode if mode == 'drag': self.x += touch.dx self.y += touch.dy return True def on_touch_up(self, touch): if self._get_uid('svavoid') in touch.ud: return super(DragBehavior, self).on_touch_up(touch) if self._drag_touch and self in [x() for x in touch.grab_list]: touch.ungrab(self) self._drag_touch = None ud = touch.ud[self._get_uid()] if ud['mode'] == 'unknown': super(DragBehavior, self).on_touch_down(touch) Clock.schedule_once(partial(self._do_touch_up, touch), .1) else: if self._drag_touch is not touch: super(DragBehavior, self).on_touch_up(touch) return self._get_uid() in touch.ud def _do_touch_up(self, touch, *largs): super(DragBehavior, self).on_touch_up(touch) # don't forget about grab event! for x in touch.grab_list[:]: touch.grab_list.remove(x) x = x() if not x: continue touch.grab_current = x super(DragBehavior, self).on_touch_up(touch) touch.grab_current = None def _change_touch_mode(self, *largs): if not self._drag_touch: return uid = self._get_uid() touch = self._drag_touch ud = touch.ud[uid] if ud['mode'] != 'unknown': return touch.ungrab(self) self._drag_touch = None touch.push() touch.apply_transform_2d(self.parent.to_widget) super(DragBehavior, self).on_touch_down(touch) touch.pop() return