235 lines
7.8 KiB
Python
235 lines
7.8 KiB
Python
"""
|
|
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')
|
|
|
|
|
|
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
|