Repository: kivy/kivy

Issue:
TextInput does not lose focus inside ScrollView
Hi. I found the issue with behavior of TextInput focus inside ScrollView. If i touch other widget - TextInput does not lose focus despite the property unfocus_on_touch: True.

Sample program:

```
<ScrollView>:
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: 'lol'
        TextInput:
            text: 'Hello World'
```

When I click on the Label, Textinput does not lose focus. But If I remove ScrollView from code - it works as intended.



Discussion:
I can reproduce it with master.

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

kv = """
ScrollView:
    BoxLayout:
        orientation: 'vertical'
        Label:
            text: 'lol'
        TextInput:
            text: 'Hello World'
"""


class TestApp(App):
    def build(self):
        return Builder.load_string(kv)

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

Is it really an issue? I mean, if you add to `on_text` `scroll_to` and go for focused TextInput, you'll get a nice behavior i.e. if you have:

<pre>
----------------------
| key  |  focused    |
----------------------
| key  |             |
----------------------
| key  |             | * 1000 next items
----------------------</pre>

and you need to check some value if you're right, then with typing you would bring that TextInput from a pile of unnecessary TextInputs to you just with typing. Rather intuitive than problematic. :yum: But I'm not sure how does it work with phones(keyboard may disappear), I'd need to check later.

@KeyWeeUsr: I don't understand what are you trying to say. Do you mean that there is a workaround? I actually found one that suits my needs - I created my own TextInput subclass with on_touch_up():

```
class TextInput(kivy.uix.textinput.TextInput):
    def on_touch_up(self, touch):
        if not self.collide_point(*touch.pos):
            self.focus = False
        return super(TextInput, self).on_touch_up(touch)

Factory.unregister('TextInput')
Factory.register('TextInput', cls=TextInput)
```

@Fak3 Not really, I'm just telling that this behavior might be a desired one in a long run, because if you have a lot of items in a Scroll/Recycle/ListView(a lot of TextInputs in this case), this may be really good thing to have.

As I said before, let's say that you have typed something into TextInput and you want to check something (other value/widget/anything) in your View. It's rather easy to get back with typing if you have the keyboard available - as I said, don't know if the keyboard doesn't close e.g. on scrolling. As you see, colliding workaround is pretty intuitive too, it's easy to fix it. However... I'm not sure if it's so easy the other way around.

Let's say that your workaround would be the default behavior... how will you get back the one that is present right now i.e. not unfocusing in View? I think it'd be more complicated (try introduce it after your workaround).

Also, the workaround will unfocus _always_, because `on_touch_up` means basically every click event. Try to type anything after using this workaround.

And there are even events for `<escape> & <enter>` unfocus.

I agree about a touch drag (scrolling) not unfocusing the currently focused widget, but a plain touch should trigger a defocus.
The ScrollView does not account for this and just ignores all touches:
https://github.com/kivy/kivy/blob/222ca2b0b3ecccc5ccb9f1dd49be277f3e1a9d7f/kivy/uix/scrollview.py#L683

<pre>class TextInput(TextInput):
    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            self.focus = False
        return super(TextInput, self).on_touch_down(touch)
Factory.unregister('TextInput')
Factory.register('TextInput', cls=TextInput)</pre>

Here's a working one.

@KeyWeeUsr My workaround is not comlpete - it should actually take into account unfocus_on_touch property. This can be easily fixed. Then it would behave _almost_ like normal.

And regarding the ability to scroll - my workaround allows that, and does not lose focus, because ScrollView eats the touch_up event if it gets scrolled (not just touched)

Aaah, sorry, now I know what do you mean. :smile: Yup, plain touch should definitelly unfocus it (thanks dessant).


Resolving patch:
diff --git a/kivy/uix/scrollview.py b/kivy/uix/scrollview.py
--- a/kivy/uix/scrollview.py
+++ b/kivy/uix/scrollview.py
@@ -682,7 +682,6 @@ def on_scroll_start(self, touch, check_children=True):
         # this touch.
         self._touch = touch
         uid = self._get_uid()
-        FocusBehavior.ignored_touch.append(touch)
 
         ud[uid] = {
             'mode': 'unknown',
@@ -736,6 +735,10 @@ def on_scroll_move(self, touch):
 
         rv = True
 
+        # By default this touch can be used to defocus currently focused
+        # widget, like any touch outside of ScrollView.
+        touch.ud['sv.can_defocus'] = True
+
         uid = self._get_uid()
         if not uid in touch.ud:
             self._touch = False
@@ -759,6 +762,8 @@ def on_scroll_move(self, touch):
                     rv = False
                 else:
                     touch.ud['sv.handled']['x'] = True
+                # Touch resulted in scroll should not defocus focused widget
+                touch.ud['sv.can_defocus'] = False
             if not touch.ud['sv.handled']['y'] and self.do_scroll_y \
                     and self.effect_y:
                 height = self.height
@@ -773,6 +778,8 @@ def on_scroll_move(self, touch):
                     rv = False
                 else:
                     touch.ud['sv.handled']['y'] = True
+                # Touch resulted in scroll should not defocus focused widget
+                touch.ud['sv.can_defocus'] = False
 
         if mode == 'unknown':
             ud['dx'] += abs(touch.dx)
@@ -809,6 +816,9 @@ def on_touch_up(self, touch):
 
         if self.dispatch('on_scroll_stop', touch):
             touch.ungrab(self)
+            if not touch.ud.get('sv.can_defocus', True):
+                # Focused widget should stay focused
+                FocusBehavior.ignored_touch.append(touch)
             return True
 
     def on_scroll_stop(self, touch, check_children=True):


Test patch: