Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  gtkstack.c

  Sprache: C
 

/*
 * Copyright (c) 2013 Red Hat, Inc.
 *
 * 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
 *
 * Author: Alexander Larsson <alexl@redhat.com>
 *
 */


#include "config.h"

#include <gtk/gtk.h>
#include "gtkstack.h"
#include "gtkenums.h"
#include "gtkaccessibleprivate.h"
#include "gtkatcontextprivate.h"
#include "gtkprivate.h"
#include "gtkprogresstrackerprivate.h"
#include "gtksettingsprivate.h"
#include "gtksnapshot.h"
#include "gtkwidgetprivate.h"
#include "gtksingleselection.h"
#include "gtklistlistmodelprivate.h"
#include <math.h>
#include <string.h>

/**
 * GtkStack:
 *
 * Shows one of its children at a time.
 *
 * <picture>
 *   <source srcset="stack-dark.png" media="(prefers-color-scheme: dark)">
 *   <img alt="An example GtkStack" src="stack.png">
 * </picture>
 *
 * In contrast to `GtkNotebook`, `GtkStack` does not provide a means
 * for users to change the visible child. Instead, a separate widget
 * such as [class@Gtk.StackSwitcher] or [class@Gtk.StackSidebar] can
 * be used with `GtkStack` to provide this functionality.
 *
 * Transitions between pages can be animated as slides or fades. This
 * can be controlled with [method@Gtk.Stack.set_transition_type].
 *
 * These animations respect the [property@Gtk.Settings:gtk-enable-animations]
 * and [property@Gtk.Settings:gtk-interface-reduced-motion] settings.
 *
 * `GtkStack` maintains a [class@Gtk.StackPage] object for each added
 * child, which holds additional per-child properties. You
 * obtain the `GtkStackPage` for a child with [method@Gtk.Stack.get_page]
 * and you can obtain a `GtkSelectionModel` containing all the pages
 * with [method@Gtk.Stack.get_pages].
 *
 * # GtkStack as GtkBuildable
 *
 * To set child-specific properties in a .ui file, create `GtkStackPage`
 * objects explicitly, and set the child widget as a property on it:
 *
 * ```xml
 *   <object class="GtkStack" id="stack">
 *     <child>
 *       <object class="GtkStackPage">
 *         <property name="name">page1</property>
 *         <property name="title">In the beginning</property>
 *         <property name="child">
 *           <object class="GtkLabel">
 *             <property name="label">It was dark</property>
 *           </object>
 *         </property>
 *       </object>
 *     </child>
 * ```
 *
 * # CSS nodes
 *
 * `GtkStack` has a single CSS node named stack.
 *
 * # Accessibility
 *
 * `GtkStack` uses the [enum@Gtk.AccessibleRole.tab_panel] role for the stack
 * pages, which are the accessible parent objects of the child widgets.
 */


/**
 * GtkStackTransitionType:
 * @GTK_STACK_TRANSITION_TYPE_NONE: No transition
 * @GTK_STACK_TRANSITION_TYPE_CROSSFADE: A cross-fade
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT: Slide from left to right
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT: Slide from right to left
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP: Slide from bottom up
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN: Slide from top down
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT: Slide from left or right according to the children order
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN: Slide from top down or bottom up according to the order
 * @GTK_STACK_TRANSITION_TYPE_OVER_UP: Cover the old page by sliding up
 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN: Cover the old page by sliding down
 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT: Cover the old page by sliding to the left
 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT: Cover the old page by sliding to the right
 * @GTK_STACK_TRANSITION_TYPE_UNDER_UP: Uncover the new page by sliding up
 * @GTK_STACK_TRANSITION_TYPE_UNDER_DOWN: Uncover the new page by sliding down
 * @GTK_STACK_TRANSITION_TYPE_UNDER_LEFT: Uncover the new page by sliding to the left
 * @GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT: Uncover the new page by sliding to the right
 * @GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN: Cover the old page sliding up or uncover the new page sliding down, according to order
 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP: Cover the old page sliding down or uncover the new page sliding up, according to order
 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT: Cover the old page sliding left or uncover the new page sliding right, according to order
 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT: Cover the old page sliding right or uncover the new page sliding left, according to order
 * @GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT: Pretend the pages are sides of a cube and rotate that cube to the left
 * @GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT: Pretend the pages are sides of a cube and rotate that cube to the right
 * @GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT: Pretend the pages are sides of a cube and rotate that cube to the left or right according to the children order
 *
 * Possible transitions between pages in a `GtkStack` widget.
 *
 * New values may be added to this enumeration over time.
 */


/**
 * GtkStackPage:
 *
 * An auxiliary class used by `GtkStack`.
 */


/* TODO:
 *  filter events out events to the last_child widget during transitions
 */


struct _GtkStack {
  GtkWidget parent_instance;
};

typedef struct _GtkStackClass GtkStackClass;
struct _GtkStackClass {
  GtkWidgetClass parent_class;
};

typedef struct {
  GPtrArray *children;

  GtkStackPage *visible_child;

  gboolean homogeneous[2];

  GtkStackTransitionType transition_type;
  guint transition_duration;

  GtkStackPage *last_visible_child;
  guint tick_id;
  GtkProgressTracker tracker;
  gboolean first_frame_skipped;

  int last_visible_widget_width;
  int last_visible_widget_height;

  gboolean interpolate_size;

  GtkStackTransitionType active_transition_type;

  GtkSelectionModel *pages;

} GtkStackPrivate;

static void gtk_stack_buildable_interface_init (GtkBuildableIface *iface);
static void gtk_stack_accessible_init (GtkAccessibleInterface *iface);

G_DEFINE_TYPE_WITH_CODE (GtkStack, gtk_stack, GTK_TYPE_WIDGET,
                         G_ADD_PRIVATE (GtkStack)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
                                                gtk_stack_accessible_init)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_stack_buildable_interface_init))
enum  {
  PROP_0,
  PROP_HHOMOGENEOUS,
  PROP_VHOMOGENEOUS,
  PROP_VISIBLE_CHILD,
  PROP_VISIBLE_CHILD_NAME,
  PROP_TRANSITION_DURATION,
  PROP_TRANSITION_TYPE,
  PROP_TRANSITION_RUNNING,
  PROP_INTERPOLATE_SIZE,
  PROP_PAGES,
  LAST_PROP
};

enum
{
  CHILD_PROP_0,
  CHILD_PROP_CHILD,
  CHILD_PROP_NAME,
  CHILD_PROP_TITLE,
  CHILD_PROP_ICON_NAME,
  CHILD_PROP_NEEDS_ATTENTION,
  CHILD_PROP_VISIBLE,
  CHILD_PROP_USE_UNDERLINE,
  /* GtkAccessible */
  PROP_ACCESSIBLE_ROLE,
  LAST_CHILD_PROP,
};

struct _GtkStackPage
{
  GObject parent_instance;

  GtkWidget *widget;
  char *name;
  char *title;
  char *icon_name;
  GtkWidget *last_focus;

  GtkStackPage *next_page;

  GtkATContext *at_context;

  guint needs_attention : 1;
  guint visible         : 1;
  guint use_underline   : 1;
  guint in_destruction  : 1;
};

typedef struct _GtkStackPageClass GtkStackPageClass;
struct _GtkStackPageClass
{
  GObjectClass parent_class;
};

static GParamSpec *stack_props[LAST_PROP] = { NULL, };
static GParamSpec *stack_page_props[LAST_CHILD_PROP] = { NULL, };

static GtkATContext *
gtk_stack_page_accessible_get_at_context (GtkAccessible *accessible)
{
  GtkStackPage *page = GTK_STACK_PAGE (accessible);

  if (page->in_destruction)
    {
      GTK_DEBUG (A11Y, "ATContext for “%s” [%p] accessed during destruction",
                       G_OBJECT_TYPE_NAME (accessible),
                       accessible);
      return NULL;
    }

  if (page->at_context == NULL)
    {
      GtkAccessibleRole role = GTK_ACCESSIBLE_ROLE_TAB_PANEL;
      GdkDisplay *display;

      if (page->widget != NULL)
        display = gtk_widget_get_display (page->widget);
      else
        display = gdk_display_get_default ();

      page->at_context = gtk_at_context_create (role, accessible, display);
      if (page->at_context == NULL)
        return NULL;
    }

  return g_object_ref (page->at_context);
}

static gboolean
gtk_stack_page_accessible_get_platform_state (GtkAccessible              *self,
                                              GtkAccessiblePlatformState  state)
{
  return FALSE;
}

static GtkAccessible *
gtk_stack_page_accessible_get_accessible_parent (GtkAccessible *accessible)
{
  GtkStackPage *page = GTK_STACK_PAGE (accessible);
  GtkWidget *parent;

  if (page->widget == NULL)
    return NULL;

  parent = _gtk_widget_get_parent (page->widget);

  return GTK_ACCESSIBLE (g_object_ref (parent));
}

static GtkAccessible *
gtk_stack_page_accessible_get_first_accessible_child (GtkAccessible *accessible)
{
  GtkStackPage *page = GTK_STACK_PAGE (accessible);

  if (page->widget == NULL)
    return NULL;

  return GTK_ACCESSIBLE (g_object_ref (page->widget));
}

static GtkAccessible *
gtk_stack_page_accessible_get_next_accessible_sibling (GtkAccessible *accessible)
{
  GtkStackPage *page = GTK_STACK_PAGE (accessible);

  if (page->next_page == NULL)
    return NULL;

  return GTK_ACCESSIBLE (g_object_ref (page->next_page));
}

static gboolean
gtk_stack_page_accessible_get_bounds (GtkAccessible *accessible,
                                      int           *x,
                                      int           *y,
                                      int           *width,
                                      int           *height)
{
  GtkStackPage *page = GTK_STACK_PAGE (accessible);
  if (page->widget != NULL)
    return gtk_accessible_get_bounds (GTK_ACCESSIBLE (page->widget), x, y, width, height);
  else
    return FALSE;
}

static void
gtk_stack_page_accessible_init (GtkAccessibleInterface *iface)
{
  iface->get_at_context = gtk_stack_page_accessible_get_at_context;
  iface->get_platform_state = gtk_stack_page_accessible_get_platform_state;
  iface->get_accessible_parent = gtk_stack_page_accessible_get_accessible_parent;
  iface->get_first_accessible_child = gtk_stack_page_accessible_get_first_accessible_child;
  iface->get_next_accessible_sibling = gtk_stack_page_accessible_get_next_accessible_sibling;
  iface->get_bounds = gtk_stack_page_accessible_get_bounds;
}

G_DEFINE_TYPE_WITH_CODE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
                                                gtk_stack_page_accessible_init))

