[gtk/wip/otte/flipper: 5/5] Add GtkFlipper




commit 1e4306cc4a33c9defdc2db77147e9ae62dc1d646
Author: Benjamin Otte <otte redhat com>
Date:   Mon Nov 22 04:18:02 2021 +0100

    Add GtkFlipper
    
    You can put the ocean on top and let the dolphin jump out of it towards
    the bottom of the screen.

 gtk/gtk.h        |   3 +-
 gtk/gtkflipper.c | 535 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/gtkflipper.h |  63 +++++++
 gtk/meson.build  |   2 +
 4 files changed, 602 insertions(+), 1 deletion(-)
---
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 0d4521e4ac..095243e6d7 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -89,6 +89,7 @@
 #include <gtk/gtkconstraintlayout.h>
 #include <gtk/gtkconstraint.h>
 #include <gtk/gtkcssprovider.h>
+#include <gtk/gtkcustomfilter.h>
 #include <gtk/gtkcustomlayout.h>
 #include <gtk/gtkcustomsorter.h>
 #include <gtk/gtkdebug.h>
@@ -125,8 +126,8 @@
 #include <gtk/gtkfilefilter.h>
 #include <gtk/gtkfilter.h>
 #include <gtk/gtkfilterlistmodel.h>
-#include <gtk/gtkcustomfilter.h>
 #include <gtk/gtkflattenlistmodel.h>
+#include <gtk/gtkflipper.h>
 #include <gtk/gtkflowbox.h>
 #include <gtk/gtkfontbutton.h>
 #include <gtk/gtkfontchooser.h>
