[libadwaita/wip/exalm/browsing-view: 13/18] AdwBrowsingView
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/browsing-view: 13/18] AdwBrowsingView
- Date: Fri, 14 Oct 2022 20:14:35 +0000 (UTC)
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]