summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garnacho <carlosg@gnome.org>2017-09-15 13:22:57 +0200
committerCarlos Garnacho <carlosg@gnome.org>2018-07-16 15:43:43 +0200
commit747be0f4999bc9e855cc1f6a4b98ccd371835d49 (patch)
tree4a3bc8177ed0c6937626c7421f7a5fda0cc0b5d5
parent9008f7702dff689ce48f2cfa7ce42db099e4ef0c (diff)
downloadgtk+-747be0f4999bc9e855cc1f6a4b98ccd371835d49.tar.gz
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.
-rw-r--r--gtk/Makefile.am2
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtkeventcontrollerscroll.c401
-rw-r--r--gtk/gtkeventcontrollerscroll.h66
4 files changed, 470 insertions, 0 deletions
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 56b41a1c90..37336d540f 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -192,6 +192,7 @@ gtk_public_h_sources = \
gtkenums.h \
gtkeventbox.h \
gtkeventcontroller.h \
+ gtkeventcontrollerscroll.h \
gtkexpander.h \
gtkfilechooser.h \
gtkfilechooserbutton.h \
@@ -757,6 +758,7 @@ gtk_base_c_sources = \
gtkentrycompletion.c \
gtkeventbox.c \
gtkeventcontroller.c \
+ gtkeventcontrollerscroll.c \
gtkexpander.c \
gtkfilechooser.c \
gtkfilechooserbutton.c \
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 7b901e1ece..1ce5e5b37e 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -94,6 +94,7 @@
#include <gtk/gtkenums.h>
#include <gtk/gtkeventbox.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 0000000000..61f916782c
--- /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 0000000000..a7303f88d8
--- /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_24
+GType gtk_event_controller_scroll_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_24
+GtkEventController *gtk_event_controller_scroll_new (GtkWidget *widget,
+ GtkEventControllerScrollFlags flags);
+GDK_AVAILABLE_IN_3_24
+void gtk_event_controller_scroll_set_flags (GtkEventControllerScroll *controller,
+ GtkEventControllerScrollFlags flags);
+GDK_AVAILABLE_IN_3_24
+GtkEventControllerScrollFlags
+ gtk_event_controller_scroll_get_flags (GtkEventControllerScroll *controller);
+
+G_END_DECLS
+
+#endif /* __GTK_EVENT_CONTROLLER_SCROLL_H__ */