diff --git a/gtk/gtkflipper.c b/gtk/gtkflipper.c
new file mode 100644
index 0000000000..9cca14e227
--- /dev/null
+++ b/gtk/gtkflipper.c
@@ -0,0 +1,535 @@
+/*
+ * Copyright © 2021 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 "gtkflipper.h"
+
+#include "gtkintl.h"
+#include "gtkprivate.h"
+
+/**
+ * GtkFlipper:
+ *
+ * `GtkFlipper` is a widget to flip or rotate a child widget.
+ *
+ * ![An example GtkFlipper](flipper.png)
+ *
+ * Widgets can be flipped horiontally and vertically, and they can
+ * be rotated. The properties of the flipper only allow rotating
+ * clockwise by 90°, but flipping both horizontally and vertically
+ * achieves a 180° rotation, so you can do that to achieve 180°
+ * and 270° rotations.
+ *
+ * In technical terms this widget implements the dihedral group
+ * [Dih4](https://en.wikipedia.org/wiki/Examples_of_groups#dihedral_group_of_order_8)
+ * for the child widget.
+ *
+ * The order that operations on the child are performed when enabled is:
+ * 
+ *  * flip horizontally
+ * 
+ *  * flip vertically
+ * 
+ *  * rotate
+ */
+
+struct _GtkFlipper
+{
+  GtkWidget parent_instance;
+
+  GtkWidget *child;
+
+  guint flip_horizontal :1;
+  guint flip_vertical :1;
+  guint rotate :1;
+};
+
+enum
+{
+  PROP_0,
+  PROP_CHILD,
+  PROP_FLIP_HORIZONTAL,
+  PROP_FLIP_VERTICAL,
+  PROP_ROTATE,
+
+  N_PROPS
+};
+
+G_DEFINE_TYPE (GtkFlipper, gtk_flipper, GTK_TYPE_WIDGET)
+
+static GParamSpec *properties[N_PROPS] = { NULL, };
+
+static GtkSizeRequestMode 
+gtk_flipper_get_request_mode (GtkWidget *widget)
+{
+  GtkFlipper *self = GTK_FLIPPER (widget);
+  GtkSizeRequestMode mode;
+
+  if (!self->child)
+    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+
+  mode = gtk_widget_get_request_mode (self->child);
+
+  if (!self->rotate)
+    return mode;
+
+  switch (mode)
+    {
+    case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
+      return GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT;
+    case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
+      return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+    case GTK_SIZE_REQUEST_CONSTANT_SIZE:
+    default:
+      return mode;
+    }
+}
+
+static void
+gtk_flipper_measure (GtkWidget      *widget,
+                     GtkOrientation  orientation,
+                     int             for_size,
+                     int            *minimum,
+                     int            *natural,
+                     int            *minimum_baseline,
+                     int            *natural_baseline)
+{
+  GtkFlipper *self = GTK_FLIPPER (widget);
+
+  if (self->rotate)
+    orientation = OPPOSITE_ORIENTATION (orientation);
+
+  if (self->child && gtk_widget_get_visible (self->child))
+    gtk_widget_measure (self->child,
+                        orientation,
+                        for_size,
+                        minimum, natural,
+                        minimum_baseline, natural_baseline);
+
+  if (self->rotate || self->flip_vertical)
+    {
+      *minimum_baseline = -1;
+      *natural_baseline = -1;
+    }
+}
+
+static void
+gtk_flipper_size_allocate (GtkWidget *widget,
+                           int        width,
+                           int        height,
+                           int        baseline)
+{
+  GtkFlipper *self = GTK_FLIPPER (widget);
+  GskTransform *transform = NULL;
+
+  if (!self->child || !gtk_widget_get_visible (self->child))
+    return;
+
+  if (self->flip_horizontal)
+    {
+      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (width, 0));
+      transform = gsk_transform_scale (transform, -1, 1);
+    }
+
+  if (self->flip_vertical)
+    {
+      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, height));
+      transform = gsk_transform_scale (transform, 1, -1);
+      baseline = -1;
+    }
+
+  if (self->rotate)
+    {
+      int tmp = width;
+      width = height;
+      height = tmp;
+      transform = gsk_transform_rotate (transform, 90);
+      transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, -height));
+      baseline = -1;
+    }
+
+  gtk_widget_allocate (self->child, width, height, baseline, transform);
+}
+
+static void
+gtk_flipper_compute_expand (GtkWidget *widget,
+                            gboolean  *hexpand,
+                            gboolean  *vexpand)
+{
+  GtkFlipper *self = GTK_FLIPPER (widget);
+
+  if (self->child)
+    {
+      if (self->rotate)
+        {
+          *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL);
+          *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL);
+        }
+      else
+        {
+          *hexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_VERTICAL);
+          *vexpand = gtk_widget_compute_expand (self->child, GTK_ORIENTATION_HORIZONTAL);
+        }
+    }
+  else
+    {
+      *hexpand = FALSE;
+      *vexpand = FALSE;
+    }
+}
+
+static void
+gtk_flipper_dispose (GObject *object)
+{
+  GtkFlipper *self = GTK_FLIPPER (object);
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (gtk_flipper_parent_class)->dispose (object);
+}
+
+static void
+gtk_flipper_get_property (GObject    *object,
+                          guint       property_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  GtkFlipper *self = GTK_FLIPPER (object);
+
+  switch (property_id)
+    {
+    case PROP_CHILD:
+      g_value_set_object (value, self->child);
+      break;
+
+    case PROP_FLIP_HORIZONTAL:
+      g_value_set_boolean (value, self->flip_horizontal);
+      break;
+
+    case PROP_FLIP_VERTICAL:
+      g_value_set_boolean (value, self->flip_vertical);
+      break;
+
+    case PROP_ROTATE:
+      g_value_set_boolean (value, self->rotate);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_flipper_set_property (GObject      *object,
+                          guint         property_id,
+                          const GValue *value,
+                          GParamSpec   *pspec)
+{
+  GtkFlipper *self = GTK_FLIPPER (object);
+
+  switch (property_id)
+    {
+    case PROP_CHILD:
+      gtk_flipper_set_child (self, g_value_get_object (value));
+      break;
+
+    case PROP_FLIP_HORIZONTAL:
+      gtk_flipper_set_flip_horizontal (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_FLIP_VERTICAL:
+      gtk_flipper_set_flip_vertical (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_ROTATE:
+      gtk_flipper_set_rotate (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_flipper_class_init (GtkFlipperClass *klass)
+{
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  widget_class->get_request_mode = gtk_flipper_get_request_mode;
+  widget_class->measure = gtk_flipper_measure;
+  widget_class->size_allocate = gtk_flipper_size_allocate;
+  widget_class->compute_expand = gtk_flipper_compute_expand;
+
+  gobject_class->dispose = gtk_flipper_dispose;
+  gobject_class->get_property = gtk_flipper_get_property;
+  gobject_class->set_property = gtk_flipper_set_property;
+
+  /**
+   * GtkFlipper:child: (attributes org.gtk.Property.get=gtk_flipper_get_child 
org.gtk.Property.set=gtk_flipper_set_child)
+   *
+   * The child to display
+   */
+  properties[PROP_CHILD] =
+    g_param_spec_object ("child",
+                         P_("Child"),
+                         P_("the child to display"),
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkFlipper:flip-horizontal: (attributes org.gtk.Property.get=gtk_flipper_get_flip_horizontal 
org.gtk.Property.set=gtk_flipper_set_flip_horizontal)
+   *
+   * If the flipper should automatically begin playing.
+   */
+  properties[PROP_FLIP_HORIZONTAL] =
+    g_param_spec_boolean ("flip-horizontal",
+                          P_("Flip horizontal"),
+                          P_("Swap the left and right of the child"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkFlipper:flip-vertical: (attributes org.gtk.Property.get=gtk_flipper_get_flip_vertical 
org.gtk.Property.set=gtk_flipper_set_flip_vertical)
+   *
+   * If the flipper should automatically begin playing.
+   */
+  properties[PROP_FLIP_VERTICAL] =
+    g_param_spec_boolean ("flip-vertical",
+                          P_("Flip vertical"),
+                          P_("Swap the top and bottom of the child"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * GtkFlipper:rotate: (attributes org.gtk.Property.get=gtk_flipper_get_rotate 
org.gtk.Property.set=gtk_flipper_set_rotate)
+   *
+   * Rotates the child by 90° if set. This is applied after any flipping.
+   */
+  properties[PROP_ROTATE] =
+    g_param_spec_boolean ("rotate",
+                          P_("Rotate"),
+                          P_("Rotate the child clockwise by 90°"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (gobject_class, N_PROPS, properties);
+
+  gtk_widget_class_set_css_name (widget_class, I_("flipper"));
+}
+
+static void
+gtk_flipper_init (GtkFlipper *self)
+{
+}
+
+/**
+ * gtk_flipper_new:
+ * @child (nullable): The child widget to display
+ *
+ * Creates a new `GtkFlipper` containing @child.
+ *
+ * Returns: a new `GtkFlipper`
+ */
+GtkWidget *
+gtk_flipper_new (GtkWidget *child)
+{
+  return g_object_new (GTK_TYPE_FLIPPER,
+                       "child", child,
+                       NULL);
+}
+
+/**
+ * gtk_flipper_get_child: (attributes org.gtk.Method.get_property=child)
+ * @self: a `GtkFlipper`
+ *
+ * Returns %TRUE if flipper should flip the child's top and bottom side.
+ *
+ * Returns: %TRUE if the child should flip vertically
+ */
+GtkWidget *
+gtk_flipper_get_child (GtkFlipper *self)
+{
+  g_return_val_if_fail (GTK_IS_FLIPPER (self), FALSE);
+
+  return self->child;
+}
+
+/**
+ * gtk_flipper_set_child: (attributes org.gtk.Method.set_property=child)
+ * @self: a `GtkFlipper`
+ * @child (nullable): the child to display
+ *
+ * Sets the child to display.
+ */
+void
+gtk_flipper_set_child (GtkFlipper *self,
+                       GtkWidget  *child)
+{
+  g_return_if_fail (GTK_IS_FLIPPER (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  if (self->child == child)
+    return;
+
+  g_clear_pointer (&self->child, gtk_widget_unparent);
+
+  if (child)
+    {
+      self->child = child;
+      gtk_widget_set_parent (self->child, GTK_WIDGET (self));
+    }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CHILD]);
+}
+
+/**
+ * gtk_flipper_get_flip_horizontal: (attributes org.gtk.Method.get_property=flip_horizontal)
+ * @self: a `GtkFlipper`
+ *
+ * Returns %TRUE if flipper should flip the child's left and right side.
+ *
+ * Returns: %TRUE if the child should flip horizontally
+ */
+gboolean
+gtk_flipper_get_flip_horizontal (GtkFlipper *self)
+{
+  g_return_val_if_fail (GTK_IS_FLIPPER (self), FALSE);
+
+  return self->flip_horizontal;
+}
+
+/**
+ * gtk_flipper_set_flip_horizontal: (attributes org.gtk.Method.set_property=flip_horizontal)
+ * @self: a `GtkFlipper`
+ * @flip_horizontal: whether the child should be flipped horizontal
+ *
+ * Sets whether the child should swap its left and right sides.
+ *
+ * The flipping is applied before rotating.
+ */
+void
+gtk_flipper_set_flip_horizontal (GtkFlipper *self,
+                                 gboolean    flip_horizontal)
+{
+  g_return_if_fail (GTK_IS_FLIPPER (self));
+
+  flip_horizontal = !!flip_horizontal;
+
+  if (self->flip_horizontal == flip_horizontal)
+    return;
+
+  self->flip_horizontal = flip_horizontal;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FLIP_HORIZONTAL]);
+}
+
+/**
+ * gtk_flipper_get_flip_vertical: (attributes org.gtk.Method.get_property=flip_vertical)
+ * @self: a `GtkFlipper`
+ *
+ * Returns %TRUE if flipper should flip the child's top and bottom side.
+ *
+ * Returns: %TRUE if the child should flip vertically
+ */
+gboolean
+gtk_flipper_get_flip_vertical (GtkFlipper *self)
+{
+  g_return_val_if_fail (GTK_IS_FLIPPER (self), FALSE);
+
+  return self->flip_vertical;
+}
+
+/**
+ * gtk_flipper_set_flip_vertical: (attributes org.gtk.Method.set_property=flip_vertical)
+ * @self: a `GtkFlipper`
+ * @flip_vertical: whether the child should be flipped vertical
+ *
+ * Sets whether the child should swap its top and bottom sides.
+ *
+ * The flipping is applied before rotating.
+ */
+void
+gtk_flipper_set_flip_vertical (GtkFlipper *self,
+                               gboolean    flip_vertical)
+{
+  g_return_if_fail (GTK_IS_FLIPPER (self));
+
+  flip_vertical = !!flip_vertical;
+
+  if (self->flip_vertical == flip_vertical)
+    return;
+
+  self->flip_vertical = flip_vertical;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_FLIP_VERTICAL]);
+}
+
+/**
+ * gtk_flipper_get_rotate: (attributes org.gtk.Method.get_property=rotate)
+ * @self: a `GtkFlipper`
+ *
+ * Returns %TRUE if flipper should rotate the child clockwise by 90°.
+ *
+ * Returns: %TRUE if the child should rotate
+ */
+gboolean
+gtk_flipper_get_rotate (GtkFlipper *self)
+{
+  g_return_val_if_fail (GTK_IS_FLIPPER (self), FALSE);
+
+  return self->rotate;
+}
+
+/**
+ * gtk_flipper_set_rotate: (attributes org.gtk.Method.set_property=rotate)
+ * @self: a `GtkFlipper`
+ * @rotate: whether the child should be rotated
+ *
+ * Sets whether the child should rotate the child clockwise by 90°.
+ *
+ * The rotation is done after any eventual flipping.
+ */
+void
+gtk_flipper_set_rotate (GtkFlipper *self,
+                        gboolean    rotate)
+{
+  g_return_if_fail (GTK_IS_FLIPPER (self));
+
+  rotate = !!rotate;
+
+  if (self->rotate == rotate)
+    return;
+
+  self->rotate = rotate;
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_ROTATE]);
+}
+
diff --git a/gtk/gtkflipper.h b/gtk/gtkflipper.h
new file mode 100644
index 0000000000..b04f2b20f7
--- /dev/null
+++ b/gtk/gtkflipper.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2021 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_FLIPPER_H__
+#define __GTK_FLIPPER_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_FLIPPER         (gtk_flipper_get_type ())
+
+GDK_AVAILABLE_IN_4_6
+G_DECLARE_FINAL_TYPE (GtkFlipper, gtk_flipper, GTK, FLIPPER, GtkWidget)
+
+GDK_AVAILABLE_IN_4_6
+GtkWidget *     gtk_flipper_new                                 (GtkWidget              *child);
+
+GDK_AVAILABLE_IN_4_6
+GtkWidget *     gtk_flipper_get_child                           (GtkFlipper             *self);
+GDK_AVAILABLE_IN_4_6
+void            gtk_flipper_set_child                           (GtkFlipper             *self,
+                                                                 GtkWidget              *child);
+GDK_AVAILABLE_IN_4_6
+gboolean        gtk_flipper_get_flip_horizontal                 (GtkFlipper             *self);
+GDK_AVAILABLE_IN_4_6
+void            gtk_flipper_set_flip_horizontal                 (GtkFlipper             *self,
+                                                                 gboolean                flip_horizontal);
+GDK_AVAILABLE_IN_4_6
+gboolean        gtk_flipper_get_flip_vertical                   (GtkFlipper             *self);
+GDK_AVAILABLE_IN_4_6
+void            gtk_flipper_set_flip_vertical                   (GtkFlipper             *self,
+                                                                 gboolean                flip_vertical);
+GDK_AVAILABLE_IN_4_6
+gboolean        gtk_flipper_get_rotate                          (GtkFlipper             *self);
+GDK_AVAILABLE_IN_4_6
+void            gtk_flipper_set_rotate                          (GtkFlipper             *self,
+                                                                 gboolean                rotate);
+
+
+G_END_DECLS
+
+#endif  /* __GTK_FLIPPER_H__ */
diff --git a/gtk/meson.build b/gtk/meson.build
index 57e573a681..8847fda6c3 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -252,6 +252,7 @@ gtk_public_sources = files([
   'gtkfixed.c',
   'gtkfixedlayout.c',
   'gtkflattenlistmodel.c',
+  'gtkflipper.c',
   'gtkflowbox.c',
   'gtkfontbutton.c',
   'gtkfontchooser.c',
@@ -541,6 +542,7 @@ gtk_public_headers = files([
   'gtkfixed.h',
   'gtkfixedlayout.h',
   'gtkflattenlistmodel.h',
+  'gtkflipper.h',
   'gtkflowbox.h',
   'gtkfontbutton.h',
   'gtkfontchooser.h',


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