[libadwaita] Add AdwViewStack



commit ea7ef0f2e2acae307ec27c9356b396c15db3d99b
Author: CodedOre <CodedOre>
Date:   Mon Jun 28 13:43:41 2021 +0500

    Add AdwViewStack

 src/adw-view-stack.c | 2036 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/adw-view-stack.h |  143 ++++
 src/adwaita.h        |    1 +
 src/meson.build      |    2 +
 4 files changed, 2182 insertions(+)
---
diff --git a/src/adw-view-stack.c b/src/adw-view-stack.c
new file mode 100644
index 00000000..f5128431
--- /dev/null
+++ b/src/adw-view-stack.c
@@ -0,0 +1,2036 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ * Copyright (C) 2021 Frederick Schenk
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on gtkstack.c
+ * https://gitlab.gnome.org/GNOME/gtk/-/blob/ba44668478b7184bec02609f292691b85a2c6cdd/gtk/gtkstack.c
+ */
+
+#include "config.h"
+
+#include "adw-view-stack.h"
+
+#include "gtkprogresstrackerprivate.h"
+#include "adw-animation-private.h"
+#include "adw-enums.h"
+#include "adw-focus-private.h"
+#include "adw-gizmo-private.h"
+#include "adw-macros-private.h"
+
+/**
+ * AdwViewStack:
+ *
+ * `AdwViewStack` is similar to [class@Gtk.Stack] and can be used with
+ * [class@Adw.ViewSwitcher]. Refer to `GtkStack` for details.
+ *
+ * `AdwViewStack` does not provide the transition types other than crossfade.
+ *
+ * Since: 1.0
+ */
+
+/**
+ * AdwViewStackPage:
+ *
+ * `AdwViewStackPage` is an auxiliary class used by [class@Adw.ViewStack].
+ *
+ * Since: 1.0
+ */
+
+enum {
+  PROP_0,
+  PROP_HHOMOGENEOUS,
+  PROP_VHOMOGENEOUS,
+  PROP_VISIBLE_CHILD,
+  PROP_VISIBLE_CHILD_NAME,
+  PROP_TRANSITION_RUNNING,
+  PROP_INTERPOLATE_SIZE,
+  PROP_PAGES,
+  LAST_PROP
+};
+
+struct _AdwViewStackPage {
+  GObject parent_instance;
+
+  GtkWidget *widget;
+  char *name;
+  char *title;
+  char *icon_name;
+  GtkWidget *last_focus;
+
+  bool needs_attention;
+  bool visible;
+  bool use_underline;
+};
+
+G_DEFINE_TYPE (AdwViewStackPage, adw_view_stack_page, G_TYPE_OBJECT)
+
+enum {
+  PAGE_PROP_0,
+  PAGE_PROP_CHILD,
+  PAGE_PROP_NAME,
+  PAGE_PROP_TITLE,
+  PAGE_PROP_ICON_NAME,
+  PAGE_PROP_NEEDS_ATTENTION,
+  PAGE_PROP_VISIBLE,
+  PAGE_PROP_USE_UNDERLINE,
+  LAST_PAGE_PROP
+};
+
+static GParamSpec *page_props[LAST_PAGE_PROP];
+
+struct _AdwViewStack {
+  GtkWidget parent_instance;
+
+  GList *children;
+
+  AdwViewStackPage *visible_child;
+
+  gboolean hhomogeneous;
+  gboolean vhomogeneous;
+
+  guint transition_duration;
+
+  AdwViewStackPage *last_visible_child;
+  guint tick_id;
+  GtkProgressTracker tracker;
+  gboolean first_frame_skipped;
+
+  int last_visible_widget_width;
+  int last_visible_widget_height;
+
+  gboolean interpolate_size;
+
+  GtkSelectionModel *pages;
+};
+
+static GParamSpec *props[LAST_PROP];
+
+static void adw_view_stack_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AdwViewStack, adw_view_stack, GTK_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_view_stack_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+static void
+adw_view_stack_page_get_property (GObject      *object,
+                                  guint         property_id,
+                                  GValue       *value,
+                                  GParamSpec   *pspec)
+{
+  AdwViewStackPage *self = ADW_VIEW_STACK_PAGE (object);
+
+  switch (property_id) {
+  case PAGE_PROP_CHILD:
+    g_value_set_object (value, self->widget);
+    break;
+  case PAGE_PROP_NAME:
+    g_value_set_string (value, adw_view_stack_page_get_name (self));
+    break;
+  case PAGE_PROP_TITLE:
+    g_value_set_string (value, adw_view_stack_page_get_title (self));
+    break;
+  case PAGE_PROP_ICON_NAME:
+    g_value_set_string (value, adw_view_stack_page_get_icon_name (self));
+    break;
+  case PAGE_PROP_NEEDS_ATTENTION:
+    g_value_set_boolean (value, adw_view_stack_page_get_needs_attention (self));
+    break;
+  case PAGE_PROP_VISIBLE:
+    g_value_set_boolean (value, adw_view_stack_page_get_visible (self));
+    break;
+  case PAGE_PROP_USE_UNDERLINE:
+    g_value_set_boolean (value, adw_view_stack_page_get_use_underline (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+adw_view_stack_page_set_property (GObject      *object,
+                                  guint         property_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  AdwViewStackPage *self = ADW_VIEW_STACK_PAGE (object);
+
+  switch (property_id) {
+  case PAGE_PROP_CHILD:
+    g_set_object (&self->widget, g_value_get_object (value));
+    break;
+  case PAGE_PROP_NAME:
+    adw_view_stack_page_set_name (self, g_value_get_string (value));
+    break;
+  case PAGE_PROP_TITLE:
+    adw_view_stack_page_set_title (self, g_value_get_string (value));
+    break;
+  case PAGE_PROP_ICON_NAME:
+    adw_view_stack_page_set_icon_name (self, g_value_get_string (value));
+    break;
+  case PAGE_PROP_NEEDS_ATTENTION:
+    adw_view_stack_page_set_needs_attention (self, g_value_get_boolean (value));
+    break;
+  case PAGE_PROP_VISIBLE:
+    adw_view_stack_page_set_visible (self, g_value_get_boolean (value));
+    break;
+  case PAGE_PROP_USE_UNDERLINE:
+    adw_view_stack_page_set_use_underline (self, g_value_get_boolean (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+adw_view_stack_page_finalize (GObject *object)
+{
+  AdwViewStackPage *self = ADW_VIEW_STACK_PAGE (object);
+
+  g_clear_object (&self->widget);
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->title, g_free);
+  g_clear_pointer (&self->icon_name, g_free);
+
+  if (self->last_focus)
+    g_object_remove_weak_pointer (G_OBJECT (self->last_focus),
+                                  (gpointer *) &self->last_focus);
+
+  G_OBJECT_CLASS (adw_view_stack_page_parent_class)->finalize (object);
+}
+
+static void
+adw_view_stack_page_class_init (AdwViewStackPageClass *class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (class);
+
+  object_class->finalize = adw_view_stack_page_finalize;
+  object_class->get_property = adw_view_stack_page_get_property;
+  object_class->set_property = adw_view_stack_page_set_property;
+
+  /**
+   * AdwViewStackPage:child: (attributes org.gtk.Property.get=adw_view_stack_page_get_child)
+   *
+   * The child of the page.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_CHILD] =
+    g_param_spec_object ("child",
+                         "Child",
+                         "The child of the page",
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  /**
+   * AdwViewStackPage:name: (attributes org.gtk.Property.get=adw_view_stack_page_get_name 
org.gtk.Property.set=adw_view_stack_page_set_name)
+   *
+   * The name of the child page.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "The name of the child page",
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  /**
+   * AdwViewStackPage:title: (attributes org.gtk.Property.get=adw_view_stack_page_get_title 
org.gtk.Property.set=adw_view_stack_page_set_title)
+   *
+   * The title of the child page.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "The title of the child page",
+                         NULL,
+                         G_PARAM_READWRITE);
+
+  /**
+   * AdwViewStackPage:icon-name: (attributes org.gtk.Property.get=adw_view_stack_page_get_icon_name 
org.gtk.Property.set=adw_view_stack_page_set_icon_name)
+   *
+   * The icon name of the child page.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_ICON_NAME] =
+    g_param_spec_string ("icon-name",
+                         "Icon name",
+                         "The icon name of the child page",
+                         NULL,
+                         G_PARAM_READWRITE);
+
+  /**
+   * AdwViewStackPage:needs-attention: (attributes 
org.gtk.Property.get=adw_view_stack_page_get_needs_attention 
org.gtk.Property.set=adw_view_stack_page_set_needs_attention)
+   *
+   * Whether the page requires the user attention.
+   *
+   * This is used by the [class@Adw.ViewSwitcher] to change the
+   * appearance of the corresponding button when a page needs
+   * attention and it is not the current one.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_NEEDS_ATTENTION] =
+    g_param_spec_boolean ("needs-attention",
+                          "Needs Attention",
+                          "Whether the page requires the user attention",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStackPage:visible: (attributes org.gtk.Property.get=adw_view_stack_page_get_visible 
org.gtk.Property.set=adw_view_stack_page_set_visible)
+   *
+   * Whether this page is visible.
+   *
+   * This is independent from the [property@Gtk.Widget:visible] property of
+   * [property@Adw.ViewStackPage:child].
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_VISIBLE] =
+    g_param_spec_boolean ("visible",
+                          "Visible",
+                          "Whether this page is visible",
+                          TRUE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStackPage:use-underline: (attributes org.gtk.Property.get=adw_view_stack_page_get_use_underline 
org.gtk.Property.set=adw_view_stack_page_set_use_underline)
+   *
+   * Whether an embedded underline in the title indicates a mnemonic.
+   *
+   * Since: 1.0
+   */
+  page_props[PAGE_PROP_USE_UNDERLINE] =
+    g_param_spec_boolean ("use-underline",
+                          "Use underline",
+                          "Whether an embedded underline in the title label indicates a mnemonic",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PAGE_PROP, page_props);
+}
+
+static void
+adw_view_stack_page_init (AdwViewStackPage *self)
+{
+  self->visible = TRUE;
+}
+
+#define ADW_TYPE_VIEW_STACK_PAGES (adw_view_stack_pages_get_type ())
+
+G_DECLARE_FINAL_TYPE (AdwViewStackPages, adw_view_stack_pages, ADW, VIEW_STACK_PAGES, GObject)
+
+struct _AdwViewStackPages
+{
+  GObject parent_instance;
+  AdwViewStack *stack;
+};
+
+static GType
+adw_view_stack_pages_get_item_type (GListModel *model)
+{
+  return ADW_TYPE_VIEW_STACK_PAGE;
+}
+
+static guint
+adw_view_stack_pages_get_n_items (GListModel *model)
+{
+  AdwViewStackPages *self = ADW_VIEW_STACK_PAGES (model);
+
+  return g_list_length (self->stack->children);
+}
+
+static gpointer
+adw_view_stack_pages_get_item (GListModel *model,
+                               guint       position)
+{
+  AdwViewStackPages *self = ADW_VIEW_STACK_PAGES (model);
+  AdwViewStackPage *page;
+
+  page = g_list_nth_data (self->stack->children, position);
+
+  if (!page)
+    return NULL;
+
+  return g_object_ref (page);
+}
+
+static void
+adw_view_stack_pages_list_model_init (GListModelInterface *iface)
+{
+  iface->get_item_type = adw_view_stack_pages_get_item_type;
+  iface->get_n_items = adw_view_stack_pages_get_n_items;
+  iface->get_item = adw_view_stack_pages_get_item;
+}
+
+static gboolean
+adw_view_stack_pages_is_selected (GtkSelectionModel *model,
+                                  guint              position)
+{
+  AdwViewStackPages *self = ADW_VIEW_STACK_PAGES (model);
+  AdwViewStackPage *page;
+
+  page = g_list_nth_data (self->stack->children, position);
+
+  return page && page == self->stack->visible_child;
+}
+
+static gboolean
+adw_view_stack_pages_select_item (GtkSelectionModel *model,
+                                  guint              position,
+                                  gboolean           exclusive)
+{
+  AdwViewStackPages *self = ADW_VIEW_STACK_PAGES (model);
+  AdwViewStackPage *page;
+
+  page = g_list_nth_data (self->stack->children, position);
+
+  adw_view_stack_set_visible_child (self->stack, page->widget);
+
+  return TRUE;
+}
+
+static void
+adw_view_stack_pages_selection_model_init (GtkSelectionModelInterface *iface)
+{
+  iface->is_selected = adw_view_stack_pages_is_selected;
+  iface->select_item = adw_view_stack_pages_select_item;
+}
+
+G_DEFINE_TYPE_WITH_CODE (AdwViewStackPages, adw_view_stack_pages, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, adw_view_stack_pages_list_model_init)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, 
adw_view_stack_pages_selection_model_init))
+
+static void
+adw_view_stack_pages_init (AdwViewStackPages *pages)
+{
+}
+
+static void
+adw_view_stack_pages_class_init (AdwViewStackPagesClass *class)
+{
+}
+
+static AdwViewStackPages *
+adw_view_stack_pages_new (AdwViewStack *stack)
+{
+  AdwViewStackPages *pages;
+
+  pages = g_object_new (ADW_TYPE_VIEW_STACK_PAGES, NULL);
+  pages->stack = stack;
+
+  return pages;
+}
+
+static AdwViewStackPage *
+find_page_for_widget (AdwViewStack *self,
+                      GtkWidget    *child)
+{
+  AdwViewStackPage *page;
+  GList *l;
+
+  for (l = self->children; l; l = l->next) {
+    page = l->data;
+
+    if (page->widget == child)
+      return page;
+  }
+
+  return NULL;
+}
+
+static AdwViewStackPage *
+find_page_for_name (AdwViewStack *self,
+                    const char   *name)
+{
+  AdwViewStackPage *page;
+  GList *l;
+
+  for (l = self->children; l; l = l->next) {
+    page = l->data;
+
+    if (g_strcmp0 (page->name, name) == 0)
+      return page;
+  }
+
+  return NULL;
+}
+
+static void
+progress_updated (AdwViewStack *self)
+{
+  if (!self->vhomogeneous || !self->hhomogeneous)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+  else
+    gtk_widget_queue_draw (GTK_WIDGET (self));
+
+  if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER &&
+      self->last_visible_child != NULL) {
+    gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+    self->last_visible_child = NULL;
+  }
+}
+
+static gboolean
+transition_cb (GtkWidget     *widget,
+               GdkFrameClock *frame_clock,
+               gpointer       user_data)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (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 animation early if not mapped anymore */
+  if (!gtk_widget_get_mapped (widget))
+    gtk_progress_tracker_finish (&self->tracker);
+
+  progress_updated (self);
+
+  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
+schedule_ticks (AdwViewStack *self)
+{
+  if (self->tick_id == 0) {
+    self->tick_id =
+      gtk_widget_add_tick_callback (GTK_WIDGET (self),
+                                    transition_cb,
+                                    self, NULL);
+    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
+  }
+}
+
+static void
+unschedule_ticks (AdwViewStack *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
+start_transition (AdwViewStack *self)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  if (gtk_widget_get_mapped (widget) &&
+      adw_get_enable_animations (widget) &&
+      self->last_visible_child != NULL) {
+    self->first_frame_skipped = FALSE;
+    schedule_ticks (self);
+    gtk_progress_tracker_start (&self->tracker,
+                                self->transition_duration * 1000,
+                                0,
+                                1.0);
+  } else {
+    unschedule_ticks (self);
+    gtk_progress_tracker_finish (&self->tracker);
+  }
+
+  progress_updated (self);
+}
+
+static void
+set_visible_child (AdwViewStack     *self,
+                   AdwViewStackPage *page)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkRoot *root;
+  GtkWidget *focus;
+  gboolean contains_focus = FALSE;
+  guint old_pos = GTK_INVALID_LIST_POSITION;
+  guint new_pos = GTK_INVALID_LIST_POSITION;
+
+  /* if we are being destroyed, do not bother with transitions
+   * and notifications
+   */
+  if (gtk_widget_in_destruction (widget))
+    return;
+
+  /* If none, pick first visible */
+  if (!page) {
+    GList *l;
+
+    for (l = self->children; l; l = l->next) {
+      AdwViewStackPage *p = l->data;
+
+      if (gtk_widget_get_visible (page->widget)) {
+        page = p;
+
+        break;
+      }
+    }
+  }
+
+  if (page == self->visible_child)
+    return;
+
+  if (self->pages) {
+    guint position;
+    GList *l;
+
+    for (l = self->children, position = 0; l; l = l->next, position++) {
+      AdwViewStackPage *p = l->data;
+      if (p == self->visible_child)
+        old_pos = position;
+      else if (p == page)
+        new_pos = position;
+    }
+  }
+
+  root = gtk_widget_get_root (widget);
+  if (root)
+    focus = gtk_root_get_focus (root);
+  else
+    focus = NULL;
+
+  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)
+    gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+  self->last_visible_child = NULL;
+
+  if (self->visible_child && self->visible_child->widget) {
+    if (gtk_widget_is_visible (widget)) {
+      self->last_visible_child = self->visible_child;
+      self->last_visible_widget_width = gtk_widget_get_width (widget);
+      self->last_visible_widget_height = gtk_widget_get_height (widget);
+    } else {
+      gtk_widget_set_child_visible (self->visible_child->widget, FALSE);
+    }
+  }
+
+  self->visible_child = page;
+
+  if (page) {
+    gtk_widget_set_child_visible (page->widget, TRUE);
+
+    if (contains_focus) {
+      if (page->last_focus)
+        gtk_widget_grab_focus (page->last_focus);
+      else
+        gtk_widget_child_focus (page->widget, GTK_DIR_TAB_FORWARD);
+    }
+  }
+
+  if (self->hhomogeneous && self->vhomogeneous)
+    gtk_widget_queue_allocate (widget);
+  else
+    gtk_widget_queue_resize (widget);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]);
+
+  if (self->pages) {
+    if (old_pos == GTK_INVALID_LIST_POSITION && new_pos == GTK_INVALID_LIST_POSITION)
+      ; /* nothing to do */
+    else if (old_pos == GTK_INVALID_LIST_POSITION)
+      gtk_selection_model_selection_changed (self->pages, new_pos, 1);
+    else if (new_pos == GTK_INVALID_LIST_POSITION)
+      gtk_selection_model_selection_changed (self->pages, old_pos, 1);
+    else
+      gtk_selection_model_selection_changed (self->pages,
+                                             MIN (old_pos, new_pos),
+                                             MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1);
+  }
+
+  start_transition (self);
+}
+
+static void
+update_child_visible (AdwViewStack     *self,
+                      AdwViewStackPage *page)
+{
+  gboolean visible;
+
+  visible = page->visible && gtk_widget_get_visible (page->widget);
+
+  if (self->visible_child == NULL && visible)
+    set_visible_child (self, page);
+  else if (self->visible_child == page && !visible)
+    set_visible_child (self, NULL);
+
+  if (page == self->last_visible_child) {
+    gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
+    self->last_visible_child = NULL;
+  }
+}
+
+static void
+stack_child_visibility_notify_cb (GObject    *obj,
+                                  GParamSpec *pspec,
+                                  gpointer    user_data)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (user_data);
+  AdwViewStackPage *page;
+
+  page = find_page_for_widget (self, GTK_WIDGET (obj));
+  g_return_if_fail (page != NULL);
+
+  update_child_visible (self, page);
+}
+
+static void
+add_page (AdwViewStack     *self,
+          AdwViewStackPage *page)
+{
+  GList *l;
+
+  g_return_if_fail (page->widget != NULL);
+
+  if (page->name) {
+    for (l = self->children; l; l = l->next) {
+      AdwViewStackPage *p = l->data;
+
+      if (p->name && g_strcmp0 (p->name, page->name) == 0) {
+        g_warning ("While adding page: duplicate child name in AdwViewStack: %s", page->name);
+        break;
+      }
+    }
+  }
+
+  self->children = g_list_append (self->children, g_object_ref (page));
+
+  gtk_widget_set_child_visible (page->widget, FALSE);
+  gtk_widget_set_parent (page->widget, GTK_WIDGET (self));
+
+  if (self->pages)
+    g_list_model_items_changed (G_LIST_MODEL (self->pages), g_list_length (self->children) - 1, 0, 1);
+
+  g_signal_connect (page->widget, "notify::visible",
+                    G_CALLBACK (stack_child_visibility_notify_cb), self);
+
+  if (self->visible_child == NULL &&
+      gtk_widget_get_visible (page->widget))
+    set_visible_child (self, page);
+
+  if (self->hhomogeneous || self->vhomogeneous || self->visible_child == page)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static AdwViewStackPage *
+add_internal (AdwViewStack *self,
+              GtkWidget    *child,
+              const char   *name,
+              const char   *title)
+{
+  AdwViewStackPage *page;
+
+  g_return_val_if_fail (child != NULL, NULL);
+
+  page = g_object_new (ADW_TYPE_VIEW_STACK_PAGE, NULL);
+  page->widget = g_object_ref (child);
+  page->name = g_strdup (name);
+  page->title = g_strdup (title);
+  page->icon_name = NULL;
+  page->needs_attention = FALSE;
+  page->last_focus = NULL;
+
+  add_page (self, page);
+
+  g_object_unref (page);
+
+  return page;
+}
+
+static void
+stack_remove (AdwViewStack  *self,
+              GtkWidget     *child,
+              gboolean       in_dispose)
+{
+  AdwViewStackPage *page;
+  gboolean was_visible;
+
+  page = find_page_for_widget (self, child);
+  if (!page)
+    return;
+
+  g_signal_handlers_disconnect_by_func (child,
+                                        stack_child_visibility_notify_cb,
+                                        self);
+
+  was_visible = gtk_widget_get_visible (child);
+
+  if (self->visible_child == page)
+    self->visible_child = NULL;
+
+  if (self->last_visible_child == page)
+    self->last_visible_child = NULL;
+
+  gtk_widget_unparent (child);
+
+  g_clear_object (&page->widget);
+
+  self->children = g_list_remove (self->children, page);
+
+  g_object_unref (page);
+
+  if (!in_dispose &&
+      (self->hhomogeneous || self->vhomogeneous) &&
+      was_visible)
+    gtk_widget_queue_resize (GTK_WIDGET (self));
+}
+
+static void
+adw_view_stack_compute_expand (GtkWidget *widget,
+                               gboolean  *hexpand_p,
+                               gboolean  *vexpand_p)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (widget);
+  gboolean hexpand, vexpand;
+  GList *l;
+
+  hexpand = FALSE;
+  vexpand = FALSE;
+
+  for (l = self->children; l; l = l->next) {
+    AdwViewStackPage *page = l->data;
+    GtkWidget *child = page->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 GtkSizeRequestMode
+adw_view_stack_get_request_mode (GtkWidget *widget)
+{
+  GtkWidget *child;
+  int wfh = 0, hfw = 0;
+
+  for (child = gtk_widget_get_first_child (widget);
+       child;
+       child = gtk_widget_get_next_sibling (child)) {
+    GtkSizeRequestMode mode = gtk_widget_get_request_mode (child);
+
+    switch (mode) {
+    case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
+      hfw++;
+      break;
+    case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
+      wfh++;
+      break;
+    case GTK_SIZE_REQUEST_CONSTANT_SIZE:
+    default:
+      break;
+    }
+  }
+
+  if (hfw == 0 && wfh == 0)
+    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+  else
+    return wfh > hfw ?
+        GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT :
+        GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+
+static void
+adw_view_stack_size_allocate (GtkWidget *widget,
+                              int        width,
+                              int        height,
+                              int        baseline)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (widget);
+  GtkAllocation child_allocation;
+
+  if (self->last_visible_child) {
+    int child_width, child_height;
+    int min, nat;
+
+    gtk_widget_measure (self->last_visible_child->widget,
+                        GTK_ORIENTATION_HORIZONTAL, -1,
+                        &min, &nat, NULL, NULL);
+    child_width = MAX (min, width);
+    gtk_widget_measure (self->last_visible_child->widget,
+                        GTK_ORIENTATION_VERTICAL, child_width,
+                        &min, &nat, NULL, NULL);
+    child_height = MAX (min, height);
+
+    gtk_widget_allocate (self->last_visible_child->widget,
+                         child_width, child_height, -1, NULL);
+  }
+
+  child_allocation.x = 0;
+  child_allocation.y = 0;
+  child_allocation.width = width;
+  child_allocation.height = height;
+
+  if (self->visible_child) {
+    int min_width;
+    int min_height;
+
+    gtk_widget_measure (self->visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
+                        height, &min_width, NULL, NULL, NULL);
+    child_allocation.width = MAX (child_allocation.width, min_width);
+
+    gtk_widget_measure (self->visible_child->widget, GTK_ORIENTATION_VERTICAL,
+                        child_allocation.width, &min_height, NULL, NULL, NULL);
+    child_allocation.height = MAX (child_allocation.height, min_height);
+
+    if (child_allocation.width > width) {
+      GtkAlign halign = gtk_widget_get_halign (self->visible_child->widget);
+
+      if (halign == GTK_ALIGN_CENTER || halign == GTK_ALIGN_FILL)
+        child_allocation.x = (width - child_allocation.width) / 2;
+      else if (halign == GTK_ALIGN_END)
+        child_allocation.x = (width - child_allocation.width);
+    }
+
+    if (child_allocation.height > height) {
+      GtkAlign valign = gtk_widget_get_valign (self->visible_child->widget);
+
+      if (valign == GTK_ALIGN_CENTER || valign == GTK_ALIGN_FILL)
+        child_allocation.y = (height - child_allocation.height) / 2;
+      else if (valign == GTK_ALIGN_END)
+        child_allocation.y = (height - child_allocation.height);
+    }
+
+    gtk_widget_size_allocate (self->visible_child->widget, &child_allocation, -1);
+  }
+}
+
+static void
+adw_view_stack_measure (GtkWidget      *widget,
+                        GtkOrientation  orientation,
+                        int             for_size,
+                        int            *minimum,
+                        int            *natural,
+                        int            *minimum_baseline,
+                        int            *natural_baseline)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (widget);
+  int child_min, child_nat;
+  GList *l;
+
+  *minimum = 0;
+  *natural = 0;
+
+  for (l = self->children; l; l = l->next) {
+    AdwViewStackPage *page = l->data;
+    GtkWidget *child = page->widget;
+
+    if (((orientation == GTK_ORIENTATION_VERTICAL && !self->vhomogeneous) ||
+         (orientation == GTK_ORIENTATION_HORIZONTAL && !self->hhomogeneous)) &&
+         self->visible_child != page)
+      continue;
+
+    if (gtk_widget_get_visible (child)) {
+      gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
+
+      *minimum = MAX (*minimum, child_min);
+      *natural = MAX (*natural, child_nat);
+    }
+  }
+
+  if (self->last_visible_child) {
+    if (orientation == GTK_ORIENTATION_VERTICAL && !self->vhomogeneous) {
+      double t = self->interpolate_size ? gtk_progress_tracker_get_ease_out_cubic (&self->tracker, FALSE) : 
0.0;
+      *minimum = adw_lerp (*minimum, self->last_visible_widget_height, 1.0 - t);
+      *natural = adw_lerp (*natural, self->last_visible_widget_height, 1.0 - t);
+    }
+
+    if (orientation == GTK_ORIENTATION_HORIZONTAL && !self->hhomogeneous) {
+      double t = self->interpolate_size ? gtk_progress_tracker_get_ease_out_cubic (&self->tracker, FALSE) : 
0.0;
+      *minimum = adw_lerp (*minimum, self->last_visible_widget_width, 1.0 - t);
+      *natural = adw_lerp (*natural, self->last_visible_widget_width, 1.0 - t);
+    }
+  }
+}
+
+static void
+adw_view_stack_snapshot (GtkWidget   *widget,
+                         GtkSnapshot *snapshot)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (widget);
+  double progress;
+
+  if (!self->visible_child)
+    return;
+
+  if (gtk_progress_tracker_get_state (&self->tracker) == GTK_PROGRESS_STATE_AFTER) {
+    gtk_widget_snapshot_child (widget,
+                               self->visible_child->widget,
+                               snapshot);
+
+    return;
+  }
+
+  progress = gtk_progress_tracker_get_progress (&self->tracker, FALSE);
+
+  gtk_snapshot_push_clip (snapshot,
+                          &GRAPHENE_RECT_INIT(
+                              0, 0,
+                              gtk_widget_get_width (widget),
+                              gtk_widget_get_height (widget)
+                          ));
+  gtk_snapshot_push_cross_fade (snapshot, progress);
+
+  if (self->last_visible_child)
+    gtk_widget_snapshot_child (widget,
+                               self->last_visible_child->widget,
+                               snapshot);
+  gtk_snapshot_pop (snapshot);
+
+  gtk_widget_snapshot_child (widget,
+                             self->visible_child->widget,
+                             snapshot);
+  gtk_snapshot_pop (snapshot);
+
+  gtk_snapshot_pop (snapshot);
+}
+
+static void
+adw_view_stack_get_property (GObject    *object,
+                             guint       property_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (object);
+
+  switch (property_id) {
+  case PROP_HHOMOGENEOUS:
+    g_value_set_boolean (value, adw_view_stack_get_hhomogeneous (self));
+    break;
+  case PROP_VHOMOGENEOUS:
+    g_value_set_boolean (value, adw_view_stack_get_vhomogeneous (self));
+    break;
+  case PROP_VISIBLE_CHILD:
+    g_value_set_object (value, adw_view_stack_get_visible_child (self));
+    break;
+  case PROP_VISIBLE_CHILD_NAME:
+    g_value_set_string (value, adw_view_stack_get_visible_child_name (self));
+    break;
+  case PROP_TRANSITION_RUNNING:
+    g_value_set_boolean (value, adw_view_stack_get_transition_running (self));
+    break;
+  case PROP_INTERPOLATE_SIZE:
+    g_value_set_boolean (value, adw_view_stack_get_interpolate_size (self));
+    break;
+  case PROP_PAGES:
+    g_value_take_object (value, adw_view_stack_get_pages (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+adw_view_stack_set_property (GObject      *object,
+                             guint         property_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (object);
+
+  switch (property_id) {
+  case PROP_HHOMOGENEOUS:
+    adw_view_stack_set_hhomogeneous (self, g_value_get_boolean (value));
+    break;
+  case PROP_VHOMOGENEOUS:
+    adw_view_stack_set_vhomogeneous (self, g_value_get_boolean (value));
+    break;
+  case PROP_VISIBLE_CHILD:
+    adw_view_stack_set_visible_child (self, g_value_get_object (value));
+    break;
+  case PROP_VISIBLE_CHILD_NAME:
+    adw_view_stack_set_visible_child_name (self, g_value_get_string (value));
+    break;
+  case PROP_INTERPOLATE_SIZE:
+    adw_view_stack_set_interpolate_size (self, g_value_get_boolean (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+static void
+adw_view_stack_dispose (GObject *object)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (object);
+  GtkWidget *child;
+
+  if (self->pages)
+    g_list_model_items_changed (G_LIST_MODEL (self->pages), 0,
+                                g_list_length (self->children), 0);
+
+  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
+    stack_remove (self, child, TRUE);
+
+  G_OBJECT_CLASS (adw_view_stack_parent_class)->dispose (object);
+}
+
+static void
+adw_view_stack_finalize (GObject *object)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (object);
+
+  if (self->pages)
+    g_object_remove_weak_pointer (G_OBJECT (self->pages),
+                                  (gpointer *) &self->pages);
+
+  unschedule_ticks (self);
+
+  G_OBJECT_CLASS (adw_view_stack_parent_class)->finalize (object);
+}
+
+static void
+adw_view_stack_class_init (AdwViewStackClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->get_property = adw_view_stack_get_property;
+  object_class->set_property = adw_view_stack_set_property;
+  object_class->dispose = adw_view_stack_dispose;
+  object_class->finalize = adw_view_stack_finalize;
+
+  widget_class->size_allocate = adw_view_stack_size_allocate;
+  widget_class->snapshot = adw_view_stack_snapshot;
+  widget_class->measure = adw_view_stack_measure;
+  widget_class->compute_expand = adw_view_stack_compute_expand;
+  widget_class->get_request_mode = adw_view_stack_get_request_mode;
+
+  /**
+   * AdwViewStack:hhomogeneous: (attributes org.gtk.Property.get=adw_view_stack_get_hhomogeneous 
org.gtk.Property.set=adw_view_stack_set_hhomogeneous)
+   *
+   * Whether the stack allocates the same width for all children.
+   *
+   * If it's `FALSE`, the stack may change width when a different child becomes
+   * visible.
+   *
+   * Since: 1.0
+   */
+  props[PROP_HHOMOGENEOUS] =
+      g_param_spec_boolean ("hhomogeneous",
+                            "Horizontally homogeneous",
+                            "Whether the stack allocates the same width for all children",
+                            TRUE,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStack:vhomogeneous: (attributes org.gtk.Property.get=adw_view_stack_get_vhomogeneous 
org.gtk.Property.set=adw_view_stack_set_vhomogeneous)
+   *
+   * Whether the stack allocates the same height for all children.
+   *
+   * If it's `FALSE`, the stack may change height when a different child becomes
+   * visible.
+   *
+   * Since: 1.0
+   */
+  props[PROP_VHOMOGENEOUS] =
+      g_param_spec_boolean ("vhomogeneous",
+                            "Vertically homogeneous",
+                            "Whether the stack allocates the same height for all children",
+                            TRUE,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStack:visible-child: (attributes org.gtk.Property.get=adw_view_stack_get_visible_child 
org.gtk.Property.set=adw_view_stack_set_visible_child)
+   *
+   * The widget currently visible in the stack.
+   *
+   * Since: 1.0
+   */
+  props[PROP_VISIBLE_CHILD] =
+      g_param_spec_object ("visible-child",
+                           "Visible child",
+                           "The widget currently visible in the stack",
+                           GTK_TYPE_WIDGET,
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStack:visible-child-name: (attributes org.gtk.Property.get=adw_view_stack_get_visible_child_name 
org.gtk.Poperty.set=adw_view_stack_set_visible_child_name)
+   *
+   * The name of the widget currently visible in the stack.
+   *
+   * See [property@Adw.ViewStack:visible-child].
+   *
+   * Since: 1.0
+   */
+  props[PROP_VISIBLE_CHILD_NAME] =
+      g_param_spec_string ("visible-child-name",
+                           "Name of visible child",
+                           "The name of the widget currently visible in the stack",
+                           NULL,
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStack:transition-running: (attributes org.gtk.Property.get=adw_view_stack_get_transition_running)
+   *
+   * Whether a transition is currently running.
+   *
+   * Since: 1.0
+   */
+  props[PROP_TRANSITION_RUNNING] =
+      g_param_spec_boolean ("transition-running",
+                            "Transition running",
+                            "Whether a transition is currently running",
+                            FALSE,
+                            G_PARAM_READABLE);
+
+  /**
+   * AdwViewStack:interpolate-size: (attributes org.gtk.Property.get=adw_view_stack_get_interpolate_size 
org.gtk.Property.set=adw_view_stack_set_interpolate_size)
+   *
+   * Whether the stack interpolates its size when changing the visible child.
+   *
+   * Since: 1.0
+   */
+  props[PROP_INTERPOLATE_SIZE] =
+      g_param_spec_boolean ("interpolate-size",
+                            "Interpolate size",
+                            "Whether the stack interpolates its size when changing the visible child",
+                            FALSE,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwViewStack:pages: (attributes org.gtk.Property.get=adw_view_stack_get_pages)
+   *
+   * A selection model with the stack's pages.
+   *
+   * This can be used to keep an up-to-date view. The model also implements
+   * [iface@Gtk.SelectionModel] and can be used to track and change the visible
+   * page.
+   */
+  props[PROP_PAGES] =
+      g_param_spec_object ("pages",
+                           "Pages",
+                           "A selection model with the stack's pages",
+                           GTK_TYPE_SELECTION_MODEL,
+                           G_PARAM_READABLE);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_set_css_name (widget_class, "stack");
+  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
+}
+
+static void
+adw_view_stack_init (AdwViewStack *self)
+{
+  self->vhomogeneous = TRUE;
+  self->hhomogeneous = TRUE;
+  self->transition_duration = 200;
+}
+
+static void
+adw_view_stack_buildable_add_child (GtkBuildable *buildable,
+                                    GtkBuilder   *builder,
+                                    GObject      *child,
+                                    const char   *type)
+{
+  AdwViewStack *self = ADW_VIEW_STACK (buildable);
+
+  if (ADW_IS_VIEW_STACK_PAGE (child))
+    add_page (self, ADW_VIEW_STACK_PAGE (child));
+  else if (GTK_IS_WIDGET (child))
+    add_internal (self, GTK_WIDGET (child), NULL, NULL);
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+adw_view_stack_buildable_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = adw_view_stack_buildable_add_child;
+}
+
+/**
+ * adw_view_stack_page_get_child: (attributes org.gtk.Method.get_property=child)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets the stack child to which @self belongs.
+ *
+ * Returns: (transfer none): the child to which @self belongs
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_view_stack_page_get_child (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), NULL);
+
+  return self->widget;
+}
+
+/**
+ * adw_view_stack_page_get_visible: (attributes org.gtk.Method.get_property=visible)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets whether @self is visible in its `AdwViewStack`.
+ *
+ * This is independent from the [property@Gtk.Widget:visible]
+ * property of its widget.
+ *
+ * Returns: whether @self is visible
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_page_get_visible (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), FALSE);
+
+  return self->visible;
+}
+
+/**
+ * adw_view_stack_page_set_visible: (attributes org.gtk.Method.set_property=visible)
+ * @self: a `AdwViewStackPage`
+ * @visible: whether @self is visible
+ *
+ * Sets whether @page is visible in its `AdwViewStack`.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_visible (AdwViewStackPage *self,
+                                 gboolean          visible)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK_PAGE (self));
+
+  visible = !!visible;
+
+  if (visible == self->visible)
+    return;
+
+  self->visible = visible;
+
+  if (self->widget && gtk_widget_get_parent (self->widget))
+    update_child_visible (ADW_VIEW_STACK (gtk_widget_get_parent (self->widget)), self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_VISIBLE]);
+}
+
+/**
+ * adw_view_stack_page_get_needs_attention: (attributes org.gtk.Method.get_property=needs-attention)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets whether the page is marked as “needs attention”.
+ *
+ * Returns: whether the page needs attention
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_page_get_needs_attention (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), FALSE);
+
+  return self->needs_attention;
+}
+
+/**
+ * adw_view_stack_page_set_needs_attention: (attributes org.gtk.Method.set_property=needs-attention)
+ * @self: a `AdwViewStackPage`
+ * @needs_attention: the new value to set
+ *
+ * Sets whether the page is marked as “needs attention”.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_needs_attention (AdwViewStackPage *self,
+                                         gboolean          needs_attention)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK_PAGE (self));
+
+  needs_attention = !!needs_attention;
+
+  if (needs_attention == self->needs_attention)
+    return;
+
+  self->needs_attention = needs_attention;
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_NEEDS_ATTENTION]);
+}
+
+/**
+ * adw_view_stack_page_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets whether underlines in the page title indicate mnemonics.
+ *
+ * Returns: whether underlines in the page title indicate mnemonics
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_page_get_use_underline (AdwViewStackPage *self)
+{
+  return self->use_underline;
+}
+
+/**
+ * adw_view_stack_page_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
+ * @self: a `AdwViewStackPage`
+ * @use_underline: the new value to set
+ *
+ * Sets whether underlines in the page title indicate mnemonics.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_use_underline (AdwViewStackPage *self,
+                                       gboolean          use_underline)
+{
+  use_underline = !!use_underline;
+
+  if (use_underline == self->use_underline)
+    return;
+
+  self->use_underline = use_underline;
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_USE_UNDERLINE]);
+}
+
+/**
+ * adw_view_stack_page_get_name: (attributes org.gtk.Method.get_property=name)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets the name of the page.
+ *
+ * Returns: (nullable): the name of the page
+ *
+ * Since: 1.0
+ */
+const char *
+adw_view_stack_page_get_name (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), NULL);
+
+  return self->name;
+}
+
+/**
+ * adw_view_stack_page_set_name: (attributes org.gtk.Method.set_property=name)
+ * @self: a `AdwViewStackPage`
+ * @name: (nullable): the page name
+ *
+ * Sets the name of the page.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_name (AdwViewStackPage *self,
+                              const char       *name)
+{
+  AdwViewStack *stack = NULL;
+
+  g_return_if_fail (ADW_IS_VIEW_STACK_PAGE (self));
+
+  if (self->widget &&
+      gtk_widget_get_parent (self->widget) &&
+      ADW_IS_VIEW_STACK (gtk_widget_get_parent (self->widget))) {
+    GList *l;
+
+    stack = ADW_VIEW_STACK (gtk_widget_get_parent (self->widget));
+
+    for (l = stack->children; l; l = l->next) {
+      AdwViewStackPage *p = l->data;
+      if (self == p)
+        continue;
+
+      if (g_strcmp0 (p->name, name) == 0) {
+        g_warning ("Duplicate child name in AdwViewStack: %s", name);
+        break;
+      }
+    }
+  }
+
+  if (name == self->name)
+    return;
+
+  g_free (self->name);
+  self->name = g_strdup (name);
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_NAME]);
+
+  if (stack && stack->visible_child == self)
+    g_object_notify_by_pspec (G_OBJECT (stack),
+                              props[PROP_VISIBLE_CHILD_NAME]);
+}
+
+/**
+ * adw_view_stack_page_get_title: (attributes org.gtk.Method.get_property=title)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets the page title.
+ *
+ * Returns: (nullable): the page title
+ *
+ * Since: 1.0
+ */
+const char *
+adw_view_stack_page_get_title (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), NULL);
+
+  return self->title;
+}
+
+/**
+ * adw_view_stack_page_set_title: (attributes org.gtk.Method.set_property=title)
+ * @self: a `AdwViewStackPage`
+ * @title: (nullable): the page title
+ *
+ * Sets the page title.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_title (AdwViewStackPage *self,
+                               const char       *title)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK_PAGE (self));
+
+  if (title == self->title)
+    return;
+
+  g_free (self->title);
+  self->title = g_strdup (title);
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_TITLE]);
+}
+
+/**
+ * adw_view_stack_page_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
+ * @self: a `AdwViewStackPage`
+ *
+ * Gets the icon name of the page.
+ *
+ * Returns: (nullable): the icon name of the page
+ *
+ * Since: 1.0
+ */
+const char *
+adw_view_stack_page_get_icon_name (AdwViewStackPage *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK_PAGE (self), NULL);
+
+  return self->icon_name;
+}
+
+/**
+ * adw_view_stack_page_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
+ * @self: a `AdwViewStackPage`
+ * @icon_name: (nullable): the icon name
+ *
+ * Sets the icon name of the page.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_page_set_icon_name (AdwViewStackPage *self,
+                                   const char       *icon_name)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK_PAGE (self));
+
+  if (icon_name == self->icon_name)
+    return;
+
+  g_free (self->icon_name);
+  self->icon_name = g_strdup (icon_name);
+
+  g_object_notify_by_pspec (G_OBJECT (self), page_props[PAGE_PROP_ICON_NAME]);
+}
+
+/**
+ * adw_view_stack_new:
+ *
+ * Creates a new `AdwViewStack`.
+ *
+ * Returns: the newly created `AdwViewStack`
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_view_stack_new (void)
+{
+  return g_object_new (ADW_TYPE_VIEW_STACK, NULL);
+}
+
+/**
+ * adw_view_stack_add:
+ * @self: a `AdwViewStack`
+ * @child: the widget to add
+ *
+ * Adds a child to @self.
+ *
+ * Returns: (transfer none): the [class@Adw.ViewStackPage] for @child
+ *
+ * Since: 1.0
+ */
+AdwViewStackPage *
+adw_view_stack_add (AdwViewStack   *self,
+                    GtkWidget      *child)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+
+  return add_internal (self, child, NULL, NULL);
+}
+
+/**
+ * adw_view_stack_add_named:
+ * @self: a `AdwViewStack`
+ * @child: the widget to add
+ * @name: (nullable): the name for @child
+ *
+ * Adds a child to @self.
+ *
+ * The child is identified by the @name.
+ *
+ * Returns: (transfer none): the `AdwViewStackPage` for @child
+ *
+ * Since: 1.0
+ */
+AdwViewStackPage *
+adw_view_stack_add_named (AdwViewStack   *self,
+                          GtkWidget      *child,
+                          const char     *name)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+
+  return add_internal (self, child, name, NULL);
+}
+
+/**
+ * adw_view_stack_add_titled:
+ * @self: a `AdwViewStack`
+ * @child: the widget to add
+ * @name: (nullable): the name for @child
+ * @title: a human-readable title for @child
+ *
+ * Adds a child to @self.
+ *
+ * The child is identified by the @name. The @title will be used by
+ * [class@Adw.ViewSwitcher] to represent @child, so it should be short.
+ *
+ * Returns: (transfer none): the `AdwViewStackPage` for @child
+ *
+ * Since: 1.0
+ */
+AdwViewStackPage *
+adw_view_stack_add_titled (AdwViewStack   *self,
+                           GtkWidget      *child,
+                           const char     *name,
+                           const char     *title)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+
+  return add_internal (self, child, name, title);
+}
+
+/**
+ * adw_view_stack_remove:
+ * @self: a `AdwViewStack`
+ * @child: the child to remove
+ *
+ * Removes a child widget from @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_remove (AdwViewStack  *self,
+                       GtkWidget     *child)
+{
+  GList *l;
+  guint position;
+
+  g_return_if_fail (ADW_IS_VIEW_STACK (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+  g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (self));
+
+  for (l = self->children, position = 0; l; l = l->next, position++) {
+    AdwViewStackPage *page = l->data;
+
+    if (page->widget == child)
+      break;
+  }
+
+  stack_remove (self, child, FALSE);
+
+  if (self->pages)
+    g_list_model_items_changed (G_LIST_MODEL (self->pages), position, 1, 0);
+}
+
+/**
+ * adw_view_stack_get_page:
+ * @self: a `AdwViewStack`
+ * @child: a child of @self
+ *
+ * Gets the [class@Adw.ViewStackPage] object for @child.
+ *
+ * Returns: (transfer none): the page object for @child
+ *
+ * Since: 1.0
+ */
+AdwViewStackPage *
+adw_view_stack_get_page (AdwViewStack  *self,
+                         GtkWidget     *child)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+
+  return find_page_for_widget (self, child);
+}
+
+/**
+ * adw_view_stack_get_child_by_name:
+ * @self: a `AdwViewStack`
+ * @name: the name of the child to find
+ *
+ * Finds the child with @name in @self.
+ *
+ * Returns: (transfer none) (nullable): the requested child
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_view_stack_get_child_by_name (AdwViewStack *self,
+                                  const char   *name)
+{
+  AdwViewStackPage *page;
+
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  page = find_page_for_name (self, name);
+
+  return page ? page->widget : NULL;
+}
+
+/**
+ * adw_view_stack_get_visible_child: (attributes org.gtk.Method.get_property=visible-child)
+ * @self: a `AdwViewStack`
+ *
+ * Gets the currently visible child of @self, .
+ *
+ * Returns: (transfer none) (nullable): the visible child
+ *
+ * Since: 1.0
+ */
+GtkWidget *
+adw_view_stack_get_visible_child (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+
+  return self->visible_child ? self->visible_child->widget : NULL;
+}
+
+/**
+ * adw_view_stack_set_visible_child: (attributes org.gtk.Method.set_property=visible-child)
+ * @self: a `AdwViewStack`
+ * @child: a child of @self
+ *
+ * Makes @child the visible child of @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_set_visible_child (AdwViewStack *self,
+                                  GtkWidget    *child)
+{
+  AdwViewStackPage *page;
+
+  g_return_if_fail (ADW_IS_VIEW_STACK (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  page = find_page_for_widget (self, child);
+  if (!page) {
+    g_warning ("Given child of type '%s' not found in AdwViewStack",
+               G_OBJECT_TYPE_NAME (child));
+
+    return;
+  }
+
+  if (gtk_widget_get_visible (page->widget))
+    set_visible_child (self, page);
+}
+
+/**
+ * adw_view_stack_get_visible_child_name: (attributes org.gtk.Method.get_property=visible-child-name)
+ * @self: a `AdwViewStack`
+ *
+ * Returns the name of the currently visible child of @self.
+ *
+ * Returns: (transfer none) (nullable): the name of the visible child
+ *
+ * Since: 1.0
+ */
+const char *
+adw_view_stack_get_visible_child_name (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+
+  return self->visible_child ? self->visible_child->name : NULL;
+}
+
+/**
+ * adw_view_stack_set_visible_child_name: (attributes org.gtk.Method.set_property=visible-child-name)
+ * @self: a `AdwViewStack`
+ * @name: the name of the child
+ *
+ * Makes the child with @name visible.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_set_visible_child_name (AdwViewStack *self,
+                                       const char   *name)
+{
+  AdwViewStackPage *page;
+
+  g_return_if_fail (ADW_IS_VIEW_STACK (self));
+
+  if (name == NULL)
+    return;
+
+  page = find_page_for_name (self, name);
+
+  if (page == NULL) {
+    g_warning ("Child name '%s' not found in AdwViewStack", name);
+
+    return;
+  }
+
+  if (gtk_widget_get_visible (page->widget))
+    set_visible_child (self, page);
+}
+
+/**
+ * adw_view_stack_set_hhomogeneous: (attributes org.gtk.Method.set_property=hhomogeneous)
+ * @self: a `AdwViewStack`
+ * @hhomogeneous: whether to make @self horizontally homogeneous
+ *
+ * Sets @self to be horizontally homogeneous or not.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_set_hhomogeneous (AdwViewStack *self,
+                                 gboolean      hhomogeneous)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK (self));
+
+  hhomogeneous = !!hhomogeneous;
+
+  if (self->hhomogeneous == hhomogeneous)
+    return;
+
+  self->hhomogeneous = hhomogeneous;
+
+  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_HHOMOGENEOUS]);
+}
+
+/**
+ * adw_view_stack_get_hhomogeneous: (attributes org.gtk.Method.get_property=hhomogeneous)
+ * @self: a `AdwViewStack`
+ *
+ * Gets whether @self is horizontally homogeneous.
+ *
+ * Returns: whether @self is horizontally homogeneous
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_get_hhomogeneous (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), FALSE);
+
+  return self->hhomogeneous;
+}
+
+/**
+ * adw_view_stack_set_vhomogeneous: (attributes org.gtk.Method.set_property=vhomogeneous)
+ * @self: a `AdwViewStack`
+ * @vhomogeneous: whether to make @self vertically homogeneous
+ *
+ * Sets @self to be vertically homogeneous or not.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_set_vhomogeneous (AdwViewStack *self,
+                                 gboolean      vhomogeneous)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK (self));
+
+  vhomogeneous = !!vhomogeneous;
+
+  if (self->vhomogeneous == vhomogeneous)
+    return;
+
+  self->vhomogeneous = vhomogeneous;
+
+  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_VHOMOGENEOUS]);
+}
+
+/**
+ * adw_view_stack_get_vhomogeneous: (attributes org.gtk.Method.get_property=vhomogeneous)
+ * @self: a `AdwViewStack`
+ *
+ * Gets whether @self is vertically homogeneous.
+ *
+ * Returns: whether @self is vertically homogeneous
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_get_vhomogeneous (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), FALSE);
+
+  return self->vhomogeneous;
+}
+
+/**
+ * adw_view_stack_get_transition_running: (attributes org.gtk.Method.get_property=transition-running)
+ * @self: a `AdwViewStack`
+ *
+ * Gets whether the @self is currently in a transition from one page to another.
+ *
+ * Returns: whether a transition is currently running
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_get_transition_running (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), FALSE);
+
+  return (self->tick_id != 0);
+}
+
+/**
+ * adw_view_stack_set_interpolate_size: (attributes org.gtk.Method.set_property=interpolate-size)
+ * @self: A `AdwViewStack`
+ * @interpolate_size: the new value
+ *
+ * Sets whether @self will interpolate its size when changing the visible child.
+ *
+ * Since: 1.0
+ */
+void
+adw_view_stack_set_interpolate_size (AdwViewStack *self,
+                                     gboolean      interpolate_size)
+{
+  g_return_if_fail (ADW_IS_VIEW_STACK (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]);
+}
+
+/**
+ * adw_view_stack_get_interpolate_size: (attributes org.gtk.Method.get_property=interpolate-size)
+ * @self: A `AdwViewStack`
+ *
+ * Gets whether @self will interpolate its size when changing the visible child.
+ *
+ * Returns: whether child sizes are interpolated
+ *
+ * Since: 1.0
+ */
+gboolean
+adw_view_stack_get_interpolate_size (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), FALSE);
+
+  return self->interpolate_size;
+}
+
+/**
+ * adw_view_stack_get_pages: (attributes org.gtk.Method.get_property=pages)
+ * @self: a `AdwViewStack`
+ *
+ * Returns a `GListModel` that contains the pages of the stack.
+ *
+ * This can be used to keep an up-to-date view. The model also implements
+ * [iface@Gtk.SelectionModel] and can be used to track and change the visible
+ * page.
+ *
+ * Returns: (transfer full): a `GtkSelectionModel` for the stack's children
+ *
+ * Since: 1.0
+ */
+GtkSelectionModel *
+adw_view_stack_get_pages (AdwViewStack *self)
+{
+  g_return_val_if_fail (ADW_IS_VIEW_STACK (self), NULL);
+
+  if (self->pages)
+    return g_object_ref (self->pages);
+
+  self->pages = GTK_SELECTION_MODEL (adw_view_stack_pages_new (self));
+  g_object_add_weak_pointer (G_OBJECT (self->pages), (gpointer *) &self->pages);
+
+  return self->pages;
+}
diff --git a/src/adw-view-stack.h b/src/adw-view-stack.h
new file mode 100644
index 00000000..cd0d0c6a
--- /dev/null
+++ b/src/adw-view-stack.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc.
+ * Copyright (C) 2021 Frederick Schenk
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Based on gtkstack.h
+ * https://gitlab.gnome.org/GNOME/gtk/-/blob/ba44668478b7184bec02609f292691b85a2c6cdd/gtk/gtkstack.h
+ */
+
+#pragma once
+
+#if !defined  (_ADWAITA_INSIDE) && !defined  (ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include "adw-version.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_VIEW_STACK_PAGE (adw_view_stack_page_get_type())
+
+ADW_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (AdwViewStackPage, adw_view_stack_page, ADW, VIEW_STACK_PAGE, GObject)
+
+ADW_AVAILABLE_IN_ALL
+GtkWidget *adw_view_stack_page_get_child (AdwViewStackPage *self);
+
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_page_get_visible (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_page_set_visible (AdwViewStackPage *self,
+                                          gboolean          visible);
+
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_page_get_needs_attention (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_page_set_needs_attention (AdwViewStackPage *self,
+                                                  gboolean          needs_attention);
+
+ADW_AVAILABLE_IN_ALL
+guint adw_view_stack_page_get_counter (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void  adw_view_stack_page_set_counter (AdwViewStackPage *self,
+                                       guint             counter);
+
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_page_get_use_underline (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_page_set_use_underline (AdwViewStackPage *self,
+                                                gboolean          use_underline);
+
+ADW_AVAILABLE_IN_ALL
+const char *adw_view_stack_page_get_name (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_view_stack_page_set_name (AdwViewStackPage *self,
+                                          const char       *name);
+
+ADW_AVAILABLE_IN_ALL
+const char *adw_view_stack_page_get_title (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_view_stack_page_set_title (AdwViewStackPage *self,
+                                           const char       *title);
+ADW_AVAILABLE_IN_ALL
+const char *adw_view_stack_page_get_icon_name (AdwViewStackPage *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_view_stack_page_set_icon_name (AdwViewStackPage *self,
+                                               const char       *icon_name);
+
+#define ADW_TYPE_VIEW_STACK (adw_view_stack_get_type())
+
+ADW_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (AdwViewStack, adw_view_stack, ADW, VIEW_STACK, GtkWidget)
+
+ADW_AVAILABLE_IN_ALL
+GtkWidget *adw_view_stack_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_ALL
+AdwViewStackPage *adw_view_stack_add       (AdwViewStack *self,
+                                            GtkWidget    *child);
+ADW_AVAILABLE_IN_ALL
+AdwViewStackPage *adw_view_stack_add_named (AdwViewStack *self,
+                                            GtkWidget    *child,
+                                            const char   *name);
+ADW_AVAILABLE_IN_ALL
+AdwViewStackPage *adw_view_stack_add_titled (AdwViewStack *self,
+                                             GtkWidget    *child,
+                                             const char   *name,
+                                             const char   *title);
+
+ADW_AVAILABLE_IN_ALL
+void adw_view_stack_remove (AdwViewStack *self,
+                            GtkWidget    *child);
+
+ADW_AVAILABLE_IN_ALL
+AdwViewStackPage *adw_view_stack_get_page (AdwViewStack *self,
+                                           GtkWidget    *child);
+
+ADW_AVAILABLE_IN_ALL
+GtkWidget *adw_view_stack_get_child_by_name (AdwViewStack *self,
+                                             const char   *name);
+
+ADW_AVAILABLE_IN_ALL
+void       adw_view_stack_set_visible_child (AdwViewStack *self,
+                                             GtkWidget    *child);
+ADW_AVAILABLE_IN_ALL
+GtkWidget *adw_view_stack_get_visible_child (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+void        adw_view_stack_set_visible_child_name (AdwViewStack *self,
+                                                   const char   *name);
+ADW_AVAILABLE_IN_ALL
+const char *adw_view_stack_get_visible_child_name (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_set_hhomogeneous (AdwViewStack *self,
+                                          gboolean      hhomogeneous);
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_get_hhomogeneous (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_set_vhomogeneous (AdwViewStack *self,
+                                          gboolean      vhomogeneous);
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_get_vhomogeneous (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_get_transition_running (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+void     adw_view_stack_set_interpolate_size (AdwViewStack *self,
+                                              gboolean      interpolate_size);
+ADW_AVAILABLE_IN_ALL
+gboolean adw_view_stack_get_interpolate_size (AdwViewStack *self);
+
+ADW_AVAILABLE_IN_ALL
+GtkSelectionModel *adw_view_stack_get_pages (AdwViewStack *self);
+
+G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index ba9bf074..e5c4e9c6 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -54,6 +54,7 @@ G_BEGIN_DECLS
 #include "adw-tab-bar.h"
 #include "adw-tab-view.h"
 #include "adw-value-object.h"
+#include "adw-view-stack.h"
 #include "adw-view-switcher.h"
 #include "adw-view-switcher-bar.h"
 #include "adw-view-switcher-title.h"
diff --git a/src/meson.build b/src/meson.build
index 95ee4d63..4fe2d2f2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -99,6 +99,7 @@ src_headers = [
   'adw-tab-bar.h',
   'adw-tab-view.h',
   'adw-value-object.h',
+  'adw-view-stack.h',
   'adw-view-switcher.h',
   'adw-view-switcher-bar.h',
   'adw-view-switcher-title.h',
@@ -158,6 +159,7 @@ src_sources = [
   'adw-tab-box.c',
   'adw-tab-view.c',
   'adw-value-object.c',
+  'adw-view-stack.c',
   'adw-view-switcher.c',
   'adw-view-switcher-bar.c',
   'adw-view-switcher-button.c',


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