[gtk/wip/carlosg/text-handle-natives: 682/685] gtktexthandle: Refactor and use native surfaces



commit b831ad76afa945a02d05f4106b2e575ea968f5bc
Author: Carlos Garnacho <carlosg gnome org>
Date:   Mon Feb 3 15:59:49 2020 +0100

    gtktexthandle: Refactor and use native surfaces
    
    Instead of being a GObject managing two GtkWidgets, make GtkTextHandle
    a GtkWidget subclass, representing a single handle.
    
    From the perspective of users (GtkText and GtkTextView), this is not a
    big leap since they have to be aware of a great deal of text handles'
    state. It actually makes things more direct and simple.
    
    With text handles being widgets, those can be actual children of the
    widget, and may have their own GdkSurface that we move around at will.
    This is the second major aspect of this refactor.

 gtk/gtktext.c              | 367 ++++++++----------
 gtk/gtktexthandle.c        | 906 ++++++++++++++++-----------------------------
 gtk/gtktexthandleprivate.h |  69 +---
 gtk/gtktextview.c          | 332 ++++++++---------
 4 files changed, 625 insertions(+), 1049 deletions(-)
---
diff --git a/gtk/gtktext.c b/gtk/gtktext.c
index 74d11dae9b..23e304cd7f 100644
--- a/gtk/gtktext.c
+++ b/gtk/gtktext.c
@@ -140,6 +140,13 @@
 
 static GQuark          quark_password_hint  = 0;
 
+enum
+{
+  TEXT_HANDLE_CURSOR,
+  TEXT_HANDLE_SELECTION_BOUND,
+  TEXT_HANDLE_N_HANDLES
+};
+
 typedef struct _GtkTextPasswordHint GtkTextPasswordHint;
 
 typedef struct _GtkTextPrivate GtkTextPrivate;
@@ -159,7 +166,7 @@ struct _GtkTextPrivate
   char         *im_module;
 
   GtkWidget     *emoji_completion;
-  GtkTextHandle *text_handle;
+  GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES];
   GtkWidget     *selection_bubble;
   guint          selection_bubble_timeout_id;
 
@@ -230,6 +237,7 @@ struct _GtkTextPrivate
   guint         selection_handle_dragged : 1;
   guint         populate_all            : 1;
   guint         propagate_text_width    : 1;
+  guint         text_handles_enabled    : 1;
 };
 
 struct _GtkTextPasswordHint
@@ -467,15 +475,12 @@ static gboolean gtk_text_key_controller_key_pressed  (GtkEventControllerKey   *c
 
 /* GtkTextHandle handlers */
 static void gtk_text_handle_drag_started  (GtkTextHandle          *handle,
-                                           GtkTextHandlePosition   pos,
                                            GtkText                *self);
 static void gtk_text_handle_dragged       (GtkTextHandle          *handle,
-                                           GtkTextHandlePosition   pos,
                                            int                     x,
                                            int                     y,
                                            GtkText                *self);
 static void gtk_text_handle_drag_finished (GtkTextHandle          *handle,
-                                           GtkTextHandlePosition   pos,
                                            GtkText                *self);
 
 /* Internal routines
@@ -542,6 +547,7 @@ static void         emit_changed                       (GtkText *self);
 
 static void         gtk_text_update_clipboard_actions (GtkText *self);
 static void         gtk_text_update_emoji_action      (GtkText *self);
+static void         gtk_text_update_handles           (GtkText *self);
 
 static void gtk_text_activate_clipboard_cut          (GtkWidget  *widget,
                                                       const char *action_name,
@@ -1704,6 +1710,24 @@ gtk_text_get_property (GObject    *object,
     }
 }
 
+static void
+gtk_text_create_text_handles (GtkText *self)
+{
+  GtkTextPrivate *priv = gtk_text_get_instance_private (self);
+  int i;
+
+  for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++)
+    {
+      priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (self));
+      g_signal_connect (priv->text_handles[i], "drag-started",
+                        G_CALLBACK (gtk_text_handle_drag_started), self);
+      g_signal_connect (priv->text_handles[i], "handle-dragged",
+                        G_CALLBACK (gtk_text_handle_dragged), self);
+      g_signal_connect (priv->text_handles[i], "drag-finished",
+                        G_CALLBACK (gtk_text_handle_drag_finished), self);
+    }
+}
+
 static void
 gtk_text_init (GtkText *self)
 {
@@ -1804,6 +1828,8 @@ gtk_text_init (GtkText *self)
     }
 
   set_text_cursor (GTK_WIDGET (self));
+
+  gtk_text_create_text_handles (self);
 }
 
 static void
@@ -1872,7 +1898,8 @@ gtk_text_finalize (GObject *object)
   g_clear_object (&priv->cached_layout);
   g_clear_object (&priv->im_context);
   g_clear_pointer (&priv->magnifier_popover, gtk_widget_destroy);
-  g_clear_object (&priv->text_handle);
+  g_clear_object (&priv->text_handles[TEXT_HANDLE_CURSOR]);
+  g_clear_object (&priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]);
   g_free (priv->im_module);
 
   g_clear_pointer (&priv->placeholder, gtk_widget_unparent);
@@ -1907,23 +1934,6 @@ gtk_text_ensure_magnifier (GtkText *self)
   gtk_widget_show (priv->magnifier);
 }
 
-static void
-gtk_text_ensure_text_handles (GtkText *self)
-{
-  GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-
-  if (priv->text_handle)
-    return;
-
-  priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (self));
-  g_signal_connect (priv->text_handle, "drag-started",
-                    G_CALLBACK (gtk_text_handle_drag_started), self);
-  g_signal_connect (priv->text_handle, "handle-dragged",
-                    G_CALLBACK (gtk_text_handle_dragged), self);
-  g_signal_connect (priv->text_handle, "drag-finished",
-                    G_CALLBACK (gtk_text_handle_drag_finished), self);
-}
-
 static void
 begin_change (GtkText *self)
 {
@@ -2054,10 +2064,8 @@ gtk_text_unmap (GtkWidget *widget)
   GtkText *self = GTK_TEXT (widget);
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
-
+  priv->text_handles_enabled = FALSE;
+  gtk_text_update_handles (self);
   priv->cursor_alpha = 1.0;
 
   GTK_WIDGET_CLASS (gtk_text_parent_class)->unmap (widget);
@@ -2121,21 +2129,21 @@ update_im_cursor_location (GtkText *self)
 }
 
 static void
-gtk_text_move_handle (GtkText               *self,
-                      GtkTextHandlePosition  pos,
-                      int                    x,
-                      int                    y,
-                      int                    height)
+gtk_text_move_handle (GtkText       *self,
+                      GtkTextHandle *handle,
+                      int            x,
+                      int            y,
+                      int            height)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
 
-  if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
+  if (!gtk_text_handle_get_is_dragged (handle) &&
       (x < 0 || x > gtk_widget_get_width (GTK_WIDGET (self))))
     {
       /* Hide the handle if it's not being manipulated
        * and fell outside of the visible text area.
        */
-      _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE);
+      gtk_widget_hide (GTK_WIDGET (handle));
     }
   else
     {
@@ -2146,9 +2154,9 @@ gtk_text_move_handle (GtkText               *self,
       rect.width = 1;
       rect.height = height;
 
-      _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
-      _gtk_text_handle_set_position (priv->text_handle, pos, &rect);
-      _gtk_text_handle_set_direction (priv->text_handle, pos, priv->resolved_dir);
+      gtk_text_handle_set_position (handle, &rect);
+      gtk_widget_set_direction (GTK_WIDGET (handle), priv->resolved_dir);
+      gtk_widget_show (GTK_WIDGET (handle));
     }
 }
 
@@ -2176,45 +2184,62 @@ gtk_text_get_selection_bound_location (GtkText *self)
 }
 
 static void
-gtk_text_update_handles (GtkText           *self,
-                         GtkTextHandleMode  mode)
+gtk_text_update_handles (GtkText *self)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
   const int text_height = gtk_widget_get_height (GTK_WIDGET (self));
   int strong_x;
   int cursor, bound;
 
-  _gtk_text_handle_set_mode (priv->text_handle, mode);
+  if (!priv->text_handles_enabled)
+    {
+      gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
+      gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
+    }
+  else
+    {
+      gtk_text_get_cursor_locations (self, &strong_x, NULL);
+      cursor = strong_x - priv->scroll_offset;
 
-  gtk_text_get_cursor_locations (self, &strong_x, NULL);
-  cursor = strong_x - priv->scroll_offset;
+      if (priv->selection_bound != priv->current_pos)
+        {
+          int start, end;
 
-  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
-    {
-      int start, end;
+          bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset;
 
-      bound = gtk_text_get_selection_bound_location (self) - priv->scroll_offset;
+          if (priv->selection_bound > priv->current_pos)
+            {
+              start = cursor;
+              end = bound;
+            }
+          else
+            {
+              start = bound;
+              end = cursor;
+            }
 
-      if (priv->selection_bound > priv->current_pos)
-        {
-          start = cursor;
-          end = bound;
+          /* Update start selection bound */
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
+                                    GTK_TEXT_HANDLE_ROLE_SELECTION_END);
+          gtk_text_move_handle (self,
+                                priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
+                                end, 0, text_height);
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR],
+                                    GTK_TEXT_HANDLE_ROLE_SELECTION_START);
+          gtk_text_move_handle (self,
+                                priv->text_handles[TEXT_HANDLE_CURSOR],
+                                start, 0, text_height);
         }
       else
         {
-          start = bound;
-          end = cursor;
+          gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR],
+                                    GTK_TEXT_HANDLE_ROLE_CURSOR);
+          gtk_text_move_handle (self,
+                                priv->text_handles[TEXT_HANDLE_CURSOR],
+                                cursor, 0, text_height);
         }
-
-      /* Update start selection bound */
-      gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_START,
-                             start, 0, text_height);
-      gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_SELECTION_END,
-                             end, 0, text_height);
     }
-  else
-    gtk_text_move_handle (self, GTK_TEXT_HANDLE_POSITION_CURSOR,
-                           cursor, 0, text_height);
 }
 
 static void
@@ -2345,13 +2370,7 @@ gtk_text_size_allocate (GtkWidget *widget,
   if (chooser)
     gtk_native_check_resize (GTK_NATIVE (chooser));
 
-  if (priv->text_handle)
-    {
-      GtkTextHandleMode handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
-
-      if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
-        gtk_text_update_handles (self, handle_mode);
-    }
+  gtk_text_update_handles (self);
 
   if (priv->emoji_completion)
     gtk_native_check_resize (GTK_NATIVE (priv->emoji_completion));
@@ -2364,6 +2383,12 @@ gtk_text_size_allocate (GtkWidget *widget,
 
   if (priv->selection_bubble)
     gtk_native_check_resize (GTK_NATIVE (priv->selection_bubble));
+
+  if (priv->text_handles[TEXT_HANDLE_CURSOR])
+    gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_CURSOR]));
+
+  if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
+    gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
 }
 
 static void
