[gtk+/wip/carlosg/event-delivery-cleanups: 9/30] gtk: Add GtkEventControllerScroll



commit 654cefacced83af328ec044f3fa86f2e48b1b926
Author: Carlos Garnacho <carlosg gnome org>
Date:   Fri Sep 15 13:22:57 2017 +0200

    gtk: Add GtkEventControllerScroll
    
    This is a GtkEventController implementation to handle mouse
    scrolling. It handles both smooth and discrete events and
    offers a way for callers to tell their preference too, so
    smooth events shall be accumulated and coalesced on request.
    
    On capable devices, it can also emit ::scroll-begin and
    ::scroll-end enclosing all ::scroll events for a scroll
    operation.
    
    It also has builtin kinetic scrolling capabilities, reporting
    the initial velocity for both axes after ::scroll-end if
    requested.

 gtk/gtk.h                      |    1 +
 gtk/gtkeventcontrollerscroll.c |  401 ++++++++++++++++++++++++++++++++++++++++
 gtk/gtkeventcontrollerscroll.h |   66 +++++++
 gtk/meson.build                |    2 +
 4 files changed, 470 insertions(+), 0 deletions(-)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index eb31ee2..3af47c8 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -94,6 +94,7 @@
 #include <gtk/gtkentrycompletion.h>
 #include <gtk/gtkenums.h>
 #include <gtk/gtkeventcontroller.h>
+#include <gtk/gtkeventcontrollerscroll.h>
 #include <gtk/gtkexpander.h>
 #include <gtk/gtkfixed.h>
 #include <gtk/gtkfilechooser.h>
