File Add-GtkHdyFlap.patch of Package gtk3

From: Alexander Mikhaylenko <alexm@gnome.org>
Date: Wed, 16 Dec 2020 19:56:17 +0500
Subject: Add GtkHdyFlap

This is imported from HdyFlap from libhandy 1.0.3.
---
 gtk/gtkprivate.h       |    1 +
 gtk/hdy-flap-private.h |   92 ++
 gtk/hdy-flap.c         | 2663 ++++++++++++++++++++++++++++++++++++++++++++++++
 gtk/meson.build        |    2 +
 4 files changed, 2758 insertions(+)
 create mode 100644 gtk/hdy-flap-private.h
 create mode 100644 gtk/hdy-flap.c

diff --git a/gtk/gtkprivate.h b/gtk/gtkprivate.h
index 6e29dc1..8b3c0ff 100644
--- a/gtk/gtkprivate.h
+++ b/gtk/gtkprivate.h
@@ -30,6 +30,7 @@
 
 #include "gtkcsstypesprivate.h"
 #include "gtktexthandleprivate.h"
+#include "hdy-flap-private.h"
 #include "hdy-navigation-direction-private.h"
 #include "hdy-squeezer-private.h"
 #include "hdy-view-switcher-private.h"
diff --git a/gtk/hdy-flap-private.h b/gtk/hdy-flap-private.h
new file mode 100644
index 0000000..d0ac61f
--- /dev/null
+++ b/gtk/hdy-flap-private.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 Felix Häcker <haeckerfelix@gnome.org>
+ *
+ * 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_FLAP (gtk_hdy_flap_get_type ())
+
+G_DECLARE_FINAL_TYPE (GtkHdyFlap, gtk_hdy_flap, GTK, HDY_FLAP, GtkContainer)
+
+typedef enum {
+  GTK_HDY_FLAP_FOLD_POLICY_NEVER,
+  GTK_HDY_FLAP_FOLD_POLICY_ALWAYS,
+  GTK_HDY_FLAP_FOLD_POLICY_AUTO,
+} GtkHdyFlapFoldPolicy;
+
+typedef enum {
+  GTK_HDY_FLAP_TRANSITION_TYPE_OVER,
+  GTK_HDY_FLAP_TRANSITION_TYPE_UNDER,
+  GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE,
+} GtkHdyFlapTransitionType;
+
+GtkWidget *gtk_hdy_flap_new (void);
+
+GtkWidget *gtk_hdy_flap_get_content (GtkHdyFlap *self);
+void       gtk_hdy_flap_set_content (GtkHdyFlap *self,
+                                     GtkWidget  *content);
+
+GtkWidget *gtk_hdy_flap_get_flap (GtkHdyFlap *self);
+void       gtk_hdy_flap_set_flap (GtkHdyFlap *self,
+                                  GtkWidget  *flap);
+
+GtkWidget *gtk_hdy_flap_get_separator (GtkHdyFlap *self);
+void       gtk_hdy_flap_set_separator (GtkHdyFlap *self,
+                                       GtkWidget  *separator);
+
+GtkPackType gtk_hdy_flap_get_flap_position (GtkHdyFlap  *self);
+void        gtk_hdy_flap_set_flap_position (GtkHdyFlap  *self,
+                                            GtkPackType  position);
+
+gboolean gtk_hdy_flap_get_reveal_flap (GtkHdyFlap *self);
+void     gtk_hdy_flap_set_reveal_flap (GtkHdyFlap *self,
+                                       gboolean    reveal_flap);
+
+guint gtk_hdy_flap_get_reveal_duration (GtkHdyFlap *self);
+void  gtk_hdy_flap_set_reveal_duration (GtkHdyFlap *self,
+                                        guint       duration);
+
+gdouble gtk_hdy_flap_get_reveal_progress (GtkHdyFlap *self);
+
+GtkHdyFlapFoldPolicy gtk_hdy_flap_get_fold_policy (GtkHdyFlap           *self);
+void                 gtk_hdy_flap_set_fold_policy (GtkHdyFlap           *self,
+                                                   GtkHdyFlapFoldPolicy  policy);
+
+guint gtk_hdy_flap_get_fold_duration (GtkHdyFlap *self);
+void  gtk_hdy_flap_set_fold_duration (GtkHdyFlap *self,
+                                      guint       duration);
+
+gboolean gtk_hdy_flap_get_folded (GtkHdyFlap *self);
+
+gboolean gtk_hdy_flap_get_locked (GtkHdyFlap *self);
+void     gtk_hdy_flap_set_locked (GtkHdyFlap *self,
+                                  gboolean    locked);
+
+GtkHdyFlapTransitionType gtk_hdy_flap_get_transition_type (GtkHdyFlap               *self);
+void                     gtk_hdy_flap_set_transition_type (GtkHdyFlap               *self,
+                                                           GtkHdyFlapTransitionType  transition_type);
+
+gboolean gtk_hdy_flap_get_modal (GtkHdyFlap *self);
+void     gtk_hdy_flap_set_modal (GtkHdyFlap *self,
+                                 gboolean    modal);
+
+gboolean gtk_hdy_flap_get_swipe_to_open (GtkHdyFlap *self);
+void     gtk_hdy_flap_set_swipe_to_open (GtkHdyFlap *self,
+                                         gboolean    swipe_to_open);
+
+gboolean gtk_hdy_flap_get_swipe_to_close (GtkHdyFlap *self);
+void     gtk_hdy_flap_set_swipe_to_close (GtkHdyFlap *self,
+                                          gboolean    swipe_to_close);
+
+G_END_DECLS
diff --git a/gtk/hdy-flap.c b/gtk/hdy-flap.c
new file mode 100644
index 0000000..8735ad9
--- /dev/null
+++ b/gtk/hdy-flap.c
@@ -0,0 +1,2663 @@
+/*
+ * Copyright (C) 2020 Felix Häcker <haeckerfelix@gnome.org>
+ * Copyright (C) 2020 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "hdy-flap-private.h"
+
+#include <math.h>
+
+#include "gtkbuildable.h"
+#include "gtkgesturemultipress.h"
+#include "gtkeventcontrollerkey.h"
+#include "gtkorientable.h"
+#include "gtkstylecontext.h"
+#include "gtksizerequest.h"
+#include "gtkprivatetypebuiltins.h"
+#include "gtktypebuiltins.h"
+#include "hdy-animation-private.h"
+#include "hdy-shadow-helper-private.h"
+#include "hdy-swipeable-private.h"
+#include "hdy-swipe-tracker-private.h"
+
+/**
+ * SECTION:hdy-flap
+ * @short_description: An adaptive container acting like a box or an overlay.
+ * @Title: GtkHdyFlap
+ *
+ * The #GtkHdyFlap widget can display its children like a #GtkBox does or like a
+ * #GtkOverlay does, according to the #GtkHdyFlap:fold-policy value.
+ *
+ * #GtkHdyFlap has at most three children: #GtkHdyFlap:content, #GtkHdyFlap:flap and
+ * #GtkHdyFlap:separator. Content is the primary child, flap is displayed next to
+ * it when unfolded, or overlays it when folded. Flap can be shown or hidden by
+ * changing the #GtkHdyFlap:reveal-flap value, as well as via swipe gestures if
+ * #GtkHdyFlap:swipe-to-open and/or #GtkHdyFlap:swipe-to-close are set to %TRUE.
+ *
+ * Optionally, a separator can be provided, which would be displayed between
+ * the content and the flap when there's no shadow to separate them, depending
+ * on the transition type.
+ *
+ * #GtkHdyFlap:flap is transparent by default; add the .background style class to
+ * it if this is unwanted.
+ *
+ * If #GtkHdyFlap:modal is set to %TRUE, content becomes completely inaccessible
+ * when the flap is revealed when folded.
+ *
+ * The position of the flap and separator children relative to the content is
+ * determined by orientation, as well as  #GtkHdyFlap:flap-position value.
+ *
+ * Folding the flap will automatically hide the flap widget, and unfolding it
+ * will automatically reveal it. If this behavior is not desired, the
+ * #GtkHdyFlap:locked property can be used to override it.
+ *
+ * Common use cases include sidebars, header bars that need to be able to
+ * overlap the window content (for example, in fullscreen mode) and bottom
+ * sheets.
+ *
+ * # GtkHdyFlap as GtkBuildable
+ *
+ * The #GtkHdyFlap implementation of the #GtkBuildable interface supports setting
+ * the flap child by specifying “flap” as the “type” attribute of a
+ * &lt;child&gt; element, and separator by specifying “separator”. Specifying
+ * “content” child type or omitting it results in setting the content child.
+ *
+ * # CSS nodes
+ *
+ * #GtkHdyFlap has a single CSS node with name flap. The node will get the style
+ * classes .folded when it is folded, and .unfolded when it's not.
+ *
+ * Since: 1.1
+ */
+
+/**
+ * GtkHdyFlapFoldPolicy:
+ * @GTK_HDY_FLAP_FOLD_POLICY_NEVER: Disable folding, the flap cannot reach narrow
+ *   sizes.
+ * @GTK_HDY_FLAP_FOLD_POLICY_ALWAYS: Keep the flap always folded.
+ * @GTK_HDY_FLAP_FOLD_POLICY_AUTO: Fold and unfold the flap based on available
+ *   space.
+ *
+ * These enumeration values describe the possible folding behavior in a #GtkHdyFlap
+ * widget.
+ *
+ * Since: 1.1
+ */
+
+/**
+ * GtkHdyFlapTransitionType:
+ * @GTK_HDY_FLAP_TRANSITION_TYPE_OVER: The flap slides over the content, which is
+ *   dimmed. When folded, only the flap can be swiped.
+ * @GTK_HDY_FLAP_TRANSITION_TYPE_UNDER: The content slides over the flap. Only the
+ *   content can be swiped.
+ * @GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE: The flap slides offscreen when hidden,
+ *   neither the flap nor content overlap each other. Both widgets can be
+ *   swiped.
+ *
+ * These enumeration values describe the possible transitions between children
+ * in a #GtkHdyFlap widget, as well as which areas can be swiped via
+ * #GtkHdyFlap:swipe-to-open and #GtkHdyFlap:swipe-to-close.
+ *
+ * New values may be added to this enum over time.
+ *
+ * Since: 1.1
+ */
+
+typedef struct {
+  GtkWidget *widget;
+  GdkWindow *window;
+  GtkAllocation allocation;
+} ChildInfo;
+
+struct _GtkHdyFlap
+{
+  GtkContainer parent_instance;
+
+  ChildInfo content;
+  ChildInfo flap;
+  ChildInfo separator;
+
+  GtkHdyFlapFoldPolicy fold_policy;
+  GtkHdyFlapTransitionType transition_type;
+  GtkPackType flap_position;
+  gboolean reveal_flap;
+  gboolean locked;
+  gboolean folded;
+
+  guint fold_duration;
+  gdouble fold_progress;
+  GtkHdyAnimation *fold_animation;
+
+  guint reveal_duration;
+  gdouble reveal_progress;
+  GtkHdyAnimation *reveal_animation;
+
+  gboolean schedule_fold;
+
+  GtkOrientation orientation;
+
+  GtkHdyShadowHelper *shadow_helper;
+
+  gboolean swipe_to_open;
+  gboolean swipe_to_close;
+  GtkHdySwipeTracker *tracker;
+  gboolean swipe_active;
+
+  gboolean modal;
+  GtkGesture *click_gesture;
+  GtkEventController *key_controller;
+};
+
+static void gtk_hdy_flap_buildable_init (GtkBuildableIface *iface);
+static void gtk_hdy_flap_swipeable_init (GtkHdySwipeableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkHdyFlap, gtk_hdy_flap, GTK_TYPE_CONTAINER,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, gtk_hdy_flap_buildable_init)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_HDY_SWIPEABLE, gtk_hdy_flap_swipeable_init))
+
+enum {
+  PROP_0,
+  PROP_CONTENT,
+  PROP_FLAP,
+  PROP_SEPARATOR,
+  PROP_FLAP_POSITION,
+  PROP_REVEAL_FLAP,
+  PROP_REVEAL_DURATION,
+  PROP_REVEAL_PROGRESS,
+  PROP_FOLD_POLICY,
+  PROP_FOLD_DURATION,
+  PROP_FOLDED,
+  PROP_LOCKED,
+  PROP_TRANSITION_TYPE,
+  PROP_MODAL,
+  PROP_SWIPE_TO_OPEN,
+  PROP_SWIPE_TO_CLOSE,
+
+  /* Overridden properties */
+  PROP_ORIENTATION,
+
+  LAST_PROP = PROP_ORIENTATION,
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void
+update_swipe_tracker (GtkHdyFlap *self)
+{
+  gboolean reverse = self->flap_position == GTK_PACK_START;
+
+  if (!self->tracker)
+    return;
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
+      gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+    reverse = !reverse;
+
+  gtk_hdy_swipe_tracker_set_enabled (self->tracker, self->flap.widget &&
+                                     (self->swipe_to_open || self->swipe_to_close));
+  gtk_hdy_swipe_tracker_set_reversed (self->tracker, reverse);
+  gtk_orientable_set_orientation (GTK_ORIENTABLE (self->tracker),
+                                  self->orientation);
+}
+
+static void
+set_orientation (GtkHdyFlap     *self,
+                 GtkOrientation  orientation)
+{
+  if (self->orientation == orientation)
+    return;
+
+  self->orientation = orientation;
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+  update_swipe_tracker (self);
+
+  g_object_notify (G_OBJECT (self), "orientation");
+}
+
+static void
+update_child_visibility (GtkHdyFlap *self)
+{
+  gboolean visible = self->reveal_progress > 0;
+
+  if (self->flap.widget)
+    gtk_widget_set_child_visible (self->flap.widget, visible);
+
+  if (self->separator.widget)
+    gtk_widget_set_child_visible (self->separator.widget, visible);
+
+  if (!gtk_widget_get_realized (GTK_WIDGET (self)))
+    return;
+
+  if (self->flap.widget) {
+    if (visible)
+      gdk_window_show (self->flap.window);
+    else
+      gdk_window_hide (self->flap.window);
+  }
+
+  if (self->separator.widget) {
+    if (visible)
+      gdk_window_show (self->separator.window);
+    else
+      gdk_window_hide (self->separator.window);
+  }
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+set_reveal_progress (GtkHdyFlap *self,
+                     gdouble     progress)
+{
+  self->reveal_progress = progress;
+
+  update_child_visibility (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_PROGRESS]);
+}
+
+static void
+fold_animation_value_cb (gdouble     value,
+                         GtkHdyFlap *self)
+{
+  self->fold_progress = value;
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+fold_animation_done_cb (GtkHdyFlap *self)
+{
+  g_clear_pointer (&self->fold_animation, gtk_hdy_animation_unref);
+}
+
+static void
+animate_fold (GtkHdyFlap *self)
+{
+  if (self->fold_animation)
+    gtk_hdy_animation_stop (self->fold_animation);
+
+  self->fold_animation =
+    gtk_hdy_animation_new (GTK_WIDGET (self),
+                           self->fold_progress,
+                           self->folded ? 1 : 0,
+                           /* When the flap is completely hidden, we can skip animation */
+                           (self->reveal_progress > 0) ? self->fold_duration : 0,
+                           gtk_hdy_ease_out_cubic,
+                           (GtkHdyAnimationValueCallback) fold_animation_value_cb,
+                           (GtkHdyAnimationDoneCallback) fold_animation_done_cb,
+                           self);
+
+  gtk_hdy_animation_start (self->fold_animation);
+}
+
+static void
+reveal_animation_value_cb (gdouble     value,
+                           GtkHdyFlap *self)
+{
+  set_reveal_progress (self, value);
+}
+
+static void
+reveal_animation_done_cb (GtkHdyFlap *self)
+{
+  g_clear_pointer (&self->reveal_animation, gtk_hdy_animation_unref);
+
+  if (self->reveal_progress <= 0 ||
+      self->transition_type == GTK_HDY_FLAP_TRANSITION_TYPE_UNDER)
+    gtk_hdy_shadow_helper_clear_cache (self->shadow_helper);
+
+  if (self->schedule_fold) {
+    self->schedule_fold = FALSE;
+
+    animate_fold (self);
+  }
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+animate_reveal (GtkHdyFlap *self,
+                gdouble     to,
+                gint64      duration)
+{
+  if (self->reveal_animation)
+    gtk_hdy_animation_stop (self->reveal_animation);
+
+  self->reveal_animation =
+    gtk_hdy_animation_new (GTK_WIDGET (self),
+                           self->reveal_progress,
+                           to,
+                           duration,
+                           gtk_hdy_ease_out_cubic,
+                           (GtkHdyAnimationValueCallback) reveal_animation_value_cb,
+                           (GtkHdyAnimationDoneCallback) reveal_animation_done_cb,
+                           self);
+
+  gtk_hdy_animation_start (self->reveal_animation);
+}
+
+static void
+set_reveal_flap (GtkHdyFlap *self,
+                 gboolean    reveal_flap,
+                 guint64     duration,
+                 gboolean    emit_child_switched)
+{
+  reveal_flap = !!reveal_flap;
+
+  if (self->reveal_flap == reveal_flap)
+    return;
+
+  self->reveal_flap = reveal_flap;
+
+  if (!self->swipe_active) {
+    animate_reveal (self, reveal_flap ? 1 : 0, duration);
+
+    if (emit_child_switched)
+      gtk_hdy_swipeable_emit_child_switched (GTK_HDY_SWIPEABLE (self), reveal_flap ? 1 : 0, duration);
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_FLAP]);
+}
+
+static void
+set_folded (GtkHdyFlap *self,
+            gboolean    folded)
+{
+  GtkStyleContext *context;
+
+  folded = !!folded;
+
+  if (self->folded == folded)
+    return;
+
+  self->folded = folded;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+   /* When unlocked, folding should also hide flap. We don't want two concurrent
+    * animations in this case, instead only animate reveal and schedule a fold
+    * after it finishes, which will be skipped because the flap is fuly hidden.
+    * Meanwhile if it's unfolding, animate folding immediately. */
+  if (!self->locked && folded)
+    self->schedule_fold = TRUE;
+  else
+    animate_fold (self);
+
+  if (!self->locked)
+    set_reveal_flap (self, !self->folded, self->fold_duration, TRUE);
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (self));
+  if (folded) {
+    gtk_style_context_add_class (context, "folded");
+    gtk_style_context_remove_class (context, "unfolded");
+  } else {
+    gtk_style_context_remove_class (context, "folded");
+    gtk_style_context_add_class (context, "unfolded");
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLDED]);
+}
+
+static inline GtkPackType
+get_start_or_end (GtkHdyFlap *self)
+{
+  GtkTextDirection direction = gtk_widget_get_direction (GTK_WIDGET (self));
+  gboolean is_rtl = direction == GTK_TEXT_DIR_RTL;
+  gboolean is_horiz = self->orientation == GTK_ORIENTATION_HORIZONTAL;
+
+  return (is_rtl && is_horiz) ? GTK_PACK_END : GTK_PACK_START;
+}
+
+static void
+begin_swipe_cb (GtkHdySwipeTracker        *tracker,
+                GtkHdyNavigationDirection  direction,
+                gboolean                   direct,
+                GtkHdyFlap                *self)
+{
+  if (self->reveal_progress <= 0 && !self->swipe_to_open)
+    return;
+
+  if (self->reveal_progress >= 1 && !self->swipe_to_close)
+    return;
+
+  if (self->reveal_animation)
+    gtk_hdy_animation_stop (self->reveal_animation);
+
+  self->swipe_active = TRUE;
+}
+
+static void
+update_swipe_cb (GtkHdySwipeTracker *tracker,
+                 gdouble             progress,
+                 GtkHdyFlap         *self)
+{
+  if (!self->swipe_active)
+    return;
+
+  set_reveal_progress (self, progress);
+}
+
+static void
+end_swipe_cb (GtkHdySwipeTracker *tracker,
+              gint64              duration,
+              gdouble             to,
+              GtkHdyFlap         *self)
+{
+  if (!self->swipe_active)
+    return;
+
+  self->swipe_active = FALSE;
+
+  if ((to > 0) == self->reveal_flap)
+    animate_reveal (self, to, duration);
+  else
+    set_reveal_flap (self, to > 0, duration, FALSE);
+}
+
+static void
+released_cb (GtkGestureMultiPress *gesture,
+             gint                  n_press,
+             gdouble               x,
+             gdouble               y,
+             GtkHdyFlap           *self)
+{
+  if (self->reveal_progress <= 0 || self->fold_progress <= 0) {
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+
+    return;
+  }
+
+  if (x >= self->flap.allocation.x &&
+      x <= self->flap.allocation.x + self->flap.allocation.width &&
+      y >= self->flap.allocation.y &&
+      y <= self->flap.allocation.y + self->flap.allocation.height) {
+    gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
+
+    return;
+  }
+
+  gtk_hdy_flap_set_reveal_flap (self, FALSE);
+}
+
+static gboolean
+key_pressed_cb (GtkEventControllerKey *controller,
+                guint                  keyval,
+                guint                  keycode,
+                GdkModifierType        modifiers,
+                GtkHdyFlap            *self)
+{
+  if (keyval == GDK_KEY_Escape &&
+      self->reveal_progress > 0 &&
+      self->fold_progress > 0) {
+    gtk_hdy_flap_set_reveal_flap (self, FALSE);
+
+    return GDK_EVENT_STOP;
+  }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+register_window (GtkHdyFlap *self,
+                 ChildInfo  *info)
+{
+  GdkWindowAttr attributes = { 0 };
+  GdkWindowAttributesType attributes_mask;
+
+  if (!info->widget)
+    return;
+
+  attributes.x = info->allocation.x;
+  attributes.y = info->allocation.y;
+  attributes.width = info->allocation.width;
+  attributes.height = info->allocation.height;
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes.visual = gtk_widget_get_visual (info->widget);
+  attributes.event_mask = gtk_widget_get_events (info->widget);
+  attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
+
+  attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (self)) |
+                          gtk_widget_get_events (info->widget);
+
+  info->window = gdk_window_new (gtk_widget_get_window (GTK_WIDGET (self)),
+                                 &attributes, attributes_mask);
+  gtk_widget_register_window (GTK_WIDGET (self), info->window);
+
+  gtk_widget_set_parent_window (info->widget, info->window);
+
+  gdk_window_show (info->window);
+}
+
+static void
+unregister_window (GtkHdyFlap *self,
+                   ChildInfo  *info)
+{
+  if (!info->window)
+    return;
+
+  gtk_widget_unregister_window (GTK_WIDGET (self), info->window);
+  gdk_window_destroy (info->window);
+  info->window = NULL;
+}
+
+static gboolean
+transition_is_content_above_flap (GtkHdyFlap *self)
+{
+  switch (self->transition_type) {
+  case GTK_HDY_FLAP_TRANSITION_TYPE_OVER:
+    return FALSE;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_UNDER:
+  case GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE:
+    return TRUE;
+
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static gboolean
+transition_should_clip (GtkHdyFlap *self)
+{
+  switch (self->transition_type) {
+  case GTK_HDY_FLAP_TRANSITION_TYPE_OVER:
+  case GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE:
+    return FALSE;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_UNDER:
+    return TRUE;
+
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static gdouble
+transition_get_content_motion_factor (GtkHdyFlap *self)
+{
+  switch (self->transition_type) {
+  case GTK_HDY_FLAP_TRANSITION_TYPE_OVER:
+    return 0;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_UNDER:
+  case GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE:
+    return 1;
+
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static gdouble
+transition_get_flap_motion_factor (GtkHdyFlap *self)
+{
+  switch (self->transition_type) {
+  case GTK_HDY_FLAP_TRANSITION_TYPE_OVER:
+  case GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE:
+    return 1;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_UNDER:
+    return 0;
+
+  default:
+    g_assert_not_reached ();
+  }
+}
+
+static void
+restack_windows (GtkHdyFlap *self)
+{
+  gboolean content_above_flap = transition_is_content_above_flap (self);
+
+  if (!content_above_flap) {
+    if (self->content.window)
+      gdk_window_raise (self->content.window);
+
+    if (self->separator.window)
+      gdk_window_raise (self->separator.window);
+  }
+
+  if (self->flap.window)
+    gdk_window_raise (self->flap.window);
+
+  if (content_above_flap) {
+    if (self->separator.window)
+      gdk_window_raise (self->separator.window);
+
+    if (self->content.window)
+      gdk_window_raise (self->content.window);
+  }
+}
+
+static void
+add_child (GtkHdyFlap *self,
+           ChildInfo  *info)
+{
+  if (gtk_widget_get_realized (GTK_WIDGET (self))) {
+    register_window (self, info);
+    restack_windows (self);
+  }
+
+  gtk_widget_set_parent (info->widget, GTK_WIDGET (self));
+}
+
+static void
+remove_child (GtkHdyFlap *self,
+              ChildInfo  *info)
+{
+  if (gtk_widget_get_realized (GTK_WIDGET (self)))
+    unregister_window (self, info);
+
+  gtk_widget_unparent (info->widget);
+}
+
+static inline void
+get_preferred_size (GtkWidget      *widget,
+                    GtkOrientation  orientation,
+                    gint           *min,
+                    gint           *nat)
+{
+  if (orientation == GTK_ORIENTATION_HORIZONTAL)
+    gtk_widget_get_preferred_width (widget, min, nat);
+  else
+    gtk_widget_get_preferred_height (widget, min, nat);
+}
+
+static void
+compute_sizes (GtkHdyFlap    *self,
+               GtkAllocation *alloc,
+               gboolean       folded,
+               gboolean       revealed,
+               gint          *flap_size,
+               gint          *content_size,
+               gint          *separator_size)
+{
+  gboolean flap_expand, content_expand;
+  gint total, extra;
+  gint flap_nat, content_nat;
+
+  if (!self->flap.widget && !self->content.widget)
+    return;
+
+  if (self->separator.widget)
+    get_preferred_size (self->separator.widget, self->orientation, separator_size, NULL);
+  else
+    *separator_size = 0;
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
+    total = alloc->width;
+  else
+    total = alloc->height;
+
+  if (!self->flap.widget) {
+    *content_size = total;
+    *flap_size = 0;
+
+    return;
+  }
+
+  if (!self->content.widget) {
+    *content_size = 0;
+    *flap_size = total;
+
+    return;
+  }
+
+  get_preferred_size (self->flap.widget, self->orientation, flap_size, &flap_nat);
+  get_preferred_size (self->content.widget, self->orientation, content_size, &content_nat);
+
+  flap_expand = gtk_widget_compute_expand (self->flap.widget, self->orientation);
+  content_expand = gtk_widget_compute_expand (self->content.widget, self->orientation);
+
+  if (folded) {
+    *content_size = total;
+
+    if (flap_expand) {
+      *flap_size = total;
+    } else {
+      get_preferred_size (self->flap.widget, self->orientation, NULL, flap_size);
+      *flap_size = MIN (*flap_size, total);
+    }
+
+    return;
+  }
+
+  if (revealed)
+    total -= *separator_size;
+
+  if (flap_expand && content_expand) {
+    *flap_size = MAX (total / 2, *flap_size);
+
+    if (!revealed)
+      *content_size = total;
+    else
+      *content_size = total - *flap_size;
+
+    return;
+  }
+
+  extra = total - *content_size - *flap_size;
+
+  if (flap_expand) {
+    *flap_size += extra;
+    extra = 0;
+
+    if (!revealed)
+      *content_size = total;
+
+    return;
+  }
+
+  if (content_expand) {
+    *content_size += extra;
+    extra = 0;
+  }
+
+  if (extra > 0) {
+    GtkRequestedSize sizes[2];
+
+    sizes[0].data = self->flap.widget;
+    sizes[0].minimum_size = *flap_size;
+    sizes[0].natural_size = flap_nat;
+
+    sizes[1].data = self->content.widget;
+    sizes[1].minimum_size = *content_size;
+    sizes[1].natural_size = content_nat;
+
+    extra = gtk_distribute_natural_allocation (extra, 2, sizes);
+
+    *flap_size = sizes[0].minimum_size;
+    *content_size = sizes[1].minimum_size + extra;
+  }
+
+  if (!revealed)
+    *content_size = total;
+}
+
+static inline void
+interpolate_reveal (GtkHdyFlap    *self,
+                    GtkAllocation *alloc,
+                    gboolean       folded,
+                    gint          *flap_size,
+                    gint          *content_size,
+                    gint          *separator_size)
+{
+  if (self->reveal_progress <= 0) {
+    compute_sizes (self, alloc, folded, FALSE, flap_size, content_size, separator_size);
+  } else if (self->reveal_progress >= 1) {
+    compute_sizes (self, alloc, folded, TRUE, flap_size, content_size, separator_size);
+  } else {
+    gint flap_revealed, content_revealed, separator_revealed;
+    gint flap_hidden, content_hidden, separator_hidden;
+
+    compute_sizes (self, alloc, folded, TRUE, &flap_revealed, &content_revealed, &separator_revealed);
+    compute_sizes (self, alloc, folded, FALSE, &flap_hidden, &content_hidden, &separator_hidden);
+
+    *flap_size =
+      (gint) round (gtk_hdy_lerp (flap_hidden, flap_revealed,
+                                  self->reveal_progress));
+    *content_size =
+      (gint) round (gtk_hdy_lerp (content_hidden, content_revealed,
+                                  self->reveal_progress));
+    *separator_size =
+      (gint) round (gtk_hdy_lerp (separator_hidden, separator_revealed,
+                                  self->reveal_progress));
+  }
+}
+
+static inline void
+interpolate_fold (GtkHdyFlap    *self,
+                  GtkAllocation *alloc,
+                  gint          *flap_size,
+                  gint          *content_size,
+                  gint          *separator_size)
+{
+  if (self->fold_progress <= 0) {
+    interpolate_reveal (self, alloc, FALSE, flap_size, content_size, separator_size);
+  } else if (self->fold_progress >= 1) {
+    interpolate_reveal (self, alloc, TRUE, flap_size, content_size, separator_size);
+  } else {
+    gint flap_folded, content_folded, separator_folded;
+    gint flap_unfolded, content_unfolded, separator_unfolded;
+
+    interpolate_reveal (self, alloc, TRUE, &flap_folded, &content_folded, &separator_folded);
+    interpolate_reveal (self, alloc, FALSE, &flap_unfolded, &content_unfolded, &separator_unfolded);
+
+    *flap_size =
+      (gint) round (gtk_hdy_lerp (flap_unfolded, flap_folded,
+                                  self->fold_progress));
+    *content_size =
+      (gint) round (gtk_hdy_lerp (content_unfolded, content_folded,
+                                  self->fold_progress));
+    *separator_size =
+      (gint) round (gtk_hdy_lerp (separator_unfolded, separator_folded,
+                                  self->fold_progress));
+  }
+}
+
+static void
+compute_allocation (GtkHdyFlap    *self,
+                    GtkAllocation *alloc,
+                    GtkAllocation *flap_alloc,
+                    GtkAllocation *content_alloc,
+                    GtkAllocation *separator_alloc)
+{
+  gdouble distance;
+  gint content_size, flap_size, separator_size;
+  gint total, content_pos, flap_pos, separator_pos;
+  gboolean content_above_flap = transition_is_content_above_flap (self);
+
+  if (!self->flap.widget && !self->content.widget && !self->separator.widget)
+    return;
+
+  content_alloc->x = 0;
+  content_alloc->y = 0;
+  flap_alloc->x = 0;
+  flap_alloc->y = 0;
+  separator_alloc->x = 0;
+  separator_alloc->y = 0;
+
+  interpolate_fold (self, alloc, &flap_size, &content_size, &separator_size);
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
+    flap_alloc->width = flap_size;
+    content_alloc->width = content_size;
+    separator_alloc->width = separator_size;
+    flap_alloc->height = content_alloc->height = separator_alloc->height = alloc->height;
+    total = alloc->width;
+  } else {
+    flap_alloc->height = flap_size;
+    content_alloc->height = content_size;
+    separator_alloc->height = separator_size;
+    flap_alloc->width = content_alloc->width = separator_alloc->width = alloc->width;
+    total = alloc->height;
+  }
+
+  if (!self->flap.widget)
+    return;
+
+  if (content_above_flap)
+    distance = flap_size + separator_size;
+  else
+    distance = flap_size + separator_size * (1 - self->fold_progress);
+
+  flap_pos = -(gint) round ((1 - self->reveal_progress) * transition_get_flap_motion_factor (self) * distance);
+
+  if (content_above_flap) {
+    content_pos = (gint) round (self->reveal_progress * transition_get_content_motion_factor (self) * distance);
+    separator_pos = flap_pos + flap_size;
+  } else {
+    content_pos = total - content_size + (gint) round (self->reveal_progress * self->fold_progress * transition_get_content_motion_factor (self) * distance);
+    separator_pos = content_pos - separator_size;
+  }
+
+  if (self->flap_position != get_start_or_end (self)) {
+    flap_pos = total - flap_pos - flap_size;
+    separator_pos = total - separator_pos - separator_size;
+    content_pos = total - content_pos - content_size;
+  }
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
+    content_alloc->x = content_pos;
+    flap_alloc->x = flap_pos;
+    separator_alloc->x = separator_pos;
+  } else {
+    content_alloc->y = content_pos;
+    flap_alloc->y = flap_pos;
+    separator_alloc->y = separator_pos;
+  }
+}
+
+static inline void
+allocate_child (GtkHdyFlap *self,
+                ChildInfo  *info,
+                gboolean    expand_window)
+{
+  GtkAllocation child_alloc;
+
+  if (!info->widget)
+    return;
+
+  if (gtk_widget_get_realized (GTK_WIDGET (self))) {
+    if (expand_window)
+      gdk_window_move_resize (info->window,
+                              0, 0,
+                              gtk_widget_get_allocated_width (GTK_WIDGET (self)),
+                              gtk_widget_get_allocated_height (GTK_WIDGET (self)));
+    else
+      gdk_window_move_resize (info->window,
+                              info->allocation.x,
+                              info->allocation.y,
+                              info->allocation.width,
+                              info->allocation.height);
+  }
+
+  child_alloc.x = expand_window ? info->allocation.x : 0;
+  child_alloc.y = expand_window ? info->allocation.y : 0;
+  child_alloc.width = info->allocation.width;
+  child_alloc.height = info->allocation.height;
+
+  gtk_widget_size_allocate (info->widget, &child_alloc);
+}
+
+static void
+gtk_hdy_flap_size_allocate (GtkWidget     *widget,
+                            GtkAllocation *alloc)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+
+  gtk_widget_set_allocation (widget, alloc);
+
+  if (gtk_widget_get_realized (widget))
+    gdk_window_move_resize (gtk_widget_get_window (widget),
+                            alloc->x, alloc->y, alloc->width, alloc->height);
+
+  if (self->fold_policy == GTK_HDY_FLAP_FOLD_POLICY_AUTO) {
+    GtkRequisition flap_min = { 0, 0 };
+    GtkRequisition content_min = { 0, 0 };
+    GtkRequisition separator_min = { 0, 0 };
+
+    if (self->flap.widget)
+      gtk_widget_get_preferred_size (self->flap.widget, &flap_min, NULL);
+    if (self->content.widget)
+      gtk_widget_get_preferred_size (self->content.widget, &content_min, NULL);
+    if (self->separator.widget)
+      gtk_widget_get_preferred_size (self->separator.widget, &separator_min, NULL);
+
+    if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
+      set_folded (self, alloc->width < content_min.width + flap_min.width + separator_min.width);
+    else
+      set_folded (self, alloc->height < content_min.height + flap_min.height + separator_min.height);
+  }
+
+  compute_allocation (self,
+                      alloc,
+                      &self->flap.allocation,
+                      &self->content.allocation,
+                      &self->separator.allocation);
+
+  allocate_child (self, &self->content, FALSE);
+  allocate_child (self, &self->separator, FALSE);
+  allocate_child (self, &self->flap,
+                  self->modal &&
+                  self->reveal_progress > 0 &&
+                  self->fold_progress > 0);
+
+  gtk_widget_set_clip (widget, alloc);
+}
+
+/* This private method is prefixed by the call name because it will be a virtual
+ * method in GTK 4.
+ */
+static void
+gtk_hdy_flap_measure (GtkWidget      *widget,
+                      GtkOrientation  orientation,
+                      gint            for_size,
+                      gint           *minimum,
+                      gint           *natural,
+                      gint           *minimum_baseline,
+                      gint           *natural_baseline)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+
+  gint content_min = 0, content_nat = 0;
+  gint flap_min = 0, flap_nat = 0;
+  gint separator_min = 0, separator_nat = 0;
+  gint min, nat;
+
+  if (self->content.widget)
+    get_preferred_size (self->content.widget, orientation, &content_min, &content_nat);
+
+  if (self->flap.widget)
+    get_preferred_size (self->flap.widget, orientation, &flap_min, &flap_nat);
+
+  if (self->separator.widget)
+    get_preferred_size (self->separator.widget, orientation, &separator_min, &separator_nat);
+
+  if (self->orientation == orientation) {
+    gdouble min_progress, nat_progress;
+
+    switch (self->fold_policy) {
+    case GTK_HDY_FLAP_FOLD_POLICY_NEVER:
+      min_progress = (1 - self->fold_progress) * self->reveal_progress;
+      nat_progress = 1;
+      break;
+
+    case GTK_HDY_FLAP_FOLD_POLICY_ALWAYS:
+      min_progress = 0;
+      nat_progress = 0;
+      break;
+
+    case GTK_HDY_FLAP_FOLD_POLICY_AUTO:
+      min_progress = 0;
+      nat_progress = self->locked ? self->reveal_progress : 1;
+      break;
+
+    default:
+      g_assert_not_reached ();
+    }
+
+    min = MAX (content_min + (gint) round ((flap_min + separator_min) * min_progress), flap_min);
+    nat = MAX (content_nat + (gint) round ((flap_nat + separator_min) * nat_progress), flap_nat);
+  } else {
+    min = MAX (MAX (content_min, flap_min), separator_min);
+    nat = MAX (MAX (content_nat, flap_nat), separator_nat);
+  }
+
+  if (minimum)
+    *minimum = min;
+  if (natural)
+    *natural = nat;
+  if (minimum_baseline)
+    *minimum_baseline = -1;
+  if (natural_baseline)
+    *natural_baseline = -1;
+}
+
+static void
+gtk_hdy_flap_get_preferred_width_for_height (GtkWidget *widget,
+                                             gint       height,
+                                             gint      *minimum,
+                                             gint      *natural)
+{
+  gtk_hdy_flap_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_flap_get_preferred_width (GtkWidget *widget,
+                                  gint      *minimum,
+                                  gint      *natural)
+{
+  gtk_hdy_flap_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
+                        minimum, natural, NULL, NULL);
+}
+
+
+static void
+gtk_hdy_flap_get_preferred_height_for_width (GtkWidget *widget,
+                                             gint       width,
+                                             gint      *minimum,
+                                             gint      *natural)
+{
+  gtk_hdy_flap_measure (widget, GTK_ORIENTATION_VERTICAL, width,
+                        minimum, natural, NULL, NULL);
+}
+
+static void
+gtk_hdy_flap_get_preferred_height (GtkWidget *widget,
+                                   gint      *minimum,
+                                   gint      *natural)
+{
+  gtk_hdy_flap_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
+                        minimum, natural, NULL, NULL);
+}
+
+static gboolean
+gtk_hdy_flap_draw (GtkWidget *widget,
+                   cairo_t   *cr)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+  gint width, height;
+  gint shadow_x = 0, shadow_y = 0;
+  gdouble shadow_progress;
+  GtkPanDirection shadow_direction;
+  gboolean content_above_flap = transition_is_content_above_flap (self);
+  GtkAllocation *shadow_alloc;
+  gboolean should_clip;
+
+  shadow_alloc = content_above_flap ? &self->content.allocation : &self->flap.allocation;
+
+  width = gtk_widget_get_allocated_width (widget);
+  height = gtk_widget_get_allocated_height (widget);
+
+  if (self->orientation == GTK_ORIENTATION_VERTICAL) {
+    if ((self->flap_position == GTK_PACK_START) != content_above_flap) {
+      shadow_direction = GTK_PAN_DIRECTION_UP;
+      shadow_y = shadow_alloc->y + shadow_alloc->height;
+    } else {
+      shadow_direction = GTK_PAN_DIRECTION_DOWN;
+      shadow_y = shadow_alloc->y - height;
+    }
+  } else {
+    if ((self->flap_position == get_start_or_end (self)) != content_above_flap) {
+      shadow_direction = GTK_PAN_DIRECTION_LEFT;
+      shadow_x = shadow_alloc->x + shadow_alloc->width;
+    } else {
+      shadow_direction = GTK_PAN_DIRECTION_RIGHT;
+      shadow_x = shadow_alloc->x - width;
+    }
+  }
+
+  switch (self->transition_type) {
+  case GTK_HDY_FLAP_TRANSITION_TYPE_OVER:
+    shadow_progress = 1 - MIN (self->reveal_progress, self->fold_progress);
+    break;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_UNDER:
+    shadow_progress = self->reveal_progress;
+    break;
+
+  case GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE:
+    shadow_progress = 1;
+    break;
+
+  default:
+    g_assert_not_reached ();
+  }
+
+  should_clip = transition_should_clip (self) &&
+                shadow_progress < 1 &&
+                self->reveal_progress > 0;
+
+  if (should_clip) {
+    cairo_save (cr);
+    cairo_rectangle (cr, shadow_x, shadow_y, width, height);
+    cairo_clip (cr);
+  }
+
+  if (!content_above_flap) {
+    if (self->content.widget)
+      gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                    self->content.widget,
+                                    cr);
+
+    if (self->separator.widget)
+      gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                    self->separator.widget,
+                                    cr);
+
+    if (should_clip)
+      cairo_restore (cr);
+  }
+
+  if (self->flap.widget)
+    gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                  self->flap.widget,
+                                  cr);
+
+  if (content_above_flap) {
+    if (self->separator.widget)
+      gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                    self->separator.widget,
+                                    cr);
+
+    if (should_clip)
+      cairo_restore (cr);
+
+    if (self->content.widget)
+      gtk_container_propagate_draw (GTK_CONTAINER (self),
+                                    self->content.widget,
+                                    cr);
+  }
+
+  if (!self->flap.widget)
+    return GDK_EVENT_PROPAGATE;
+
+  if (shadow_progress < 1 && gtk_widget_get_mapped (self->flap.widget)) {
+    cairo_save (cr);
+    cairo_translate (cr, shadow_x, shadow_y);
+    gtk_hdy_shadow_helper_draw_shadow (self->shadow_helper, cr, width, height,
+                                       shadow_progress, shadow_direction);
+    cairo_restore (cr);
+  }
+
+  return GDK_EVENT_PROPAGATE;
+}
+
+static void
+gtk_hdy_flap_realize (GtkWidget *widget)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+  GtkAllocation allocation;
+  GdkWindowAttr attributes;
+  gint attributes_mask;
+  GdkWindow *window;
+
+  gtk_widget_get_allocation (widget, &allocation);
+  gtk_widget_set_realized (widget, TRUE);
+
+  attributes.x = allocation.x;
+  attributes.y = allocation.y;
+  attributes.width = allocation.width;
+  attributes.height = allocation.height;
+  attributes.window_type = GDK_WINDOW_CHILD;
+  attributes.event_mask = gtk_widget_get_events (widget);
+  attributes.visual = gtk_widget_get_visual (widget);
+  attributes.wclass = GDK_INPUT_OUTPUT;
+  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
+
+  window = gdk_window_new (gtk_widget_get_parent_window (widget),
+                           &attributes,
+                           attributes_mask);
+  gtk_widget_set_window (widget, window);
+  gtk_widget_register_window (widget, window);
+
+  register_window (self, &self->content);
+  register_window (self, &self->separator);
+  register_window (self, &self->flap);
+
+  restack_windows (self);
+}
+
+static void
+gtk_hdy_flap_unrealize (GtkWidget *widget)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+
+  unregister_window (self, &self->content);
+  unregister_window (self, &self->separator);
+  unregister_window (self, &self->flap);
+
+  GTK_WIDGET_CLASS (gtk_hdy_flap_parent_class)->unrealize (widget);
+}
+
+static void
+gtk_hdy_flap_direction_changed (GtkWidget        *widget,
+                                GtkTextDirection  previous_direction)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (widget);
+
+  update_swipe_tracker (self);
+
+  GTK_WIDGET_CLASS (gtk_hdy_flap_parent_class)->direction_changed (widget,
+                                                                   previous_direction);
+}
+
+static void
+gtk_hdy_flap_forall (GtkContainer *container,
+                     gboolean      include_internals,
+                     GtkCallback   callback,
+                     gpointer      callback_data)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (container);
+
+  if (self->content.widget)
+    callback (self->content.widget, callback_data);
+
+  if (self->separator.widget)
+    callback (self->separator.widget, callback_data);
+
+  if (self->flap.widget)
+    callback (self->flap.widget, callback_data);
+}
+
+static void
+gtk_hdy_flap_add (GtkContainer *container,
+                  GtkWidget    *widget)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (container);
+
+  if (self->content.widget) {
+    g_warning ("Attempting to add a widget with type %s to a %s, "
+               "but %s can only contain one widget at a time; "
+               "it already contains a widget of type %s",
+               g_type_name (G_OBJECT_TYPE (widget)),
+               g_type_name (G_OBJECT_TYPE (self)),
+               g_type_name (G_OBJECT_TYPE (self)),
+               g_type_name (G_OBJECT_TYPE (self->content.widget)));
+
+    return;
+  }
+
+  gtk_hdy_flap_set_content (self, widget);
+}
+
+static void
+gtk_hdy_flap_remove (GtkContainer *container,
+                     GtkWidget    *widget)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (container);
+
+  if (widget == self->flap.widget)
+    gtk_hdy_flap_set_flap (self, NULL);
+  else if (widget == self->separator.widget)
+    gtk_hdy_flap_set_separator (self, NULL);
+  else if (widget == self->content.widget)
+    gtk_hdy_flap_set_content (self, NULL);
+  else
+    g_return_if_reached ();
+}
+
+static void
+gtk_hdy_flap_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (object);
+
+  switch (prop_id) {
+  case PROP_CONTENT:
+    g_value_set_object (value, gtk_hdy_flap_get_content (self));
+    break;
+  case PROP_FLAP:
+    g_value_set_object (value, gtk_hdy_flap_get_flap (self));
+    break;
+  case PROP_SEPARATOR:
+    g_value_set_object (value, gtk_hdy_flap_get_separator (self));
+    break;
+  case PROP_FLAP_POSITION:
+    g_value_set_enum (value, gtk_hdy_flap_get_flap_position (self));
+    break;
+  case PROP_REVEAL_FLAP:
+    g_value_set_boolean (value, gtk_hdy_flap_get_reveal_flap (self));
+    break;
+  case PROP_REVEAL_DURATION:
+    g_value_set_uint (value, gtk_hdy_flap_get_reveal_duration (self));
+    break;
+  case PROP_REVEAL_PROGRESS:
+    g_value_set_double (value, gtk_hdy_flap_get_reveal_progress (self));
+    break;
+  case PROP_FOLD_POLICY:
+    g_value_set_enum (value, gtk_hdy_flap_get_fold_policy (self));
+    break;
+  case PROP_FOLD_DURATION:
+    g_value_set_uint (value, gtk_hdy_flap_get_fold_duration (self));
+    break;
+  case PROP_FOLDED:
+    g_value_set_boolean (value, gtk_hdy_flap_get_folded (self));
+    break;
+  case PROP_LOCKED:
+    g_value_set_boolean (value, gtk_hdy_flap_get_locked (self));
+    break;
+  case PROP_TRANSITION_TYPE:
+    g_value_set_enum (value, gtk_hdy_flap_get_transition_type (self));
+    break;
+  case PROP_MODAL:
+    g_value_set_boolean (value, gtk_hdy_flap_get_modal (self));
+    break;
+  case PROP_SWIPE_TO_OPEN:
+    g_value_set_boolean (value, gtk_hdy_flap_get_swipe_to_open (self));
+    break;
+  case PROP_SWIPE_TO_CLOSE:
+    g_value_set_boolean (value, gtk_hdy_flap_get_swipe_to_close (self));
+    break;
+  case PROP_ORIENTATION:
+    g_value_set_enum (value, self->orientation);
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gtk_hdy_flap_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (object);
+
+  switch (prop_id) {
+  case PROP_CONTENT:
+    gtk_hdy_flap_set_content (self, g_value_get_object (value));
+    break;
+  case PROP_FLAP:
+    gtk_hdy_flap_set_flap (self, g_value_get_object (value));
+    break;
+  case PROP_SEPARATOR:
+    gtk_hdy_flap_set_separator (self, g_value_get_object (value));
+    break;
+  case PROP_FLAP_POSITION:
+    gtk_hdy_flap_set_flap_position (self, g_value_get_enum (value));
+    break;
+  case PROP_REVEAL_FLAP:
+    gtk_hdy_flap_set_reveal_flap (self, g_value_get_boolean (value));
+    break;
+  case PROP_REVEAL_DURATION:
+    gtk_hdy_flap_set_reveal_duration (self, g_value_get_uint (value));
+    break;
+  case PROP_FOLD_POLICY:
+    gtk_hdy_flap_set_fold_policy (self, g_value_get_enum (value));
+    break;
+  case PROP_FOLD_DURATION:
+    gtk_hdy_flap_set_fold_duration (self, g_value_get_uint (value));
+    break;
+  case PROP_LOCKED:
+    gtk_hdy_flap_set_locked (self, g_value_get_boolean (value));
+    break;
+  case PROP_TRANSITION_TYPE:
+    gtk_hdy_flap_set_transition_type (self, g_value_get_enum (value));
+    break;
+  case PROP_MODAL:
+    gtk_hdy_flap_set_modal (self, g_value_get_boolean (value));
+    break;
+  case PROP_SWIPE_TO_OPEN:
+    gtk_hdy_flap_set_swipe_to_open (self, g_value_get_boolean (value));
+    break;
+  case PROP_SWIPE_TO_CLOSE:
+    gtk_hdy_flap_set_swipe_to_close (self, g_value_get_boolean (value));
+    break;
+  case PROP_ORIENTATION:
+    set_orientation (self, g_value_get_enum (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+gtk_hdy_flap_dispose (GObject *object)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (object);
+
+  g_clear_object (&self->shadow_helper);
+  g_clear_object (&self->tracker);
+  g_clear_object (&self->click_gesture);
+  g_clear_object (&self->key_controller);
+
+  G_OBJECT_CLASS (gtk_hdy_flap_parent_class)->dispose (object);
+}
+
+static void
+gtk_hdy_flap_class_init (GtkHdyFlapClass *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_flap_get_property;
+  object_class->set_property = gtk_hdy_flap_set_property;
+  object_class->dispose = gtk_hdy_flap_dispose;
+
+  widget_class->get_preferred_width = gtk_hdy_flap_get_preferred_width;
+  widget_class->get_preferred_width_for_height = gtk_hdy_flap_get_preferred_width_for_height;
+  widget_class->get_preferred_height = gtk_hdy_flap_get_preferred_height;
+  widget_class->get_preferred_height_for_width = gtk_hdy_flap_get_preferred_height_for_width;
+  widget_class->size_allocate = gtk_hdy_flap_size_allocate;
+  widget_class->draw = gtk_hdy_flap_draw;
+  widget_class->realize = gtk_hdy_flap_realize;
+  widget_class->unrealize = gtk_hdy_flap_unrealize;
+  widget_class->direction_changed = gtk_hdy_flap_direction_changed;
+
+  container_class->remove = gtk_hdy_flap_remove;
+  container_class->add = gtk_hdy_flap_add;
+  container_class->forall = gtk_hdy_flap_forall;
+
+  /**
+   * GtkHdyFlap:content:
+   *
+   * The content widget, always displayed when unfolded, and partially visible
+   * when folded.
+   *
+   * Since: 1.1
+   */
+  props[PROP_CONTENT] =
+    g_param_spec_object ("content",
+                         _("Content"),
+                         _("The content Widget"),
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:flap:
+   *
+   * The flap widget, only visible when #GtkHdyFlap:reveal-progress is greater than
+   * 0.
+   *
+   * Since: 1.1
+   */
+  props[PROP_FLAP] =
+    g_param_spec_object ("flap",
+                         _("Flap"),
+                         _("The flap widget"),
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:separator:
+   *
+   * The separator widget, displayed between content and flap when there's no
+   * shadow to display. When exactly it's visible depends on the
+   * #GtkHdyFlap:transition-type value. If %NULL, no separator will be used.
+   *
+   * Since: 1.1
+   */
+  props[PROP_SEPARATOR] =
+    g_param_spec_object ("separator",
+                         _("Separator"),
+                         _("The separator widget"),
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:flap-position:
+   *
+   * The flap position for @self. If @GTK_PACK_START, the flap is displayed
+   * before the content, if @GTK_PACK_END, it's displayed after the content.
+   *
+   * Since: 1.1
+   */
+  props[PROP_FLAP_POSITION] =
+    g_param_spec_enum ("flap-position",
+                       _("Flap Position"),
+                       _("The flap position"),
+                       GTK_TYPE_PACK_TYPE,
+                       GTK_PACK_START,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:reveal-flap:
+   *
+   * Whether the flap widget is revealed.
+   *
+   * Since: 1.1
+   */
+  props[PROP_REVEAL_FLAP] =
+    g_param_spec_boolean ("reveal-flap",
+                          _("Reveal Flap"),
+                          _("Whether the flap is revealed"),
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:reveal-duration:
+   *
+   * The reveal transition animation duration, in milliseconds.
+   *
+   * Since: 1.1
+   */
+  props[PROP_REVEAL_DURATION] =
+    g_param_spec_uint ("reveal-duration",
+                       _("Reveal Duration"),
+                       _("The reveal transition animation duration, in milliseconds"),
+                       0, G_MAXINT,
+                       250,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:reveal-progress:
+   *
+   * The current reveal transition progress. 0 means fully hidden, 1 means fully
+   * revealed See #GtkHdyFlap:reveal-flap.
+   *
+   * Since: 1.1
+   */
+  props[PROP_REVEAL_PROGRESS] =
+    g_param_spec_double ("reveal-progress",
+                          _("Reveal Progress"),
+                          _("The current reveal transition progress"),
+                          0.0, 1.0, 1.0,
+                          G_PARAM_READABLE);
+
+  /**
+   * GtkHdyFlap:fold-policy:
+   *
+   * The current fold policy. See #GtkHdyFlapFoldPolicy for available
+   * policies.
+   *
+   * Since: 1.1
+   */
+  props[PROP_FOLD_POLICY] =
+    g_param_spec_enum ("fold-policy",
+                       _("Fold Policy"),
+                       _("The current fold policy"),
+                       GTK_TYPE_HDY_FLAP_FOLD_POLICY,
+                       GTK_HDY_FLAP_FOLD_POLICY_AUTO,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:fold-duration:
+   *
+   * The fold transition animation duration, in milliseconds.
+   *
+   * Since: 1.1
+   */
+  props[PROP_FOLD_DURATION] =
+    g_param_spec_uint ("fold-duration",
+                       _("Fold Duration"),
+                       _("The fold transition animation duration, in milliseconds"),
+                       0, G_MAXINT,
+                       250,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:folded:
+   *
+   * Whether the flap is currently folded.
+   *
+   * See #GtkHdyFlap:fold-policy.
+   *
+   * Since: 1.1
+   */
+  props[PROP_FOLDED] =
+    g_param_spec_boolean ("folded",
+                          _("Folded"),
+                          _("Whether the flap is currently folded"),
+                          FALSE,
+                          G_PARAM_READABLE);
+
+  /**
+   * GtkHdyFlap:locked:
+   *
+   * Whether the flap is locked.
+   *
+   * If %FALSE, folding when the flap is revealed automatically closes it, and
+   * unfolding it when the flap is not revealed opens it. If %TRUE,
+   * #GtkHdyFlap:reveal-flap value never changes on its own.
+   *
+   * Since: 1.1
+   */
+  props[PROP_LOCKED] =
+    g_param_spec_boolean ("locked",
+                          _("Locked"),
+                          _("Whether the flap is locked"),
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:transition-type:
+   *
+   * The type of animation that will be used for reveal and fold transitions
+   * in @self.
+   *
+   * #GtkHdyFlap:flap is transparent by default, which means the content will be
+   * seen through it with %HDY_FLAP_TRANSITION_TYPE_OVER transitions; add the
+   * .background style class to it if this is unwanted.
+   *
+   * Since: 1.1
+   */
+  props[PROP_TRANSITION_TYPE] =
+    g_param_spec_enum ("transition-type",
+                       _("Transition Type"),
+                       _("The type of animation used for reveal and fold transitions"),
+                       GTK_TYPE_HDY_FLAP_TRANSITION_TYPE,
+                       GTK_HDY_FLAP_TRANSITION_TYPE_OVER,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:modal:
+   *
+   * Whether the flap is modal.
+   *
+   * If %TRUE, clicking the content widget while flap is revealed, as well as
+   * pressing Escape key, will close the flap. If %FALSE, clicks are passed
+   * through to the content widget.
+   *
+   * Since: 1.1
+   */
+  props[PROP_MODAL] =
+    g_param_spec_boolean ("modal",
+                          _("Modal"),
+                          _("Whether the flap is modal"),
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:swipe-to-open:
+   *
+   * Whether the flap can be opened with a swipe gesture.
+   *
+   * The area that can be swiped depends on the #GtkHdyFlap:transition-type value.
+   *
+   * Since: 1.1
+   */
+  props[PROP_SWIPE_TO_OPEN] =
+    g_param_spec_boolean ("swipe-to-open",
+                          _("Swipe to Open"),
+                          _("Whether the flap can be opened with a swipe gesture"),
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * GtkHdyFlap:swipe-to-close:
+   *
+   * Whether the flap can be closed with a swipe gesture.
+   *
+   * The area that can be swiped depends on the #GtkHdyFlap:transition-type value.
+   *
+   * Since: 1.1
+   */
+  props[PROP_SWIPE_TO_CLOSE] =
+    g_param_spec_boolean ("swipe-to-close",
+                          _("Swipe to Close"),
+                          _("Whether the flap can be closed with a swipe gesture"),
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  g_object_class_override_property (object_class,
+                                    PROP_ORIENTATION,
+                                    "orientation");
+
+  gtk_widget_class_set_css_name (widget_class, "flap");
+}
+
+static void
+gtk_hdy_flap_init (GtkHdyFlap *self)
+{
+  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  gtk_widget_add_events (GTK_WIDGET (self), GDK_KEY_PRESS_MASK);
+
+  self->orientation = GTK_ORIENTATION_HORIZONTAL;
+  self->flap_position = GTK_PACK_START;
+  self->fold_policy = GTK_HDY_FLAP_FOLD_POLICY_AUTO;
+  self->transition_type = GTK_HDY_FLAP_TRANSITION_TYPE_OVER;
+  self->reveal_flap = TRUE;
+  self->locked = FALSE;
+  self->reveal_progress = 1;
+  self->folded = FALSE;
+  self->fold_progress = 0;
+  self->fold_duration = 250;
+  self->reveal_duration = 250;
+  self->modal = TRUE;
+  self->swipe_to_open = TRUE;
+  self->swipe_to_close = TRUE;
+
+  self->shadow_helper = gtk_hdy_shadow_helper_new (GTK_WIDGET (self));
+  self->tracker = gtk_hdy_swipe_tracker_new (GTK_HDY_SWIPEABLE (self));
+  gtk_hdy_swipe_tracker_set_enabled (self->tracker, FALSE);
+
+  g_signal_connect_object (self->tracker, "begin-swipe", G_CALLBACK (begin_swipe_cb), self, 0);
+  g_signal_connect_object (self->tracker, "update-swipe", G_CALLBACK (update_swipe_cb), self, 0);
+  g_signal_connect_object (self->tracker, "end-swipe", G_CALLBACK (end_swipe_cb), self, 0);
+
+  update_swipe_tracker (self);
+
+  self->click_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self));
+  gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (self->click_gesture), TRUE);
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), GDK_BUTTON_PRIMARY);
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
+                                              GTK_PHASE_CAPTURE);
+  g_signal_connect_object (self->click_gesture, "released", G_CALLBACK (released_cb), self, 0);
+
+  self->key_controller = gtk_event_controller_key_new (GTK_WIDGET (self));
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->key_controller),
+                                              GTK_PHASE_BUBBLE);
+  g_signal_connect_object (self->key_controller, "key-pressed", G_CALLBACK (key_pressed_cb), self, 0);
+
+  gtk_style_context_add_class (context, "unfolded");
+}
+
+static void
+gtk_hdy_flap_add_child (GtkBuildable *buildable,
+                        GtkBuilder   *builder,
+                        GObject      *child,
+                        const gchar  *type)
+{
+  if (!type || !g_strcmp0 (type, "content"))
+    gtk_hdy_flap_set_content (GTK_HDY_FLAP (buildable), GTK_WIDGET (child));
+  else if (!g_strcmp0 (type, "flap"))
+    gtk_hdy_flap_set_flap (GTK_HDY_FLAP (buildable), GTK_WIDGET (child));
+  else if (!g_strcmp0 (type, "separator"))
+    gtk_hdy_flap_set_separator (GTK_HDY_FLAP (buildable), GTK_WIDGET (child));
+  else
+    GTK_BUILDER_WARN_INVALID_CHILD_TYPE (buildable, type);
+}
+
+static void
+gtk_hdy_flap_buildable_init (GtkBuildableIface *iface)
+{
+  iface->add_child = gtk_hdy_flap_add_child;
+}
+
+static void
+gtk_hdy_flap_switch_child (GtkHdySwipeable *swipeable,
+                           guint            index,
+                           gint64           duration)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+
+  set_reveal_flap (self, index > 0, duration, FALSE);
+}
+
+static GtkHdySwipeTracker *
+gtk_hdy_flap_get_swipe_tracker (GtkHdySwipeable *swipeable)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+
+  return self->tracker;
+}
+
+static gdouble
+gtk_hdy_flap_get_distance (GtkHdySwipeable *swipeable)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+  gint flap, separator;
+
+  if (!self->flap.widget)
+    return 0;
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
+    flap = self->flap.allocation.width;
+    separator = self->separator.allocation.width;
+  } else {
+    flap = self->flap.allocation.height;
+    separator = self->separator.allocation.height;
+  }
+
+  if (transition_is_content_above_flap (self))
+    return flap + separator;
+
+  return flap + separator * (1 - self->fold_progress);
+}
+
+static gdouble *
+gtk_hdy_flap_get_snap_points (GtkHdySwipeable *swipeable,
+                              gint            *n_snap_points)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+  gboolean can_open = self->reveal_progress > 0 || self->swipe_to_open || self->swipe_active;
+  gboolean can_close = self->reveal_progress < 1 || self->swipe_to_close || self->swipe_active;
+  gdouble *points;
+
+  if (!can_open && !can_close)
+    return NULL;
+
+  if (can_open && can_close) {
+    points = g_new0 (gdouble, 2);
+
+    if (n_snap_points)
+      *n_snap_points = 2;
+
+    points[0] = 0;
+    points[1] = 1;
+
+    return points;
+  }
+
+  points = g_new0 (gdouble, 1);
+
+  if (n_snap_points)
+    *n_snap_points = 1;
+
+  points[0] = can_open ? 1 : 0;
+
+  return points;
+}
+
+static gdouble
+gtk_hdy_flap_get_progress (GtkHdySwipeable *swipeable)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+
+  return self->reveal_progress;
+}
+
+static gdouble
+gtk_hdy_flap_get_cancel_progress (GtkHdySwipeable *swipeable)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+
+  return round (self->reveal_progress);
+}
+
+static void
+gtk_hdy_flap_get_swipe_area (GtkHdySwipeable           *swipeable,
+                             GtkHdyNavigationDirection  navigation_direction,
+                             gboolean                   is_drag,
+                             GdkRectangle              *rect)
+{
+  GtkHdyFlap *self = GTK_HDY_FLAP (swipeable);
+  GtkAllocation *alloc;
+  gint width, height;
+  gdouble flap_factor, content_factor;
+  gboolean content_above_flap;
+
+  if (!self->flap.widget) {
+    rect->x = 0;
+    rect->y = 0;
+    rect->width = 0;
+    rect->height = 0;
+
+    return;
+  }
+
+  width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
+  height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
+
+  content_above_flap = transition_is_content_above_flap (self);
+  flap_factor = transition_get_flap_motion_factor (self);
+  content_factor = transition_get_content_motion_factor (self);
+
+  if (!is_drag ||
+      (flap_factor >= 1 && content_factor >= 1) ||
+      (self->fold_progress < 1 && flap_factor > 0)) {
+    rect->x = 0;
+    rect->y = 0;
+    rect->width = width;
+    rect->height = height;
+
+    return;
+  }
+
+  alloc = content_above_flap
+    ? &self->content.allocation
+    : &self->flap.allocation;
+
+  if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
+    if (alloc->x <= 0) {
+      rect->x = 0;
+      rect->width = MAX (alloc->width + alloc->x, GTK_HDY_SWIPE_BORDER);
+    } else if (alloc->x + alloc->width >= width) {
+      rect->width = MAX (width - alloc->x, GTK_HDY_SWIPE_BORDER);
+      rect->x = width - rect->width;
+    } else {
+      g_assert_not_reached ();
+    }
+
+    rect->y = alloc->y;
+    rect->height = alloc->height;
+  } else {
+    if (alloc->y <= 0) {
+      rect->y = 0;
+      rect->height = MAX (alloc->height + alloc->y, GTK_HDY_SWIPE_BORDER);
+    } else if (alloc->y + alloc->height >= height) {
+      rect->height = MAX (height - alloc->y, GTK_HDY_SWIPE_BORDER);
+      rect->y = height - rect->height;
+    } else {
+      g_assert_not_reached ();
+    }
+
+    rect->x = alloc->x;
+    rect->width = alloc->width;
+  }
+}
+
+static void
+gtk_hdy_flap_swipeable_init (GtkHdySwipeableInterface *iface)
+{
+  iface->switch_child = gtk_hdy_flap_switch_child;
+  iface->get_swipe_tracker = gtk_hdy_flap_get_swipe_tracker;
+  iface->get_distance = gtk_hdy_flap_get_distance;
+  iface->get_snap_points = gtk_hdy_flap_get_snap_points;
+  iface->get_progress = gtk_hdy_flap_get_progress;
+  iface->get_cancel_progress = gtk_hdy_flap_get_cancel_progress;
+  iface->get_swipe_area = gtk_hdy_flap_get_swipe_area;
+}
+
+/**
+ * gtk_hdy_flap_new:
+ *
+ * Creates a new #GtkHdyFlap.
+ *
+ * Returns: a new #GtkHdyFlap
+ *
+ * Since: 1.1
+ */
+GtkWidget *
+gtk_hdy_flap_new (void)
+{
+  return g_object_new (GTK_TYPE_HDY_FLAP, NULL);
+}
+
+/**
+ * gtk_hdy_flap_get_content:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the content widget for @self
+ *
+ * Returns: (transfer none) (nullable): the content widget for @self
+ *
+ * Since: 1.1
+ */
+GtkWidget *
+gtk_hdy_flap_get_content (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), NULL);
+
+  return self->content.widget;
+}
+
+/**
+ * gtk_hdy_flap_set_content:
+ * @self: a #GtkHdyFlap
+ * @content: (nullable): the content widget, or %NULL
+ *
+ * Sets the content widget for @self, always displayed when unfolded, and
+ * partially visible when folded.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_content (GtkHdyFlap *self,
+                          GtkWidget  *content)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (GTK_IS_WIDGET (content) || content == NULL);
+
+  if (self->content.widget == content)
+    return;
+
+  if (self->content.widget)
+    remove_child (self, &self->content);
+
+  self->content.widget = content;
+
+  if (self->content.widget)
+    add_child (self, &self->content);
+
+  update_child_visibility (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONTENT]);
+}
+
+/**
+ * gtk_hdy_flap_get_flap:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the flap widget for @self
+ *
+ * Returns: (transfer none) (nullable): the flap widget for @self
+ *
+ * Since: 1.1
+ */
+GtkWidget *
+gtk_hdy_flap_get_flap (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), NULL);
+
+  return self->flap.widget;
+}
+
+/**
+ * gtk_hdy_flap_set_flap:
+ * @self: a #GtkHdyFlap
+ * @flap: (nullable): the flap widget, or %NULL
+ *
+ * Sets the flap widget for @self, only visible when #GtkHdyFlap:reveal-progress is
+ * greater than 0.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_flap (GtkHdyFlap *self,
+                       GtkWidget  *flap)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (GTK_IS_WIDGET (flap) || flap == NULL);
+
+  if (self->flap.widget == flap)
+    return;
+
+  if (self->flap.widget)
+    remove_child (self, &self->flap);
+
+  self->flap.widget = flap;
+
+  if (self->flap.widget)
+    add_child (self, &self->flap);
+
+  update_swipe_tracker (self);
+  update_child_visibility (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FLAP]);
+}
+
+/**
+ * gtk_hdy_flap_get_separator:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the separator widget for @self.
+ *
+ * Returns: (transfer none) (nullable): the separator widget for @self
+ *
+ * Since: 1.1
+ */
+GtkWidget *
+gtk_hdy_flap_get_separator (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), NULL);
+
+  return self->separator.widget;
+}
+
+/**
+ * gtk_hdy_flap_set_separator:
+ * @self: a #GtkHdyFlap
+ * @separator: (nullable): the separator widget, or %NULL
+ *
+ * Sets the separator widget for @self, displayed between content and flap when
+ * there's no shadow to display. When exactly it's visible depends on the
+ * #GtkHdyFlap:transition-type value. If %NULL, no separator will be used.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_separator (GtkHdyFlap *self,
+                            GtkWidget  *separator)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (GTK_IS_WIDGET (separator) || separator == NULL);
+
+  if (self->separator.widget == separator)
+    return;
+
+  if (self->separator.widget)
+    remove_child (self, &self->separator);
+
+  self->separator.widget = separator;
+
+  if (self->separator.widget)
+    add_child (self, &self->separator);
+
+  update_child_visibility (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SEPARATOR]);
+}
+
+/**
+ * gtk_hdy_flap_get_flap_position:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the flap position for @self.
+ *
+ * Returns: the flap position for @self
+ *
+ * Since: 1.1
+ */
+GtkPackType
+gtk_hdy_flap_get_flap_position (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), GTK_PACK_START);
+
+  return self->flap_position;
+}
+
+/**
+ * gtk_hdy_flap_set_flap_position:
+ * @self: a #GtkHdyFlap
+ * @position: the new value
+ *
+ * Sets the flap position for @self. If @GTK_PACK_START, the flap is displayed
+ * before the content, if @GTK_PACK_END, it's displayed after the content.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_flap_position (GtkHdyFlap  *self,
+                                GtkPackType  position)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (position <= GTK_PACK_END);
+
+  if (self->flap_position == position)
+    return;
+
+  self->flap_position = position;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+  gtk_hdy_shadow_helper_clear_cache (self->shadow_helper);
+  update_swipe_tracker (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FLAP_POSITION]);
+}
+
+/**
+ * gtk_hdy_flap_get_reveal_flap:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether the flap widget is revealed for @self.
+ *
+ * Returns: %TRUE if the flap widget is revealed, %FALSE otherwise.
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_reveal_flap (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->reveal_flap;
+}
+
+/**
+ * gtk_hdy_flap_set_reveal_flap:
+ * @self: a #GtkHdyFlap
+ * @reveal_flap: %TRUE to reveal the flap widget, %FALSE otherwise
+ *
+ * Sets whether the flap widget is revealed for @self.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_reveal_flap (GtkHdyFlap *self,
+                              gboolean    reveal_flap)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  set_reveal_flap (self, reveal_flap, self->reveal_duration, TRUE);
+}
+
+/**
+ * gtk_hdy_flap_get_reveal_duration:
+ * @self: a #GtkHdyFlap
+ *
+ * Returns the amount of time (in milliseconds) that reveal transitions in @self
+ * will take.
+ *
+ * Returns: the reveal transition duration
+ *
+ * Since: 1.1
+ */
+guint
+gtk_hdy_flap_get_reveal_duration (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), 0);
+
+  return self->reveal_duration;
+}
+
+/**
+ * gtk_hdy_flap_set_reveal_duration:
+ * @self: a #GtkHdyFlap
+ * @duration: the new duration, in milliseconds
+ *
+ * Sets the duration that reveal transitions in @self will take.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_reveal_duration (GtkHdyFlap *self,
+                                  guint       duration)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  if (self->reveal_duration == duration)
+    return;
+
+  self->reveal_duration = duration;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_DURATION]);
+}
+
+/**
+ * gtk_hdy_flap_get_reveal_progress:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the current reveal transition progress for @self. 0 means fully hidden,
+ * 1 means fully revealed. See #GtkHdyFlap:reveal-flap.
+ *
+ * Returns: the current reveal progress for @self
+ *
+ * Since: 1.1
+ */
+gdouble
+gtk_hdy_flap_get_reveal_progress (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), 0.0);
+
+  return self->reveal_progress;
+}
+
+/**
+ * gtk_hdy_flap_get_fold_policy:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the current fold policy of @self. See gtk_hdy_flap_set_fold_policy().
+ *
+ * Returns: the current fold policy of @self
+ *
+ * Since: 1.1
+ */
+GtkHdyFlapFoldPolicy
+gtk_hdy_flap_get_fold_policy (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), GTK_HDY_FLAP_FOLD_POLICY_NEVER);
+
+  return self->fold_policy;
+}
+
+/**
+ * gtk_hdy_flap_set_fold_policy:
+ * @self: a #GtkHdyFlap
+ * @policy: Fold policy
+ *
+ * Sets the current fold policy for @self. See #GtkHdyFlapFoldPolicy for available
+ * policies.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_fold_policy (GtkHdyFlap           *self,
+                              GtkHdyFlapFoldPolicy  policy)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (policy <= GTK_HDY_FLAP_FOLD_POLICY_AUTO);
+
+  if (self->fold_policy == policy)
+    return;
+
+  self->fold_policy = policy;
+
+  switch (self->fold_policy) {
+  case GTK_HDY_FLAP_FOLD_POLICY_NEVER:
+    set_folded (self, FALSE);
+    break;
+
+  case GTK_HDY_FLAP_FOLD_POLICY_ALWAYS:
+    set_folded (self, TRUE);
+    break;
+
+  case GTK_HDY_FLAP_FOLD_POLICY_AUTO:
+    gtk_widget_queue_allocate (GTK_WIDGET (self));
+    break;
+
+  default:
+    g_assert_not_reached ();
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLD_POLICY]);
+}
+
+/**
+ * gtk_hdy_flap_get_fold_duration:
+ * @self: a #GtkHdyFlap
+ *
+ * Returns the amount of time (in milliseconds) that fold transitions in @self
+ * will take.
+ *
+ * Returns: the fold transition duration
+ *
+ * Since: 1.1
+ */
+guint
+gtk_hdy_flap_get_fold_duration (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), 0);
+
+  return self->fold_duration;
+}
+
+/**
+ * gtk_hdy_flap_set_fold_duration:
+ * @self: a #GtkHdyFlap
+ * @duration: the new duration, in milliseconds
+ *
+ * Sets the duration that fold transitions in @self will take.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_fold_duration (GtkHdyFlap *self,
+                                guint       duration)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  if (self->fold_duration == duration)
+    return;
+
+  self->fold_duration = duration;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLD_DURATION]);
+}
+
+/**
+ * gtk_hdy_flap_get_folded:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether @self is currently folded.
+ *
+ * See #GtkHdyFlap:fold-policy.
+ *
+ * Returns: %TRUE if @self is currently folded, %FALSE otherwise
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_folded (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->folded;
+}
+
+/**
+ * gtk_hdy_flap_get_locked:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether @self is locked.
+ *
+ * Returns: %TRUE if @self is locked, %FALSE otherwise
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_locked (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->locked;
+}
+
+/**
+ * gtk_hdy_flap_set_locked:
+ * @self: a #GtkHdyFlap
+ * @locked: the new value
+ *
+ * Sets whether @self is locked.
+ *
+ * If %FALSE, folding @self when the flap is revealed automatically closes it,
+ * and unfolding it when the flap is not revealed opens it. If %TRUE,
+ * #GtkHdyFlap:reveal-flap value never changes on its own.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_locked (GtkHdyFlap *self,
+                         gboolean    locked)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  locked = !!locked;
+
+  if (self->locked == locked)
+    return;
+
+  self->locked = locked;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOCKED]);
+}
+
+/**
+ * gtk_hdy_flap_get_transition_type:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets the type of animation that will be used for reveal and fold transitions
+ * in @self.
+ *
+ * Returns: the current transition type of @self
+ *
+ * Since: 1.1
+ */
+GtkHdyFlapTransitionType
+gtk_hdy_flap_get_transition_type (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), GTK_HDY_FLAP_TRANSITION_TYPE_OVER);
+
+  return self->transition_type;
+}
+
+/**
+ * gtk_hdy_flap_set_transition_type:
+ * @self: a #GtkHdyFlap
+ * @transition_type: the new transition type
+ *
+ * Sets the type of animation that will be used for reveal and fold transitions
+ * in @self.
+ *
+ * #GtkHdyFlap:flap is transparent by default, which means the content will be seen
+ * through it with %HDY_FLAP_TRANSITION_TYPE_OVER transitions; add the
+ * .background style class to it if this is unwanted.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_transition_type (GtkHdyFlap               *self,
+                                  GtkHdyFlapTransitionType  transition_type)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+  g_return_if_fail (transition_type <= GTK_HDY_FLAP_TRANSITION_TYPE_SLIDE);
+
+  if (self->transition_type == transition_type)
+    return;
+
+  self->transition_type = transition_type;
+
+  restack_windows (self);
+
+  if (self->reveal_progress > 0 || (self->fold_progress > 0 && self->fold_progress < 1))
+    gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]);
+}
+
+/**
+ * gtk_hdy_flap_get_modal:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether the @self is modal. See gtk_hdy_flap_set_modal().
+ *
+ * Returns: %TRUE if @self is modal
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_modal (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->modal;
+}
+
+/**
+ * gtk_hdy_flap_set_modal:
+ * @self: a #GtkHdyFlap
+ * @modal: Whether @self can be closed with a click
+ *
+ * Sets whether the @self can be closed with a click.
+ *
+ * If @modal is %TRUE, clicking the content widget while flap is revealed, or
+ * pressing Escape key, will close the flap. If %FALSE, clicks are passed
+ * through to the content widget.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_modal (GtkHdyFlap *self,
+                        gboolean    modal)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  modal = !!modal;
+
+  if (self->modal == modal)
+    return;
+
+  self->modal = modal;
+
+  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
+                                              modal ? GTK_PHASE_CAPTURE : GTK_PHASE_NONE);
+  gtk_event_controller_set_propagation_phase (self->key_controller,
+                                              modal ? GTK_PHASE_BUBBLE : GTK_PHASE_NONE);
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MODAL]);
+}
+
+/**
+ * gtk_hdy_flap_get_swipe_to_open:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether @self can be opened with a swipe gesture.
+ *
+ * Returns: %TRUE if @self can be opened with a swipe gesture
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_swipe_to_open (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->swipe_to_open;
+}
+
+/**
+ * gtk_hdy_flap_set_swipe_to_open:
+ * @self: a #GtkHdyFlap
+ * @swipe_to_open: Whether @self can be opened with a swipe gesture
+ *
+ * Sets whether @self can be opened with a swipe gesture.
+ *
+ * The area that can be swiped depends on the #GtkHdyFlap:transition-type value.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_swipe_to_open (GtkHdyFlap *self,
+                                gboolean    swipe_to_open)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  swipe_to_open = !!swipe_to_open;
+
+  if (self->swipe_to_open == swipe_to_open)
+    return;
+
+  self->swipe_to_open = swipe_to_open;
+
+  update_swipe_tracker (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SWIPE_TO_OPEN]);
+}
+
+/**
+ * gtk_hdy_flap_get_swipe_to_close:
+ * @self: a #GtkHdyFlap
+ *
+ * Gets whether @self can be closed with a swipe gesture.
+ *
+ * Returns: %TRUE if @self can be closed with a swipe gesture
+ *
+ * Since: 1.1
+ */
+gboolean
+gtk_hdy_flap_get_swipe_to_close (GtkHdyFlap *self)
+{
+  g_return_val_if_fail (GTK_IS_HDY_FLAP (self), FALSE);
+
+  return self->swipe_to_close;
+}
+
+/**
+ * gtk_hdy_flap_set_swipe_to_close:
+ * @self: a #GtkHdyFlap
+ * @swipe_to_close: Whether @self can be closed with a swipe gesture
+ *
+ * Sets whether @self can be closed with a swipe gesture.
+ *
+ * The area that can be swiped depends on the #GtkHdyFlap:transition-type value.
+ *
+ * Since: 1.1
+ */
+void
+gtk_hdy_flap_set_swipe_to_close (GtkHdyFlap *self,
+                                 gboolean    swipe_to_close)
+{
+  g_return_if_fail (GTK_IS_HDY_FLAP (self));
+
+  swipe_to_close = !!swipe_to_close;
+
+  if (self->swipe_to_close == swipe_to_close)
+    return;
+
+  self->swipe_to_close = swipe_to_close;
+
+  update_swipe_tracker (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SWIPE_TO_CLOSE]);
+}
diff --git a/gtk/meson.build b/gtk/meson.build
index dfcb45d..ab64833 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -386,6 +386,7 @@ gtk_sources = files(
   'hdy-animation.c',
   'hdy-clamp.c',
   'hdy-css.c',
+  'hdy-flap.c',
   'hdy-navigation-direction.c',
   'hdy-shadow-helper.c',
   'hdy-squeezer.c',
@@ -406,6 +407,7 @@ gtk_private_type_headers = files(
   'hdy-cairo-private.h',
   'hdy-clamp-private.h',
   'hdy-css-private.h',
+  'hdy-flap-private.h',
   'hdy-navigation-direction-private.h',
   'hdy-shadow-helper-private.h',
   'hdy-squeezer-private.h',
openSUSE Build Service is sponsored by