Quick search

Table Of Contents

Source code for kivy.uix.behaviors

'''
Behaviors
=========

.. versionadded:: 1.8.0

This module implements behaviors that can be mixed with existing base widgets.
For example, if you want to add a "button" capability to an `Image`, you could
do::


    class IconButton(ButtonBehavior, Image):
        pass

.. note::

    The behavior class must always be _before_ the widget class. If you don't
    specify the inheritance in this order, the behavior will not work.

'''

__all__ = ('ButtonBehavior', 'ToggleButtonBehavior', 'DragBehavior')

from kivy.clock import Clock
from kivy.properties import OptionProperty, ObjectProperty,\
    NumericProperty, ReferenceListProperty
from weakref import ref
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 ButtonBehavior(object): '''Button behavior. :Events: `on_press` Fired when the button is pressed. `on_release` Fired when the button is released (i.e. the touch/click that pressed the button goes away). ''' state = OptionProperty('normal', options=('normal', 'down')) '''State of the button, must be one of 'normal' or 'down'. The state is 'down' only when the button is currently touched/clicked, otherwise 'normal'. :attr:`state` is an :class:`~kivy.properties.OptionProperty`. ''' last_touch = ObjectProperty(None) '''Contains the last relevant touch received by the Button. This can be used in `on_press` or `on_release` in order to know which touch dispatched the event. .. versionadded:: 1.8.0 :attr:`last_touch` is a :class:`~kivy.properties.ObjectProperty`, defaults to None. ''' def __init__(self, **kwargs): self.register_event_type('on_press') self.register_event_type('on_release') super(ButtonBehavior, self).__init__(**kwargs) def _do_press(self): self.state = 'down' def _do_release(self): self.state = 'normal' def on_touch_down(self, touch): if super(ButtonBehavior, self).on_touch_down(touch): return True if touch.is_mouse_scrolling: return False if not self.collide_point(touch.x, touch.y): return False if self in touch.ud: return False touch.grab(self) touch.ud[self] = True self.last_touch = touch self._do_press() self.dispatch('on_press') return True def on_touch_move(self, touch): if touch.grab_current is self: return True if super(ButtonBehavior, self).on_touch_move(touch): return True return self in touch.ud def on_touch_up(self, touch): if touch.grab_current is not self: return super(ButtonBehavior, self).on_touch_up(touch) assert(self in touch.ud) touch.ungrab(self) self.last_touch = touch self._do_release() self.dispatch('on_release') return True def on_press(self): pass def on_release(self): pass
[docs] def trigger_action(self, duration=0.1): '''Trigger whatever action(s) have been bound to the button by calling both the on_press and on_release callbacks. This simulates a quick button press without using any touch events. Duration is the length of the press in seconds. Pass 0 if you want the action to happen instantly. .. versionadded:: 1.8.0 ''' self._do_press() self.dispatch('on_press') def trigger_release(dt): self._do_release() self.dispatch('on_release') if not duration: trigger_release(0) else: Clock.schedule_once(trigger_release, duration)
[docs]class ToggleButtonBehavior(ButtonBehavior): '''ToggleButton behavior, see ToggleButton module documentation for more information. .. versionadded:: 1.8.0 ''' __groups = {} group = ObjectProperty(None, allownone=True) '''Group of the button. If None, no group will be used (button is independent). If specified, :attr:`group` must be a hashable object, like a string. Only one button in a group can be in 'down' state. :attr:`group` is a :class:`~kivy.properties.ObjectProperty` ''' def __init__(self, **kwargs): self._previous_group = None super(ToggleButtonBehavior, self).__init__(**kwargs) def on_group(self, *largs): groups = ToggleButtonBehavior.__groups if self._previous_group: group = groups[self._previous_group] for item in group[:]: if item() is self: group.remove(item) break group = self._previous_group = self.group if group not in groups: groups[group] = [] r = ref(self, ToggleButtonBehavior._clear_groups) groups[group].append(r) def _release_group(self, current): if self.group is None: return group = self.__groups[self.group] for item in group[:]: widget = item() if widget is None: group.remove(item) if widget is current: continue widget.state = 'normal' def _do_press(self): self._release_group(self) self.state = 'normal' if self.state == 'down' else 'down' def _do_release(self): pass @staticmethod def _clear_groups(wk): # auto flush the element when the weak reference have been deleted groups = ToggleButtonBehavior.__groups for group in list(groups.values()): if wk in group: group.remove(wk) break @staticmethod
[docs] def get_widgets(groupname): '''Return the widgets contained in a specific group. If the group doesn't exist, an empty list will be returned. .. important:: Always release the result of this method! In doubt, do:: l = ToggleButtonBehavior.get_widgets('mygroup') # do your job del l .. warning:: It's possible that some widgets that you have previously deleted are still in the list. Garbage collector might need more elements before flushing it. The return of this method is informative, you've been warned! ''' groups = ToggleButtonBehavior.__groups if groupname not in groups: return [] return [x() for x in groups[groupname] if x()][:]
[docs]class DragBehavior(object): '''Drag behavior. When combined with a widget, dragging in the rectangle defined by :attr:`drag_rectangle` will drag the widget. For example, to make a popup which is draggable by its title do:: from kivy.uix.behaviors import DragBehavior from kivy.uix.popup import Popup class DragPopup(DragBehavior, Popup): pass And in .kv do:: <DragPopup>: drag_rectangle: self.x, self.y+self._container.height, self.width,\ self.height - self._container.height drag_timeout: 10000000 drag_distance: 0 .. 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 go to 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`, defaults to 20 (pixels), according to the default value of scroll_distance in user configuration. ''' 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 go to the children. :attr:`drag_timeout` is a :class:`~kivy.properties.NumericProperty`, defaults to 55 (milliseconds), according to the default value of scroll_timeout in user configuration. ''' 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`, 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`, 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`, 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`, 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 super(DragBehavior, self).on_touch_down(touch) return