[gtk/wip/carlosg/for-master: 2/6] gtk/texthandle: Handle events on parent surface's native




commit 9b9cc2f947d553e2283deab21b3b17dd2207d6b8
Author: Carlos Garnacho <carlosg gnome org>
Date:   Thu Nov 5 15:11:09 2020 +0100

    gtk/texthandle: Handle events on parent surface's native
    
    In wayland, popup positioning and event handling are doubly async.
    This makes it unreliable to figure out parent surface coordinates
    out of the popup position and the events received. This results in
    jumpy text handles there.
    
    The best way to deal with parent surface coordinates is to handle
    the events there. Make the handles transparent to input, and make
    the drag gesture be set up on the parent widget's native.
    
    The gesture is set up in the capture phase, setting it on the native
    (as opposed to the parent widget) achieves a feeling similar to it
    being a distinct surface, as it should take precedence over other
    gestures in the emission chain (e.g. scrolledwindows).
    
    As everything is in parent widget's native's coordinates, the drag
    handles become smooth again.

 gtk/gtktexthandle.c | 164 ++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 114 insertions(+), 50 deletions(-)
---
diff --git a/gtk/gtktexthandle.c b/gtk/gtktexthandle.c
index b5b645a7c9..b900d699eb 100644
--- a/gtk/gtktexthandle.c
+++ b/gtk/gtktexthandle.c
@@ -44,9 +44,10 @@ struct _GtkTextHandle
 {
   GtkWidget parent_instance;
 
-  GtkWidget *parent;
   GdkSurface *surface;
   GskRenderer *renderer;
+  GtkEventController *controller;
+  GtkWidget *controller_widget;
 
   GdkRectangle pointing_to;
   GtkBorder border;
@@ -61,6 +62,19 @@ struct _GtkTextHandle
 
 static void gtk_text_handle_native_interface_init (GtkNativeInterface *iface);
 
+static void handle_drag_begin (GtkGestureDrag *gesture,
+                               double          x,
+                               double          y,
+                               GtkTextHandle  *handle);
+static void handle_drag_update (GtkGestureDrag *gesture,
+                                double          offset_x,
+                                double          offset_y,
+                                GtkWidget      *widget);
+static void handle_drag_end (GtkGestureDrag *gesture,
+                             double          offset_x,
+                             double          offset_y,
+                             GtkTextHandle  *handle);
+
 G_DEFINE_TYPE_WITH_CODE (GtkTextHandle, gtk_text_handle, GTK_TYPE_WIDGET,
                          G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE,
                                                 gtk_text_handle_native_interface_init))
@@ -185,15 +199,6 @@ surface_render (GdkSurface     *surface,
   return TRUE;
 }
 
-static gboolean
-surface_event (GdkSurface    *surface,
-               GdkEvent      *event,
-               GtkTextHandle *handle)
-{
-  gtk_main_do_event (event);
-  return TRUE;
-}
-
 static void
 surface_mapped_changed (GtkWidget *widget)
 {
@@ -226,11 +231,11 @@ gtk_text_handle_realize (GtkWidget *widget)
 
   handle->surface = gdk_surface_new_popup (parent_surface, FALSE);
   gdk_surface_set_widget (handle->surface, widget);
+  gdk_surface_set_input_region (handle->surface, cairo_region_create ());
 
   g_signal_connect_swapped (handle->surface, "notify::mapped",
                             G_CALLBACK (surface_mapped_changed), widget);
   g_signal_connect (handle->surface, "render", G_CALLBACK (surface_render), widget);
-  g_signal_connect (handle->surface, "event", G_CALLBACK (surface_event), widget);
 
   GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->realize (widget);
 
@@ -248,7 +253,6 @@ gtk_text_handle_unrealize (GtkWidget *widget)
   g_clear_object (&handle->renderer);
 
   g_signal_handlers_disconnect_by_func (handle->surface, surface_render, widget);
-  g_signal_handlers_disconnect_by_func (handle->surface, surface_event, widget);
   g_signal_handlers_disconnect_by_func (handle->surface, surface_mapped_changed, widget);
 
   gdk_surface_set_widget (handle->surface, NULL);
@@ -256,6 +260,28 @@ gtk_text_handle_unrealize (GtkWidget *widget)
   g_clear_object (&handle->surface);
 }
 
+static void
+text_handle_set_up_gesture (GtkTextHandle *handle)
+{
+  GtkNative *native;
+
+  /* The drag gesture is hooked on the parent native */
+  native = gtk_widget_get_native (gtk_widget_get_parent (GTK_WIDGET (handle)));
+  handle->controller_widget = GTK_WIDGET (native);
+
+  handle->controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
+  gtk_event_controller_set_propagation_phase (handle->controller,
+                                              GTK_PHASE_CAPTURE);
+  g_signal_connect (handle->controller, "drag-begin",
+                    G_CALLBACK (handle_drag_begin), handle);
+  g_signal_connect (handle->controller, "drag-update",
+                    G_CALLBACK (handle_drag_update), handle);
+  g_signal_connect (handle->controller, "drag-end",
+                    G_CALLBACK (handle_drag_end), handle);
+
+  gtk_widget_add_controller (handle->controller_widget, handle->controller);
+}
+
 static void
 gtk_text_handle_map (GtkWidget *widget)
 {
@@ -264,7 +290,10 @@ gtk_text_handle_map (GtkWidget *widget)
   GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->map (widget);
 
   if (handle->has_point)
-    gtk_text_handle_present_surface (handle);
+    {
+      gtk_text_handle_present_surface (handle);
+      text_handle_set_up_gesture (handle);
+    }
 }
 
 static void
@@ -274,6 +303,14 @@ gtk_text_handle_unmap (GtkWidget *widget)
 
   GTK_WIDGET_CLASS (gtk_text_handle_parent_class)->unmap (widget);
   gdk_surface_hide (handle->surface);
+
+  if (handle->controller_widget)
+    {
+      gtk_widget_remove_controller (handle->controller_widget,
+                                    handle->controller);
+      handle->controller_widget = NULL;
+      handle->controller = NULL;
+    }
 }
 
 static void
@@ -330,26 +367,67 @@ gtk_text_handle_class_init (GtkTextHandleClass *klass)
   gtk_widget_class_set_css_name (widget_class, I_("cursor-handle"));
 }
 
+/* Relative to pointing_to x/y */
 static void
-handle_drag_begin (GtkGestureDrag *gesture,
-                   double          x,
-                   double          y,
-                   GtkTextHandle  *handle)
+handle_get_input_extents (GtkTextHandle *handle,
+                          GtkBorder     *border)
 {
-  GtkWidget *widget;
-
-  widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
+  GtkWidget *widget = GTK_WIDGET (handle);
 
   if (handle->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
-    x -= gtk_widget_get_width (widget) / 2;
+    {
+      border->left = (-gtk_widget_get_width (widget) / 2) - handle->border.left;
+      border->right = (gtk_widget_get_width (widget) / 2) + handle->border.right;
+    }
   else if ((handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
             gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
            (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
             gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
-    x -= gtk_widget_get_width (widget);
+    {
+      border->left = -gtk_widget_get_width (widget) - handle->border.left;
+      border->right = handle->border.right;
+    }
+  else
+    {
+      border->left = -handle->border.left;
+      border->right = gtk_widget_get_width (widget) + handle->border.right;
+    }
 
-  y += handle->border.top / 2;
+  border->top = - handle->border.top;
+  border->bottom = gtk_widget_get_height (widget) + handle->border.bottom;
+}
+
+static void
+handle_drag_begin (GtkGestureDrag *gesture,
+                   double          x,
+                   double          y,
+                   GtkTextHandle  *handle)
+{
+  GtkBorder input_extents;
+  double widget_x, widget_y;
+
+  x -= handle->pointing_to.x;
+  y -= handle->pointing_to.y;
+
+  /* Figure out if the coordinates fall into the handle input area, coordinates
+   * are relative to the parent widget.
+   */
+  handle_get_input_extents (handle, &input_extents);
+  gtk_widget_translate_coordinates (handle->controller_widget,
+                                    gtk_widget_get_parent (GTK_WIDGET (handle)),
+                                    x, y, &widget_x, &widget_y);
+
+  if (widget_x < input_extents.left || widget_x >= input_extents.right ||
+      widget_y < input_extents.top || widget_y >= input_extents.bottom)
+    {
+      gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
 
+  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
+  /* Store untranslated coordinates here, so ::update does not need
+   * an extra translation
+   */
   handle->dx = x;
   handle->dy = y;
   handle->dragged = TRUE;
@@ -368,19 +446,8 @@ handle_drag_update (GtkGestureDrag *gesture,
 
   gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
 
-  x = handle->pointing_to.x + handle->pointing_to.width / 2 +
-    start_x + offset_x - handle->dx;
-  y = handle->pointing_to.y + handle->pointing_to.height +
-    start_y + offset_y - handle->dy;
-
-  if (handle->role == GTK_TEXT_HANDLE_ROLE_CURSOR)
-    x -= gtk_widget_get_width (widget) / 2;
-  else if ((handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_END &&
-            gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) ||
-           (handle->role == GTK_TEXT_HANDLE_ROLE_SELECTION_START &&
-            gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL))
-    x -= gtk_widget_get_width (widget);
-
+  x = start_x + offset_x - handle->dx;
+  y = start_y + offset_y - handle->dy;
   g_signal_emit (widget, signals[HANDLE_DRAGGED], 0, x, y);
 }
 
@@ -390,7 +457,15 @@ handle_drag_end (GtkGestureDrag *gesture,
                  double          offset_y,
                  GtkTextHandle  *handle)
 {
-  g_signal_emit (handle, signals[DRAG_FINISHED], 0);
+  GdkEventSequence *sequence;
+  GtkEventSequenceState state;
+
+  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
+  state = gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence);
+
+  if (state == GTK_EVENT_SEQUENCE_CLAIMED)
+    g_signal_emit (handle, signals[DRAG_FINISHED], 0);
+
   handle->dragged = FALSE;
 }
 
@@ -422,20 +497,9 @@ gtk_text_handle_update_for_role (GtkTextHandle *handle)
 }
 
 static void
-gtk_text_handle_init (GtkTextHandle *widget)
+gtk_text_handle_init (GtkTextHandle *handle)
 {
-  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));
+  gtk_text_handle_update_for_role (handle);
 }
 
 GtkTextHandle *


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