static void
gtk_stack_page_init (GtkStackPage *page)
{
  page->visible = TRUE;
}

static void
gtk_stack_page_finalize (GObject *object)
{
  GtkStackPage *page = GTK_STACK_PAGE (object);

  g_clear_object (&page->widget);
  g_free (page->name);
  g_free (page->title);
  g_free (page->icon_name);

  if (page->last_focus)
    g_object_remove_weak_pointer (G_OBJECT (page->last_focus),
                                  (gpointer *)&page->last_focus);

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

static void
gtk_stack_page_dispose (GObject *object)
{
  GtkStackPage *page = GTK_STACK_PAGE (object);

  page->in_destruction = TRUE;

  g_clear_object (&page->at_context);

  G_OBJECT_CLASS (gtk_stack_page_parent_class)->dispose (object);
}

static void
gtk_stack_page_get_property (GObject      *object,
                             guint         property_id,
                             GValue       *value,
                             GParamSpec   *pspec)
{
  GtkStackPage *info = GTK_STACK_PAGE (object);

  switch (property_id)
    {
    case CHILD_PROP_CHILD:
      g_value_set_object (value, info->widget);
      break;

    case CHILD_PROP_NAME:
      g_value_set_string (value, gtk_stack_page_get_name (info));
      break;

    case CHILD_PROP_TITLE:
      g_value_set_string (value, gtk_stack_page_get_title (info));
      break;

    case CHILD_PROP_ICON_NAME:
      g_value_set_string (value, gtk_stack_page_get_icon_name (info));
      break;

    case CHILD_PROP_NEEDS_ATTENTION:
      g_value_set_boolean (value, gtk_stack_page_get_needs_attention (info));
      break;

    case CHILD_PROP_VISIBLE:
      g_value_set_boolean (value, gtk_stack_page_get_visible (info));
      break;

    case CHILD_PROP_USE_UNDERLINE:
      g_value_set_boolean (value, gtk_stack_page_get_use_underline (info));
      break;

    case PROP_ACCESSIBLE_ROLE:
      g_value_set_enum (value, GTK_ACCESSIBLE_ROLE_TAB_PANEL);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_page_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GtkStackPage *info = GTK_STACK_PAGE (object);

  switch (property_id)
    {
    case CHILD_PROP_CHILD:
      g_set_object (&info->widget, g_value_get_object (value));
      gtk_accessible_set_accessible_parent (GTK_ACCESSIBLE (info->widget),
                                            GTK_ACCESSIBLE (info),
                                            NULL);
      break;

    case CHILD_PROP_NAME:
      gtk_stack_page_set_name (info, g_value_get_string (value));
      break;

    case CHILD_PROP_TITLE:
      gtk_stack_page_set_title (info, g_value_get_string (value));
      break;

    case CHILD_PROP_ICON_NAME:
      gtk_stack_page_set_icon_name (info, g_value_get_string (value));
      break;

    case CHILD_PROP_NEEDS_ATTENTION:
      gtk_stack_page_set_needs_attention (info, g_value_get_boolean (value));
      break;

    case CHILD_PROP_VISIBLE:
      gtk_stack_page_set_visible (info, g_value_get_boolean (value));
      break;

    case CHILD_PROP_USE_UNDERLINE:
      gtk_stack_page_set_use_underline (info, g_value_get_boolean (value));
      break;

    case PROP_ACCESSIBLE_ROLE:
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_page_constructed (GObject *gobject)
{
  GtkStackPage *self = GTK_STACK_PAGE (gobject);

  if (G_UNLIKELY (self->widget == NULL))
    g_error ("GtkStackPage '%s' [%p] is missing a child widget",
             self->name != NULL ? self->name : "<unnamed>",
             self);

  G_OBJECT_CLASS (gtk_stack_page_parent_class)->constructed (gobject);
}

static void
gtk_stack_page_class_init (GtkStackPageClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);
  gpointer iface;

  object_class->finalize = gtk_stack_page_finalize;
  object_class->dispose = gtk_stack_page_dispose;
  object_class->get_property = gtk_stack_page_get_property;
  object_class->set_property = gtk_stack_page_set_property;
  object_class->constructed = gtk_stack_page_constructed;

  /**
   * GtkStackPage:child:
   *
   * The child that this page is for.
   */

  stack_page_props[CHILD_PROP_CHILD] =
    g_param_spec_object ("child", NULL, NULL,
                         GTK_TYPE_WIDGET,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_CONSTRUCT_ONLY);

  /**
   * GtkStackPage:name:
   *
   * The name of the child page.
   */

  stack_page_props[CHILD_PROP_NAME] =
    g_param_spec_string ("name", NULL, NULL,
                         NULL,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME);

  /**
   * GtkStackPage:title:
   *
   * The title of the child page.
   */

  stack_page_props[CHILD_PROP_TITLE] =
    g_param_spec_string ("title", NULL, NULL,
                         NULL,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME);

  /**
   * GtkStackPage:icon-name:
   *
   * The icon name of the child page.
   */

  stack_page_props[CHILD_PROP_ICON_NAME] =
    g_param_spec_string ("icon-name", NULL, NULL,
                         NULL,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME);

  /**
   * GtkStackPage:needs-attention:
   *
   * Whether the page requires the user attention.
   *
   * This is used by the [class@Gtk.StackSwitcher] to change the
   * appearance of the corresponding button when a page needs
   * attention and it is not the current one.
   */

  stack_page_props[CHILD_PROP_NEEDS_ATTENTION] =
    g_param_spec_boolean ("needs-attention", NULL, NULL,
                         FALSE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStackPage:visible:
   *
   * Whether this page is visible.
   */

  stack_page_props[CHILD_PROP_VISIBLE] =
    g_param_spec_boolean ("visible", NULL, NULL,
                         TRUE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStackPage:use-underline:
   *
   * If set, an underline in the title indicates a mnemonic.
   */

  stack_page_props[CHILD_PROP_USE_UNDERLINE] =
    g_param_spec_boolean ("use-underline", NULL, NULL,
                         FALSE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /* GtkAccessible */
  iface = g_type_default_interface_peek (GTK_TYPE_ACCESSIBLE);
  stack_page_props[PROP_ACCESSIBLE_ROLE] =
    g_param_spec_override ("accessible-role",
                           g_object_interface_find_property (iface, "accessible-role"));

  g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_page_props);
}

#define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ())
G_DECLARE_FINAL_TYPE (GtkStackPages, gtk_stack_pages, GTK, STACK_PAGES, GObject)

struct _GtkStackPages
{
  GObject parent_instance;
  GtkStack *stack;
};

struct _GtkStackPagesClass
{
  GObjectClass parent_class;
};

enum {
  PAGES_PROP_0,
  PAGES_PROP_ITEM_TYPE,
  PAGES_PROP_N_ITEMS,

  PAGES_N_PROPS
};

static GParamSpec *pages_properties[PAGES_N_PROPS] = { NULL, };

static GType
gtk_stack_pages_get_item_type (GListModel *model)
{
  return GTK_TYPE_STACK_PAGE;
}

static guint
gtk_stack_pages_get_n_items (GListModel *model)
{
  GtkStackPages *pages = GTK_STACK_PAGES (model);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);

  return priv->children->len;
}

static gpointer
gtk_stack_pages_get_item (GListModel *model,
                          guint       position)
{
  GtkStackPages *pages = GTK_STACK_PAGES (model);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
  GtkStackPage *page;

  if (position >= priv->children->len)
    return NULL;

  page = g_ptr_array_index (priv->children, position);

  return g_object_ref (page);
}

static void
gtk_stack_pages_list_model_init (GListModelInterface *iface)
{
  iface->get_item_type = gtk_stack_pages_get_item_type;
  iface->get_n_items = gtk_stack_pages_get_n_items;
  iface->get_item = gtk_stack_pages_get_item;
}

static gboolean
gtk_stack_pages_is_selected (GtkSelectionModel *model,
                             guint              position)
{
  GtkStackPages *pages = GTK_STACK_PAGES (model);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
  GtkStackPage *page;

  if (position >= priv->children->len)
    return FALSE;

  page = g_ptr_array_index (priv->children, position);

  return page == priv->visible_child;
}

static void set_visible_child (GtkStack               *stack,
                               GtkStackPage           *child_info,
                               GtkStackTransitionType  transition_type,
                               guint                   transition_duration);

static gboolean
gtk_stack_pages_select_item (GtkSelectionModel *model,
                             guint              position,
                             gboolean           exclusive)
{
  GtkStackPages *pages = GTK_STACK_PAGES (model);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (pages->stack);
  GtkStackPage *page;

  if (position >= priv->children->len)
    return FALSE;

  page = g_ptr_array_index (priv->children, position);

  set_visible_child (pages->stack, page, priv->transition_type, priv->transition_duration);

  return TRUE;
}

static void
gtk_stack_pages_selection_model_init (GtkSelectionModelInterface *iface)
{
  iface->is_selected = gtk_stack_pages_is_selected;
  iface->select_item = gtk_stack_pages_select_item;
}

G_DEFINE_TYPE_WITH_CODE (GtkStackPages, gtk_stack_pages, G_TYPE_OBJECT,
                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_stack_pages_list_model_init)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, gtk_stack_pages_selection_model_init))

static void
gtk_stack_pages_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  GtkStackPages *self = GTK_STACK_PAGES (object);

  switch (prop_id)
    {
    case PAGES_PROP_ITEM_TYPE:
      g_value_set_gtype (value, GTK_TYPE_STACK_PAGE);
      break;

    case PAGES_PROP_N_ITEMS:
      g_value_set_uint (value, gtk_stack_pages_get_n_items (G_LIST_MODEL (self)));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gtk_stack_pages_init (GtkStackPages *pages)
{
}

static void
gtk_stack_pages_class_init (GtkStackPagesClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->get_property = gtk_stack_pages_get_property;

  pages_properties[PAGES_PROP_ITEM_TYPE] =
    g_param_spec_gtype ("item-type", NULL, NULL,
                        GTK_TYPE_STACK_PAGE,
                        G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  pages_properties[PAGES_PROP_N_ITEMS] =
    g_param_spec_uint ("n-items", NULL, NULL,
                       0, G_MAXUINT, 0,
                       G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, PAGES_N_PROPS, pages_properties);
}

static GtkStackPages *
gtk_stack_pages_new (GtkStack *stack)
{
  GtkStackPages *pages;

  pages = g_object_new (GTK_TYPE_STACK_PAGES, NULL);
  pages->stack = stack;

  return pages;
}

static GtkStackPage *gtk_stack_add_internal (GtkStack *stack,
                                             GtkWidget  *child,
                                             const char *name,
                                             const char *title);

static GtkSizeRequestMode gtk_stack_get_request_mode (GtkWidget *widget);
static void     gtk_stack_compute_expand                 (GtkWidget     *widget,
                                                          gboolean      *hexpand,
                                                          gboolean      *vexpand);
static void     gtk_stack_size_allocate                  (GtkWidget     *widget,
                                                          int            width,
                                                          int            height,
                                                          int            baseline);
static void     gtk_stack_snapshot                       (GtkWidget     *widget,
                                                          GtkSnapshot   *snapshot);
static void     gtk_stack_measure                        (GtkWidget      *widget,
                                                          GtkOrientation  orientation,
                                                          int             for_size,
                                                          int            *minimum,
                                                          int            *natural,
                                                          int            *minimum_baseline,
                                                          int            *natural_baseline);
static void     gtk_stack_dispose                        (GObject       *obj);
static void     gtk_stack_finalize                       (GObject       *obj);
static void     gtk_stack_get_property                   (GObject       *object,
                                                          guint          property_id,
                                                          GValue        *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_set_property                   (GObject       *object,
                                                          guint          property_id,
                                                          const GValue  *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_unschedule_ticks               (GtkStack      *stack);


static void     gtk_stack_add_page                       (GtkStack     *stack,
                                                          GtkStackPage *page);

static GtkBuildableIface *parent_buildable_iface;

static void
gtk_stack_buildable_add_child (GtkBuildable *buildable,
                               GtkBuilder   *builder,
                               GObject      *child,
                               const char   *type)
{
  if (GTK_IS_STACK_PAGE (child))
    gtk_stack_add_page (GTK_STACK (buildable), GTK_STACK_PAGE (child));
  else if (GTK_IS_WIDGET (child))
    gtk_stack_add_internal (GTK_STACK (buildable), GTK_WIDGET (child), NULL, NULL);
  else
    parent_buildable_iface->add_child (buildable, builder, child, type);
}

static void
gtk_stack_buildable_interface_init (GtkBuildableIface *iface)
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);

  iface->add_child = gtk_stack_buildable_add_child;
}

static GtkAccessible *
gtk_stack_accessible_get_first_accessible_child (GtkAccessible *accessible)
{
  GtkStack *stack = GTK_STACK (accessible);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkAccessible *page_accessible = NULL;

  if (priv->children->len > 0)
    page_accessible = GTK_ACCESSIBLE (g_object_ref (g_ptr_array_index (priv->children, 0)));

  return page_accessible;
}

static void
gtk_stack_accessible_init (GtkAccessibleInterface *iface)
{
  iface->get_first_accessible_child = gtk_stack_accessible_get_first_accessible_child;
}

static void stack_remove (GtkStack  *stack,
                          GtkWidget *child,
                          gboolean   in_dispose);

static void
gtk_stack_dispose (GObject *obj)
{
  GtkStack *stack = GTK_STACK (obj);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkWidget *child;
  guint n_pages = priv->children->len;

  while ((child = gtk_widget_get_first_child (GTK_WIDGET (stack))))
    stack_remove (stack, child, TRUE);

  if (priv->pages && n_pages > 0)
    {
      g_list_model_items_changed (G_LIST_MODEL (priv->pages), 0, n_pages, 0);
      g_object_notify_by_pspec (G_OBJECT (priv->pages), pages_properties[PAGES_PROP_N_ITEMS]);
    }

  G_OBJECT_CLASS (gtk_stack_parent_class)->dispose (obj);
}

static void
gtk_stack_finalize (GObject *obj)
{
  GtkStack *stack = GTK_STACK (obj);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (priv->pages)
    g_object_remove_weak_pointer (G_OBJECT (priv->pages), (gpointer *)&priv->pages);

  gtk_stack_unschedule_ticks (stack);

  g_ptr_array_free (priv->children, TRUE);

  G_OBJECT_CLASS (gtk_stack_parent_class)->finalize (obj);
}

static void
gtk_stack_get_property (GObject   *object,
                        guint       property_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
  GtkStack *stack = GTK_STACK (object);

  switch (property_id)
    {
    case PROP_HHOMOGENEOUS:
      g_value_set_boolean (value, gtk_stack_get_hhomogeneous (stack));
      break;
    case PROP_VHOMOGENEOUS:
      g_value_set_boolean (value, gtk_stack_get_vhomogeneous (stack));
      break;
    case PROP_VISIBLE_CHILD:
      g_value_set_object (value, gtk_stack_get_visible_child (stack));
      break;
    case PROP_VISIBLE_CHILD_NAME:
      g_value_set_string (value, gtk_stack_get_visible_child_name (stack));
      break;
    case PROP_TRANSITION_DURATION:
      g_value_set_uint (value, gtk_stack_get_transition_duration (stack));
      break;
    case PROP_TRANSITION_TYPE:
      g_value_set_enum (value, gtk_stack_get_transition_type (stack));
      break;
    case PROP_TRANSITION_RUNNING:
      g_value_set_boolean (value, gtk_stack_get_transition_running (stack));
      break;
    case PROP_INTERPOLATE_SIZE:
      g_value_set_boolean (value, gtk_stack_get_interpolate_size (stack));
      break;
    case PROP_PAGES:
      g_value_take_object (value, gtk_stack_get_pages (stack));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_set_property (GObject     *object,
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
  GtkStack *stack = GTK_STACK (object);

  switch (property_id)
    {
    case PROP_HHOMOGENEOUS:
      gtk_stack_set_hhomogeneous (stack, g_value_get_boolean (value));
      break;
    case PROP_VHOMOGENEOUS:
      gtk_stack_set_vhomogeneous (stack, g_value_get_boolean (value));
      break;
    case PROP_VISIBLE_CHILD:
      gtk_stack_set_visible_child (stack, g_value_get_object (value));
      break;
    case PROP_VISIBLE_CHILD_NAME:
      gtk_stack_set_visible_child_name (stack, g_value_get_string (value));
      break;
    case PROP_TRANSITION_DURATION:
      gtk_stack_set_transition_duration (stack, g_value_get_uint (value));
      break;
    case PROP_TRANSITION_TYPE:
      gtk_stack_set_transition_type (stack, g_value_get_enum (value));
      break;
    case PROP_INTERPOLATE_SIZE:
      gtk_stack_set_interpolate_size (stack, g_value_get_boolean (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_class_init (GtkStackClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->get_property = gtk_stack_get_property;
  object_class->set_property = gtk_stack_set_property;
  object_class->dispose = gtk_stack_dispose;
  object_class->finalize = gtk_stack_finalize;

  widget_class->size_allocate = gtk_stack_size_allocate;
  widget_class->snapshot = gtk_stack_snapshot;
  widget_class->measure = gtk_stack_measure;
  widget_class->compute_expand = gtk_stack_compute_expand;
  widget_class->get_request_mode = gtk_stack_get_request_mode;

  /**
   * GtkStack:hhomogeneous:
   *
   * %TRUE if the stack allocates the same width for all children.
   */

  stack_props[PROP_HHOMOGENEOUS] =
      g_param_spec_boolean ("hhomogeneous", NULL, NULL,
                            TRUE,
                            G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:vhomogeneous:
   *
   * %TRUE if the stack allocates the same height for all children.
   */

  stack_props[PROP_VHOMOGENEOUS] =
      g_param_spec_boolean ("vhomogeneous", NULL, NULL,
                            TRUE,
                            G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:visible-child:
   *
   * The widget currently visible in the stack.
   */

  stack_props[PROP_VISIBLE_CHILD] =
      g_param_spec_object ("visible-child", NULL, NULL,
                           GTK_TYPE_WIDGET,
                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:visible-child-name:
   *
   * The name of the widget currently visible in the stack.
   */

  stack_props[PROP_VISIBLE_CHILD_NAME] =
      g_param_spec_string ("visible-child-name", NULL, NULL,
                           NULL,
                           G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:transition-duration:
   *
   * The animation duration, in milliseconds.
   */

  stack_props[PROP_TRANSITION_DURATION] =
      g_param_spec_uint ("transition-duration", NULL, NULL,
                         0, G_MAXUINT, 200,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:transition-type:
   *
   * The type of animation used to transition.
   */

  stack_props[PROP_TRANSITION_TYPE] =
      g_param_spec_enum ("transition-type", NULL, NULL,
                         GTK_TYPE_STACK_TRANSITION_TYPE, GTK_STACK_TRANSITION_TYPE_NONE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:transition-running:
   *
   * Whether or not the transition is currently running.
   */

  stack_props[PROP_TRANSITION_RUNNING] =
      g_param_spec_boolean ("transition-running", NULL, NULL,
                            FALSE,
                            G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  /**
   * GtkStack:interpolate-size:
   *
   * Whether or not the size should smoothly change during the transition.
   */

  stack_props[PROP_INTERPOLATE_SIZE] =
      g_param_spec_boolean ("interpolate-size", NULL, NULL,
                            FALSE,
                            G_PARAM_READWRITE | G_PARAM_STATIC_NAME | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:pages:
   *
   * A selection model with the stack pages.
   */

  stack_props[PROP_PAGES] =
      g_param_spec_object ("pages", NULL, NULL,
                           GTK_TYPE_SELECTION_MODEL,
                           G_PARAM_READABLE | G_PARAM_STATIC_NAME);

  g_object_class_install_properties (object_class, LAST_PROP, stack_props);

  gtk_widget_class_set_css_name (widget_class, I_("stack"));
  gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP);
}

/**
 * gtk_stack_new:
 *
 * Creates a new `GtkStack`.
 *
 * Returns: a new `GtkStack`
 */

GtkWidget *
gtk_stack_new (void)
{
  return g_object_new (GTK_TYPE_STACK, NULL);
}

static GtkStackPage *
find_child_info_for_widget (GtkStack  *stack,
                            GtkWidget *child)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *info;
  guint idx;

  for (idx = 0; idx < priv->children->len; idx++)
    {
      info = g_ptr_array_index (priv->children, idx);
      if (info->widget == child)
        return info;
    }

  return NULL;
}

static inline gboolean
is_left_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT);
}

static inline gboolean
is_right_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
}

static inline gboolean
is_up_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP);
}

static inline gboolean
is_down_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN);
}

/* Transitions that cause the bin window to move */
static inline gboolean
is_window_moving_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
}

/* Transitions that change direction depending on the relative order of the
old and new child */

static inline gboolean
is_direction_dependent_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT);
}

/* Returns simple transition type for a direction dependent transition, given
whether the new child (the one being switched to) is first in the stacking order
(added earlier). */

static inline GtkStackTransitionType
get_simple_transition_type (gboolean               new_child_first,
                            GtkStackTransitionType transition_type)
{
  switch (transition_type)
    {
    case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT : GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
    case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT : GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT;
    case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN : GTK_STACK_TRANSITION_TYPE_SLIDE_UP;
    case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_DOWN : GTK_STACK_TRANSITION_TYPE_OVER_UP;
    case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_UP : GTK_STACK_TRANSITION_TYPE_OVER_DOWN;
    case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT : GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
    case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_LEFT : GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
    case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
    case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
    case GTK_STACK_TRANSITION_TYPE_OVER_UP:
    case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
    case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
    case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
    case GTK_STACK_TRANSITION_TYPE_NONE:
    case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
    case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
    case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
    case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
    case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
    case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
    case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
    case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
    case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
    default:
      return transition_type;
    }
}

static int
get_bin_window_x (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  int width;
  int x = 0;

  width = gtk_widget_get_width (GTK_WIDGET (stack));

  if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
    {
      if (is_left_transition (priv->active_transition_type))
        x = width * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      if (is_right_transition (priv->active_transition_type))
        x = -width * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
    }

  return x;
}

static int
get_bin_window_y (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  int height;
  int y = 0;

  height = gtk_widget_get_height (GTK_WIDGET (stack));

  if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
    {
      if (is_up_transition (priv->active_transition_type))
        y = height * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      if (is_down_transition(priv->active_transition_type))
        y = -height * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
    }

  return y;
}

static void
gtk_stack_progress_updated (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (!priv->homogeneous[GTK_ORIENTATION_VERTICAL] || !priv->homogeneous[GTK_ORIENTATION_HORIZONTAL])
    gtk_widget_queue_resize (GTK_WIDGET (stack));
  else if (is_window_moving_transition (priv->active_transition_type))
    gtk_widget_queue_allocate (GTK_WIDGET (stack));
  else
    gtk_widget_queue_draw (GTK_WIDGET (stack));

  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER &&
      priv->last_visible_child != NULL)
    {
      gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
      priv->last_visible_child = NULL;
    }
}

static gboolean
gtk_stack_transition_cb (GtkWidget     *widget,
                         GdkFrameClock *frame_clock,
                         gpointer       user_data)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  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 animation early if not mapped anymore */
  if (!gtk_widget_get_mapped (widget))
    gtk_progress_tracker_finish (&priv->tracker);

  gtk_stack_progress_updated (GTK_STACK (widget));

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

      return G_SOURCE_REMOVE;
    }

  return G_SOURCE_CONTINUE;
}

static void
gtk_stack_schedule_ticks (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (priv->tick_id == 0)
    {
      priv->tick_id =
        gtk_widget_add_tick_callback (GTK_WIDGET (stack), gtk_stack_transition_cb, stack, NULL);
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_TRANSITION_RUNNING]);
    }
}

static void
gtk_stack_unschedule_ticks (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (priv->tick_id != 0)
    {
      gtk_widget_remove_tick_callback (GTK_WIDGET (stack), priv->tick_id);
      priv->tick_id = 0;
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_TRANSITION_RUNNING]);
      gtk_widget_set_overflow (GTK_WIDGET (stack), GTK_OVERFLOW_VISIBLE);
    }
}