@@ -2602,7 +2627,6 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
   else if (button == GDK_BUTTON_PRIMARY)
     {
       gboolean have_selection;
-      GtkTextHandleMode mode;
       gboolean is_touchscreen, extend_selection;
       GdkDevice *source;
       guint state;
@@ -2615,15 +2639,7 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
       is_touchscreen = gtk_simulate_touchscreen () ||
                        gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN;
 
-      if (!is_touchscreen)
-        mode = GTK_TEXT_HANDLE_MODE_NONE;
-      else if (have_selection)
-        mode = GTK_TEXT_HANDLE_MODE_SELECTION;
-      else
-        mode = GTK_TEXT_HANDLE_MODE_CURSOR;
-
-      if (is_touchscreen)
-        gtk_text_ensure_text_handles (self);
+      priv->text_handles_enabled = is_touchscreen;
 
       priv->in_drag = FALSE;
       priv->select_words = FALSE;
@@ -2696,15 +2712,11 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
         case 2:
           priv->select_words = TRUE;
           gtk_text_select_word (self);
-          if (is_touchscreen)
-            mode = GTK_TEXT_HANDLE_MODE_SELECTION;
           break;
 
         case 3:
           priv->select_lines = TRUE;
           gtk_text_select_line (self);
-          if (is_touchscreen)
-            mode = GTK_TEXT_HANDLE_MODE_SELECTION;
           break;
 
         default:
@@ -2736,8 +2748,7 @@ gtk_text_click_gesture_pressed (GtkGestureClick *gesture,
       gtk_gesture_set_state (priv->drag_gesture,
                              GTK_EVENT_SEQUENCE_CLAIMED);
 
-      if (priv->text_handle)
-        gtk_text_update_handles (self, mode);
+      gtk_text_update_handles (self);
     }
 
   if (n_press >= 3)
@@ -2953,11 +2964,8 @@ gtk_text_drag_gesture_update (GtkGestureDrag *gesture,
       if (gtk_simulate_touchscreen () ||
           input_source == GDK_SOURCE_TOUCHSCREEN)
         {
-          gtk_text_ensure_text_handles (self);
-          gtk_text_update_handles (self,
-                                    (priv->current_pos == priv->selection_bound) ?
-                                    GTK_TEXT_HANDLE_MODE_CURSOR :
-                                    GTK_TEXT_HANDLE_MODE_SELECTION);
+          priv->text_handles_enabled = TRUE;
+          gtk_text_update_handles (self);
           gtk_text_show_magnifier (self, x - priv->scroll_offset, y);
         }
     }
@@ -2970,10 +2978,8 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture,
                            GtkText        *self)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-  gboolean in_drag, is_touchscreen;
+  gboolean in_drag;
   GdkEventSequence *sequence;
-  GdkEvent *event;
-  GdkDevice *source;
 
   sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
   in_drag = priv->in_drag;
@@ -2986,11 +2992,6 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture,
   if (!gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
     return;
 
-  event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
-  source = gdk_event_get_source_device (event);
-  is_touchscreen = gtk_simulate_touchscreen () ||
-                   gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN;
-
   if (in_drag)
     {
       int tmp_pos = gtk_text_find_position (self, priv->drag_start_x);
@@ -2998,8 +2999,7 @@ gtk_text_drag_gesture_end (GtkGestureDrag *gesture,
       gtk_text_set_selection_bounds (self, tmp_pos, tmp_pos);
     }
 
-  if (is_touchscreen && priv->selection_bound != priv->current_pos)
-    gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR);
+  gtk_text_update_handles (self);
 
   gtk_text_update_primary_selection (self);
 }
@@ -3032,9 +3032,8 @@ gtk_text_key_controller_key_pressed (GtkEventControllerKey *controller,
 
   gtk_text_selection_bubble_popup_unset (self);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
+  priv->text_handles_enabled = FALSE;
+  gtk_text_update_handles (self);
 
   if (keyval == GDK_KEY_Return ||
       keyval == GDK_KEY_KP_Enter ||
@@ -3085,9 +3084,8 @@ gtk_text_focus_out (GtkWidget *widget)
 
   gtk_text_selection_bubble_popup_unset (self);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
+  priv->text_handles_enabled = FALSE;
+  gtk_text_update_handles (self);
 
   gtk_widget_queue_draw (widget);
 
@@ -3961,15 +3959,7 @@ gtk_text_cut_clipboard (GtkText *self)
 
   gtk_text_selection_bubble_popup_unset (self);
 
-  if (priv->text_handle)
-    {
-      GtkTextHandleMode handle_mode;
-
-      handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
-
-      if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
-        gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR);
-    }
+  gtk_text_update_handles (self);
 }
 
 static void
@@ -3982,15 +3972,7 @@ gtk_text_paste_clipboard (GtkText *self)
   else
     gtk_widget_error_bell (GTK_WIDGET (self));
 
-  if (priv->text_handle)
-    {
-      GtkTextHandleMode handle_mode;
-
-      handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
-
-      if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
-        gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_CURSOR);
-    }
+  gtk_text_update_handles (self);
 }
 
 static void
@@ -4247,9 +4229,6 @@ gtk_text_reset_layout (GtkText *self)
 static void
 gtk_text_recompute (GtkText *self)
 {
-  GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-  GtkTextHandleMode handle_mode;
-
   gtk_text_reset_layout (self);
   gtk_text_check_cursor_blink (self);
 
@@ -4257,13 +4236,7 @@ gtk_text_recompute (GtkText *self)
 
   update_im_cursor_location (self);
 
-  if (priv->text_handle)
-    {
-      handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
-
-      if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
-        gtk_text_update_handles (self, handle_mode);
-    }
+  gtk_text_update_handles (self);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
 }
@@ -4546,84 +4519,67 @@ gtk_text_draw_cursor (GtkText     *self,
 }
 
 static void
-gtk_text_handle_dragged (GtkTextHandle         *handle,
-                         GtkTextHandlePosition  pos,
-                         int                    x,
-                         int                    y,
-                         GtkText               *self)
+gtk_text_handle_dragged (GtkTextHandle *handle,
+                         int            x,
+                         int            y,
+                         GtkText       *self)
 {
-  int cursor_pos, selection_bound_pos, tmp_pos;
+  int cursor_pos, selection_bound_pos, tmp_pos, *old_pos;
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-  GtkTextHandleMode mode;
-  int *min, *max;
 
   gtk_text_selection_bubble_popup_unset (self);
 
   cursor_pos = priv->current_pos;
   selection_bound_pos = priv->selection_bound;
-  mode = _gtk_text_handle_get_mode (handle);
 
   tmp_pos = gtk_text_find_position (self, x + priv->scroll_offset);
 
-  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR ||
-      cursor_pos >= selection_bound_pos)
-    {
-      max = &cursor_pos;
-      min = &selection_bound_pos;
-    }
-  else
-    {
-      max = &selection_bound_pos;
-      min = &cursor_pos;
-    }
-
-  if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
+  if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+      /* Avoid running past the other handle in selection mode */
+      if (tmp_pos >= selection_bound_pos &&
+          gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
         {
-          int min_pos;
-
-          min_pos = MAX (*min + 1, 0);
-          tmp_pos = MAX (tmp_pos, min_pos);
+          tmp_pos = selection_bound_pos - 1;
         }
 
-      *max = tmp_pos;
+      old_pos = &cursor_pos;
     }
-  else
+  else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
-        {
-          int max_pos;
+      /* Avoid running past the other handle */
+      if (tmp_pos <= cursor_pos)
+        tmp_pos = cursor_pos + 1;
 
-          max_pos = *max - 1;
-          *min = MIN (tmp_pos, max_pos);
-        }
+      old_pos = &selection_bound_pos;
     }
+  else
+    g_assert_not_reached ();
 
-  if (cursor_pos != priv->current_pos ||
-      selection_bound_pos != priv->selection_bound)
+  if (tmp_pos != *old_pos)
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-        {
-          priv->cursor_handle_dragged = TRUE;
-          gtk_text_set_positions (self, cursor_pos, cursor_pos);
-        }
+      *old_pos = tmp_pos;
+
+      if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] &&
+          !gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
+        gtk_text_set_positions (self, cursor_pos, cursor_pos);
       else
-        {
-          priv->selection_handle_dragged = TRUE;
-          gtk_text_set_positions (self, cursor_pos, selection_bound_pos);
-        }
+        gtk_text_set_positions (self, cursor_pos, selection_bound_pos);
+
+      if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
+        priv->cursor_handle_dragged = TRUE;
+      else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
+        priv->selection_handle_dragged = TRUE;
 
-      gtk_text_update_handles (self, mode);
+      gtk_text_update_handles (self);
     }
 
   gtk_text_show_magnifier (self, x, y);
 }
 
 static void
-gtk_text_handle_drag_started (GtkTextHandle         *handle,
-                              GtkTextHandlePosition  pos,
-                              GtkText               *self)
+gtk_text_handle_drag_started (GtkTextHandle *handle,
+                              GtkText       *self)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
 
@@ -4632,9 +4588,8 @@ gtk_text_handle_drag_started (GtkTextHandle         *handle,
 }
 
 static void
-gtk_text_handle_drag_finished (GtkTextHandle         *handle,
-                               GtkTextHandlePosition  pos,
-                               GtkText               *self)
+gtk_text_handle_drag_finished (GtkTextHandle *handle,
+                               GtkText       *self)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
 
@@ -4648,7 +4603,7 @@ gtk_text_handle_drag_finished (GtkTextHandle         *handle,
       if (g_get_monotonic_time() - priv->handle_place_time < double_click_time * 1000)
         {
           gtk_text_select_word (self);
-          gtk_text_update_handles (self, GTK_TEXT_HANDLE_MODE_SELECTION);
+          gtk_text_update_handles (self);
         }
       else
         gtk_text_selection_bubble_popup_set (self);
@@ -4759,20 +4714,14 @@ static gboolean
 gtk_text_get_is_selection_handle_dragged (GtkText *self)
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
-  GtkTextHandlePosition pos;
-
-  if (!priv->text_handle)
-    return FALSE;
-
-  if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION)
-    return FALSE;
+  GtkTextHandle *handle;
 
   if (priv->current_pos >= priv->selection_bound)
-    pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
+    handle = priv->text_handles[TEXT_HANDLE_CURSOR];
   else
-    pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
+    handle = priv->text_handles[TEXT_HANDLE_SELECTION_BOUND];
 
-  return _gtk_text_handle_get_is_dragged (priv->text_handle, pos);
+  return gtk_text_handle_get_is_dragged (handle);
 }
 
 static void
@@ -4822,7 +4771,6 @@ gtk_text_adjust_scroll (GtkText *self)
   int min_offset, max_offset;
   int strong_x, weak_x;
   int strong_xoffset, weak_xoffset;
-  GtkTextHandleMode handle_mode;
 
   if (!gtk_widget_get_realized (GTK_WIDGET (self)))
     return;
@@ -4885,13 +4833,7 @@ gtk_text_adjust_scroll (GtkText *self)
 
   g_object_notify_by_pspec (G_OBJECT (self), text_props[PROP_SCROLL_OFFSET]);
 
-  if (priv->text_handle)
-    {
-      handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
-
-      if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
-        gtk_text_update_handles (self, handle_mode);
-    }
+  gtk_text_update_handles (self);
 }
 
 static int
@@ -5930,23 +5872,10 @@ show_or_hide_handles (GtkWidget  *popover,
 {
   GtkTextPrivate *priv = gtk_text_get_instance_private (self);
   gboolean visible;
-  GtkTextHandle *handle;
-  GtkTextHandleMode mode;
 
   visible = gtk_widget_get_visible (popover);
-
-  handle = priv->text_handle;
-  mode = _gtk_text_handle_get_mode (handle);
-
-  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-    {
-      _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_CURSOR, !visible);
-    }
-  else if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
-    {
-      _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, !visible);
-      _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, !visible);
-    }
+  priv->text_handles_enabled = !visible;
+  gtk_text_update_handles (self);
 }
 
 static void
