forked from AuroraMiddleware/gtk
1d802881eb
Unparent the child widget before tearing down its stack page. This is necessary so a11y can still access the stack page accessible to emit change notification when it learns that the child is removed.
2800 lines
87 KiB
C
2800 lines
87 KiB
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 "gtkintl.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>
|
|
|
|
/**
|
|
* SECTION:gtkstack
|
|
* @Short_description: A stacking container
|
|
* @Title: GtkStack
|
|
* @See_also: #GtkNotebook, #GtkStackSwitcher
|
|
*
|
|
* The GtkStack widget is a container which only shows
|
|
* one of its children at a time. In contrast to GtkNotebook,
|
|
* GtkStack does not provide a means for users to change the
|
|
* visible child. Instead, the #GtkStackSwitcher widget can be
|
|
* used with GtkStack to provide this functionality.
|
|
*
|
|
* Transitions between pages can be animated as slides or
|
|
* fades. This can be controlled with gtk_stack_set_transition_type().
|
|
* These animations respect the #GtkSettings:gtk-enable-animations
|
|
* setting.
|
|
*
|
|
* GtkStack maintains a #GtkStackPage object for each added
|
|
* child, which holds additional per-child properties. You
|
|
* obtain the #GtkStackPage for a child with gtk_stack_get_page().
|
|
*
|
|
* # 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:
|
|
* |[
|
|
* <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 #GTK_ACCESSIBLE_ROLE_TAB_PANEL 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
|
|
*
|
|
* These enumeration values describe the possible transitions
|
|
* between pages in a #GtkStack widget.
|
|
*
|
|
* New values may be added to this enumeration over time.
|
|
*/
|
|
|
|
/* 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 {
|
|
GList *children;
|
|
|
|
GtkStackPage *visible_child;
|
|
|
|
gboolean hhomogeneous;
|
|
gboolean vhomogeneous;
|
|
|
|
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);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GtkStack, gtk_stack, GTK_TYPE_WIDGET,
|
|
G_ADD_PRIVATE (GtkStack)
|
|
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,
|
|
LAST_CHILD_PROP,
|
|
|
|
PROP_ACCESSIBLE_ROLE
|
|
};
|
|
|
|
struct _GtkStackPage
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GtkWidget *widget;
|
|
char *name;
|
|
char *title;
|
|
char *icon_name;
|
|
GtkWidget *last_focus;
|
|
|
|
GtkATContext *at_context;
|
|
|
|
guint needs_attention : 1;
|
|
guint visible : 1;
|
|
guint use_underline : 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->at_context == NULL)
|
|
{
|
|
GtkAccessibleRole role = GTK_ACCESSIBLE_ROLE_TAB_PANEL;
|
|
GdkDisplay *display = gtk_widget_get_display (page->widget);
|
|
|
|
page->at_context = gtk_at_context_create (role, accessible, display);
|
|
}
|
|
|
|
return page->at_context;
|
|
}
|
|
|
|
static gboolean
|
|
gtk_stack_page_accessible_get_platform_state (GtkAccessible *self,
|
|
GtkAccessiblePlatformState state)
|
|
{
|
|
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;
|
|
}
|
|
|
|
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_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));
|
|
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_class_init (GtkStackPageClass *class)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (class);
|
|
|
|
object_class->finalize = gtk_stack_page_finalize;
|
|
object_class->get_property = gtk_stack_page_get_property;
|
|
object_class->set_property = gtk_stack_page_set_property;
|
|
|
|
stack_page_props[CHILD_PROP_CHILD] =
|
|
g_param_spec_object ("child",
|
|
P_("Child"),
|
|
P_("The child of the page"),
|
|
GTK_TYPE_WIDGET,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
stack_page_props[CHILD_PROP_NAME] =
|
|
g_param_spec_string ("name",
|
|
P_("Name"),
|
|
P_("The name of the child page"),
|
|
NULL,
|
|
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
|
|
|
|
stack_page_props[CHILD_PROP_TITLE] =
|
|
g_param_spec_string ("title",
|
|
P_("Title"),
|
|
P_("The title of the child page"),
|
|
NULL,
|
|
GTK_PARAM_READWRITE);
|
|
|
|
stack_page_props[CHILD_PROP_ICON_NAME] =
|
|
g_param_spec_string ("icon-name",
|
|
P_("Icon name"),
|
|
P_("The icon name of the child page"),
|
|
NULL,
|
|
GTK_PARAM_READWRITE);
|
|
|
|
/**
|
|
* GtkStackPage:needs-attention:
|
|
*
|
|
* Sets a flag specifying whether the page requires the user attention.
|
|
* This is used by the #GtkStackSwitcher 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",
|
|
P_("Needs Attention"),
|
|
P_("Whether this page needs attention"),
|
|
FALSE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
stack_page_props[CHILD_PROP_VISIBLE] =
|
|
g_param_spec_boolean ("visible",
|
|
P_("Visible"),
|
|
P_("Whether this page is visible"),
|
|
TRUE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
stack_page_props[CHILD_PROP_USE_UNDERLINE] =
|
|
g_param_spec_boolean ("use-underline",
|
|
P_("Use underline"),
|
|
P_("If set, an underline in the title indicates the next character should be used for the mnemonic accelerator key"),
|
|
FALSE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_page_props);
|
|
|
|
g_object_class_override_property (object_class, PROP_ACCESSIBLE_ROLE, "accessible-role");
|
|
}
|
|
|
|
#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;
|
|
};
|
|
|
|
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 g_list_length (priv->children);
|
|
}
|
|
|
|
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;
|
|
|
|
page = g_list_nth_data (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;
|
|
|
|
page = g_list_nth_data (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;
|
|
|
|
page = g_list_nth_data (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_init (GtkStackPages *pages)
|
|
{
|
|
}
|
|
|
|
static void
|
|
gtk_stack_pages_class_init (GtkStackPagesClass *class)
|
|
{
|
|
}
|
|
|
|
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 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;
|
|
|
|
if (priv->pages)
|
|
g_list_model_items_changed (G_LIST_MODEL (priv->pages), 0, g_list_length (priv->children), 0);
|
|
|
|
while ((child = gtk_widget_get_first_child (GTK_WIDGET (stack))))
|
|
stack_remove (stack, child, TRUE);
|
|
|
|
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_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_set_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", P_("Horizontally homogeneous"), P_("Horizontally homogeneous sizing"),
|
|
TRUE,
|
|
GTK_PARAM_READWRITE | 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", P_("Vertically homogeneous"), P_("Vertically homogeneous sizing"),
|
|
TRUE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
stack_props[PROP_VISIBLE_CHILD] =
|
|
g_param_spec_object ("visible-child", P_("Visible child"), P_("The widget currently visible in the stack"),
|
|
GTK_TYPE_WIDGET,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
stack_props[PROP_VISIBLE_CHILD_NAME] =
|
|
g_param_spec_string ("visible-child-name", P_("Name of visible child"), P_("The name of the widget currently visible in the stack"),
|
|
NULL,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
stack_props[PROP_TRANSITION_DURATION] =
|
|
g_param_spec_uint ("transition-duration", P_("Transition duration"), P_("The animation duration, in milliseconds"),
|
|
0, G_MAXUINT, 200,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
stack_props[PROP_TRANSITION_TYPE] =
|
|
g_param_spec_enum ("transition-type", P_("Transition type"), P_("The type of animation used to transition"),
|
|
GTK_TYPE_STACK_TRANSITION_TYPE, GTK_STACK_TRANSITION_TYPE_NONE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
stack_props[PROP_TRANSITION_RUNNING] =
|
|
g_param_spec_boolean ("transition-running", P_("Transition running"), P_("Whether or not the transition is currently running"),
|
|
FALSE,
|
|
GTK_PARAM_READABLE);
|
|
stack_props[PROP_INTERPOLATE_SIZE] =
|
|
g_param_spec_boolean ("interpolate-size", P_("Interpolate size"), P_("Whether or not the size should smoothly change when changing between differently sized children"),
|
|
FALSE,
|
|
GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
|
|
|
|
stack_props[PROP_PAGES] =
|
|
g_param_spec_object ("pages", P_("Pages"), P_("A selection model with the stacks pages"),
|
|
GTK_TYPE_SELECTION_MODEL,
|
|
GTK_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties (object_class, LAST_PROP, stack_props);
|
|
|
|
gtk_widget_class_set_css_name (widget_class, I_("stack"));
|
|
}
|
|
|
|
/**
|
|
* gtk_stack_new:
|
|
*
|
|
* Creates a new #GtkStack container.
|
|
*
|
|
* 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;
|
|
GList *l;
|
|
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
info = l->data;
|
|
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->vhomogeneous || !priv->hhomogeneous)
|
|
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 FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
static GtkStackTransitionType
|
|
effective_transition_type (GtkStack *stack,
|
|
GtkStackTransitionType transition_type)
|
|
{
|
|
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);
|
|
|
|
if (gtk_widget_get_mapped (widget) &&
|
|
gtk_settings_get_enable_animations (gtk_widget_get_settings (widget)) &&
|
|
transition_type != GTK_STACK_TRANSITION_TYPE_NONE &&
|
|
transition_duration != 0 &&
|
|
priv->last_visible_child != NULL)
|
|
{
|
|
priv->active_transition_type = effective_transition_type (stack, 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);
|
|
}
|
|
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);
|
|
GList *l;
|
|
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 (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
info = l->data;
|
|
if (gtk_widget_get_visible (info->widget))
|
|
{
|
|
child_info = info;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (child_info == priv->visible_child)
|
|
return;
|
|
|
|
if (priv->pages)
|
|
{
|
|
guint position;
|
|
for (l = priv->children, position = 0; l != NULL; l = l->next, position++)
|
|
{
|
|
info = l->data;
|
|
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);
|
|
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);
|
|
}
|
|
}
|
|
|
|
priv->visible_child = child_info;
|
|
|
|
if (child_info)
|
|
{
|
|
gtk_widget_set_child_visible (child_info->widget, TRUE);
|
|
|
|
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 (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
if (child_info == l->data)
|
|
{
|
|
i_first = TRUE;
|
|
break;
|
|
}
|
|
if (priv->last_visible_child == l->data)
|
|
break;
|
|
}
|
|
|
|
transition_type = get_simple_transition_type (i_first, transition_type);
|
|
}
|
|
|
|
if (priv->hhomogeneous && priv->vhomogeneous)
|
|
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;
|
|
}
|
|
}
|
|
|
|
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 or %NULL
|
|
*
|
|
* 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, NULL);
|
|
child_info->widget = g_object_ref (child);
|
|
child_info->name = g_strdup (name);
|
|
child_info->title = g_strdup (title);
|
|
child_info->icon_name = NULL;
|
|
child_info->needs_attention = FALSE;
|
|
child_info->last_focus = 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);
|
|
GList *l;
|
|
|
|
g_return_if_fail (child_info->widget != NULL);
|
|
|
|
if (child_info->name)
|
|
{
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
GtkStackPage *info = l->data;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
priv->children = g_list_append (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), g_list_length (priv->children) - 1, 0, 1);
|
|
|
|
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->hhomogeneous || priv->vhomogeneous || 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)
|
|
{
|
|
if (in_dispose)
|
|
priv->visible_child = NULL;
|
|
else
|
|
set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);
|
|
}
|
|
|
|
if (priv->last_visible_child == child_info)
|
|
priv->last_visible_child = NULL;
|
|
|
|
gtk_widget_unparent (child);
|
|
|
|
g_clear_object (&child_info->widget);
|
|
|
|
priv->children = g_list_remove (priv->children, child_info);
|
|
|
|
g_object_unref (child_info);
|
|
|
|
if (!in_dispose &&
|
|
(priv->hhomogeneous || priv->vhomogeneous) &&
|
|
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);
|
|
GList *l;
|
|
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 (l = priv->children, position = 0; l; l = l->next, position++)
|
|
{
|
|
GtkStackPage *page = l->data;
|
|
if (page->widget == child)
|
|
break;
|
|
}
|
|
|
|
stack_remove (stack, child, FALSE);
|
|
|
|
if (priv->pages)
|
|
g_list_model_items_changed (G_LIST_MODEL (priv->pages), position, 1, 0);
|
|
}
|
|
|
|
/**
|
|
* 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 of the #GtkStack 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;
|
|
GList *l;
|
|
|
|
g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
|
|
g_return_val_if_fail (name != NULL, NULL);
|
|
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
info = l->data;
|
|
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->hhomogeneous == hhomogeneous)
|
|
return;
|
|
|
|
priv->hhomogeneous = 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.
|
|
* See gtk_stack_set_hhomogeneous().
|
|
*
|
|
* 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->hhomogeneous;
|
|
}
|
|
|
|
/**
|
|
* 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->vhomogeneous == vhomogeneous)
|
|
return;
|
|
|
|
priv->vhomogeneous = 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.
|
|
* See gtk_stack_set_vhomogeneous().
|
|
*
|
|
* 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->vhomogeneous;
|
|
}
|
|
|
|
/**
|
|
* 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 #GtkStack: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, or %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, or
|
|
* %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 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 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 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;
|
|
GList *l;
|
|
|
|
g_return_if_fail (GTK_IS_STACK (stack));
|
|
|
|
if (name == NULL)
|
|
return;
|
|
|
|
child_info = NULL;
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
info = l->data;
|
|
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;
|
|
GList *l;
|
|
|
|
hexpand = FALSE;
|
|
vexpand = FALSE;
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
child_info = l->data;
|
|
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)
|
|
{
|
|
GtkWidget *w;
|
|
int wfh = 0, hfw = 0;
|
|
|
|
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);
|
|
|
|
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 (
|
|
0, 0,
|
|
- 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 (
|
|
0, 0,
|
|
- 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
|
|
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 (
|
|
0, 0,
|
|
- 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(
|
|
0, 0,
|
|
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
|
|
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)
|
|
{
|
|
int child_width, child_height;
|
|
int min, nat;
|
|
|
|
gtk_widget_measure (priv->last_visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
|
|
-1,
|
|
&min, &nat, NULL, NULL);
|
|
child_width = MAX (min, width);
|
|
gtk_widget_measure (priv->last_visible_child->widget, GTK_ORIENTATION_VERTICAL,
|
|
child_width,
|
|
&min, &nat, NULL, NULL);
|
|
child_height = MAX (min, height);
|
|
|
|
gtk_widget_size_allocate (priv->last_visible_child->widget,
|
|
&(GtkAllocation) { 0, 0, child_width, child_height }, -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)
|
|
{
|
|
int min_width;
|
|
int min_height;
|
|
|
|
gtk_widget_measure (priv->visible_child->widget, GTK_ORIENTATION_HORIZONTAL,
|
|
height, &min_width, NULL, NULL, NULL);
|
|
child_allocation.width = MAX (child_allocation.width, min_width);
|
|
|
|
gtk_widget_measure (priv->visible_child->widget, GTK_ORIENTATION_VERTICAL,
|
|
child_allocation.width, &min_height, NULL, NULL, NULL);
|
|
child_allocation.height = MAX (child_allocation.height, min_height);
|
|
|
|
if (child_allocation.width > width)
|
|
{
|
|
GtkAlign halign = gtk_widget_get_halign (priv->visible_child->widget);
|
|
|
|
if (halign == GTK_ALIGN_CENTER || halign == GTK_ALIGN_FILL)
|
|
child_allocation.x = (width - child_allocation.width) / 2;
|
|
else if (halign == GTK_ALIGN_END)
|
|
child_allocation.x = (width - child_allocation.width);
|
|
}
|
|
|
|
if (child_allocation.height > height)
|
|
{
|
|
GtkAlign valign = gtk_widget_get_valign (priv->visible_child->widget);
|
|
|
|
if (valign == GTK_ALIGN_CENTER || valign == GTK_ALIGN_FILL)
|
|
child_allocation.y = (height - child_allocation.height) / 2;
|
|
else if (valign == GTK_ALIGN_END)
|
|
child_allocation.y = (height - child_allocation.height);
|
|
}
|
|
|
|
gtk_widget_size_allocate (priv->visible_child->widget, &child_allocation, -1);
|
|
}
|
|
}
|
|
|
|
#define LERP(a, b, t) ((a) + (((b) - (a)) * (1.0 - (t))))
|
|
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;
|
|
int child_min, child_nat;
|
|
GList *l;
|
|
|
|
*minimum = 0;
|
|
*natural = 0;
|
|
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
child_info = l->data;
|
|
child = child_info->widget;
|
|
|
|
if (((orientation == GTK_ORIENTATION_VERTICAL && !priv->vhomogeneous) ||
|
|
(orientation == GTK_ORIENTATION_HORIZONTAL && !priv->hhomogeneous)) &&
|
|
priv->visible_child != child_info)
|
|
continue;
|
|
|
|
if (gtk_widget_get_visible (child))
|
|
{
|
|
gtk_widget_measure (child, orientation, for_size, &child_min, &child_nat, NULL, NULL);
|
|
|
|
*minimum = MAX (*minimum, child_min);
|
|
*natural = MAX (*natural, child_nat);
|
|
}
|
|
}
|
|
|
|
if (priv->last_visible_child != NULL)
|
|
{
|
|
if (orientation == GTK_ORIENTATION_VERTICAL && !priv->vhomogeneous)
|
|
{
|
|
double t = priv->interpolate_size ? gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE) : 1.0;
|
|
*minimum = LERP (*minimum, priv->last_visible_widget_height, t);
|
|
*natural = LERP (*natural, priv->last_visible_widget_height, t);
|
|
}
|
|
if (orientation == GTK_ORIENTATION_HORIZONTAL && !priv->hhomogeneous)
|
|
{
|
|
double t = priv->interpolate_size ? gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE) : 1.0;
|
|
*minimum = LERP (*minimum, priv->last_visible_widget_width, t);
|
|
*natural = LERP (*natural, priv->last_visible_widget_width, t);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
gtk_stack_init (GtkStack *stack)
|
|
{
|
|
GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
|
|
|
|
priv->vhomogeneous = TRUE;
|
|
priv->hhomogeneous = TRUE;
|
|
priv->transition_duration = 200;
|
|
priv->transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
|
|
}
|
|
|
|
/**
|
|
* gtk_stack_get_pages:
|
|
* @stack: a #GtkStack
|
|
*
|
|
* Returns a #GListModel that contains the pages of the stack,
|
|
* and can be used to keep an up-to-date view. The model also
|
|
* implements #GtkSelectionModel 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 #GtkWidget:visible value of its
|
|
* #GtkWidget.
|
|
*
|
|
* 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 the new value of the #GtkStackPage:visible property
|
|
* to @visible.
|
|
*/
|
|
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 the current value of the #GtkStackPage:needs-attention property.
|
|
*
|
|
* Returns: The value of the #GtkStackPage:needs-attention property.
|
|
* See gtk_stack_page_set_needs_attention() for details on how to set a new value.
|
|
*/
|
|
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 the new value of the #GtkStackPage:needs-attention property.
|
|
* See also gtk_stack_page_get_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
|
|
*
|
|
* Returns the current value of the #GtkStackPage:use-underline property.
|
|
*
|
|
* Returns: The value of the #GtkStackPage:use-underline property.
|
|
* See gtk_stack_page_set_use_underline() for details on how to set a new value.
|
|
*/
|
|
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 the new value of the #GtkStackPage:use-underline property.
|
|
* See also gtk_stack_page_get_use_underline()
|
|
*
|
|
*/
|
|
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 current value of the #GtkStackPage:name property.
|
|
*
|
|
* Returns: (nullable): The value of the #GtkStackPage:name property.
|
|
* See gtk_stack_page_set_name() for details on how to set a new value.
|
|
*/
|
|
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 new value of the #GtkStackPage:name property.
|
|
* See also gtk_stack_page_get_name()
|
|
*/
|
|
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)))
|
|
{
|
|
GList *l;
|
|
|
|
stack = GTK_STACK (gtk_widget_get_parent (self->widget));
|
|
priv = gtk_stack_get_instance_private (stack);
|
|
|
|
for (l = priv->children; l != NULL; l = l->next)
|
|
{
|
|
GtkStackPage *info2 = l->data;
|
|
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
|
|
*
|
|
* Returns the current value of the #GtkStackPage:title property.
|
|
*
|
|
* Returns: (nullable): The value of the #GtkStackPage:title property.
|
|
* See gtk_stack_page_set_title() for details on how to set a new value.
|
|
*/
|
|
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 new value of the #GtkStackPage:title property.
|
|
* See also gtk_stack_page_get_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_stack_page_get_icon_name:
|
|
* @self: a #GtkStackPage
|
|
*
|
|
* Returns the current value of the #GtkStackPage:icon-name property.
|
|
*
|
|
* Returns: (nullable): The value of the #GtkStackPage:icon-name property.
|
|
* See gtk_stack_page_set_icon_name() for details on how to set a new value.
|
|
*/
|
|
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 new value of the #GtkStackPage:icon-name property.
|
|
* See also gtk_stack_page_get_icon_name()
|
|
*/
|
|
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]);
|
|
}
|