static GtkStackTransitionType
get_effective_transition_type (GtkStack               *stack,
                               GtkStackTransitionType  transition_type)
{
  if (transition_type != GTK_STACK_TRANSITION_TYPE_NONE)
    {
      GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
      GtkReducedMotion reduced_motion;

      g_object_get (gtk_widget_get_settings (GTK_WIDGET (stack)),
                    "gtk-interface-reduced-motion", &reduced_motion,
                    NULL);

      if (reduced_motion == GTK_REDUCED_MOTION_REDUCE)
        {
          if (priv->homogeneous[GTK_ORIENTATION_VERTICAL] &&
              priv->homogeneous[GTK_ORIENTATION_HORIZONTAL])
            return GTK_STACK_TRANSITION_TYPE_CROSSFADE;
          else
            return GTK_STACK_TRANSITION_TYPE_NONE;
        }
    }

  if (_gtk_widget_get_direction (GTK_WIDGET (stack)) == GTK_TEXT_DIR_RTL)
    {
      switch (transition_type)
        {
        case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
          return GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
        case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
          return GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT;
        case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
          return GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
        case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
          return GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_UNDER_LEFT;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
        case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
        case GTK_STACK_TRANSITION_TYPE_OVER_UP:
        case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
        case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
        case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
        case GTK_STACK_TRANSITION_TYPE_NONE:
        case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
        case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
        case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
        case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
        case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
        case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
        case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
        case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
        default:
          return transition_type;
        }
    }
  else
    {
      return transition_type;
    }
}