diff --git a/gtk/gtktexthandle.c b/gtk/gtktexthandle.c
index c22a928731..a79df5442a 100644
--- a/gtk/gtktexthandle.c
+++ b/gtk/gtktexthandle.c
@@ -25,7 +25,6 @@
 #include "gtkwindowprivate.h"
 #include "gtkcssnodeprivate.h"
 #include "gtkwidgetprivate.h"
-#include "gtkgizmoprivate.h"
 #include "gtkrendericonprivate.h"
 #include "gtkstylecontextprivate.h"
 #include "gtkintl.h"
@@ -33,7 +32,6 @@
 #include <gtk/gtk.h>
 
 typedef struct _GtkTextHandlePrivate GtkTextHandlePrivate;
-typedef struct _HandleWindow HandleWindow;
 
 enum {
   DRAG_STARTED,
@@ -42,571 +40,279 @@ enum {
   LAST_SIGNAL
 };
 
-enum {
-  PROP_0,
-  PROP_PARENT
-};
-
-struct _HandleWindow
+struct _GtkTextHandlePrivate
 {
-  GtkWidget *widget;
+  GtkWidget *parent;
+  GdkSurface *surface;
+  GskRenderer *renderer;
+
   GdkRectangle pointing_to;
   GtkBorder border;
   gint dx;
   gint dy;
-  GtkTextDirection dir;
+  guint role : 2;
   guint dragged : 1;
   guint mode_visible : 1;
   guint user_visible : 1;
   guint has_point : 1;
 };
 
-struct _GtkTextHandlePrivate
+struct _GtkTextHandle
 {
-  HandleWindow windows[2];
-  GtkWidget *toplevel;
-  GtkWidget *parent;
-  GtkScrollable *parent_scrollable;
-  GtkAdjustment *vadj;
-  GtkAdjustment *hadj;
-  guint hierarchy_changed_id;
-  guint scrollable_notify_id;
-  guint mode : 2;
+  GtkWidget parent_instance;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE (GtkTextHandle, _gtk_text_handle, G_TYPE_OBJECT)
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-static void _gtk_text_handle_update (GtkTextHandle         *handle,
-                                     GtkTextHandlePosition  pos);
+static void gtk_text_handle_native_interface_init (GtkNativeInterface *iface);
 
-static void
-_gtk_text_handle_get_size (GtkTextHandle         *handle,
-                           GtkTextHandlePosition  pos,
-                           gint                  *width,
-                           gint                  *height)
-{
-  GtkTextHandlePrivate *priv = handle->priv;
-  GtkWidget *widget = priv->windows[pos].widget;
-  GtkStyleContext *context;
-
-  context = gtk_widget_get_style_context (widget);
-  
-  *width = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, 
GTK_CSS_PROPERTY_MIN_WIDTH), 100);
-  *height = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, 
GTK_CSS_PROPERTY_MIN_HEIGHT), 100);
-}
+G_DEFINE_TYPE_WITH_CODE (GtkTextHandle, gtk_text_handle, GTK_TYPE_WIDGET,
+                         G_ADD_PRIVATE (GtkTextHandle)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
+                                                gtk_text_handle_native_interface_init))
 
-static gint
-_text_handle_pos_from_widget (GtkTextHandle *handle,
-                              GtkWidget     *widget)
-{
-  GtkTextHandlePrivate *priv = handle->priv;
-
-  if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
-    return GTK_TEXT_HANDLE_POSITION_SELECTION_START;
-  else if (widget == priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
-    return GTK_TEXT_HANDLE_POSITION_SELECTION_END;
-  else
-    return -1;
-}
+static guint signals[LAST_SIGNAL] = { 0 };
 
-static void
-handle_drag_begin (GtkGestureDrag *gesture,
-                   gdouble         x,
-                   gdouble         y,
-                   GtkTextHandle  *handle)
+static GdkSurface *
+gtk_text_handle_native_get_surface (GtkNative *native)
 {
-  GtkTextHandlePrivate *priv = handle->priv;
-  GtkWidget *widget;
-  gint pos;
-
-  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
-  pos = _text_handle_pos_from_widget (handle, widget);
-
-  if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
-      priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-    x -= gtk_widget_get_width (widget) / 2;
-  else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
-            priv->windows[pos].dir == GTK_TEXT_DIR_RTL) ||
-           (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START &&
-            priv->windows[pos].dir != GTK_TEXT_DIR_RTL))
-    x -= gtk_widget_get_width (widget);
-
-  y += priv->windows[pos].border.top / 2;
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (native);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  priv->windows[pos].dx = x;
-  priv->windows[pos].dy = y;
-  priv->windows[pos].dragged = TRUE;
-  g_signal_emit (handle, signals[DRAG_STARTED], 0, pos);
+  return priv->surface;
 }
 
-static void
-handle_drag_update (GtkGestureDrag *gesture,
-                    gdouble         offset_x,
-                    gdouble         offset_y,
-                    GtkTextHandle  *handle)
+static GskRenderer *
+gtk_text_handle_native_get_renderer (GtkNative *native)
 {
-  GtkTextHandlePrivate *priv = handle->priv;
-  gdouble start_x, start_y;
-  gint pos, x, y;
-
-  pos = _text_handle_pos_from_widget (handle,
-                                      gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)));
-  gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (native);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  gtk_widget_translate_coordinates (priv->windows[pos].widget, priv->parent,
-                                    start_x + offset_x - priv->windows[pos].dx,
-                                    start_y + offset_y - priv->windows[pos].dy,
-                                    &x, &y);
-  g_signal_emit (handle, signals[HANDLE_DRAGGED], 0, pos, x, y);
+  return priv->renderer;
 }
 
 static void
-handle_drag_end (GtkGestureDrag *gesture,
-                 gdouble         offset_x,
-                 gdouble         offset_y,
-                 GtkTextHandle  *handle)
+gtk_text_handle_native_get_surface_transform (GtkNative *native,
+                                              int       *x,
+                                              int       *y)
 {
-  GtkTextHandlePrivate *priv = handle->priv;
-  gint pos;
-
-  pos = _text_handle_pos_from_widget (handle,
-                                      gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)));
-  g_signal_emit (handle, signals[DRAG_FINISHED], 0, pos);
-  priv->windows[pos].dragged = FALSE;
+  GtkCssStyle *style;
+
+  style = gtk_css_node_get_style (gtk_widget_get_css_node (GTK_WIDGET (native)));
+  *x  = _gtk_css_number_value_get (style->size->margin_left, 100) +
+        _gtk_css_number_value_get (style->border->border_left_width, 100) +
+        _gtk_css_number_value_get (style->size->padding_left, 100);
+  *y  = _gtk_css_number_value_get (style->size->margin_top, 100) +
+        _gtk_css_number_value_get (style->border->border_top_width, 100) +
+        _gtk_css_number_value_get (style->size->padding_top, 100);
 }
 
 static void
-snapshot_func (GtkGizmo    *gizmo,
-               GtkSnapshot *snapshot)
+gtk_text_handle_native_check_resize (GtkNative *native)
 {
-  GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (GTK_WIDGET (gizmo)));
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (native);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
+  GtkWidget *widget = GTK_WIDGET (native);
 
-  gtk_css_style_snapshot_icon (style,
-                               snapshot,
-                               gtk_widget_get_width (GTK_WIDGET (gizmo)),
-                               gtk_widget_get_height (GTK_WIDGET (gizmo)));
-}
-
-static GtkWidget *
-_gtk_text_handle_ensure_widget (GtkTextHandle         *handle,
-                                GtkTextHandlePosition  pos)
-{
-  GtkTextHandlePrivate *priv;
-
-  priv = handle->priv;
-
-  if (!priv->windows[pos].widget)
+  if (!_gtk_widget_get_alloc_needed (widget))
+    gtk_widget_ensure_allocate (widget);
+  else if (gtk_widget_get_visible (widget) && priv->surface)
     {
-      GtkWidget *widget, *window;
-      GtkEventController *controller;
-
-      widget = gtk_gizmo_new (I_("cursor-handle"), NULL, NULL, snapshot_func, NULL);
-
-      gtk_widget_set_direction (widget, priv->windows[pos].dir);
-
-      controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
-      g_signal_connect (controller, "drag-begin",
-                        G_CALLBACK (handle_drag_begin), handle);
-      g_signal_connect (controller, "drag-update",
-                        G_CALLBACK (handle_drag_update), handle);
-      g_signal_connect (controller, "drag-end",
-                        G_CALLBACK (handle_drag_end), handle);
-      gtk_widget_add_controller (widget, controller);
-
-      priv->windows[pos].widget = g_object_ref_sink (widget);
-      priv->toplevel = window = gtk_widget_get_ancestor (priv->parent, GTK_TYPE_WINDOW);
-      _gtk_window_add_popover (GTK_WINDOW (window), widget, priv->parent, FALSE);
-
-      if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
-        {
-          gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM);
-          if (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-            gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR);
-        }
-      else
-        gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_TOP);
-    }
-
-  return priv->windows[pos].widget;
-}
-
-static void
-_handle_update_child_visible (GtkTextHandle         *handle,
-                              GtkTextHandlePosition  pos)
-{
-  HandleWindow *handle_window;
-  GtkTextHandlePrivate *priv;
-  cairo_rectangle_int_t rect;
-  GtkAllocation allocation;
-  GtkWidget *parent;
+      GtkRequisition req;
 
-  priv = handle->priv;
-  handle_window = &priv->windows[pos];
+      gtk_widget_get_preferred_size (GTK_WIDGET (handle), NULL, &req);
+      gdk_surface_resize (priv->surface, req.width, req.height);
 
-  if (!priv->parent_scrollable)
-    {
-      gtk_widget_set_child_visible (handle_window->widget, TRUE);
-      return;
+      gtk_widget_allocate (widget,
+                           req.width, req.height,
+                           -1, NULL);
     }
-
-  parent = gtk_widget_get_parent (GTK_WIDGET (priv->parent_scrollable));
-  rect = handle_window->pointing_to;
-
-  gtk_widget_translate_coordinates (priv->parent, parent,
-                                    rect.x, rect.y, &rect.x, &rect.y);
-
-  gtk_widget_get_allocation (GTK_WIDGET (parent), &allocation);
-
-  if (rect.x < 0 || rect.x + rect.width > allocation.width ||
-      rect.y < 0 || rect.y + rect.height > allocation.height)
-    gtk_widget_set_child_visible (handle_window->widget, FALSE);
-  else
-    gtk_widget_set_child_visible (handle_window->widget, TRUE);
 }
 
 static void