diff --git a/gtk/gtkeventcontrollerscroll.c b/gtk/gtkeventcontrollerscroll.c
new file mode 100644
index 0000000..61f9167
--- /dev/null
+++ b/gtk/gtkeventcontrollerscroll.c
@@ -0,0 +1,401 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s): Carlos Garnacho <carlosg gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkintl.h"
+#include "gtkwidget.h"
+#include "gtkeventcontrollerprivate.h"
+#include "gtkeventcontrollerscroll.h"
+#include "gtktypebuiltins.h"
+#include "gtkmarshalers.h"
+
+#define SCROLL_CAPTURE_THRESHOLD_MS 150
+
+typedef struct
+{
+  gdouble dx;
+  gdouble dy;
+  guint32 evtime;
+} ScrollHistoryElem;
+
+struct _GtkEventControllerScroll
+{
+  GtkEventController parent_instance;
+  GtkEventControllerScrollFlags flags;
+  GArray *scroll_history;
+
+  /* For discrete event coalescing */
+  gdouble cur_dx;
+  gdouble cur_dy;
+
+  guint active : 1;
+};
+
+struct _GtkEventControllerScrollClass
+{
+  GtkEventControllerClass parent_class;
+};
+
+enum {
+  SCROLL_BEGIN,
+  SCROLL,
+  SCROLL_END,
+  DECELERATE,
+  N_SIGNALS
+};
+
+enum {
+  PROP_0,
+  PROP_FLAGS,
+  N_PROPS
+};
+
+static GParamSpec *pspecs[N_PROPS] = { NULL };
+static guint signals[N_SIGNALS] = { 0 };
+
+G_DEFINE_TYPE (GtkEventControllerScroll, gtk_event_controller_scroll,
+               GTK_TYPE_EVENT_CONTROLLER)
+
+static void
+scroll_history_push (GtkEventControllerScroll *scroll,
+                     gdouble                   delta_x,
+                     gdouble                   delta_y,
+                     guint32                   evtime)
+{
+  ScrollHistoryElem new_item;
+  guint i;
+
+  for (i = 0; i < scroll->scroll_history->len; i++)
+    {
+      ScrollHistoryElem *elem;
+
+      elem = &g_array_index (scroll->scroll_history, ScrollHistoryElem, i);
+
+      if (elem->evtime >= evtime - SCROLL_CAPTURE_THRESHOLD_MS)
+        break;
+    }
+
+  if (i > 0)
+    g_array_remove_range (scroll->scroll_history, 0, i);
+
+  new_item.dx = delta_x;
+  new_item.dy = delta_y;
+  new_item.evtime = evtime;
+  g_array_append_val (scroll->scroll_history, new_item);
+}
+
+static void
+scroll_history_reset (GtkEventControllerScroll *scroll)
+{
+  if (scroll->scroll_history->len == 0)
+    return;
+
+  g_array_remove_range (scroll->scroll_history, 0,
+                        scroll->scroll_history->len);
+}
+
+static void
+scroll_history_finish (GtkEventControllerScroll *scroll,
+                       gdouble                  *velocity_x,
+                       gdouble                  *velocity_y)
+{
+  gdouble accum_dx = 0, accum_dy = 0;
+  guint32 first = 0, last = 0;
+  guint i;
+
+  *velocity_x = 0;
+  *velocity_y = 0;
+
+  if (scroll->scroll_history->len == 0)
+    return;
+
+  for (i = 0; i < scroll->scroll_history->len; i++)
+    {
+      ScrollHistoryElem *elem;
+
+      elem = &g_array_index (scroll->scroll_history, ScrollHistoryElem, i);
+      accum_dx += elem->dx;
+      accum_dy += elem->dy;
+      last = elem->evtime;
+
+      if (i == 0)
+        first = elem->evtime;
+    }
+
+  if (last != first)
+    {
+      *velocity_x = (accum_dx * 1000) / (last - first);
+      *velocity_y = (accum_dy * 1000) / (last - first);
+    }
+
+  scroll_history_reset (scroll);
+}
+
+static void
+gtk_event_controller_scroll_finalize (GObject *object)
+{
+  GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (object);
+
+  g_array_unref (scroll->scroll_history);
+
+  G_OBJECT_CLASS (gtk_event_controller_scroll_parent_class)->finalize (object);
+}
+
+static void
+gtk_event_controller_scroll_set_property (GObject      *object,
+                                          guint         prop_id,
+                                          const GValue *value,
+                                          GParamSpec   *pspec)
+{
+  GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      scroll->flags = g_value_get_flags (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_event_controller_scroll_get_property (GObject    *object,
+                                          guint       prop_id,
+                                          GValue     *value,
+                                          GParamSpec *pspec)
+{
+  GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (object);
+
+  switch (prop_id)
+    {
+    case PROP_FLAGS:
+      g_value_set_flags (value, scroll->flags);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static gboolean
+gtk_event_controller_scroll_handle_event (GtkEventController *controller,
+                                          const GdkEvent     *event)
+{
+  GtkEventControllerScroll *scroll = GTK_EVENT_CONTROLLER_SCROLL (controller);
+  GdkScrollDirection direction = GDK_SCROLL_SMOOTH;
+  gdouble dx = 0, dy = 0;
+
+  if (gdk_event_get_event_type (event) != GDK_SCROLL)
+    return FALSE;
+  if ((scroll->flags & (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL |
+                        GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL)) == 0)
+    return FALSE;
+
+  /* FIXME: Handle device changes */
+
+  if (gdk_event_get_scroll_deltas (event, &dx, &dy))
+    {
+      GdkDevice *device = gdk_event_get_source_device (event);
+      GdkInputSource input_source = gdk_device_get_source (device);
+
+      if (!scroll->active &&
+          (input_source == GDK_SOURCE_TRACKPOINT ||
+           input_source == GDK_SOURCE_TOUCHPAD))
+        {
+          g_signal_emit (controller, signals[SCROLL_BEGIN], 0);
+          scroll_history_reset (scroll);
+          scroll->active = TRUE;
+        }
+
+      if ((scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_VERTICAL) == 0)
+        dy = 0;
+      if ((scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL) == 0)
+        dx = 0;
+
+      if (scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_DISCRETE)
+        {
+          gint steps;
+
+          scroll->cur_dx += dx;
+          scroll->cur_dy += dy;
+          dx = dy = 0;
+
+          if (ABS (scroll->cur_dx) > 1)
+            {
+              steps = trunc (scroll->cur_dx);
+              scroll->cur_dx -= steps;
+              dx = steps;
+            }
+
+          if (ABS (scroll->cur_dy) > 1)
+            {
+              steps = trunc (scroll->cur_dy);
+              scroll->cur_dy -= steps;
+              dy = steps;
+            }
+        }
+    }
+  else if (gdk_event_get_scroll_direction (event, &direction))
+    {
+      switch (direction)
+        {
+        case GDK_SCROLL_UP:
+          dy -= 1;
+          break;
+        case GDK_SCROLL_DOWN:
+          dy += 1;
+          break;
+        case GDK_SCROLL_LEFT:
+          dx -= 1;
+          break;
+        case GDK_SCROLL_RIGHT:
+          dx += 1;
+          break;
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+
+      if ((scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_VERTICAL) == 0)
+        dy = 0;
+      if ((scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL) == 0)
+        dx = 0;
+    }
+
+  if (dx != 0 || dy != 0)
+    {
+      g_signal_emit (controller, signals[SCROLL], 0, dx, dy);
+
+      if (scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_KINETIC)
+        scroll_history_push (scroll, dx, dy, gdk_event_get_time (event));
+    }
+
+  if (scroll->active && gdk_event_is_scroll_stop_event (event))
+    {
+      g_signal_emit (controller, signals[SCROLL_END], 0);
+      scroll->active = FALSE;
+
+      if (scroll->flags & GTK_EVENT_CONTROLLER_SCROLL_KINETIC)
+        {
+          gdouble vel_x, vel_y;
+
+          scroll_history_finish (scroll, &vel_x, &vel_y);
+          g_signal_emit (controller, signals[DECELERATE], 0, vel_x, vel_y);
+        }
+    }
+
+  return TRUE;
+}
+
+static void
+gtk_event_controller_scroll_class_init (GtkEventControllerScrollClass *klass)
+{
+  GtkEventControllerClass *controller_class = GTK_EVENT_CONTROLLER_CLASS (klass);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_event_controller_scroll_finalize;
+  object_class->set_property = gtk_event_controller_scroll_set_property;
+  object_class->get_property = gtk_event_controller_scroll_get_property;
+
+  controller_class->handle_event = gtk_event_controller_scroll_handle_event;
+
+  pspecs[PROP_FLAGS] =
+    g_param_spec_flags ("flags",
+                        P_("Flags"),
+                        P_("Flags"),
+                        GTK_TYPE_EVENT_CONTROLLER_SCROLL_FLAGS,
+                        GTK_EVENT_CONTROLLER_SCROLL_NONE,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY);
+
+  signals[SCROLL_BEGIN] =
+    g_signal_new (I_("scroll-begin"),
+                  GTK_TYPE_EVENT_CONTROLLER_SCROLL,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+  signals[SCROLL] =
+    g_signal_new (I_("scroll"),
+                  GTK_TYPE_EVENT_CONTROLLER_SCROLL,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                 _gtk_marshal_VOID__DOUBLE_DOUBLE,
+                  G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+  signals[SCROLL_END] =
+    g_signal_new (I_("scroll-end"),
+                  GTK_TYPE_EVENT_CONTROLLER_SCROLL,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+  signals[DECELERATE] =
+    g_signal_new (I_("decelerate"),
+                  GTK_TYPE_EVENT_CONTROLLER_SCROLL,
+                  G_SIGNAL_RUN_FIRST,
+                  0, NULL, NULL,
+                 _gtk_marshal_VOID__DOUBLE_DOUBLE,
+                  G_TYPE_NONE, 2, G_TYPE_DOUBLE, G_TYPE_DOUBLE);
+
+  g_object_class_install_properties (object_class, N_PROPS, pspecs);
+}
+
+static void
+gtk_event_controller_scroll_init (GtkEventControllerScroll *scroll)
+{
+  scroll->scroll_history = g_array_new (FALSE, FALSE,
+                                        sizeof (ScrollHistoryElem));
+}
+
+GtkEventController *
+gtk_event_controller_scroll_new (GtkWidget                     *widget,
+                                 GtkEventControllerScrollFlags  flags)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);
+
+  return g_object_new (GTK_TYPE_EVENT_CONTROLLER_SCROLL,
+                       "widget", widget,
+                       "flags", flags,
+                       NULL);
+}
+
+void
+gtk_event_controller_scroll_set_flags (GtkEventControllerScroll      *scroll,
+                                       GtkEventControllerScrollFlags  flags)
+{
+  g_return_if_fail (GTK_IS_EVENT_CONTROLLER_SCROLL (scroll));
+
+  if (scroll->flags != flags)
+    {
+      scroll->flags = flags;
+      g_object_notify (G_OBJECT (scroll), "flags");
+    }
+}
+
+GtkEventControllerScrollFlags
+gtk_event_controller_scroll_get_flags (GtkEventControllerScroll *scroll)
+{
+  g_return_val_if_fail (GTK_IS_EVENT_CONTROLLER_SCROLL (scroll),
+                        GTK_EVENT_CONTROLLER_SCROLL_NONE);
+
+  return scroll->flags;
+}
diff --git a/gtk/gtkeventcontrollerscroll.h b/gtk/gtkeventcontrollerscroll.h
new file mode 100644
index 0000000..bbbf32e
--- /dev/null
+++ b/gtk/gtkeventcontrollerscroll.h
@@ -0,0 +1,66 @@
+/* GTK - The GIMP Toolkit
+ * Copyright (C) 2017, Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author(s): Carlos Garnacho <carlosg gnome org>
+ */
+
+#ifndef __GTK_EVENT_CONTROLLER_SCROLL_H__
+#define __GTK_EVENT_CONTROLLER_SCROLL_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gdk/gdk.h>
+#include <gtk/gtkeventcontroller.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_EVENT_CONTROLLER_SCROLL         (gtk_event_controller_scroll_get_type ())
+#define GTK_EVENT_CONTROLLER_SCROLL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
GTK_TYPE_EVENT_CONTROLLER_SCROLL, GtkEventControllerScroll))
+#define GTK_EVENT_CONTROLLER_SCROLL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), 
GTK_TYPE_EVENT_CONTROLLER_SCROLL, GtkEventControllerScrollClass))
+#define GTK_IS_EVENT_CONTROLLER_SCROLL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
GTK_TYPE_EVENT_CONTROLLER_SCROLL))
+#define GTK_IS_EVENT_CONTROLLER_SCROLL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
GTK_TYPE_EVENT_CONTROLLER_SCROLL))
+#define GTK_EVENT_CONTROLLER_SCROLL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
GTK_TYPE_EVENT_CONTROLLER_SCROLL, GtkEventControllerScrollClass))
+
+typedef struct _GtkEventControllerScroll GtkEventControllerScroll;
+typedef struct _GtkEventControllerScrollClass GtkEventControllerScrollClass;
+
+typedef enum {
+  GTK_EVENT_CONTROLLER_SCROLL_NONE       = 0,
+  GTK_EVENT_CONTROLLER_SCROLL_VERTICAL   = 1 << 0,
+  GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL = 1 << 1,
+  GTK_EVENT_CONTROLLER_SCROLL_DISCRETE   = 1 << 2,
+  GTK_EVENT_CONTROLLER_SCROLL_KINETIC    = 1 << 3,
+  GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES  = (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL | 
GTK_EVENT_CONTROLLER_SCROLL_HORIZONTAL),
+} GtkEventControllerScrollFlags;
+
+GDK_AVAILABLE_IN_3_92
+GType               gtk_event_controller_scroll_get_type  (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_92
+GtkEventController *gtk_event_controller_scroll_new (GtkWidget                     *widget,
+                                                     GtkEventControllerScrollFlags  flags);
+GDK_AVAILABLE_IN_3_92
+void                gtk_event_controller_scroll_set_flags (GtkEventControllerScroll      *controller,
+                                                           GtkEventControllerScrollFlags  flags);
+GDK_AVAILABLE_IN_3_92
+GtkEventControllerScrollFlags
+                    gtk_event_controller_scroll_get_flags (GtkEventControllerScroll      *controller);
+
+G_END_DECLS
+
+#endif /* __GTK_EVENT_CONTROLLER_SCROLL_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 5dc3188..202e032 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -147,6 +147,7 @@ gtk_public_sources = files([
   'gtkentrycompletion.c',
   'gtkeventcontroller.c',
   'gtkeventcontrollerlegacy.c',
+  'gtkeventcontrollerscroll.c',
   'gtkexpander.c',
   'gtkfilechooser.c',
   'gtkfilechooserbutton.c',
@@ -440,6 +441,7 @@ gtk_public_headers = files([
   'gtkentrycompletion.h',
   'gtkenums.h',
   'gtkeventcontroller.h',
+  'gtkeventcontrollerscroll.h',
   'gtkexpander.h',
   'gtkfilechooser.h',
   'gtkfilechooserbutton.h',


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