Repository: kivy/kivy

Issue:
Scrollview not working properly on Lenovo Yoga 920 W10
<!--
The issue tracker is a tool to address bugs.
Please use the #support Discord channel at https://chat.kivy.org/ or Stack Overflow for
support questions, more information at https://git.io/vM1yQ.

Before opening a new issue, make sure you do the following:
    * check that your issue isn't already filed: https://git.io/vM1iE
    * prepare a short, runnable example that reproduces the issue
    * reproduce the problem with the latest development version of Kivy
    * double-check that the issue is indeed a bug and not a support request
-->

### Versions

* Python: 3.73
* OS: Windows10
* Kivy: 1.11.1
* Kivy installation method: pip installed in the venv

### Description
Scrollview not working properly on Lenovo Yoga 920 W10: using the touchpad to scroll down, scrolls up.  Using a mouse wheel works as expected, using touch works as expected.  The touchpad does not scroll properly.

### Code and Logs

```python
# Spinner and Scrollview tests. Scrollup does not work properly with a trackpad.

from kivy.app import App
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.uix.spinner import Spinner

kv = '''
BoxLayout:
    orientation: 'vertical'
    BoxLayout:  # Line 1
        size_hint_y: 1
        BankSpinner:
            values: ['Bank: ' + str(n) for n in range(1,31)]
            text: 'Bank Select'
    ScrollView:
        size_hint_y:3
        do_scroll_x: False
        do_scroll_y: True
        scroll_type: ['bars', 'content']
        bar_width: 20
        Label:
            size_hint_y: None
            height: self.texture_size[1]
            text_size: self.width, None
            padding: 10, 10
            text: 'really some amazing text\\n' * 100    
    
    BoxLayout:
        size_hint_y: 8
'''


class BankSpinner(Spinner):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.dropdown_cls.max_height = 10 * dp(48)

    def on_touch_up(self, touch):
        print(f'touch up:{touch}, touch.device: {touch.device} {touch.profile} ')
        if 'button' in touch.profile:
            print(f'button: {touch.button}')
        return super().on_touch_up(touch)

    def on_touch_move(self, touch):
        print(f'touch move:{touch}, touch.device: {touch.device}')
        return super().on_touch_move(touch)

    def on_touch_down(self, touch):
        print(f'touch down:{touch}, touch.device: {touch.device}')
        return super().on_touch_down(touch)


class SpinnerTestApp(App):

    def build(self):
        return Builder.load_string(kv)


SpinnerTestApp().run()
```
Additionally under Inclement's direction I instrumented window_sdl2.py
in _mainloop()  scrolling on the touchpad reports mousewheelup if the scroll gesture is up or down.  In the section that starts, 'elif action.startswith('mousewheel'):'  x and y are always reported as zero when using the scrollup or scroll down gesture on the trackpad.

