[libadwaita/wip/exalm/browsing-view: 13/18] AdwBrowsingView




commit f5451663c22cdae9d5c47964b85d93d28e05b176
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sun Oct 9 02:15:23 2022 +0400

    AdwBrowsingView

 src/adw-browsing-view.c                        | 1750 ++++++++++++++++++++++++
 src/adw-browsing-view.h                        |  130 ++
 src/adwaita.h                                  |    1 +
 src/meson.build                                |    2 +
 src/stylesheet/widgets/_transition-shadow.scss |    3 +-
 5 files changed, 1885 insertions(+), 1 deletion(-)
---
diff --git a/src/adw-browsing-view.c b/src/adw-browsing-view.c
new file mode 100644
index 00000000..0e410950
--- /dev/null
+++ b/src/adw-browsing-view.c
@@ -0,0 +1,1750 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+#include "adw-browsing-view.h"
+
+#include "adw-shadow-helper-private.h"
+#include "adw-spring-animation.h"
+#include "adw-widget-utils-private.h"
+
+/**
+ * AdwBrowsingView:
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+
+/**
+ * AdwBrowsingViewChild:
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+
+typedef struct
+{
+  GtkWidget *child;
+  char *title;
+  char *name;
+
+  GtkWidget *last_focus;
+} AdwBrowsingViewChildPrivate;
+
+static void adw_browsing_view_child_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (AdwBrowsingViewChild, adw_browsing_view_child, GTK_TYPE_WIDGET,
+                         G_ADD_PRIVATE (AdwBrowsingViewChild)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_browsing_view_child_buildable_init))
+
+static GtkBuildableIface *parent_buildable_iface;
+
+enum {
+  CHILD_PROP_0,
+  CHILD_PROP_CHILD,
+  CHILD_PROP_TITLE,
+  CHILD_PROP_CHILD_NAME,
+  LAST_CHILD_PROP
+};
+
+static GParamSpec *child_props[LAST_CHILD_PROP];
+
+enum {
+  CHILD_SIGNAL_SHOWING,
+  CHILD_SIGNAL_SHOWN,
+  CHILD_SIGNAL_HIDING,
+  CHILD_SIGNAL_HIDDEN,
+  LAST_CHILD_SIGNAL,
+};
+
+static guint child_signals[LAST_CHILD_SIGNAL];
+
+struct _AdwBrowsingView
+{
+  GtkWidget parent_instance;
+
+  GHashTable *child_mapping;
+  GSList *navigation_stack;
+
+  AdwAnimation *transition;
+  AdwBrowsingViewChild *hiding_child;
+  gboolean transition_pop;
+  gboolean transition_phony;
+
+  AdwShadowHelper *shadow_helper;
+
+  AdwBrowsingViewChild *last_child;
+
+  AdwBrowsingView *previous_view;
+  AdwBrowsingView *next_view;
+};
+
+static void adw_browsing_view_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (AdwBrowsingView, adw_browsing_view, GTK_TYPE_WIDGET,
+                               G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_browsing_view_buildable_init))
+
+enum {
+  PROP_0,
+  PROP_VISIBLE_CHILD,
+  PROP_PREVIOUS_VIEW,
+  PROP_NEXT_VIEW,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+  SIGNAL_PUSHED,
+  SIGNAL_POPPED,
+  SIGNAL_GET_NEXT_CHILD,
+  LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+adw_browsing_view_child_dispose (GObject *object)
+{
+  AdwBrowsingViewChild *self = ADW_BROWSING_VIEW_CHILD (object);
+  AdwBrowsingViewChildPrivate *priv = adw_browsing_view_child_get_instance_private (self);
+
+  g_clear_pointer (&priv->child, gtk_widget_unparent);
+
+  G_OBJECT_CLASS (adw_browsing_view_child_parent_class)->dispose (object);
+}
+
+static void
+adw_browsing_view_child_finalize (GObject *object)
+{
+  AdwBrowsingViewChild *self = ADW_BROWSING_VIEW_CHILD (object);
+  AdwBrowsingViewChildPrivate *priv = adw_browsing_view_child_get_instance_private (self);
+
+  g_free (priv->title);
+  g_free (priv->name);
+
+  if (priv->last_focus)
+    g_object_remove_weak_pointer (G_OBJECT (priv->last_focus),
+                                  (gpointer *) &priv->last_focus);
+
+  G_OBJECT_CLASS (adw_browsing_view_child_parent_class)->finalize (object);
+}
+
+static void
+adw_browsing_view_child_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+  AdwBrowsingViewChild *self = ADW_BROWSING_VIEW_CHILD (object);
+
+  switch (prop_id) {
+  case CHILD_PROP_CHILD:
+    g_value_set_object (value, adw_browsing_view_child_get_child (self));
+    break;
+  case CHILD_PROP_TITLE:
+    g_value_set_string (value, adw_browsing_view_child_get_title (self));
+    break;
+  case CHILD_PROP_CHILD_NAME:
+    g_value_set_string (value, adw_browsing_view_child_get_child_name (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_browsing_view_child_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+  AdwBrowsingViewChild *self = ADW_BROWSING_VIEW_CHILD (object);
+
+  switch (prop_id) {
+  case CHILD_PROP_CHILD:
+    adw_browsing_view_child_set_child (self, g_value_get_object (value));
+    break;
+  case CHILD_PROP_TITLE:
+    adw_browsing_view_child_set_title (self, g_value_get_string (value));
+    break;
+  case CHILD_PROP_CHILD_NAME:
+    adw_browsing_view_child_set_child_name (self, g_value_get_string (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_browsing_view_child_class_init (AdwBrowsingViewChildClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = adw_browsing_view_child_dispose;
+  object_class->finalize = adw_browsing_view_child_finalize;
+  object_class->get_property = adw_browsing_view_child_get_property;
+  object_class->set_property = adw_browsing_view_child_set_property;
+
+  widget_class->compute_expand = adw_widget_compute_expand;
+
+  /**
+   * AdwBrowsingViewChild:child: (attributes org.gtk.Property.get=adw_browsing_view_child_get_child 
org.gtk.Property.set=adw_browsing_view_child_set_child)
+   *
+   * The child widget.
+   *
+   * Since: 1.3
+   */
+  child_props[CHILD_PROP_CHILD] =
+    g_param_spec_object ("child", NULL, NULL,
+                         GTK_TYPE_WIDGET,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwBrowsingViewChild:title: (attributes org.gtk.Property.get=adw_browsing_view_child_get_title 
org.gtk.Property.set=adw_browsing_view_child_set_title)
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_props[CHILD_PROP_TITLE] =
+    g_param_spec_string ("title", NULL, NULL,
+                         "",
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwBrowsingViewChild:child-name: (attributes 
org.gtk.Property.get=adw_browsing_view_child_get_child_name 
org.gtk.Property.set=adw_browsing_view_child_set_child_name)
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_props[CHILD_PROP_CHILD_NAME] =
+    g_param_spec_string ("child-name", NULL, NULL,
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_CHILD_PROP, child_props);
+
+  /**
+   * AdwBrowsingViewChild::showing:
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_signals[CHILD_SIGNAL_SHOWING] =
+    g_signal_new ("showing",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (AdwBrowsingViewChildClass, showing),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  /**
+   * AdwBrowsingViewChild::shown:
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_signals[CHILD_SIGNAL_SHOWN] =
+    g_signal_new ("shown",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (AdwBrowsingViewChildClass, shown),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  /**
+   * AdwBrowsingViewChild::hiding:
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_signals[CHILD_SIGNAL_HIDING] =
+    g_signal_new ("hiding",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (AdwBrowsingViewChildClass, hiding),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  /**
+   * AdwBrowsingViewChild::hidden:
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  child_signals[CHILD_SIGNAL_HIDDEN] =
+    g_signal_new ("hidden",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (AdwBrowsingViewChildClass, hidden),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+}
+
+static void
+adw_browsing_view_child_init (AdwBrowsingViewChild *self)
+{
+  AdwBrowsingViewChildPrivate *priv = adw_browsing_view_child_get_instance_private (self);
+
+  priv->title = g_strdup ("");
+}
+
+static void
+adw_browsing_view_child_buildable_add_child (GtkBuildable *buildable,
+                                             GtkBuilder   *builder,
+                                             GObject      *child,
+                                             const char   *type)
+{
+  if (GTK_IS_WIDGET (child))
+    adw_browsing_view_child_set_child (ADW_BROWSING_VIEW_CHILD (buildable), GTK_WIDGET (child));
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+adw_browsing_view_child_buildable_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = adw_browsing_view_child_buildable_add_child;
+}
+
+static void
+switch_child (AdwBrowsingView      *self,
+              AdwBrowsingViewChild *prev_child,
+              AdwBrowsingViewChild *child,
+              gboolean              pop,
+              gboolean              animate)
+{
+  GtkWidget *focus = NULL;
+  gboolean contains_focus = FALSE;
+  GtkRoot *root;
+
+  g_assert (child != prev_child);
+  g_assert (child || prev_child);
+
+  if (gtk_widget_in_destruction (GTK_WIDGET (self)))
+    return;
+
+  root = gtk_widget_get_root (GTK_WIDGET (self));
+  if (root)
+    focus = gtk_root_get_focus (root);
+
+  if (focus && prev_child && gtk_widget_is_ancestor (focus, GTK_WIDGET (prev_child))) {
+    AdwBrowsingViewChildPrivate *priv = adw_browsing_view_child_get_instance_private (prev_child);
+
+    contains_focus = TRUE;
+
+    if (priv->last_focus)
+      g_object_remove_weak_pointer (G_OBJECT (priv->last_focus),
+                                    (gpointer *)&priv->last_focus);
+    priv->last_focus = focus;
+    g_object_add_weak_pointer (G_OBJECT (priv->last_focus),
+                               (gpointer *)&priv->last_focus);
+  }
+
+  if (self->hiding_child) {
+    g_signal_emit (self->hiding_child, child_signals[CHILD_SIGNAL_HIDDEN], 0);
+    gtk_widget_set_child_visible (GTK_WIDGET (self->hiding_child), FALSE);
+  }
+
+  if (child) {
+    AdwBrowsingViewChildPrivate *priv = adw_browsing_view_child_get_instance_private (child);
+
+    gtk_widget_set_child_visible (GTK_WIDGET (child), TRUE);
+    g_signal_emit (child, child_signals[CHILD_SIGNAL_SHOWING], 0);
+
+    if (contains_focus) {
+      if (priv->last_focus)
+        gtk_widget_grab_focus (priv->last_focus);
+      else
+        gtk_widget_child_focus (GTK_WIDGET (child), GTK_DIR_TAB_FORWARD);
+    }
+  }
+
+  adw_animation_reset (self->transition);
+
+  self->hiding_child = prev_child;
+  self->transition_pop = pop;
+  self->transition_phony = FALSE;
+
+  if (!child)
+    self->last_child = prev_child;
+
+  if (self->hiding_child)
+    g_signal_emit (self->hiding_child, child_signals[CHILD_SIGNAL_HIDING], 0);
+
+  if (animate)
+    adw_animation_play (self->transition);
+  else
+    adw_animation_skip (self->transition);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
+}
+
+static void
+push_to_stack (AdwBrowsingView      *self,
+               AdwBrowsingViewChild *child,
+               gboolean              animate,
+               gboolean              emit_signal)
+{
+  AdwBrowsingViewChild *previous_child = adw_browsing_view_get_visible_child (self);
+
+  if (child == previous_child)
+    return;
+
+  if (g_slist_find (self->navigation_stack, child)) {
+    g_critical ("Child '%s' is already in navigation stack\n",
+                adw_browsing_view_child_get_title (child));
+    return;
+  }
+
+  self->navigation_stack = g_slist_prepend (self->navigation_stack, child);
+
+  switch_child (self, previous_child, child, FALSE, animate);
+
+  if (!previous_child && self->previous_view) {
+    AdwBrowsingViewChild *prev_visible_child = adw_browsing_view_get_visible_child (self->previous_view);
+
+    adw_animation_reset (self->previous_view->transition);
+
+    self->previous_view->hiding_child = prev_visible_child;
+    self->previous_view->transition_pop = FALSE;
+    self->previous_view->transition_phony = TRUE;
+
+    if (animate)
+      adw_animation_play (self->previous_view->transition);
+    else
+      adw_animation_skip (self->previous_view->transition);
+  }
+
+  g_signal_emit (self, signals[SIGNAL_PUSHED], 0);
+}
+
+static void
+pop_from_stack (AdwBrowsingView      *self,
+                AdwBrowsingViewChild *child_to,
+                gboolean              animate)
+{
+  AdwBrowsingViewChild *old_child;
+  AdwBrowsingViewChild *new_child;
+  GSList *popped = NULL, *l;
+
+  g_assert (self->navigation_stack);
+
+  old_child = adw_browsing_view_get_visible_child (self);
+
+  while (self->navigation_stack &&
+         self->navigation_stack->data &&
+         self->navigation_stack->data != child_to) {
+    AdwBrowsingViewChild *c = self->navigation_stack->data;
+
+    popped = g_slist_prepend (popped, c);
+    self->navigation_stack = g_slist_remove (self->navigation_stack, c);
+  }
+
+  new_child = adw_browsing_view_get_visible_child (self);
+
+  switch_child (self, old_child, new_child, TRUE, animate);
+
+  if (!new_child && self->previous_view) {
+    AdwBrowsingViewChild *prev_visible_child = adw_browsing_view_get_visible_child (self->previous_view);
+
+    adw_animation_reset (self->previous_view->transition);
+
+    self->previous_view->hiding_child = prev_visible_child;
+    self->previous_view->transition_pop = TRUE;
+    self->previous_view->transition_phony = TRUE;
+
+    if (animate)
+      adw_animation_play (self->previous_view->transition);
+    else
+      adw_animation_skip (self->previous_view->transition);
+  }
+
+  for (l = 0; l; l = l->next)
+    g_signal_emit (self, signals[SIGNAL_POPPED], 0, l->data);
+
+  g_slist_free (popped);
+}
+
+static void
+transition_cb (double           value,
+               AdwBrowsingView *self)
+{
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+transition_done_cb (AdwBrowsingView *self)
+{
+  if (self->hiding_child) {
+    if (!self->transition_phony) {
+      g_signal_emit (self->hiding_child, child_signals[CHILD_SIGNAL_HIDDEN], 0);
+      gtk_widget_set_child_visible (GTK_WIDGET (self->hiding_child), FALSE);
+    }
+    self->hiding_child = NULL;
+  }
+
+  if (!self->transition_phony) {
+    AdwBrowsingViewChild *visible_child = adw_browsing_view_get_visible_child (self);
+
+    if (visible_child)
+      g_signal_emit (visible_child, child_signals[CHILD_SIGNAL_SHOWN], 0);
+  }
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+static void
+browsing_push_cb (AdwBrowsingView *self,
+                  const char      *action_name,
+                  GVariant        *params)
+{
+  const char *name = g_variant_get_string (params, NULL);
+
+  adw_browsing_view_push_by_name (self, name, TRUE);
+}
+
+static void
+browsing_pop_cb (AdwBrowsingView *self)
+{
+  adw_browsing_view_pop (self, TRUE);
+}
+
+static AdwBrowsingViewChild *
+get_next_child (AdwBrowsingView *self)
+{
+  AdwBrowsingViewChild *child = NULL;
+
+  g_signal_emit (self, signals[SIGNAL_GET_NEXT_CHILD], 0, &child);
+
+  if (!child)
+    return NULL;
+
+  if (gtk_widget_get_parent (GTK_WIDGET (child)) != GTK_WIDGET (self)) {
+    // TODO: critical about it not being a child
+    return NULL;
+  }
+
+  return child;
+}
+
+static gboolean
+back_forward_shortcut_cb (AdwBrowsingView *self,
+                          GVariant        *args)
+{
+  gboolean is_pop = FALSE;
+
+  g_variant_get (args, "b", &is_pop);
+
+  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+    is_pop = !is_pop;
+
+  if (is_pop) {
+    AdwBrowsingViewChild *child = adw_browsing_view_get_visible_child (self);
+
+    if (!adw_browsing_view_get_previous_child (self, child))
+      return GDK_EVENT_PROPAGATE;
+
+    // TODO: check if it's enabled
+
+    adw_browsing_view_pop (self, TRUE);
+  } else {
+    AdwBrowsingViewChild *next_page = get_next_child (self);
+
+    if (!next_page)
+      return GDK_EVENT_PROPAGATE;
+
+    adw_browsing_view_push (self, GTK_WIDGET (next_page), TRUE);
+  }
+
+  return GDK_EVENT_STOP;
+}
+
+static void
+back_forward_button_pressed_cb (GtkGesture      *gesture,
+                                int              n_press,
+                                double           x,
+                                double           y,
+                                AdwBrowsingView *self)
+{
+  gboolean is_pop = FALSE;
+  guint button;
+
+  if (n_press > 1) {
+    gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+    return;
+  }
+
+  button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
+
+  /* Unfortunately, there are no constants for these buttons */
+  if (button == 8) {
+    is_pop = TRUE;
+  } else if (button == 9) {
+    is_pop = FALSE;
+  } else {
+    gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+    return;
+  }
+
+  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+    is_pop = !is_pop;
+
+  if (is_pop) {
+    AdwBrowsingViewChild *child = adw_browsing_view_get_visible_child (self);
+
+    if (!adw_browsing_view_get_previous_child (self, child)) {
+      gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+    // TODO: check if it's enabled
+
+    adw_browsing_view_pop (self, TRUE);
+  } else {
+    AdwBrowsingViewChild *next_page = get_next_child (self);
+
+    if (!next_page) {
+      gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
+      return;
+    }
+
+    adw_browsing_view_push (self, GTK_WIDGET (next_page), TRUE);
+  }
+
+  gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
+}
+
+static void
+adw_browsing_view_measure (GtkWidget      *widget,
+                           GtkOrientation  orientation,
+                           int             for_size,
+                           int            *minimum,
+                           int            *natural,
+                           int            *minimum_baseline,
+                           int            *natural_baseline)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (widget);
+  AdwBrowsingViewChild *visible_child = NULL;
+  int min = 0, nat = 0, min_baseline = -1, nat_baseline = -1;
+  int last_min = 0, last_nat = 0, last_min_baseline = -1, last_nat_baseline = -1;
+
+  visible_child = adw_browsing_view_get_visible_child (self);
+
+  if (visible_child)
+    gtk_widget_measure (GTK_WIDGET (visible_child), orientation, for_size,
+                        &min, &nat, &min_baseline, &nat_baseline);
+
+  if (self->hiding_child)
+    gtk_widget_measure (GTK_WIDGET (self->hiding_child),
+                        orientation, for_size, &last_min, &last_nat,
+                        &last_min_baseline, &last_nat_baseline);
+
+  if (minimum)
+    *minimum = MAX (min, last_min);
+  if (natural)
+    *natural = MAX (nat, last_nat);
+  if (minimum_baseline)
+    *minimum_baseline = MAX (min_baseline, last_min_baseline);
+  if (natural_baseline)
+    *natural_baseline = MAX (nat_baseline, last_nat_baseline);
+}
+
+static void
+adw_browsing_view_size_allocate (GtkWidget *widget,
+                                 int        width,
+                                 int        height,
+                                 int        baseline)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (widget);
+  AdwBrowsingViewChild *visible_child = NULL;
+  GtkWidget *static_child = NULL, *moving_child = NULL;
+  gboolean is_rtl;
+  double progress;
+  int offset;
+
+  visible_child = adw_browsing_view_get_visible_child (self);
+
+  is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
+
+  if (adw_animation_get_state (self->transition) != ADW_ANIMATION_PLAYING) {
+    if (visible_child)
+      gtk_widget_allocate (GTK_WIDGET (visible_child), width, height, baseline, NULL);
+
+    adw_shadow_helper_size_allocate (self->shadow_helper, 0, 0,
+                                     baseline, 0, 0, 0,
+                                     is_rtl ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT);
+    return;
+  }
+
+  if (self->transition_pop) {
+    if (visible_child)
+      static_child = GTK_WIDGET (visible_child);
+    if (self->hiding_child && visible_child != self->hiding_child)
+      moving_child = GTK_WIDGET (self->hiding_child);
+  } else {
+    if (self->hiding_child)
+      static_child = GTK_WIDGET (self->hiding_child);
+    if (visible_child && visible_child != self->hiding_child)
+      moving_child = GTK_WIDGET (visible_child);
+  }
+
+  progress = adw_animation_get_value (self->transition);
+
+  if (!self->transition_pop)
+    progress = 1 - progress;
+
+  offset = (int) round (progress * width);
+
+  if (static_child)
+    gtk_widget_allocate (static_child, width, height, baseline, NULL);
+
+  if (is_rtl) {
+    if (moving_child)
+      gtk_widget_allocate (moving_child, width, height, baseline,
+                           gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (-offset, 0)));
+
+    adw_shadow_helper_size_allocate (self->shadow_helper, offset, height,
+                                     baseline, width - offset, 0, progress,
+                                     GTK_PAN_DIRECTION_LEFT);
+  } else {
+    if (moving_child)
+      gtk_widget_allocate (moving_child, width, height, baseline,
+                           gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (offset, 0)));
+
+    adw_shadow_helper_size_allocate (self->shadow_helper, offset, height,
+                                     baseline, 0, 0, progress,
+                                     GTK_PAN_DIRECTION_RIGHT);
+  }
+}
+
+static void
+adw_browsing_view_snapshot (GtkWidget   *widget,
+                            GtkSnapshot *snapshot)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (widget);
+  AdwBrowsingViewChild *visible_child = NULL;
+  GtkWidget *static_child = NULL, *moving_child = NULL;
+  int width, height;
+  int offset;
+  int clip_x, clip_width;
+  double progress;
+
+  if (adw_animation_get_state (self->transition) != ADW_ANIMATION_PLAYING) {
+    if (self->next_view && adw_browsing_view_get_visible_child (self->next_view))
+      return;
+
+    GTK_WIDGET_CLASS (adw_browsing_view_parent_class)->snapshot (widget, snapshot);
+    return;
+  }
+
+  visible_child = adw_browsing_view_get_visible_child (self);
+
+  if (self->transition_pop) {
+    if (visible_child)
+      static_child = GTK_WIDGET (visible_child);
+    if (self->hiding_child && visible_child != self->hiding_child)
+      moving_child = GTK_WIDGET (self->hiding_child);
+  } else {
+    if (self->hiding_child)
+      static_child = GTK_WIDGET (self->hiding_child);
+    if (visible_child && visible_child != self->hiding_child)
+      moving_child = GTK_WIDGET (visible_child);
+  }
+
+  width = gtk_widget_get_width (widget);
+  height = gtk_widget_get_height (widget);
+  progress = adw_animation_get_value (self->transition);
+
+  if (!self->transition_pop)
+    progress = 1 - progress;
+
+  offset = (int) round (progress * width);
+
+  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
+    clip_x = width - offset;
+    clip_width = offset;
+  } else {
+    clip_x = 0;
+    clip_width = offset;
+  }
+
+  if (static_child) {
+    gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (clip_x, 0, clip_width, height));
+    gtk_widget_snapshot_child (widget, static_child, snapshot);
+    gtk_snapshot_pop (snapshot);
+  }
+
+  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) {
+    clip_x = 0;
+    clip_width = width - offset;
+  } else {
+    clip_x = offset;
+    clip_width = width - offset;
+  }
+
+  if (moving_child) {
+    gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (clip_x, 0, clip_width, height));
+    gtk_widget_snapshot_child (widget, moving_child, snapshot);
+    gtk_snapshot_pop (snapshot);
+  }
+
+  if (!self->transition_phony)
+    adw_shadow_helper_snapshot (self->shadow_helper, snapshot);
+}
+
+static gboolean
+adw_browsing_view_contains (GtkWidget *widget,
+                            double     x,
+                            double     y)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (widget);
+
+  if (self->previous_view && !adw_browsing_view_get_visible_child (self))
+    return FALSE;
+
+  return GTK_WIDGET_CLASS (adw_browsing_view_parent_class)->contains (widget, x, y);
+}
+
+static void
+adw_browsing_view_dispose (GObject *object)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (object);
+  GtkWidget *child;
+
+  g_clear_object (&self->shadow_helper);
+
+  while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
+    gtk_widget_unparent (child);
+
+  g_clear_pointer (&self->child_mapping, g_hash_table_unref);
+  g_clear_object (&self->transition);
+
+  G_OBJECT_CLASS (adw_browsing_view_parent_class)->dispose (object);
+}
+
+static void
+adw_browsing_view_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (object);
+
+  switch (prop_id) {
+  case PROP_VISIBLE_CHILD:
+    g_value_set_object (value, adw_browsing_view_get_visible_child (self));
+    break;
+  case PROP_PREVIOUS_VIEW:
+    g_value_set_object (value, adw_browsing_view_get_previous_view (self));
+    break;
+  case PROP_NEXT_VIEW:
+    g_value_set_object (value, adw_browsing_view_get_next_view (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_browsing_view_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  AdwBrowsingView *self = ADW_BROWSING_VIEW (object);
+
+  switch (prop_id) {
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static gboolean
+object_handled_accumulator (GSignalInvocationHint *ihint,
+                            GValue                *return_accu,
+                            const GValue          *handler_return,
+                            gpointer               data)
+{
+  GObject *object = g_value_get_object (handler_return);
+
+  g_value_set_object (return_accu, object);
+
+  return !object;
+}
+
+static void
+adw_browsing_view_class_init (AdwBrowsingViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = adw_browsing_view_dispose;
+  object_class->get_property = adw_browsing_view_get_property;
+  object_class->set_property = adw_browsing_view_set_property;
+
+  widget_class->measure = adw_browsing_view_measure;
+  widget_class->size_allocate = adw_browsing_view_size_allocate;
+  widget_class->snapshot = adw_browsing_view_snapshot;
+  widget_class->contains = adw_browsing_view_contains;
+  widget_class->get_request_mode = adw_widget_get_request_mode;
+  widget_class->compute_expand = adw_widget_compute_expand;
+
+  /**
+   * AdwBrowsingView:visible-child: (attributes 
org.gtk.Property.get=adw_browsing_view_child_get_visible_child)
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  props[PROP_VISIBLE_CHILD] =
+    g_param_spec_object ("visible-child", NULL, NULL,
+                         ADW_TYPE_BROWSING_VIEW_CHILD,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * AdwBrowsingView:previous-view: (attributes 
org.gtk.Property.get=adw_browsing_view_child_get_previous_view)
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  props[PROP_PREVIOUS_VIEW] =
+    g_param_spec_object ("previous-view", NULL, NULL,
+                         ADW_TYPE_BROWSING_VIEW,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * AdwBrowsingView:next-view: (attributes org.gtk.Property.get=adw_browsing_view_child_get_next_view)
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  props[PROP_NEXT_VIEW] =
+    g_param_spec_object ("next-view", NULL, NULL,
+                         ADW_TYPE_BROWSING_VIEW,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  /**
+   * AdwBrowsingView::pushed:
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  signals[SIGNAL_PUSHED] =
+    g_signal_new ("pushed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
+  /**
+   * AdwBrowsingView::popped:
+   * @child: TODO
+   *
+   * TODO
+   *
+   * Since: 1.3
+   */
+  signals[SIGNAL_POPPED] =
+    g_signal_new ("popped",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  ADW_TYPE_BROWSING_VIEW_CHILD);
+
+  /**
+   * AdwBrowsingView::get-next-child:
+   *
+   * TODO
+   *
+   * Returns: (transfer none): TODO
+   *
+   * Since: 1.3
+   */
+  signals[SIGNAL_GET_NEXT_CHILD] =
+    g_signal_new ("get-next-child",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  object_handled_accumulator,
+                  NULL, NULL,
+                  ADW_TYPE_BROWSING_VIEW_CHILD,
+                  0);
+
+  gtk_widget_class_install_action (widget_class, "browsing.push", "s",
+                                   (GtkWidgetActionActivateFunc) browsing_push_cb);
+  gtk_widget_class_install_action (widget_class, "browsing.pop", NULL,
+                                   (GtkWidgetActionActivateFunc) browsing_pop_cb);
+
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_Back, 0,
+                                (GtkShortcutFunc) back_forward_shortcut_cb, "b", TRUE);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_Forward, 0,
+                                (GtkShortcutFunc) back_forward_shortcut_cb, "b", FALSE);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, GDK_ALT_MASK,
+                                (GtkShortcutFunc) back_forward_shortcut_cb, "b", TRUE);
+  gtk_widget_class_add_binding (widget_class,  GDK_KEY_Right, GDK_ALT_MASK,
+                                (GtkShortcutFunc) back_forward_shortcut_cb, "b", FALSE);
+
+  gtk_widget_class_set_css_name (widget_class, "browsingview");
+}
+
+static void
+adw_browsing_view_init (AdwBrowsingView *self)
+{
+  AdwAnimationTarget *target;
+  GtkGesture *gesture;
+
+  self->child_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+  target = adw_callback_animation_target_new ((AdwAnimationTargetFunc) transition_cb, self, NULL);
+  self->transition = adw_spring_animation_new (GTK_WIDGET (self), 0, 1,
+                                               adw_spring_params_new (1, 0.5, 500), target);
+  adw_spring_animation_set_clamp (ADW_SPRING_ANIMATION (self->transition), TRUE);
+  g_signal_connect_swapped (self->transition, "done", G_CALLBACK (transition_done_cb), self);
+
+  self->shadow_helper = adw_shadow_helper_new (GTK_WIDGET (self));
+
+  gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
+
+  gesture = gtk_gesture_click_new ();
+  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
+  g_signal_connect_object (gesture, "pressed", G_CALLBACK (back_forward_button_pressed_cb), self, 0);
+  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (gesture));
+}
+
+static void
+adw_browsing_view_buildable_add_child (GtkBuildable *buildable,
+                                       GtkBuilder   *builder,
+                                       GObject      *child,
+                                       const char   *type)
+{
+  if (ADW_IS_BROWSING_VIEW_CHILD (child))
+    adw_browsing_view_add (ADW_BROWSING_VIEW (buildable),
+                           ADW_BROWSING_VIEW_CHILD (child));
+  else if (GTK_IS_WIDGET (child))
+    g_critical ("123\n");
+    // TODO critical
+  else
+    parent_buildable_iface->add_child (buildable, builder, child, type);
+}
+
+static void
+adw_browsing_view_buildable_init (GtkBuildableIface *iface)
+{
+  parent_buildable_iface = g_type_interface_peek_parent (iface);
+
+  iface->add_child = adw_browsing_view_buildable_add_child;
+}
+
+/**
+ * adw_browsing_view_child_new:
+ * @child: TODO
+ * @title: TODO
+ *
+ * Creates a new `AdwBrowsingViewChild`.
+ *
+ * Returns: the new created `AdwBrowsingViewChild`
+ *
+ * Since: 1.3
+ */
+GtkWidget *
+adw_browsing_view_child_new (GtkWidget  *child,
+                             const char *title)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+  g_return_val_if_fail (title != NULL, NULL);
+
+  return g_object_new (ADW_TYPE_BROWSING_VIEW_CHILD,
+                       "child", child,
+                       "title", title);
+}
+
+/**
+ * adw_browsing_view_child_get_child: (attributes org.gtk.Method.get_property=child)
+ * @self: a browsing view child
+ *
+ * Gets the child widget of @self.
+ *
+ * Returns: (nullable) (transfer none): the child widget of @self
+ *
+ * Since: 1.3
+ */
+GtkWidget *
+adw_browsing_view_child_get_child (AdwBrowsingViewChild *self)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self), NULL);
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  return priv->child;
+}
+
+/**
+ * adw_browsing_view_child_set_child: (attributes org.gtk.Method.set_property=child)
+ * @self: a browsing view child
+ * @child: (nullable): the child widget
+ *
+ * Sets the child widget of @self.
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_child_set_child (AdwBrowsingViewChild *self,
+                                   GtkWidget            *child)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self));
+  g_return_if_fail (child == NULL || GTK_IS_WIDGET (child));
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  if (priv->child == child)
+    return;
+
+  if (priv->child)
+    gtk_widget_unparent (priv->child);
+
+  priv->child = child;
+
+  if (priv->child)
+    gtk_widget_set_parent (priv->child, GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), child_props[CHILD_PROP_CHILD]);
+}
+
+/**
+ * adw_browsing_view_child_get_title: (attributes org.gtk.Method.get_property=title)
+ * @self: a browsing view child
+ *
+ * Gets the title of @self.
+ *
+ * Returns: (transfer none): the title of @self
+ *
+ * Since: 1.3
+ */
+const char *
+adw_browsing_view_child_get_title (AdwBrowsingViewChild *self)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self), NULL);
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  return priv->title;
+}
+
+/**
+ * adw_browsing_view_child_set_title: (attributes org.gtk.Method.set_property=title)
+ * @self: a browsing view child
+ * @title: the title
+ *
+ * Sets the title of @self.
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_child_set_title (AdwBrowsingViewChild *self,
+                                   const char           *title)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self));
+  g_return_if_fail (title != NULL);
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  if (!g_strcmp0 (priv->title, title))
+    return;
+
+  g_free (priv->title);
+  priv->title = g_strdup (title);
+
+  g_object_notify_by_pspec (G_OBJECT (self), child_props[CHILD_PROP_TITLE]);
+}
+
+/**
+ * adw_browsing_view_child_get_child_name: (attributes org.gtk.Method.get_property=child-name)
+ * @self: a browsing view child
+ *
+ * TODO
+ *
+ * Returns: (transfer none): (nullable): the name of @self
+ *
+ * Since: 1.3
+ */
+const char *
+adw_browsing_view_child_get_child_name (AdwBrowsingViewChild *self)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self), NULL);
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  return priv->name;
+}
+
+/**
+ * adw_browsing_view_child_set_child_name: (attributes org.gtk.Method.set_property=child-name)
+ * @self: a browsing view child
+ * @name: (nullable): the name
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_child_set_child_name (AdwBrowsingViewChild *self,
+                                        const char           *name)
+{
+  AdwBrowsingViewChildPrivate *priv;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW_CHILD (self));
+
+  priv = adw_browsing_view_child_get_instance_private (self);
+
+  if (!g_strcmp0 (priv->name, name))
+    return;
+
+  // TODO: check for duplicate names
+
+  g_free (priv->name);
+  priv->name = g_strdup (name);
+
+  g_object_notify_by_pspec (G_OBJECT (self), child_props[CHILD_PROP_CHILD_NAME]);
+}
+
+/**
+ * adw_browsing_view_new:
+ *
+ * Creates a new `AdwBrowsingView`.
+ *
+ * Returns: the new created `AdwBrowsingView`
+ *
+ * Since: 1.3
+ */
+GtkWidget *
+adw_browsing_view_new (void)
+{
+  return g_object_new (ADW_TYPE_BROWSING_VIEW, NULL);
+}
+
+/**
+ * adw_browsing_view_add:
+ * @self: a browsing view
+ * @child: TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_add (AdwBrowsingView      *self,
+                       AdwBrowsingViewChild *child)
+{
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+  g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) == NULL);
+
+  // TODO: check for duplicate names
+
+  gtk_widget_set_parent (GTK_WIDGET (child), GTK_WIDGET (self));
+  g_hash_table_insert (self->child_mapping, child, child);
+
+  if (self->navigation_stack)
+    gtk_widget_set_child_visible (GTK_WIDGET (child), FALSE);
+  else
+    push_to_stack (self, child, FALSE, TRUE);
+}
+
+/**
+ * adw_browsing_view_add_with_title:
+ * @self: a browsing view
+ * @child: TODO
+ * @title: TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_add_with_title (AdwBrowsingView *self,
+                                  GtkWidget       *child,
+                                  const char      *title)
+{
+  GtkWidget *wrapper;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+  g_return_if_fail (gtk_widget_get_parent (GTK_WIDGET (child)) == NULL);
+  g_return_if_fail (title != NULL);
+
+  wrapper = adw_browsing_view_child_new (child, title);
+
+  gtk_widget_set_parent (GTK_WIDGET (wrapper), GTK_WIDGET (self));
+  g_hash_table_insert (self->child_mapping, child, wrapper);
+
+  if (self->navigation_stack)
+    gtk_widget_set_child_visible (wrapper, FALSE);
+  else
+    push_to_stack (self, ADW_BROWSING_VIEW_CHILD (wrapper), FALSE, TRUE);
+}
+
+/**
+ * adw_browsing_view_add_with_title:
+ * @self: a browsing view
+ * @child: TODO
+ * @title: TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_remove (AdwBrowsingView *self,
+                          GtkWidget       *child)
+{
+  GtkWidget *wrapper;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  if (ADW_IS_BROWSING_VIEW_CHILD (child) &&
+      gtk_widget_get_parent (child) == GTK_WIDGET (self))
+    wrapper = child;
+  else
+    wrapper = g_hash_table_lookup (self->child_mapping, child);
+
+  if (wrapper == NULL) {
+    g_critical ("Tried to remove non-child %s %p from AdwBrowsingView %p",
+                G_OBJECT_TYPE_NAME (child), child, self);
+    return;
+  }
+
+  if (self->hiding_child && child == GTK_WIDGET (self->hiding_child))
+    adw_animation_skip (self->transition);
+
+  // TODO: what is self->last_child is removed?
+
+  if (adw_browsing_view_get_visible_child (self) == ADW_BROWSING_VIEW_CHILD (wrapper))
+    adw_browsing_view_pop (self, FALSE);
+  else if (g_slist_find (self->navigation_stack, wrapper))
+    self->navigation_stack = g_slist_remove (self->navigation_stack, wrapper);
+
+  g_hash_table_remove (self->child_mapping, child);
+
+  gtk_widget_unparent (GTK_WIDGET (wrapper));
+}
+
+static gboolean
+find_child_func (GtkWidget            *child,
+                 AdwBrowsingViewChild *wrapper,
+                 const char           *name)
+{
+  return !g_strcmp0 (adw_browsing_view_child_get_child_name (wrapper), name);
+}
+
+/**
+ * adw_browsing_view_find_child:
+ * @self: a browsing view
+ * @id: TODO
+ *
+ * TODO
+ *
+ * Returns: (nullable): TODO
+ *
+ * Since: 1.3
+ */
+AdwBrowsingViewChild *
+adw_browsing_view_find_child (AdwBrowsingView *self,
+                              const char      *name)
+{
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), NULL);
+  g_return_val_if_fail (name != NULL, NULL);
+
+  return g_hash_table_find (self->child_mapping,
+                            (GHRFunc) find_child_func,
+                            (char *) name);
+}
+
+/**
+ * adw_browsing_view_push:
+ * @self: a browsing view
+ * @child: TODO
+ * @animate: TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_push (AdwBrowsingView *self,
+                        GtkWidget       *child,
+                        gboolean         animate)
+{
+  AdwBrowsingViewChild *wrapper;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (GTK_IS_WIDGET (child));
+
+  if (ADW_IS_BROWSING_VIEW_CHILD (child) &&
+      gtk_widget_get_parent (child) == GTK_WIDGET (self))
+    wrapper = ADW_BROWSING_VIEW_CHILD (child);
+  else
+    wrapper = g_hash_table_lookup (self->child_mapping, child);
+
+  animate = !!animate;
+
+  if (wrapper == NULL) {
+    g_critical ("Tried to push non-child %s %p in AdwBrowsingView %p",
+                G_OBJECT_TYPE_NAME (child), child, self);
+    return;
+  }
+
+  push_to_stack (self, wrapper, animate, TRUE);
+}
+
+/**
+ * adw_browsing_view_push_by_name:
+ * @self: a browsing view
+ * @name: TODO
+ * @animate: TODO
+ *
+ * TODO
+ *
+ * Since: 1.3
+ */
+void
+adw_browsing_view_push_by_name (AdwBrowsingView *self,
+                                const char      *name,
+                                gboolean         animate)
+{
+  AdwBrowsingViewChild *wrapper;
+
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (name != NULL);
+
+  animate = !!animate;
+  wrapper = adw_browsing_view_find_child (self, name);
+
+  if (wrapper == NULL) {
+    g_critical ("No child found with the name '%s' in AdwBrowsingView %p",
+                name, self);
+    return;
+  }
+
+  push_to_stack (self, wrapper, animate, TRUE);
+}
+
+/**
+ * adw_browsing_view_pop:
+ * @self: a browsing view
+ * @animate: TODO
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+gboolean
+adw_browsing_view_pop (AdwBrowsingView *self,
+                       gboolean         animate)
+{
+  AdwBrowsingViewChild *child;
+  AdwBrowsingViewChild *prev_child;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), FALSE);
+
+  animate = !!animate;
+
+  child = adw_browsing_view_get_visible_child (self);
+
+  prev_child = adw_browsing_view_get_previous_child (self, child);
+
+  if (!prev_child)
+    return FALSE;
+
+  pop_from_stack (self, prev_child, animate);
+
+  return TRUE;
+}
+
+/**
+ * adw_browsing_view_pop_to_child:
+ * @self: a browsing view
+ * @child: TODO
+ * @animate: TODO
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+gboolean
+adw_browsing_view_pop_to_child (AdwBrowsingView *self,
+                                GtkWidget       *child,
+                                gboolean         animate)
+{
+  AdwBrowsingViewChild *visible_child;
+  AdwBrowsingViewChild *wrapper;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), FALSE);
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW_CHILD (child), FALSE);
+
+  animate = !!animate;
+
+  visible_child = adw_browsing_view_get_visible_child (self);
+
+  if (ADW_IS_BROWSING_VIEW_CHILD (child) &&
+      gtk_widget_get_parent (child) == GTK_WIDGET (self))
+    wrapper = ADW_BROWSING_VIEW_CHILD (child);
+  else
+    wrapper = g_hash_table_lookup (self->child_mapping, child);
+
+  if (wrapper == visible_child)
+    return FALSE;
+
+  if (!g_slist_find (self->navigation_stack, wrapper)) {
+    g_critical ("Child '%s' is not in the navigation stack\n",
+                adw_browsing_view_child_get_title (wrapper));
+    return FALSE;
+  }
+
+  pop_from_stack (self, wrapper, animate);
+
+  return TRUE;
+}
+
+/**
+ * adw_browsing_view_pop_to_name:
+ * @self: a browsing view
+ * @name: TODO
+ * @animate: TODO
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+gboolean
+adw_browsing_view_pop_to_name (AdwBrowsingView *self,
+                               const char      *name,
+                               gboolean         animate)
+{
+  AdwBrowsingViewChild *child;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), FALSE);
+  g_return_val_if_fail (name != NULL, FALSE);
+
+  child = adw_browsing_view_find_child (self, name);
+
+  if (child == NULL) {
+    g_critical ("No child found with the name '%s' in AdwBrowsingView %p",
+                name, self);
+    return FALSE;
+  }
+
+  return adw_browsing_view_pop_to_child (self, GTK_WIDGET (child), animate);
+}
+
+/**
+ * adw_browsing_view_get_visible_child: (attributes org.gtk.Method.set_property=visible-child)
+ * @self: a browsing view
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+AdwBrowsingViewChild *
+adw_browsing_view_get_visible_child (AdwBrowsingView *self)
+{
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), NULL);
+
+  if (!self->navigation_stack)
+    return NULL;
+
+  return self->navigation_stack->data;
+}
+
+/**
+ * adw_browsing_view_get_previous_child:
+ * @self: a browsing view
+ * @child: TODO
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+AdwBrowsingViewChild *
+adw_browsing_view_get_previous_child (AdwBrowsingView      *self,
+                                      AdwBrowsingViewChild *child)
+{
+  GSList *l;
+
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), NULL);
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW_CHILD (child), NULL);
+
+  l = g_slist_find (self->navigation_stack, child);
+
+  /* The stack is reversed, so we get the next element instead */
+  if (l && l->next)
+    return l->next->data;
+
+  if (l && self->previous_view)
+    return adw_browsing_view_get_visible_child (self->previous_view);
+
+  return NULL;
+}
+
+/**
+ * adw_browsing_view_get_previous_view: (attributes org.gtk.Method.set_property=previous-view)
+ * @self: a browsing view
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+AdwBrowsingView *
+adw_browsing_view_get_previous_view (AdwBrowsingView *self)
+{
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), NULL);
+
+  return self->previous_view;
+}
+
+/**
+ * adw_browsing_view_get_next_view: (attributes org.gtk.Method.set_property=next-view)
+ * @self: a browsing view
+ *
+ * TODO
+ *
+ * Returns: TODO
+ *
+ * Since: 1.3
+ */
+AdwBrowsingView *
+adw_browsing_view_get_next_view (AdwBrowsingView *self)
+{
+  g_return_val_if_fail (ADW_IS_BROWSING_VIEW (self), NULL);
+
+  return self->next_view;
+}
+
+static void
+set_next_view (AdwBrowsingView *self,
+               AdwBrowsingView *next_view)
+{
+  AdwBrowsingView *old_next = self->next_view;
+  AdwBrowsingView *old_next_prev = next_view ? next_view->previous_view : NULL;
+  gboolean pushed = FALSE;
+
+  // TODO: weak refs
+
+  if (old_next_prev)
+    old_next_prev->next_view = NULL;
+
+  if (old_next) {
+    old_next->previous_view = NULL;
+
+    if (!adw_browsing_view_get_visible_child (old_next) && old_next->last_child) {
+      g_object_freeze_notify (G_OBJECT (old_next));
+      pushed = TRUE;
+      push_to_stack (old_next, old_next->last_child, FALSE, FALSE);
+      old_next->last_child = NULL;
+    }
+  }
+
+  self->next_view = next_view;
+
+  if (next_view)
+    next_view->previous_view = self;
+
+  if (old_next_prev)
+    g_object_notify_by_pspec (G_OBJECT (old_next_prev), props[PROP_NEXT_VIEW]);
+  if (old_next)
+    g_object_notify_by_pspec (G_OBJECT (old_next), props[PROP_PREVIOUS_VIEW]);
+  if (next_view)
+    g_object_notify_by_pspec (G_OBJECT (next_view), props[PROP_PREVIOUS_VIEW]);
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEXT_VIEW]);
+
+  if (pushed) {
+    g_object_thaw_notify (G_OBJECT (old_next));
+    g_signal_emit (self, signals[SIGNAL_PUSHED], 0);
+  }
+}
+
+void
+adw_browsing_view_connect (AdwBrowsingView *self,
+                           AdwBrowsingView *next_view)
+{
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (next_view));
+  g_return_if_fail (self != next_view);
+
+  if (self->next_view == next_view)
+    return;
+
+  set_next_view (self, next_view);
+}
+
+void
+adw_browsing_view_disconnect (AdwBrowsingView *self,
+                              AdwBrowsingView *other_view)
+{
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (self));
+  g_return_if_fail (ADW_IS_BROWSING_VIEW (other_view));
+  g_return_if_fail (self != other_view);
+
+  if (self->next_view == other_view)
+    set_next_view (self, NULL);
+  else if (other_view->next_view == self)
+    set_next_view (other_view, NULL);
+  else
+    g_critical ("Can't disconnect AdwBrowsingView %p and %p as they haven't connected'", self, other_view);
+}
diff --git a/src/adw-browsing-view.h b/src/adw-browsing-view.h
new file mode 100644
index 00000000..5e35af13
--- /dev/null
+++ b/src/adw-browsing-view.h
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#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_BROWSING_VIEW_CHILD (adw_browsing_view_child_get_type())
+
+ADW_AVAILABLE_IN_1_3
+G_DECLARE_DERIVABLE_TYPE (AdwBrowsingViewChild, adw_browsing_view_child, ADW, BROWSING_VIEW_CHILD, GtkWidget)
+
+struct _AdwBrowsingViewChildClass
+{
+  GtkWidgetClass parent_class;
+
+  void (* showing) (AdwBrowsingViewChild *self);
+  void (* shown)   (AdwBrowsingViewChild *self);
+  void (* hiding)  (AdwBrowsingViewChild *self);
+  void (* hidden)  (AdwBrowsingViewChild *self);
+
+  /*< private >*/
+  gpointer padding[8];
+};
+
+ADW_AVAILABLE_IN_1_3
+GtkWidget *adw_browsing_view_child_new (GtkWidget  *child,
+                                        const char *title) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+GtkWidget *adw_browsing_view_child_get_child (AdwBrowsingViewChild *self);
+ADW_AVAILABLE_IN_1_3
+void       adw_browsing_view_child_set_child (AdwBrowsingViewChild *self,
+                                              GtkWidget            *child);
+
+ADW_AVAILABLE_IN_1_3
+const char *adw_browsing_view_child_get_title (AdwBrowsingViewChild *self);
+ADW_AVAILABLE_IN_1_3
+void        adw_browsing_view_child_set_title (AdwBrowsingViewChild *self,
+                                               const char           *title);
+
+ADW_AVAILABLE_IN_1_3
+const char *adw_browsing_view_child_get_child_name (AdwBrowsingViewChild *self);
+ADW_AVAILABLE_IN_1_3
+void        adw_browsing_view_child_set_child_name (AdwBrowsingViewChild *self,
+                                                    const char           *name);
+
+#define ADW_TYPE_BROWSING_VIEW (adw_browsing_view_get_type())
+
+ADW_AVAILABLE_IN_1_3
+G_DECLARE_FINAL_TYPE (AdwBrowsingView, adw_browsing_view, ADW, BROWSING_VIEW, GtkWidget)
+
+ADW_AVAILABLE_IN_1_3
+GtkWidget *adw_browsing_view_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+void adw_browsing_view_add (AdwBrowsingView      *self,
+                            AdwBrowsingViewChild *child);
+
+ADW_AVAILABLE_IN_1_3
+void adw_browsing_view_add_with_title (AdwBrowsingView *self,
+                                       GtkWidget       *child,
+                                       const char      *title);
+
+ADW_AVAILABLE_IN_1_3
+void adw_browsing_view_remove (AdwBrowsingView *self,
+                               GtkWidget       *child);
+
+ADW_AVAILABLE_IN_1_3
+AdwBrowsingViewChild *adw_browsing_view_find_child (AdwBrowsingView *self,
+                                                    const char      *name);
+
+ADW_AVAILABLE_IN_1_3
+void adw_browsing_view_push (AdwBrowsingView *self,
+                             GtkWidget       *child,
+                             gboolean         animate);
+
+ADW_AVAILABLE_IN_1_3
+void adw_browsing_view_push_by_name (AdwBrowsingView *self,
+                                     const char      *name,
+                                     gboolean         animate);
+
+ADW_AVAILABLE_IN_1_3
+gboolean adw_browsing_view_pop (AdwBrowsingView *self,
+                                gboolean         animate);
+
+ADW_AVAILABLE_IN_1_3
+gboolean adw_browsing_view_pop_to_child (AdwBrowsingView *self,
+                                         GtkWidget       *child,
+                                         gboolean         animate);
+
+ADW_AVAILABLE_IN_1_3
+gboolean adw_browsing_view_pop_to_name (AdwBrowsingView *self,
+                                        const char      *name,
+                                        gboolean         animate);
+
+ADW_AVAILABLE_IN_1_3
+AdwBrowsingViewChild *adw_browsing_view_get_visible_child (AdwBrowsingView *self);
+
+ADW_AVAILABLE_IN_1_3
+AdwBrowsingViewChild *adw_browsing_view_get_previous_child (AdwBrowsingView      *self,
+                                                            AdwBrowsingViewChild *child);
+
+ADW_AVAILABLE_IN_1_3
+AdwBrowsingView *adw_browsing_view_get_previous_view (AdwBrowsingView *self);
+ADW_AVAILABLE_IN_1_3
+AdwBrowsingView *adw_browsing_view_get_next_view (AdwBrowsingView *self);
+
+ADW_AVAILABLE_IN_1_3
+void             adw_browsing_view_connect (AdwBrowsingView *self,
+                                            AdwBrowsingView *next_view);
+
+ADW_AVAILABLE_IN_1_3
+void             adw_browsing_view_disconnect (AdwBrowsingView *self,
+                                               AdwBrowsingView *other_view);
+
+G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index 077b6521..574218e3 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -31,6 +31,7 @@ G_BEGIN_DECLS
 #include "adw-application-window.h"
 #include "adw-avatar.h"
 #include "adw-bin.h"
+#include "adw-browsing-view.h"
 #include "adw-button-content.h"
 #include "adw-carousel.h"
 #include "adw-carousel-indicator-dots.h"
diff --git a/src/meson.build b/src/meson.build
index ade587ef..7e2f79dc 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -93,6 +93,7 @@ src_headers = [
   'adw-application-window.h',
   'adw-avatar.h',
   'adw-bin.h',
+  'adw-browsing-view.h',
   'adw-button-content.h',
   'adw-carousel.h',
   'adw-carousel-indicator-dots.h',
@@ -161,6 +162,7 @@ src_sources = [
   'adw-application-window.c',
   'adw-avatar.c',
   'adw-bin.c',
+  'adw-browsing-view.c',
   'adw-button-content.c',
   'adw-carousel.c',
   'adw-carousel-indicator-dots.c',
diff --git a/src/stylesheet/widgets/_transition-shadow.scss b/src/stylesheet/widgets/_transition-shadow.scss
index 1cd13d17..4576b91b 100644
--- a/src/stylesheet/widgets/_transition-shadow.scss
+++ b/src/stylesheet/widgets/_transition-shadow.scss
@@ -11,7 +11,8 @@
 }
 
 flap,
-leaflet {
+leaflet,
+browsingview {
   > dimming {
     background: $shade_color;
   }


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