summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2019-10-27 08:03:03 +0100
committerBenjamin Otte <otte@redhat.com>2019-12-12 07:47:44 +0100
commit5d6e2144e7422d46260638becfa495ee09fa00c3 (patch)
tree6a6649cfbea1ba80b557223c31943b30472d102f
parent84ef6ae62b9b49900f5a6eb1c572e97409dc20e4 (diff)
downloadgtk+-5d6e2144e7422d46260638becfa495ee09fa00c3.tar.gz
wip: Add GtkCoverFlow
The widget mostly works out of the box, but some tweaking may be necessary (in particular in the theme) and the gtk-demo changes might require removing before this is production-ready.
-rw-r--r--gtk/gtk.h1
-rw-r--r--gtk/gtkcoverflow.c704
-rw-r--r--gtk/gtkcoverflow.h68
-rw-r--r--gtk/gtklistbase.c9
-rw-r--r--gtk/gtklistbaseprivate.h1
-rw-r--r--gtk/meson.build2
-rw-r--r--gtk/theme/Adwaita/_common.scss6
-rw-r--r--gtk/theme/Adwaita/gtk-contained-dark.css2
-rw-r--r--gtk/theme/Adwaita/gtk-contained.css2
9 files changed, 795 insertions, 0 deletions
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 0725db7528..66480b1923 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -88,6 +88,7 @@
#include <gtk/gtkconstraintlayout.h>
#include <gtk/gtkconstraint.h>
#include <gtk/gtkcontainer.h>
+#include <gtk/gtkcoverflow.h>
#include <gtk/gtkcssprovider.h>
#include <gtk/gtkcustomlayout.h>
#include <gtk/gtkcustomsorter.h>
diff --git a/gtk/gtkcoverflow.c b/gtk/gtkcoverflow.c
new file mode 100644
index 0000000000..1e0915973e
--- /dev/null
+++ b/gtk/gtkcoverflow.c
@@ -0,0 +1,704 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#include "config.h"
+
+#include "gtkcoverflow.h"
+
+#include "gtkintl.h"
+#include "gtklistbaseprivate.h"
+#include "gtkmain.h"
+#include "gtkorientable.h"
+#include "gtkprivate.h"
+#include "gtkrbtreeprivate.h"
+#include "gtkwidgetprivate.h"
+
+/* Extra items to display at most above + below the central item */
+#define GTK_COVER_FLOW_DISPLAY_ITEMS 5
+
+/* Number of extra space we leave around the covers */
+#define GTK_COVER_FLOW_SCALE_ALONG 3
+#define GTK_COVER_FLOW_SCALE_ACROSS 2
+
+/**
+ * SECTION:gtkcoverflow
+ * @title: GtkCoverFlow
+ * @short_description: A coverflow list widget
+ * @see_also: #GListModel
+ *
+ * GtkCoverFlow is a widget to present a coverflow list
+ */
+
+struct _GtkCoverFlow
+{
+ GtkListBase parent_instance;
+
+ int size_across;
+ int size_along;
+};
+
+struct _GtkCoverFlowClass
+{
+ GtkListBaseClass parent_class;
+};
+
+enum
+{
+ PROP_0,
+ PROP_FACTORY,
+ PROP_MODEL,
+
+ N_PROPS
+};
+
+enum {
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (GtkCoverFlow, gtk_cover_flow, GTK_TYPE_LIST_BASE)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+static guint signals[LAST_SIGNAL] = { 0 };
+
+static gboolean
+gtk_cover_flow_get_allocation_along (GtkListBase *base,
+ guint pos,
+ int *offset,
+ int *size)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (base);
+
+ if (offset)
+ *offset = pos * self->size_along;
+ if (size)
+ *size = self->size_along;
+
+ return TRUE;
+}
+
+static gboolean
+gtk_cover_flow_get_allocation_across (GtkListBase *base,
+ guint pos,
+ int *offset,
+ int *size)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (base);
+
+ if (offset)
+ *offset = pos * self->size_across;
+ if (size)
+ *size = self->size_across;
+
+ return TRUE;
+}
+
+static guint
+gtk_cover_flow_move_focus_along (GtkListBase *base,
+ guint pos,
+ int steps)
+{
+ if (steps < 0)
+ return pos - MIN (pos, -steps);
+ else
+ {
+ pos += MIN (gtk_list_base_get_n_items (base) - pos - 1, steps);
+ }
+
+ return pos;
+}
+
+static guint
+gtk_cover_flow_move_focus_across (GtkListBase *base,
+ guint pos,
+ int steps)
+{
+ return pos;
+}
+
+static gboolean
+gtk_cover_flow_get_position_from_allocation (GtkListBase *base,
+ int across,
+ int along,
+ guint *pos,
+ cairo_rectangle_int_t *area)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (base);
+
+ if (across >= self->size_across ||
+ along >= self->size_along * gtk_list_base_get_n_items (base))
+ return FALSE;
+
+ *pos = along / self->size_along;
+
+ if (area)
+ {
+ area->x = 0;
+ area->width = self->size_across;
+ area->y = *pos * self->size_along;
+ area->height = self->size_along;
+ }
+
+ return TRUE;
+}
+
+static void
+gtk_cover_flow_measure_children (GtkCoverFlow *self,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural)
+{
+ GtkListItemManagerItem *item;
+ int min, nat, child_min, child_nat;
+
+ min = 0;
+ nat = 0;
+
+ for (item = gtk_list_item_manager_get_first (gtk_list_base_get_manager (GTK_LIST_BASE (self)));
+ item != NULL;
+ item = gtk_rb_tree_node_get_next (item))
+ {
+ /* ignore unavailable items */
+ if (item->widget == NULL)
+ continue;
+
+ gtk_widget_measure (item->widget,
+ orientation, for_size,
+ &child_min, &child_nat, NULL, NULL);
+ min = MAX (min, child_min);
+ nat = MAX (nat, child_nat);
+ }
+
+ *minimum = min;
+ *natural = nat;
+}
+
+static void
+gtk_cover_flow_measure_across (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (widget);
+
+ if (for_size > 0)
+ for_size /= GTK_COVER_FLOW_SCALE_ALONG;
+
+ gtk_cover_flow_measure_children (self, orientation, for_size, minimum, natural);
+
+ *minimum *= GTK_COVER_FLOW_SCALE_ACROSS;
+ *natural *= GTK_COVER_FLOW_SCALE_ACROSS;
+}
+
+static void
+gtk_cover_flow_measure_along (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (widget);
+
+ if (for_size > 0)
+ for_size /= GTK_COVER_FLOW_SCALE_ACROSS;
+
+ gtk_cover_flow_measure_children (self, orientation, for_size, minimum, natural);
+
+ *minimum *= GTK_COVER_FLOW_SCALE_ALONG;
+ *natural *= GTK_COVER_FLOW_SCALE_ALONG;
+}
+
+static void
+gtk_cover_flow_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (widget);
+
+ if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
+ gtk_cover_flow_measure_along (widget, orientation, for_size, minimum, natural);
+ else
+ gtk_cover_flow_measure_across (widget, orientation, for_size, minimum, natural);
+}
+
+static GskTransform *
+transform_translate_oriented (GskTransform *transform,
+ GtkOrientation orientation,
+ GtkTextDirection dir,
+ float across,
+ float along)
+{
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ return gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (across, along));
+ else if (dir == GTK_TEXT_DIR_LTR)
+ return gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (along, across));
+ else
+ return gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-along, across));
+}
+
+static void
+gtk_cover_flow_size_allocate_child (GtkWidget *child,
+ GtkOrientation orientation,
+ GtkTextDirection dir,
+ GskTransform *transform,
+ int width,
+ int height)
+{
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ {
+ transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-width / 2, -height / 2));
+ gtk_widget_allocate (child, width, height, -1, transform);
+ }
+ else
+ {
+ transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (-height / 2, -width / 2));
+ gtk_widget_allocate (child, height, width, -1, transform);
+ }
+}
+
+static void
+gtk_cover_flow_size_allocate (GtkWidget *widget,
+ int width,
+ int height,
+ int baseline)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (widget);
+ GtkOrientation orientation, opposite_orientation;
+ GtkTextDirection dir;
+ GtkListItemManagerItem *item;
+ GtkListItemManager *manager;
+ guint i, pos;
+ int x, y, along, across;
+
+ manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
+ orientation = gtk_list_base_get_orientation (GTK_LIST_BASE (self));
+ opposite_orientation = OPPOSITE_ORIENTATION (orientation);
+
+ /* step 0: exit early if list is empty */
+ if (gtk_list_item_manager_get_root (manager) == NULL)
+ return;
+
+ /* step 1: determine size of children */
+ along = orientation == GTK_ORIENTATION_HORIZONTAL ? width : height;
+ across = opposite_orientation == GTK_ORIENTATION_HORIZONTAL ? width : height;
+ self->size_along = along / GTK_COVER_FLOW_SCALE_ALONG;
+ self->size_across = across / GTK_COVER_FLOW_SCALE_ACROSS;
+
+ /* step 2: update the adjustments */
+ gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
+ self->size_across,
+ self->size_along * gtk_list_base_get_n_items (GTK_LIST_BASE (self)),
+ self->size_across,
+ self->size_along,
+ &x, &y);
+ pos = gtk_list_base_get_anchor (GTK_LIST_BASE (self));
+
+ /* step 4: actually allocate the widgets */
+ dir = _gtk_widget_get_direction (widget);
+ i = 0;
+
+ for (item = gtk_list_item_manager_get_first (manager);
+ item != NULL;
+ item = gtk_rb_tree_node_get_next (item))
+ {
+ if (item->widget)
+ {
+ /* start at the center */
+ GskTransform *transform = transform_translate_oriented (NULL, orientation, dir, across / 2, along / 2);
+
+ if (i == pos)
+ {
+ /* nothing to do, we're already centered */
+ }
+ else if (MAX (pos, i) - MIN (pos, i) < GTK_COVER_FLOW_DISPLAY_ITEMS) /* ABS() doesn't work with guint */
+ {
+ int diff = i - pos;
+ transform = gsk_transform_perspective (transform, MAX (width, height) * 2);
+ transform = transform_translate_oriented (transform,
+ orientation, dir,
+ 0,
+ diff * self->size_along / 4);
+ transform = transform_translate_oriented (transform,
+ orientation, dir,
+ 0,
+ (diff < 0 ? -1 : 1) * self->size_along / 2);
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ transform = gsk_transform_rotate_3d (transform,
+ diff > 0 ? 60 : -60,
+ graphene_vec3_x_axis ());
+ else
+ transform = gsk_transform_rotate_3d (transform,
+ diff < 0 ? 60 : -60,
+ graphene_vec3_y_axis ());
+ transform = transform_translate_oriented (transform,
+ orientation, dir,
+ 0,
+ (diff < 0 ? 1 : -1) * self->size_along / 4);
+ }
+ else
+ {
+ transform = transform_translate_oriented (transform,
+ orientation, dir,
+ - 2 * self->size_across,
+ - 2 * self->size_along);
+ }
+ gtk_cover_flow_size_allocate_child (item->widget,
+ orientation, dir,
+ transform,
+ self->size_across,
+ self->size_along);
+ }
+
+ i += item->n_items;
+ }
+}
+
+static void
+gtk_cover_flow_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GtkListBase *base = GTK_LIST_BASE (object);
+
+ switch (property_id)
+ {
+ case PROP_FACTORY:
+ g_value_set_object (value, gtk_list_item_manager_get_factory (gtk_list_base_get_manager (base)));
+ break;
+
+ case PROP_MODEL:
+ g_value_set_object (value, gtk_list_base_get_model (base));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_cover_flow_snapshot (GtkWidget *widget,
+ GtkSnapshot *snapshot)
+{
+ GtkWidget *child;
+ GtkListItemManagerItem *item;
+
+ item = gtk_list_item_manager_get_nth (gtk_list_base_get_manager (GTK_LIST_BASE (widget)),
+ gtk_list_base_get_anchor (GTK_LIST_BASE (widget)),
+ NULL);
+ if (item == NULL || item->widget == NULL)
+ {
+ GTK_WIDGET_CLASS (gtk_cover_flow_parent_class)->snapshot (widget, snapshot);
+ return;
+ }
+
+ for (child = _gtk_widget_get_first_child (widget);
+ child != item->widget;
+ child = _gtk_widget_get_next_sibling (child))
+ gtk_widget_snapshot_child (widget, child, snapshot);
+
+ for (child = _gtk_widget_get_last_child (widget);
+ child != item->widget;
+ child = _gtk_widget_get_prev_sibling (child))
+ gtk_widget_snapshot_child (widget, child, snapshot);
+
+ gtk_widget_snapshot_child (widget, item->widget, snapshot);
+}
+
+static void
+gtk_cover_flow_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (object);
+
+ switch (property_id)
+ {
+ case PROP_FACTORY:
+ gtk_cover_flow_set_factory (self, g_value_get_object (value));
+ break;
+
+ case PROP_MODEL:
+ gtk_cover_flow_set_model (self, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gtk_cover_flow_activate_item (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GtkCoverFlow *self = GTK_COVER_FLOW (widget);
+ guint pos;
+
+ if (!g_variant_check_format_string (parameter, "u", FALSE))
+ return;
+
+ g_variant_get (parameter, "u", &pos);
+ if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
+ return;
+
+ g_signal_emit (widget, signals[ACTIVATE], 0, pos);
+}
+
+static void
+gtk_cover_flow_class_init (GtkCoverFlowClass *klass)
+{
+ GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ list_base_class->list_item_name = "cover";
+ list_base_class->list_item_size = sizeof (GtkListItemManagerItem);
+ list_base_class->list_item_augment_size = sizeof (GtkListItemManagerItemAugment);
+ list_base_class->list_item_augment_func = gtk_list_item_manager_augment_node;
+ list_base_class->get_allocation_along = gtk_cover_flow_get_allocation_along;
+ list_base_class->get_allocation_across = gtk_cover_flow_get_allocation_across;
+ list_base_class->get_position_from_allocation = gtk_cover_flow_get_position_from_allocation;
+ list_base_class->move_focus_along = gtk_cover_flow_move_focus_along;
+ list_base_class->move_focus_across = gtk_cover_flow_move_focus_across;
+
+ widget_class->measure = gtk_cover_flow_measure;
+ widget_class->size_allocate = gtk_cover_flow_size_allocate;
+ widget_class->snapshot = gtk_cover_flow_snapshot;
+
+ gobject_class->get_property = gtk_cover_flow_get_property;
+ gobject_class->set_property = gtk_cover_flow_set_property;
+
+ /**
+ * GtkCoverFlow:factory:
+ *
+ * Factory for populating list items
+ */
+ properties[PROP_FACTORY] =
+ g_param_spec_object ("factory",
+ P_("Factory"),
+ P_("Factory for populating list items"),
+ GTK_TYPE_LIST_ITEM_FACTORY,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ /**
+ * GtkCoverFlow:model:
+ *
+ * Model for the items displayed
+ */
+ properties[PROP_MODEL] =
+ g_param_spec_object ("model",
+ P_("Model"),
+ P_("Model for the items displayed"),
+ G_TYPE_LIST_MODEL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+ /**
+ * GtkCoverFlow::activate:
+ * @self: The #GtkCoverFlow
+ * @position: position of item to activate
+ *
+ * The ::activate signal is emitted when an item has been activated by the user,
+ * usually via activating the GtkCoverFlow|list.activate-item action.
+ *
+ * This allows for a convenient way to handle activation in a listview.
+ * See gtk_list_item_set_activatable() for details on how to use this signal.
+ */
+ signals[ACTIVATE] =
+ g_signal_new (I_("activate"),
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+ g_signal_set_va_marshaller (signals[ACTIVATE],
+ G_TYPE_FROM_CLASS (gobject_class),
+ g_cclosure_marshal_VOID__UINTv);
+
+ /**
+ * GtkCoverFlow|list.activate-item:
+ * @position: position of item to activate
+ *
+ * Activates the item given in @position by emitting the GtkCoverFlow::activate
+ * signal.
+ */
+ gtk_widget_class_install_action (widget_class,
+ "list.activate-item",
+ "u",
+ gtk_cover_flow_activate_item);
+
+ gtk_widget_class_set_css_name (widget_class, I_("coverflow"));
+}
+
+static void
+gtk_cover_flow_init (GtkCoverFlow *self)
+{
+ gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
+ 0,
+ GTK_COVER_FLOW_DISPLAY_ITEMS);
+
+ /* FIXME: this should overwrite the property default, but gobject properties... */
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+}
+
+/**
+ * gtk_cover_flow_new:
+ *
+ * Creates a new empty #GtkCoverFlow.
+ *
+ * You most likely want to call gtk_cover_flow_set_factory() to
+ * set up a way to map its items to widgets and gtk_cover_flow_set_model()
+ * to set a model to provide items next.
+ *
+ * Returns: a new #GtkCoverFlow
+ **/
+GtkWidget *
+gtk_cover_flow_new (void)
+{
+ return g_object_new (GTK_TYPE_COVER_FLOW, NULL);
+}
+
+/**
+ * gtk_cover_flow_new_with_factory:
+ * @factory: (transfer full): The factory to populate items with
+ *
+ * Creates a new #GtkCoverFlow that uses the given @factory for
+ * mapping items to widgets.
+ *
+ * You most likely want to call gtk_cover_flow_set_model() to set
+ * a model next.
+ *
+ * The function takes ownership of the
+ * argument, so you can write code like
+ * ```
+ * cover_flow = gtk_cover_flow_new_with_factory (
+ * gtk_builder_list_item_factory_newfrom_resource ("/resource.ui"));
+ * ```
+ *
+ * Returns: a new #GtkCoverFlow using the given @factory
+ **/
+GtkWidget *
+gtk_cover_flow_new_with_factory (GtkListItemFactory *factory)
+{
+ GtkWidget *result;
+
+ g_return_val_if_fail (GTK_IS_LIST_ITEM_FACTORY (factory), NULL);
+
+ result = g_object_new (GTK_TYPE_COVER_FLOW,
+ "factory", factory,
+ NULL);
+
+ g_object_unref (factory);
+
+ return result;
+}
+
+/**
+ * gtk_cover_flow_get_model:
+ * @self: a #GtkCoverFlow
+ *
+ * Gets the model that's currently used to read the items displayed.
+ *
+ * Returns: (nullable) (transfer none): The model in use
+ **/
+GListModel *
+gtk_cover_flow_get_model (GtkCoverFlow *self)
+{
+ g_return_val_if_fail (GTK_IS_COVER_FLOW (self), NULL);
+
+ return gtk_list_base_get_model (GTK_LIST_BASE (self));
+}
+
+/**
+ * gtk_cover_flow_set_model:
+ * @self: a #GtkCoverFlow
+ * @model: (allow-none) (transfer none): the model to use or %NULL for none
+ *
+ * Sets the #GListModel to use.
+ *
+ * If the @model is a #GtkSelectionModel, it is used for managing the selection.
+ * Otherwise, @self creates a #GtkSingleSelection for the selection.
+ **/
+void
+gtk_cover_flow_set_model (GtkCoverFlow *self,
+ GListModel *model)
+{
+ g_return_if_fail (GTK_IS_COVER_FLOW (self));
+ g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
+
+ if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
+ return;
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
+}
+
+/**
+ * gtk_cover_flow_get_factory:
+ * @self: a #GtkCoverFlow
+ *
+ * Gets the factory that's currently used to populate list items.
+ *
+ * Returns: (nullable) (transfer none): The factory in use
+ **/
+GtkListItemFactory *
+gtk_cover_flow_get_factory (GtkCoverFlow *self)
+{
+ g_return_val_if_fail (GTK_IS_COVER_FLOW (self), NULL);
+
+ return gtk_list_item_manager_get_factory (gtk_list_base_get_manager (GTK_LIST_BASE (self)));
+}
+
+/**
+ * gtk_cover_flow_set_factory:
+ * @self: a #GtkCoverFlow
+ * @factory: (allow-none) (transfer none): the factory to use or %NULL for none
+ *
+ * Sets the #GtkListItemFactory to use for populating list items.
+ **/
+void
+gtk_cover_flow_set_factory (GtkCoverFlow *self,
+ GtkListItemFactory *factory)
+{
+ GtkListItemManager *manager;
+
+ g_return_if_fail (GTK_IS_COVER_FLOW (self));
+ g_return_if_fail (factory == NULL || GTK_LIST_ITEM_FACTORY (factory));
+
+ manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
+
+ if (factory == gtk_list_item_manager_get_factory (manager))
+ return;
+
+ gtk_list_item_manager_set_factory (manager, factory);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FACTORY]);
+}
diff --git a/gtk/gtkcoverflow.h b/gtk/gtkcoverflow.h
new file mode 100644
index 0000000000..55253862bf
--- /dev/null
+++ b/gtk/gtkcoverflow.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2018 Benjamin Otte
+ *
+ * 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.1 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/>.
+ *
+ * Authors: Benjamin Otte <otte@gnome.org>
+ */
+
+#ifndef __GTK_COVER_FLOW_H__
+#define __GTK_COVER_FLOW_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtklistbase.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_COVER_FLOW (gtk_cover_flow_get_type ())
+#define GTK_COVER_FLOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GTK_TYPE_COVER_FLOW, GtkCoverFlow))
+#define GTK_COVER_FLOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GTK_TYPE_COVER_FLOW, GtkCoverFlowClass))
+#define GTK_IS_COVER_FLOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GTK_TYPE_COVER_FLOW))
+#define GTK_IS_COVER_FLOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GTK_TYPE_COVER_FLOW))
+#define GTK_COVER_FLOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GTK_TYPE_COVER_FLOW, GtkCoverFlowClass))
+
+/**
+ * GtkCoverFlow:
+ *
+ * GtkCoverFlow is the simple list implementation for GTK's list widgets.
+ */
+typedef struct _GtkCoverFlow GtkCoverFlow;
+typedef struct _GtkCoverFlowClass GtkCoverFlowClass;
+
+GDK_AVAILABLE_IN_ALL
+GType gtk_cover_flow_get_type (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_cover_flow_new (void);
+GDK_AVAILABLE_IN_ALL
+GtkWidget * gtk_cover_flow_new_with_factory (GtkListItemFactory *factory);
+
+GDK_AVAILABLE_IN_ALL
+GListModel * gtk_cover_flow_get_model (GtkCoverFlow *self);
+GDK_AVAILABLE_IN_ALL
+void gtk_cover_flow_set_model (GtkCoverFlow *self,
+ GListModel *model);
+GDK_AVAILABLE_IN_ALL
+void gtk_cover_flow_set_factory (GtkCoverFlow *self,
+ GtkListItemFactory *factory);
+GDK_AVAILABLE_IN_ALL
+GtkListItemFactory *
+ gtk_cover_flow_get_factory (GtkCoverFlow *self);
+
+G_END_DECLS
+
+#endif /* __GTK_COVER_FLOW_H__ */
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index 3f270bc223..fef41dda87 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -1323,6 +1323,15 @@ gtk_list_base_get_manager (GtkListBase *self)
return priv->item_manager;
}
+guint
+gtk_list_base_get_anchor (GtkListBase *self)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ return gtk_list_item_tracker_get_position (priv->item_manager,
+ priv->anchor);
+}
+
/*
* gtk_list_base_set_anchor:
* @self: a #GtkListBase
diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h
index fd4e8a2ea7..06757985d3 100644
--- a/gtk/gtklistbaseprivate.h
+++ b/gtk/gtklistbaseprivate.h
@@ -80,6 +80,7 @@ void gtk_list_base_update_adjustments (GtkListBase
int *across,
int *along);
+guint gtk_list_base_get_anchor (GtkListBase *self);
void gtk_list_base_set_anchor (GtkListBase *self,
guint anchor_pos,
double anchor_align_across,
diff --git a/gtk/meson.build b/gtk/meson.build
index 8ec3a8f648..fc83617b88 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -213,6 +213,7 @@ gtk_public_sources = files([
'gtkconstraintlayout.c',
'gtkconstraint.c',
'gtkcontainer.c',
+ 'gtkcoverflow.c',
'gtkcssprovider.c',
'gtkcustomfilter.c',
'gtkcustomsorter.c',
@@ -498,6 +499,7 @@ gtk_public_headers = files([
'gtkconstraintlayout.h',
'gtkconstraint.h',
'gtkcontainer.h',
+ 'gtkcoverflow.h',
'gtkcssprovider.h',
'gtkcustomfilter.h',
'gtkcustomlayout.h',
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index c02a72a58b..0f9382cbcc 100644
--- a/gtk/theme/Adwaita/_common.scss
+++ b/gtk/theme/Adwaita/_common.scss
@@ -160,6 +160,12 @@ flowbox {
}
}
+coverflow cover {
+ color: $text_color;
+ background-color: $base_color;
+ border: 1px solid black;
+}
+
.content-view .tile {
margin: 2px;
background-color: if($variant=='light', transparent, black);
diff --git a/gtk/theme/Adwaita/gtk-contained-dark.css b/gtk/theme/Adwaita/gtk-contained-dark.css
index a8dd8906cf..7a497dc064 100644
--- a/gtk/theme/Adwaita/gtk-contained-dark.css
+++ b/gtk/theme/Adwaita/gtk-contained-dark.css
@@ -49,6 +49,8 @@ flowbox flowboxchild { padding: 3px; }
flowbox flowboxchild:selected { outline-offset: -2px; }
+coverflow cover { color: white; background-color: #2d2d2d; border: 1px solid black; }
+
.content-view .tile { margin: 2px; background-color: black; border-radius: 0; padding: 0; }
.content-view .tile:backdrop { background-color: #232323; }
diff --git a/gtk/theme/Adwaita/gtk-contained.css b/gtk/theme/Adwaita/gtk-contained.css
index 2b3580f4eb..1a033120d0 100644
--- a/gtk/theme/Adwaita/gtk-contained.css
+++ b/gtk/theme/Adwaita/gtk-contained.css
@@ -49,6 +49,8 @@ flowbox flowboxchild { padding: 3px; }
flowbox flowboxchild:selected { outline-offset: -2px; }
+coverflow cover { color: black; background-color: #ffffff; border: 1px solid black; }
+
.content-view .tile { margin: 2px; background-color: transparent; border-radius: 0; padding: 0; }
.content-view .tile:backdrop { background-color: transparent; }