File Add-GtkHdySqueezer.patch of Package gtk3

From: Adrien Plazas <kekun.plazas@laposte.net>
Date: Thu, 10 Sep 2020 10:46:42 +0200
Subject: Add GtkHdySqueezer

This is imported from HdySqueezer from libhandy 1.0.2.
---
 gtk/gtkprivate.h           |    1 +
 gtk/hdy-cairo-private.h    |   21 +
 gtk/hdy-squeezer-private.h |   63 ++
 gtk/hdy-squeezer.c         | 1573 ++++++++++++++++++++++++++++++++++++++++++++
 gtk/meson.build            |    3 +
 5 files changed, 1661 insertions(+)
 create mode 100644 gtk/hdy-cairo-private.h
 create mode 100644 gtk/hdy-squeezer-private.h
 create mode 100644 gtk/hdy-squeezer.c

diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h
index da05e4e..ba9f46f 100644
--- a/gtk/gtkprivate.h
+++ b/gtk/gtkprivate.h
@@ -30,6 +30,7 @@
 
 #include "gtkcsstypesprivate.h"
 #include "gtktexthandleprivate.h"
+#include "hdy-squeezer-private.h"
 #include "hdy-view-switcher-private.h"
 
 G_BEGIN_DECLS
diff --git a/gtk/hdy-cairo-private.h b/gtk/hdy-cairo-private.h
new file mode 100644
index 0000000..4226ec8
--- /dev/null
+++ b/gtk/hdy-cairo-private.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <glib.h>
+#include <cairo/cairo.h>
+
+G_BEGIN_DECLS
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (cairo_t, cairo_destroy)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (cairo_surface_t, cairo_surface_destroy)
+
+G_END_DECLS
diff --git a/gtk/hdy-squeezer-private.h b/gtk/hdy-squeezer-private.h
new file mode 100644
index 0000000..9224230
--- /dev/null
+++ b/gtk/hdy-squeezer-private.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include "gtkcontainer.h"
+#include "gtkenums.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_HDY_SQUEEZER (gtk_hdy_squeezer_get_type ())
+
+G_DECLARE_FINAL_TYPE (GtkHdySqueezer, gtk_hdy_squeezer, GTK, HDY_SQUEEZER, GtkContainer)
+
+typedef enum {
+  GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE,
+  GTK_HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE,
+} GtkHdySqueezerTransitionType;
+
+GtkWidget *gtk_hdy_squeezer_new (void);
+
+gboolean gtk_hdy_squeezer_get_homogeneous (GtkHdySqueezer *self);
+void     gtk_hdy_squeezer_set_homogeneous (GtkHdySqueezer *self,
+                                           gboolean        homogeneous);
+
+guint gtk_hdy_squeezer_get_transition_duration (GtkHdySqueezer *self);
+void  gtk_hdy_squeezer_set_transition_duration (GtkHdySqueezer *self,
+                                                guint           duration);
+
+GtkHdySqueezerTransitionType gtk_hdy_squeezer_get_transition_type (GtkHdySqueezer *self);
+void                         gtk_hdy_squeezer_set_transition_type (GtkHdySqueezer               *self,
+                                                                   GtkHdySqueezerTransitionType  transition);
+
+gboolean gtk_hdy_squeezer_get_transition_running (GtkHdySqueezer *self);
+
+gboolean gtk_hdy_squeezer_get_interpolate_size (GtkHdySqueezer *self);
+void     gtk_hdy_squeezer_set_interpolate_size (GtkHdySqueezer *self,
+                                                gboolean        interpolate_size);
+
+GtkWidget *gtk_hdy_squeezer_get_visible_child (GtkHdySqueezer *self);
+
+gboolean gtk_hdy_squeezer_get_child_enabled (GtkHdySqueezer *self,
+                                             GtkWidget      *child);
+void     gtk_hdy_squeezer_set_child_enabled (GtkHdySqueezer *self,
+                                             GtkWidget      *child,
+                                             gboolean        enabled);
+
+gfloat gtk_hdy_squeezer_get_xalign (GtkHdySqueezer *self);
+void   gtk_hdy_squeezer_set_xalign (GtkHdySqueezer *self,
+                                    gfloat          xalign);
+
+gfloat gtk_hdy_squeezer_get_yalign (GtkHdySqueezer *self);
+void   gtk_hdy_squeezer_set_yalign (GtkHdySqueezer *self,
+                                    gfloat          yalign);
+
+G_END_DECLS
diff --git a/gtk/hdy-squeezer.c b/gtk/hdy-squeezer.c
new file mode 100644
index 0000000..b36db93
--- /dev/null
+++ b/gtk/hdy-squeezer.c
@@ -0,0 +1,1573 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ * Copyright (C) 2019 Purism SPC
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ * Author: Adrien Plazas <adrien.plazas@puri.sm>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+/*
+ * Forked from the GTK+ 3.24.2 GtkStack widget initially written by Alexander
+ * Larsson, and heavily modified for libhandy by Adrien Plazas on behalf of
+ * Purism SPC 2019.
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-squeezer-private.h"
+
+#include "gtkorientable.h"
+#include "gtkprivatetypebuiltins.h"
+#include "gtkprogresstrackerprivate.h"
+#include "gtkrender.h"
+#include "gtkstylecontext.h"
+#include "gtktypebuiltins.h"
+#include "gtkwindow.h"
+#include "hdy-animation-private.h"
+#include "hdy-cairo-private.h"
+#include "hdy-css-private.h"
+
+/**
+ * SECTION:hdy-squeezer
+ * @short_description: A best fit container.
+ * @Title: GtkHdySqueezer
+ *
+ * The GtkHdySqueezer widget is a container which only shows the first of its
+ * children that fits in the available size. It is convenient to offer different
+ * widgets to represent the same data with different levels of detail, making
+ * the widget seem to squeeze itself to fit in the available space.
+ *
+ * Transitions between children can be animated as fades. This can be controlled
+ * with gtk_hdy_squeezer_set_transition_type().
+ *
+ * # CSS nodes
+ *
+ * #GtkHdySqueezer has a single CSS node with name squeezer.
+ */
+
+enum  {
+  PROP_0,
+  PROP_HOMOGENEOUS,
+  PROP_VISIBLE_CHILD,
+  PROP_TRANSITION_DURATION,
+  PROP_TRANSITION_TYPE,
+  PROP_TRANSITION_RUNNING,
+  PROP_INTERPOLATE_SIZE,
+  PROP_XALIGN,
+  PROP_YALIGN,
+
+  /* Overridden properties */
+  PROP_ORIENTATION,
+
+  LAST_PROP = PROP_YALIGN + 1,
+};
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_ENABLED,
+
+  LAST_CHILD_PROP,
+};
+
+typedef struct {
+  GtkWidget *widget;
+  gboolean enabled;
+  GtkWidget *last_focus;
+} GtkHdySqueezerChildInfo;
+
+struct _GtkHdySqueezer
+{
+  GtkContainer parent_instance;
+
+  GList *children;
+
+  GdkWindow* bin_window;
+  GdkWindow* view_window;
+
+  GtkHdySqueezerChildInfo *visible_child;
+
+  gboolean homogeneous;
+
+  GtkHdySqueezerTransitionType transition_type;
+  guint transition_duration;
+
+  GtkHdySqueezerChildInfo *last_visible_child;
+  cairo_surface_t *last_visible_surface;
+  GtkAllocation last_visible_surface_allocation;
+  guint tick_id;
+  GtkProgressTracker tracker;
+  gboolean first_frame_skipped;
+
+  gint last_visible_widget_width;
+  gint last_visible_widget_height;
+
+  GtkHdySqueezerTransitionType active_transition_type;
+
+  gboolean interpolate_size;
+
+  gfloat xalign;
+  gfloat yalign;
+
+  GtkOrientation orientation;
+};
+
+static GParamSpec *props[LAST_PROP];
+static GParamSpec *child_props[LAST_CHILD_PROP];
+
+G_DEFINE_TYPE_WITH_CODE (GtkHdySqueezer, gtk_hdy_squeezer, GTK_TYPE_CONTAINER,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL))
+
+static GtkOrientation
+get_orientation (GtkHdySqueezer *self)
+{
+  return self->orientation;
+}
+
+static void
+set_orientation (GtkHdySqueezer *self,
+                 GtkOrientation  orientation)
+{
+  if (self->orientation == orientation)
+    return;
+
+  self->orientation = orientation;
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+  g_object_notify (G_OBJECT (self), "orientation");
+}
+
+static GtkHdySqueezerChildInfo *
+find_child_info_for_widget (GtkHdySqueezer *self,
+                            GtkWidget      *child)
+{
+  GtkHdySqueezerChildInfo *info;
+  GList *l;
+
+  for (l = self->children; l != NULL; l = l->next) {
+    info = l->data;
+    if (info->widget == child)
+      return info;
+  }
+
+  return NULL;
+}
+
+static void
+gtk_hdy_squeezer_progress_updated (GtkHdySqueezer *self)
+{
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+
+  if (!self->homogeneous)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
+    if (self->last_visible_surface != NULL) {
+      cairo_surface_destroy (self->last_visible_surface);
+      self->last_visible_surface = NULL;
+    }
+
+    if (self->last_visible_child != NULL) {
+      gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+      self->last_visible_child = NULL;
+    }
+  }
+}
+
+static gboolean
+gtk_hdy_squeezer_transition_cb (GtkWidget     *widget,
+                                GdkFrameClock *frame_clock,
+                                gpointer       user_data)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+
+  if (self->first_frame_skipped) {
+    gtk_progress_tracker_advance_frame (&self->tracker,
+                                        gdk_frame_clock_get_frame_time (frame_clock));
+  } else {
+    self->first_frame_skipped = TRUE;
+  }
+
+  /* Finish the animation early if the widget isn't mapped anymore. */
+  if (!gtk_widget_get_mapped (widget))
+    gtk_progress_tracker_finish (&self->tracker);
+
+  gtk_hdy_squeezer_progress_updated (GTK_HDY_SQUEEZER (widget));
+
+  if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
+    self->tick_id = 0;
+    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+static void
+gtk_hdy_squeezer_schedule_ticks (GtkHdySqueezer *self)
+{
+  if (self->tick_id == 0) {
+    self->tick_id =
+      gtk_widget_add_tick_callback (GTK_WIDGET (self), gtk_hdy_squeezer_transition_cb, self, NULL);
+    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+  }
+}
+
+static void
+gtk_hdy_squeezer_unschedule_ticks (GtkHdySqueezer *self)
+{
+  if (self->tick_id != 0) {
+    gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->tick_id);
+    self->tick_id = 0;
+    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+  }
+}
+
+static void
+gtk_hdy_squeezer_start_transition (GtkHdySqueezer               *self,
+                                   GtkHdySqueezerTransitionType  transition_type,
+                                   guint                         transition_duration)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  if (gtk_widget_get_mapped (widget) &&
+      gtk_hdy_get_enable_animations (widget) &&
+      transition_type != GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE &&
+      transition_duration != 0 &&
+      self->last_visible_child != NULL) {
+    self->active_transition_type = transition_type;
+    self->first_frame_skipped = FALSE;
+    gtk_hdy_squeezer_schedule_ticks (self);
+    gtk_progress_tracker_start (&self->tracker,
+                                self->transition_duration * 1000,
+                                0,
+                                1.0);
+  } else {
+    gtk_hdy_squeezer_unschedule_ticks (self);
+    self->active_transition_type = GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE;
+    gtk_progress_tracker_finish (&self->tracker);
+  }
+
+  gtk_hdy_squeezer_progress_updated (GTK_HDY_SQUEEZER (widget));
+}
+
+static void
+set_visible_child (GtkHdySqueezer               *self,
+                   GtkHdySqueezerChildInfo      *child_info,
+                   GtkHdySqueezerTransitionType  transition_type,
+                   guint                         transition_duration)
+{
+  GtkHdySqueezerChildInfo *info;
+  GtkWidget *widget = GTK_WIDGET (self);
+  GList *l;
+  GtkWidget *toplevel;
+  GtkWidget *focus;
+  gboolean contains_focus = FALSE;
+
+  /* If we are being destroyed, do not bother with transitions and
+   * notifications.
+   */
+  if (gtk_widget_in_destruction (widget))
+    return;
+
+  /* If none, pick the first visible. */
+  if (child_info == NULL) {
+    for (l = self->children; l != NULL; l = l->next) {
+      info = l->data;
+      if (gtk_widget_get_visible (info->widget)) {
+        child_info = info;
+        break;
+      }
+    }
+  }
+
+  if (child_info == self->visible_child)
+    return;
+
+  toplevel = gtk_widget_get_toplevel (widget);
+  if (GTK_IS_WINDOW (toplevel)) {
+    focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
+    if (focus &&
+        self->visible_child &&
+        self->visible_child->widget &&
+        gtk_widget_is_ancestor (focus, self->visible_child->widget)) {
+      contains_focus = TRUE;
+
+      if (self->visible_child->last_focus)
+        g_object_remove_weak_pointer (G_OBJECT (self->visible_child->last_focus),
+                                      (gpointer *)&self->visible_child->last_focus);
+      self->visible_child->last_focus = focus;
+      g_object_add_weak_pointer (G_OBJECT (self->visible_child->last_focus),
+                                 (gpointer *)&self->visible_child->last_focus);
+    }
+  }
+
+  if (self->last_visible_child != NULL)
+    gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+  self->last_visible_child = NULL;
+
+  if (self->last_visible_surface != NULL)
+    cairo_surface_destroy (self->last_visible_surface);
+  self->last_visible_surface = NULL;
+
+  if (self->visible_child && self->visible_child->widget) {
+    if (gtk_widget_is_visible (widget)) {
+      GtkAllocation allocation;
+
+      self->last_visible_child = self->visible_child;
+      gtk_widget_get_allocated_size (self->last_visible_child->widget, &allocation, NULL);
+      self->last_visible_widget_width = allocation.width;
+      self->last_visible_widget_height = allocation.height;
+    } else {
+      gtk_widget_set_child_visible (self->visible_child->widget, FALSE);
+    }
+  }
+
+  self->visible_child = child_info;
+
+  if (child_info) {
+    gtk_widget_set_child_visible (child_info->widget, TRUE);
+
+    if (contains_focus) {
+      if (child_info->last_focus)
+        gtk_widget_grab_focus (child_info->last_focus);
+      else
+        gtk_widget_child_focus (child_info->widget, GTK_DIR_TAB_FORWARD);
+    }
+  }
+
+  if (self->homogeneous)
+    gtk_widget_queue_allocate (widget);
+  else
+    gtk_widget_queue_resize (widget);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
+
+  gtk_hdy_squeezer_start_transition (self, transition_type, transition_duration);
+}
+
+static void
+stack_child_visibility_notify_cb (GObject    *obj,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (user_data);
+  GtkWidget *child = GTK_WIDGET (obj);
+  GtkHdySqueezerChildInfo *child_info;
+
+  child_info = find_child_info_for_widget (self, child);
+
+  if (self->visible_child == NULL &&
+      gtk_widget_get_visible (child))
+    set_visible_child (self, child_info, self->transition_type, self->transition_duration);
+  else if (self->visible_child == child_info &&
+           !gtk_widget_get_visible (child))
+    set_visible_child (self, NULL, self->transition_type, self->transition_duration);
+
+  if (child_info == self->last_visible_child) {
+    gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+    self->last_visible_child = NULL;
+  }
+}
+
+static void
+gtk_hdy_squeezer_add (GtkContainer *container,
+                      GtkWidget    *child)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (container);
+  GtkHdySqueezerChildInfo *child_info;
+
+  g_return_if_fail (child != NULL);
+
+  child_info = g_slice_new (GtkHdySqueezerChildInfo);
+  child_info->widget = child;
+  child_info->enabled = TRUE;
+  child_info->last_focus = NULL;
+
+  self->children = g_list_append (self->children, child_info);
+
+  gtk_widget_set_child_visible (child, FALSE);
+  gtk_widget_set_parent_window (child, self->bin_window);
+  gtk_widget_set_parent (child, GTK_WIDGET (self));
+
+  if (self->bin_window != NULL) {
+    gdk_window_set_events (self->bin_window,
+                           gdk_window_get_events (self->bin_window) |
+                           gtk_widget_get_events (child));
+  }
+
+  g_signal_connect (child, "notify::visible",
+                    G_CALLBACK (stack_child_visibility_notify_cb), self);
+
+  if (self->visible_child == NULL &&
+      gtk_widget_get_visible (child))
+    set_visible_child (self, child_info, self->transition_type, self->transition_duration);
+
+  if (self->visible_child == child_info)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+gtk_hdy_squeezer_remove (GtkContainer *container,
+                         GtkWidget    *child)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (container);
+  GtkHdySqueezerChildInfo *child_info;
+  gboolean was_visible;
+
+  child_info = find_child_info_for_widget (self, child);
+  if (child_info == NULL)
+    return;
+
+  self->children = g_list_remove (self->children, child_info);
+
+  g_signal_handlers_disconnect_by_func (child,
+                                        stack_child_visibility_notify_cb,
+                                        self);
+
+  was_visible = gtk_widget_get_visible (child);
+
+  child_info->widget = NULL;
+
+  if (self->visible_child == child_info)
+    set_visible_child (self, NULL, self->transition_type, self->transition_duration);
+
+  if (self->last_visible_child == child_info)
+    self->last_visible_child = NULL;
+
+  gtk_widget_unparent (child);
+
+  if (child_info->last_focus)
+    g_object_remove_weak_pointer (G_OBJECT (child_info->last_focus),
+                                  (gpointer *)&child_info->last_focus);
+
+  g_slice_free (GtkHdySqueezerChildInfo, child_info);
+
+  if (self->homogeneous && was_visible)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+gtk_hdy_squeezer_get_property (GObject    *object,
+                               guint       property_id,
+                               GValue     *value,
+                               GParamSpec *pspec)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (object);
+
+  switch (property_id) {
+  case PROP_HOMOGENEOUS:
+    g_value_set_boolean (value, gtk_hdy_squeezer_get_homogeneous (self));
+    break;
+  case PROP_VISIBLE_CHILD:
+    g_value_set_object (value, gtk_hdy_squeezer_get_visible_child (self));
+    break;
+  case PROP_TRANSITION_DURATION:
+    g_value_set_uint (value, gtk_hdy_squeezer_get_transition_duration (self));
+    break;
+  case PROP_TRANSITION_TYPE:
+    g_value_set_enum (value, gtk_hdy_squeezer_get_transition_type (self));
+    break;
+  case PROP_TRANSITION_RUNNING:
+    g_value_set_boolean (value, gtk_hdy_squeezer_get_transition_running (self));
+    break;
+  case PROP_INTERPOLATE_SIZE:
+    g_value_set_boolean (value, gtk_hdy_squeezer_get_interpolate_size (self));
+    break;
+  case PROP_XALIGN:
+    g_value_set_float (value, gtk_hdy_squeezer_get_xalign (self));
+    break;
+  case PROP_YALIGN:
+    g_value_set_float (value, gtk_hdy_squeezer_get_yalign (self));
+    break;
+  case PROP_ORIENTATION:
+    g_value_set_enum (value, get_orientation (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+gtk_hdy_squeezer_set_property (GObject      *object,
+                               guint         property_id,
+                               const GValue *value,
+                               GParamSpec   *pspec)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (object);
+
+  switch (property_id) {
+  case PROP_HOMOGENEOUS:
+    gtk_hdy_squeezer_set_homogeneous (self, g_value_get_boolean (value));
+    break;
+  case PROP_TRANSITION_DURATION:
+    gtk_hdy_squeezer_set_transition_duration (self, g_value_get_uint (value));
+    break;
+  case PROP_TRANSITION_TYPE:
+    gtk_hdy_squeezer_set_transition_type (self, g_value_get_enum (value));
+    break;
+  case PROP_INTERPOLATE_SIZE:
+    gtk_hdy_squeezer_set_interpolate_size (self, g_value_get_boolean (value));
+    break;
+  case PROP_XALIGN:
+    gtk_hdy_squeezer_set_xalign (self, g_value_get_float (value));
+    break;
+  case PROP_YALIGN:
+    gtk_hdy_squeezer_set_yalign (self, g_value_get_float (value));
+    break;
+  case PROP_ORIENTATION:
+    set_orientation (self, g_value_get_enum (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+gtk_hdy_squeezer_realize (GtkWidget *widget)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+  GtkAllocation allocation;
+  GdkWindowAttr attributes = { 0 };
+  GdkWindowAttributesType attributes_mask;
+  GtkHdySqueezerChildInfo *info;
+  GList *l;
+
+  gtk_widget_set_realized (widget, TRUE);
+  gtk_widget_set_window (widget, g_object_ref (gtk_widget_get_parent_window (widget)));
+
+  gtk_widget_get_allocation (widget, &allocation);
+
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.event_mask =
+    gtk_widget_get_events (widget);
+  attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
+
+  self->view_window =
+    gdk_window_new (gtk_widget_get_window (GTK_WIDGET (self)),
+                    &attributes, attributes_mask);
+  gtk_widget_register_window (widget, self->view_window);
+
+  attributes.x = 0;
+  attributes.y = 0;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+
+  for (l = self->children; l != NULL; l = l->next) {
+    info = l->data;
+    attributes.event_mask |= gtk_widget_get_events (info->widget);
+  }
+
+  self->bin_window =
+    gdk_window_new (self->view_window, &attributes, attributes_mask);
+  gtk_widget_register_window (widget, self->bin_window);
+
+  for (l = self->children; l != NULL; l = l->next) {
+    info = l->data;
+
+    gtk_widget_set_parent_window (info->widget, self->bin_window);
+  }
+
+  gdk_window_show (self->bin_window);
+}
+
+static void
+gtk_hdy_squeezer_unrealize (GtkWidget *widget)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+
+  gtk_widget_unregister_window (widget, self->bin_window);
+  gdk_window_destroy (self->bin_window);
+  self->bin_window = NULL;
+  gtk_widget_unregister_window (widget, self->view_window);
+  gdk_window_destroy (self->view_window);
+  self->view_window = NULL;
+
+  GTK_WIDGET_CLASS (gtk_hdy_squeezer_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_hdy_squeezer_map (GtkWidget *widget)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+
+  GTK_WIDGET_CLASS (gtk_hdy_squeezer_parent_class)->map (widget);
+
+  gdk_window_show (self->view_window);
+}
+
+static void
+gtk_hdy_squeezer_unmap (GtkWidget *widget)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+
+  gdk_window_hide (self->view_window);
+
+  GTK_WIDGET_CLASS (gtk_hdy_squeezer_parent_class)->unmap (widget);
+}
+
+static void
+gtk_hdy_squeezer_forall (GtkContainer *container,
+                         gboolean      include_internals,
+                         GtkCallback   callback,
+                         gpointer      callback_data)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (container);
+  GtkHdySqueezerChildInfo *child_info;
+  GList *l;
+
+  l = self->children;
+  while (l) {
+    child_info = l->data;
+    l = l->next;
+
+    (* callback) (child_info->widget, callback_data);
+  }
+}
+
+static void
+gtk_hdy_squeezer_compute_expand (GtkWidget *widget,
+                                 gboolean  *hexpand_p,
+                                 gboolean  *vexpand_p)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+  gboolean hexpand, vexpand;
+  GtkHdySqueezerChildInfo *child_info;
+  GtkWidget *child;
+  GList *l;
+
+  hexpand = FALSE;
+  vexpand = FALSE;
+  for (l = self->children; l != NULL; l = l->next) {
+    child_info = l->data;
+    child = child_info->widget;
+
+    if (!hexpand &&
+        gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL))
+      hexpand = TRUE;
+
+    if (!vexpand &&
+        gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL))
+      vexpand = TRUE;
+
+    if (hexpand && vexpand)
+      break;
+  }
+
+  *hexpand_p = hexpand;
+  *vexpand_p = vexpand;
+}
+
+static void
+gtk_hdy_squeezer_draw_crossfade (GtkWidget *widget,
+                                 cairo_t   *cr)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+  gdouble progress = gtk_progress_tracker_get_progress (&self->tracker, FALSE);
+
+  cairo_push_group (cr);
+  gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                self->visible_child->widget,
+                                cr);
+  cairo_save (cr);
+
+  /* Multiply alpha by progress. */
+  cairo_set_source_rgba (cr, 1, 1, 1, progress);
+  cairo_set_operator (cr, CAIRO_OPERATOR_DEST_IN);
+  cairo_paint (cr);
+
+  if (self->last_visible_surface != NULL) {
+    gint width_diff = gtk_widget_get_allocated_width (widget) - self->last_visible_surface_allocation.width;
+    gint height_diff = gtk_widget_get_allocated_height (widget) - self->last_visible_surface_allocation.height;
+
+    cairo_set_source_surface (cr, self->last_visible_surface,
+                              width_diff * self->xalign,
+                              height_diff * self->yalign);
+    cairo_set_operator (cr, CAIRO_OPERATOR_ADD);
+    cairo_paint_with_alpha (cr, MAX (1.0 - progress, 0));
+  }
+
+  cairo_restore (cr);
+
+  cairo_pop_group_to_source (cr);
+  cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+  cairo_paint (cr);
+}
+
+static gboolean
+gtk_hdy_squeezer_draw (GtkWidget *widget,
+                       cairo_t   *cr)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+
+  if (gtk_cairo_should_draw_window (cr, self->view_window)) {
+    GtkStyleContext *context;
+
+    context = gtk_widget_get_style_context (widget);
+    gtk_render_background (context,
+                           cr,
+                           0, 0,
+                           gtk_widget_get_allocated_width (widget),
+                           gtk_widget_get_allocated_height (widget));
+  }
+
+  if (self->visible_child) {
+    if (gtk_progress_tracker_get_state (&self->tracker) != GTK_PROGRESS_STATE_AFTER) {
+      if (self->last_visible_surface == NULL &&
+          self->last_visible_child != NULL) {
+        g_autoptr (cairo_t) pattern_cr = NULL;
+
+        gtk_widget_get_allocation (self->last_visible_child->widget,
+                                   &self->last_visible_surface_allocation);
+        self->last_visible_surface =
+          gdk_window_create_similar_surface (gtk_widget_get_window (widget),
+                                             CAIRO_CONTENT_COLOR_ALPHA,
+                                             self->last_visible_surface_allocation.width,
+                                             self->last_visible_surface_allocation.height);
+        pattern_cr = cairo_create (self->last_visible_surface);
+        /* We don't use propagate_draw here, because we don't want to apply the
+         * bin_window offset.
+         */
+        gtk_widget_draw (self->last_visible_child->widget, pattern_cr);
+      }
+
+      cairo_rectangle (cr,
+                       0, 0,
+                       gtk_widget_get_allocated_width (widget),
+                       gtk_widget_get_allocated_height (widget));
+      cairo_clip (cr);
+
+      switch (self->active_transition_type) {
+      case GTK_HDY_SQUEEZER_TRANSITION_TYPE_CROSSFADE:
+        if (gtk_cairo_should_draw_window (cr, self->bin_window))
+          gtk_hdy_squeezer_draw_crossfade (widget, cr);
+        break;
+      case GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE:
+      default:
+        g_assert_not_reached ();
+      }
+
+    } else if (gtk_cairo_should_draw_window (cr, self->bin_window))
+      gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                    self->visible_child->widget,
+                                    cr);
+  }
+
+  return FALSE;
+}
+
+static void
+gtk_hdy_squeezer_size_allocate (GtkWidget     *widget,
+                                GtkAllocation *allocation)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+  GtkHdySqueezerChildInfo *child_info = NULL;
+  GtkWidget *child = NULL;
+  gint child_min;
+  GList *l;
+  GtkAllocation child_allocation;
+
+  gtk_hdy_css_size_allocate (widget, allocation);
+
+  gtk_widget_set_allocation (widget, allocation);
+
+  for (l = self->children; l != NULL; l = l->next) {
+    child_info = l->data;
+    child = child_info->widget;
+
+    if (!gtk_widget_get_visible (child))
+      continue;
+
+    if (!child_info->enabled)
+      continue;
+
+    if (self->orientation == GTK_ORIENTATION_VERTICAL) {
+      if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH)
+        gtk_widget_get_preferred_height (child, &child_min, NULL);
+      else
+        gtk_widget_get_preferred_height_for_width (child, allocation->width, &child_min, NULL);
+
+      if (child_min <= allocation->height)
+        break;
+    } else {
+      if (gtk_widget_get_request_mode (child) != GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
+        gtk_widget_get_preferred_width (child, &child_min, NULL);
+      else
+        gtk_widget_get_preferred_width_for_height (child, allocation->height, &child_min, NULL);
+
+      if (child_min <= allocation->width)
+        break;
+    }
+  }
+
+  set_visible_child (self, child_info,
+                     self->transition_type,
+                     self->transition_duration);
+
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+
+  if (gtk_widget_get_realized (widget)) {
+    gdk_window_move_resize (self->view_window,
+                            allocation->x, allocation->y,
+                            allocation->width, allocation->height);
+    gdk_window_move_resize (self->bin_window,
+                            0, 0,
+                            allocation->width, allocation->height);
+  }
+
+  if (self->last_visible_child != NULL) {
+    int min, nat;
+    gtk_widget_get_preferred_width (self->last_visible_child->widget, &min, &nat);
+    child_allocation.width = MAX (min, allocation->width);
+    gtk_widget_get_preferred_height_for_width (self->last_visible_child->widget,
+                                               child_allocation.width,
+                                               &min, &nat);
+    child_allocation.height = MAX (min, allocation->height);
+
+    gtk_widget_size_allocate (self->last_visible_child->widget, &child_allocation);
+  }
+
+  child_allocation.width = allocation->width;
+  child_allocation.height = allocation->height;
+
+  if (self->visible_child) {
+    int min, nat;
+    GtkAlign valign;
+
+    gtk_widget_get_preferred_height_for_width (self->visible_child->widget,
+                                               allocation->width,
+                                               &min, &nat);
+    if (self->interpolate_size) {
+      valign = gtk_widget_get_valign (self->visible_child->widget);
+      child_allocation.height = MAX (nat, allocation->height);
+      if (valign == GTK_ALIGN_END &&
+          child_allocation.height > allocation->height)
+        child_allocation.y -= nat - allocation->height;
+      else if (valign == GTK_ALIGN_CENTER &&
+               child_allocation.height > allocation->height)
+        child_allocation.y -= (nat - allocation->height) / 2;
+    }
+
+    gtk_widget_size_allocate (self->visible_child->widget, &child_allocation);
+  }
+}
+
+/* This private method is prefixed by the class name because it will be a
+ * virtual method in GTK 4.
+ */
+static void
+gtk_hdy_squeezer_measure (GtkWidget      *widget,
+                          GtkOrientation  orientation,
+                          int             for_size,
+                          int            *minimum,
+                          int            *natural,
+                          int            *minimum_baseline,
+                          int            *natural_baseline)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (widget);
+  GtkHdySqueezerChildInfo *child_info;
+  GtkWidget *child;
+  gint child_min, child_nat;
+  GList *l;
+
+  *minimum = 0;
+  *natural = 0;
+
+  for (l = self->children; l != NULL; l = l->next) {
+    child_info = l->data;
+    child = child_info->widget;
+
+    if (self->orientation != orientation && !self->homogeneous &&
+        self->visible_child != child_info)
+      continue;
+
+    if (!gtk_widget_get_visible (child))
+      continue;
+
+    /* Disabled children are taken into account when measuring the widget, to
+     * keep its size request and allocation consistent. This avoids the
+     * appearant size and position of a child to changes suddenly when a larger
+     * child gets enabled/disabled.
+     */
+
+    if (orientation == GTK_ORIENTATION_VERTICAL) {
+      if (for_size < 0)
+        gtk_widget_get_preferred_height (child, &child_min, &child_nat);
+      else
+        gtk_widget_get_preferred_height_for_width (child, for_size, &child_min, &child_nat);
+    } else {
+      if (for_size < 0)
+        gtk_widget_get_preferred_width (child, &child_min, &child_nat);
+      else
+        gtk_widget_get_preferred_width_for_height (child, for_size, &child_min, &child_nat);
+    }
+
+    if (self->orientation == orientation)
+      *minimum = *minimum == 0 ? child_min : MIN (*minimum, child_min);
+    else
+      *minimum = MAX (*minimum, child_min);
+    *natural = MAX (*natural, child_nat);
+  }
+
+  if (self->orientation != orientation && !self->homogeneous &&
+      self->interpolate_size &&
+      self->last_visible_child != NULL) {
+    gdouble t = gtk_progress_tracker_get_ease_out_cubic (&self->tracker, FALSE);
+    if (orientation == GTK_ORIENTATION_VERTICAL) {
+      *minimum = gtk_hdy_lerp (self->last_visible_widget_height, *minimum, t);
+      *natural = gtk_hdy_lerp (self->last_visible_widget_height, *natural, t);
+    } else {
+      *minimum = gtk_hdy_lerp (self->last_visible_widget_width, *minimum, t);
+      *natural = gtk_hdy_lerp (self->last_visible_widget_width, *natural, t);
+    }
+  }
+
+  gtk_hdy_css_measure (widget, orientation, minimum, natural);
+}
+
+static void
+gtk_hdy_squeezer_get_preferred_width (GtkWidget *widget,
+                                      gint      *minimum,
+                                      gint      *natural)
+{
+  gtk_hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_squeezer_get_preferred_width_for_height (GtkWidget *widget,
+                                                 gint       height,
+                                                 gint      *minimum,
+                                                 gint      *natural)
+{
+  gtk_hdy_squeezer_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_squeezer_get_preferred_height (GtkWidget *widget,
+                                       gint      *minimum,
+                                       gint      *natural)
+{
+  gtk_hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_squeezer_get_preferred_height_for_width (GtkWidget *widget,
+                                                 gint       width,
+                                                 gint      *minimum,
+                                                 gint      *natural)
+{
+  gtk_hdy_squeezer_measure (widget, GTK_ORIENTATION_VERTICAL, width,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_squeezer_get_child_property (GtkContainer *container,
+                                     GtkWidget    *widget,
+                                     guint         property_id,
+                                     GValue       *value,
+                                     GParamSpec   *pspec)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (container);
+  GtkHdySqueezerChildInfo *child_info;
+
+  child_info = find_child_info_for_widget (self, widget);
+  if (child_info == NULL) {
+    g_param_value_set_default (pspec, value);
+
+    return;
+  }
+
+  switch (property_id) {
+  case CHILD_PROP_ENABLED:
+    g_value_set_boolean (value, gtk_hdy_squeezer_get_child_enabled (self, widget));
+    break;
+  default:
+    GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+    break;
+  }
+}
+
+static void
+gtk_hdy_squeezer_set_child_property (GtkContainer *container,
+                                     GtkWidget    *widget,
+                                     guint         property_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (container);
+  GtkHdySqueezerChildInfo *child_info;
+
+  child_info = find_child_info_for_widget (self, widget);
+  if (child_info == NULL)
+    return;
+
+  switch (property_id) {
+  case CHILD_PROP_ENABLED:
+    gtk_hdy_squeezer_set_child_enabled (self, widget, g_value_get_boolean (value));
+    break;
+  default:
+    GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
+    break;
+  }
+}
+
+static void
+gtk_hdy_squeezer_dispose (GObject *object)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (object);
+
+  self->visible_child = NULL;
+
+  G_OBJECT_CLASS (gtk_hdy_squeezer_parent_class)->dispose (object);
+}
+
+static void
+gtk_hdy_squeezer_finalize (GObject *object)
+{
+  GtkHdySqueezer *self = GTK_HDY_SQUEEZER (object);
+
+  gtk_hdy_squeezer_unschedule_ticks (self);
+
+  if (self->last_visible_surface != NULL)
+    cairo_surface_destroy (self->last_visible_surface);
+
+  G_OBJECT_CLASS (gtk_hdy_squeezer_parent_class)->finalize (object);
+}
+
+static void
+gtk_hdy_squeezer_class_init (GtkHdySqueezerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
+
+  object_class->get_property = gtk_hdy_squeezer_get_property;
+  object_class->set_property = gtk_hdy_squeezer_set_property;
+  object_class->dispose = gtk_hdy_squeezer_dispose;
+  object_class->finalize = gtk_hdy_squeezer_finalize;
+
+  widget_class->size_allocate = gtk_hdy_squeezer_size_allocate;
+  widget_class->draw = gtk_hdy_squeezer_draw;
+  widget_class->realize = gtk_hdy_squeezer_realize;
+  widget_class->unrealize = gtk_hdy_squeezer_unrealize;
+  widget_class->map = gtk_hdy_squeezer_map;
+  widget_class->unmap = gtk_hdy_squeezer_unmap;
+  widget_class->get_preferred_height = gtk_hdy_squeezer_get_preferred_height;
+  widget_class->get_preferred_height_for_width = gtk_hdy_squeezer_get_preferred_height_for_width;
+  widget_class->get_preferred_width = gtk_hdy_squeezer_get_preferred_width;
+  widget_class->get_preferred_width_for_height = gtk_hdy_squeezer_get_preferred_width_for_height;
+  widget_class->compute_expand = gtk_hdy_squeezer_compute_expand;
+
+  container_class->add = gtk_hdy_squeezer_add;
+  container_class->remove = gtk_hdy_squeezer_remove;
+  container_class->forall = gtk_hdy_squeezer_forall;
+  container_class->set_child_property = gtk_hdy_squeezer_set_child_property;
+  container_class->get_child_property = gtk_hdy_squeezer_get_child_property;
+  gtk_container_class_handle_border_width (container_class);
+
+  g_object_class_override_property (object_class,
+                                    PROP_ORIENTATION,
+                                    "orientation");
+
+  props[PROP_HOMOGENEOUS] =
+    g_param_spec_boolean ("homogeneous",
+                          _("Homogeneous"),
+                          _("Homogeneous sizing"),
+                            FALSE,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_VISIBLE_CHILD] =
+    g_param_spec_object ("visible-child",
+                         _("Visible child"),
+                         _("The widget currently visible in the squeezer"),
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_TRANSITION_DURATION] =
+    g_param_spec_uint ("transition-duration",
+                       _("Transition duration"),
+                       _("The animation duration, in milliseconds"),
+                       0, G_MAXUINT, 200,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_TRANSITION_TYPE] =
+    g_param_spec_enum ("transition-type",
+                       _("Transition type"),
+                       _("The type of animation used to transition"),
+                       GTK_TYPE_HDY_SQUEEZER_TRANSITION_TYPE,
+                       GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_TRANSITION_RUNNING] =
+    g_param_spec_boolean ("transition-running",
+                          _("Transition running"),
+                          _("Whether or not the transition is currently running"),
+                          FALSE,
+                          G_PARAM_READABLE);
+
+  props[PROP_INTERPOLATE_SIZE] =
+    g_param_spec_boolean ("interpolate-size",
+                          _("Interpolate size"),
+                          _("Whether or not the size should smoothly change when changing between differently sized children"),
+                            FALSE,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdySqueezer:xalign:
+   *
+   * The xalign property determines the horizontal alignment of the children
+   * inside the squeezer's size allocation.
+   * Compare this to #GtkWidget:halign, which determines how the squeezer's size
+   * allocation is positioned in the space available for the squeezer.
+   * The range goes from 0 (start) to 1 (end).
+   *
+   * This will affect the position of children too wide to fit in the squeezer
+   * as they are fading out.
+   *
+   * Since: 1.0
+   */
+  props[PROP_XALIGN] =
+    g_param_spec_float ("xalign",
+                        _("X align"),
+                        _("The horizontal alignment, from 0 (start) to 1 (end)"),
+                        0.0, 1.0,
+                        0.5,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdySqueezer:yalign:
+   *
+   * The yalign property determines the vertical alignment of the children inside
+   * the squeezer's size allocation.
+   * Compare this to #GtkWidget:valign, which determines how the squeezer's size
+   * allocation is positioned in the space available for the squeezer.
+   * The range goes from 0 (top) to 1 (bottom).
+   *
+   * This will affect the position of children too tall to fit in the squeezer
+   * as they are fading out.
+   *
+   * Since: 1.0
+   */
+  props[PROP_YALIGN] =
+    g_param_spec_float ("yalign",
+                        _("Y align"),
+                        _("The vertical alignment, from 0 (top) to 1 (bottom)"),
+                        0.0, 1.0,
+                        0.5,
+                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  child_props[CHILD_PROP_ENABLED] =
+    g_param_spec_boolean ("enabled",
+                          _("Enabled"),
+                          _("Whether the child can be picked or should be ignored when looking for the child fitting the available size best"),
+                          TRUE,
+                          G_PARAM_READWRITE);
+
+  gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props);
+
+  gtk_widget_class_set_css_name (widget_class, "squeezer");
+}
+
+static void
+gtk_hdy_squeezer_init (GtkHdySqueezer *self)
+{
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+
+  self->homogeneous = TRUE;
+  self->transition_duration = 200;
+  self->transition_type = GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE;
+  self->xalign = 0.5;
+  self->yalign = 0.5;
+}
+
+/**
+ * gtk_hdy_squeezer_new:
+ *
+ * Creates a new #GtkHdySqueezer container.
+ *
+ * Returns: a new #GtkHdySqueezer
+ */
+GtkWidget *
+gtk_hdy_squeezer_new (void)
+{
+  return g_object_new (GTK_TYPE_HDY_SQUEEZER, NULL);
+}
+
+/**
+ * gtk_hdy_squeezer_get_homogeneous:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets whether @self is homogeneous.
+ *
+ * See gtk_hdy_squeezer_set_homogeneous().
+ *
+ * Returns: %TRUE if @self is homogeneous, %FALSE is not
+ *
+ * Since: 0.0.10
+ */
+gboolean
+gtk_hdy_squeezer_get_homogeneous (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), FALSE);
+
+  return self->homogeneous;
+}
+
+/**
+ * gtk_hdy_squeezer_set_homogeneous:
+ * @self: a #GtkHdySqueezer
+ * @homogeneous: %TRUE to make @self homogeneous
+ *
+ * Sets @self to be homogeneous or not. If it is homogeneous, @self will request
+ * the same size for all its children for its opposite orientation, e.g. if
+ * @self is oriented horizontally and is homogeneous, it will request the same
+ * height for all its children. If it isn't, @self may change size when a
+ * different child becomes visible.
+ *
+ * Since: 0.0.10
+ */
+void
+gtk_hdy_squeezer_set_homogeneous (GtkHdySqueezer *self,
+                                  gboolean        homogeneous)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  homogeneous = !!homogeneous;
+
+  if (self->homogeneous == homogeneous)
+    return;
+
+  self->homogeneous = homogeneous;
+
+  if (gtk_widget_get_visible (GTK_WIDGET(self)))
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOMOGENEOUS]);
+}
+
+/**
+ * gtk_hdy_squeezer_get_transition_duration:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets the amount of time (in milliseconds) that transitions between children
+ * in @self will take.
+ *
+ * Returns: the transition duration
+ */
+guint
+gtk_hdy_squeezer_get_transition_duration (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), 0);
+
+  return self->transition_duration;
+}
+
+/**
+ * gtk_hdy_squeezer_set_transition_duration:
+ * @self: a #GtkHdySqueezer
+ * @duration: the new duration, in milliseconds
+ *
+ * Sets the duration that transitions between children in @self will take.
+ */
+void
+gtk_hdy_squeezer_set_transition_duration (GtkHdySqueezer *self,
+                                          guint           duration)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  if (self->transition_duration == duration)
+    return;
+
+  self->transition_duration = duration;
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]);
+}
+
+/**
+ * gtk_hdy_squeezer_get_transition_type:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets the type of animation that will be used for transitions between children
+ * in @self.
+ *
+ * Returns: the current transition type of @self
+ */
+GtkHdySqueezerTransitionType
+gtk_hdy_squeezer_get_transition_type (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), GTK_HDY_SQUEEZER_TRANSITION_TYPE_NONE);
+
+  return self->transition_type;
+}
+
+/**
+ * gtk_hdy_squeezer_set_transition_type:
+ * @self: a #GtkHdySqueezer
+ * @transition: the new transition type
+ *
+ * Sets the type of animation that will be used for transitions between children
+ * in @self. Available types include various kinds of fades and slides.
+ *
+ * The transition type can be changed without problems at runtime, so it is
+ * possible to change the animation based on the child that is about to become
+ * current.
+ */
+void
+gtk_hdy_squeezer_set_transition_type (GtkHdySqueezer               *self,
+                                      GtkHdySqueezerTransitionType  transition)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  if (self->transition_type == transition)
+    return;
+
+  self->transition_type = transition;
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]);
+}
+
+/**
+ * gtk_hdy_squeezer_get_transition_running:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets whether @self is currently in a transition from one child to another.
+ *
+ * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
+ */
+gboolean
+gtk_hdy_squeezer_get_transition_running (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), FALSE);
+
+  return (self->tick_id != 0);
+}
+
+/**
+ * gtk_hdy_squeezer_get_interpolate_size:
+ * @self: A #GtkHdySqueezer
+ *
+ * Gets whether @self should interpolate its size on visible child change.
+ *
+ * See gtk_hdy_squeezer_set_interpolate_size().
+ *
+ * Returns: %TRUE if @self interpolates its size on visible child change, %FALSE if not
+ *
+ * Since: 0.0.10
+ */
+gboolean
+gtk_hdy_squeezer_get_interpolate_size (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), FALSE);
+
+  return self->interpolate_size;
+}
+
+/**
+ * gtk_hdy_squeezer_set_interpolate_size:
+ * @self: A #GtkHdySqueezer
+ * @interpolate_size: %TRUE to interpolate the size
+ *
+ * Sets whether or not @self will interpolate the size of its opposing
+ * orientation when changing the visible child. If %TRUE, @self will interpolate
+ * its size between the one of the previous visible child and the one of the new
+ * visible child, according to the set transition duration and the orientation,
+ * e.g. if @self is horizontal, it will interpolate the its height.
+ *
+ * Since: 0.0.10
+ */
+void
+gtk_hdy_squeezer_set_interpolate_size (GtkHdySqueezer *self,
+                                       gboolean        interpolate_size)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  interpolate_size = !!interpolate_size;
+
+  if (self->interpolate_size == interpolate_size)
+    return;
+
+  self->interpolate_size = interpolate_size;
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]);
+}
+
+/**
+ * gtk_hdy_squeezer_get_visible_child:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets the currently visible child of @self, or %NULL if there are no visible
+ * children.
+ *
+ * Returns: (transfer none) (nullable): the visible child of the #GtkHdySqueezer
+ */
+GtkWidget *
+gtk_hdy_squeezer_get_visible_child (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), NULL);
+
+  return self->visible_child ? self->visible_child->widget : NULL;
+}
+
+/**
+ * gtk_hdy_squeezer_get_child_enabled:
+ * @self: a #GtkHdySqueezer
+ * @child: a child of @self
+ *
+ * Gets whether @child is enabled.
+ *
+ * See gtk_hdy_squeezer_set_child_enabled().
+ *
+ * Returns: %TRUE if @child is enabled, %FALSE otherwise.
+ */
+gboolean
+gtk_hdy_squeezer_get_child_enabled (GtkHdySqueezer *self,
+                                    GtkWidget      *child)
+{
+  GtkHdySqueezerChildInfo *child_info;
+
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), FALSE);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), FALSE);
+
+  child_info = find_child_info_for_widget (self, child);
+
+  g_return_val_if_fail (child_info != NULL, FALSE);
+
+  return child_info->enabled;
+}
+
+/**
+ * gtk_hdy_squeezer_set_child_enabled:
+ * @self: a #GtkHdySqueezer
+ * @child: a child of @self
+ * @enabled: %TRUE to enable the child, %FALSE to disable it
+ *
+ * Make @self enable or disable @child. If a child is disabled, it will be
+ * ignored when looking for the child fitting the available size best. This
+ * allows to programmatically and prematurely hide a child of @self even if it
+ * fits in the available space.
+ *
+ * This can be used e.g. to ensure a certain child is hidden below a certain
+ * window width, or any other constraint you find suitable.
+ */
+void
+gtk_hdy_squeezer_set_child_enabled (GtkHdySqueezer *self,
+                                    GtkWidget      *child,
+                                    gboolean        enabled)
+{
+  GtkHdySqueezerChildInfo *child_info;
+
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  child_info = find_child_info_for_widget (self, child);
+
+  g_return_if_fail (child_info != NULL);
+
+  enabled = !!enabled;
+
+  if (child_info->enabled == enabled)
+    return;
+
+  child_info->enabled = enabled;
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+/**
+ * gtk_hdy_squeezer_get_xalign:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets the #GtkHdySqueezer:xalign property for @self.
+ *
+ * Returns: the xalign property
+ *
+ * Since: 1.0
+ */
+gfloat
+gtk_hdy_squeezer_get_xalign (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), 0.5);
+
+  return self->xalign;
+}
+
+/**
+ * gtk_hdy_squeezer_set_xalign:
+ * @self: a #GtkHdySqueezer
+ * @xalign: the new xalign value, between 0 and 1
+ *
+ * Sets the #GtkHdySqueezer:xalign property for @self.
+ *
+ * Since: 1.0
+ */
+void
+gtk_hdy_squeezer_set_xalign (GtkHdySqueezer *self,
+                             gfloat          xalign)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  xalign = CLAMP (xalign, 0.0, 1.0);
+
+  if (self->xalign == xalign)
+    return;
+
+  self->xalign = xalign;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_XALIGN]);
+}
+
+/**
+ * gtk_hdy_squeezer_get_yalign:
+ * @self: a #GtkHdySqueezer
+ *
+ * Gets the #GtkHdySqueezer:yalign property for @self.
+ *
+ * Returns: the yalign property
+ *
+ * Since: 1.0
+ */
+gfloat
+gtk_hdy_squeezer_get_yalign (GtkHdySqueezer *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_SQUEEZER (self), 0.5);
+
+  return self->yalign;
+}
+
+/**
+ * gtk_hdy_squeezer_set_yalign:
+ * @self: a #GtkHdySqueezer
+ * @yalign: the new yalign value, between 0 and 1
+ *
+ * Sets the #GtkHdySqueezer:yalign property for @self.
+ *
+ * Since: 1.0
+ */
+void
+gtk_hdy_squeezer_set_yalign (GtkHdySqueezer *self,
+                             gfloat          yalign)
+{
+  g_return_if_fail (GTK_IS_HDY_SQUEEZER (self));
+
+  yalign = CLAMP (yalign, 0.0, 1.0);
+
+  if (self->yalign == yalign)
+    return;
+
+  self->yalign = yalign;
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_YALIGN]);
+}
diff --git a/gtk/meson.build b/gtk/meson.build
index 02bf905..7c00b1b 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -385,6 +385,7 @@ gtk_sources = files(
   'gdkpixbufutils.c',
   'hdy-animation.c',
   'hdy-css.c',
+  'hdy-squeezer.c',
   'hdy-view-switcher-bar.c',
   'hdy-view-switcher-button.c',
   'hdy-view-switcher.c',
@@ -396,7 +397,9 @@ gtk_private_type_headers = files(
   'gtkcsstypesprivate.h',
   'gtktexthandleprivate.h',
   'hdy-animation-private.h',
+  'hdy-cairo-private.h',
   'hdy-css-private.h',
+  'hdy-squeezer-private.h',
   'hdy-view-switcher-bar-private.h',
   'hdy-view-switcher-button-private.h',
   'hdy-view-switcher-private.h',
openSUSE Build Service is sponsored by