-_gtk_text_handle_update (GtkTextHandle         *handle,
-                         GtkTextHandlePosition  pos)
+gtk_text_handle_native_interface_init (GtkNativeInterface *iface)
 {
-  GtkTextHandlePrivate *priv;
-  HandleWindow *handle_window;
-  GtkBorder *border;
-
-  priv = handle->priv;
-  handle_window = &priv->windows[pos];
-  border = &handle_window->border;
-
-  if (!priv->parent || !gtk_widget_is_drawable (priv->parent))
-    return;
-
-  if (handle_window->has_point &&
-      handle_window->mode_visible && handle_window->user_visible)
-    {
-      cairo_rectangle_int_t rect;
-      gint width, height;
-      GtkWidget *window;
-      GtkAllocation alloc;
-      gint w, h;
-
-      _gtk_text_handle_ensure_widget (handle, pos);
-      _gtk_text_handle_get_size (handle, pos, &width, &height);
-
-      border->top = height;
-      border->bottom = height;
-      border->left = width;
-      border->right = width;
-
-      rect.x = handle_window->pointing_to.x;
-      rect.y = handle_window->pointing_to.y + handle_window->pointing_to.height - handle_window->border.top;
-      rect.width = width;
-      rect.height = 0;
-
-      _handle_update_child_visible (handle, pos);
-
-      window = gtk_widget_get_parent (handle_window->widget);
-      gtk_widget_translate_coordinates (priv->parent, window,
-                                        rect.x, rect.y, &rect.x, &rect.y);
-
-      if (pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
-          priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-        rect.x -= rect.width / 2;
-      else if ((pos == GTK_TEXT_HANDLE_POSITION_CURSOR &&
-                handle_window->dir == GTK_TEXT_DIR_RTL) ||
-               (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_START &&
-                handle_window->dir != GTK_TEXT_DIR_RTL))
-        rect.x -= rect.width;
-
-      /* The goal is to make the window 3 times as wide and high. The handle
-       * will be rendered in the center, making the rest an invisible border.
-       * If we hit the edge of the toplevel, we shrink the border to avoid
-       * mispositioning the handle, if at all possible. This calculation uses
-       * knowledge about how popover_get_rect() works.
-       */
-
-      gtk_widget_get_allocation (window, &alloc);
-
-      w = width + border->left + border->right;
-      h = height + border->top + border->bottom;
-
-      if (rect.x + rect.width/2 - w/2 < alloc.x)
-        border->left = MAX (0, border->left - (alloc.x - (rect.x + rect.width/2 - w/2)));
-      if (rect.y + rect.height/2 - h/2 < alloc.y)
-        border->top = MAX (0, border->top - (alloc.y - (rect.y + rect.height/2 - h/2)));
-      if (rect.x + rect.width/2 + w/2 > alloc.x + alloc.width)
-        border->right = MAX (0, border->right - (rect.x + rect.width/2 + w/2 - (alloc.x + alloc.width)));
-      if (rect.y + rect.height/2 + h/2 > alloc.y + alloc.height)
-        border->bottom = MAX (0, border->bottom - (rect.y + rect.height/2 + h/2 - (alloc.y + alloc.height)));
-
-      width += border->left + border->right;
-      height += border->top + border->bottom;
-
-      gtk_widget_set_size_request (handle_window->widget, width, height);
-      gtk_widget_show (handle_window->widget);
-      _gtk_window_raise_popover (GTK_WINDOW (window), handle_window->widget);
-      _gtk_window_set_popover_position (GTK_WINDOW (window),
-                                        handle_window->widget,
-                                        GTK_POS_BOTTOM, &rect);
-    }
-  else if (handle_window->widget)
-    gtk_widget_hide (handle_window->widget);
+  iface->get_surface = gtk_text_handle_native_get_surface;
+  iface->get_renderer = gtk_text_handle_native_get_renderer;
+  iface->get_surface_transform = gtk_text_handle_native_get_surface_transform;
+  iface->check_resize = gtk_text_handle_native_check_resize;
 }
 
-static void
-adjustment_changed_cb (GtkAdjustment *adjustment,
-                       GtkTextHandle *handle)
+static gboolean
+surface_render (GdkSurface     *surface,
+                cairo_region_t *region,
+                GtkTextHandle  *handle)
 {
-  _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
-  _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
+  gtk_widget_render (GTK_WIDGET (handle), surface, region);
+  return TRUE;
 }
 
-static void
-_gtk_text_handle_set_scrollable (GtkTextHandle *handle,
-                                 GtkScrollable *scrollable)
+static gboolean
+surface_event (GdkSurface    *surface,
+               GdkEvent      *event,
+               GtkTextHandle *handle)
 {
-  GtkTextHandlePrivate *priv;
-
-  priv = handle->priv;
-
-  if (priv->vadj)
-    {
-      g_signal_handlers_disconnect_by_data (priv->vadj, handle);
-      g_clear_object (&priv->vadj);
-    }
-
-  if (priv->hadj)
-    {
-      g_signal_handlers_disconnect_by_data (priv->hadj, handle);
-      g_clear_object (&priv->hadj);
-    }
-
-  if (priv->parent_scrollable)
-    g_object_remove_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable);
-
-  priv->parent_scrollable = scrollable;
-
-  if (scrollable)
-    {
-      g_object_add_weak_pointer (G_OBJECT (priv->parent_scrollable), (gpointer *) &priv->parent_scrollable);
-
-      priv->vadj = gtk_scrollable_get_vadjustment (scrollable);
-      priv->hadj = gtk_scrollable_get_hadjustment (scrollable);
-
-      if (priv->vadj)
-        {
-          g_object_ref (priv->vadj);
-          g_signal_connect (priv->vadj, "changed",
-                            G_CALLBACK (adjustment_changed_cb), handle);
-          g_signal_connect (priv->vadj, "value-changed",
-                            G_CALLBACK (adjustment_changed_cb), handle);
-        }
-
-      if (priv->hadj)
-        {
-          g_object_ref (priv->hadj);
-          g_signal_connect (priv->hadj, "changed",
-                            G_CALLBACK (adjustment_changed_cb), handle);
-          g_signal_connect (priv->hadj, "value-changed",
-                            G_CALLBACK (adjustment_changed_cb), handle);
-        }
-    }
+  gtk_main_do_event (event);
+  return TRUE;
 }
 
 static void
-_gtk_text_handle_scrollable_notify (GObject       *object,
-                                    GParamSpec    *pspec,
-                                    GtkTextHandle *handle)
+gtk_text_handle_snapshot (GtkWidget   *widget,
+                          GtkSnapshot *snapshot)
 {
-  if (pspec->value_type == GTK_TYPE_ADJUSTMENT)
-    _gtk_text_handle_set_scrollable (handle, GTK_SCROLLABLE (object));
+  GtkCssStyle *style = gtk_css_node_get_style (gtk_widget_get_css_node (widget));
+
+  gtk_css_style_snapshot_icon (style,
+                               snapshot,
+                               gtk_widget_get_width (widget),
+                               gtk_widget_get_height (widget));
 }
 
 static void
-_gtk_text_handle_update_scrollable (GtkTextHandle *handle,
-                                    GtkScrollable *scrollable)
+gtk_text_handle_get_size (GtkTextHandle *handle,
+                          gint          *width,
+                          gint          *height)
 {
-  GtkTextHandlePrivate *priv;
-
-  priv = handle->priv;
-
-  if (priv->parent_scrollable == scrollable)
-    return;
-
-  if (priv->parent_scrollable && priv->scrollable_notify_id &&
-      g_signal_handler_is_connected (priv->parent_scrollable,
-                                     priv->scrollable_notify_id))
-    g_signal_handler_disconnect (priv->parent_scrollable,
-                                 priv->scrollable_notify_id);
+  GtkWidget *widget = GTK_WIDGET (handle);
+  GtkStyleContext *context;
 
-  _gtk_text_handle_set_scrollable (handle, scrollable);
+  context = gtk_widget_get_style_context (widget);
 
-  if (priv->parent_scrollable)
-    priv->scrollable_notify_id =
-      g_signal_connect (priv->parent_scrollable, "notify",
-                        G_CALLBACK (_gtk_text_handle_scrollable_notify),
-                        handle);
+  *width = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, 
GTK_CSS_PROPERTY_MIN_WIDTH), 100);
+  *height = _gtk_css_number_value_get (_gtk_style_context_peek_property (context, 
GTK_CSS_PROPERTY_MIN_HEIGHT), 100);
 }
 
-static GtkWidget *
-gtk_text_handle_lookup_scrollable (GtkTextHandle *handle)
+static void
+gtk_text_handle_realize (GtkWidget *widget)
 {
-  GtkTextHandlePrivate *priv;
-  GtkWidget *scrolled_window;
-
-  priv = handle->priv;
-  scrolled_window = gtk_widget_get_ancestor (priv->parent,
-                                             GTK_TYPE_SCROLLED_WINDOW);
-  if (!scrolled_window)
-    return NULL;
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
+  GdkSurface *parent_surface;
+  GtkWidget *parent;
 
-  return gtk_bin_get_child (GTK_BIN (scrolled_window));
-}
+  parent = gtk_widget_get_parent (widget);
+  parent_surface = gtk_native_get_surface (gtk_widget_get_native (parent));
 
-static void
-_gtk_text_handle_parent_hierarchy_changed (GtkWidget     *widget,
-                                           GParamSpec    *pspec,
-                                           GtkTextHandle *handle)
-{
-  GtkWidget *toplevel, *scrollable;
-  GtkTextHandlePrivate *priv;
+  priv->surface = gdk_surface_new_popup (parent_surface, FALSE);
+  gdk_surface_set_widget (priv->surface, widget);
 
-  priv = handle->priv;
-  toplevel = gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW);
+  g_signal_connect (priv->surface, "render", G_CALLBACK (surface_render), widget);
+  g_signal_connect (priv->surface, "event", G_CALLBACK (surface_event), widget);
 
-  if (priv->toplevel && !toplevel)
-    {
-      if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
-        {
-          _gtk_window_remove_popover (GTK_WINDOW (priv->toplevel),
-                                      priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
-          g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
-          priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget = NULL;
-        }
-
-      if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
-        {
-          _gtk_window_remove_popover (GTK_WINDOW (priv->toplevel),
-                                      priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
-          g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
-          priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget = NULL;
-        }
-
-      priv->toplevel = NULL;
-    }
+  GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->realize (widget);
 
-  scrollable = gtk_text_handle_lookup_scrollable (handle);
-  _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable));
+  priv->renderer = gsk_renderer_new_for_surface (priv->surface);
 }
 
 static void
-_gtk_text_handle_set_parent (GtkTextHandle *handle,
-                             GtkWidget     *parent)
+gtk_text_handle_unrealize (GtkWidget *widget)
 {
-  GtkTextHandlePrivate *priv;
-  GtkWidget *scrollable = NULL;
-
-  priv = handle->priv;
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  if (priv->parent == parent)
-    return;
-
-  if (priv->parent && priv->hierarchy_changed_id &&
-      g_signal_handler_is_connected (priv->parent, priv->hierarchy_changed_id))
-    g_signal_handler_disconnect (priv->parent, priv->hierarchy_changed_id);
-
-  priv->parent = parent;
+  GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unrealize (widget);
 
-  if (parent)
-    {
-      priv->hierarchy_changed_id =
-        g_signal_connect (parent, "notify::root",
-                          G_CALLBACK (_gtk_text_handle_parent_hierarchy_changed),
-                          handle);
+  gsk_renderer_unrealize (priv->renderer);
+  g_clear_object (&priv->renderer);
 
-      scrollable = gtk_text_handle_lookup_scrollable (handle);
-    }
+  g_signal_handlers_disconnect_by_func (priv->surface, surface_render, widget);
+  g_signal_handlers_disconnect_by_func (priv->surface, surface_event, widget);
 
-  _gtk_text_handle_update_scrollable (handle, GTK_SCROLLABLE (scrollable));
+  gdk_surface_set_widget (priv->surface, NULL);
+  gdk_surface_destroy (priv->surface);
+  g_clear_object (&priv->surface);
 }
 
 static void
