gtkeventcontrollerscroll: Handle hold gestures
authorJosé Expósito <jose.exposito89@gmail.com>
Mon, 28 Jun 2021 15:48:04 +0000 (17:48 +0200)
committerCarlos Garnacho <carlosg@gnome.org>
Wed, 26 Jan 2022 21:49:53 +0000 (22:49 +0100)
Handle hold events:

 - GDK_TOUCHPAD_GESTURE_PHASE_BEGIN: scroll-begin is emitted.
 - GDK_TOUCHPAD_GESTURE_PHASE_END: A hold gesture ends only when all
   fingers are lifted from the touchpad without movement, so
   scroll-end is emitted right away.
 - GDK_TOUCHPAD_GESTURE_PHASE_CANCEL: A hold gesture is cancelled when
   some fingers are lifted/put down or movement is detected. In this
   case, scroll-end is emitted after a small timeout only if
   GDK_SCROLL wasn't detected.

Part-of: <!3454>

gtk/gtkeventcontrollerscroll.c

index e0acee7a2ef419da995d46568b06f597e821cf8f..67dafb73edf80825a9657f39ac4536337ca95175 100644 (file)
@@ -66,6 +66,7 @@
 #include "gtkprivate.h"
 
 #define SCROLL_CAPTURE_THRESHOLD_MS 150
+#define HOLD_TIMEOUT_MS 50
 
 typedef struct
 {
@@ -84,6 +85,7 @@ struct _GtkEventControllerScroll
   double cur_dx;
   double cur_dy;
 
+  guint hold_timeout_id;
   guint active : 1;
 };
 
@@ -193,6 +195,7 @@ gtk_event_controller_scroll_finalize (GObject *object)
   GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (object);
 
   g_array_unref (scroll->scroll_history);
+  g_clear_handle_id (&scroll->hold_timeout_id, g_source_remove);
 
   G_OBJECT_CLASS (gtk_event_controller_scroll_parent_class)->finalize (object);
 }
@@ -272,6 +275,70 @@ gtk_event_controller_scroll_end (GtkEventController *controller)
   return TRUE;
 }
 
+static gboolean
+gtk_event_controller_scroll_hold_timeout (gpointer user_data)
+{
+  GtkEventController *controller;
+  GtkEventControllerScroll *scroll;
+
+  controller = user_data;
+  scroll = GTK_EVENT_CONTROLLER_SCROLL (controller);
+
+  gtk_event_controller_scroll_end (controller);
+  scroll->hold_timeout_id = 0;
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+gtk_event_controller_scroll_handle_hold_event (GtkEventController *controller,
+                                               GdkEvent           *event)
+{
+  GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (controller);
+  gboolean handled = GDK_EVENT_PROPAGATE;
+  GdkTouchpadGesturePhase phase;
+  guint n_fingers = 0;
+
+  if (gdk_event_get_event_type (event) != GDK_TOUCHPAD_HOLD)
+    return handled;
+
+  n_fingers = gdk_touchpad_event_get_n_fingers (event);
+  if (n_fingers != 1 && n_fingers != 2)
+    return handled;
+
+  if (scroll->hold_timeout_id != 0)
+    return handled;
+
+  phase = gdk_touchpad_event_get_gesture_phase (event);
+
+  switch (phase)
+    {
+    case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
+      handled = gtk_event_controller_scroll_begin (controller);
+      break;
+
+    case GDK_TOUCHPAD_GESTURE_PHASE_END:
+      handled = gtk_event_controller_scroll_end (controller);
+      break;
+
+    case GDK_TOUCHPAD_GESTURE_PHASE_CANCEL:
+      if (scroll->hold_timeout_id == 0)
+        {
+          scroll->hold_timeout_id =
+              g_timeout_add (HOLD_TIMEOUT_MS,
+                             gtk_event_controller_scroll_hold_timeout,
+                             controller);
+        }
+      break;
+
+    case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
+    default:
+      break;
+    }
+
+  return handled;
+}
+
 static gboolean
 gtk_event_controller_scroll_handle_event (GtkEventController *controller,
                                           GdkEvent           *event,
@@ -282,14 +349,22 @@ gtk_event_controller_scroll_handle_event (GtkEventController *controller,
   GdkScrollDirection direction = GDK_SCROLL_SMOOTH;
   double dx = 0, dy = 0;
   gboolean handled = GDK_EVENT_PROPAGATE;
+  GdkEventType event_type;
+
+  event_type = gdk_event_get_event_type (event);
 
-  if (gdk_event_get_event_type (event) != GDK_SCROLL)
+  if (event_type == GDK_TOUCHPAD_HOLD)
+    return gtk_event_controller_scroll_handle_hold_event (controller, event);
+
+  if (event_type != GDK_SCROLL)
     return FALSE;
 
   if ((scroll->flags & (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
                         GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL)) == 0)
     return FALSE;
 
+  g_clear_handle_id (&scroll->hold_timeout_id, g_source_remove);
+
   /* FIXME: Handle device changes */
   direction = gdk_scroll_event_get_direction (event);
   if (direction == GDK_SCROLL_SMOOTH)
@@ -484,6 +559,7 @@ gtk_event_controller_scroll_init (GtkEventControllerScroll *scroll)
 {
   scroll->scroll_history = g_array_new (FALSE, FALSE,
                                         sizeof (ScrollHistoryElem));
+  scroll->hold_timeout_id = 0;
 }
 
 /**