kivy/kivy — pull request #2371 by kived (2014-07-29 04:01:05+00:00)
Title: fix nested scrollviews

Make `ScrollView` interaction work when nested (#2370). Scrolling should work like it does in most applications; when the inner `ScrollView` has reached its limit, it cancels scrolling and allows the containing `ScrollView` to take over.

To implement this, I added functionality to `EventDispatcher` for handling generic events. This allowed me to add `on_scroll_start`, `on_scroll_move`, and `on_scroll_up` events to `ScrollView` and dispatch them through the child tree without adding those events to every `Widget`. Any events which a given `Widget` does not register will be silently ignored instead of throwing a `KeyError`. I figured this could be a useful addition, but I can easily remove that part and add a local method to `ScrollView` for dispatching these events instead.

My test code:

````python
import kivy
kivy.require('1.8.1')

from kivy.app import App
from kivy.lang import Builder

import sys

if 'horizontal' in sys.argv:
sys.argv.remove('horizontal')
fmt = {
'horizontal': 'vertical',
'x': 'y',
'y': 'x',
'width': 'height',
'height': 'width',
'col': 'row',
'row': 'col',
}
else:
fmt = {
'horizontal': 'horizontal',
'x': 'x',
'y': 'y',
'width': 'width',
'height': 'height',
'col': 'col',
'row': 'row',
}

root = Builder.load_string('''
BoxLayout:
orientation: '{horizontal}'
BoxLayout:
orientation: '{horizontal}'
size_hint_{x}: None
{width}: 0

Widget:
id: quarter
Widget
Widget
BoxLayout:
orientation: '{horizontal}'
Widget:
id: eighth
Widget
ScrollView:
canvas.before:
Color:
rgba: 1, 1, 1, 0.3
Rectangle:
pos: self.pos
size: self.size

GridLayout:
{col}s: 1
size_hint_{y}: None
{height}: self.minimum_{height}
{row}_default_{height}: quarter.{height}
{row}_force_default: True

Widget

BoxLayout:
orientation: '{horizontal}'
canvas.before:
Color:
rgba: 1, 0, 0, 0.5
Rectangle:
pos: self.pos
size: self.size

Widget

ScrollView:
GridLayout:
{col}s: 1
size_hint_{y}: None
size_hint_{x}: 2.0
{height}: self.minimum_{height}
{row}_default_{height}: eighth.{height}
{row}_force_default: True

canvas.before:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size

Widget

Label:
text: 'Hi!'

canvas.before:
Color:
rgba: 0, 0, 1, 0.5
Rectangle:
pos: self.pos
size: self.size

Widget

Widget

Widget
'''.format(**fmt))

class TestApp(App):
def build(self):
return root

if __name__ == '__main__':
TestApp().run()
````