-gtk_text_handle_finalize (GObject *object)
+gtk_text_handle_present_surface (GtkTextHandle *handle)
 {
-  GtkTextHandlePrivate *priv;
-
-  priv = GTK_TEXT_HANDLE (object)->priv;
-
-  _gtk_text_handle_set_parent (GTK_TEXT_HANDLE (object), NULL);
-
-  /* We sank the references, unref here */
-  if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget)
-    g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START].widget);
-
-  if (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget)
-    g_object_unref (priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END].widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
+  GtkWidget *widget = GTK_WIDGET (handle);
+  GdkPopupLayout *layout;
+  GdkRectangle rect;
+  gint width, height;
+
+  gtk_text_handle_get_size (handle, &width, &height);
+  priv->border.left = priv->border.right = width;
+  priv->border.top = priv->border.bottom = height;
+
+  rect.x = priv->pointing_to.x;
+  rect.y = priv->pointing_to.y + priv->pointing_to.height - priv->border.top;
+  rect.width = width;
+  rect.height = 1;
+
+  gtk_widget_translate_coordinates (gtk_widget_get_parent (widget),
+                                    gtk_widget_get_ancestor (widget, GTK_TYPE_WINDOW),
+                                    rect.x, rect.y, &rect.x, &rect.y);
 
-  G_OBJECT_CLASS (_gtk_text_handle_parent_class)->finalize (object);
+  if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
+    rect.x -= rect.width / 2;
+  else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
+            gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
+           (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
+            gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
+    rect.x -= rect.width;
+
+  /* The goal is to make the window 3 times as wide and high. The handle
+   * will be rendered in the center, making the rest an invisible border.
+   */
+  width += priv->border.left + priv->border.right;
+  height += priv->border.top + priv->border.bottom;
+  gtk_widget_set_size_request (widget, width, height);
+  gtk_widget_queue_allocate (widget);
+
+  layout = gdk_popup_layout_new (&rect,
+                                 GDK_GRAVITY_SOUTH,
+                                 GDK_GRAVITY_NORTH);
+  gdk_popup_layout_set_anchor_hints (layout,
+                                     GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE_X);
+
+  gdk_surface_present_popup (priv->surface,
+                             MAX (width, 1),
+                             MAX (height, 1),
+                             layout);
+  gdk_popup_layout_unref (layout);
 }
 
 static void
-gtk_text_handle_set_property (GObject      *object,
-                              guint         prop_id,
-                              const GValue *value,
-                              GParamSpec   *pspec)
+gtk_text_handle_map (GtkWidget *widget)
 {
-  GtkTextHandle *handle;
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  handle = GTK_TEXT_HANDLE (object);
+  GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->map (widget);
 
-  switch (prop_id)
-    {
-    case PROP_PARENT:
-      _gtk_text_handle_set_parent (handle, g_value_get_object (value));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
+  if (priv->has_point)
+    gtk_text_handle_present_surface (handle);
 }
 
 static void
-gtk_text_handle_get_property (GObject    *object,
-                              guint       prop_id,
-                              GValue     *value,
-                              GParamSpec *pspec)
+gtk_text_handle_unmap (GtkWidget *widget)
 {
-  GtkTextHandlePrivate *priv;
+  GtkTextHandle *handle = GTK_TEXT_HANDLE (widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  priv = GTK_TEXT_HANDLE (object)->priv;
-
-  switch (prop_id)
-    {
-    case PROP_PARENT:
-      g_value_set_object (value, priv->parent);
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-    }
+  GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unmap (widget);
+  gdk_surface_hide (priv->surface);
 }
 
 static void
-_gtk_text_handle_class_init (GtkTextHandleClass *klass)
+gtk_text_handle_class_init (GtkTextHandleClass *klass)
 {
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-  object_class->finalize = gtk_text_handle_finalize;
-  object_class->set_property = gtk_text_handle_set_property;
-  object_class->get_property = gtk_text_handle_get_property;
+  widget_class->snapshot = gtk_text_handle_snapshot;
+  widget_class->realize = gtk_text_handle_realize;
+  widget_class->unrealize = gtk_text_handle_unrealize;
+  widget_class->map = gtk_text_handle_map;
+  widget_class->unmap = gtk_text_handle_unmap;
 
   signals[HANDLE_DRAGGED] =
     g_signal_new (I_("handle-dragged"),
                  G_OBJECT_CLASS_TYPE (object_class),
-                 G_SIGNAL_RUN_LAST,
-                 G_STRUCT_OFFSET (GtkTextHandleClass, handle_dragged),
+                 G_SIGNAL_RUN_LAST, 0,
                  NULL, NULL,
-                 _gtk_marshal_VOID__ENUM_INT_INT,
-                 G_TYPE_NONE, 3,
-                  GTK_TYPE_TEXT_HANDLE_POSITION,
+                 _gtk_marshal_VOID__INT_INT,
+                 G_TYPE_NONE, 2,
                   G_TYPE_INT, G_TYPE_INT);
   signals[DRAG_STARTED] =
     g_signal_new (I_("drag-started"),
@@ -614,183 +320,199 @@ _gtk_text_handle_class_init (GtkTextHandleClass *klass)
                  G_SIGNAL_RUN_LAST, 0,
                  NULL, NULL,
                   NULL,
-                  G_TYPE_NONE, 1,
-                  GTK_TYPE_TEXT_HANDLE_POSITION);
+                  G_TYPE_NONE, 0, G_TYPE_NONE);
   signals[DRAG_FINISHED] =
     g_signal_new (I_("drag-finished"),
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST, 0,
                  NULL, NULL,
                   NULL,
-                  G_TYPE_NONE, 1,
-                  GTK_TYPE_TEXT_HANDLE_POSITION);
-
-  g_object_class_install_property (object_class,
-                                   PROP_PARENT,
-                                   g_param_spec_object ("parent",
-                                                        P_("Parent widget"),
-                                                        P_("Parent widget"),
-                                                        GTK_TYPE_WIDGET,
-                                                        GTK_PARAM_READWRITE |
-                                                        G_PARAM_CONSTRUCT_ONLY));
+                  G_TYPE_NONE, 0, G_TYPE_NONE);
 }
 
 static void
-_gtk_text_handle_init (GtkTextHandle *handle)
+handle_drag_begin (GtkGestureDrag *gesture,
+                   gdouble         x,
+                   gdouble         y,
+                   GtkTextHandle  *handle)
 {
-  handle->priv = _gtk_text_handle_get_instance_private (handle);
-}
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
+  GtkWidget *widget;
 
-GtkTextHandle *
-_gtk_text_handle_new (GtkWidget *parent)
-{
-  return g_object_new (GTK_TYPE_TEXT_HANDLE,
-                       "parent", parent,
-                       NULL);
+  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+
+  if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
+    x -= gtk_widget_get_width (widget) / 2;
+  else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
+            gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
+           (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
+            gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
+    x -= gtk_widget_get_width (widget);
+
+  y += priv->border.top / 2;
+
+  priv->dx = x;
+  priv->dy = y;
+  priv->dragged = TRUE;
+  g_signal_emit (handle, signals[DRAG_STARTED], 0);
 }
 
-void
-_gtk_text_handle_set_mode (GtkTextHandle     *handle,
-                           GtkTextHandleMode  mode)
+static void
+handle_drag_update (GtkGestureDrag *gesture,
+                    gdouble         offset_x,
+                    gdouble         offset_y,
+                    GtkWidget      *widget)
 {
-  GtkTextHandlePrivate *priv;
-  HandleWindow *start, *end;
+  GtkTextHandle *text_handle = GTK_TEXT_HANDLE (widget);
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (text_handle);
+  gdouble start_x, start_y;
+  gint x, y;
 
-  g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
+  gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
 
-  priv = handle->priv;
+  x = priv->pointing_to.x + priv->pointing_to.width / 2 +
+    start_x + offset_x - priv->dx;
+  y = priv->pointing_to.y + priv->pointing_to.height +
+    start_y + offset_y - priv->dy;
 
-  if (priv->mode == mode)
-    return;
+  if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
+    x -= gtk_widget_get_width (widget) / 2;
+  else if ((priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
+            gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
+           (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
+            gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
+    x -= gtk_widget_get_width (widget);
+
+  g_signal_emit (widget, signals[HANDLE_DRAGGED], 0, x, y);
+}
+
+static void
+handle_drag_end (GtkGestureDrag *gesture,
+                 gdouble         offset_x,
+                 gdouble         offset_y,
+                 GtkTextHandle  *handle)
+{
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  priv->mode = mode;
-  start = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_START];
-  end = &priv->windows[GTK_TEXT_HANDLE_POSITION_SELECTION_END];
+  g_signal_emit (handle, signals[DRAG_FINISHED], 0);
+  priv->dragged = FALSE;
+}
+
+static void
+gtk_text_handle_update_for_role (GtkTextHandle *handle)
+{
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
+  GtkWidget *widget = GTK_WIDGET (handle);
 
-  switch (mode)
+  if (priv->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
     {
-    case GTK_TEXT_HANDLE_MODE_CURSOR:
-      start->mode_visible = FALSE;
-      /* end = cursor */
-      end->mode_visible = TRUE;
-      break;
-    case GTK_TEXT_HANDLE_MODE_SELECTION:
-      start->mode_visible = TRUE;
-      end->mode_visible = TRUE;
-      break;
-    case GTK_TEXT_HANDLE_MODE_NONE:
-    default:
-      start->mode_visible = FALSE;
-      end->mode_visible = FALSE;
-      break;
+      gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_TOP);
+      gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM);
+      gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR);
     }
-
-  if (end->widget)
+  else if (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END)
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
-        gtk_style_context_add_class (gtk_widget_get_style_context (end->widget), 
GTK_STYLE_CLASS_INSERTION_CURSOR);
-      else
-        gtk_style_context_remove_class (gtk_widget_get_style_context (end->widget), 
GTK_STYLE_CLASS_INSERTION_CURSOR);
+      gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_TOP);
+      gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_BOTTOM);
+      gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR);
     }
+  else if (priv->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START)
+    {
+      gtk_widget_add_css_class (widget, GTK_STYLE_CLASS_TOP);
+      gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_BOTTOM);
+      gtk_widget_remove_css_class (widget, GTK_STYLE_CLASS_INSERTION_CURSOR);
+    }
+}
 
-  _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START);
-  _gtk_text_handle_update (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END);
-
-  if (start->widget && start->mode_visible)
-    gtk_widget_queue_draw (start->widget);
-  if (end->widget && end->mode_visible)
-    gtk_widget_queue_draw (end->widget);
+static void
+gtk_text_handle_init (GtkTextHandle *widget)
+{
+  GtkEventController *controller;
+
+  controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
+  g_signal_connect (controller, "drag-begin",
+                    G_CALLBACK (handle_drag_begin), widget);
+  g_signal_connect (controller, "drag-update",
+                    G_CALLBACK (handle_drag_update), widget);
+  g_signal_connect (controller, "drag-end",
+                    G_CALLBACK (handle_drag_end), widget);
+  gtk_widget_add_controller (GTK_WIDGET (widget), controller);
+
+  gtk_text_handle_update_for_role (GTK_TEXT_HANDLE (widget));
 }
 
