Repository: kivy/kivy

Issue:
Textinput selectionhandle: right to left
There's the issue that the when you select from the right to the left side, the handles get somehow mixed up. No issue when selecting from left to right.

See video: 
http://youtu.be/UGZPtXWaMe8

SelectionHandle in Layout
While dragging the handles there's a delta (absolute pos x of the textinput) between the touch and the handle. (Just like the issue with a textinput in the scatter)

To reproduce (with the latest selection_handles branch): http://pastebin.com/JCAHSk5A



Discussion:
I can't reproduce this on the latest master, could you please try with latest master?



Resolving patch:
diff --git a/kivy/uix/textinput.py b/kivy/uix/textinput.py
--- a/kivy/uix/textinput.py
+++ b/kivy/uix/textinput.py
@@ -373,9 +373,16 @@ def __init__(self, **kwargs):
 
         self.bind(pos=self._trigger_update_graphics)
 
+        self._trigger_position_handles = Clock.create_trigger(
+            self._position_handles)
+        self._trigger_show_handles = Clock.create_trigger(
+            self._show_handles, .05)
         self._trigger_refresh_line_options()
         self._trigger_refresh_text()
 
+        self.bind(pos=self._trigger_position_handles,
+                  size=self._trigger_position_handles)
+
         # when the gl context is reloaded, trigger the text rendering again.
         _textinput_list.append(ref(self, TextInput._reload_remove_observer))
 
@@ -1008,19 +1015,25 @@ def on_touch_up(self, touch):
                                        on_release=self._handle_released)
                 if not self._handle_middle.parent and self.text:
                     self._win.add_widget(handle_middle)
-                self._position_handles('middle')
+                self._position_handles(mode='middle')
             return True
 
     def _handle_pressed(self, instance):
         self._hide_cut_copy_paste()
+        sf, st = self._selection_from, self.selection_to
+        if sf > st:
+            self._selection_from , self._selection_to = st, sf
 
     def _handle_released(self, instance):
-        if self.selection_to != self.selection_from:
-            self._update_selection()
-            self._show_cut_copy_paste(
-                (instance.x + ((1 if instance is self._handle_left else - 1)
-                 * self._bubble.width / 2) if self._bubble else 0,
-                 instance.y + self.line_height), self._win)
+        sf, st = self._selection_from, self.selection_to
+        if sf == st:
+            return
+
+        self._update_selection()
+        self._show_cut_copy_paste(
+            (instance.x + ((1 if instance is self._handle_left else - 1)
+                * self._bubble.width / 2) if self._bubble else 0,
+                instance.y + self.line_height), self._win)
 
     def _handle_move(self, instance, touch):
         if touch.grab_current != instance:
@@ -1030,51 +1043,65 @@ def _handle_move(self, instance, touch):
         handle_left = self._handle_left
         handle_middle = self._handle_middle
 
+        x, y = self.to_widget(*touch.pos)
         cursor = get_cursor(
-            touch.x, touch.y + instance._touch_diff + (self.line_height / 2))
+            x,
+            y + instance._touch_diff + (self.line_height / 2))
 
         if instance != touch.grab_current:
             return
 
         if instance == handle_middle:
             self.cursor = cursor
-            self._position_handles('middle')
+            self._position_handles(mode='middle')
             return
+
+        ci = self.cursor_index(cursor=cursor)
+        sf, st = self._selection_from, self.selection_to
+
         if instance == handle_left:
-            self._selection_from = self.cursor_index(cursor=cursor)
+            self._selection_from = ci
         elif instance == handle_right:
-            self._selection_to = self.cursor_index(cursor=cursor)
+            self._selection_to = ci
         self._trigger_update_graphics()
-        Clock.schedule_once(lambda dt: self._position_handles())
+        self._trigger_position_handles()
+
+    def _position_handles(self, *args, **kwargs):
+        if not self.text:
+            return
+        mode = kwargs.get('mode', 'both')
 
-    def _position_handles(self, mode='both'):
-        group = self.canvas.get_group('selection')
         lh = self.line_height
+        to_win = self.to_window
 
-        if mode[0] == 'm':
-            handle_middle = self._handle_middle
+        handle_middle = self._handle_middle
+        if handle_middle:
             hp_mid = self.cursor_pos
-            pos = self.to_window(*hp_mid)
+            pos = to_win(*hp_mid)
             handle_middle.x = pos[0] - handle_middle.width / 2
             handle_middle.top = pos[1] - lh
+        if mode[0] == 'm':
             return
 
+        group = self.canvas.get_group('selection')
+        if not group:
+            return
         self._win.remove_widget(self._handle_middle)
 
-        if mode[0] != 'm':
-            handle_left = self._handle_left
-            hp_left = group[2].pos
-            handle_left.pos = self.to_window(*hp_left)
-            handle_left.x -= handle_left.width
-            handle_left.y -= handle_left.height
-
-        #if mode[0] in ('b', 'r'):
-            handle_right = self._handle_right
-            last_rect = group[-1]
-            hp_right = last_rect.pos[0], last_rect.pos[1]
-            handle_right.pos = self.to_window(*hp_right)
-            handle_right.x += last_rect.size[0]
-            handle_right.y -= handle_right.height
+        handle_left = self._handle_left
+        if not handle_left:
+            return
+        hp_left = group[2].pos
+        handle_left.pos = to_win(*hp_left)
+        handle_left.x -= handle_left.width
+        handle_left.y -= handle_left.height
+
+        handle_right = self._handle_right
+        last_rect = group[-1]
+        hp_right = last_rect.pos[0], last_rect.pos[1]
+        x, y = to_win(*hp_right)
+        handle_right.x =  x + last_rect.size[0]
+        handle_right.y = y - handle_right.height
 
     def _hide_handles(self, win=None):
         win = win or self._win
@@ -1094,10 +1121,11 @@ def _hide_cut_copy_paste(self, win=None):
             anim.bind(on_complete=lambda *args: win.remove_widget(bubble))
             anim.start(bubble)
 
-    def _show_handles(self, win):
-        if not self.use_handles:
+    def _show_handles(self, dt):
+        if not self.use_handles or not self.text:
             return
 
+        win = self._win
         if not win:
             self._set_window()
             win = self._win
@@ -1120,12 +1148,13 @@ def _show_handles(self, win):
                               on_touch_move=self._handle_move,
                               on_release=self._handle_released)
         else:
-            win.remove_widget(self._handle_left)
-            win.remove_widget(self._handle_right)
+            if self._handle_left.parent:
+                self._position_handles()
+                return
             if not self.parent:
                 return
 
-        Clock.schedule_once(lambda dt: self._position_handles())
+        self._trigger_position_handles()
         if self.selection_from != self.selection_to:
             self._handle_left.opacity = self._handle_right.opacity = 0
             win.add_widget(self._handle_left)
@@ -1220,7 +1249,7 @@ def _set_window(self, *largs):
                 return
             else:
                 #XXX where do `value` comes from?
-                Clock.schedule_once(partial(self.on_focus, self, value), 0)
+                Clock.schedule_once(partial(self.on_focus, self, largs), 0)
             return
 
     def on_focus(self, instance, value, *largs):
@@ -1680,6 +1709,7 @@ def _update_graphics_selection(self):
                                padding_left, padding_right, x,
                                canvas_add, selection_color)
             y -= dy
+        self._position_handles('both')
 
     def _draw_selection(self, *largs):
         pos, size, line_num, (s1c, s1r), (s2c, s2r),\
@@ -2358,8 +2388,8 @@ def get_sel_to(self):
     '''
 
     def on_selection_text(self, instance, value):
-        if value:
-            self._show_handles(self._win)
+        if value and self.use_handles:
+            self._trigger_show_handles()
 
     focus = BooleanProperty(False)
     '''If focus is True, the keyboard will be requested and you can start


Test patch: