summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/src/hdy-swipe-group.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/libhandy/src/hdy-swipe-group.c')
-rw-r--r--subprojects/libhandy/src/hdy-swipe-group.c568
1 files changed, 568 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-swipe-group.c b/subprojects/libhandy/src/hdy-swipe-group.c
new file mode 100644
index 0000000..2779a31
--- /dev/null
+++ b/subprojects/libhandy/src/hdy-swipe-group.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "hdy-swipe-group.h"
+#include <gtk/gtk.h>
+#include "hdy-navigation-direction.h"
+#include "hdy-swipe-tracker-private.h"
+
+#define BUILDABLE_TAG_OBJECT "object"
+#define BUILDABLE_TAG_SWIPEABLE "swipeable"
+#define BUILDABLE_TAG_SWIPEABLES "swipeables"
+#define BUILDABLE_TAG_TEMPLATE "template"
+
+/**
+ * SECTION:hdy-swipe-group
+ * @short_description: An object for syncing swipeable widgets.
+ * @title: HdySwipeGroup
+ * @See_also: #HdyCarousel, #HdyDeck, #HdyLeaflet, #HdySwipeable
+ *
+ * The #HdySwipeGroup object can be used to sync multiple swipeable widgets
+ * that implement the #HdySwipeable interface, such as #HdyCarousel, so that
+ * animating one of them also animates all the other widgets in the group.
+ *
+ * This can be useful for syncing widgets between a window's titlebar and
+ * content area.
+ *
+ * # #HdySwipeGroup as #GtkBuildable
+ *
+ * #HdySwipeGroup can be created in an UI definition. The list of swipeable
+ * widgets is specified with a &lt;swipeables&gt; element containing multiple
+ * &lt;swipeable&gt; elements with their ”name” attribute specifying the id of
+ * the widgets.
+ *
+ * |[
+ * <object class="HdySwipeGroup">
+ * <swipeables>
+ * <swipeable name="carousel1"/>
+ * <swipeable name="carousel2"/>
+ * </swipeables>
+ * </object>
+ * ]|
+ *
+ * Since: 0.0.12
+ */
+
+struct _HdySwipeGroup
+{
+ GObject parent_instance;
+
+ GSList *swipeables;
+ HdySwipeable *current;
+ gboolean block;
+};
+
+static void hdy_swipe_group_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (HdySwipeGroup, hdy_swipe_group, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+ hdy_swipe_group_buildable_init))
+
+static gboolean
+contains (HdySwipeGroup *self,
+ HdySwipeable *swipeable)
+{
+ GSList *swipeables;
+
+ for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next)
+ if (swipeables->data == swipeable)
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+swipeable_destroyed (HdySwipeGroup *self,
+ HdySwipeable *swipeable)
+{
+ g_return_if_fail (HDY_IS_SWIPE_GROUP (self));
+
+ self->swipeables = g_slist_remove (self->swipeables, swipeable);
+
+ g_object_unref (self);
+}
+
+/**
+ * hdy_swipe_group_new:
+ *
+ * Create a new #HdySwipeGroup object.
+ *
+ * Returns: The newly created #HdySwipeGroup object
+ *
+ * Since: 0.0.12
+ */
+HdySwipeGroup *
+hdy_swipe_group_new (void)
+{
+ return g_object_new (HDY_TYPE_SWIPE_GROUP, NULL);
+}
+
+static void
+child_switched_cb (HdySwipeGroup *self,
+ guint index,
+ gint64 duration,
+ HdySwipeable *swipeable)
+{
+ GSList *swipeables;
+
+ if (self->block)
+ return;
+
+ if (self->current != NULL && self->current != swipeable)
+ return;
+
+ self->block = TRUE;
+
+ for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next)
+ if (swipeables->data != swipeable)
+ hdy_swipeable_switch_child (swipeables->data, index, duration);
+
+ self->block = FALSE;
+}
+
+static void
+begin_swipe_cb (HdySwipeGroup *self,
+ HdyNavigationDirection direction,
+ gboolean direct,
+ HdySwipeTracker *tracker)
+{
+ HdySwipeable *swipeable;
+ GSList *swipeables;
+
+ if (self->block)
+ return;
+
+ swipeable = hdy_swipe_tracker_get_swipeable (tracker);
+
+ if (self->current != NULL && self->current != swipeable)
+ return;
+
+ self->current = swipeable;
+
+ self->block = TRUE;
+
+ for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next)
+ if (swipeables->data != swipeable)
+ hdy_swipe_tracker_emit_begin_swipe (hdy_swipeable_get_swipe_tracker (swipeables->data),
+ direction, FALSE);
+
+ self->block = FALSE;
+}
+
+static void
+update_swipe_cb (HdySwipeGroup *self,
+ gdouble progress,
+ HdySwipeTracker *tracker)
+{
+ HdySwipeable *swipeable;
+ GSList *swipeables;
+
+ if (self->block)
+ return;
+
+ swipeable = hdy_swipe_tracker_get_swipeable (tracker);
+
+ if (swipeable != self->current)
+ return;
+
+ self->block = TRUE;
+
+ for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next)
+ if (swipeables->data != swipeable)
+ hdy_swipe_tracker_emit_update_swipe (hdy_swipeable_get_swipe_tracker (swipeables->data),
+ progress);
+
+ self->block = FALSE;
+}
+
+static void
+end_swipe_cb (HdySwipeGroup *self,
+ gint64 duration,
+ gdouble to,
+ HdySwipeTracker *tracker)
+{
+ HdySwipeable *swipeable;
+ GSList *swipeables;
+
+ if (self->block)
+ return;
+
+ swipeable = hdy_swipe_tracker_get_swipeable (tracker);
+
+ if (swipeable != self->current)
+ return;
+
+ self->block = TRUE;
+
+ for (swipeables = self->swipeables; swipeables != NULL; swipeables = swipeables->next)
+ if (swipeables->data != swipeable)
+ hdy_swipe_tracker_emit_end_swipe (hdy_swipeable_get_swipe_tracker (swipeables->data),
+ duration, to);
+
+ self->current = NULL;
+
+ self->block = FALSE;
+}
+
+/**
+ * hdy_swipe_group_add_swipeable:
+ * @self: a #HdySwipeGroup
+ * @swipeable: the #HdySwipeable to add
+ *
+ * When the widget is destroyed or no longer referenced elsewhere, it will
+ * be removed from the swipe group.
+ *
+ * Since: 0.0.12
+ */
+void
+hdy_swipe_group_add_swipeable (HdySwipeGroup *self,
+ HdySwipeable *swipeable)
+{
+ HdySwipeTracker *tracker;
+
+ g_return_if_fail (HDY_IS_SWIPE_GROUP (self));
+ g_return_if_fail (HDY_IS_SWIPEABLE (swipeable));
+
+ tracker = hdy_swipeable_get_swipe_tracker (swipeable);
+
+ g_return_if_fail (HDY_IS_SWIPE_TRACKER (tracker));
+
+ g_signal_connect_swapped (swipeable, "child-switched", G_CALLBACK (child_switched_cb), self);
+ g_signal_connect_swapped (tracker, "begin-swipe", G_CALLBACK (begin_swipe_cb), self);
+ g_signal_connect_swapped (tracker, "update-swipe", G_CALLBACK (update_swipe_cb), self);
+ g_signal_connect_swapped (tracker, "end-swipe", G_CALLBACK (end_swipe_cb), self);
+
+ self->swipeables = g_slist_prepend (self->swipeables, swipeable);
+
+ g_object_ref (self);
+
+ g_signal_connect_swapped (swipeable, "destroy", G_CALLBACK (swipeable_destroyed), self);
+}
+
+
+/**
+ * hdy_swipe_group_remove_swipeable:
+ * @self: a #HdySwipeGroup
+ * @swipeable: the #HdySwipeable to remove
+ *
+ * Removes a widget from a #HdySwipeGroup.
+ *
+ * Since: 0.0.12
+ **/
+void
+hdy_swipe_group_remove_swipeable (HdySwipeGroup *self,
+ HdySwipeable *swipeable)
+{
+ HdySwipeTracker *tracker;
+
+ g_return_if_fail (HDY_IS_SWIPE_GROUP (self));
+ g_return_if_fail (HDY_IS_SWIPEABLE (swipeable));
+ g_return_if_fail (contains (self, swipeable));
+
+ tracker = hdy_swipeable_get_swipe_tracker (swipeable);
+
+ self->swipeables = g_slist_remove (self->swipeables, swipeable);
+
+ g_signal_handlers_disconnect_by_data (swipeable, self);
+ g_signal_handlers_disconnect_by_data (tracker, self);
+
+ g_object_unref (self);
+}
+
+
+/**
+ * hdy_swipe_group_get_swipeables:
+ * @self: a #HdySwipeGroup
+ *
+ * Returns the list of swipeables associated with @self.
+ *
+ * Returns: (element-type HdySwipeable) (transfer none): a #GSList of
+ * swipeables. The list is owned by libhandy and should not be modified.
+ *
+ * Since: 0.0.12
+ **/
+GSList *
+hdy_swipe_group_get_swipeables (HdySwipeGroup *self)
+{
+ g_return_val_if_fail (HDY_IS_SWIPE_GROUP (self), NULL);
+
+ return self->swipeables;
+}
+
+typedef struct {
+ gchar *name;
+ gint line;
+ gint col;
+} ItemData;
+
+static void
+item_data_free (gpointer data)
+{
+ ItemData *item_data = data;
+
+ g_free (item_data->name);
+ g_free (item_data);
+}
+
+typedef struct {
+ GObject *object;
+ GtkBuilder *builder;
+ GSList *items;
+} GSListSubParserData;
+
+static void
+hdy_swipe_group_dispose (GObject *object)
+{
+ HdySwipeGroup *self = (HdySwipeGroup *)object;
+
+ g_slist_free_full (self->swipeables, (GDestroyNotify) g_object_unref);
+ self->swipeables = NULL;
+
+ G_OBJECT_CLASS (hdy_swipe_group_parent_class)->dispose (object);
+}
+
+/*< private >
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @parent_name: the name of the expected parent element
+ * @error: return location for an error
+ *
+ * Checks that the parent element of the currently handled
+ * start tag is @parent_name and set @error if it isn't.
+ *
+ * This is intended to be called in start_element vfuncs to
+ * ensure that element nesting is as intended.
+ *
+ * Returns: %TRUE if @parent_name is the parent element
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static gboolean
+_gtk_builder_check_parent (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ const gchar *parent_name,
+ GError **error)
+{
+ const GSList *stack;
+ gint line, col;
+ const gchar *parent;
+ const gchar *element;
+
+ stack = g_markup_parse_context_get_element_stack (context);
+
+ element = (const gchar *)stack->data;
+ parent = stack->next ? (const gchar *)stack->next->data : "";
+
+ if (g_str_equal (parent_name, parent) ||
+ (g_str_equal (parent_name, BUILDABLE_TAG_OBJECT) &&
+ g_str_equal (parent, BUILDABLE_TAG_TEMPLATE)))
+ return TRUE;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_INVALID_TAG,
+ ".:%d:%d Can't use <%s> here",
+ line, col, element);
+
+ return FALSE;
+}
+
+/*< private >
+ * _gtk_builder_prefix_error:
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @error: an error
+ *
+ * Calls g_prefix_error() to prepend a filename:line:column marker
+ * to the given error. The filename is taken from @builder, and
+ * the line and column are obtained by calling
+ * g_markup_parse_context_get_position().
+ *
+ * This is intended to be called on errors returned by
+ * g_markup_collect_attributes() in a start_element vfunc.
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static void
+_gtk_builder_prefix_error (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ GError **error)
+{
+ gint line, col;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_prefix_error (error, ".:%d:%d ", line, col);
+}
+
+/*< private >
+ * _gtk_builder_error_unhandled_tag:
+ * @builder: a #GtkBuilder
+ * @context: the #GMarkupParseContext
+ * @object: name of the object that is being handled
+ * @element_name: name of the element whose start tag is being handled
+ * @error: return location for the error
+ *
+ * Sets @error to a suitable error indicating that an @element_name
+ * tag is not expected in the custom markup for @object.
+ *
+ * This is intended to be called in a start_element vfunc.
+ */
+/* This has been copied and modified from gtkbuilder.c. */
+static void
+_gtk_builder_error_unhandled_tag (GtkBuilder *builder,
+ GMarkupParseContext *context,
+ const gchar *object,
+ const gchar *element_name,
+ GError **error)
+{
+ gint line, col;
+
+ g_markup_parse_context_get_position (context, &line, &col);
+ g_set_error (error,
+ GTK_BUILDER_ERROR,
+ GTK_BUILDER_ERROR_UNHANDLED_TAG,
+ ".:%d:%d Unsupported tag for %s: <%s>",
+ line, col,
+ object, element_name);
+}
+
+/* This has been copied and modified from gtksizegroup.c. */
+static void
+swipe_group_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **names,
+ const gchar **values,
+ gpointer user_data,
+ GError **error)
+{
+ GSListSubParserData *data = (GSListSubParserData*)user_data;
+
+ if (strcmp (element_name, BUILDABLE_TAG_SWIPEABLE) == 0)
+ {
+ const gchar *name;
+ ItemData *item_data;
+
+ if (!_gtk_builder_check_parent (data->builder, context, BUILDABLE_TAG_SWIPEABLES, error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_STRING, "name", &name,
+ G_MARKUP_COLLECT_INVALID))
+ {
+ _gtk_builder_prefix_error (data->builder, context, error);
+ return;
+ }
+
+ item_data = g_new (ItemData, 1);
+ item_data->name = g_strdup (name);
+ g_markup_parse_context_get_position (context, &item_data->line, &item_data->col);
+ data->items = g_slist_prepend (data->items, item_data);
+ }
+ else if (strcmp (element_name, BUILDABLE_TAG_SWIPEABLES) == 0)
+ {
+ if (!_gtk_builder_check_parent (data->builder, context, BUILDABLE_TAG_OBJECT, error))
+ return;
+
+ if (!g_markup_collect_attributes (element_name, names, values, error,
+ G_MARKUP_COLLECT_INVALID, NULL, NULL,
+ G_MARKUP_COLLECT_INVALID))
+ _gtk_builder_prefix_error (data->builder, context, error);
+ }
+ else
+ {
+ _gtk_builder_error_unhandled_tag (data->builder, context,
+ "HdySwipeGroup", element_name,
+ error);
+ }
+}
+
+
+/* This has been copied and modified from gtksizegroup.c. */
+static const GMarkupParser swipe_group_parser =
+ {
+ swipe_group_start_element
+ };
+
+/* This has been copied and modified from gtksizegroup.c. */
+static gboolean
+hdy_swipe_group_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ GMarkupParser *parser,
+ gpointer *parser_data)
+{
+ GSListSubParserData *data;
+
+ if (child)
+ return FALSE;
+
+ if (strcmp (tagname, BUILDABLE_TAG_SWIPEABLES) == 0)
+ {
+ data = g_slice_new0 (GSListSubParserData);
+ data->items = NULL;
+ data->object = G_OBJECT (buildable);
+ data->builder = builder;
+
+ *parser = swipe_group_parser;
+ *parser_data = data;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/* This has been copied and modified from gtksizegroup.c. */
+static void
+hdy_swipe_group_buildable_custom_finished (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
+ gpointer user_data)
+{
+ GSList *l;
+ GSListSubParserData *data;
+ GObject *object;
+
+ if (strcmp (tagname, BUILDABLE_TAG_SWIPEABLES) != 0)
+ return;
+
+ data = (GSListSubParserData*)user_data;
+ data->items = g_slist_reverse (data->items);
+
+ for (l = data->items; l; l = l->next)
+ {
+ ItemData *item_data = l->data;
+ object = gtk_builder_get_object (builder, item_data->name);
+ if (!object)
+ continue;
+ hdy_swipe_group_add_swipeable (HDY_SWIPE_GROUP (data->object), HDY_SWIPEABLE (object));
+ }
+ g_slist_free_full (data->items, item_data_free);
+ g_slice_free (GSListSubParserData, data);
+}
+
+static void
+hdy_swipe_group_class_init (HdySwipeGroupClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = hdy_swipe_group_dispose;
+}
+
+static void
+hdy_swipe_group_init (HdySwipeGroup *self)
+{
+}
+
+static void
+hdy_swipe_group_buildable_init (GtkBuildableIface *iface)
+{
+ iface->custom_tag_start = hdy_swipe_group_buildable_custom_tag_start;
+ iface->custom_finished = hdy_swipe_group_buildable_custom_finished;
+}