static void
gtk_stack_start_transition (GtkStack               *stack,
                            GtkStackTransitionType  transition_type,
                            guint                   transition_duration)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkWidget *widget = GTK_WIDGET (stack);
  GtkStackTransitionType effective_transition_type = GTK_STACK_TRANSITION_TYPE_NONE;

  if (gtk_widget_get_mapped (widget) &&
      gtk_settings_get_enable_animations (gtk_widget_get_settings (widget)))
    effective_transition_type = get_effective_transition_type (stack, transition_type);

  if (effective_transition_type != GTK_STACK_TRANSITION_TYPE_NONE &&
      transition_duration != 0 &&
      priv->last_visible_child != NULL)
    {
      priv->active_transition_type = effective_transition_type;
      priv->first_frame_skipped = FALSE;
      gtk_stack_schedule_ticks (stack);
      gtk_progress_tracker_start (&priv->tracker,
                                  priv->transition_duration * 1000,
                                  0,
                                  1.0);
      /* We set overflow to hidden during transitions to avoid
       * input problems.
       */

      gtk_widget_set_overflow (GTK_WIDGET (stack), GTK_OVERFLOW_HIDDEN);
    }
  else
    {
      gtk_stack_unschedule_ticks (stack);
      priv->active_transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
      gtk_progress_tracker_finish (&priv->tracker);
    }

  gtk_stack_progress_updated (GTK_STACK (widget));
}

static void
set_visible_child (GtkStack               *stack,
                   GtkStackPage      *child_info,
                   GtkStackTransitionType  transition_type,
                   guint                   transition_duration)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *info;
  GtkWidget *widget = GTK_WIDGET (stack);
  guint idx;
  GtkWidget *focus;
  gboolean contains_focus = FALSE;
  guint old_pos = GTK_INVALID_LIST_POSITION;
  guint new_pos = GTK_INVALID_LIST_POSITION;

  /* if we are being destroyed, do not bother with transitions
   * and notifications
   */

  if (gtk_widget_in_destruction (widget))
    return;

  /* If none, pick first visible */
  if (child_info == NULL)
    {
      for (idx = 0; idx < priv->children->len; idx++)
        {
          info = g_ptr_array_index (priv->children, idx);
          if (gtk_widget_get_visible (info->widget))
            {
              child_info = info;
              break;
            }
        }
    }

  if (child_info == priv->visible_child)
    return;

  if (priv->pages)
    {
      guint position;
      for (idx = 0, position = 0; idx < priv->children->len; idx++, position++)
        {
          info = g_ptr_array_index (priv->children, idx);
          if (info == priv->visible_child)
            old_pos = position;
          else if (info == child_info)
            new_pos = position;
        }
    }

  if (gtk_widget_get_root (widget))
    focus = gtk_root_get_focus (gtk_widget_get_root (widget));
  else
    focus = NULL;
  if (focus &&
      priv->visible_child &&
      priv->visible_child->widget &&
      gtk_widget_is_ancestor (focus, priv->visible_child->widget))
    {
      contains_focus = TRUE;

      if (priv->visible_child->last_focus)
        g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
                                      (gpointer *)&priv->visible_child->last_focus);
      priv->visible_child->last_focus = focus;
      g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
                                 (gpointer *)&priv->visible_child->last_focus);
    }

  if (priv->last_visible_child)
    {
      gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
      gtk_accessible_update_state (GTK_ACCESSIBLE (priv->last_visible_child),
                                   GTK_ACCESSIBLE_STATE_HIDDEN, TRUE,
                                   -1);
    }
      priv->last_visible_child = NULL;

  if (priv->visible_child && priv->visible_child->widget)
    {
      if (gtk_widget_is_visible (widget))
        {
          priv->last_visible_child = priv->visible_child;
          priv->last_visible_widget_width = gtk_widget_get_width (widget);
          priv->last_visible_widget_height = gtk_widget_get_height (widget);
        }
      else
        {
          gtk_widget_set_child_visible (priv->visible_child->widget, FALSE);
        }
      gtk_accessible_update_state (GTK_ACCESSIBLE (priv->visible_child),
                                   GTK_ACCESSIBLE_STATE_HIDDEN, TRUE,
                                   -1);
    }

  priv->visible_child = child_info;

  if (child_info)
    {
      gtk_widget_set_child_visible (child_info->widget, TRUE);
      gtk_accessible_update_state (GTK_ACCESSIBLE (child_info),
                                   GTK_ACCESSIBLE_STATE_HIDDEN, FALSE,
                                   -1);

      if (contains_focus)
        {
          if (child_info->last_focus)
            gtk_widget_grab_focus (child_info->last_focus);
          else
            gtk_widget_child_focus (child_info->widget, GTK_DIR_TAB_FORWARD);
        }
    }

  if ((child_info == NULL || priv->last_visible_child == NULL) &&
      is_direction_dependent_transition (transition_type))
    {
      transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
    }
  else if (is_direction_dependent_transition (transition_type))
    {
      gboolean i_first = FALSE;
      for (idx = 0; idx < priv->children->len; idx++)
        {
   if (child_info == g_ptr_array_index (priv->children, idx))
     {
       i_first = TRUE;
       break;
     }
   if (priv->last_visible_child == g_ptr_array_index (priv->children, idx))
     break;
        }

      transition_type = get_simple_transition_type (i_first, transition_type);
    }

  if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] && priv->homogeneous[GTK_ORIENTATION_VERTICAL])
    gtk_widget_queue_allocate (widget);
  else
    gtk_widget_queue_resize (widget);

  g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_VISIBLE_CHILD]);
  g_object_notify_by_pspec (G_OBJECT (stack),
                            stack_props[PROP_VISIBLE_CHILD_NAME]);

  if (priv->pages)
    {
      if (old_pos == GTK_INVALID_LIST_POSITION && new_pos == GTK_INVALID_LIST_POSITION)
        ; /* nothing to do */
      else if (old_pos == GTK_INVALID_LIST_POSITION)
        gtk_selection_model_selection_changed (priv->pages, new_pos, 1);
      else if (new_pos == GTK_INVALID_LIST_POSITION)
        gtk_selection_model_selection_changed (priv->pages, old_pos, 1);
      else
        gtk_selection_model_selection_changed (priv->pages,
                                               MIN (old_pos, new_pos),
                                               MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1);
    }

  gtk_stack_start_transition (stack, transition_type, transition_duration);
}

