diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2017-09-15 13:22:57 +0200 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2018-07-16 15:43:43 +0200 |
commit | 747be0f4999bc9e855cc1f6a4b98ccd371835d49 (patch) | |
tree | 4a3bc8177ed0c6937626c7421f7a5fda0cc0b5d5 | |
parent | 9008f7702dff689ce48f2cfa7ce42db099e4ef0c (diff) | |
download | gtk+-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.am | 2 | ||||
-rw-r--r-- | gtk/gtk.h | 1 | ||||
-rw-r--r-- | gtk/gtkeventcontrollerscroll.c | 401 | ||||
-rw-r--r-- | gtk/gtkeventcontrollerscroll.h | 66 |
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 \ @@ -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__ */ |