The attached log is the result of opening the app, scrolling down and then scrolling up with the trackpad.
[spinnertest.txt](https://github.com/kivy/kivy/files/3965576/spinnertest.txt)



Discussion:
Same problem
When you say "using the touchpad to scroll down, scrolls up" i assume you mean "putting two fingers on the touchpad, and moving them up" (i think is now the default in windows10 as in osx?) I think we are (ironicaly) still implementing the more traditional way (of before touchscreen became ubiquitous), maybe we could check the os preference and invert the direction depending on it.
@tshirtman I think @ElliotGarbus is talking about a different problem: when using two-finger scrolling, ScrollView and Spinner widgets scroll to the bottom regardless of the direction of your gesture. Changing the os preference for scroll inversion doesn't affect this behavior.

I'm seeing this problem on a Lenovo X1 Carbon (6th gen). When I put two fingers on the touchpad and move them up, the widget scrolls down towards the bottom as expected (I use "inverted" scrolling). But when I put two fingers on the touchpad and move them down, the widget jerkily scrolls quickly up and then right back down to the bottom.

When I scroll with the middle mouse button + TrackPoint (pointing nub), the scroll behavior is normal.

Windows 10 Pro (1909), Python 3.7.1, Kivy 1.11.1
@sbsusw is correct.
The ScrollView will scroll up if the gesture on the touchpad is up or down.  The same behavior exists independent of the scrolling direction in settings.  On the same machine things work correctly with a mouse.  The issue is only on the touchpad.  I do not have this issue on any other W10 machines, or the mac.  Just the Lenovo Yoga 920W10.
ah, that does ring a bell, i don't know anymore if it was with you, but i remember a discussion about this, it's a bit hard to investigate if a specific hardware is needed :/.
@tshirtman If I can do anything to help, Let me know.  As I recall, @inclement thought this was an issue in SDL.
I'd be very willing to help investigate, though I don't have a clear idea of where to start.

Another potentially helpful detail: weirdly enough, if I use the two-finger pinch gesture (pinch-to-zoom), I can scroll up and down perfectly by pinching inward and outward. Using the code example in the original post, it prints out "scrollup" for an inward pinch, and "scrolldown" for an outward pinch. Two-finger scroll only ever prints out "scrollup".

To get the pinch-to-zoom to work I had to disable multi-touch:
`from kivy.config import Config`
`Config.set( 'input', 'mouse', 'mouse,disable_multitouch')`
Has anyone found a solution to this? I'm having the same problem on a DELL Latitude. It has to do with windows gesture support on touch pad.
Do we or any libraries that we depended on (e.g. `SDL2`) use something like [`RegisterRawInputDevices`](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerrawinputdevices) on Windows? I think they might do something there.

When we have a [Windows Precision Touchpad](https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collection), it is registered as 4 input devices.

- Configuration
- Normal mouse input
- Multi-touch input (our code or some libraries may not handle this device properly)
- Firmware update

I just `grep` `chromium` the other day and found that they did use that for the some touch/mouse input on Windows (You can zoom in and out the web page with touchpad without changing web page layout.)
Same problem on different Dell Precision 55x0 devices.

I can do testing or provide remote access to a machine if it helps.

I believe that some programs made with Java will not response to scrolling gestures from the track pad on Windows at all (e.g. JD-GUI) but they will response with the mouse scrolling wheel. As we don't have control over what the implementation do under the hood, it is not clear where to start to investigate this problem.
seems to be the same issue as #6600 , im also having trouble with this and tried to track it down, see my comment in that thread for more details... also to illustrate the issue, this is what happens when i try to scroll up (ONLY scrolling up):
![scrollviewproblem](https://user-images.githubusercontent.com/13342757/90997789-52b9d200-e577-11ea-9cb5-e1146d155389.gif)

Just commenting that 2 finger scroll works here on my macbook. Kivy 1.11

Resolving patch:
diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py
--- a/kivy/uix/scrollview.py
+++ b/kivy/uix/scrollview.py
@@ -148,7 +148,7 @@
 from kivy.clock import Clock
 from kivy.factory import Factory
 from kivy.uix.stencilview import StencilView
-from kivy.metrics import sp, dp
+from kivy.metrics import dp
 from kivy.effects.dampedscroll import DampedScrollEffect
 from kivy.properties import NumericProperty, BooleanProperty, AliasProperty, \
     ObjectProperty, ListProperty, ReferenceListProperty, OptionProperty, \
@@ -607,17 +607,14 @@ def _update_effect_x_bounds(self, *args):
     def _update_effect_y_bounds(self, *args):
         if not self._viewport or not self.effect_y:
             return
-        self.effect_y.min = max(0, self.height - self.viewport_size[1])
-        self.effect_y.max = self.height - self.viewport_size[1]
+        scrollable_height = self.height - self.viewport_size[1]
+        self.effect_y.min = 0 if scrollable_height < 0 else scrollable_height
+        self.effect_y.max = scrollable_height
         self.effect_y.value = self.effect_y.max * self.scroll_y
 
     def _update_effect_bounds(self, *args):
-        if not self._viewport:
-            return
-        if self.effect_x:
-            self._update_effect_x_bounds()
-        if self.effect_y:
-            self._update_effect_y_bounds()
+        self._update_effect_x_bounds()
+        self._update_effect_y_bounds()
 
     def _update_effect_x(self, *args):
         vp = self._viewport
@@ -720,12 +717,11 @@ def on_scroll_start(self, touch, check_children=True):
              'right': self.right - touch.x - self.bar_margin}
 
         ud['in_bar_x'] = (scroll_bar and width_scrollable and
-                             (0 <= d[self.bar_pos_x] <= self.bar_width))
+                          (0 <= d[self.bar_pos_x] <= self.bar_width))
         ud['in_bar_y'] = (scroll_bar and height_scrollable and
-                             (0 <= d[self.bar_pos_y] <= self.bar_width))
+                          (0 <= d[self.bar_pos_y] <= self.bar_width))
 
-        if vp and 'button' in touch.profile and \
-                touch.button.startswith('scroll'):
+        if 'button' in touch.profile and touch.button.startswith('scroll'):
             btn = touch.button
             m = self.scroll_wheel_distance
             e = None
@@ -738,12 +734,20 @@ def on_scroll_start(self, touch, check_children=True):
             ):
                 return False
 
-            if (self.effect_x and self.do_scroll_y and height_scrollable and
-                    btn in ('scrolldown', 'scrollup')):
+            if (
+                self.effect_x
+                and self.do_scroll_y
+                and height_scrollable
+                and btn in ('scrolldown', 'scrollup')
+            ):
                 e = self.effect_x if ud['in_bar_x'] else self.effect_y
 
-            elif (self.effect_y and self.do_scroll_x and width_scrollable and
-                    btn in ('scrollleft', 'scrollright')):
+            elif (
+                self.effect_y
+                and self.do_scroll_x
+                and width_scrollable
+                and btn in ('scrollleft', 'scrollright')
+            ):
                 e = self.effect_y if ud['in_bar_y'] else self.effect_x
 
             if e:
@@ -753,19 +757,19 @@ def on_scroll_start(self, touch, check_children=True):
                     if self.smooth_scroll_end:
                         e.velocity -= m * self.smooth_scroll_end
                     else:
-                        if not self.always_overscroll:
-                            e.value = max(e.value - m, e.min)
-                        else:
+                        if self.always_overscroll:
                             e.value = e.value - m
+                        else:
+                            e.value = max(e.value - m, e.max)
                         e.velocity = 0
                 elif btn in ('scrollup', 'scrollright'):
                     if self.smooth_scroll_end:
                         e.velocity += m * self.smooth_scroll_end
                     else:
-                        if not self.always_overscroll:
-                            e.value = min(e.value + m, e.min)
-                        else:
+                        if self.always_overscroll:
                             e.value = e.value + m
+                        else:
+                            e.value = min(e.value + m, e.min)
                         e.velocity = 0
                 touch.ud[self._get_uid('svavoid')] = True
                 e.trigger_velocity_update()


Test patch: