mirror of https://github.com/kivy/kivy.git
Merge branch 'better_scroll' of https://github.com/milanboers/kivy into milanboers-better_scroll
This commit is contained in:
commit
ffbc764d97
|
@ -138,6 +138,16 @@ Available configuration tokens
|
|||
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
`scroll_stoptime`: int
|
||||
Default value of :data:`~kivy.uix.scrollview.Scrollview.scroll_stoptime`
|
||||
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
`scroll_moves`: int
|
||||
Default value of :data:`~kivy.uix.scrollview.Scrollview.scroll_moves`
|
||||
property in :class:`~kivy.uix.scrollview.Scrollview` widget.
|
||||
Check the widget documentation for more information.
|
||||
|
||||
:modules:
|
||||
|
||||
You can activate modules with this syntax::
|
||||
|
@ -388,9 +398,11 @@ if not environ.get('KIVY_DOC_INCLUDE'):
|
|||
|
||||
elif version == 3:
|
||||
# add token for scrollview
|
||||
Config.setdefault('widgets', 'scroll_timeout', '250')
|
||||
Config.setdefault('widgets', 'scroll_timeout', '55')
|
||||
Config.setdefault('widgets', 'scroll_distance', '20')
|
||||
Config.setdefault('widgets', 'scroll_friction', '1.')
|
||||
Config.setdefault('widgets', 'scroll_stoptime', '300')
|
||||
Config.setdefault('widgets', 'scroll_moves', '5')
|
||||
|
||||
# remove old list_* token
|
||||
Config.remove_option('widgets', 'list_friction')
|
||||
|
|
|
@ -82,23 +82,47 @@ If you want to reduce the default timeout, you can set::
|
|||
|
||||
__all__ = ('ScrollView', )
|
||||
|
||||
from copy import copy
|
||||
from functools import partial
|
||||
from kivy.animation import Animation
|
||||
from kivy.config import Config
|
||||
from kivy.clock import Clock
|
||||
from kivy.uix.stencilview import StencilView
|
||||
from kivy.properties import NumericProperty, BooleanProperty, AliasProperty, \
|
||||
ObjectProperty, ListProperty
|
||||
ObjectProperty, ListProperty
|
||||
|
||||
|
||||
# When we are generating documentation, Config doesn't exist
|
||||
_scroll_timeout = _scroll_distance = _scroll_friction = 0
|
||||
_scroll_timeout = _scroll_stoptime = _scroll_distance = _scroll_friction = 0
|
||||
if Config:
|
||||
_scroll_timeout = Config.getint('widgets', 'scroll_timeout')
|
||||
_scroll_stoptime = Config.getint('widgets', 'scroll_stoptime')
|
||||
_scroll_distance = Config.getint('widgets', 'scroll_distance')
|
||||
_scroll_moves = Config.getint('widgets', 'scroll_moves')
|
||||
_scroll_friction = Config.getfloat('widgets', 'scroll_friction')
|
||||
|
||||
|
||||
class FixedList(list):
|
||||
'''A list. In addition, you can specify the maximum length.
|
||||
This will save memory.
|
||||
'''
|
||||
def __init__(self, maxlength=0, *args, **kwargs):
|
||||
super(FixedList, self).__init__(*args, **kwargs)
|
||||
self.maxlength = maxlength
|
||||
|
||||
def append(self, x):
|
||||
super(FixedList, self).append(x)
|
||||
self._cut()
|
||||
|
||||
def extend(self, L):
|
||||
super(FixedList, self).append(L)
|
||||
self._cut()
|
||||
|
||||
def _cut(self):
|
||||
while len(self) > self.maxlength:
|
||||
self.pop(0)
|
||||
|
||||
|
||||
class ScrollView(StencilView):
|
||||
'''ScrollView class. See module documentation for more information.
|
||||
'''
|
||||
|
@ -206,10 +230,12 @@ class ScrollView(StencilView):
|
|||
return
|
||||
uid = self._get_uid()
|
||||
touch = self._touch
|
||||
mode = touch.ud[uid]['mode']
|
||||
if mode == 'unknown':
|
||||
touch.ungrab(self)
|
||||
self._touch = None
|
||||
ud = touch.ud[uid]
|
||||
if ud['mode'] == 'unknown' and \
|
||||
not ud['user_stopped'] and \
|
||||
touch.dx + touch.dy == 0:
|
||||
#touch.ungrab(self)
|
||||
#self._touch = None
|
||||
# correctly calculate the position of the touch inside the
|
||||
# scrollview
|
||||
touch.push()
|
||||
|
@ -231,31 +257,40 @@ class ScrollView(StencilView):
|
|||
super(ScrollView, self).on_touch_up(touch)
|
||||
touch.grab_current = None
|
||||
|
||||
def _do_animation(self, touch):
|
||||
def _do_animation(self, touch, *largs):
|
||||
uid = self._get_uid()
|
||||
ud = touch.ud[uid]
|
||||
dt = touch.time_end - ud['time']
|
||||
if dt > self.scroll_timeout / 1000.:
|
||||
self._tdx = self._tdy = self._ts = 0
|
||||
avgdx = sum([move.dx for move in ud['moves']]) / len(ud['moves'])
|
||||
avgdy = sum([move.dy for move in ud['moves']]) / len(ud['moves'])
|
||||
if ud['user_stopped'] and \
|
||||
abs(avgdy) < self.scroll_distance and \
|
||||
abs(avgdx) < self.scroll_distance:
|
||||
return
|
||||
if ud['same'] > self.scroll_stoptime / 1000.:
|
||||
return
|
||||
dt = ud['dt']
|
||||
if dt == 0:
|
||||
self._tdx = self._tdy = self._ts = 0
|
||||
return
|
||||
dx = touch.dx
|
||||
dy = touch.dy
|
||||
dx = avgdx
|
||||
dy = avgdy
|
||||
self._sx = ud['sx']
|
||||
self._sy = ud['sy']
|
||||
self._tdx = dx = dx / dt
|
||||
self._tdy = dy = dy / dt
|
||||
if abs(dx) < 10 and abs(dy) < 10:
|
||||
return
|
||||
self._ts = self._tsn = touch.time_update
|
||||
|
||||
Clock.unschedule(self._update_animation)
|
||||
Clock.schedule_interval(self._update_animation, 0)
|
||||
|
||||
def _update_animation(self, dt):
|
||||
if self._touch is not None or self._ts == 0:
|
||||
touch = self._touch
|
||||
uid = self._get_uid()
|
||||
ud = touch.ud[uid]
|
||||
# scrolling stopped by user input
|
||||
ud['user_stopped'] = True
|
||||
return False
|
||||
self._tsn += dt
|
||||
global_dt = self._tsn - self._ts
|
||||
|
@ -268,6 +303,7 @@ class ScrollView(StencilView):
|
|||
(self.do_scroll_y and not self.do_scroll_x and test_dy) or\
|
||||
(self.do_scroll_x and self.do_scroll_y and test_dx and test_dy):
|
||||
self._ts = 0
|
||||
# scrolling stopped by friction
|
||||
return False
|
||||
dx *= dt
|
||||
dy *= dt
|
||||
|
@ -284,8 +320,18 @@ class ScrollView(StencilView):
|
|||
self.scroll_y -= sy
|
||||
self._scroll_y_mouse = self.scroll_y
|
||||
if ssx == self.scroll_x and ssy == self.scroll_y:
|
||||
# scrolling stopped by end of box
|
||||
return False
|
||||
|
||||
def _update_delta(self, dt):
|
||||
touch = self._touch
|
||||
uid = self._get_uid()
|
||||
ud = touch.ud[uid]
|
||||
if touch.dx + touch.dy != 0:
|
||||
ud['same'] += dt
|
||||
else:
|
||||
ud['same'] = 0
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
if not self.collide_point(*touch.pos):
|
||||
touch.ud[self._get_uid('svavoid')] = True
|
||||
|
@ -299,12 +345,10 @@ class ScrollView(StencilView):
|
|||
vp = self._viewport
|
||||
if vp.height > self.height:
|
||||
# let's say we want to move over 40 pixels each scroll
|
||||
d = (vp.height - self.height)
|
||||
d = self.scroll_distance / float(d)
|
||||
if touch.button == 'scrollup':
|
||||
syd = self._scroll_y_mouse - d
|
||||
syd = self._scroll_y_mouse
|
||||
elif touch.button == 'scrolldown':
|
||||
syd = self._scroll_y_mouse + d
|
||||
syd = self._scroll_y_mouse
|
||||
self._scroll_y_mouse = scroll_y = min(max(syd, 0), 1)
|
||||
Animation.stop_all(self, 'scroll_y')
|
||||
Animation(scroll_y=scroll_y, d=.3, t='out_quart').start(self)
|
||||
|
@ -319,9 +363,14 @@ class ScrollView(StencilView):
|
|||
'sx': self.scroll_x,
|
||||
'sy': self.scroll_y,
|
||||
'dt': None,
|
||||
'time': touch.time_start}
|
||||
'time': touch.time_start,
|
||||
'user_stopped': False,
|
||||
'same': 0,
|
||||
'moves': FixedList(self.scroll_moves)}
|
||||
|
||||
Clock.schedule_interval(self._update_delta, 0)
|
||||
Clock.schedule_once(self._change_touch_mode,
|
||||
self.scroll_timeout / 1000.)
|
||||
self.scroll_timeout / 1000.)
|
||||
return True
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
|
@ -335,23 +384,22 @@ class ScrollView(StencilView):
|
|||
uid = self._get_uid()
|
||||
ud = touch.ud[uid]
|
||||
mode = ud['mode']
|
||||
ud['moves'].append(copy(touch))
|
||||
|
||||
# seperate the distance to both X and Y axis.
|
||||
# if a distance is reach, but on the inverse axis, stop scroll mode !
|
||||
if mode == 'unknown':
|
||||
distance = abs(touch.ox - touch.x)
|
||||
if distance > self.scroll_distance:
|
||||
if not self.do_scroll_x:
|
||||
self._change_touch_mode()
|
||||
return
|
||||
mode = 'scroll'
|
||||
if not self.do_scroll_x:
|
||||
self._change_touch_mode()
|
||||
return
|
||||
mode = 'scroll'
|
||||
|
||||
distance = abs(touch.oy - touch.y)
|
||||
if distance > self.scroll_distance:
|
||||
if not self.do_scroll_y:
|
||||
self._change_touch_mode()
|
||||
return
|
||||
mode = 'scroll'
|
||||
if not self.do_scroll_y:
|
||||
self._change_touch_mode()
|
||||
return
|
||||
mode = 'scroll'
|
||||
|
||||
if mode == 'scroll':
|
||||
ud['mode'] = mode
|
||||
|
@ -374,7 +422,7 @@ class ScrollView(StencilView):
|
|||
# never ungrabed, cause their on_touch_up will be never called.
|
||||
# base.py: the me.grab_list[:] => it's a copy, and we are already
|
||||
# iterate on it.
|
||||
|
||||
Clock.unschedule(self._update_delta)
|
||||
if self._get_uid('svavoid') in touch.ud:
|
||||
return
|
||||
|
||||
|
@ -385,10 +433,12 @@ class ScrollView(StencilView):
|
|||
touch.ungrab(self)
|
||||
self._touch = None
|
||||
uid = self._get_uid()
|
||||
mode = touch.ud[uid]['mode']
|
||||
if mode == 'unknown':
|
||||
ud = touch.ud[uid]
|
||||
if ud['mode'] == 'unknown':
|
||||
# we must do the click at least..
|
||||
super(ScrollView, self).on_touch_down(touch)
|
||||
# only send the click if it was not a click to stop autoscrolling
|
||||
if not ud['user_stopped']:
|
||||
super(ScrollView, self).on_touch_down(touch)
|
||||
Clock.schedule_once(partial(self._do_touch_up, touch), .1)
|
||||
elif self.auto_scroll:
|
||||
self._do_animation(touch)
|
||||
|
@ -433,10 +483,35 @@ class ScrollView(StencilView):
|
|||
default to 1, according to the default value in user configuration.
|
||||
'''
|
||||
|
||||
scroll_moves = NumericProperty(_scroll_moves)
|
||||
'''The speed of automatic scrolling is based on previous touch moves. This
|
||||
is to prevent accidental slowing down by the user at the end of the swipe
|
||||
to slow down the automatic scrolling.
|
||||
The moves property specifies the amount of previous scrollmoves that
|
||||
should be taken into consideration when calculating the automatic scrolling
|
||||
speed.
|
||||
|
||||
:data:`scroll_moves` is a :class:`~kivy.properties.NumericProperty`,
|
||||
default to 5.
|
||||
'''
|
||||
|
||||
scroll_stoptime = NumericProperty(_scroll_stoptime)
|
||||
'''Time after which user input not moving will disable autoscroll for that
|
||||
move. If the user has not moved within the stoptime, autoscroll will not
|
||||
start.
|
||||
This is to prevent autoscroll to trigger while the user has slowed down
|
||||
on purpose to prevent this.
|
||||
|
||||
:data:`scroll_stoptime` is a :class:`~kivy.properties.NumericProperty`,
|
||||
default to 300 (milliseconds)
|
||||
'''
|
||||
|
||||
scroll_distance = NumericProperty(_scroll_distance)
|
||||
'''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
|
||||
soon as the distance has been traveled, the :class:`ScrollView` will start
|
||||
to scroll, 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.
|
||||
|
||||
:data:`scroll_distance` is a :class:`~kivy.properties.NumericProperty`,
|
||||
default to 20 (pixels), according to the default value in user
|
||||
|
@ -445,11 +520,11 @@ class ScrollView(StencilView):
|
|||
|
||||
scroll_timeout = NumericProperty(_scroll_timeout)
|
||||
'''Timeout allowed to trigger the :data:`scroll_distance`, in milliseconds.
|
||||
If the timeout is reached, the scrolling will be disabled, and the touch
|
||||
event will go to the children.
|
||||
If the user has not moved :data:`scroll_distance` within the timeout,
|
||||
the scrolling will be disabled, and the touch event will go to the children.
|
||||
|
||||
:data:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty`,
|
||||
default to 250 (milliseconds), according to the default value in user
|
||||
default to 55 (milliseconds), according to the default value in user
|
||||
configuration.
|
||||
'''
|
||||
|
||||
|
@ -601,4 +676,3 @@ class ScrollView(StencilView):
|
|||
if value:
|
||||
value.bind(size=self._set_viewport_size)
|
||||
self._viewport_size = value.size
|
||||
|
||||
|
|
Loading…
Reference in New Issue