Repository: kivy/kivy

Issue:
Updating ScrollView from outside by setting scroll_x/y directly (#1078 again)
It is practically the same issue as #1078. It was fixed, but with new implementation of ScrollView in 1.7.0 it's back again. Test code in previous issue is showing the bug again. 



Discussion:
I ran into the same problem today..I keep digging, maybe I'll come up with a (temporary) solution. Any hints would be appreciated!

Seems that `event_(x|y).value` is not updated when setting `scroll_(x|y)`
Here's the quick-and-dirty hack I use:

``` python
class HackedScrollView(ScrollView):

    def goto(self, scroll_x, scroll_y):
        ''' Update event value along with scroll_*
        '''
        def _update(e, x):
            if e:
                e.value = (e.max + e.min) * x

        if not (scroll_x is None):
            self.scroll_x = scroll_x
            _update(self.effect_x, scroll_x)

        if not (scroll_y is None):
            self.scroll_y = scroll_y
            _update(self.effect_y, scroll_y)
```


Resolving patch:
diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py
--- a/kivy/uix/scrollview.py
+++ b/kivy/uix/scrollview.py
@@ -134,8 +134,8 @@ class ScrollView(StencilView):
 
     .. versionadded:: 1.8.0
 
-    :data:`scroll_wheel_distance` is a :class:`~kivy.properties.NumericProperty`,
-    defaults to 20 pixels.
+    :data:`scroll_wheel_distance` is a :class:`~kivy.properties.NumericProperty`
+    , defaults to 20 pixels.
     '''
 
     scroll_timeout = NumericProperty(_scroll_timeout)
@@ -322,19 +322,20 @@ def _get_hbar(self):
     child in the scrollview.
     '''
 
-    scroll_type = OptionProperty('content', options=('content', 'bars', 'both'))
+    scroll_type = OptionProperty(['content'], options=(['content'], ['bars'],
+                                    ['bars', 'content'], ['content', 'bars']))
     '''Sets the type of scrolling to use for the content of the scrollview.
-    Available options are: content, bars, both.
+    Available options are: ['content'], ['bars'], ['bars', 'content'].
 
     .. versionadded:: 1.8.0
 
     :data:`scroll_type` is a :class:`~kivy.properties.OptionProperty`, defaults
-    to content.
+    to ['content'].
     '''
 
     def on_scroll_type(self, instance, value):
         self.bar_width = max('9dp', self.bar_width)\
-            if value[0] == 'b' else self.bar_width
+            if 'bars' in value else self.bar_width
 
     # private, for internal use only
 
@@ -450,6 +451,16 @@ def on_touch_down(self, touch):
         # handle mouse scrolling, only if the viewport size is bigger than the
         # scrollview size, and if the user allowed to do it
         vp = self._viewport
+        scroll_type = self.scroll_type
+        ud = touch.ud
+        scroll_bar_content = 'bars' in scroll_type
+
+        ud['in_bar_x'] = ud['in_bar_y'] = False
+        if (scroll_bar_content and touch.y < self.bar_width):
+            ud['in_bar_x'] = True
+        if scroll_bar_content and touch.x > self.right - self.bar_width:
+            ud['in_bar_y'] = True
+
         if vp and 'button' in touch.profile and \
             touch.button.startswith('scroll'):
             btn = touch.button
@@ -458,11 +469,11 @@ def on_touch_down(self, touch):
 
             if (self.effect_x and self.do_scroll_y and vp.height > self.height
                     and btn in ('scrolldown', 'scrollup')):
-                e = self.effect_y
+                e = self.effect_x if ud['in_bar_x'] else self.effect_y
 
             elif (self.effect_y and self.do_scroll_x and vp.width > self.width
                     and btn in ('scrollleft', 'scrollright')):
-                e = self.effect_x
+                e = self.effect_y if ud['in_bar_y'] else self.effect_x
 
             if e:
                 if btn in ('scrolldown', 'scrollleft'):
@@ -475,35 +486,29 @@ def on_touch_down(self, touch):
                 e.trigger_velocity_update()
                 return True
 
-        scroll_type = self.scroll_type
         # no mouse scrolling, so the user is going to drag the scrollview with
         # this touch.
         self._touch = touch
         uid = self._get_uid()
         touch.grab(self)
-        ud = touch.ud
+
         ud[uid] = {
             'mode': 'unknown',
             'dx': 0,
             'dy': 0,
             'user_stopped': False,
             'time': touch.time_start}
+
         if self.do_scroll_x and self.effect_x:
-            if scroll_type[0] == 'b' and touch.y < self.bar_width:
-                ud['in_bar_x'] = True
-            else:
-                if scroll_type != 'bars':
+            if not ud['in_bar_x'] and scroll_type != ['bars']:
                     self.effect_x.start(touch.x)
         if self.do_scroll_y and self.effect_y:
-            if scroll_type[0] == 'b' and touch.x > self.right - self.bar_width:
-                ud['in_bar_y'] = True
-            else:
-                if scroll_type != 'bars':
+            if not ud['in_bar_y'] and scroll_type != ['bars']:
                     self.effect_y.start(touch.y)
 
         if (ud.get('in_bar_x', False) or ud.get('in_bar_y', False)):
             return
-        if scroll_type == 'bars':
+        if scroll_type == ['bars']:
             self._change_touch_mode()
         else:
             Clock.schedule_once(self._change_touch_mode,
@@ -532,7 +537,7 @@ def on_touch_move(self, touch):
                     self.scroll_x = min(max(self.scroll_x + dx, 0.), 1.)
                     self._trigger_update_from_scroll()
                 else:
-                    if self.scroll_type != 'bars':
+                    if self.scroll_type != ['bars']:
                         self.effect_x.update(touch.x)
             if self.do_scroll_y and self.effect_y:
                 height = self.height
@@ -541,7 +546,7 @@ def on_touch_move(self, touch):
                     self.scroll_y = min(max(self.scroll_y + dy, 0.), 1.)
                     self._trigger_update_from_scroll()
                 else:
-                    if self.scroll_type != 'bars':
+                    if self.scroll_type != ['bars']:
                         self.effect_y.update(touch.y)
 
         if mode == 'unknown':
@@ -578,10 +583,10 @@ def on_touch_up(self, touch):
             ud = touch.ud[uid]
             if self.do_scroll_x and self.effect_x:
                 if not touch.ud.get('in_bar_x', False) and\
-                    self.scroll_type != 'bars':
+                    self.scroll_type != ['bars']:
                     self.effect_x.stop(touch.x)
             if self.do_scroll_y and self.effect_y and\
-                    self.scroll_type != 'bars':
+                    self.scroll_type != ['bars']:
                 if not touch.ud.get('in_bar_y', False):
                     self.effect_y.stop(touch.y)
             if ud['mode'] == 'unknown':
@@ -591,6 +596,8 @@ def on_touch_up(self, touch):
                 if not ud['user_stopped']:
                     super(ScrollView, self).on_touch_down(touch)
                 Clock.schedule_once(partial(self._do_touch_up, touch), .2)
+            Clock.unschedule(self._update_effect_bounds)
+            Clock.schedule_once(self._update_effect_bounds)
         else:
             if self._touch is not touch and self.uid not in touch.ud:
                 super(ScrollView, self).on_touch_up(touch)
@@ -641,7 +648,7 @@ def update_from_scroll(self, *largs):
     def _start_decrease_alpha(self, *l):
         self.bar_alpha = 1.
         # show bars if scroll_type != content
-        bar_alpha = .2 if self.scroll_type[0] != 'c' else 0
+        bar_alpha = .2 if self.scroll_type != ['content'] else 0
         Animation(bar_alpha=bar_alpha, d=.5, t='out_quart').start(self)
 
     #
@@ -731,7 +738,8 @@ def build(self):
                 btn = Button(text=str(i), size_hint=(None, None),
                              size=(200, 100))
                 layout2.add_widget(btn)
-            scrollview2 = ScrollView(scroll_type='bars', scroll_wheel_distance=100)
+            scrollview2 = ScrollView(scroll_type=['content', 'bars'],
+                                     scroll_wheel_distance=100)
             scrollview2.add_widget(layout2)
 
             root = GridLayout(cols=2)


Test patch: