/*
 * Copyright (c) 2013 Red Hat, Inc.
 * Copyright (C) 2019 Purism SPC
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"
#include <glib/gi18n-lib.h>

#include "hdy-header-bar.h"

#include "hdy-animation-private.h"
#include "hdy-cairo-private.h"
#include "hdy-css-private.h"
#include "hdy-enums.h"
#include "hdy-window-handle-controller-private.h"
#include "gtkprogresstrackerprivate.h"
#include "gtk-window-private.h"

/**
 * HdyHeaderBar:
 *
 * A title bar widget.
 *
 * `HdyHeaderBar` is similar to [class@Gtk.HeaderBar] but is designed to fix
 * some of its shortcomings for adaptive applications.
 *
 * `HdyHeaderBar` doesn't force the custom title widget to be vertically
 * centered, hence allowing it to fill up the whole height, which is e.g. needed
 * for [class@ViewSwitcher].
 *
 * When used in a mobile dialog, `HdyHeaderBar` will replace its window
 * decorations by a back button allowing to close it. It doesn't have to be its
 * direct child and you can use any complex contraption you like as the dialog's
 * titlebar.
 *
 * `HdyHeaderBar` can be used in window's content area rather than titlebar, and
 * will still be draggable and will handle right click, middle click and double
 * click as expected from a titlebar. This is particularly useful with
 * [class@Window] or [class@ApplicationWindow].
 *
 * ## CSS nodes
 *
 * `HdyHeaderBar` has a single CSS node with name `headerbar`.
 *
 * Since: 1.0
 */

/**
 * HdyCenteringPolicy:
 * @HDY_CENTERING_POLICY_LOOSE: Keep the title centered when possible
 * @HDY_CENTERING_POLICY_STRICT: Keep the title centered at all cost
 *
 * Describes title centering behavior of a [class@HeaderBar] widget.
 *
 * Since: 1.0
 */

#define DEFAULT_SPACING 6
#define MIN_TITLE_CHARS 5

#define MOBILE_WINDOW_WIDTH  480
#define MOBILE_WINDOW_HEIGHT 800

typedef struct {
  gchar *title;
  gchar *subtitle;
  GtkWidget *title_label;
  GtkWidget *subtitle_label;
  GtkWidget *label_box;
  GtkWidget *label_sizing_box;
  GtkWidget *subtitle_sizing_label;
  GtkWidget *custom_title;
  gint spacing;
  gboolean has_subtitle;

  GList *children;

  gboolean shows_wm_decorations;
  gchar *decoration_layout;
  gboolean decoration_layout_set;

  GtkWidget *titlebar_start_box;
  GtkWidget *titlebar_end_box;

  GtkWidget *titlebar_start_separator;
  GtkWidget *titlebar_end_separator;

  GtkWidget *titlebar_icon;

  guint tick_id;
  GtkProgressTracker tracker;
  gboolean first_frame_skipped;

  HdyCenteringPolicy centering_policy;
  guint transition_duration;
  gboolean interpolate_size;

  gboolean is_mobile_window;

  gulong window_size_allocated_id;

  HdyWindowHandleController *controller;
} HdyHeaderBarPrivate;

typedef struct _Child Child;
struct _Child
{
  GtkWidget *widget;
  GtkPackType pack_type;
};

enum {
  PROP_0,
  PROP_TITLE,
  PROP_SUBTITLE,
  PROP_HAS_SUBTITLE,
  PROP_CUSTOM_TITLE,
  PROP_SPACING,
  PROP_SHOW_CLOSE_BUTTON,
  PROP_DECORATION_LAYOUT,
  PROP_DECORATION_LAYOUT_SET,
  PROP_CENTERING_POLICY,
  PROP_TRANSITION_DURATION,
  PROP_TRANSITION_RUNNING,
  PROP_INTERPOLATE_SIZE,
  LAST_PROP
};

enum {
  CHILD_PROP_0,
  CHILD_PROP_PACK_TYPE,
  CHILD_PROP_POSITION
};

static GParamSpec *props[LAST_PROP] = { NULL, };

static void hdy_header_bar_buildable_init (GtkBuildableIface *iface);

G_DEFINE_TYPE_WITH_CODE (HdyHeaderBar, hdy_header_bar, GTK_TYPE_CONTAINER,
                         G_ADD_PRIVATE (HdyHeaderBar)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                hdy_header_bar_buildable_init));

static gboolean
hdy_header_bar_transition_cb (GtkWidget     *widget,
                              GdkFrameClock *frame_clock,
                              gpointer       user_data)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  if (priv->first_frame_skipped)
    gtk_progress_tracker_advance_frame (&priv->tracker,
                                        gdk_frame_clock_get_frame_time (frame_clock));
  else
    priv->first_frame_skipped = TRUE;

  /* Finish the animation early if the widget isn't mapped anymore. */
  if (!gtk_widget_get_mapped (widget))
    gtk_progress_tracker_finish (&priv->tracker);

  gtk_widget_queue_resize (widget);

  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) {
    priv->tick_id = 0;
    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);

    return FALSE;
  }

  return TRUE;
}

static void
hdy_header_bar_schedule_ticks (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  if (priv->tick_id == 0) {
    priv->tick_id =
      gtk_widget_add_tick_callback (GTK_WIDGET (self), hdy_header_bar_transition_cb, self, NULL);
    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
  }
}

static void
hdy_header_bar_unschedule_ticks (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  if (priv->tick_id != 0) {
    gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->tick_id);
    priv->tick_id = 0;
    g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_RUNNING]);
  }
}

static void
hdy_header_bar_start_transition (HdyHeaderBar *self,
                                 guint         transition_duration)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkWidget *widget = GTK_WIDGET (self);

  if (gtk_widget_get_mapped (widget) &&
      priv->interpolate_size &&
      transition_duration != 0) {
    priv->first_frame_skipped = FALSE;
    hdy_header_bar_schedule_ticks (self);
    gtk_progress_tracker_start (&priv->tracker,
                                priv->transition_duration * 1000,
                                0,
                                1.0);
  } else {
    hdy_header_bar_unschedule_ticks (self);
    gtk_progress_tracker_finish (&priv->tracker);
  }

  gtk_widget_queue_resize (widget);
}

static void
init_sizing_box (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkWidget *w;
  GtkStyleContext *context;

  /* We use this box to always request size for the two labels (title
   * and subtitle) as if they were always visible, but then allocate
   * the real label box with its actual size, to keep it center-aligned
   * in case we have only the title.
   */
  w = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_widget_show (w);
  priv->label_sizing_box = g_object_ref_sink (w);

  w = gtk_label_new (NULL);
  gtk_widget_show (w);
  context = gtk_widget_get_style_context (w);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE);
  gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
  gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END);
  gtk_label_set_width_chars (GTK_LABEL (w), MIN_TITLE_CHARS);

  w = gtk_label_new (NULL);
  context = gtk_widget_get_style_context (w);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE);
  gtk_box_pack_start (GTK_BOX (priv->label_sizing_box), w, FALSE, FALSE, 0);
  gtk_label_set_line_wrap (GTK_LABEL (w), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END);
  gtk_widget_set_visible (w, priv->has_subtitle || (priv->subtitle && priv->subtitle[0]));
  priv->subtitle_sizing_label = w;
}

static GtkWidget *
create_title_box (const char *title,
                  const char *subtitle,
                  GtkWidget **ret_title_label,
                  GtkWidget **ret_subtitle_label)
{
  GtkWidget *label_box;
  GtkWidget *title_label;
  GtkWidget *subtitle_label;
  GtkStyleContext *context;

  label_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  gtk_widget_set_valign (label_box, GTK_ALIGN_CENTER);
  gtk_widget_show (label_box);

  title_label = gtk_label_new (title);
  context = gtk_widget_get_style_context (title_label);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_TITLE);
  gtk_label_set_line_wrap (GTK_LABEL (title_label), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (title_label), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (title_label), PANGO_ELLIPSIZE_END);
  gtk_box_pack_start (GTK_BOX (label_box), title_label, FALSE, FALSE, 0);
  gtk_widget_show (title_label);
  gtk_label_set_width_chars (GTK_LABEL (title_label), MIN_TITLE_CHARS);

  subtitle_label = gtk_label_new (subtitle);
  context = gtk_widget_get_style_context (subtitle_label);
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_SUBTITLE);
  gtk_label_set_line_wrap (GTK_LABEL (subtitle_label), FALSE);
  gtk_label_set_single_line_mode (GTK_LABEL (subtitle_label), TRUE);
  gtk_label_set_ellipsize (GTK_LABEL (subtitle_label), PANGO_ELLIPSIZE_END);
  gtk_box_pack_start (GTK_BOX (label_box), subtitle_label, FALSE, FALSE, 0);
  gtk_widget_set_no_show_all (subtitle_label, TRUE);
  gtk_widget_set_visible (subtitle_label, subtitle && subtitle[0]);

  if (ret_title_label)
    *ret_title_label = title_label;
  if (ret_subtitle_label)
    *ret_subtitle_label = subtitle_label;

  return label_box;
}

static gboolean
hdy_header_bar_update_window_icon (HdyHeaderBar *self,
                                   GtkWindow    *window)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GdkPixbuf *pixbuf;
  gint scale;

  if (priv->titlebar_icon == NULL)
    return FALSE;

  scale = gtk_widget_get_scale_factor (priv->titlebar_icon);
  if (GTK_IS_BUTTON (gtk_widget_get_parent (priv->titlebar_icon)))
    pixbuf = hdy_gtk_window_get_icon_for_size (window, scale * 16);
  else
    pixbuf = hdy_gtk_window_get_icon_for_size (window, scale * 20);

  if (pixbuf) {
    g_autoptr (cairo_surface_t) surface =
      gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, gtk_widget_get_window (priv->titlebar_icon));

    gtk_image_set_from_surface (GTK_IMAGE (priv->titlebar_icon), surface);
    g_object_unref (pixbuf);
    gtk_widget_show (priv->titlebar_icon);

    return TRUE;
  }

  return FALSE;
}

static void
_hdy_header_bar_update_separator_visibility (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  gboolean have_visible_at_start = FALSE;
  gboolean have_visible_at_end = FALSE;
  GList *l;

  for (l = priv->children; l != NULL; l = l->next) {
    Child *child = l->data;

    if (gtk_widget_get_visible (child->widget)) {
      if (child->pack_type == GTK_PACK_START)
        have_visible_at_start = TRUE;
      else
        have_visible_at_end = TRUE;
    }
  }

  if (priv->titlebar_start_separator != NULL)
    gtk_widget_set_visible (priv->titlebar_start_separator, have_visible_at_start);

  if (priv->titlebar_end_separator != NULL)
    gtk_widget_set_visible (priv->titlebar_end_separator, have_visible_at_end);
}

static void
hdy_header_bar_update_window_buttons (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkWidget *widget = GTK_WIDGET (self), *toplevel;
  GtkWindow *window;
  GtkTextDirection direction;
  gchar *layout_desc;
  gchar **tokens, **t;
  gint i, j;
  GMenuModel *menu;
  gboolean shown_by_shell;
  gboolean is_sovereign_window;
  gboolean is_mobile_dialog;

  toplevel = gtk_widget_get_toplevel (widget);
  if (!gtk_widget_is_toplevel (toplevel))
    return;

  if (priv->titlebar_start_box) {
    gtk_widget_unparent (priv->titlebar_start_box);
    priv->titlebar_start_box = NULL;
    priv->titlebar_start_separator = NULL;
  }
  if (priv->titlebar_end_box) {
    gtk_widget_unparent (priv->titlebar_end_box);
    priv->titlebar_end_box = NULL;
    priv->titlebar_end_separator = NULL;
  }

  priv->titlebar_icon = NULL;

  if (!priv->shows_wm_decorations)
    return;

  direction = gtk_widget_get_direction (widget);

  g_object_get (gtk_widget_get_settings (widget),
                "gtk-shell-shows-app-menu", &shown_by_shell,
                "gtk-decoration-layout", &layout_desc,
                NULL);

  if (priv->decoration_layout_set) {
    g_free (layout_desc);
    layout_desc = g_strdup (priv->decoration_layout);
  }

  window = GTK_WINDOW (toplevel);

  if (!shown_by_shell && gtk_window_get_application (window))
    menu = gtk_application_get_app_menu (gtk_window_get_application (window));
  else
    menu = NULL;

  is_sovereign_window = (!gtk_window_get_modal (window) &&
                          gtk_window_get_transient_for (window) == NULL &&
                          gtk_window_get_type_hint (window) == GDK_WINDOW_TYPE_HINT_NORMAL);

  is_mobile_dialog= (priv->is_mobile_window && !is_sovereign_window);

  tokens = g_strsplit (layout_desc, ":", 2);
  if (tokens)   {
    for (i = 0; i < 2; i++) {
      GtkWidget *box;
      GtkWidget *separator;
      int n_children = 0;

      if (tokens[i] == NULL)
        break;

      t = g_strsplit (tokens[i], ",", -1);

      separator = gtk_separator_new (GTK_ORIENTATION_VERTICAL);
      gtk_widget_set_no_show_all (separator, TRUE);
      gtk_style_context_add_class (gtk_widget_get_style_context (separator), "titlebutton");

      box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, priv->spacing);

      for (j = 0; t[j]; j++) {
        GtkWidget *button = NULL;
        GtkWidget *image = NULL;
        AtkObject *accessible;

        if (strcmp (t[j], "icon") == 0 &&
            is_sovereign_window) {
            button = gtk_image_new ();
            gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
            priv->titlebar_icon = button;
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "icon");
            gtk_widget_set_size_request (button, 20, 20);
            gtk_widget_show (button);

            if (!hdy_header_bar_update_window_icon (self, window))
              {
                gtk_widget_destroy (button);
                priv->titlebar_icon = NULL;
                button = NULL;
              }
        } else if (strcmp (t[j], "menu") == 0 &&
                 menu != NULL &&
                 is_sovereign_window) {
            button = gtk_menu_button_new ();
            gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
            gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), menu);
            gtk_menu_button_set_use_popover (GTK_MENU_BUTTON (button), TRUE);
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "appmenu");
            image = gtk_image_new ();
            gtk_container_add (GTK_CONTAINER (button), image);
            gtk_widget_set_can_focus (button, FALSE);
            gtk_widget_show_all (button);

            accessible = gtk_widget_get_accessible (button);
            if (GTK_IS_ACCESSIBLE (accessible))
              atk_object_set_name (accessible, _("Application menu"));

            priv->titlebar_icon = image;
            if (!hdy_header_bar_update_window_icon (self, window))
              gtk_image_set_from_icon_name (GTK_IMAGE (priv->titlebar_icon),
                                            "application-x-executable-symbolic", GTK_ICON_SIZE_MENU);
        } else if (strcmp (t[j], "minimize") == 0 &&
                 is_sovereign_window) {
            button = gtk_button_new ();
            gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "minimize");
            image = gtk_image_new_from_icon_name ("window-minimize-symbolic", GTK_ICON_SIZE_MENU);
            g_object_set (image, "use-fallback", TRUE, NULL);
            gtk_container_add (GTK_CONTAINER (button), image);
            gtk_widget_set_can_focus (button, FALSE);
            gtk_widget_show_all (button);
            g_signal_connect_swapped (button, "clicked",
                                      G_CALLBACK (gtk_window_iconify), window);

            accessible = gtk_widget_get_accessible (button);
            if (GTK_IS_ACCESSIBLE (accessible))
              atk_object_set_name (accessible, _("Minimize"));
        } else if (strcmp (t[j], "maximize") == 0 &&
                 gtk_window_get_resizable (window) &&
                 is_sovereign_window) {
            const gchar *icon_name;
            gboolean maximized = gtk_window_is_maximized (window);

            icon_name = maximized ? "window-restore-symbolic" : "window-maximize-symbolic";
            button = gtk_button_new ();
            gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
            gtk_style_context_add_class (gtk_widget_get_style_context (button), "maximize");
            image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
            g_object_set (image, "use-fallback", TRUE, NULL);
            gtk_container_add (GTK_CONTAINER (button), image);
            gtk_widget_set_can_focus (button, FALSE);
            gtk_widget_show_all (button);
            g_signal_connect_swapped (button, "clicked",
                                      G_CALLBACK (hdy_gtk_window_toggle_maximized), window);

            accessible = gtk_widget_get_accessible (button);
            if (GTK_IS_ACCESSIBLE (accessible))
              atk_object_set_name (accessible, maximized ? _("Restore") : _("Maximize"));
        } else if (strcmp (t[j], "close") == 0 &&
                   gtk_window_get_deletable (window) &&
                   !is_mobile_dialog) {
          button = gtk_button_new ();
          gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
          image = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU);
          gtk_style_context_add_class (gtk_widget_get_style_context (button), "titlebutton");
          gtk_style_context_add_class (gtk_widget_get_style_context (button), "close");
          g_object_set (image, "use-fallback", TRUE, NULL);
          gtk_container_add (GTK_CONTAINER (button), image);
          gtk_widget_set_can_focus (button, FALSE);
          gtk_widget_show_all (button);
          g_signal_connect_swapped (button, "clicked",
                                    G_CALLBACK (gtk_window_close), window);

          accessible = gtk_widget_get_accessible (button);
          if (GTK_IS_ACCESSIBLE (accessible))
            atk_object_set_name (accessible, _("Close"));
        } else if (i == 0 && /* Only at the start. */
                   gtk_window_get_deletable (window) &&
                   is_mobile_dialog) {
          button = gtk_button_new ();
          gtk_widget_set_valign (button, GTK_ALIGN_CENTER);
          image = gtk_image_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_BUTTON);
          g_object_set (image, "use-fallback", TRUE, NULL);
          gtk_container_add (GTK_CONTAINER (button), image);
          gtk_widget_set_can_focus (button, TRUE);
          gtk_widget_show_all (button);
          g_signal_connect_swapped (button, "clicked",
                                    G_CALLBACK (gtk_window_close), window);

          accessible = gtk_widget_get_accessible (button);
          if (GTK_IS_ACCESSIBLE (accessible))
            atk_object_set_name (accessible, _("Back"));
        }

        if (button) {
          gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
          n_children ++;
        }
      }
      g_strfreev (t);

      if (n_children == 0) {
        g_object_ref_sink (box);
        g_object_unref (box);
        g_object_ref_sink (separator);
        g_object_unref (separator);
        continue;
      }

      gtk_box_pack_start (GTK_BOX (box), separator, FALSE, FALSE, 0);
      if (i == 1)
        gtk_box_reorder_child (GTK_BOX (box), separator, 0);

      if ((direction == GTK_TEXT_DIR_LTR && i == 0) ||
          (direction == GTK_TEXT_DIR_RTL && i == 1))
        gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_LEFT);
      else
        gtk_style_context_add_class (gtk_widget_get_style_context (box), GTK_STYLE_CLASS_RIGHT);

      gtk_widget_show (box);
      gtk_widget_set_parent (box, GTK_WIDGET (self));

      if (i == 0) {
        priv->titlebar_start_box = box;
        priv->titlebar_start_separator = separator;
      } else {
        priv->titlebar_end_box = box;
        priv->titlebar_end_separator = separator;
      }
    }
    g_strfreev (tokens);
  }
  g_free (layout_desc);

  _hdy_header_bar_update_separator_visibility (self);
}

static gboolean
compute_is_mobile_window (GtkWindow *window)
{
  gint window_width, window_height;

  gtk_window_get_size (window, &window_width, &window_height);

  if (window_width <= MOBILE_WINDOW_WIDTH &&
      gtk_window_is_maximized (window))
    return TRUE;

  /* Mobile landscape mode. */
  if (window_width <= MOBILE_WINDOW_HEIGHT &&
      window_height <= MOBILE_WINDOW_WIDTH &&
      gtk_window_is_maximized (window))
    return TRUE;

  return FALSE;
}

static void
update_is_mobile_window (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
  gboolean was_mobile_window = priv->is_mobile_window;

  if (!gtk_widget_is_toplevel (toplevel))
    return;

  priv->is_mobile_window = compute_is_mobile_window (GTK_WINDOW (toplevel));

  if (priv->is_mobile_window != was_mobile_window)
    hdy_header_bar_update_window_buttons (self);
}

static void
construct_label_box (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_assert (priv->label_box == NULL);

  priv->label_box = create_title_box (priv->title,
                                      priv->subtitle,
                                      &priv->title_label,
                                      &priv->subtitle_label);
  gtk_widget_set_parent (priv->label_box, GTK_WIDGET (self));
}

static gint
count_visible_children (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  Child *child;
  gint n;

  n = 0;
  for (l = priv->children; l; l = l->next) {
    child = l->data;
    if (gtk_widget_get_visible (child->widget))
      n++;
  }

  return n;
}

static gint
count_visible_children_for_pack_type (HdyHeaderBar *self, GtkPackType pack_type)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  Child *child;
  gint n;

  n = 0;
  for (l = priv->children; l; l = l->next) {
    child = l->data;
    if (gtk_widget_get_visible (child->widget) && child->pack_type == pack_type)
      n++;
  }

  return n;
}

static gboolean
add_child_size (GtkWidget      *child,
                GtkOrientation  orientation,
                gint           *minimum,
                gint           *natural)
{
  gint child_minimum, child_natural;

  if (!gtk_widget_get_visible (child))
    return FALSE;

  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    gtk_widget_get_preferred_width (child, &child_minimum, &child_natural);
  else
    gtk_widget_get_preferred_height (child, &child_minimum, &child_natural);

  if (GTK_ORIENTATION_HORIZONTAL == orientation) {
    *minimum += child_minimum;
    *natural += child_natural;
  } else {
    *minimum = MAX (*minimum, child_minimum);
    *natural = MAX (*natural, child_natural);
  }

  return TRUE;
}

static void
hdy_header_bar_get_size (GtkWidget      *widget,
                         GtkOrientation  orientation,
                         gint           *minimum,
                         gint           *natural)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  gint n_start_children = 0, n_end_children = 0;
  gint start_min = 0, start_nat = 0;
  gint end_min = 0, end_nat = 0;
  gint center_min = 0, center_nat = 0;

  for (l = priv->children; l; l = l->next) {
    Child *child = l->data;

    if (child->pack_type == GTK_PACK_START) {
      if (add_child_size (child->widget, orientation, &start_min, &start_nat))
        n_start_children += 1;
    } else {
      if (add_child_size (child->widget, orientation, &end_min, &end_nat))
        n_end_children += 1;
    }
  }

  if (priv->label_box != NULL) {
    if (orientation == GTK_ORIENTATION_HORIZONTAL)
      add_child_size (priv->label_box, orientation, &center_min, &center_nat);
    else
      add_child_size (priv->label_sizing_box, orientation, &center_min, &center_nat);
  }

  if (priv->custom_title != NULL)
    add_child_size (priv->custom_title, orientation, &center_min, &center_nat);

  if (priv->titlebar_start_box != NULL) {
    if (add_child_size (priv->titlebar_start_box, orientation, &start_min, &start_nat))
      n_start_children += 1;
  }

  if (priv->titlebar_end_box != NULL) {
    if (add_child_size (priv->titlebar_end_box, orientation, &end_min, &end_nat))
      n_end_children += 1;
  }

  if (orientation == GTK_ORIENTATION_HORIZONTAL) {
    gdouble strict_centering_t;
    gint start_min_spaced = start_min + n_start_children * priv->spacing;
    gint end_min_spaced = end_min + n_end_children * priv->spacing;
    gint start_nat_spaced = start_nat + n_start_children * priv->spacing;
    gint end_nat_spaced = end_nat + n_end_children * priv->spacing;

    if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER) {
      strict_centering_t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
      if (priv->centering_policy != HDY_CENTERING_POLICY_STRICT)
        strict_centering_t = 1.0 - strict_centering_t;
    } else
      strict_centering_t = priv->centering_policy == HDY_CENTERING_POLICY_STRICT ? 1.0 : 0.0;

    *minimum = center_min + n_start_children * priv->spacing +
               hdy_lerp (start_min_spaced + end_min_spaced,
                         2 * MAX (start_min_spaced, end_min_spaced),
                         strict_centering_t);
    *natural = center_nat + n_start_children * priv->spacing +
               hdy_lerp (start_nat_spaced + end_nat_spaced,
                         2 * MAX (start_nat_spaced, end_nat_spaced),
                         strict_centering_t);
  } else {
    *minimum = MAX (MAX (start_min, end_min), center_min);
    *natural = MAX (MAX (start_nat, end_nat), center_nat);
  }
}

static void
hdy_header_bar_compute_size_for_orientation (GtkWidget *widget,
                                             gint       avail_size,
                                             gint      *minimum_size,
                                             gint      *natural_size)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *children;
  gint required_size = 0;
  gint required_natural = 0;
  gint child_size;
  gint child_natural;
  gint nvis_children = 0;

  for (children = priv->children; children != NULL; children = children->next) {
    Child *child = children->data;

    if (gtk_widget_get_visible (child->widget)) {
      gtk_widget_get_preferred_width_for_height (child->widget,
                                                 avail_size, &child_size, &child_natural);

      required_size += child_size;
      required_natural += child_natural;

      nvis_children += 1;
    }
  }

  if (priv->label_box != NULL) {
    gtk_widget_get_preferred_width (priv->label_sizing_box,
                                    &child_size, &child_natural);
    required_size += child_size;
    required_natural += child_natural;
  }

  if (priv->custom_title != NULL &&
      gtk_widget_get_visible (priv->custom_title)) {
    gtk_widget_get_preferred_width (priv->custom_title,
                                    &child_size, &child_natural);
    required_size += child_size;
    required_natural += child_natural;
  }

  if (priv->titlebar_start_box != NULL) {
    gtk_widget_get_preferred_width (priv->titlebar_start_box,
                                    &child_size, &child_natural);
    required_size += child_size;
    required_natural += child_natural;
    nvis_children += 1;
  }

  if (priv->titlebar_end_box != NULL) {
    gtk_widget_get_preferred_width (priv->titlebar_end_box,
                                    &child_size, &child_natural);
    required_size += child_size;
    required_natural += child_natural;
    nvis_children += 1;
  }

  required_size += nvis_children * priv->spacing;
  required_natural += nvis_children * priv->spacing;

  *minimum_size = required_size;
  *natural_size = required_natural;
}

static void
hdy_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget,
                                                      gint       avail_size,
                                                      gint      *minimum_size,
                                                      gint      *natural_size)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  Child *child;
  GList *children;
  gint nvis_children;
  gint computed_minimum = 0;
  gint computed_natural = 0;
  GtkRequestedSize *sizes;
  GtkPackType packing;
  gint i;
  gint child_size;
  gint child_minimum;
  gint child_natural;
  gint center_min, center_nat;

  nvis_children = count_visible_children (self);

  if (nvis_children <= 0)
    return;

  sizes = g_newa (GtkRequestedSize, nvis_children);

  /* Retrieve desired size for visible children */
  for (i = 0, children = priv->children; children; children = children->next) {
    child = children->data;

    if (gtk_widget_get_visible (child->widget)) {
      gtk_widget_get_preferred_width (child->widget,
                                      &sizes[i].minimum_size,
                                      &sizes[i].natural_size);

      sizes[i].data = child;
      i += 1;
    }
  }

  /* Bring children up to size first */
  gtk_distribute_natural_allocation (MAX (0, avail_size), nvis_children, sizes);

  /* Allocate child positions. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing) {
    for (i = 0, children = priv->children; children; children = children->next) {
      child = children->data;

      /* If widget is not visible, skip it. */
      if (!gtk_widget_get_visible (child->widget))
        continue;

      /* If widget is packed differently skip it, but still increment i,
       * since widget is visible and will be handled in next loop
       * iteration.
       */
      if (child->pack_type != packing) {
        i++;
        continue;
      }

      child_size = sizes[i].minimum_size;

      gtk_widget_get_preferred_height_for_width (child->widget,
                                                 child_size, &child_minimum, &child_natural);

      computed_minimum = MAX (computed_minimum, child_minimum);
      computed_natural = MAX (computed_natural, child_natural);
    }
  }

  center_min = center_nat = 0;
  if (priv->label_box != NULL) {
    gtk_widget_get_preferred_height (priv->label_sizing_box,
                                     &center_min, &center_nat);
  }

  if (priv->custom_title != NULL &&
      gtk_widget_get_visible (priv->custom_title)) {
    gtk_widget_get_preferred_height (priv->custom_title,
                                     &center_min, &center_nat);
  }

  if (priv->titlebar_start_box != NULL) {
    gtk_widget_get_preferred_height (priv->titlebar_start_box,
                                     &child_minimum, &child_natural);
    computed_minimum = MAX (computed_minimum, child_minimum);
    computed_natural = MAX (computed_natural, child_natural);
  }

  if (priv->titlebar_end_box != NULL) {
    gtk_widget_get_preferred_height (priv->titlebar_end_box,
                                     &child_minimum, &child_natural);
    computed_minimum = MAX (computed_minimum, child_minimum);
    computed_natural = MAX (computed_natural, child_natural);
  }

  *minimum_size = computed_minimum;
  *natural_size = computed_natural;
}

static void
hdy_header_bar_measure (GtkWidget      *widget,
                        GtkOrientation  orientation,
                        gint            for_size,
                        gint           *minimum,
                        gint           *natural,
                        gint           *minimum_baseline,
                        gint           *natural_baseline)
{
  gint css_width, css_height;

  gtk_style_context_get (gtk_widget_get_style_context (widget),
                         gtk_widget_get_state_flags (widget),
                         "min-width", &css_width,
                         "min-height", &css_height,
                         NULL);

  if (for_size < 0)
    hdy_header_bar_get_size (widget, orientation, minimum, natural);
  else if (orientation == GTK_ORIENTATION_HORIZONTAL)
    hdy_header_bar_compute_size_for_orientation (widget, MAX (for_size, css_height), minimum, natural);
  else
    hdy_header_bar_compute_size_for_opposing_orientation (widget, MAX (for_size, css_width), minimum, natural);

  hdy_css_measure (widget, orientation, minimum, natural);
}

static void
hdy_header_bar_get_preferred_width (GtkWidget *widget,
                                    gint      *minimum,
                                    gint      *natural)
{
  hdy_header_bar_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
                          minimum, natural,
                          NULL, NULL);
}

static void
hdy_header_bar_get_preferred_height (GtkWidget *widget,
                                     gint      *minimum,
                                     gint      *natural)
{
  hdy_header_bar_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
                          minimum, natural,
                          NULL, NULL);
}

static void
hdy_header_bar_get_preferred_width_for_height (GtkWidget *widget,
                                               gint       height,
                                               gint      *minimum,
                                               gint      *natural)
{
  hdy_header_bar_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
                          minimum, natural,
                          NULL, NULL);
}

static void
hdy_header_bar_get_preferred_height_for_width (GtkWidget *widget,
                                               gint       width,
                                               gint      *minimum,
                                               gint      *natural)
{
  hdy_header_bar_measure (widget, GTK_ORIENTATION_VERTICAL, width,
                          minimum, natural,
                          NULL, NULL);
}

static GtkWidget *
get_title_size (HdyHeaderBar     *self,
                gint              for_height,
                GtkRequestedSize *size,
                gint             *expanded)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkWidget *title_widget;

  if (priv->custom_title != NULL &&
      gtk_widget_get_visible (priv->custom_title))
    title_widget = priv->custom_title;
  else if (priv->label_box != NULL)
    title_widget = priv->label_box;
  else
    return NULL;

  gtk_widget_get_preferred_width_for_height (title_widget,
                                             for_height,
                                             &(size->minimum_size),
                                             &(size->natural_size));

  *expanded = gtk_widget_compute_expand (title_widget, GTK_ORIENTATION_HORIZONTAL);

  return title_widget;
}

static void
children_allocate (HdyHeaderBar      *self,
                   GtkAllocation     *allocation,
                   GtkAllocation    **allocations,
                   GtkRequestedSize  *sizes,
                   gint               decoration_width[2],
                   gint               uniform_expand_bonus[2],
                   gint               leftover_expand_bonus[2])
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkPackType packing;
  GtkAllocation child_allocation;
  gint x;
  gint i;
  GList *l;
  Child *child;
  gint child_size;
  /* GtkTextDirection direction; */

  /* Allocate the children on both sides of the title. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    child_allocation.y = allocation->y;
    child_allocation.height = allocation->height;
    if (packing == GTK_PACK_START)
      x = allocation->x + decoration_width[0];
    else
      x = allocation->x + allocation->width - decoration_width[1];

    i = 0;
    for (l = priv->children; l != NULL; l = l->next) {
      child = l->data;
      if (!gtk_widget_get_visible (child->widget))
        continue;

      if (child->pack_type != packing)
        goto next;

      child_size = sizes[i].minimum_size;

      /* If this child is expanded, give it extra space from the reserves. */
      if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL)) {
        gint expand_bonus;

        expand_bonus = uniform_expand_bonus[packing];

        if (leftover_expand_bonus[packing] > 0) {
          expand_bonus++;
          leftover_expand_bonus[packing]--;
        }

        child_size += expand_bonus;
      }

      child_allocation.width = child_size;

      if (packing == GTK_PACK_START) {
        child_allocation.x = x;
        x += child_size;
        x += priv->spacing;
      } else {
        x -= child_size;
        child_allocation.x = x;
        x -= priv->spacing;
      }

      if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
        child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width;

      (*allocations)[i] = child_allocation;

      next:
        i++;
    }
  }
}

static void
get_loose_centering_allocations (HdyHeaderBar   *self,
                                 GtkAllocation  *allocation,
                                 GtkAllocation **allocations,
                                 GtkAllocation  *title_allocation,
                                 gint            decoration_width[2])
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkRequestedSize *sizes;
  gint width;
  gint nvis_children;
  GtkRequestedSize title_size = { 0 };
  gboolean title_expands = FALSE;
  gint side[2] = { 0 };
  gint uniform_expand_bonus[2] = { 0 };
  gint leftover_expand_bonus[2] = { 0 };
  gint side_free_space[2] = { 0 };
  gint center_free_space[2] = { 0 };
  gint nexpand_children[2] = { 0 };
  GList *l;
  gint i;
  Child *child;
  GtkPackType packing;

  nvis_children = count_visible_children (self);
  sizes = g_newa (GtkRequestedSize, nvis_children);

  width = allocation->width - nvis_children * priv->spacing;

  i = 0;
  for (l = priv->children; l; l = l->next) {
    child = l->data;
    if (!gtk_widget_get_visible (child->widget))
      continue;

    if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
      nexpand_children[child->pack_type]++;

    gtk_widget_get_preferred_width_for_height (child->widget,
                                               allocation->height,
                                               &sizes[i].minimum_size,
                                               &sizes[i].natural_size);
    width -= sizes[i].minimum_size;
    i++;
  }

  get_title_size (self, allocation->height, &title_size, &title_expands);
  width -= title_size.minimum_size;

  /* Compute the nominal size of the children filling up each side of the title
   * in titlebar.
   */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    i = 0;
    for (l = priv->children; l != NULL; l = l->next) {
      child = l->data;
      if (!gtk_widget_get_visible (child->widget))
        continue;

      if (child->pack_type == packing)
        side[packing] += sizes[i].minimum_size + priv->spacing;

      i++;
    }
  }

  /* Distribute the available space for natural expansion of the children. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
    width -= decoration_width[packing];
  width = gtk_distribute_natural_allocation (MAX (0, width), 1, &title_size);
  width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes);

  /* Compute the nominal size of the children filling up each side of the title
   * in titlebar.
   */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    i = 0;
    side[packing] = 0;
    for (l = priv->children; l != NULL; l = l->next) {
      child = l->data;
      if (!gtk_widget_get_visible (child->widget))
        continue;

      if (child->pack_type == packing)
        side[packing] += sizes[i].minimum_size + priv->spacing;

      i++;
    }
  }

  /* Figure out how much space is left on each side of the title, and earkmark
   * that space for the expanded children.
   *
   * If the title itself is expanded, then it gets half the spoils from each
   * side, or all the spoil of that side if no child is expanded there.
   */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    side_free_space[packing] = MIN (MAX (allocation->width / 2 - title_size.natural_size / 2 - decoration_width[packing] - side[packing], 0), width);
    if (title_expands)
      center_free_space[packing] = nexpand_children[packing] > 0 ?
        side_free_space[packing] / 2 :
        side_free_space[packing];
  }
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    side_free_space[packing] -= center_free_space[packing];
    width -= side_free_space[packing];

    if (nexpand_children[packing] == 0)
      continue;

    uniform_expand_bonus[packing] = (side_free_space[packing]) / nexpand_children[packing];
    leftover_expand_bonus[packing] = (side_free_space[packing]) % nexpand_children[packing];
  }

  children_allocate (self, allocation, allocations, sizes, decoration_width, uniform_expand_bonus, leftover_expand_bonus);

  /* We don't enforce css borders on the center widget, to make title/subtitle
   * combinations fit without growing the header.
   */
  title_allocation->y = allocation->y;
  title_allocation->height = allocation->height;

  title_allocation->width = MIN (allocation->width - decoration_width[0] - side[0] - decoration_width[1] - side[1],
                                 title_size.natural_size);
  title_allocation->x = allocation->x + (allocation->width - title_allocation->width) / 2;

  /* If the title widget is expanded, then grow it by all the available free
   * space, and recenter it.
   */
  if (title_expands && width > 0) {
    title_allocation->width += width;
    title_allocation->x -= width / 2;
  }

  if (allocation->x + decoration_width[0] + side[0] > title_allocation->x)
    title_allocation->x = allocation->x + decoration_width[0] + side[0];
  else if (allocation->x + allocation->width - decoration_width[1] - side[1] < title_allocation->x + title_allocation->width)
    title_allocation->x = allocation->x + allocation->width - decoration_width[1] - side[1] - title_allocation->width;

  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    title_allocation->x = allocation->x + allocation->width - (title_allocation->x - allocation->x) - title_allocation->width;
}

static void
get_strict_centering_allocations (HdyHeaderBar   *self,
                                  GtkAllocation  *allocation,
                                  GtkAllocation **allocations,
                                  GtkAllocation  *title_allocation,
                                  gint            decoration_width[2])
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  GtkRequestedSize *children_sizes = { 0 };
  GtkRequestedSize *children_sizes_for_side[2] = { 0 };
  GtkRequestedSize side_size[2] = { 0 }; /* The size requested by each side. */
  GtkRequestedSize title_size = { 0 }; /* The size requested by the title. */
  GtkRequestedSize side_request = { 0 }; /* The maximum size requested by each side, decoration included. */
  gint side_max; /* The maximum space allocatable to each side, decoration included. */
  gint title_leftover; /* The or 0px or 1px leftover from ensuring each side is allocated the same size. */
  /* The space available for expansion on each side, including for the title. */
  gint free_space[2] = { 0 };
  /* The space the title will take from the free space of each side for its expansion. */
  gint title_expand_bonus = 0;
  gint uniform_expand_bonus[2] = { 0 };
  gint leftover_expand_bonus[2] = { 0 };

  gint nvis_children, n_side_vis_children[2] = { 0 };
  gint nexpand_children[2] = { 0 };
  gboolean title_expands = FALSE;
  GList *l;
  gint i;
  Child *child;
  GtkPackType packing;

  get_title_size (self, allocation->height, &title_size, &title_expands);

  nvis_children = count_visible_children (self);
  children_sizes = g_newa (GtkRequestedSize, nvis_children);
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    n_side_vis_children[packing] = count_visible_children_for_pack_type (self, packing);
    children_sizes_for_side[packing] = packing == 0 ? children_sizes : children_sizes + n_side_vis_children[packing - 1];
    free_space[packing] = (allocation->width - title_size.minimum_size) / 2 - decoration_width[packing];
  }

  /* Compute the nominal size of the children filling up each side of the title
   * in titlebar.
   */
  i = 0;
  for (l = priv->children; l; l = l->next) {
    child = l->data;
    if (!gtk_widget_get_visible (child->widget))
      continue;

    if (gtk_widget_compute_expand (child->widget, GTK_ORIENTATION_HORIZONTAL))
      nexpand_children[child->pack_type]++;

    gtk_widget_get_preferred_width_for_height (child->widget,
                                               allocation->height,
                                               &children_sizes[i].minimum_size,
                                               &children_sizes[i].natural_size);
    side_size[child->pack_type].minimum_size += children_sizes[i].minimum_size + priv->spacing;
    side_size[child->pack_type].natural_size += children_sizes[i].natural_size + priv->spacing;
    free_space[child->pack_type] -= children_sizes[i].minimum_size + priv->spacing;

    i++;
  }

  /* Figure out the space maximum size requests from each side to help centering
   * the title.
   */
  side_request.minimum_size = MAX (side_size[GTK_PACK_START].minimum_size + decoration_width[GTK_PACK_START],
                                   side_size[GTK_PACK_END].minimum_size + decoration_width[GTK_PACK_END]);
  side_request.natural_size = MAX (side_size[GTK_PACK_START].natural_size + decoration_width[GTK_PACK_START],
                                   side_size[GTK_PACK_END].natural_size + decoration_width[GTK_PACK_END]);
  title_leftover = (allocation->width - title_size.natural_size) % 2;
  side_max = MAX ((allocation->width - title_size.natural_size) / 2, side_request.minimum_size);

  /* Distribute the available space for natural expansion of the children and
   * figure out how much space is left on each side of the title, free to be
   * used for expansion.
   */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    gint leftovers = side_max - side_size[packing].minimum_size - decoration_width[packing];
    free_space[packing] = gtk_distribute_natural_allocation (leftovers, n_side_vis_children[packing], children_sizes_for_side[packing]);
  }

  /* Compute how much of each side's free space should be distributed to the
   * title for its expansion.
   */
  title_expand_bonus = !title_expands ? 0 :
    MIN (nexpand_children[GTK_PACK_START] > 0 ? free_space[GTK_PACK_START] / 2 :
                                                free_space[GTK_PACK_START],
         nexpand_children[GTK_PACK_END] > 0 ? free_space[GTK_PACK_END] / 2 :
                                              free_space[GTK_PACK_END]);

  /* Remove the space the title takes from each side for its expansion. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++)
    free_space[packing] -= title_expand_bonus;

  /* Distribute the free space for expansion of the children. */
  for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    if (nexpand_children[packing] == 0)
      continue;

    uniform_expand_bonus[packing] = free_space[packing] / nexpand_children[packing];
    leftover_expand_bonus[packing] = free_space[packing] % nexpand_children[packing];
  }

  children_allocate (self, allocation, allocations, children_sizes, decoration_width, uniform_expand_bonus, leftover_expand_bonus);

  /* We don't enforce css borders on the center widget, to make title/subtitle
   * combinations fit without growing the header.
   */
  title_allocation->y = allocation->y;
  title_allocation->height = allocation->height;

  title_allocation->width = MIN (allocation->width - 2 * side_max + title_leftover,
                                title_size.natural_size);
  title_allocation->x = allocation->x + (allocation->width - title_allocation->width) / 2;

  /* If the title widget is expanded, then grow it by the free space available
   * for it.
   */
  if (title_expands) {
    title_allocation->width += 2 * title_expand_bonus;
    title_allocation->x -= title_expand_bonus;
  }

  if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
    title_allocation->x = allocation->x + allocation->width - (title_allocation->x - allocation->x) - title_allocation->width;
}

static void
hdy_header_bar_size_allocate (GtkWidget     *widget,
                              GtkAllocation *allocation)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GtkAllocation *allocations;
  GtkAllocation title_allocation;
  GtkAllocation clip;
  gint nvis_children;
  GList *l;
  gint i;
  Child *child;
  GtkAllocation child_allocation;
  GtkTextDirection direction;
  GtkWidget *decoration_box[2] = { priv->titlebar_start_box, priv->titlebar_end_box };
  gint decoration_width[2] = { 0 };

  gtk_render_background_get_clip (gtk_widget_get_style_context (widget),
                                  allocation->x,
                                  allocation->y,
                                  allocation->width,
                                  allocation->height,
                                  &clip);

  gtk_widget_set_allocation (widget, allocation);

  if (gtk_widget_get_realized (widget))
    gdk_window_move_resize (gtk_widget_get_window (widget),
                            allocation->x,
                            allocation->y,
                            allocation->width,
                            allocation->height);

  allocation->x = 0;
  allocation->y = 0;
  hdy_css_size_allocate (widget, allocation);

  direction = gtk_widget_get_direction (widget);
  nvis_children = count_visible_children (self);
  allocations = g_newa (GtkAllocation, nvis_children);

  /* Get the decoration width. */
  for (GtkPackType packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) {
    gint min, nat;

    if (decoration_box[packing] == NULL)
      continue;

    gtk_widget_get_preferred_width_for_height (decoration_box[packing],
                                               allocation->height,
                                               &min, &nat);
    decoration_width[packing] = nat + priv->spacing;
  }

  /* Allocate the decoration widgets. */
  child_allocation.y = allocation->y;
  child_allocation.height = allocation->height;

  if (priv->titlebar_start_box) {
    if (direction == GTK_TEXT_DIR_LTR)
      child_allocation.x = allocation->x;
    else
      child_allocation.x = allocation->x + allocation->width - decoration_width[GTK_PACK_START] + priv->spacing;
    child_allocation.width = decoration_width[GTK_PACK_START] - priv->spacing;
    gtk_widget_size_allocate (priv->titlebar_start_box, &child_allocation);
  }

  if (priv->titlebar_end_box) {
    if (direction != GTK_TEXT_DIR_LTR)
      child_allocation.x = allocation->x;
    else
      child_allocation.x = allocation->x + allocation->width - decoration_width[GTK_PACK_END] + priv->spacing;
    child_allocation.width = decoration_width[GTK_PACK_END] - priv->spacing;
    gtk_widget_size_allocate (priv->titlebar_end_box, &child_allocation);
  }

  /* Get the allocation for widgets on both side of the title. */
  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER) {
    if (priv->centering_policy == HDY_CENTERING_POLICY_STRICT)
      get_strict_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width);
    else
      get_loose_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width);
  } else {
    /* For memory usage optimisation's sake, we will use the allocations
     * variable to store the loose centering allocations and the
     * title_allocation variable to store the loose title allocation.
     */
    GtkAllocation *strict_allocations = g_newa (GtkAllocation, nvis_children);
    GtkAllocation strict_title_allocation;
    gdouble strict_centering_t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);

    if (priv->centering_policy != HDY_CENTERING_POLICY_STRICT)
      strict_centering_t = 1.0 - strict_centering_t;

    get_loose_centering_allocations (self, allocation, &allocations, &title_allocation, decoration_width);
    get_strict_centering_allocations (self, allocation, &strict_allocations, &strict_title_allocation, decoration_width);

    for (i = 0; i < nvis_children; i++) {
      allocations[i].x = hdy_lerp (allocations[i].x, strict_allocations[i].x, strict_centering_t);
      allocations[i].y = hdy_lerp (allocations[i].y, strict_allocations[i].y, strict_centering_t);
      allocations[i].width = hdy_lerp (allocations[i].width, strict_allocations[i].width, strict_centering_t);
      allocations[i].height = hdy_lerp (allocations[i].height, strict_allocations[i].height, strict_centering_t);
    }
    title_allocation.x = hdy_lerp (title_allocation.x, strict_title_allocation.x, strict_centering_t);
    title_allocation.y = hdy_lerp (title_allocation.y, strict_title_allocation.y, strict_centering_t);
    title_allocation.width = hdy_lerp (title_allocation.width, strict_title_allocation.width, strict_centering_t);
    title_allocation.height = hdy_lerp (title_allocation.height, strict_title_allocation.height, strict_centering_t);
  }

  /* Allocate the children on both sides of the title. */
  i = 0;
  for (l = priv->children; l != NULL; l = l->next) {
    child = l->data;
    if (!gtk_widget_get_visible (child->widget))
      continue;

    gtk_widget_size_allocate (child->widget, &allocations[i]);
    i++;
  }

  /* Allocate the title widget. */
  if (priv->custom_title != NULL && gtk_widget_get_visible (priv->custom_title))
    gtk_widget_size_allocate (priv->custom_title, &title_allocation);
  else if (priv->label_box != NULL)
    gtk_widget_size_allocate (priv->label_box, &title_allocation);

  gtk_widget_set_clip (widget, &clip);
}

static void
hdy_header_bar_destroy (GtkWidget *widget)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (HDY_HEADER_BAR (widget));

  if (priv->label_sizing_box) {
    gtk_widget_destroy (priv->label_sizing_box);
    g_clear_object (&priv->label_sizing_box);
  }

  if (priv->custom_title) {
    gtk_widget_unparent (priv->custom_title);
    priv->custom_title = NULL;
  }

  if (priv->label_box) {
    gtk_widget_unparent (priv->label_box);
    priv->label_box = NULL;
  }

  if (priv->titlebar_start_box) {
    gtk_widget_unparent (priv->titlebar_start_box);
    priv->titlebar_start_box = NULL;
    priv->titlebar_start_separator = NULL;
  }

  if (priv->titlebar_end_box) {
    gtk_widget_unparent (priv->titlebar_end_box);
    priv->titlebar_end_box = NULL;
    priv->titlebar_end_separator = NULL;
  }

  GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->destroy (widget);
}

static void
hdy_header_bar_finalize (GObject *object)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (HDY_HEADER_BAR (object));

  g_clear_pointer (&priv->title, g_free);
  g_clear_pointer (&priv->subtitle, g_free);
  g_clear_pointer (&priv->decoration_layout, g_free);
  g_clear_object (&priv->controller);

  G_OBJECT_CLASS (hdy_header_bar_parent_class)->finalize (object);
}

static void
hdy_header_bar_get_property (GObject    *object,
                             guint       prop_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (object);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  switch (prop_id) {
  case PROP_TITLE:
    g_value_set_string (value, priv->title);
    break;
  case PROP_SUBTITLE:
    g_value_set_string (value, priv->subtitle);
    break;
  case PROP_CUSTOM_TITLE:
    g_value_set_object (value, priv->custom_title);
    break;
  case PROP_SPACING:
    g_value_set_int (value, priv->spacing);
    break;
  case PROP_SHOW_CLOSE_BUTTON:
    g_value_set_boolean (value, hdy_header_bar_get_show_close_button (self));
    break;
  case PROP_HAS_SUBTITLE:
    g_value_set_boolean (value, hdy_header_bar_get_has_subtitle (self));
    break;
  case PROP_DECORATION_LAYOUT:
    g_value_set_string (value, hdy_header_bar_get_decoration_layout (self));
    break;
  case PROP_DECORATION_LAYOUT_SET:
    g_value_set_boolean (value, priv->decoration_layout_set);
    break;
  case PROP_CENTERING_POLICY:
    g_value_set_enum (value, hdy_header_bar_get_centering_policy (self));
    break;
  case PROP_TRANSITION_DURATION:
    g_value_set_uint (value, hdy_header_bar_get_transition_duration (self));
    break;
  case PROP_TRANSITION_RUNNING:
    g_value_set_boolean (value, hdy_header_bar_get_transition_running (self));
    break;
  case PROP_INTERPOLATE_SIZE:
    g_value_set_boolean (value, hdy_header_bar_get_interpolate_size (self));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
hdy_header_bar_set_property (GObject      *object,
                             guint         prop_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (object);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  switch (prop_id) {
  case PROP_TITLE:
    hdy_header_bar_set_title (self, g_value_get_string (value));
    break;
  case PROP_SUBTITLE:
    hdy_header_bar_set_subtitle (self, g_value_get_string (value));
    break;
  case PROP_CUSTOM_TITLE:
    hdy_header_bar_set_custom_title (self, g_value_get_object (value));
    break;
  case PROP_SPACING:
    if (priv->spacing != g_value_get_int (value)) {
      priv->spacing = g_value_get_int (value);
      gtk_widget_queue_resize (GTK_WIDGET (self));
      g_object_notify_by_pspec (object, pspec);
    }
    break;
  case PROP_SHOW_CLOSE_BUTTON:
    hdy_header_bar_set_show_close_button (self, g_value_get_boolean (value));
    break;
  case PROP_HAS_SUBTITLE:
    hdy_header_bar_set_has_subtitle (self, g_value_get_boolean (value));
    break;
  case PROP_DECORATION_LAYOUT:
    hdy_header_bar_set_decoration_layout (self, g_value_get_string (value));
    break;
  case PROP_DECORATION_LAYOUT_SET:
    priv->decoration_layout_set = g_value_get_boolean (value);
    break;
  case PROP_CENTERING_POLICY:
    hdy_header_bar_set_centering_policy (self, g_value_get_enum (value));
    break;
  case PROP_TRANSITION_DURATION:
    hdy_header_bar_set_transition_duration (self, g_value_get_uint (value));
    break;
  case PROP_INTERPOLATE_SIZE:
    hdy_header_bar_set_interpolate_size (self, g_value_get_boolean (value));
    break;
  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
notify_child_cb (GObject      *child,
                 GParamSpec   *pspec,
                 HdyHeaderBar *self)
{
  _hdy_header_bar_update_separator_visibility (self);
}

static void
hdy_header_bar_pack (HdyHeaderBar *self,
                     GtkWidget    *widget,
                     GtkPackType   pack_type)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  Child *child;

  g_return_if_fail (gtk_widget_get_parent (widget) == NULL);

  child = g_new (Child, 1);
  child->widget = widget;
  child->pack_type = pack_type;

  priv->children = g_list_append (priv->children, child);

  gtk_widget_freeze_child_notify (widget);
  gtk_widget_set_parent (widget, GTK_WIDGET (self));
  g_signal_connect (widget, "notify::visible", G_CALLBACK (notify_child_cb), self);
  gtk_widget_child_notify (widget, "pack-type");
  gtk_widget_child_notify (widget, "position");
  gtk_widget_thaw_child_notify (widget);

  _hdy_header_bar_update_separator_visibility (self);
}

static void
hdy_header_bar_add (GtkContainer *container,
                    GtkWidget    *child)
{
  hdy_header_bar_pack (HDY_HEADER_BAR (container), child, GTK_PACK_START);
}

static GList *
find_child_link (HdyHeaderBar *self,
                 GtkWidget    *widget,
                 gint         *position)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  Child *child;
  gint i;

  for (l = priv->children, i = 0; l; l = l->next, i++) {
    child = l->data;
    if (child->widget == widget) {
      if (position)
        *position = i;

      return l;
    }
  }

  return NULL;
}

static void
hdy_header_bar_remove (GtkContainer *container,
                       GtkWidget    *widget)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (container);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  Child *child;

  l = find_child_link (self, widget, NULL);
  if (l) {
    child = l->data;
    g_signal_handlers_disconnect_by_func (widget, notify_child_cb, self);
    gtk_widget_unparent (child->widget);
    priv->children = g_list_delete_link (priv->children, l);
    g_free (child);
    gtk_widget_queue_resize (GTK_WIDGET (container));
    _hdy_header_bar_update_separator_visibility (self);
  }
}

static void
hdy_header_bar_forall (GtkContainer *container,
                       gboolean      include_internals,
                       GtkCallback   callback,
                       gpointer      callback_data)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (container);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  Child *child;
  GList *children;

  if (include_internals && priv->titlebar_start_box != NULL)
    (* callback) (priv->titlebar_start_box, callback_data);

  children = priv->children;
  while (children) {
    child = children->data;
    children = children->next;
    if (child->pack_type == GTK_PACK_START)
      (* callback) (child->widget, callback_data);
  }

  if (priv->custom_title != NULL)
    (* callback) (priv->custom_title, callback_data);

  if (include_internals && priv->label_box != NULL)
    (* callback) (priv->label_box, callback_data);

  children = priv->children;
  while (children) {
    child = children->data;
    children = children->next;
    if (child->pack_type == GTK_PACK_END)
      (* callback) (child->widget, callback_data);
  }

  if (include_internals && priv->titlebar_end_box != NULL)
    (* callback) (priv->titlebar_end_box, callback_data);
}

static void
hdy_header_bar_reorder_child (HdyHeaderBar *self,
                              GtkWidget    *widget,
                              gint          position)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  gint old_position;
  Child *child;

  l = find_child_link (self, widget, &old_position);

  if (l == NULL)
    return;

  if (old_position == position)
    return;

  child = l->data;
  priv->children = g_list_delete_link (priv->children, l);

  if (position < 0)
    l = NULL;
  else
    l = g_list_nth (priv->children, position);

  priv->children = g_list_insert_before (priv->children, l, child);
  gtk_widget_child_notify (widget, "position");
  gtk_widget_queue_resize (widget);
}

static GType
hdy_header_bar_child_type (GtkContainer *container)
{
  return GTK_TYPE_WIDGET;
}

static void
hdy_header_bar_get_child_property (GtkContainer *container,
                                   GtkWidget    *widget,
                                   guint         property_id,
                                   GValue       *value,
                                   GParamSpec   *pspec)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (container);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  GList *l;
  Child *child;

  l = find_child_link (self, widget, NULL);
  if (l == NULL) {
    g_param_value_set_default (pspec, value);

    return;
  }

  child = l->data;

  switch (property_id) {
  case CHILD_PROP_PACK_TYPE:
    g_value_set_enum (value, child->pack_type);
    break;

  case CHILD_PROP_POSITION:
    g_value_set_int (value, g_list_position (priv->children, l));
    break;

  default:
    GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
    break;
  }
}

static void
hdy_header_bar_set_child_property (GtkContainer *container,
                                   GtkWidget    *widget,
                                   guint         property_id,
                                   const GValue *value,
                                   GParamSpec   *pspec)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (container);
  GList *l;
  Child *child;

  l = find_child_link (self, widget, NULL);
  if (l == NULL)
    return;

  child = l->data;

  switch (property_id) {
  case CHILD_PROP_PACK_TYPE:
    child->pack_type = g_value_get_enum (value);
    _hdy_header_bar_update_separator_visibility (self);
    gtk_widget_queue_resize (widget);
    break;

  case CHILD_PROP_POSITION:
    hdy_header_bar_reorder_child (self, widget, g_value_get_int (value));
    break;

  default:
    GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
    break;
  }
}

static gboolean
hdy_header_bar_draw (GtkWidget *widget,
                     cairo_t   *cr)
{
  GtkStyleContext *context;

  context = gtk_widget_get_style_context (widget);
  /* GtkWidget draws nothing by default so we have to render the background
   * explicitly for HdyHederBar to render the typical titlebar background.
   */
  gtk_render_background (context,
                         cr,
                         0, 0,
                         gtk_widget_get_allocated_width (widget),
                         gtk_widget_get_allocated_height (widget));
  /* Ditto for the borders. */
  gtk_render_frame (context,
                    cr,
                    0, 0,
                    gtk_widget_get_allocated_width (widget),
                    gtk_widget_get_allocated_height (widget));

  return GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->draw (widget, cr);
}

static void
hdy_header_bar_realize (GtkWidget *widget)
{
  GtkSettings *settings;
  GtkAllocation allocation;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GdkWindow *window;

  settings = gtk_widget_get_settings (widget);
  g_signal_connect_swapped (settings, "notify::gtk-shell-shows-app-menu",
                            G_CALLBACK (hdy_header_bar_update_window_buttons), widget);
  g_signal_connect_swapped (settings, "notify::gtk-decoration-layout",
                            G_CALLBACK (hdy_header_bar_update_window_buttons), widget);
  update_is_mobile_window (HDY_HEADER_BAR (widget));
  hdy_header_bar_update_window_buttons (HDY_HEADER_BAR (widget));

  gtk_widget_get_allocation (widget, &allocation);
  gtk_widget_set_realized (widget, TRUE);

  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;

  window = gdk_window_new (gtk_widget_get_parent_window (widget),
                           &attributes,
                           attributes_mask);
  gtk_widget_set_window (widget, window);
  gtk_widget_register_window (widget, window);
}

static void
hdy_header_bar_unrealize (GtkWidget *widget)
{
  GtkSettings *settings;

  settings = gtk_widget_get_settings (widget);

  g_signal_handlers_disconnect_by_func (settings, hdy_header_bar_update_window_buttons, widget);

  GTK_WIDGET_CLASS (hdy_header_bar_parent_class)->unrealize (widget);
}

static gboolean
window_state_changed (GtkWidget           *window,
                      GdkEventWindowState *event,
                      gpointer             data)
{
  HdyHeaderBar *self = HDY_HEADER_BAR (data);

  if (event->changed_mask & (GDK_WINDOW_STATE_FULLSCREEN |
                             GDK_WINDOW_STATE_MAXIMIZED |
                             GDK_WINDOW_STATE_TILED |
                             GDK_WINDOW_STATE_TOP_TILED |
                             GDK_WINDOW_STATE_RIGHT_TILED |
                             GDK_WINDOW_STATE_BOTTOM_TILED |
                             GDK_WINDOW_STATE_LEFT_TILED))
    hdy_header_bar_update_window_buttons (self);

  return FALSE;
}

static void
hdy_header_bar_hierarchy_changed (GtkWidget *widget,
                                  GtkWidget *previous_toplevel)
{
  GtkWidget *toplevel;
  HdyHeaderBar *self = HDY_HEADER_BAR (widget);
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  toplevel = gtk_widget_get_toplevel (widget);

  if (previous_toplevel)
    g_signal_handlers_disconnect_by_func (previous_toplevel,
                                          window_state_changed, widget);

  if (toplevel)
    g_signal_connect_after (toplevel, "window-state-event",
                            G_CALLBACK (window_state_changed), widget);

  if (priv->window_size_allocated_id > 0) {
    g_signal_handler_disconnect (previous_toplevel, priv->window_size_allocated_id);
    priv->window_size_allocated_id = 0;
  }

  if (GTK_IS_WINDOW (toplevel))
    priv->window_size_allocated_id =
      g_signal_connect_swapped (toplevel, "size-allocate",
                                G_CALLBACK (update_is_mobile_window), self);

  update_is_mobile_window (self);
  hdy_header_bar_update_window_buttons (self);
}

static void
hdy_header_bar_class_init (HdyHeaderBarClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class);

  object_class->finalize = hdy_header_bar_finalize;
  object_class->get_property = hdy_header_bar_get_property;
  object_class->set_property = hdy_header_bar_set_property;

  widget_class->destroy = hdy_header_bar_destroy;
  widget_class->size_allocate = hdy_header_bar_size_allocate;
  widget_class->get_preferred_width = hdy_header_bar_get_preferred_width;
  widget_class->get_preferred_height = hdy_header_bar_get_preferred_height;
  widget_class->get_preferred_height_for_width = hdy_header_bar_get_preferred_height_for_width;
  widget_class->get_preferred_width_for_height = hdy_header_bar_get_preferred_width_for_height;
  widget_class->draw = hdy_header_bar_draw;
  widget_class->realize = hdy_header_bar_realize;
  widget_class->unrealize = hdy_header_bar_unrealize;
  widget_class->hierarchy_changed = hdy_header_bar_hierarchy_changed;

  container_class->add = hdy_header_bar_add;
  container_class->remove = hdy_header_bar_remove;
  container_class->forall = hdy_header_bar_forall;
  container_class->child_type = hdy_header_bar_child_type;
  container_class->set_child_property = hdy_header_bar_set_child_property;
  container_class->get_child_property = hdy_header_bar_get_child_property;
  gtk_container_class_handle_border_width (container_class);

  gtk_container_class_install_child_property (container_class,
                                              CHILD_PROP_PACK_TYPE,
                                              g_param_spec_enum ("pack-type",
                                                                 _("Pack type"),
                                                                 _("A GtkPackType indicating whether the child is packed with reference to the start or end of the parent"),
                                                                 GTK_TYPE_PACK_TYPE, GTK_PACK_START,
                                                                 G_PARAM_READWRITE));
  gtk_container_class_install_child_property (container_class,
                                              CHILD_PROP_POSITION,
                                              g_param_spec_int ("position",
                                                                _("Position"),
                                                                _("The index of the child in the parent"),
                                                                -1, G_MAXINT, 0,
                                                                G_PARAM_READWRITE));

  /**
   * HdyHeaderBar:title: (attributes org.gtk.Property.get=hdy_header_bar_get_title org.gtk.Property.set=hdy_header_bar_set_title)
   *
   * The title to display.
   *
   * Since: 1.0
   */
  props[PROP_TITLE] =
    g_param_spec_string ("title",
                         _("Title"),
                         _("The title to display"),
                         NULL,
                         G_PARAM_READWRITE);

  /**
   * HdyHeaderBar:subtitle: (attributes org.gtk.Property.get=hdy_header_bar_get_subtitle org.gtk.Property.set=hdy_header_bar_set_subtitle)
   *
   * The subtitle to display.
   *
   * Since: 1.0
   */
  props[PROP_SUBTITLE] =
    g_param_spec_string ("subtitle",
                         _("Subtitle"),
                         _("The subtitle to display"),
                         NULL,
                         G_PARAM_READWRITE);

  /**
   * HdyHeaderBar:custom-title: (attributes org.gtk.Property.get=hdy_header_bar_get_custom_title org.gtk.Property.set=hdy_header_bar_set_custom_title)
   *
   * Custom title widget to display.
   *
   * Since: 1.0
   */
  props[PROP_CUSTOM_TITLE] =
    g_param_spec_object ("custom-title",
                         _("Custom Title"),
                         _("Custom title widget to display"),
                         GTK_TYPE_WIDGET,
                         G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS);

  /**
   * HdyHeaderBar:spacing:
   *
   * The amount of space between children.
   *
   * Since: 1.0
   */
  props[PROP_SPACING] =
    g_param_spec_int ("spacing",
                      _("Spacing"),
                      _("The amount of space between children"),
                      0, G_MAXINT,
                      DEFAULT_SPACING,
                      G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  /**
   * HdyHeaderBar:show-close-button: (attributes org.gtk.Property.get=hdy_header_bar_get_show_close_button org.gtk.Property.set=hdy_header_bar_set_show_close_button)
   *
   * Whether to show window decorations.
   *
   * Which buttons are actually shown and where is determined by the
   * [property@HeaderBar:decoration-layout] property, and by the state of the
   * window (e.g. a close button will not be shown if the window can't be
   * closed).
   *
   * Since: 1.0
   */
  props[PROP_SHOW_CLOSE_BUTTON] =
    g_param_spec_boolean ("show-close-button",
                          _("Show decorations"),
                          _("Whether to show window decorations"),
                          FALSE,
                          G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  /**
   * HdyHeaderBar:decoration-layout: (attributes org.gtk.Property.get=hdy_header_bar_get_decoration_layout org.gtk.Property.set=hdy_header_bar_set_decoration_layout)
   *
   * The decoration layout for buttons.
   *
   * If this property is not set, the
   * [property@Gtk.Settings:gtk-decoration-layout] setting is used.
   *
   * There can be valid reasons for overriding the setting, such as a header bar
   * design that does not allow for buttons to take room on the right, or only
   * offers room for a single close button. Split header bars are another example
   * for overriding the setting.
   *
   * The format of the string is button names, separated by commas. A colon
   * separates the buttons that should appear on the start from those on the
   * end. Recognized button names are minimize, maximize, close, icon (the
   * window icon) and menu (a menu button for the fallback app menu).
   *
   * For example, “menu:minimize,maximize,close” specifies a menu on the left, and
   * minimize, maximize and close buttons on the right.
   *
   * Since: 1.0
   */
  props[PROP_DECORATION_LAYOUT] =
    g_param_spec_string ("decoration-layout",
                         _("Decoration Layout"),
                         _("The layout for window decorations"),
                         NULL,
                         G_PARAM_READWRITE);

  /**
   * HdyHeaderBar:decoration-layout-set:
   *
   * Whether [property@HeaderBar:decoration-layout] is set.
   *
   * Since: 1.0
   */
  props[PROP_DECORATION_LAYOUT_SET] =
    g_param_spec_boolean ("decoration-layout-set",
                          _("Decoration Layout Set"),
                          _("Whether the decoration-layout property has been set"),
                          FALSE,
                          G_PARAM_READWRITE);

  /**
   * HdyHeaderBar:has-subtitle: (attributes org.gtk.Property.get=hdy_header_bar_get_has_subtitle org.gtk.Property.set=hdy_header_bar_set_has_subtitle)
   *
   * Whether to reserve space for a subtitle, even if none is currently set.
   *
   * Since: 1.0
   */
  props[PROP_HAS_SUBTITLE] =
    g_param_spec_boolean ("has-subtitle",
                          _("Has Subtitle"),
                          _("Whether to reserve space for a subtitle"),
                          TRUE,
                          G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);

  /**
   * HdyHeaderBar:centering-policy: (attributes org.gtk.Property.get=hdy_header_bar_get_centering_policy org.gtk.Property.set=hdy_header_bar_set_centering_policy)
   *
   * The policy for aligning the center widget.
   *
   * Since: 1.0
   */
  props[PROP_CENTERING_POLICY] =
    g_param_spec_enum ("centering-policy",
                       _("Centering policy"),
                       _("The policy to horizontally align the center widget"),
                       HDY_TYPE_CENTERING_POLICY, HDY_CENTERING_POLICY_LOOSE,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * HdyHeaderBar:transition-duration: (attributes org.gtk.Property.get=hdy_header_bar_get_transition_duration org.gtk.Property.set=hdy_header_bar_set_transition_duration)
   *
   * The transition duration, in milliseconds.
   *
   * Since: 1.0
   */
  props[PROP_TRANSITION_DURATION] =
    g_param_spec_uint ("transition-duration",
                       _("Transition duration"),
                       _("The animation duration, in milliseconds"),
                       0, G_MAXUINT, 200,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * HdyHeaderBar:transition-running: (attributes org.gtk.Property.get=hdy_header_bar_get_transition_running)
   *
   * Whether or not the transition is currently running.
   *
   * Since: 1.0
   */
  props[PROP_TRANSITION_RUNNING] =
    g_param_spec_boolean ("transition-running",
                          _("Transition running"),
                          _("Whether or not the transition is currently running"),
                          FALSE,
                          G_PARAM_READABLE);

  /**
   * HdyHeaderBar:interpolate-size: (attributes org.gtk.Property.get=hdy_header_bar_get_interpolate_size org.gtk.Property.set=hdy_header_bar_set_interpolate_size)
   *
   * Whether the size should smoothly change when changing between children.
   *
   * If `TRUE`, the header bar will interpolate its size between the one of the
   * previous visible child and the one of the new visible child, according to
   * the set transition duration and the orientation, e.g. if the orientation is
   * horizontal, it will interpolate the its height.
   *
   * Since: 1.0
   */
  props[PROP_INTERPOLATE_SIZE] =
    g_param_spec_boolean ("interpolate-size",
                          _("Interpolate size"),
                          _("Whether or not the size should smoothly change when changing between differently sized children"),
                            FALSE,
                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (object_class, LAST_PROP, props);

  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL);
  gtk_widget_class_set_css_name (widget_class, "headerbar");
}

static void
hdy_header_bar_init (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv;
  GtkStyleContext *context;

  priv = hdy_header_bar_get_instance_private (self);

  priv->title = NULL;
  priv->subtitle = NULL;
  priv->custom_title = NULL;
  priv->children = NULL;
  priv->spacing = DEFAULT_SPACING;
  priv->has_subtitle = TRUE;
  priv->decoration_layout = NULL;
  priv->decoration_layout_set = FALSE;
  priv->transition_duration = 200;

  init_sizing_box (self);
  construct_label_box (self);

  priv->controller = hdy_window_handle_controller_new (GTK_WIDGET (self));

  context = gtk_widget_get_style_context (GTK_WIDGET (self));
  /* Ensure the widget has the titlebar style class. */
  gtk_style_context_add_class (context, "titlebar");
}

static void
hdy_header_bar_buildable_add_child (GtkBuildable *buildable,
                                    GtkBuilder   *builder,
                                    GObject      *child,
                                    const gchar  *type)
{
  if (type && strcmp (type, "title") == 0)
    hdy_header_bar_set_custom_title (HDY_HEADER_BAR (buildable), GTK_WIDGET (child));
  else if (!type)
    gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child));
  else
    GTK_BUILDER_WARN_INVALID_CHILD_TYPE (HDY_HEADER_BAR (buildable), type);
}

static void
hdy_header_bar_buildable_init (GtkBuildableIface *iface)
{
  iface->add_child = hdy_header_bar_buildable_add_child;
}

/**
 * hdy_header_bar_new:
 *
 * Creates a new `HdyHeaderBar`.
 *
 * Returns: the newly created `HdyHeaderBar`.
 *
 * Since: 1.0
 */
GtkWidget *
hdy_header_bar_new (void)
{
  return GTK_WIDGET (g_object_new (HDY_TYPE_HEADER_BAR, NULL));
}

/**
 * hdy_header_bar_pack_start:
 * @self: a header bar
 * @child: the widget to be added to @self
 *
 * Adds @child to @self, packed with reference to the start of the @self.
 *
 * Since: 1.0
 */
void
hdy_header_bar_pack_start (HdyHeaderBar *self,
                           GtkWidget    *child)
{
  hdy_header_bar_pack (self, child, GTK_PACK_START);
}

/**
 * hdy_header_bar_pack_end:
 * @self: a header bar
 * @child: the widget to be added to @self
 *
 * Adds @child to @self, packed with reference to the end of the @self.
 *
 * Since: 1.0
 */
void
hdy_header_bar_pack_end (HdyHeaderBar *self,
                         GtkWidget    *child)
{
  hdy_header_bar_pack (self, child, GTK_PACK_END);
}

/**
 * hdy_header_bar_set_title: (attributes org.gtk.Method.set_property=title)
 * @self: a header bar
 * @title: (nullable): a title
 *
 * Sets the title of the [class@HeaderBar].
 *
 * The title should help a user identify the current view. A good title should
 * not include the application name.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_title (HdyHeaderBar *self,
                          const gchar  *title)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  gchar *new_title;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  new_title = g_strdup (title);
  g_free (priv->title);
  priv->title = new_title;

  if (priv->title_label != NULL) {
    gtk_label_set_label (GTK_LABEL (priv->title_label), priv->title);
    gtk_widget_queue_resize (GTK_WIDGET (self));
  }

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
}

/**
 * hdy_header_bar_get_title: (attributes org.gtk.Method.get_property=title)
 * @self: a header bar
 *
 * Retrieves the title of the header.
 *
 * Returns: (nullable): the title of the header.
 *
 * Since: 1.0
 */
const gchar *
hdy_header_bar_get_title (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL);

  return priv->title;
}

/**
 * hdy_header_bar_set_subtitle: (attributes org.gtk.Method.set_property=subtitle)
 * @self: a header bar
 * @subtitle: (nullable): a subtitle
 *
 * Sets the subtitle of the header bar.
 *
 * The title should give a user an additional detail to help them identify the
 * current view.
 *
 * Note that [class@HeaderBar] by default reserves room for the subtitle, even
 * if none is currently set. If this is not desired, set the
 * [property@HeaderBar:has-subtitle] property to `FALSE`.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_subtitle (HdyHeaderBar *self,
                             const gchar  *subtitle)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);
  gchar *new_subtitle;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  new_subtitle = g_strdup (subtitle);
  g_free (priv->subtitle);
  priv->subtitle = new_subtitle;

  if (priv->subtitle_label != NULL) {
    gtk_label_set_label (GTK_LABEL (priv->subtitle_label), priv->subtitle);
    gtk_widget_set_visible (priv->subtitle_label, priv->subtitle && priv->subtitle[0]);
    gtk_widget_queue_resize (GTK_WIDGET (self));
  }

  gtk_widget_set_visible (priv->subtitle_sizing_label, priv->has_subtitle || (priv->subtitle && priv->subtitle[0]));

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUBTITLE]);
}

/**
 * hdy_header_bar_get_subtitle: (attributes org.gtk.Method.get_property=subtitle)
 * @self: a header bar
 *
 * Gets the subtitle of the header.
 *
 * Returns: (nullable): the subtitle of the header
 *
 * Since: 1.0
 */
const gchar *
hdy_header_bar_get_subtitle (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL);

  return priv->subtitle;
}

/**
 * hdy_header_bar_set_custom_title: (attributes org.gtk.Method.set_property=custom-title)
 * @self: a header bar
 * @title_widget: (nullable): a custom widget to use for a title
 *
 * Sets a custom title for the header bar.
 *
 * The title should help a user identify the current view. This supersedes any
 * title set by [method@HeaderBar.set_title] or [method@HeaderBar.set_subtitle].
 * To achieve the same style as the builtin title and subtitle, use the `.title`
 * and `.subtitle` style classes.
 *
 * You should set the custom title to `NULL`, for the header title label to be
 * visible again.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_custom_title (HdyHeaderBar *self,
                                 GtkWidget    *title_widget)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_if_fail (HDY_IS_HEADER_BAR (self));
  if (title_widget)
    g_return_if_fail (GTK_IS_WIDGET (title_widget));

  /* No need to do anything if the custom widget stays the same */
  if (priv->custom_title == title_widget)
    return;

  if (priv->custom_title) {
    GtkWidget *custom = priv->custom_title;

    priv->custom_title = NULL;
    gtk_widget_unparent (custom);
  }

  if (title_widget != NULL) {
    priv->custom_title = title_widget;

    gtk_widget_set_parent (priv->custom_title, GTK_WIDGET (self));

    if (priv->label_box != NULL) {
      GtkWidget *label_box = priv->label_box;

      priv->label_box = NULL;
      priv->title_label = NULL;
      priv->subtitle_label = NULL;
      gtk_widget_unparent (label_box);
    }
  } else {
    if (priv->label_box == NULL)
      construct_label_box (self);
  }

  gtk_widget_queue_resize (GTK_WIDGET (self));

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_TITLE]);
}

/**
 * hdy_header_bar_get_custom_title: (attributes org.gtk.Method.get_property=custom-title)
 * @self: a header bar
 *
 * Retrieves the custom title widget of the header.
 *
 * Returns: (nullable) (transfer none): the custom title widget of the header
 *
 * Since: 1.0
 */
GtkWidget *
hdy_header_bar_get_custom_title (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL);

  return priv->custom_title;
}

/**
 * hdy_header_bar_get_show_close_button: (attributes org.gtk.Method.get_property=show-close-button)
 * @self: a header bar
 *
 * Gets whether this header bar shows the standard window decorations.
 *
 * Returns: whether decorations are shown
 *
 * Since: 1.0
 */
gboolean
hdy_header_bar_get_show_close_button (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv;

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE);

  priv = hdy_header_bar_get_instance_private (self);

  return priv->shows_wm_decorations;
}

/**
 * hdy_header_bar_set_show_close_button: (attributes org.gtk.Method.set_property=show-close-button)
 * @self: a header bar
 * @setting: `TRUE` to show standard window decorations
 *
 * Sets whether this header bar shows the standard window decorations.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_show_close_button (HdyHeaderBar *self,
                                      gboolean      setting)
{
  HdyHeaderBarPrivate *priv;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  priv = hdy_header_bar_get_instance_private (self);

  setting = setting != FALSE;

  if (priv->shows_wm_decorations == setting)
    return;

  priv->shows_wm_decorations = setting;
  hdy_header_bar_update_window_buttons (self);
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_CLOSE_BUTTON]);
}

/**
 * hdy_header_bar_set_has_subtitle: (attributes org.gtk.Method.set_property=has-subtitle)
 * @self: a header bar
 * @setting: `TRUE` to reserve space for a subtitle
 *
 * Sets whether space is reserved for a subtitle, even if none is currently set.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_has_subtitle (HdyHeaderBar *self,
                                 gboolean      setting)
{
  HdyHeaderBarPrivate *priv;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  priv = hdy_header_bar_get_instance_private (self);

  setting = setting != FALSE;

  if (priv->has_subtitle == setting)
    return;

  priv->has_subtitle = setting;
  gtk_widget_set_visible (priv->subtitle_sizing_label, setting || (priv->subtitle && priv->subtitle[0]));

  gtk_widget_queue_resize (GTK_WIDGET (self));

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HAS_SUBTITLE]);
}

/**
 * hdy_header_bar_get_has_subtitle: (attributes org.gtk.Method.get_property=has-subtitle)
 * @self: a header bar
 *
 * Gets whether space is reserved for a subtitle, regardless if one is currently
 * set or not.
 *
 * Returns: `TRUE` if the header bar reserves space for a subtitle
 *
 * Since: 1.0
 */
gboolean
hdy_header_bar_get_has_subtitle (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv;

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE);

  priv = hdy_header_bar_get_instance_private (self);

  return priv->has_subtitle;
}

/**
 * hdy_header_bar_set_decoration_layout: (attributes org.gtk.Method.set_property=decoration-layout)
 * @self: a header bar
 * @layout: (nullable): a decoration layout
 *
 * Sets the decoration layout for this header bar.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_decoration_layout (HdyHeaderBar *self,
                                      const gchar  *layout)
{
  HdyHeaderBarPrivate *priv;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  priv = hdy_header_bar_get_instance_private (self);

  g_clear_pointer (&priv->decoration_layout, g_free);
  priv->decoration_layout = g_strdup (layout);
  priv->decoration_layout_set = (layout != NULL);

  hdy_header_bar_update_window_buttons (self);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DECORATION_LAYOUT]);
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DECORATION_LAYOUT_SET]);
}

/**
 * hdy_header_bar_get_decoration_layout: (attributes org.gtk.Method.get_property=decoration-layout)
 * @self: a header bar
 *
 * Gets the decoration layout.
 *
 * Returns: the decoration layout
 *
 * Since: 1.0
 */
const gchar *
hdy_header_bar_get_decoration_layout (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv;

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), NULL);

  priv = hdy_header_bar_get_instance_private (self);

  return priv->decoration_layout;
}

/**
 * hdy_header_bar_get_centering_policy: (attributes org.gtk.Method.get_property=centering-policy)
 * @self: a header bar
 *
 * Gets the policy @self follows to horizontally align its center widget.
 *
 * Returns: the centering policy
 *
 * Since: 1.0
 */
HdyCenteringPolicy
hdy_header_bar_get_centering_policy (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), HDY_CENTERING_POLICY_LOOSE);

  return priv->centering_policy;
}

/**
 * hdy_header_bar_set_centering_policy: (attributes org.gtk.Method.set_property=centering-policy)
 * @self: a header bar
 * @centering_policy: the centering policy
 *
 * Sets the policy @self must follow to horizontally align its center widget.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_centering_policy (HdyHeaderBar       *self,
                                     HdyCenteringPolicy  centering_policy)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  if (priv->centering_policy == centering_policy)
    return;

  priv->centering_policy = centering_policy;
  if (priv->interpolate_size)
    hdy_header_bar_start_transition (self, priv->transition_duration);
  else
    gtk_widget_queue_resize (GTK_WIDGET (self));
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CENTERING_POLICY]);
}

/**
 * hdy_header_bar_get_transition_duration: (attributes org.gtk.Method.get_property=transition-duration)
 * @self: a header bar
 *
 * Gets the amount of time that transitions between pages will take.
 *
 * Returns: the transition duration, in milliseconds
 *
 * Since: 1.0
 */
guint
hdy_header_bar_get_transition_duration (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), 0);

  return priv->transition_duration;
}

/**
 * hdy_header_bar_set_transition_duration: (attributes org.gtk.Method.set_property=transition-duration)
 * @self: a header bar
 * @duration: the new duration, in milliseconds
 *
 * Sets the duration that transitions between pages will take.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_transition_duration (HdyHeaderBar *self,
                                         guint          duration)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  if (priv->transition_duration == duration)
    return;

  priv->transition_duration = duration;
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_DURATION]);
}

/**
 * hdy_header_bar_get_transition_running: (attributes org.gtk.Method.get_property=transition-running)
 * @self: a header bar
 *
 * Gets whether the @self is currently in a transition from one page to another.
 *
 * Returns: whether the transition is currently running
 *
 * Since: 1.0
 */
gboolean
hdy_header_bar_get_transition_running (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv = hdy_header_bar_get_instance_private (self);

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE);

  return (priv->tick_id != 0);
}

/**
 * hdy_header_bar_get_interpolate_size: (attributes org.gtk.Method.get_property=interpolate-size)
 * @self: a header bar
 *
 * Gets whether @self should interpolate its size on visible child change.
 *
 * Returns: whether @self interpolates its size on visible child change
 *
 * Since: 1.0
 */
gboolean
hdy_header_bar_get_interpolate_size (HdyHeaderBar *self)
{
  HdyHeaderBarPrivate *priv;

  g_return_val_if_fail (HDY_IS_HEADER_BAR (self), FALSE);

  priv = hdy_header_bar_get_instance_private (self);

  return priv->interpolate_size;
}

/**
 * hdy_header_bar_set_interpolate_size: (attributes org.gtk.Method.set_property=interpolate-size)
 * @self: a header bar
 * @interpolate_size: `TRUE` to interpolate the size
 *
 * Sets whether @self should interpolate its size on visible child change.
 *
 * Since: 1.0
 */
void
hdy_header_bar_set_interpolate_size (HdyHeaderBar *self,
                                      gboolean       interpolate_size)
{
  HdyHeaderBarPrivate *priv;

  g_return_if_fail (HDY_IS_HEADER_BAR (self));

  priv = hdy_header_bar_get_instance_private (self);

  interpolate_size = !!interpolate_size;

  if (priv->interpolate_size == interpolate_size)
    return;

  priv->interpolate_size = interpolate_size;
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]);
}