static void
update_child_visible (GtkStack     *stack,
                      GtkStackPage *child_info)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  gboolean visible;

  visible = child_info->visible && gtk_widget_get_visible (child_info->widget);

  if (priv->visible_child == NULL && visible)
    set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration);
  else if (priv->visible_child == child_info && !visible)
    set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);

  if (child_info == priv->last_visible_child)
    {
      gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
      priv->last_visible_child = NULL;
    }

  gtk_accessible_update_state (GTK_ACCESSIBLE (child_info),
                               GTK_ACCESSIBLE_STATE_HIDDEN, !visible,
                               -1);
}

static void
stack_child_visibility_notify_cb (GObject    *obj,
                                  GParamSpec *pspec,
                                  gpointer    user_data)
{
  GtkStack *stack = GTK_STACK (user_data);
  GtkStackPage *child_info;

  child_info = find_child_info_for_widget (stack, GTK_WIDGET (obj));
  g_return_if_fail (child_info != NULL);

  update_child_visible (stack, child_info);
}

/**
 * gtk_stack_add_titled:
 * @stack: a `GtkStack`
 * @child: the widget to add
 * @name: (nullable): the name for @child
 * @title: a human-readable title for @child
 *
 * Adds a child to @stack.
 *
 * The child is identified by the @name. The @title
 * will be used by `GtkStackSwitcher` to represent
 * @child in a tab bar, so it should be short.
 *
 * Returns: (transfer none): the `GtkStackPage` for @child
 */

GtkStackPage *
gtk_stack_add_titled (GtkStack   *stack,
                      GtkWidget  *child,
                      const char *name,
                      const char *title)
{
  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);

  return gtk_stack_add_internal (stack, child, name, title);
}

/**
 * gtk_stack_add_child:
 * @stack: a `GtkStack`
 * @child: the widget to add
 *
 * Adds a child to @stack.
 *
 * Returns: (transfer none): the `GtkStackPage` for @child
 */

GtkStackPage *
gtk_stack_add_child (GtkStack   *stack,
                     GtkWidget  *child)
{
  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);

  return gtk_stack_add_internal (stack, child, NULL, NULL);
}

/**
 * gtk_stack_add_named:
 * @stack: a `GtkStack`
 * @child: the widget to add
 * @name: (nullable): the name for @child
 *
 * Adds a child to @stack.
 *
 * The child is identified by the @name.
 *
 * Returns: (transfer none): the `GtkStackPage` for @child
 */

GtkStackPage *
gtk_stack_add_named (GtkStack   *stack,
                     GtkWidget  *child,
                     const char *name)
{
  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);

  return gtk_stack_add_internal (stack, child, name, NULL);
}

static GtkStackPage *
gtk_stack_add_internal (GtkStack   *stack,
                        GtkWidget  *child,
                        const char *name,
                        const char *title)
{
  GtkStackPage *child_info;

  g_return_val_if_fail (child != NULL, NULL);

  child_info = g_object_new (GTK_TYPE_STACK_PAGE,
                             "child", child,
                             "name", name,
                             "title", title,
                             NULL);

  gtk_stack_add_page (stack, child_info);

  g_object_unref (child_info);

  return child_info;
}

static void
gtk_stack_add_page (GtkStack     *stack,
                    GtkStackPage *child_info)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  guint idx;

  g_return_if_fail (child_info->widget != NULL);

  if (child_info->name)
    {
      for (idx = 0; idx < priv->children->len; idx++)
        {
          GtkStackPage *info = g_ptr_array_index (priv->children, idx);
          if (info->name &&
              g_strcmp0 (info->name, child_info->name) == 0)
            {
              g_warning ("While adding page: duplicate child name in GtkStack: %s", child_info->name);
              break;
            }
        }
    }


  if (priv->children->len > 0)
    {
      GtkStackPage *prev_last = g_ptr_array_index (priv->children, priv->children->len - 1);

      prev_last->next_page = child_info;
    }
  else
    {
      child_info->next_page = NULL;
    }

  g_ptr_array_add (priv->children, g_object_ref (child_info));

  gtk_widget_set_child_visible (child_info->widget, FALSE);
  gtk_widget_set_parent (child_info->widget, GTK_WIDGET (stack));

  if (priv->pages)
    {
      g_list_model_items_changed (G_LIST_MODEL (priv->pages), priv->children->len - 101);
      g_object_notify_by_pspec (G_OBJECT (priv->pages), pages_properties[PAGES_PROP_N_ITEMS]);
    }

  g_signal_connect (child_info->widget, "notify::visible",
                    G_CALLBACK (stack_child_visibility_notify_cb), stack);

  if (priv->visible_child == NULL &&
      gtk_widget_get_visible (child_info->widget))
    set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration);

  if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] || priv->homogeneous[GTK_ORIENTATION_VERTICAL] || priv->visible_child == child_info)
    gtk_widget_queue_resize (GTK_WIDGET (stack));
}

static void
stack_remove (GtkStack  *stack,
              GtkWidget *child,
              gboolean   in_dispose)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *child_info;
  gboolean was_visible;

  child_info = find_child_info_for_widget (stack, child);
  if (child_info == NULL)
    return;

  g_signal_handlers_disconnect_by_func (child,
                                        stack_child_visibility_notify_cb,
                                        stack);

  was_visible = gtk_widget_get_visible (child);

  if (priv->visible_child == child_info)
    priv->visible_child = NULL;

  if (priv->last_visible_child == child_info)
    priv->last_visible_child = NULL;

  gtk_widget_unparent (child);

  g_clear_object (&child_info->widget);

    g_ptr_array_remove (priv->children, child_info);

  for (guint prev_idx = 0; prev_idx < priv->children->len; prev_idx++)
    {
      GtkStackPage *prev_page = g_ptr_array_index (priv->children, prev_idx);
      if (prev_page->next_page == child_info)
        {
          prev_page->next_page = child_info->next_page;
          break;
        }
    }

  g_object_unref (child_info);

  if (!in_dispose &&
      (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] || priv->homogeneous[GTK_ORIENTATION_VERTICAL]) &&
      was_visible)
    gtk_widget_queue_resize (GTK_WIDGET (stack));
}

/**
 * gtk_stack_remove:
 * @stack: a `GtkStack`
 * @child: the child to remove
 *
 * Removes a child widget from @stack.
 */

void
gtk_stack_remove (GtkStack  *stack,
                  GtkWidget *child)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  guint position;

  g_return_if_fail (GTK_IS_STACK (stack));
  g_return_if_fail (GTK_IS_WIDGET (child));
  g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (stack));

  for (position = 0; position < priv->children->len; position++)
    {
      GtkStackPage *page = g_ptr_array_index (priv->children, position);
      if (page->widget == child)
        break;
    }

  stack_remove (stack, child, FALSE);

  if (priv->pages)
    {
      g_list_model_items_changed (G_LIST_MODEL (priv->pages), position, 10);
      g_object_notify_by_pspec (G_OBJECT (priv->pages), pages_properties[PAGES_PROP_N_ITEMS]);
    }
}

/**
 * gtk_stack_get_page:
 * @stack: a `GtkStack`
 * @child: a child of @stack
 *
 * Returns the `GtkStackPage` object for @child.
 *
 * Returns: (transfer none): the `GtkStackPage` for @child
 */

GtkStackPage *
gtk_stack_get_page (GtkStack  *stack,
                    GtkWidget *child)
{
  return find_child_info_for_widget (stack, child);
}

/**
 * gtk_stack_get_child_by_name:
 * @stack: a `GtkStack`
 * @name: the name of the child to find
 *
 * Finds the child with the name given as the argument.
 *
 * Returns %NULL if there is no child with this name.
 *
 * Returns: (transfer none) (nullable): the requested child
 *   of the `GtkStack`
 */

GtkWidget *
gtk_stack_get_child_by_name (GtkStack    *stack,
                             const char *name)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *info;
  guint idx;

  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  for (idx = 0; idx < priv->children->len; idx++)
    {
      info = g_ptr_array_index (priv->children, idx);
      if (info->name && strcmp (info->name, name) == 0)
        return info->widget;
    }

  return NULL;
}

/**
 * gtk_stack_page_get_child:
 * @self: a `GtkStackPage`
 *
 * Returns the stack child to which @self belongs.
 *
 * Returns: (transfer none): the child to which @self belongs
 */

GtkWidget *
gtk_stack_page_get_child (GtkStackPage *self)
{
  return self->widget;
}

/**
 * gtk_stack_set_hhomogeneous:
 * @stack: a `GtkStack`
 * @hhomogeneous: %TRUE to make @stack horizontally homogeneous
 *
 * Sets the `GtkStack` to be horizontally homogeneous or not.
 *
 * If it is homogeneous, the `GtkStack` will request the same
 * width for all its children. If it isn't, the stack
 * may change width when a different child becomes visible.
 */

void
gtk_stack_set_hhomogeneous (GtkStack *stack,
                            gboolean  hhomogeneous)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_if_fail (GTK_IS_STACK (stack));

  hhomogeneous = !!hhomogeneous;

  if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] == hhomogeneous)
    return;

  priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] = hhomogeneous;

  if (gtk_widget_get_visible (GTK_WIDGET(stack)))
    gtk_widget_queue_resize (GTK_WIDGET (stack));

  g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_HHOMOGENEOUS]);
}

/**
 * gtk_stack_get_hhomogeneous:
 * @stack: a `GtkStack`
 *
 * Gets whether @stack is horizontally homogeneous.
 *
 * Returns: whether @stack is horizontally homogeneous.
 */

gboolean
gtk_stack_get_hhomogeneous (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);

  return priv->homogeneous[GTK_ORIENTATION_HORIZONTAL];
}

/**
 * gtk_stack_set_vhomogeneous:
 * @stack: a `GtkStack`
 * @vhomogeneous: %TRUE to make @stack vertically homogeneous
 *
 * Sets the `GtkStack` to be vertically homogeneous or not.
 *
 * If it is homogeneous, the `GtkStack` will request the same
 * height for all its children. If it isn't, the stack
 * may change height when a different child becomes visible.
 */

void
gtk_stack_set_vhomogeneous (GtkStack *stack,
                            gboolean  vhomogeneous)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_if_fail (GTK_IS_STACK (stack));

  vhomogeneous = !!vhomogeneous;

  if (priv->homogeneous[GTK_ORIENTATION_VERTICAL] == vhomogeneous)
    return;

  priv->homogeneous[GTK_ORIENTATION_VERTICAL] = vhomogeneous;

  if (gtk_widget_get_visible (GTK_WIDGET(stack)))
    gtk_widget_queue_resize (GTK_WIDGET (stack));

  g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_VHOMOGENEOUS]);
}

/**
 * gtk_stack_get_vhomogeneous:
 * @stack: a `GtkStack`
 *
 * Gets whether @stack is vertically homogeneous.
 *
 * Returns: whether @stack is vertically homogeneous.
 */

gboolean
gtk_stack_get_vhomogeneous (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);

  return priv->homogeneous[GTK_ORIENTATION_VERTICAL];
}

/**
 * gtk_stack_get_transition_duration:
 * @stack: a `GtkStack`
 *
 * Returns the amount of time (in milliseconds) that
 * transitions between pages in @stack will take.
 *
 * Returns: the transition duration
 */

guint
gtk_stack_get_transition_duration (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), 0);

  return priv->transition_duration;
}

/**
 * gtk_stack_set_transition_duration:
 * @stack: a `GtkStack`
 * @duration: the new duration, in milliseconds
 *
 * Sets the duration that transitions between pages in @stack
 * will take.
 */

void
gtk_stack_set_transition_duration (GtkStack *stack,
                                   guint     duration)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_if_fail (GTK_IS_STACK (stack));

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

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

/**
 * gtk_stack_get_transition_type:
 * @stack: a `GtkStack`
 *
 * Gets the type of animation that will be used
 * for transitions between pages in @stack.
 *
 * Returns: the current transition type of @stack
 */

GtkStackTransitionType
gtk_stack_get_transition_type (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), GTK_STACK_TRANSITION_TYPE_NONE);

  return priv->transition_type;
}

/**
 * gtk_stack_set_transition_type:
 * @stack: a `GtkStack`
 * @transition: the new transition type
 *
 * Sets the type of animation that will be used for
 * transitions between pages in @stack.
 *
 * Available types include various kinds of fades and slides.
 *
 * The transition type can be changed without problems
 * at runtime, so it is possible to change the animation
 * based on the page that is about to become current.
 */

void
gtk_stack_set_transition_type (GtkStack              *stack,
                              GtkStackTransitionType  transition)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_if_fail (GTK_IS_STACK (stack));

  if (priv->transition_type == transition)
    return;

  priv->transition_type = transition;
  g_object_notify_by_pspec (G_OBJECT (stack),
                            stack_props[PROP_TRANSITION_TYPE]);
}

/**
 * gtk_stack_get_transition_running:
 * @stack: a `GtkStack`
 *
 * Returns whether the @stack is currently in a transition from one page to
 * another.
 *
 * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
 */

gboolean
gtk_stack_get_transition_running (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);

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

/**
 * gtk_stack_set_interpolate_size:
 * @stack: A `GtkStack`
 * @interpolate_size: the new value
 *
 * Sets whether or not @stack will interpolate its size when
 * changing the visible child.
 *
 * If the [property@Gtk.Stack:interpolate-size] property is set
 * to %TRUE, @stack will interpolate its size between the current
 * one and the one it'll take after changing the visible child,
 * according to the set transition duration.
 */

void
gtk_stack_set_interpolate_size (GtkStack *stack,
                                gboolean  interpolate_size)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  g_return_if_fail (GTK_IS_STACK (stack));

  interpolate_size = !!interpolate_size;

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

  priv->interpolate_size = interpolate_size;
  g_object_notify_by_pspec (G_OBJECT (stack),
                            stack_props[PROP_INTERPOLATE_SIZE]);
}

/**
 * gtk_stack_get_interpolate_size:
 * @stack: A `GtkStack`
 *
 * Returns whether the `GtkStack` is set up to interpolate between
 * the sizes of children on page switch.
 *
 * Returns: %TRUE if child sizes are interpolated
 */

gboolean
gtk_stack_get_interpolate_size (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);

  return priv->interpolate_size;
}



/**
 * gtk_stack_get_visible_child:
 * @stack: a `GtkStack`
 *
 * Gets the currently visible child of @stack.
 *
 * Returns %NULL if there are no visible children.
 *
 * Returns: (transfer none) (nullable): the visible child of the `GtkStack`
 */

GtkWidget *
gtk_stack_get_visible_child (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);

  return priv->visible_child ? priv->visible_child->widget : NULL;
}

/**
 * gtk_stack_get_visible_child_name:
 * @stack: a `GtkStack`
 *
 * Returns the name of the currently visible child of @stack.
 *
 * Returns %NULL if there is no visible child.
 *
 * Returns: (transfer none) (nullable): the name of the visible child
 *   of the `GtkStack`
 */

const char *
gtk_stack_get_visible_child_name (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);

  if (priv->visible_child)
    return priv->visible_child->name;

  return NULL;
}

/**
 * gtk_stack_set_visible_child:
 * @stack: a `GtkStack`
 * @child: a child of @stack
 *
 * Makes @child the visible child of @stack.
 *
 * If @child is different from the currently visible child,
 * the transition between the two will be animated with the
 * current transition type of @stack.
 *
 * Note that the @child widget has to be visible itself
 * (see [method@Gtk.Widget.show]) in order to become the visible
 * child of @stack.
 */

void
gtk_stack_set_visible_child (GtkStack  *stack,
                             GtkWidget *child)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *child_info;

  g_return_if_fail (GTK_IS_STACK (stack));
  g_return_if_fail (GTK_IS_WIDGET (child));

  child_info = find_child_info_for_widget (stack, child);
  if (child_info == NULL)
    {
      g_warning ("Given child of type '%s' not found in GtkStack",
                 G_OBJECT_TYPE_NAME (child));
      return;
    }

  if (gtk_widget_get_visible (child_info->widget))
    set_visible_child (stack, child_info,
                       priv->transition_type,
                       priv->transition_duration);
}

/**
 * gtk_stack_set_visible_child_name:
 * @stack: a `GtkStack`
 * @name: the name of the child to make visible
 *
 * Makes the child with the given name visible.
 *
 * If @child is different from the currently visible child,
 * the transition between the two will be animated with the
 * current transition type of @stack.
 *
 * Note that the child widget has to be visible itself
 * (see [method@Gtk.Widget.show]) in order to become the visible
 * child of @stack.
 */

void
gtk_stack_set_visible_child_name (GtkStack   *stack,
                                 const char *name)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_if_fail (GTK_IS_STACK (stack));

  gtk_stack_set_visible_child_full (stack, name, priv->transition_type);
}

/**
 * gtk_stack_set_visible_child_full:
 * @stack: a `GtkStack`
 * @name: the name of the child to make visible
 * @transition: the transition type to use
 *
 * Makes the child with the given name visible.
 *
 * Note that the child widget has to be visible itself
 * (see [method@Gtk.Widget.show]) in order to become the visible
 * child of @stack.
 */

void
gtk_stack_set_visible_child_full (GtkStack               *stack,
                                  const char             *name,
                                  GtkStackTransitionType  transition)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *child_info, *info;
  guint idx;

  g_return_if_fail (GTK_IS_STACK (stack));

  if (name == NULL)
    return;

  child_info = NULL;
  for (idx = 0; idx < priv->children->len; idx++)
    {
      info = g_ptr_array_index (priv->children, idx);
      if (info->name != NULL &&
          strcmp (info->name, name) == 0)
        {
          child_info = info;
          break;
        }
    }

  if (child_info == NULL)
    {
      g_warning ("Child name '%s' not found in GtkStack", name);
      return;
    }

  if (gtk_widget_get_visible (child_info->widget))
    set_visible_child (stack, child_info, transition, priv->transition_duration);
}

static void
gtk_stack_compute_expand (GtkWidget *widget,
                          gboolean  *hexpand_p,
                          gboolean  *vexpand_p)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  gboolean hexpand, vexpand;
  GtkStackPage *child_info;
  GtkWidget *child;
  guint idx;

  hexpand = FALSE;
  vexpand = FALSE;
  for (idx = 0; idx < priv->children->len; idx++)
    {
      child_info = g_ptr_array_index (priv->children, idx);
      child = child_info->widget;

      if (!hexpand &&
          gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL))
        hexpand = TRUE;

      if (!vexpand &&
          gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL))
        vexpand = TRUE;

      if (hexpand && vexpand)
        break;
    }

  *hexpand_p = hexpand;
  *vexpand_p = vexpand;
}

static GtkSizeRequestMode
gtk_stack_get_request_mode (GtkWidget *widget)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkWidget *w;
  int wfh = 0, hfw = 0;

  if (!priv->homogeneous[GTK_ORIENTATION_VERTICAL] &&
      !priv->homogeneous[GTK_ORIENTATION_HORIZONTAL])
    {
      GtkSizeRequestMode lv_mode;

      /* Only the visible child, and perhaps the last visible child
       * during a transition, matter.  Attempt to return constant-size
       * when we can.  */

      if (priv->last_visible_child)
        lv_mode = gtk_widget_get_request_mode (priv->last_visible_child->widget);
      else
        lv_mode = GTK_SIZE_REQUEST_CONSTANT_SIZE;

      if (lv_mode == GTK_SIZE_REQUEST_CONSTANT_SIZE && priv->visible_child)
        return gtk_widget_get_request_mode (priv->visible_child->widget);
      else
        return lv_mode;
    }

  for (w = gtk_widget_get_first_child (widget);
       w != NULL;
       w = gtk_widget_get_next_sibling (w))
    {
      GtkSizeRequestMode mode = gtk_widget_get_request_mode (w);

      switch (mode)
        {
        case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
          hfw ++;
          break;
        case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
          wfh ++;
          break;
        case GTK_SIZE_REQUEST_CONSTANT_SIZE:
        default:
          break;
        }
    }

  if (hfw == 0 && wfh == 0)
    return GTK_SIZE_REQUEST_CONSTANT_SIZE;
  else
    return wfh > hfw ?
        GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT :
        GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
}

static void
gtk_stack_snapshot_crossfade (GtkWidget   *widget,
                              GtkSnapshot *snapshot)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  double progress = gtk_progress_tracker_get_progress (&priv->tracker, FALSE);

  gtk_snapshot_push_cross_fade (snapshot, progress);

  if (priv->last_visible_child)
    {
      gtk_widget_snapshot_child (widget,
                                 priv->last_visible_child->widget,
                                 snapshot);
    }
  gtk_snapshot_pop (snapshot);

  gtk_widget_snapshot_child (widget,
                             priv->visible_child->widget,
                             snapshot);
  gtk_snapshot_pop (snapshot);
}

static void
gtk_stack_snapshot_under (GtkWidget   *widget,
                          GtkSnapshot *snapshot)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  int widget_width, widget_height;
  int x, y, width, height, pos_x, pos_y;


  x = y = 0;
  width = widget_width = gtk_widget_get_width (widget);
  height = widget_height = gtk_widget_get_height (widget);

  pos_x = pos_y = 0;

  switch ((guint) priv->active_transition_type)
    {
    case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
      y = 0;
      height = widget_height * (gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      pos_y = height;
      break;
    case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
      y = widget_height * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      height = widget_height - y;
      pos_y = y - widget_height;
      break;
    case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
      x = widget_width * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      width = widget_width - x;
      pos_x = x - widget_width;
      break;
    case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
      x = 0;
      width = widget_width * (gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
      pos_x = width;
      break;
    default:
      g_assert_not_reached ();
    }

  gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT(x, y, width, height));

  gtk_widget_snapshot_child (widget,
                             priv->visible_child->widget,
                             snapshot);

  gtk_snapshot_pop (snapshot);

  if (priv->last_visible_child)
    {
      gtk_snapshot_save (snapshot);
      gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (pos_x, pos_y));
      gtk_widget_snapshot_child (widget, priv->last_visible_child->widget, snapshot);
      gtk_snapshot_restore (snapshot);
    }
}

static void
gtk_stack_snapshot_cube (GtkWidget   *widget,
                         GtkSnapshot *snapshot)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  double progress = gtk_progress_tracker_get_progress (&priv->tracker, FALSE);

  g_assert (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT ||
            priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT);

  if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT)
    progress = 1 - progress;

  if (priv->last_visible_child && progress > 0.5)
    {
      gtk_snapshot_save (snapshot);
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 gtk_widget_get_width (widget) / 2.f,
                                 gtk_widget_get_height (widget) / 2.f,
                                 0));
      gtk_snapshot_perspective (snapshot, 2 * gtk_widget_get_width (widget) / 1.f);
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 00,
                                 - gtk_widget_get_width (widget) / 2.f));
      gtk_snapshot_rotate_3d (snapshot, -90 * progress, graphene_vec3_y_axis());
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 - gtk_widget_get_width (widget) / 2.f,
                                 - gtk_widget_get_height (widget) / 2.f,
                                 gtk_widget_get_width (widget) / 2.f));
      if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
        gtk_widget_snapshot_child (widget, priv->last_visible_child->widget, snapshot);
      else
        gtk_widget_snapshot_child (widget, priv->visible_child->widget, snapshot);
      gtk_snapshot_restore (snapshot);
    }

  gtk_snapshot_save (snapshot);
  gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                             gtk_widget_get_width (widget) / 2.f,
                             gtk_widget_get_height (widget) / 2.f,
                             0));
  gtk_snapshot_perspective (snapshot, 2 * gtk_widget_get_width (widget) / 1.f);
  gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                             00,
                             - gtk_widget_get_width (widget) / 2.f));
  gtk_snapshot_rotate_3d (snapshot, 90 * (1.0 - progress), graphene_vec3_y_axis());
  gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                             - gtk_widget_get_width (widget) / 2.f,
                             - gtk_widget_get_height (widget) / 2.f,
                             gtk_widget_get_width (widget) / 2.f));

  if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
    gtk_widget_snapshot_child (widget, priv->visible_child->widget, snapshot);
  else if (priv->last_visible_child)
    gtk_widget_snapshot_child (widget, priv->last_visible_child->widget, snapshot);
  gtk_snapshot_restore (snapshot);

  if (priv->last_visible_child && progress <= 0.5)
    {
      gtk_snapshot_save (snapshot);
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 gtk_widget_get_width (widget) / 2.f,
                                 gtk_widget_get_height (widget) / 2.f,
                                 0));
      gtk_snapshot_perspective (snapshot, 2 * gtk_widget_get_width (widget) / 1.f);
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 00,
                                 - gtk_widget_get_width (widget) / 2.f));
      gtk_snapshot_rotate_3d (snapshot, -90 * progress, graphene_vec3_y_axis());
      gtk_snapshot_translate_3d (snapshot, &GRAPHENE_POINT3D_INIT (
                                 - gtk_widget_get_width (widget) / 2.f,
                                 - gtk_widget_get_height (widget) / 2.f,
                                 gtk_widget_get_width (widget) / 2.f));
      if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
        gtk_widget_snapshot_child (widget, priv->last_visible_child->widget, snapshot);
      else
        gtk_widget_snapshot_child (widget, priv->visible_child->widget, snapshot);
      gtk_snapshot_restore (snapshot);
    }
}

static void
gtk_stack_snapshot_slide (GtkWidget   *widget,
                          GtkSnapshot *snapshot)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (priv->last_visible_child)
    {
      int x, y;
      int width, height;

      width = gtk_widget_get_width (widget);
      height = gtk_widget_get_height (widget);

      x = get_bin_window_x (stack);
      y = get_bin_window_y (stack);

      switch ((guint) priv->active_transition_type)
        {
        case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
          x -= width;
          break;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
          x += width;
          break;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
          y -= height;
          break;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
          y += height;
          break;
        case GTK_STACK_TRANSITION_TYPE_OVER_UP:
        case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
          y = 0;
          break;
        case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
        case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
          x = 0;
          break;
        default:
          g_assert_not_reached ();
          break;
        }

      if (priv->last_visible_child != NULL)
        {
          if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_END &&
              priv->last_visible_widget_height > height)
            y -= priv->last_visible_widget_height - height;
          else if (gtk_widget_get_valign (priv->last_visible_child->widget) == GTK_ALIGN_CENTER)
            y -= (priv->last_visible_widget_height - height) / 2;
        }

      gtk_snapshot_save (snapshot);
      gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y));
      gtk_widget_snapshot_child (widget, priv->last_visible_child->widget, snapshot);
      gtk_snapshot_restore (snapshot);
     }

  gtk_widget_snapshot_child (widget,
                             priv->visible_child->widget,
                             snapshot);
}

static void
gtk_stack_snapshot (GtkWidget   *widget,
                    GtkSnapshot *snapshot)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  if (priv->visible_child)
    {
      if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
        {
          gtk_snapshot_push_clip (snapshot,
                                  &GRAPHENE_RECT_INIT(
                                      00,
                                      gtk_widget_get_width (widget),
                                      gtk_widget_get_height (widget)
                                  ));

          switch (priv->active_transition_type)
            {
            case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
              gtk_stack_snapshot_crossfade (widget, snapshot);
              break;
            case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
            case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
            case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
            case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
            case GTK_STACK_TRANSITION_TYPE_OVER_UP:
            case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
            case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
            case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
              gtk_stack_snapshot_slide (widget, snapshot);
              break;
            case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
            case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
            case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
            case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
              gtk_stack_snapshot_under (widget, snapshot);
              break;
            case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
            case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
              gtk_stack_snapshot_cube (widget, snapshot);
              break;
            case GTK_STACK_TRANSITION_TYPE_NONE:
            case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
            case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
            case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
            case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
            case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
            case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
            case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
            default:
              g_assert_not_reached ();
            }

          gtk_snapshot_pop (snapshot);
        }
      else
        gtk_widget_snapshot_child (widget,
                                   priv->visible_child->widget,
                                   snapshot);
    }
}

static void
adjust_child_allocation (GtkWidget     *child,
                         GtkAllocation *allocation)
{
  int min, width, height;
  GtkAlign align;

  if (gtk_widget_get_request_mode (child) == GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT)
    {
      gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, -1,
                          &min, NULL, NULL, NULL);
      height = MAX (allocation->height, min);
      gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, height,
                          &min, NULL, NULL, NULL);
      width = MAX (allocation->width, min);
    }
  else /* GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH or CONSTANT_SIZE */
    {
      gtk_widget_measure (child, GTK_ORIENTATION_HORIZONTAL, -1,
                          &min, NULL, NULL, NULL);
      width = MAX (allocation->width, min);
      gtk_widget_measure (child, GTK_ORIENTATION_VERTICAL, width,
                          &min, NULL, NULL, NULL);
      height = MAX (allocation->height, min);
    }

  if (width > allocation->width)
    {
      GtkTextDirection direction = _gtk_widget_get_direction (child);
      align = gtk_widget_get_halign (child);

      if (align == GTK_ALIGN_CENTER || align == GTK_ALIGN_FILL)
        allocation->x -= (width - allocation->width) / 2;
      else if (align == (direction == GTK_TEXT_DIR_RTL ? GTK_ALIGN_START : GTK_ALIGN_END))
        allocation->x -= (width - allocation->width);

      allocation->width = width;
    }

  if (height > allocation->height)
    {
      align = gtk_widget_get_valign (child);

      if (align == GTK_ALIGN_CENTER || align == GTK_ALIGN_FILL)
        allocation->y -= (height - allocation->height) / 2;
      else if (align == GTK_ALIGN_END)
        allocation->y -= (height - allocation->height);

      allocation->height = height;
    }
}

static void
gtk_stack_size_allocate (GtkWidget *widget,
                         int        width,
                         int        height,
                         int        baseline)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkAllocation child_allocation;

  if (priv->last_visible_child)
    {
      child_allocation = (GtkAllocation) {00, width, height};
      adjust_child_allocation (priv->last_visible_child->widget,
                               &child_allocation);

      gtk_widget_size_allocate (priv->last_visible_child->widget,
                                &child_allocation, -1);
    }

  child_allocation.x = get_bin_window_x (stack);
  child_allocation.y = get_bin_window_y (stack);
  child_allocation.width = width;
  child_allocation.height = height;

  if (priv->visible_child)
    {
      adjust_child_allocation (priv->visible_child->widget,
                               &child_allocation);

      gtk_widget_size_allocate (priv->visible_child->widget, &child_allocation, -1);
    }
}

static inline double
lerp (int    a,
      int    b,
      double progress)
{
  return a * (1.0 - progress) + b * progress;
}

/*
 * Given lerp (a, b, progress) -> r, find b. In other words: we know what size
 * we're interpolating *from*, the current size, and the current interpolation
 * progress; guess which size we're interpolating *to*.
 */

static inline double
inverse_lerp (int    a,
              int    r,
              double progress)
{
  return (r - a * (1.0 - progress)) / progress;
}