-GtkTextHandleMode
-_gtk_text_handle_get_mode (GtkTextHandle *handle)
+GtkTextHandle *
+gtk_text_handle_new (GtkWidget *parent)
 {
-  GtkTextHandlePrivate *priv;
+  GtkWidget *handle;
 
-  g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_MODE_NONE);
+  handle = g_object_new (GTK_TYPE_TEXT_HANDLE,
+                         "css-name", I_("cursor-handle"),
+                         NULL);
+  gtk_widget_set_parent (handle, parent);
 
-  priv = handle->priv;
-  return priv->mode;
+  return GTK_TEXT_HANDLE (handle);
 }
 
 void
-_gtk_text_handle_set_position (GtkTextHandle         *handle,
-                               GtkTextHandlePosition  pos,
-                               GdkRectangle          *rect)
+gtk_text_handle_set_role (GtkTextHandle     *handle,
+                          GtkTextHandleRole  role)
 {
-  GtkTextHandlePrivate *priv;
-  HandleWindow *handle_window;
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
   g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
 
-  priv = handle->priv;
-  pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
-               GTK_TEXT_HANDLE_POSITION_SELECTION_START);
-  handle_window = &priv->windows[pos];
-
-  if (priv->mode == GTK_TEXT_HANDLE_MODE_NONE ||
-      (priv->mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
-       pos != GTK_TEXT_HANDLE_POSITION_CURSOR))
+  if (priv->role == role)
     return;
 
-  handle_window->pointing_to = *rect;
-  handle_window->has_point = TRUE;
+  priv->role = role;
+  gtk_text_handle_update_for_role (handle);
 
-  if (gtk_widget_is_visible (priv->parent))
-    _gtk_text_handle_update (handle, pos);
+  if (gtk_widget_get_visible (GTK_WIDGET (handle)))
+    {
+      gtk_widget_queue_draw (GTK_WIDGET (handle));
+      if (priv->has_point)
+        gtk_text_handle_present_surface (handle);
+    }
 }
 
-void
-_gtk_text_handle_set_visible (GtkTextHandle         *handle,
-                              GtkTextHandlePosition  pos,
-                              gboolean               visible)
+GtkTextHandleRole
+gtk_text_handle_get_role (GtkTextHandle *handle)
 {
-  GtkTextHandlePrivate *priv;
-
-  g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  priv = handle->priv;
-  pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
-               GTK_TEXT_HANDLE_POSITION_SELECTION_START);
+  g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), GTK_TEXT_HANDLE_ROLE_CURSOR);
 
-  priv->windows[pos].user_visible = visible;
-
-  if (gtk_widget_is_visible (priv->parent))
-    _gtk_text_handle_update (handle, pos);
+  return priv->role;
 }
 
-gboolean
-_gtk_text_handle_get_is_dragged (GtkTextHandle         *handle,
-                                 GtkTextHandlePosition  pos)
+void
+gtk_text_handle_set_position (GtkTextHandle      *handle,
+                              const GdkRectangle *rect)
 {
-  GtkTextHandlePrivate *priv;
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
+  g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
+
+  if (priv->pointing_to.x == rect->x &&
+      priv->pointing_to.y == rect->y &&
+      priv->pointing_to.width == rect->width &&
+      priv->pointing_to.height == rect->height)
+    return;
 
-  priv = handle->priv;
-  pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
-               GTK_TEXT_HANDLE_POSITION_SELECTION_START);
+  priv->pointing_to = *rect;
+  priv->has_point = TRUE;
 
-  return priv->windows[pos].dragged;
+  if (gtk_widget_is_visible (GTK_WIDGET (handle)))
+    gtk_text_handle_present_surface (handle);
 }
 
-void
-_gtk_text_handle_set_direction (GtkTextHandle         *handle,
-                                GtkTextHandlePosition  pos,
-                                GtkTextDirection       dir)
+gboolean
+gtk_text_handle_get_is_dragged (GtkTextHandle *handle)
 {
-  GtkTextHandlePrivate *priv;
+  GtkTextHandlePrivate *priv = gtk_text_handle_get_instance_private (handle);
 
-  g_return_if_fail (GTK_IS_TEXT_HANDLE (handle));
-
-  priv = handle->priv;
-  pos = CLAMP (pos, GTK_TEXT_HANDLE_POSITION_CURSOR,
-               GTK_TEXT_HANDLE_POSITION_SELECTION_START);
-  priv->windows[pos].dir = dir;
+  g_return_val_if_fail (GTK_IS_TEXT_HANDLE (handle), FALSE);
 
-  if (priv->windows[pos].widget)
-    {
-      gtk_widget_set_direction (priv->windows[pos].widget, dir);
-      _gtk_text_handle_update (handle, pos);
-    }
+  return priv->dragged;
 }
diff --git a/gtk/gtktexthandleprivate.h b/gtk/gtktexthandleprivate.h
index c79aa04cab..0a02eac12e 100644
--- a/gtk/gtktexthandleprivate.h
+++ b/gtk/gtktexthandleprivate.h
@@ -22,68 +22,27 @@
 
 G_BEGIN_DECLS
 
-#define GTK_TYPE_TEXT_HANDLE           (_gtk_text_handle_get_type ())
-#define GTK_TEXT_HANDLE(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_TEXT_HANDLE, 
GtkTextHandle))
-#define GTK_TEXT_HANDLE_CLASS(c)       (G_TYPE_CHECK_CLASS_CAST ((c), GTK_TYPE_TEXT_HANDLE, 
GtkTextHandleClass))
-#define GTK_IS_TEXT_HANDLE(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_TEXT_HANDLE))
-#define GTK_IS_TEXT_HANDLE_CLASS(o)    (G_TYPE_CHECK_CLASS_TYPE ((o), GTK_TYPE_TEXT_HANDLE))
-#define GTK_TEXT_HANDLE_GET_CLASS(o)   (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_TEXT_HANDLE, 
GtkTextHandleClass))
-
-typedef struct _GtkTextHandle GtkTextHandle;
-typedef struct _GtkTextHandleClass GtkTextHandleClass;
+#define GTK_TYPE_TEXT_HANDLE (gtk_text_handle_get_type ())
+G_DECLARE_FINAL_TYPE (GtkTextHandle, gtk_text_handle,
+                     GTK, TEXT_HANDLE, GtkWidget)
 
 typedef enum
 {
-  GTK_TEXT_HANDLE_POSITION_CURSOR,
-  GTK_TEXT_HANDLE_POSITION_SELECTION_START,
-  GTK_TEXT_HANDLE_POSITION_SELECTION_END = GTK_TEXT_HANDLE_POSITION_CURSOR
-} GtkTextHandlePosition;
-
-typedef enum
-{
-  GTK_TEXT_HANDLE_MODE_NONE,
-  GTK_TEXT_HANDLE_MODE_CURSOR,
-  GTK_TEXT_HANDLE_MODE_SELECTION
-} GtkTextHandleMode;
-
-struct _GtkTextHandle
-{
-  GObject parent_instance;
-  gpointer priv;
-};
-
-struct _GtkTextHandleClass
-{
-  GObjectClass parent_class;
-
-  void (* handle_dragged) (GtkTextHandle         *handle,
-                           GtkTextHandlePosition  pos,
-                           gint                   x,
-                           gint                   y);
-  void (* drag_finished)  (GtkTextHandle         *handle,
-                           GtkTextHandlePosition  pos);
-};
+  GTK_TEXT_HANDLE_ROLE_CURSOR,
+  GTK_TEXT_HANDLE_ROLE_SELECTION_START,
+  GTK_TEXT_HANDLE_ROLE_SELECTION_END,
+} GtkTextHandleRole;
 
-GType           _gtk_text_handle_get_type     (void) G_GNUC_CONST;
+GtkTextHandle *    gtk_text_handle_new          (GtkWidget             *parent);
 
-GtkTextHandle * _gtk_text_handle_new          (GtkWidget             *parent);
+void               gtk_text_handle_set_role (GtkTextHandle     *handle,
+                                            GtkTextHandleRole  role);
+GtkTextHandleRole  gtk_text_handle_get_role (GtkTextHandle     *handle);
 
-void            _gtk_text_handle_set_mode     (GtkTextHandle         *handle,
-                                               GtkTextHandleMode      mode);
-GtkTextHandleMode
-                _gtk_text_handle_get_mode     (GtkTextHandle         *handle);
-void            _gtk_text_handle_set_position (GtkTextHandle         *handle,
-                                               GtkTextHandlePosition  pos,
-                                               GdkRectangle          *rect);
-void            _gtk_text_handle_set_visible  (GtkTextHandle         *handle,
-                                               GtkTextHandlePosition  pos,
-                                               gboolean               visible);
+void               gtk_text_handle_set_position (GtkTextHandle      *handle,
+                                                const GdkRectangle *rect);
 
-gboolean        _gtk_text_handle_get_is_dragged (GtkTextHandle         *handle,
-                                                 GtkTextHandlePosition  pos);
-void            _gtk_text_handle_set_direction (GtkTextHandle         *handle,
-                                                GtkTextHandlePosition  pos,
-                                                GtkTextDirection       dir);
+gboolean           gtk_text_handle_get_is_dragged (GtkTextHandle *handle);
 
 G_END_DECLS
 
diff --git a/gtk/gtktextview.c b/gtk/gtktextview.c
index a2ae4cdd2e..437c9f90eb 100644
--- a/gtk/gtktextview.c
+++ b/gtk/gtktextview.c
@@ -148,6 +148,13 @@
 typedef struct _GtkTextWindow GtkTextWindow;
 typedef struct _GtkTextPendingScroll GtkTextPendingScroll;
 
+enum
+{
+  TEXT_HANDLE_CURSOR,
+  TEXT_HANDLE_SELECTION_BOUND,
+  TEXT_HANDLE_N_HANDLES
+};
+
 struct _GtkTextViewPrivate 
 {
   GtkTextLayout *layout;
@@ -160,7 +167,7 @@ struct _GtkTextViewPrivate
   gint dnd_x;
   gint dnd_y;
 
-  GtkTextHandle *text_handle;
+  GtkTextHandle *text_handles[TEXT_HANDLE_N_HANDLES];
   GtkWidget *selection_bubble;
   guint selection_bubble_timeout_id;
 
@@ -272,6 +279,8 @@ struct _GtkTextViewPrivate
 
   guint scroll_after_paste : 1;
 
+  guint text_handles_enabled : 1;
+
   /* GtkScrollablePolicy needs to be checked when
    * driving the scrollable adjustment values */
   guint hscroll_policy : 1;
@@ -565,18 +574,14 @@ static void update_node_ordering (GtkWidget    *widget);
 
 /* GtkTextHandle handlers */
 static void gtk_text_view_handle_drag_started  (GtkTextHandle         *handle,
-                                                GtkTextHandlePosition  pos,
                                                 GtkTextView           *text_view);
 static void gtk_text_view_handle_dragged       (GtkTextHandle         *handle,
-                                                GtkTextHandlePosition  pos,
                                                 gint                   x,
                                                 gint                   y,
                                                 GtkTextView           *text_view);
 static void gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
-                                                GtkTextHandlePosition  pos,
                                                 GtkTextView           *text_view);
-static void gtk_text_view_update_handles       (GtkTextView           *text_view,
-                                                GtkTextHandleMode      mode);
+static void gtk_text_view_update_handles       (GtkTextView           *text_view);
 
 static void gtk_text_view_selection_bubble_popup_unset (GtkTextView *text_view);
 static void gtk_text_view_selection_bubble_popup_set   (GtkTextView *text_view);
@@ -1607,6 +1612,24 @@ gtk_text_view_class_init (GtkTextViewClass *klass)
                                    gtk_text_view_activate_misc_insert_emoji);
 }
 
+static void
+_gtk_text_view_create_text_handles (GtkTextView *text_view)
+{
+  GtkTextViewPrivate *priv = text_view->priv;
+  int i;
+
+  for (i = 0; i < TEXT_HANDLE_N_HANDLES; i++)
+    {
+      priv->text_handles[i] = gtk_text_handle_new (GTK_WIDGET (text_view));
+      g_signal_connect (priv->text_handles[i], "drag-started",
+                        G_CALLBACK (gtk_text_view_handle_drag_started), text_view);
+      g_signal_connect (priv->text_handles[i], "handle-dragged",
+                        G_CALLBACK (gtk_text_view_handle_dragged), text_view);
+      g_signal_connect (priv->text_handles[i], "drag-finished",
+                        G_CALLBACK (gtk_text_view_handle_drag_finished), text_view);
+    }
+}
+
 static void
 gtk_text_view_init (GtkTextView *text_view)
 {
@@ -1715,6 +1738,8 @@ gtk_text_view_init (GtkTextView *text_view)
 
   gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.can-redo", FALSE);
   gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.can-undo", FALSE);
+
+  _gtk_text_view_create_text_handles (text_view);
 }
 
 GtkCssNode *
@@ -1729,23 +1754,6 @@ gtk_text_view_get_selection_node (GtkTextView *text_view)
   return text_view->priv->selection_node;
 }
 
-static void
-_gtk_text_view_ensure_text_handles (GtkTextView *text_view)
-{
-  GtkTextViewPrivate *priv = text_view->priv;
-
-  if (priv->text_handle)
-    return;
-
-  priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (text_view));
-  g_signal_connect (priv->text_handle, "drag-started",
-                    G_CALLBACK (gtk_text_view_handle_drag_started), text_view);
-  g_signal_connect (priv->text_handle, "handle-dragged",
-                    G_CALLBACK (gtk_text_view_handle_dragged), text_view);
-  g_signal_connect (priv->text_handle, "drag-finished",
-                    G_CALLBACK (gtk_text_view_handle_drag_finished), text_view);
-}
-
 static void
 _gtk_text_view_ensure_magnifier (GtkTextView *text_view)
 {
@@ -1924,8 +1932,7 @@ gtk_text_view_set_buffer (GtkTextView   *text_view,
          gtk_text_buffer_add_selection_clipboard (priv->buffer, clipboard);
        }
 
-      if (priv->text_handle)
-        gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_NONE);
+      gtk_text_view_update_handles (text_view);
 
       gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.undo", can_undo);
       gtk_widget_action_set_enabled (GTK_WIDGET (text_view), "text.redo", can_redo);
@@ -2550,9 +2557,7 @@ gtk_text_view_flush_scroll (GtkTextView *text_view)
                                           scroll->yalign,
                                           TRUE);
 
-  if (text_view->priv->text_handle)
-    gtk_text_view_update_handles (text_view,
-                                  _gtk_text_handle_get_mode (text_view->priv->text_handle));
+  gtk_text_view_update_handles (text_view);
 
   free_pending_scroll (scroll);
 
@@ -3632,8 +3637,8 @@ gtk_text_view_finalize (GObject *object)
 
   if (priv->magnifier_popover)
     gtk_widget_destroy (priv->magnifier_popover);
-  if (priv->text_handle)
-    g_object_unref (priv->text_handle);
+  g_clear_object (&priv->text_handles[TEXT_HANDLE_CURSOR]);
+  g_clear_object (&priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]);
   g_object_unref (priv->im_context);
 
   g_free (priv->im_module);
@@ -4323,6 +4328,12 @@ gtk_text_view_size_allocate (GtkWidget *widget,
 
   if (priv->popup_menu)
     gtk_native_check_resize (GTK_NATIVE (priv->popup_menu));
+
+  if (priv->text_handles[TEXT_HANDLE_CURSOR])
+    gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_CURSOR]));
+
+  if (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
+    gtk_native_check_resize (GTK_NATIVE (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
 }
 
 static void
@@ -4755,9 +4766,9 @@ _widget_to_text_surface_coords (GtkTextView *text_view,
 }
 
 static void
-gtk_text_view_set_handle_position (GtkTextView           *text_view,
-                                   GtkTextIter           *iter,
-                                   GtkTextHandlePosition  pos)
+gtk_text_view_set_handle_position (GtkTextView   *text_view,
+                                   GtkTextHandle *handle,
+                                   GtkTextIter   *iter)
 {
   GtkTextViewPrivate *priv;
   GdkRectangle rect;
@@ -4769,32 +4780,32 @@ gtk_text_view_set_handle_position (GtkTextView           *text_view,
   x = rect.x - priv->xoffset;
   y = rect.y - priv->yoffset;
 
-  if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
+  if (!gtk_text_handle_get_is_dragged (handle) &&
       (x < 0 || x > SCREEN_WIDTH (text_view) ||
        y < 0 || y > SCREEN_HEIGHT (text_view)))
     {
       /* Hide the handle if it's not being manipulated
        * and fell outside of the visible text area.
        */
-      _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE);
+      gtk_widget_hide (GTK_WIDGET (handle));
     }
   else
     {
       GtkTextDirection dir = GTK_TEXT_DIR_LTR;
       GtkTextAttributes attributes = { 0 };
 
-      _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
+      gtk_widget_show (GTK_WIDGET (handle));
 
       rect.x = CLAMP (x, 0, SCREEN_WIDTH (text_view));
       rect.y = CLAMP (y, 0, SCREEN_HEIGHT (text_view));
       _text_window_to_widget_coords (text_view, &rect.x, &rect.y);
 
-      _gtk_text_handle_set_position (priv->text_handle, pos, &rect);
+      gtk_text_handle_set_position (handle, &rect);
 
       if (gtk_text_iter_get_attributes (iter, &attributes))
         dir = attributes.direction;
 
-      _gtk_text_handle_set_direction (priv->text_handle, pos, dir);
+      gtk_widget_set_direction (GTK_WIDGET (handle), dir);
     }
 }
 
@@ -4843,23 +4854,17 @@ gtk_text_view_show_magnifier (GtkTextView *text_view,
 }
 
 static void
-gtk_text_view_handle_dragged (GtkTextHandle         *handle,
-                              GtkTextHandlePosition  pos,
-                              gint                   x,
-                              gint                   y,
-                              GtkTextView           *text_view)
+gtk_text_view_handle_dragged (GtkTextHandle *handle,
+                              gint           x,
+                              gint           y,
+                              GtkTextView   *text_view)
 {
   GtkTextViewPrivate *priv;
-  GtkTextIter old_cursor, old_bound;
-  GtkTextIter cursor, bound, iter;
-  GtkTextIter *min, *max;
-  GtkTextHandleMode mode;
+  GtkTextIter cursor, bound, iter, *old_iter;
   GtkTextBuffer *buffer;
-  GtkTextHandlePosition cursor_pos;
 
   priv = text_view->priv;
   buffer = get_buffer (text_view);
-  mode = _gtk_text_handle_get_mode (handle);
 
   _widget_to_text_surface_coords (text_view, &x, &y);
 
@@ -4867,93 +4872,81 @@ gtk_text_view_handle_dragged (GtkTextHandle         *handle,
   gtk_text_layout_get_iter_at_pixel (priv->layout, &iter,
                                      x + priv->xoffset,
                                      y + priv->yoffset);
-  gtk_text_buffer_get_iter_at_mark (buffer, &old_cursor,
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
                                     gtk_text_buffer_get_insert (buffer));
-  gtk_text_buffer_get_iter_at_mark (buffer, &old_bound,
+  gtk_text_buffer_get_iter_at_mark (buffer, &bound,
                                     gtk_text_buffer_get_selection_bound (buffer));
-  cursor = old_cursor;
-  bound = old_bound;
 
-  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR ||
-      gtk_text_iter_compare (&cursor, &bound) >= 0)
-    {
-      cursor_pos = GTK_TEXT_HANDLE_POSITION_CURSOR;
-      max = &cursor;
-      min = &bound;
-    }
-  else
-    {
-      cursor_pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
-      max = &bound;
-      min = &cursor;
-    }
 
-  if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
+  if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
-         gtk_text_iter_compare (&iter, min) <= 0)
+      /* Avoid running past the other handle in selection mode */
+      if (gtk_text_iter_compare (&iter, &bound) >= 0 &&
+          gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
         {
-          iter = *min;
-          gtk_text_iter_forward_char (&iter);
+          iter = bound;
+          gtk_text_iter_backward_char (&iter);
         }
 
-      *max = iter;
-      gtk_text_view_set_handle_position (text_view, &iter, pos);
+      old_iter = &cursor;
+      gtk_text_view_set_handle_position (text_view, handle, &iter);
     }
-  else
+  else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
-         gtk_text_iter_compare (&iter, max) >= 0)
+      /* Avoid running past the other handle */
+      if (gtk_text_iter_compare (&iter, &cursor) <= 0)
         {
-          iter = *max;
-          gtk_text_iter_backward_char (&iter);
+          iter = cursor;
+          gtk_text_iter_forward_char (&iter);
         }
 
-      *min = iter;
-      gtk_text_view_set_handle_position (text_view, &iter, pos);
+      old_iter = &bound;
+      gtk_text_view_set_handle_position (text_view, handle, &iter);
     }
+  else
+    g_assert_not_reached ();
 
-  if (gtk_text_iter_compare (&old_cursor, &cursor) != 0 ||
-      gtk_text_iter_compare (&old_bound, &bound) != 0)
+  if (gtk_text_iter_compare (&iter, old_iter) != 0)
     {
-      if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
+      *old_iter = iter;
+
+      if (handle == priv->text_handles[TEXT_HANDLE_CURSOR] &&
+          !gtk_widget_is_visible (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])))
         gtk_text_buffer_place_cursor (buffer, &cursor);
       else
         gtk_text_buffer_select_range (buffer, &cursor, &bound);
 
-      if (_gtk_text_handle_get_is_dragged (priv->text_handle, cursor_pos))
+      if (handle == priv->text_handles[TEXT_HANDLE_CURSOR])
         {
           text_view->priv->cursor_handle_dragged = TRUE;
           gtk_text_view_scroll_mark_onscreen (text_view,
                                               gtk_text_buffer_get_insert (buffer));
         }
-      else
+      else if (handle == priv->text_handles[TEXT_HANDLE_SELECTION_BOUND])
         {
           text_view->priv->selection_handle_dragged = TRUE;
           gtk_text_view_scroll_mark_onscreen (text_view,
                                               gtk_text_buffer_get_selection_bound (buffer));
         }
+
+      gtk_text_view_update_handles (text_view);
     }
 
-  if (_gtk_text_handle_get_is_dragged (priv->text_handle, cursor_pos))
-    gtk_text_view_show_magnifier (text_view, &cursor, x, y);
-  else
-    gtk_text_view_show_magnifier (text_view, &bound, x, y);
+  gtk_text_view_show_magnifier (text_view, &iter, x, y);
 }
 
 static void
-gtk_text_view_handle_drag_started (GtkTextHandle         *handle,
-                                   GtkTextHandlePosition  pos,
-                                   GtkTextView           *text_view)
+gtk_text_view_handle_drag_started (GtkTextHandle *handle,
+                                   GtkTextView   *text_view)
 {
   text_view->priv->cursor_handle_dragged = FALSE;
   text_view->priv->selection_handle_dragged = FALSE;
 }
 
 static void
-gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
-                                    GtkTextHandlePosition  pos,
-                                    GtkTextView           *text_view)
+gtk_text_view_handle_drag_finished (GtkTextHandle *handle,
+                                    GtkTextView   *text_view)
 {
   GtkTextViewPrivate *priv = text_view->priv;
 
@@ -4974,7 +4967,7 @@ gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
           extend_selection (text_view, SELECT_WORDS, &cursor, &start, &end);
           gtk_text_buffer_select_range (buffer, &start, &end);
 
-          gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
+          gtk_text_view_update_handles (text_view);
         }
       else
         gtk_text_view_selection_bubble_popup_set (text_view);
@@ -4987,52 +4980,61 @@ gtk_text_view_handle_drag_finished (GtkTextHandle         *handle,
 static gboolean cursor_visible (GtkTextView *text_view);
 
 static void
-gtk_text_view_update_handles (GtkTextView       *text_view,
-                              GtkTextHandleMode  mode)
+gtk_text_view_update_handles (GtkTextView *text_view)
 {
   GtkTextViewPrivate *priv = text_view->priv;
-  GtkTextIter cursor, bound, min, max;
+  GtkTextIter cursor, bound;
   GtkTextBuffer *buffer;
 
-  buffer = get_buffer (text_view);
-
-  gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
-                                    gtk_text_buffer_get_insert (buffer));
-  gtk_text_buffer_get_iter_at_mark (buffer, &bound,
-                                    gtk_text_buffer_get_selection_bound (buffer));
-
-  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION &&
-      gtk_text_iter_compare (&cursor, &bound) == 0)
-    {
-      mode = GTK_TEXT_HANDLE_MODE_CURSOR;
-    }
-
-  if (mode == GTK_TEXT_HANDLE_MODE_CURSOR &&
-      (!gtk_widget_is_sensitive (GTK_WIDGET (text_view)) || !cursor_visible (text_view)))
+  if (!priv->text_handles_enabled)
     {
-      mode = GTK_TEXT_HANDLE_MODE_NONE;
-    }
-
-  _gtk_text_handle_set_mode (priv->text_handle, mode);
-
-  if (gtk_text_iter_compare (&cursor, &bound) >= 0)
-    {
-      min = bound;
-      max = cursor;
+      gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
+      gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
     }
   else
     {
-      min = cursor;
-      max = bound;
-    }
+      buffer = get_buffer (text_view);
 
-  if (mode != GTK_TEXT_HANDLE_MODE_NONE)
-    gtk_text_view_set_handle_position (text_view, &max,
-                                       GTK_TEXT_HANDLE_POSITION_SELECTION_END);
+      gtk_text_buffer_get_iter_at_mark (buffer, &cursor,
+                                        gtk_text_buffer_get_insert (buffer));
+      gtk_text_buffer_get_iter_at_mark (buffer, &bound,
+                                        gtk_text_buffer_get_selection_bound (buffer));
 
-  if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
-    gtk_text_view_set_handle_position (text_view, &min,
-                                       GTK_TEXT_HANDLE_POSITION_SELECTION_START);
+      if (gtk_text_iter_compare (&cursor, &bound) == 0 && priv->editable)
+        {
+          /* Cursor mode */
+          gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
+
+          gtk_text_view_set_handle_position (text_view,
+                                             priv->text_handles[TEXT_HANDLE_CURSOR],
+                                             &cursor);
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR],
+                                    GTK_TEXT_HANDLE_ROLE_CURSOR);
+          gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
+        }
+      else if (gtk_text_iter_compare (&cursor, &bound) != 0)
+        {
+          /* Selection mode */
+          gtk_text_view_set_handle_position (text_view,
+                                             priv->text_handles[TEXT_HANDLE_CURSOR],
+                                             &cursor);
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_CURSOR],
+                                    GTK_TEXT_HANDLE_ROLE_SELECTION_START);
+          gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
+
+          gtk_text_view_set_handle_position (text_view,
+                                             priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
+                                             &bound);
+          gtk_text_handle_set_role (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND],
+                                    GTK_TEXT_HANDLE_ROLE_SELECTION_END);
+          gtk_widget_show (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
+        }
+      else
+        {
+          gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_CURSOR]));
+          gtk_widget_hide (GTK_WIDGET (priv->text_handles[TEXT_HANDLE_SELECTION_BOUND]));
+        }
+    }
 }
 
 static gboolean
@@ -5094,9 +5096,8 @@ gtk_text_view_key_controller_key_pressed (GtkEventControllerKey *controller,
   gtk_text_view_reset_blink_time (text_view);
   gtk_text_view_pend_cursor_blink (text_view);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
+  text_view->priv->text_handles_enabled = FALSE;
+  gtk_text_view_update_handles (text_view);
 
   gtk_text_view_selection_bubble_popup_unset (text_view);
 
@@ -5207,7 +5208,6 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
     }
   else if (button == GDK_BUTTON_PRIMARY)
     {
-      GtkTextHandleMode handle_mode = GTK_TEXT_HANDLE_MODE_NONE;
       gboolean extends = FALSE;
       GdkModifierType state;
 
@@ -5227,8 +5227,7 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
              */
             GtkTextIter start, end;
 
-            if (is_touchscreen)
-              handle_mode = GTK_TEXT_HANDLE_MODE_CURSOR;
+            priv->text_handles_enabled = is_touchscreen;
 
             get_iter_from_gesture (text_view, GTK_GESTURE (gesture),
                                    &iter, NULL, NULL);
@@ -5243,12 +5242,11 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
                        !gtk_widget_get_visible (priv->selection_bubble))
                       {
                         gtk_text_view_selection_bubble_popup_set (text_view);
-                        handle_mode = GTK_TEXT_HANDLE_MODE_NONE;
+                        priv->text_handles_enabled = FALSE;
                       }
                     else
                       {
                         gtk_text_view_selection_bubble_popup_unset (text_view);
-                        handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION;
                       }
                   }
                 else
@@ -5278,11 +5276,6 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
           }
         case 2:
         case 3:
-          if (is_touchscreen)
-            {
-              handle_mode = GTK_TEXT_HANDLE_MODE_SELECTION;
-              break;
-            }
           gtk_text_view_end_selection_drag (text_view);
 
           get_iter_from_gesture (text_view, GTK_GESTURE (gesture),
@@ -5295,8 +5288,7 @@ gtk_text_view_click_gesture_pressed (GtkGestureClick *gesture,
           break;
         }
 
-      _gtk_text_view_ensure_text_handles (text_view);
-      gtk_text_view_update_handles (text_view, handle_mode);
+      gtk_text_view_update_handles (text_view);
     }
 
   if (n_press >= 3)
@@ -5363,9 +5355,8 @@ gtk_text_view_focus_out (GtkWidget *widget)
                                         text_view);
   gtk_text_view_selection_bubble_popup_unset (text_view);
 
-  if (priv->text_handle)
-    _gtk_text_handle_set_mode (priv->text_handle,
-                               GTK_TEXT_HANDLE_MODE_NONE);
+  text_view->priv->text_handles_enabled = FALSE;
+  gtk_text_view_update_handles (text_view);
 
   if (priv->editable)
     {
@@ -6735,11 +6726,8 @@ gtk_text_view_buffer_changed_handler (GtkTextBuffer *buffer,
                                       gpointer       data)
 {
   GtkTextView *text_view = data;
-  GtkTextViewPrivate *priv = text_view->priv;
 
-  if (priv->text_handle)
-    gtk_text_view_update_handles (text_view,
-                                  _gtk_text_handle_get_mode (priv->text_handle));
+  gtk_text_view_update_handles (text_view);
 }
 
 static void
@@ -7237,8 +7225,8 @@ gtk_text_view_drag_gesture_update (GtkGestureDrag *gesture,
 
   if (is_touchscreen)
     {
-      _gtk_text_view_ensure_text_handles (text_view);
-      gtk_text_view_update_handles (text_view, GTK_TEXT_HANDLE_MODE_SELECTION);
+      text_view->priv->text_handles_enabled = TRUE;
+      gtk_text_view_update_handles (text_view);
       gtk_text_view_show_magnifier (text_view, &cursor, x, y);
     }
 }
@@ -7287,7 +7275,6 @@ gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture,
   if (!is_touchscreen && clicked_in_selection &&
       !gtk_drag_check_threshold (GTK_WIDGET (text_view), start_x, start_y, x, y))
     {
-      GtkTextHandleMode mode = GTK_TEXT_HANDLE_MODE_NONE;
       GtkTextIter iter;
 
       /* Unselect everything; we clicked inside selection, but
@@ -7300,13 +7287,7 @@ gtk_text_view_drag_gesture_end (GtkGestureDrag *gesture,
       gtk_text_buffer_place_cursor (get_buffer (text_view), &iter);
       gtk_text_view_check_cursor_blink (text_view);
 
-      if (priv->text_handle)
-        {
-          if (is_touchscreen)
-            mode = GTK_TEXT_HANDLE_MODE_CURSOR;
-
-          gtk_text_view_update_handles (text_view, mode);
-        }
+      gtk_text_view_update_handles (text_view);
     }
 }
 
@@ -8145,9 +8126,7 @@ gtk_text_view_value_changed (GtkAdjustment *adjustment,
    */
   gtk_text_view_update_im_spot_location (text_view);
 
-  if (priv->text_handle)
-    gtk_text_view_update_handles (text_view,
-                                  _gtk_text_handle_get_mode (priv->text_handle));
+  gtk_text_view_update_handles (text_view);
 
   if (priv->anchored_children.length > 0)
     gtk_widget_queue_allocate (GTK_WIDGET (text_view));
@@ -8336,9 +8315,7 @@ gtk_text_view_mark_set_handler (GtkTextBuffer     *buffer,
   if (need_reset)
     {
       gtk_text_view_reset_im_context (text_view);
-      if (text_view->priv->text_handle)
-        gtk_text_view_update_handles (text_view,
-                                      _gtk_text_handle_get_mode (text_view->priv->text_handle));
+      gtk_text_view_update_handles (text_view);
 
       has_selection = gtk_text_buffer_get_selection_bounds (get_buffer (text_view), NULL, NULL);
       gtk_css_node_set_visible (text_view->priv->selection_node, has_selection);
@@ -8748,21 +8725,10 @@ show_or_hide_handles (GtkWidget   *popover,
                       GtkTextView *text_view)
 {
   gboolean visible;
-  GtkTextHandle *handle;
-  GtkTextHandleMode mode;
 
   visible = gtk_widget_get_visible (popover);
-
-  handle = text_view->priv->text_handle;
-  mode = _gtk_text_handle_get_mode (handle);
-
-  if (!visible)
-    gtk_text_view_update_handles (text_view, mode);
-  else
-    {
-      _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_START, FALSE);
-      _gtk_text_handle_set_visible (handle, GTK_TEXT_HANDLE_POSITION_SELECTION_END, FALSE);
-    }
+  text_view->priv->text_handles_enabled = !visible;
+  gtk_text_view_update_handles (text_view);
 }
 
 static void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]