[gtk/wip/otte/listview: 171/215] wip: Add GtkCoverFlow



commit a27141ccb48d4f3f4abe1c06dcfa356bb32c3cae
Author: Benjamin Otte <otte redhat com>
Date:   Sun Oct 27 08:03:03 2019 +0100

    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.

 gtk/gtk.h                                |   1 +
 gtk/gtkcoverflow.c                       | 704 +++++++++++++++++++++++++++++++
 gtk/gtkcoverflow.h                       |  68 +++
 gtk/gtklistbase.c                        |   9 +
 gtk/gtklistbaseprivate.h                 |   1 +
 gtk/meson.build                          |   2 +
 gtk/theme/Adwaita/_common.scss           |   6 +
 gtk/theme/Adwaita/gtk-contained-dark.css |   2 +
 gtk/theme/Adwaita/gtk-contained.css      |   2 +
 9 files changed, 795 insertions(+)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 0842ce982a..b889698cf2 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -87,6 +87,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/gtkdebug.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 6867a7263d..bbdb45b74d 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -1326,6 +1326,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 de6892c8ae..294ff2bcc0 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -212,6 +212,7 @@ gtk_public_sources = files([
   'gtkconstraintlayout.c',
   'gtkconstraint.c',
   'gtkcontainer.c',
+  'gtkcoverflow.c',
   'gtkcssprovider.c',
   'gtkdialog.c',
   'gtkdirectorylist.c',
@@ -486,6 +487,7 @@ gtk_public_headers = files([
   'gtkconstraintlayout.h',
   'gtkconstraint.h',
   'gtkcontainer.h',
+  'gtkcoverflow.h',
   'gtkcssprovider.h',
   'gtkcustomlayout.h',
   'gtkdebug.h',
diff --git a/gtk/theme/Adwaita/_common.scss b/gtk/theme/Adwaita/_common.scss
index 7c6284972c..bcb6e80fc4 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 41b1d8a0ca..dd83e7cd8b 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 a7650c9fd9..a88311be3e 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; }


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