static void
gtk_stack_measure (GtkWidget      *widget,
                   GtkOrientation  orientation,
                   int             for_size,
                   int            *minimum,
                   int            *natural,
                   int            *minimum_baseline,
                   int            *natural_baseline)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackPage *child_info;
  GtkWidget *child;
  guint idx;
  int child_min, child_nat, child_for_size;
  int last_size, last_opposite_size;
  double t;

  if (priv->interpolate_size && priv->last_visible_child)
    {
      t = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);

      if (orientation == GTK_ORIENTATION_HORIZONTAL)
        {
          last_size = priv->last_visible_widget_width;
          last_opposite_size = priv->last_visible_widget_height;
        }
      else
        {
          last_size = priv->last_visible_widget_height;
          last_opposite_size = priv->last_visible_widget_width;
        }

      /* Work out which for_size we want to pass for measuring our children */
      if (for_size == -1 || for_size == last_opposite_size ||
          priv->homogeneous[OPPOSITE_ORIENTATION(orientation)])
        child_for_size = for_size;
      else if (t <= 0.0)
        /* We're going to return last_size anyway */
        child_for_size = -1;
      else
        {
          double d = inverse_lerp (last_opposite_size, for_size, t);
          /* inverse_lerp is numerically unstable due to its use of floating-
           * point division, potentially by a very small value when progress is
           * close to zero. So we sanity check the return value.
           */

          if (isnan (d) || isinf (d) || d < 0 || d >= G_MAXINT)
            child_for_size = -1;
          else
            child_for_size = floor (d);
        }
    }
  else
    {
      t = 1.0;
      last_size = 0;
      child_for_size = for_size;
    }

  *minimum = 0;
  *natural = 0;

  for (idx = 0; idx < priv->children->len; idx++)
    {
      child_info = g_ptr_array_index (priv->children, idx);
      child = child_info->widget;

      if (!priv->homogeneous[orientation] &&
          priv->visible_child != child_info)
        continue;
      if (!gtk_widget_get_visible (child))
        continue;

      if (!priv->homogeneous[OPPOSITE_ORIENTATION(orientation)] && priv->visible_child != child_info)
        {
          int measure_for_size;

          /* Make sure to measure at least for the minimum size */
          if (child_for_size == -1)
            measure_for_size = -1;
          else
            {
              gtk_widget_measure (child, OPPOSITE_ORIENTATION (orientation),
                                  -1,
                                  &measure_for_size, NULL,
                                  NULL, NULL);
              measure_for_size = MAX (measure_for_size, child_for_size);
            }

          gtk_widget_measure (child, orientation,
                              measure_for_size,
                              &child_min, &child_nat,
                              NULL, NULL);
        }
      else
        gtk_widget_measure (child, orientation,
                            child_for_size,
                            &child_min, &child_nat,
                            NULL, NULL);

      *minimum = MAX (*minimum, child_min);
      *natural = MAX (*natural, child_nat);
  }

  if (priv->last_visible_child != NULL &&
      priv->interpolate_size &&
      !priv->homogeneous[orientation])
    {
      *minimum = ceil (lerp (last_size, *minimum, t));
      *natural = ceil (lerp (last_size, *natural, t));
    }
}

static void
gtk_stack_init (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  priv->homogeneous[GTK_ORIENTATION_VERTICAL] = TRUE;
  priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] = TRUE;
  priv->transition_duration = 200;
  priv->transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
  priv->children = g_ptr_array_new();
}

/**
 * gtk_stack_get_pages:
 * @stack: a `GtkStack`
 *
 * Returns a `GListModel` that contains the pages of the stack.
 *
 * This can be used to keep an up-to-date view. The model also
 * implements [iface@Gtk.SelectionModel] and can be used to track
 * and modify the visible page.
 *
 * Returns: (transfer full): a `GtkSelectionModel` for the stack's children
 */

GtkSelectionModel *
gtk_stack_get_pages (GtkStack *stack)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);

  if (priv->pages)
    return g_object_ref (priv->pages);

  priv->pages = GTK_SELECTION_MODEL (gtk_stack_pages_new (stack));
  g_object_add_weak_pointer (G_OBJECT (priv->pages), (gpointer *)&priv->pages);

  return priv->pages;
}

/**
 * gtk_stack_page_get_visible:
 * @self: a `GtkStackPage`
 *
 * Returns whether @page is visible in its `GtkStack`.
 *
 * This is independent from the [property@Gtk.Widget:visible]
 * property of its widget.
 *
 * Returns: %TRUE if @page is visible
 */

gboolean
gtk_stack_page_get_visible (GtkStackPage *self)
{
  g_return_val_if_fail (GTK_IS_STACK_PAGE (self), FALSE);

  return self->visible;
}

/**
 * gtk_stack_page_set_visible:
 * @self: a `GtkStackPage`
 * @visible: The new property value
 *
 * Sets whether @page is visible in its `GtkStack`.
 */

void
gtk_stack_page_set_visible (GtkStackPage *self,
                            gboolean      visible)
{
  g_return_if_fail (GTK_IS_STACK_PAGE (self));

  visible = !!visible;

  if (visible == self->visible)
    return;

  self->visible = visible;

  if (self->widget && gtk_widget_get_parent (self->widget))
    update_child_visible (GTK_STACK (gtk_widget_get_parent (self->widget)), self);

  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_VISIBLE]);
}

/**
 * gtk_stack_page_get_needs_attention:
 * @self: a `GtkStackPage`
 *
 * Returns whether the page is marked as needs attention.
 *
 * Returns: The value of the [property@Gtk.StackPage:needs-attention]
 *   property.
 */

gboolean
gtk_stack_page_get_needs_attention (GtkStackPage *self)
{
  return self->needs_attention;
}

/**
 * gtk_stack_page_set_needs_attention:
 * @self: a `GtkStackPage`
 * @setting: the new value to set
 *
 * Sets whether the page is marked as needs attention.
 */

void
gtk_stack_page_set_needs_attention (GtkStackPage *self,
                                    gboolean      setting)
{
  setting = !!setting;

  if (setting == self->needs_attention)
    return;

  self->needs_attention = setting;
  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_NEEDS_ATTENTION]);
}

/**
 * gtk_stack_page_get_use_underline:
 * @self: a `GtkStackPage`
 *
 * Gets whether underlines in the page title indicate mnemonics.
 *
 * Returns: The value of the [property@Gtk.StackPage:use-underline] property
 */

gboolean
gtk_stack_page_get_use_underline (GtkStackPage *self)
{
  return self->use_underline;
}

/**
 * gtk_stack_page_set_use_underline:
 * @self: a `GtkStackPage`
 * @setting: the new value to set
 *
 * Sets whether underlines in the page title indicate mnemonics.
 */

void
gtk_stack_page_set_use_underline (GtkStackPage *self,
                                  gboolean      setting)
{
  setting = !!setting;

  if (setting == self->use_underline)
    return;

  self->use_underline = setting;
  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_USE_UNDERLINE]);
}


/**
 * gtk_stack_page_get_name:
 * @self: a `GtkStackPage`
 *
 * Returns the name of the page.
 *
 * Returns: (nullable): The value of the [property@Gtk.StackPage:name] property
 */

const char *
gtk_stack_page_get_name (GtkStackPage *self)
{
  g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);

  return self->name;
}

/**
 * gtk_stack_page_set_name:
 * @self: a `GtkStackPage`
 * @setting: (transfer none): the new value to set
 *
 * Sets the name of the page.
 */

void
gtk_stack_page_set_name (GtkStackPage *self,
                         const char   *setting)
{
  GtkStack *stack = NULL;
  GtkStackPrivate *priv = NULL;

  g_return_if_fail (GTK_IS_STACK_PAGE (self));

  if (self->widget &&
      gtk_widget_get_parent (self->widget) &&
      GTK_IS_STACK (gtk_widget_get_parent (self->widget)))
    {
      guint idx;

      stack = GTK_STACK (gtk_widget_get_parent (self->widget));
      priv = gtk_stack_get_instance_private (stack);

      for (idx = 0; idx < priv->children->len; idx++)
        {
          GtkStackPage *info2 = g_ptr_array_index (priv->children, idx);
          if (self == info2)
            continue;

          if (g_strcmp0 (info2->name, setting) == 0)
            {
              g_warning ("Duplicate child name in GtkStack: %s", setting);
              break;
            }
        }
    }

  if (setting == self->name)
    return;

  g_free (self->name);
  self->name = g_strdup (setting);
  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_NAME]);

  if (priv && priv->visible_child == self)
    g_object_notify_by_pspec (G_OBJECT (stack),
                              stack_props[PROP_VISIBLE_CHILD_NAME]);
}

/**
 * gtk_stack_page_get_title:
 * @self: a `GtkStackPage`
 *
 * Gets the page title.
 *
 * Returns: (nullable): The value of the [property@Gtk.StackPage:title] property
 */

const char *
gtk_stack_page_get_title (GtkStackPage *self)
{
  g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);

  return self->title;
}

/**
 * gtk_stack_page_set_title:
 * @self: a `GtkStackPage`
 * @setting: (transfer none): the new value to set
 *
 * Sets the page title.
 */

void
gtk_stack_page_set_title (GtkStackPage *self,
                          const char   *setting)
{
  g_return_if_fail (GTK_IS_STACK_PAGE (self));

  if (setting == self->title)
    return;

  g_free (self->title);
  self->title = g_strdup (setting);
  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_TITLE]);

  gtk_accessible_update_property (GTK_ACCESSIBLE (self),
                                  GTK_ACCESSIBLE_PROPERTY_LABEL, self->title,
                                  -1);
}

/**
 * gtk_stack_page_get_icon_name:
 * @self: a `GtkStackPage`
 *
 * Returns the icon name of the page.
 *
 * Returns: (nullable): The value of the [property@Gtk.StackPage:icon-name] property
 */

const char *
gtk_stack_page_get_icon_name (GtkStackPage *self)
{
  g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);

  return self->icon_name;
}

/**
 * gtk_stack_page_set_icon_name:
 * @self: a `GtkStackPage`
 * @setting: (transfer none): the new value to set
 *
 * Sets the icon name of the page.
 */

void
gtk_stack_page_set_icon_name (GtkStackPage *self,
                              const char   *setting)
{
  g_return_if_fail (GTK_IS_STACK_PAGE (self));

  if (setting == self->icon_name)
    return;

  g_free (self->icon_name);
  self->icon_name = g_strdup (setting);
  g_object_notify_by_pspec (G_OBJECT (self), stack_page_props[CHILD_PROP_ICON_NAME]);
}

Messung V0.5 in Prozent
C=98 H=96 G=96

¤ Dauer der Verarbeitung: 0.83 Sekunden  (vorverarbeitet am  2026-07-03) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik