1/*
2 * Copyright (c) 2013 Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
12 * License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *
18 * Author: Alexander Larsson <alexl@redhat.com>
19 *
20 */
21
22#include "config.h"
23
24#include <gtk/gtk.h>
25#include "gtkstack.h"
26#include "gtkenums.h"
27#include "gtkaccessibleprivate.h"
28#include "gtkatcontextprivate.h"
29#include "gtkprivate.h"
30#include "gtkintl.h"
31#include "gtkprogresstrackerprivate.h"
32#include "gtksettingsprivate.h"
33#include "gtksnapshot.h"
34#include "gtkwidgetprivate.h"
35#include "gtksingleselection.h"
36#include "gtklistlistmodelprivate.h"
37#include <math.h>
38#include <string.h>
39
40/**
41 * GtkStack:
42 *
43 * `GtkStack` is a container which only shows one of its children
44 * at a time.
45 *
46 * In contrast to `GtkNotebook`, `GtkStack` does not provide a means
47 * for users to change the visible child. Instead, a separate widget
48 * such as [class@Gtk.StackSwitcher] or [class@Gtk.StackSidebar] can
49 * be used with `GtkStack` to provide this functionality.
50 *
51 * Transitions between pages can be animated as slides or fades. This
52 * can be controlled with [method@Gtk.Stack.set_transition_type].
53 * These animations respect the [property@Gtk.Settings:gtk-enable-animations]
54 * setting.
55 *
56 * `GtkStack` maintains a [class@Gtk.StackPage] object for each added
57 * child, which holds additional per-child properties. You
58 * obtain the `GtkStackPage` for a child with [method@Gtk.Stack.get_page]
59 * and you can obtain a `GtkSelectionModel` containing all the pages
60 * with [method@Gtk.Stack.get_pages].
61 *
62 * # GtkStack as GtkBuildable
63 *
64 * To set child-specific properties in a .ui file, create `GtkStackPage`
65 * objects explicitly, and set the child widget as a property on it:
66 *
67 * ```xml
68 * <object class="GtkStack" id="stack">
69 * <child>
70 * <object class="GtkStackPage">
71 * <property name="name">page1</property>
72 * <property name="title">In the beginning…</property>
73 * <property name="child">
74 * <object class="GtkLabel">
75 * <property name="label">It was dark</property>
76 * </object>
77 * </property>
78 * </object>
79 * </child>
80 * ```
81 *
82 * # CSS nodes
83 *
84 * `GtkStack` has a single CSS node named stack.
85 *
86 * # Accessibility
87 *
88 * `GtkStack` uses the %GTK_ACCESSIBLE_ROLE_TAB_PANEL for the stack
89 * pages, which are the accessible parent objects of the child widgets.
90 */
91
92/**
93 * GtkStackTransitionType:
94 * @GTK_STACK_TRANSITION_TYPE_NONE: No transition
95 * @GTK_STACK_TRANSITION_TYPE_CROSSFADE: A cross-fade
96 * @GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT: Slide from left to right
97 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT: Slide from right to left
98 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP: Slide from bottom up
99 * @GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN: Slide from top down
100 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT: Slide from left or right according to the children order
101 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN: Slide from top down or bottom up according to the order
102 * @GTK_STACK_TRANSITION_TYPE_OVER_UP: Cover the old page by sliding up
103 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN: Cover the old page by sliding down
104 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT: Cover the old page by sliding to the left
105 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT: Cover the old page by sliding to the right
106 * @GTK_STACK_TRANSITION_TYPE_UNDER_UP: Uncover the new page by sliding up
107 * @GTK_STACK_TRANSITION_TYPE_UNDER_DOWN: Uncover the new page by sliding down
108 * @GTK_STACK_TRANSITION_TYPE_UNDER_LEFT: Uncover the new page by sliding to the left
109 * @GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT: Uncover the new page by sliding to the right
110 * @GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN: Cover the old page sliding up or uncover the new page sliding down, according to order
111 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP: Cover the old page sliding down or uncover the new page sliding up, according to order
112 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT: Cover the old page sliding left or uncover the new page sliding right, according to order
113 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT: Cover the old page sliding right or uncover the new page sliding left, according to order
114 * @GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT: Pretend the pages are sides of a cube and rotate that cube to the left
115 * @GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT: Pretend the pages are sides of a cube and rotate that cube to the right
116 * @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
117 *
118 * Possible transitions between pages in a `GtkStack` widget.
119 *
120 * New values may be added to this enumeration over time.
121 */
122
123/**
124 * GtkStackPage:
125 *
126 * `GtkStackPage` is an auxiliary class used by `GtkStack`.
127 */
128
129/* TODO:
130 * filter events out events to the last_child widget during transitions
131 */
132
133struct _GtkStack {
134 GtkWidget parent_instance;
135};
136
137typedef struct _GtkStackClass GtkStackClass;
138struct _GtkStackClass {
139 GtkWidgetClass parent_class;
140};
141
142typedef struct {
143 GList *children;
144
145 GtkStackPage *visible_child;
146
147 gboolean homogeneous[2];
148
149 GtkStackTransitionType transition_type;
150 guint transition_duration;
151
152 GtkStackPage *last_visible_child;
153 guint tick_id;
154 GtkProgressTracker tracker;
155 gboolean first_frame_skipped;
156
157 int last_visible_widget_width;
158 int last_visible_widget_height;
159
160 gboolean interpolate_size;
161
162 GtkStackTransitionType active_transition_type;
163
164 GtkSelectionModel *pages;
165
166} GtkStackPrivate;
167
168static void gtk_stack_buildable_interface_init (GtkBuildableIface *iface);
169
170G_DEFINE_TYPE_WITH_CODE (GtkStack, gtk_stack, GTK_TYPE_WIDGET,
171 G_ADD_PRIVATE (GtkStack)
172 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
173 gtk_stack_buildable_interface_init))
174enum {
175 PROP_0,
176 PROP_HHOMOGENEOUS,
177 PROP_VHOMOGENEOUS,
178 PROP_VISIBLE_CHILD,
179 PROP_VISIBLE_CHILD_NAME,
180 PROP_TRANSITION_DURATION,
181 PROP_TRANSITION_TYPE,
182 PROP_TRANSITION_RUNNING,
183 PROP_INTERPOLATE_SIZE,
184 PROP_PAGES,
185 LAST_PROP
186};
187
188enum
189{
190 CHILD_PROP_0,
191 CHILD_PROP_CHILD,
192 CHILD_PROP_NAME,
193 CHILD_PROP_TITLE,
194 CHILD_PROP_ICON_NAME,
195 CHILD_PROP_NEEDS_ATTENTION,
196 CHILD_PROP_VISIBLE,
197 CHILD_PROP_USE_UNDERLINE,
198 LAST_CHILD_PROP,
199
200 PROP_ACCESSIBLE_ROLE
201};
202
203struct _GtkStackPage
204{
205 GObject parent_instance;
206
207 GtkWidget *widget;
208 char *name;
209 char *title;
210 char *icon_name;
211 GtkWidget *last_focus;
212
213 GtkATContext *at_context;
214
215 guint needs_attention : 1;
216 guint visible : 1;
217 guint use_underline : 1;
218};
219
220typedef struct _GtkStackPageClass GtkStackPageClass;
221struct _GtkStackPageClass
222{
223 GObjectClass parent_class;
224};
225
226static GParamSpec *stack_props[LAST_PROP] = { NULL, };
227static GParamSpec *stack_page_props[LAST_CHILD_PROP] = { NULL, };
228
229static GtkATContext *
230gtk_stack_page_accessible_get_at_context (GtkAccessible *accessible)
231{
232 GtkStackPage *page = GTK_STACK_PAGE (accessible);
233
234 if (page->at_context == NULL)
235 {
236 GtkAccessibleRole role = GTK_ACCESSIBLE_ROLE_TAB_PANEL;
237 GdkDisplay *display;
238
239 if (page->widget != NULL)
240 display = gtk_widget_get_display (widget: page->widget);
241 else
242 display = gdk_display_get_default ();
243
244 page->at_context = gtk_at_context_create (accessible_role: role, accessible, display);
245 }
246
247 return page->at_context;
248}
249
250static gboolean
251gtk_stack_page_accessible_get_platform_state (GtkAccessible *self,
252 GtkAccessiblePlatformState state)
253{
254 return FALSE;
255}
256
257static void
258gtk_stack_page_accessible_init (GtkAccessibleInterface *iface)
259{
260 iface->get_at_context = gtk_stack_page_accessible_get_at_context;
261 iface->get_platform_state = gtk_stack_page_accessible_get_platform_state;
262}
263
264G_DEFINE_TYPE_WITH_CODE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT,
265 G_IMPLEMENT_INTERFACE (GTK_TYPE_ACCESSIBLE,
266 gtk_stack_page_accessible_init))
267
268static void
269gtk_stack_page_init (GtkStackPage *page)
270{
271 page->visible = TRUE;
272}
273
274static void
275gtk_stack_page_finalize (GObject *object)
276{
277 GtkStackPage *page = GTK_STACK_PAGE (object);
278
279 g_clear_object (&page->widget);
280 g_free (mem: page->name);
281 g_free (mem: page->title);
282 g_free (mem: page->icon_name);
283
284 if (page->last_focus)
285 g_object_remove_weak_pointer (G_OBJECT (page->last_focus),
286 weak_pointer_location: (gpointer *)&page->last_focus);
287
288 g_clear_object (&page->at_context);
289
290 G_OBJECT_CLASS (gtk_stack_page_parent_class)->finalize (object);
291}
292
293static void
294gtk_stack_page_dispose (GObject *object)
295{
296 GtkStackPage *page = GTK_STACK_PAGE (object);
297
298 if (page->at_context != NULL)
299 gtk_at_context_unrealize (self: page->at_context);
300
301 G_OBJECT_CLASS (gtk_stack_page_parent_class)->dispose (object);
302}
303
304static void
305gtk_stack_page_get_property (GObject *object,
306 guint property_id,
307 GValue *value,
308 GParamSpec *pspec)
309{
310 GtkStackPage *info = GTK_STACK_PAGE (object);
311
312 switch (property_id)
313 {
314 case CHILD_PROP_CHILD:
315 g_value_set_object (value, v_object: info->widget);
316 break;
317
318 case CHILD_PROP_NAME:
319 g_value_set_string (value, v_string: gtk_stack_page_get_name (self: info));
320 break;
321
322 case CHILD_PROP_TITLE:
323 g_value_set_string (value, v_string: gtk_stack_page_get_title (self: info));
324 break;
325
326 case CHILD_PROP_ICON_NAME:
327 g_value_set_string (value, v_string: gtk_stack_page_get_icon_name (self: info));
328 break;
329
330 case CHILD_PROP_NEEDS_ATTENTION:
331 g_value_set_boolean (value, v_boolean: gtk_stack_page_get_needs_attention (self: info));
332 break;
333
334 case CHILD_PROP_VISIBLE:
335 g_value_set_boolean (value, v_boolean: gtk_stack_page_get_visible (self: info));
336 break;
337
338 case CHILD_PROP_USE_UNDERLINE:
339 g_value_set_boolean (value, v_boolean: gtk_stack_page_get_use_underline (self: info));
340 break;
341
342 case PROP_ACCESSIBLE_ROLE:
343 g_value_set_enum (value, v_enum: GTK_ACCESSIBLE_ROLE_TAB_PANEL);
344 break;
345
346 default:
347 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
348 break;
349 }
350}
351
352static void
353gtk_stack_page_set_property (GObject *object,
354 guint property_id,
355 const GValue *value,
356 GParamSpec *pspec)
357{
358 GtkStackPage *info = GTK_STACK_PAGE (object);
359
360 switch (property_id)
361 {
362 case CHILD_PROP_CHILD:
363 g_set_object (&info->widget, g_value_get_object (value));
364 break;
365
366 case CHILD_PROP_NAME:
367 gtk_stack_page_set_name (self: info, setting: g_value_get_string (value));
368 break;
369
370 case CHILD_PROP_TITLE:
371 gtk_stack_page_set_title (self: info, setting: g_value_get_string (value));
372 break;
373
374 case CHILD_PROP_ICON_NAME:
375 gtk_stack_page_set_icon_name (self: info, setting: g_value_get_string (value));
376 break;
377
378 case CHILD_PROP_NEEDS_ATTENTION:
379 gtk_stack_page_set_needs_attention (self: info, setting: g_value_get_boolean (value));
380 break;
381
382 case CHILD_PROP_VISIBLE:
383 gtk_stack_page_set_visible (self: info, visible: g_value_get_boolean (value));
384 break;
385
386 case CHILD_PROP_USE_UNDERLINE:
387 gtk_stack_page_set_use_underline (self: info, setting: g_value_get_boolean (value));
388 break;
389
390 case PROP_ACCESSIBLE_ROLE:
391 break;
392
393 default:
394 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
395 break;
396 }
397}
398static void
399gtk_stack_page_class_init (GtkStackPageClass *class)
400{
401 GObjectClass *object_class = G_OBJECT_CLASS (class);
402
403 object_class->finalize = gtk_stack_page_finalize;
404 object_class->dispose = gtk_stack_page_dispose;
405 object_class->get_property = gtk_stack_page_get_property;
406 object_class->set_property = gtk_stack_page_set_property;
407
408 /**
409 * GtkStackPage:child: (attributes org.gtk.Property.get=gtk_stack_page_get_child)
410 *
411 * The child that this page is for.
412 */
413 stack_page_props[CHILD_PROP_CHILD] =
414 g_param_spec_object (name: "child",
415 P_("Child"),
416 P_("The child of the page"),
417 GTK_TYPE_WIDGET,
418 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
419
420 /**
421 * GtkStackPage:name: (attributes org.gtk.Property.get=gtk_stack_page_get_name org.gtk.Property.set=gtk_stack_page_set_name)
422 *
423 * The name of the child page.
424 */
425 stack_page_props[CHILD_PROP_NAME] =
426 g_param_spec_string (name: "name",
427 P_("Name"),
428 P_("The name of the child page"),
429 NULL,
430 GTK_PARAM_READWRITE);
431
432 /**
433 * GtkStackPage:title: (attributes org.gtk.Property.get=gtk_stack_page_get_title org.gtk.Property.set=gtk_stack_page_set_title)
434 *
435 * The title of the child page.
436 */
437 stack_page_props[CHILD_PROP_TITLE] =
438 g_param_spec_string (name: "title",
439 P_("Title"),
440 P_("The title of the child page"),
441 NULL,
442 GTK_PARAM_READWRITE);
443
444 /**
445 * GtkStackPage:icon-name: (attributes org.gtk.Property.get=gtk_stack_page_get_icon_name org.gtk.Property.set=gtk_stack_page_set_icon_name)
446 *
447 * The icon name of the child page.
448 */
449 stack_page_props[CHILD_PROP_ICON_NAME] =
450 g_param_spec_string (name: "icon-name",
451 P_("Icon name"),
452 P_("The icon name of the child page"),
453 NULL,
454 GTK_PARAM_READWRITE);
455
456 /**
457 * GtkStackPage:needs-attention: (attributes org.gtk.Property.get=gtk_stack_page_get_needs_attention org.gtk.Property.set=gtk_stack_page_set_needs_attention)
458 *
459 * Whether the page requires the user attention.
460 *
461 * This is used by the [class@Gtk.StackSwitcher] to change the
462 * appearance of the corresponding button when a page needs
463 * attention and it is not the current one.
464 */
465 stack_page_props[CHILD_PROP_NEEDS_ATTENTION] =
466 g_param_spec_boolean (name: "needs-attention",
467 P_("Needs Attention"),
468 P_("Whether this page needs attention"),
469 FALSE,
470 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
471
472 /**
473 * GtkStackPage:visible: (attributes org.gtk.Property.get=gtk_stack_page_get_visible org.gtk.Property.set=gtk_stack_page_set_visible)
474 *
475 * Whether this page is visible.
476 */
477 stack_page_props[CHILD_PROP_VISIBLE] =
478 g_param_spec_boolean (name: "visible",
479 P_("Visible"),
480 P_("Whether this page is visible"),
481 TRUE,
482 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
483
484 /**
485 * GtkStackPage:use-underline: (attributes org.gtk.Property.get=gtk_stack_page_get_use_underline org.gtk.Property.set=gtk_stack_page_set_use_underline)
486 *
487 * If set, an underline in the title indicates a mnemonic.
488 */
489 stack_page_props[CHILD_PROP_USE_UNDERLINE] =
490 g_param_spec_boolean (name: "use-underline",
491 P_("Use underline"),
492 P_("If set, an underline in the title indicates the next character should be used for the mnemonic accelerator key"),
493 FALSE,
494 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
495
496 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_CHILD_PROP, pspecs: stack_page_props);
497
498 g_object_class_override_property (oclass: object_class, property_id: PROP_ACCESSIBLE_ROLE, name: "accessible-role");
499}
500
501#define GTK_TYPE_STACK_PAGES (gtk_stack_pages_get_type ())
502G_DECLARE_FINAL_TYPE (GtkStackPages, gtk_stack_pages, GTK, STACK_PAGES, GObject)
503
504struct _GtkStackPages
505{
506 GObject parent_instance;
507 GtkStack *stack;
508};
509
510struct _GtkStackPagesClass
511{
512 GObjectClass parent_class;
513};
514
515static GType
516gtk_stack_pages_get_item_type (GListModel *model)
517{
518 return GTK_TYPE_STACK_PAGE;
519}
520
521static guint
522gtk_stack_pages_get_n_items (GListModel *model)
523{
524 GtkStackPages *pages = GTK_STACK_PAGES (ptr: model);
525 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: pages->stack);
526
527 return g_list_length (list: priv->children);
528}
529
530static gpointer
531gtk_stack_pages_get_item (GListModel *model,
532 guint position)
533{
534 GtkStackPages *pages = GTK_STACK_PAGES (ptr: model);
535 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: pages->stack);
536 GtkStackPage *page;
537
538 page = g_list_nth_data (list: priv->children, n: position);
539
540 if (!page)
541 return NULL;
542
543 return g_object_ref (page);
544}
545
546static void
547gtk_stack_pages_list_model_init (GListModelInterface *iface)
548{
549 iface->get_item_type = gtk_stack_pages_get_item_type;
550 iface->get_n_items = gtk_stack_pages_get_n_items;
551 iface->get_item = gtk_stack_pages_get_item;
552}
553
554static gboolean
555gtk_stack_pages_is_selected (GtkSelectionModel *model,
556 guint position)
557{
558 GtkStackPages *pages = GTK_STACK_PAGES (ptr: model);
559 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: pages->stack);
560 GtkStackPage *page;
561
562 page = g_list_nth_data (list: priv->children, n: position);
563
564 return page && page == priv->visible_child;
565}
566
567static void set_visible_child (GtkStack *stack,
568 GtkStackPage *child_info,
569 GtkStackTransitionType transition_type,
570 guint transition_duration);
571
572static gboolean
573gtk_stack_pages_select_item (GtkSelectionModel *model,
574 guint position,
575 gboolean exclusive)
576{
577 GtkStackPages *pages = GTK_STACK_PAGES (ptr: model);
578 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: pages->stack);
579 GtkStackPage *page;
580
581 page = g_list_nth_data (list: priv->children, n: position);
582
583 set_visible_child (stack: pages->stack, child_info: page, transition_type: priv->transition_type, transition_duration: priv->transition_duration);
584
585 return TRUE;
586}
587
588static void
589gtk_stack_pages_selection_model_init (GtkSelectionModelInterface *iface)
590{
591 iface->is_selected = gtk_stack_pages_is_selected;
592 iface->select_item = gtk_stack_pages_select_item;
593}
594
595G_DEFINE_TYPE_WITH_CODE (GtkStackPages, gtk_stack_pages, G_TYPE_OBJECT,
596 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_stack_pages_list_model_init)
597 G_IMPLEMENT_INTERFACE (GTK_TYPE_SELECTION_MODEL, gtk_stack_pages_selection_model_init))
598
599static void
600gtk_stack_pages_init (GtkStackPages *pages)
601{
602}
603
604static void
605gtk_stack_pages_class_init (GtkStackPagesClass *class)
606{
607}
608
609static GtkStackPages *
610gtk_stack_pages_new (GtkStack *stack)
611{
612 GtkStackPages *pages;
613
614 pages = g_object_new (GTK_TYPE_STACK_PAGES, NULL);
615 pages->stack = stack;
616
617 return pages;
618}
619
620static GtkStackPage *gtk_stack_add_internal (GtkStack *stack,
621 GtkWidget *child,
622 const char *name,
623 const char *title);
624
625static GtkSizeRequestMode gtk_stack_get_request_mode (GtkWidget *widget);
626static void gtk_stack_compute_expand (GtkWidget *widget,
627 gboolean *hexpand,
628 gboolean *vexpand);
629static void gtk_stack_size_allocate (GtkWidget *widget,
630 int width,
631 int height,
632 int baseline);
633static void gtk_stack_snapshot (GtkWidget *widget,
634 GtkSnapshot *snapshot);
635static void gtk_stack_measure (GtkWidget *widget,
636 GtkOrientation orientation,
637 int for_size,
638 int *minimum,
639 int *natural,
640 int *minimum_baseline,
641 int *natural_baseline);
642static void gtk_stack_dispose (GObject *obj);
643static void gtk_stack_finalize (GObject *obj);
644static void gtk_stack_get_property (GObject *object,
645 guint property_id,
646 GValue *value,
647 GParamSpec *pspec);
648static void gtk_stack_set_property (GObject *object,
649 guint property_id,
650 const GValue *value,
651 GParamSpec *pspec);
652static void gtk_stack_unschedule_ticks (GtkStack *stack);
653
654
655static void gtk_stack_add_page (GtkStack *stack,
656 GtkStackPage *page);
657
658static GtkBuildableIface *parent_buildable_iface;
659
660static void
661gtk_stack_buildable_add_child (GtkBuildable *buildable,
662 GtkBuilder *builder,
663 GObject *child,
664 const char *type)
665{
666 if (GTK_IS_STACK_PAGE (child))
667 gtk_stack_add_page (GTK_STACK (buildable), GTK_STACK_PAGE (child));
668 else if (GTK_IS_WIDGET (child))
669 gtk_stack_add_internal (GTK_STACK (buildable), GTK_WIDGET (child), NULL, NULL);
670 else
671 parent_buildable_iface->add_child (buildable, builder, child, type);
672}
673
674static void
675gtk_stack_buildable_interface_init (GtkBuildableIface *iface)
676{
677 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
678
679 iface->add_child = gtk_stack_buildable_add_child;
680}
681
682static void stack_remove (GtkStack *stack,
683 GtkWidget *child,
684 gboolean in_dispose);
685
686static void
687gtk_stack_dispose (GObject *obj)
688{
689 GtkStack *stack = GTK_STACK (obj);
690 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
691 GtkWidget *child;
692 guint n_pages = g_list_length (list: priv->children);
693
694 while ((child = gtk_widget_get_first_child (GTK_WIDGET (stack))))
695 stack_remove (stack, child, TRUE);
696
697 if (priv->pages)
698 g_list_model_items_changed (list: G_LIST_MODEL (ptr: priv->pages), position: 0, removed: n_pages, added: 0);
699
700 G_OBJECT_CLASS (gtk_stack_parent_class)->dispose (obj);
701}
702
703static void
704gtk_stack_finalize (GObject *obj)
705{
706 GtkStack *stack = GTK_STACK (obj);
707 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
708
709 if (priv->pages)
710 g_object_remove_weak_pointer (G_OBJECT (priv->pages), weak_pointer_location: (gpointer *)&priv->pages);
711
712 gtk_stack_unschedule_ticks (stack);
713
714 G_OBJECT_CLASS (gtk_stack_parent_class)->finalize (obj);
715}
716
717static void
718gtk_stack_get_property (GObject *object,
719 guint property_id,
720 GValue *value,
721 GParamSpec *pspec)
722{
723 GtkStack *stack = GTK_STACK (object);
724
725 switch (property_id)
726 {
727 case PROP_HHOMOGENEOUS:
728 g_value_set_boolean (value, v_boolean: gtk_stack_get_hhomogeneous (stack));
729 break;
730 case PROP_VHOMOGENEOUS:
731 g_value_set_boolean (value, v_boolean: gtk_stack_get_vhomogeneous (stack));
732 break;
733 case PROP_VISIBLE_CHILD:
734 g_value_set_object (value, v_object: gtk_stack_get_visible_child (stack));
735 break;
736 case PROP_VISIBLE_CHILD_NAME:
737 g_value_set_string (value, v_string: gtk_stack_get_visible_child_name (stack));
738 break;
739 case PROP_TRANSITION_DURATION:
740 g_value_set_uint (value, v_uint: gtk_stack_get_transition_duration (stack));
741 break;
742 case PROP_TRANSITION_TYPE:
743 g_value_set_enum (value, v_enum: gtk_stack_get_transition_type (stack));
744 break;
745 case PROP_TRANSITION_RUNNING:
746 g_value_set_boolean (value, v_boolean: gtk_stack_get_transition_running (stack));
747 break;
748 case PROP_INTERPOLATE_SIZE:
749 g_value_set_boolean (value, v_boolean: gtk_stack_get_interpolate_size (stack));
750 break;
751 case PROP_PAGES:
752 g_value_take_object (value, v_object: gtk_stack_get_pages (stack));
753 break;
754 default:
755 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
756 break;
757 }
758}
759
760static void
761gtk_stack_set_property (GObject *object,
762 guint property_id,
763 const GValue *value,
764 GParamSpec *pspec)
765{
766 GtkStack *stack = GTK_STACK (object);
767
768 switch (property_id)
769 {
770 case PROP_HHOMOGENEOUS:
771 gtk_stack_set_hhomogeneous (stack, hhomogeneous: g_value_get_boolean (value));
772 break;
773 case PROP_VHOMOGENEOUS:
774 gtk_stack_set_vhomogeneous (stack, vhomogeneous: g_value_get_boolean (value));
775 break;
776 case PROP_VISIBLE_CHILD:
777 gtk_stack_set_visible_child (stack, child: g_value_get_object (value));
778 break;
779 case PROP_VISIBLE_CHILD_NAME:
780 gtk_stack_set_visible_child_name (stack, name: g_value_get_string (value));
781 break;
782 case PROP_TRANSITION_DURATION:
783 gtk_stack_set_transition_duration (stack, duration: g_value_get_uint (value));
784 break;
785 case PROP_TRANSITION_TYPE:
786 gtk_stack_set_transition_type (stack, transition: g_value_get_enum (value));
787 break;
788 case PROP_INTERPOLATE_SIZE:
789 gtk_stack_set_interpolate_size (stack, interpolate_size: g_value_get_boolean (value));
790 break;
791 default:
792 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
793 break;
794 }
795}
796
797static void
798gtk_stack_class_init (GtkStackClass *klass)
799{
800 GObjectClass *object_class = G_OBJECT_CLASS (klass);
801 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
802
803 object_class->get_property = gtk_stack_get_property;
804 object_class->set_property = gtk_stack_set_property;
805 object_class->dispose = gtk_stack_dispose;
806 object_class->finalize = gtk_stack_finalize;
807
808 widget_class->size_allocate = gtk_stack_size_allocate;
809 widget_class->snapshot = gtk_stack_snapshot;
810 widget_class->measure = gtk_stack_measure;
811 widget_class->compute_expand = gtk_stack_compute_expand;
812 widget_class->get_request_mode = gtk_stack_get_request_mode;
813
814 /**
815 * GtkStack:hhomogeneous: (attributes org.gtk.Property.get=gtk_stack_get_hhomogeneous org.gtk.Property.set=gtk_stack_set_hhomogeneous)
816 *
817 * %TRUE if the stack allocates the same width for all children.
818 */
819 stack_props[PROP_HHOMOGENEOUS] =
820 g_param_spec_boolean (name: "hhomogeneous", P_("Horizontally homogeneous"), P_("Horizontally homogeneous sizing"),
821 TRUE,
822 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
823
824 /**
825 * GtkStack:vhomogeneous: (attributes org.gtk.Property.get=gtk_stack_get_vhomogeneous org.gtk.Property.set=gtk_stack_set_vhomogeneous)
826 *
827 * %TRUE if the stack allocates the same height for all children.
828 */
829 stack_props[PROP_VHOMOGENEOUS] =
830 g_param_spec_boolean (name: "vhomogeneous", P_("Vertically homogeneous"), P_("Vertically homogeneous sizing"),
831 TRUE,
832 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
833
834 /**
835 * GtkStack:visible-child: (attributes org.gtk.Property.get=gtk_stack_get_visible_child org.gtk.Property.set=gtk_stack_set_visible_child)
836 *
837 * The widget currently visible in the stack.
838 */
839 stack_props[PROP_VISIBLE_CHILD] =
840 g_param_spec_object (name: "visible-child", P_("Visible child"), P_("The widget currently visible in the stack"),
841 GTK_TYPE_WIDGET,
842 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
843
844 /**
845 * GtkStack:visible-child-name: (attributes org.gtk.Property.get=gtk_stack_get_visible_child_name org.gtk.Poperty.set=gtk_stack_set_visible_child_name)
846 *
847 * The name of the widget currently visible in the stack.
848 */
849 stack_props[PROP_VISIBLE_CHILD_NAME] =
850 g_param_spec_string (name: "visible-child-name", P_("Name of visible child"), P_("The name of the widget currently visible in the stack"),
851 NULL,
852 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
853
854 /**
855 * GtkStack:transition-duration: (attributes org.gtk.Property.get=gtk_stack_get_transition_duration org.gtk.Property.set=gtk_stack_set_transition_duration)
856 *
857 * The animation duration, in milliseconds.
858 */
859 stack_props[PROP_TRANSITION_DURATION] =
860 g_param_spec_uint (name: "transition-duration", P_("Transition duration"), P_("The animation duration, in milliseconds"),
861 minimum: 0, G_MAXUINT, default_value: 200,
862 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
863
864 /**
865 * GtkStack:transition-type: (attributes org.gtk.Property.get=gtk_stack_get_transition_type org.gtk.Property.set=gtk_stack_set_transition_type)
866 *
867 * The type of animation used to transition.
868 */
869 stack_props[PROP_TRANSITION_TYPE] =
870 g_param_spec_enum (name: "transition-type", P_("Transition type"), P_("The type of animation used to transition"),
871 enum_type: GTK_TYPE_STACK_TRANSITION_TYPE, default_value: GTK_STACK_TRANSITION_TYPE_NONE,
872 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
873
874 /**
875 * GtkStack:transition-running: (attributes org.gtk.Property.get=gtk_stack_get_transition_running)
876 *
877 * Whether or not the transition is currently running.
878 */
879 stack_props[PROP_TRANSITION_RUNNING] =
880 g_param_spec_boolean (name: "transition-running", P_("Transition running"), P_("Whether or not the transition is currently running"),
881 FALSE,
882 GTK_PARAM_READABLE);
883
884 /**
885 * GtkStack:interpolate-size: (attributes org.gtk.Property.get=gtk_stack_get_interpolate_size org.gtk.Property.set=gtk_stack_set_interpolate_size)
886 *
887 * Whether or not the size should smoothly change during the transition.
888 */
889 stack_props[PROP_INTERPOLATE_SIZE] =
890 g_param_spec_boolean (name: "interpolate-size", P_("Interpolate size"), P_("Whether or not the size should smoothly change when changing between differently sized children"),
891 FALSE,
892 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
893
894 /**
895 * GtkStack:pages: (attributes org.gtk.Property.get=gtk_stack_get_pages)
896 *
897 * A selection model with the stack pages.
898 */
899 stack_props[PROP_PAGES] =
900 g_param_spec_object (name: "pages", P_("Pages"), P_("A selection model with the stacks pages"),
901 GTK_TYPE_SELECTION_MODEL,
902 GTK_PARAM_READABLE);
903
904 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: stack_props);
905
906 gtk_widget_class_set_css_name (widget_class, I_("stack"));
907 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_GROUP);
908}
909
910/**
911 * gtk_stack_new:
912 *
913 * Creates a new `GtkStack`.
914 *
915 * Returns: a new `GtkStack`
916 */
917GtkWidget *
918gtk_stack_new (void)
919{
920 return g_object_new (GTK_TYPE_STACK, NULL);
921}
922
923static GtkStackPage *
924find_child_info_for_widget (GtkStack *stack,
925 GtkWidget *child)
926{
927 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
928 GtkStackPage *info;
929 GList *l;
930
931 for (l = priv->children; l != NULL; l = l->next)
932 {
933 info = l->data;
934 if (info->widget == child)
935 return info;
936 }
937
938 return NULL;
939}
940
941static inline gboolean
942is_left_transition (GtkStackTransitionType transition_type)
943{
944 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
945 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT);
946}
947
948static inline gboolean
949is_right_transition (GtkStackTransitionType transition_type)
950{
951 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
952 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
953}
954
955static inline gboolean
956is_up_transition (GtkStackTransitionType transition_type)
957{
958 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
959 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP);
960}
961
962static inline gboolean
963is_down_transition (GtkStackTransitionType transition_type)
964{
965 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
966 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN);
967}
968
969/* Transitions that cause the bin window to move */
970static inline gboolean
971is_window_moving_transition (GtkStackTransitionType transition_type)
972{
973 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
974 transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
975 transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
976 transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
977 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP ||
978 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN ||
979 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT ||
980 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
981}
982
983/* Transitions that change direction depending on the relative order of the
984old and new child */
985static inline gboolean
986is_direction_dependent_transition (GtkStackTransitionType transition_type)
987{
988 return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT ||
989 transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN ||
990 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN ||
991 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP ||
992 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT ||
993 transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT ||
994 transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT);
995}
996
997/* Returns simple transition type for a direction dependent transition, given
998whether the new child (the one being switched to) is first in the stacking order
999(added earlier). */
1000static inline GtkStackTransitionType
1001get_simple_transition_type (gboolean new_child_first,
1002 GtkStackTransitionType transition_type)
1003{
1004 switch (transition_type)
1005 {
1006 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
1007 return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT : GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
1008 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
1009 return new_child_first ? GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT : GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT;
1010 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
1011 return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN : GTK_STACK_TRANSITION_TYPE_SLIDE_UP;
1012 case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
1013 return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_DOWN : GTK_STACK_TRANSITION_TYPE_OVER_UP;
1014 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
1015 return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_UP : GTK_STACK_TRANSITION_TYPE_OVER_DOWN;
1016 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
1017 return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT : GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
1018 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
1019 return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_LEFT : GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
1020 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
1021 case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
1022 case GTK_STACK_TRANSITION_TYPE_OVER_UP:
1023 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
1024 case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
1025 case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
1026 case GTK_STACK_TRANSITION_TYPE_NONE:
1027 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
1028 case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
1029 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
1030 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
1031 case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
1032 case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
1033 case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
1034 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
1035 case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
1036 default:
1037 return transition_type;
1038 }
1039}
1040
1041static int
1042get_bin_window_x (GtkStack *stack)
1043{
1044 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1045 int width;
1046 int x = 0;
1047
1048 width = gtk_widget_get_width (GTK_WIDGET (stack));
1049
1050 if (gtk_progress_tracker_get_state (tracker: &priv->tracker) != GTK_PROGRESS_STATE_AFTER)
1051 {
1052 if (is_left_transition (transition_type: priv->active_transition_type))
1053 x = width * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
1054 if (is_right_transition (transition_type: priv->active_transition_type))
1055 x = -width * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
1056 }
1057
1058 return x;
1059}
1060
1061static int
1062get_bin_window_y (GtkStack *stack)
1063{
1064 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1065 int height;
1066 int y = 0;
1067
1068 height = gtk_widget_get_height (GTK_WIDGET (stack));
1069
1070 if (gtk_progress_tracker_get_state (tracker: &priv->tracker) != GTK_PROGRESS_STATE_AFTER)
1071 {
1072 if (is_up_transition (transition_type: priv->active_transition_type))
1073 y = height * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
1074 if (is_down_transition(transition_type: priv->active_transition_type))
1075 y = -height * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
1076 }
1077
1078 return y;
1079}
1080
1081static void
1082gtk_stack_progress_updated (GtkStack *stack)
1083{
1084 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1085
1086 if (!priv->homogeneous[GTK_ORIENTATION_VERTICAL] || !priv->homogeneous[GTK_ORIENTATION_HORIZONTAL])
1087 gtk_widget_queue_resize (GTK_WIDGET (stack));
1088 else if (is_window_moving_transition (transition_type: priv->active_transition_type))
1089 gtk_widget_queue_allocate (GTK_WIDGET (stack));
1090 else
1091 gtk_widget_queue_draw (GTK_WIDGET (stack));
1092
1093 if (gtk_progress_tracker_get_state (tracker: &priv->tracker) == GTK_PROGRESS_STATE_AFTER &&
1094 priv->last_visible_child != NULL)
1095 {
1096 gtk_widget_set_child_visible (widget: priv->last_visible_child->widget, FALSE);
1097 priv->last_visible_child = NULL;
1098 }
1099}
1100
1101static gboolean
1102gtk_stack_transition_cb (GtkWidget *widget,
1103 GdkFrameClock *frame_clock,
1104 gpointer user_data)
1105{
1106 GtkStack *stack = GTK_STACK (widget);
1107 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1108
1109 if (priv->first_frame_skipped)
1110 gtk_progress_tracker_advance_frame (tracker: &priv->tracker,
1111 frame_time: gdk_frame_clock_get_frame_time (frame_clock));
1112 else
1113 priv->first_frame_skipped = TRUE;
1114
1115 /* Finish animation early if not mapped anymore */
1116 if (!gtk_widget_get_mapped (widget))
1117 gtk_progress_tracker_finish (tracker: &priv->tracker);
1118
1119 gtk_stack_progress_updated (GTK_STACK (widget));
1120
1121 if (gtk_progress_tracker_get_state (tracker: &priv->tracker) == GTK_PROGRESS_STATE_AFTER)
1122 {
1123 priv->tick_id = 0;
1124 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_TRANSITION_RUNNING]);
1125
1126 return FALSE;
1127 }
1128
1129 return TRUE;
1130}
1131
1132static void
1133gtk_stack_schedule_ticks (GtkStack *stack)
1134{
1135 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1136
1137 if (priv->tick_id == 0)
1138 {
1139 priv->tick_id =
1140 gtk_widget_add_tick_callback (GTK_WIDGET (stack), callback: gtk_stack_transition_cb, user_data: stack, NULL);
1141 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_TRANSITION_RUNNING]);
1142 }
1143}
1144
1145static void
1146gtk_stack_unschedule_ticks (GtkStack *stack)
1147{
1148 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1149
1150 if (priv->tick_id != 0)
1151 {
1152 gtk_widget_remove_tick_callback (GTK_WIDGET (stack), id: priv->tick_id);
1153 priv->tick_id = 0;
1154 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_TRANSITION_RUNNING]);
1155 }
1156}
1157
1158static GtkStackTransitionType
1159effective_transition_type (GtkStack *stack,
1160 GtkStackTransitionType transition_type)
1161{
1162 if (_gtk_widget_get_direction (GTK_WIDGET (stack)) == GTK_TEXT_DIR_RTL)
1163 {
1164 switch (transition_type)
1165 {
1166 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
1167 return GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT;
1168 case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
1169 return GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
1170 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
1171 return GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT;
1172 case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
1173 return GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT;
1174 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
1175 return GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
1176 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
1177 return GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
1178 case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
1179 return GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT;
1180 case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
1181 return GTK_STACK_TRANSITION_TYPE_UNDER_LEFT;
1182 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
1183 case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
1184 case GTK_STACK_TRANSITION_TYPE_OVER_UP:
1185 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
1186 case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
1187 case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
1188 case GTK_STACK_TRANSITION_TYPE_NONE:
1189 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
1190 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
1191 case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
1192 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
1193 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
1194 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
1195 case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
1196 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
1197 default:
1198 return transition_type;
1199 }
1200 }
1201 else
1202 {
1203 return transition_type;
1204 }
1205}
1206
1207static void
1208gtk_stack_start_transition (GtkStack *stack,
1209 GtkStackTransitionType transition_type,
1210 guint transition_duration)
1211{
1212 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1213 GtkWidget *widget = GTK_WIDGET (stack);
1214
1215 if (gtk_widget_get_mapped (widget) &&
1216 gtk_settings_get_enable_animations (settings: gtk_widget_get_settings (widget)) &&
1217 transition_type != GTK_STACK_TRANSITION_TYPE_NONE &&
1218 transition_duration != 0 &&
1219 priv->last_visible_child != NULL)
1220 {
1221 priv->active_transition_type = effective_transition_type (stack, transition_type);
1222 priv->first_frame_skipped = FALSE;
1223 gtk_stack_schedule_ticks (stack);
1224 gtk_progress_tracker_start (tracker: &priv->tracker,
1225 duration: priv->transition_duration * 1000,
1226 delay: 0,
1227 iteration_count: 1.0);
1228 }
1229 else
1230 {
1231 gtk_stack_unschedule_ticks (stack);
1232 priv->active_transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
1233 gtk_progress_tracker_finish (tracker: &priv->tracker);
1234 }
1235
1236 gtk_stack_progress_updated (GTK_STACK (widget));
1237}
1238
1239static void
1240set_visible_child (GtkStack *stack,
1241 GtkStackPage *child_info,
1242 GtkStackTransitionType transition_type,
1243 guint transition_duration)
1244{
1245 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1246 GtkStackPage *info;
1247 GtkWidget *widget = GTK_WIDGET (stack);
1248 GList *l;
1249 GtkWidget *focus;
1250 gboolean contains_focus = FALSE;
1251 guint old_pos = GTK_INVALID_LIST_POSITION;
1252 guint new_pos = GTK_INVALID_LIST_POSITION;
1253
1254 /* if we are being destroyed, do not bother with transitions
1255 * and notifications
1256 */
1257 if (gtk_widget_in_destruction (widget))
1258 return;
1259
1260 /* If none, pick first visible */
1261 if (child_info == NULL)
1262 {
1263 for (l = priv->children; l != NULL; l = l->next)
1264 {
1265 info = l->data;
1266 if (gtk_widget_get_visible (widget: info->widget))
1267 {
1268 child_info = info;
1269 break;
1270 }
1271 }
1272 }
1273
1274 if (child_info == priv->visible_child)
1275 return;
1276
1277 if (priv->pages)
1278 {
1279 guint position;
1280 for (l = priv->children, position = 0; l != NULL; l = l->next, position++)
1281 {
1282 info = l->data;
1283 if (info == priv->visible_child)
1284 old_pos = position;
1285 else if (info == child_info)
1286 new_pos = position;
1287 }
1288 }
1289
1290 if (gtk_widget_get_root (widget))
1291 focus = gtk_root_get_focus (self: gtk_widget_get_root (widget));
1292 else
1293 focus = NULL;
1294 if (focus &&
1295 priv->visible_child &&
1296 priv->visible_child->widget &&
1297 gtk_widget_is_ancestor (widget: focus, ancestor: priv->visible_child->widget))
1298 {
1299 contains_focus = TRUE;
1300
1301 if (priv->visible_child->last_focus)
1302 g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
1303 weak_pointer_location: (gpointer *)&priv->visible_child->last_focus);
1304 priv->visible_child->last_focus = focus;
1305 g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
1306 weak_pointer_location: (gpointer *)&priv->visible_child->last_focus);
1307 }
1308
1309 if (priv->last_visible_child)
1310 gtk_widget_set_child_visible (widget: priv->last_visible_child->widget, FALSE);
1311 priv->last_visible_child = NULL;
1312
1313 if (priv->visible_child && priv->visible_child->widget)
1314 {
1315 if (gtk_widget_is_visible (widget))
1316 {
1317 priv->last_visible_child = priv->visible_child;
1318 priv->last_visible_widget_width = gtk_widget_get_width (widget);
1319 priv->last_visible_widget_height = gtk_widget_get_height (widget);
1320 }
1321 else
1322 {
1323 gtk_widget_set_child_visible (widget: priv->visible_child->widget, FALSE);
1324 }
1325 }
1326
1327 priv->visible_child = child_info;
1328
1329 if (child_info)
1330 {
1331 gtk_widget_set_child_visible (widget: child_info->widget, TRUE);
1332
1333 if (contains_focus)
1334 {
1335 if (child_info->last_focus)
1336 gtk_widget_grab_focus (widget: child_info->last_focus);
1337 else
1338 gtk_widget_child_focus (widget: child_info->widget, direction: GTK_DIR_TAB_FORWARD);
1339 }
1340 }
1341
1342 if ((child_info == NULL || priv->last_visible_child == NULL) &&
1343 is_direction_dependent_transition (transition_type))
1344 {
1345 transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
1346 }
1347 else if (is_direction_dependent_transition (transition_type))
1348 {
1349 gboolean i_first = FALSE;
1350 for (l = priv->children; l != NULL; l = l->next)
1351 {
1352 if (child_info == l->data)
1353 {
1354 i_first = TRUE;
1355 break;
1356 }
1357 if (priv->last_visible_child == l->data)
1358 break;
1359 }
1360
1361 transition_type = get_simple_transition_type (new_child_first: i_first, transition_type);
1362 }
1363
1364 if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] && priv->homogeneous[GTK_ORIENTATION_VERTICAL])
1365 gtk_widget_queue_allocate (widget);
1366 else
1367 gtk_widget_queue_resize (widget);
1368
1369 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_VISIBLE_CHILD]);
1370 g_object_notify_by_pspec (G_OBJECT (stack),
1371 pspec: stack_props[PROP_VISIBLE_CHILD_NAME]);
1372
1373 if (priv->pages)
1374 {
1375 if (old_pos == GTK_INVALID_LIST_POSITION && new_pos == GTK_INVALID_LIST_POSITION)
1376 ; /* nothing to do */
1377 else if (old_pos == GTK_INVALID_LIST_POSITION)
1378 gtk_selection_model_selection_changed (model: priv->pages, position: new_pos, n_items: 1);
1379 else if (new_pos == GTK_INVALID_LIST_POSITION)
1380 gtk_selection_model_selection_changed (model: priv->pages, position: old_pos, n_items: 1);
1381 else
1382 gtk_selection_model_selection_changed (model: priv->pages,
1383 MIN (old_pos, new_pos),
1384 MAX (old_pos, new_pos) - MIN (old_pos, new_pos) + 1);
1385 }
1386
1387 gtk_stack_start_transition (stack, transition_type, transition_duration);
1388}
1389
1390static void
1391update_child_visible (GtkStack *stack,
1392 GtkStackPage *child_info)
1393{
1394 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1395 gboolean visible;
1396
1397 visible = child_info->visible && gtk_widget_get_visible (widget: child_info->widget);
1398
1399 if (priv->visible_child == NULL && visible)
1400 set_visible_child (stack, child_info, transition_type: priv->transition_type, transition_duration: priv->transition_duration);
1401 else if (priv->visible_child == child_info && !visible)
1402 set_visible_child (stack, NULL, transition_type: priv->transition_type, transition_duration: priv->transition_duration);
1403
1404 if (child_info == priv->last_visible_child)
1405 {
1406 gtk_widget_set_child_visible (widget: priv->last_visible_child->widget, FALSE);
1407 priv->last_visible_child = NULL;
1408 }
1409
1410 gtk_accessible_update_state (self: GTK_ACCESSIBLE (ptr: child_info),
1411 first_state: GTK_ACCESSIBLE_STATE_HIDDEN, !visible,
1412 -1);
1413}
1414
1415static void
1416stack_child_visibility_notify_cb (GObject *obj,
1417 GParamSpec *pspec,
1418 gpointer user_data)
1419{
1420 GtkStack *stack = GTK_STACK (user_data);
1421 GtkStackPage *child_info;
1422
1423 child_info = find_child_info_for_widget (stack, GTK_WIDGET (obj));
1424 g_return_if_fail (child_info != NULL);
1425
1426 update_child_visible (stack, child_info);
1427}
1428
1429/**
1430 * gtk_stack_add_titled:
1431 * @stack: a `GtkStack`
1432 * @child: the widget to add
1433 * @name: (nullable): the name for @child
1434 * @title: a human-readable title for @child
1435 *
1436 * Adds a child to @stack.
1437 *
1438 * The child is identified by the @name. The @title
1439 * will be used by `GtkStackSwitcher` to represent
1440 * @child in a tab bar, so it should be short.
1441 *
1442 * Returns: (transfer none): the `GtkStackPage` for @child
1443 */
1444GtkStackPage *
1445gtk_stack_add_titled (GtkStack *stack,
1446 GtkWidget *child,
1447 const char *name,
1448 const char *title)
1449{
1450 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1451 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
1452
1453 return gtk_stack_add_internal (stack, child, name, title);
1454}
1455
1456/**
1457 * gtk_stack_add_child:
1458 * @stack: a `GtkStack`
1459 * @child: the widget to add
1460 *
1461 * Adds a child to @stack.
1462 *
1463 * Returns: (transfer none): the `GtkStackPage` for @child
1464 */
1465GtkStackPage *
1466gtk_stack_add_child (GtkStack *stack,
1467 GtkWidget *child)
1468{
1469 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1470 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
1471
1472 return gtk_stack_add_internal (stack, child, NULL, NULL);
1473}
1474
1475/**
1476 * gtk_stack_add_named:
1477 * @stack: a `GtkStack`
1478 * @child: the widget to add
1479 * @name: (nullable): the name for @child
1480 *
1481 * Adds a child to @stack.
1482 *
1483 * The child is identified by the @name.
1484 *
1485 * Returns: (transfer none): the `GtkStackPage` for @child
1486 */
1487GtkStackPage *
1488gtk_stack_add_named (GtkStack *stack,
1489 GtkWidget *child,
1490 const char *name)
1491{
1492 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1493 g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
1494
1495 return gtk_stack_add_internal (stack, child, name, NULL);
1496}
1497
1498static GtkStackPage *
1499gtk_stack_add_internal (GtkStack *stack,
1500 GtkWidget *child,
1501 const char *name,
1502 const char *title)
1503{
1504 GtkStackPage *child_info;
1505
1506 g_return_val_if_fail (child != NULL, NULL);
1507
1508 child_info = g_object_new (GTK_TYPE_STACK_PAGE, NULL);
1509 child_info->widget = g_object_ref (child);
1510 child_info->name = g_strdup (str: name);
1511 child_info->title = g_strdup (str: title);
1512 child_info->icon_name = NULL;
1513 child_info->needs_attention = FALSE;
1514 child_info->last_focus = NULL;
1515
1516 gtk_stack_add_page (stack, page: child_info);
1517
1518 g_object_unref (object: child_info);
1519
1520 return child_info;
1521}
1522
1523static void
1524gtk_stack_add_page (GtkStack *stack,
1525 GtkStackPage *child_info)
1526{
1527 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1528 GList *l;
1529
1530 g_return_if_fail (child_info->widget != NULL);
1531
1532 if (child_info->name)
1533 {
1534 for (l = priv->children; l != NULL; l = l->next)
1535 {
1536 GtkStackPage *info = l->data;
1537 if (info->name &&
1538 g_strcmp0 (str1: info->name, str2: child_info->name) == 0)
1539 {
1540 g_warning ("While adding page: duplicate child name in GtkStack: %s", child_info->name);
1541 break;
1542 }
1543 }
1544 }
1545
1546 priv->children = g_list_append (list: priv->children, g_object_ref (child_info));
1547
1548 gtk_widget_set_child_visible (widget: child_info->widget, FALSE);
1549 gtk_widget_set_parent (widget: child_info->widget, GTK_WIDGET (stack));
1550
1551 if (priv->pages)
1552 g_list_model_items_changed (list: G_LIST_MODEL (ptr: priv->pages), position: g_list_length (list: priv->children) - 1, removed: 0, added: 1);
1553
1554 g_signal_connect (child_info->widget, "notify::visible",
1555 G_CALLBACK (stack_child_visibility_notify_cb), stack);
1556
1557 if (priv->visible_child == NULL &&
1558 gtk_widget_get_visible (widget: child_info->widget))
1559 set_visible_child (stack, child_info, transition_type: priv->transition_type, transition_duration: priv->transition_duration);
1560
1561 if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] || priv->homogeneous[GTK_ORIENTATION_VERTICAL] || priv->visible_child == child_info)
1562 gtk_widget_queue_resize (GTK_WIDGET (stack));
1563}
1564
1565static void
1566stack_remove (GtkStack *stack,
1567 GtkWidget *child,
1568 gboolean in_dispose)
1569{
1570 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1571 GtkStackPage *child_info;
1572 gboolean was_visible;
1573
1574 child_info = find_child_info_for_widget (stack, child);
1575 if (child_info == NULL)
1576 return;
1577
1578 g_signal_handlers_disconnect_by_func (child,
1579 stack_child_visibility_notify_cb,
1580 stack);
1581
1582 was_visible = gtk_widget_get_visible (widget: child);
1583
1584 if (priv->visible_child == child_info)
1585 priv->visible_child = NULL;
1586
1587 if (priv->last_visible_child == child_info)
1588 priv->last_visible_child = NULL;
1589
1590 gtk_widget_unparent (widget: child);
1591
1592 g_clear_object (&child_info->widget);
1593
1594 priv->children = g_list_remove (list: priv->children, data: child_info);
1595
1596 g_object_unref (object: child_info);
1597
1598 if (!in_dispose &&
1599 (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] || priv->homogeneous[GTK_ORIENTATION_VERTICAL]) &&
1600 was_visible)
1601 gtk_widget_queue_resize (GTK_WIDGET (stack));
1602}
1603
1604/**
1605 * gtk_stack_remove:
1606 * @stack: a `GtkStack`
1607 * @child: the child to remove
1608 *
1609 * Removes a child widget from @stack.
1610 */
1611void
1612gtk_stack_remove (GtkStack *stack,
1613 GtkWidget *child)
1614{
1615 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1616 GList *l;
1617 guint position;
1618
1619 g_return_if_fail (GTK_IS_STACK (stack));
1620 g_return_if_fail (GTK_IS_WIDGET (child));
1621 g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (stack));
1622
1623 for (l = priv->children, position = 0; l; l = l->next, position++)
1624 {
1625 GtkStackPage *page = l->data;
1626 if (page->widget == child)
1627 break;
1628 }
1629
1630 stack_remove (stack, child, FALSE);
1631
1632 if (priv->pages)
1633 g_list_model_items_changed (list: G_LIST_MODEL (ptr: priv->pages), position, removed: 1, added: 0);
1634}
1635
1636/**
1637 * gtk_stack_get_page:
1638 * @stack: a `GtkStack`
1639 * @child: a child of @stack
1640 *
1641 * Retrieves the stack page for the given @child.
1642 *
1643 * If the given @child is not a child widget of the stack, this function will return `NULL`.
1644 *
1645 * Returns: (transfer none) (nullable): the stack page object
1646 */
1647GtkStackPage *
1648gtk_stack_get_page (GtkStack *stack,
1649 GtkWidget *child)
1650{
1651 return find_child_info_for_widget (stack, child);
1652}
1653
1654/**
1655 * gtk_stack_get_child_by_name:
1656 * @stack: a `GtkStack`
1657 * @name: the name of the child to find
1658 *
1659 * Finds the child with the name given as the argument.
1660 *
1661 * Returns %NULL if there is no child with this name.
1662 *
1663 * Returns: (transfer none) (nullable): the requested child
1664 * of the `GtkStack`
1665 */
1666GtkWidget *
1667gtk_stack_get_child_by_name (GtkStack *stack,
1668 const char *name)
1669{
1670 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1671 GtkStackPage *info;
1672 GList *l;
1673
1674 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1675 g_return_val_if_fail (name != NULL, NULL);
1676
1677 for (l = priv->children; l != NULL; l = l->next)
1678 {
1679 info = l->data;
1680 if (info->name && strcmp (s1: info->name, s2: name) == 0)
1681 return info->widget;
1682 }
1683
1684 return NULL;
1685}
1686
1687/**
1688 * gtk_stack_page_get_child: (attributes org.gtk.Method.get_property=child)
1689 * @self: a `GtkStackPage`
1690 *
1691 * Returns the stack child to which @self belongs.
1692 *
1693 * Returns: (transfer none): the child to which @self belongs
1694 */
1695GtkWidget *
1696gtk_stack_page_get_child (GtkStackPage *self)
1697{
1698 return self->widget;
1699}
1700
1701/**
1702 * gtk_stack_set_hhomogeneous: (attributes org.gtk.Method.set_property=hhomogeneous)
1703 * @stack: a `GtkStack`
1704 * @hhomogeneous: %TRUE to make @stack horizontally homogeneous
1705 *
1706 * Sets the `GtkStack` to be horizontally homogeneous or not.
1707 *
1708 * If it is homogeneous, the `GtkStack` will request the same
1709 * width for all its children. If it isn't, the stack
1710 * may change width when a different child becomes visible.
1711 */
1712void
1713gtk_stack_set_hhomogeneous (GtkStack *stack,
1714 gboolean hhomogeneous)
1715{
1716 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1717
1718 g_return_if_fail (GTK_IS_STACK (stack));
1719
1720 hhomogeneous = !!hhomogeneous;
1721
1722 if (priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] == hhomogeneous)
1723 return;
1724
1725 priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] = hhomogeneous;
1726
1727 if (gtk_widget_get_visible (GTK_WIDGET(stack)))
1728 gtk_widget_queue_resize (GTK_WIDGET (stack));
1729
1730 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_HHOMOGENEOUS]);
1731}
1732
1733/**
1734 * gtk_stack_get_hhomogeneous: (attributes org.gtk.Method.get_property=hhomogeneous)
1735 * @stack: a `GtkStack`
1736 *
1737 * Gets whether @stack is horizontally homogeneous.
1738 *
1739 * Returns: whether @stack is horizontally homogeneous.
1740 */
1741gboolean
1742gtk_stack_get_hhomogeneous (GtkStack *stack)
1743{
1744 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1745
1746 g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);
1747
1748 return priv->homogeneous[GTK_ORIENTATION_HORIZONTAL];
1749}
1750
1751/**
1752 * gtk_stack_set_vhomogeneous: (attributes org.gtk.Method.set_property=vhomogeneous)
1753 * @stack: a `GtkStack`
1754 * @vhomogeneous: %TRUE to make @stack vertically homogeneous
1755 *
1756 * Sets the `GtkStack` to be vertically homogeneous or not.
1757 *
1758 * If it is homogeneous, the `GtkStack` will request the same
1759 * height for all its children. If it isn't, the stack
1760 * may change height when a different child becomes visible.
1761 */
1762void
1763gtk_stack_set_vhomogeneous (GtkStack *stack,
1764 gboolean vhomogeneous)
1765{
1766 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1767
1768 g_return_if_fail (GTK_IS_STACK (stack));
1769
1770 vhomogeneous = !!vhomogeneous;
1771
1772 if (priv->homogeneous[GTK_ORIENTATION_VERTICAL] == vhomogeneous)
1773 return;
1774
1775 priv->homogeneous[GTK_ORIENTATION_VERTICAL] = vhomogeneous;
1776
1777 if (gtk_widget_get_visible (GTK_WIDGET(stack)))
1778 gtk_widget_queue_resize (GTK_WIDGET (stack));
1779
1780 g_object_notify_by_pspec (G_OBJECT (stack), pspec: stack_props[PROP_VHOMOGENEOUS]);
1781}
1782
1783/**
1784 * gtk_stack_get_vhomogeneous: (attributes org.gtk.Method.get_property=vhomogeneous)
1785 * @stack: a `GtkStack`
1786 *
1787 * Gets whether @stack is vertically homogeneous.
1788 *
1789 * Returns: whether @stack is vertically homogeneous.
1790 */
1791gboolean
1792gtk_stack_get_vhomogeneous (GtkStack *stack)
1793{
1794 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1795
1796 g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);
1797
1798 return priv->homogeneous[GTK_ORIENTATION_VERTICAL];
1799}
1800
1801/**
1802 * gtk_stack_get_transition_duration: (attributes org.gtk.Method.get_property=transition-duration)
1803 * @stack: a `GtkStack`
1804 *
1805 * Returns the amount of time (in milliseconds) that
1806 * transitions between pages in @stack will take.
1807 *
1808 * Returns: the transition duration
1809 */
1810guint
1811gtk_stack_get_transition_duration (GtkStack *stack)
1812{
1813 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1814
1815 g_return_val_if_fail (GTK_IS_STACK (stack), 0);
1816
1817 return priv->transition_duration;
1818}
1819
1820/**
1821 * gtk_stack_set_transition_duration: (attributes org.gtk.Method.set_property=transition-duration)
1822 * @stack: a `GtkStack`
1823 * @duration: the new duration, in milliseconds
1824 *
1825 * Sets the duration that transitions between pages in @stack
1826 * will take.
1827 */
1828void
1829gtk_stack_set_transition_duration (GtkStack *stack,
1830 guint duration)
1831{
1832 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1833
1834 g_return_if_fail (GTK_IS_STACK (stack));
1835
1836 if (priv->transition_duration == duration)
1837 return;
1838
1839 priv->transition_duration = duration;
1840 g_object_notify_by_pspec (G_OBJECT (stack),
1841 pspec: stack_props[PROP_TRANSITION_DURATION]);
1842}
1843
1844/**
1845 * gtk_stack_get_transition_type: (attributes org.gtk.Method.get_property=transition-type)
1846 * @stack: a `GtkStack`
1847 *
1848 * Gets the type of animation that will be used
1849 * for transitions between pages in @stack.
1850 *
1851 * Returns: the current transition type of @stack
1852 */
1853GtkStackTransitionType
1854gtk_stack_get_transition_type (GtkStack *stack)
1855{
1856 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1857
1858 g_return_val_if_fail (GTK_IS_STACK (stack), GTK_STACK_TRANSITION_TYPE_NONE);
1859
1860 return priv->transition_type;
1861}
1862
1863/**
1864 * gtk_stack_set_transition_type: (attributes org.gtk.Method.set_property=transition-type)
1865 * @stack: a `GtkStack`
1866 * @transition: the new transition type
1867 *
1868 * Sets the type of animation that will be used for
1869 * transitions between pages in @stack.
1870 *
1871 * Available types include various kinds of fades and slides.
1872 *
1873 * The transition type can be changed without problems
1874 * at runtime, so it is possible to change the animation
1875 * based on the page that is about to become current.
1876 */
1877void
1878gtk_stack_set_transition_type (GtkStack *stack,
1879 GtkStackTransitionType transition)
1880{
1881 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1882
1883 g_return_if_fail (GTK_IS_STACK (stack));
1884
1885 if (priv->transition_type == transition)
1886 return;
1887
1888 priv->transition_type = transition;
1889 g_object_notify_by_pspec (G_OBJECT (stack),
1890 pspec: stack_props[PROP_TRANSITION_TYPE]);
1891}
1892
1893/**
1894 * gtk_stack_get_transition_running: (attributes org.gtk.Method.get_property=transition-running)
1895 * @stack: a `GtkStack`
1896 *
1897 * Returns whether the @stack is currently in a transition from one page to
1898 * another.
1899 *
1900 * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
1901 */
1902gboolean
1903gtk_stack_get_transition_running (GtkStack *stack)
1904{
1905 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1906
1907 g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);
1908
1909 return (priv->tick_id != 0);
1910}
1911
1912/**
1913 * gtk_stack_set_interpolate_size: (attributes org.gtk.Method.set_property=interpolate-size)
1914 * @stack: A `GtkStack`
1915 * @interpolate_size: the new value
1916 *
1917 * Sets whether or not @stack will interpolate its size when
1918 * changing the visible child.
1919 *
1920 * If the [property@Gtk.Stack:interpolate-size] property is set
1921 * to %TRUE, @stack will interpolate its size between the current
1922 * one and the one it'll take after changing the visible child,
1923 * according to the set transition duration.
1924 */
1925void
1926gtk_stack_set_interpolate_size (GtkStack *stack,
1927 gboolean interpolate_size)
1928{
1929 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1930 g_return_if_fail (GTK_IS_STACK (stack));
1931
1932 interpolate_size = !!interpolate_size;
1933
1934 if (priv->interpolate_size == interpolate_size)
1935 return;
1936
1937 priv->interpolate_size = interpolate_size;
1938 g_object_notify_by_pspec (G_OBJECT (stack),
1939 pspec: stack_props[PROP_INTERPOLATE_SIZE]);
1940}
1941
1942/**
1943 * gtk_stack_get_interpolate_size: (attributes org.gtk.Method.get_property=interpolate-size)
1944 * @stack: A `GtkStack`
1945 *
1946 * Returns whether the `GtkStack` is set up to interpolate between
1947 * the sizes of children on page switch.
1948 *
1949 * Returns: %TRUE if child sizes are interpolated
1950 */
1951gboolean
1952gtk_stack_get_interpolate_size (GtkStack *stack)
1953{
1954 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1955 g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);
1956
1957 return priv->interpolate_size;
1958}
1959
1960
1961
1962/**
1963 * gtk_stack_get_visible_child: (attributes org.gtk.Method.get_property=visible-child)
1964 * @stack: a `GtkStack`
1965 *
1966 * Gets the currently visible child of @stack.
1967 *
1968 * Returns %NULL if there are no visible children.
1969 *
1970 * Returns: (transfer none) (nullable): the visible child of the `GtkStack`
1971 */
1972GtkWidget *
1973gtk_stack_get_visible_child (GtkStack *stack)
1974{
1975 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1976
1977 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1978
1979 return priv->visible_child ? priv->visible_child->widget : NULL;
1980}
1981
1982/**
1983 * gtk_stack_get_visible_child_name: (attributes org.gtk.Method.get_property=visible-child-name)
1984 * @stack: a `GtkStack`
1985 *
1986 * Returns the name of the currently visible child of @stack.
1987 *
1988 * Returns %NULL if there is no visible child.
1989 *
1990 * Returns: (transfer none) (nullable): the name of the visible child
1991 * of the `GtkStack`
1992 */
1993const char *
1994gtk_stack_get_visible_child_name (GtkStack *stack)
1995{
1996 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
1997
1998 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
1999
2000 if (priv->visible_child)
2001 return priv->visible_child->name;
2002
2003 return NULL;
2004}
2005
2006/**
2007 * gtk_stack_set_visible_child: (attributes org.gtk.Method.set_property=visible-child)
2008 * @stack: a `GtkStack`
2009 * @child: a child of @stack
2010 *
2011 * Makes @child the visible child of @stack.
2012 *
2013 * If @child is different from the currently visible child,
2014 * the transition between the two will be animated with the
2015 * current transition type of @stack.
2016 *
2017 * Note that the @child widget has to be visible itself
2018 * (see [method@Gtk.Widget.show]) in order to become the visible
2019 * child of @stack.
2020 */
2021void
2022gtk_stack_set_visible_child (GtkStack *stack,
2023 GtkWidget *child)
2024{
2025 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2026 GtkStackPage *child_info;
2027
2028 g_return_if_fail (GTK_IS_STACK (stack));
2029 g_return_if_fail (GTK_IS_WIDGET (child));
2030
2031 child_info = find_child_info_for_widget (stack, child);
2032 if (child_info == NULL)
2033 {
2034 g_warning ("Given child of type '%s' not found in GtkStack",
2035 G_OBJECT_TYPE_NAME (child));
2036 return;
2037 }
2038
2039 if (gtk_widget_get_visible (widget: child_info->widget))
2040 set_visible_child (stack, child_info,
2041 transition_type: priv->transition_type,
2042 transition_duration: priv->transition_duration);
2043}
2044
2045/**
2046 * gtk_stack_set_visible_child_name: (attributes org.gtk.Method.set_property=visible-child-name)
2047 * @stack: a `GtkStack`
2048 * @name: the name of the child to make visible
2049 *
2050 * Makes the child with the given name visible.
2051 *
2052 * If @child is different from the currently visible child,
2053 * the transition between the two will be animated with the
2054 * current transition type of @stack.
2055 *
2056 * Note that the child widget has to be visible itself
2057 * (see [method@Gtk.Widget.show]) in order to become the visible
2058 * child of @stack.
2059 */
2060void
2061gtk_stack_set_visible_child_name (GtkStack *stack,
2062 const char *name)
2063{
2064 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2065
2066 g_return_if_fail (GTK_IS_STACK (stack));
2067
2068 gtk_stack_set_visible_child_full (stack, name, transition: priv->transition_type);
2069}
2070
2071/**
2072 * gtk_stack_set_visible_child_full:
2073 * @stack: a `GtkStack`
2074 * @name: the name of the child to make visible
2075 * @transition: the transition type to use
2076 *
2077 * Makes the child with the given name visible.
2078 *
2079 * Note that the child widget has to be visible itself
2080 * (see [method@Gtk.Widget.show]) in order to become the visible
2081 * child of @stack.
2082 */
2083void
2084gtk_stack_set_visible_child_full (GtkStack *stack,
2085 const char *name,
2086 GtkStackTransitionType transition)
2087{
2088 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2089 GtkStackPage *child_info, *info;
2090 GList *l;
2091
2092 g_return_if_fail (GTK_IS_STACK (stack));
2093
2094 if (name == NULL)
2095 return;
2096
2097 child_info = NULL;
2098 for (l = priv->children; l != NULL; l = l->next)
2099 {
2100 info = l->data;
2101 if (info->name != NULL &&
2102 strcmp (s1: info->name, s2: name) == 0)
2103 {
2104 child_info = info;
2105 break;
2106 }
2107 }
2108
2109 if (child_info == NULL)
2110 {
2111 g_warning ("Child name '%s' not found in GtkStack", name);
2112 return;
2113 }
2114
2115 if (gtk_widget_get_visible (widget: child_info->widget))
2116 set_visible_child (stack, child_info, transition_type: transition, transition_duration: priv->transition_duration);
2117}
2118
2119static void
2120gtk_stack_compute_expand (GtkWidget *widget,
2121 gboolean *hexpand_p,
2122 gboolean *vexpand_p)
2123{
2124 GtkStack *stack = GTK_STACK (widget);
2125 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2126 gboolean hexpand, vexpand;
2127 GtkStackPage *child_info;
2128 GtkWidget *child;
2129 GList *l;
2130
2131 hexpand = FALSE;
2132 vexpand = FALSE;
2133 for (l = priv->children; l != NULL; l = l->next)
2134 {
2135 child_info = l->data;
2136 child = child_info->widget;
2137
2138 if (!hexpand &&
2139 gtk_widget_compute_expand (widget: child, orientation: GTK_ORIENTATION_HORIZONTAL))
2140 hexpand = TRUE;
2141
2142 if (!vexpand &&
2143 gtk_widget_compute_expand (widget: child, orientation: GTK_ORIENTATION_VERTICAL))
2144 vexpand = TRUE;
2145
2146 if (hexpand && vexpand)
2147 break;
2148 }
2149
2150 *hexpand_p = hexpand;
2151 *vexpand_p = vexpand;
2152}
2153
2154static GtkSizeRequestMode
2155gtk_stack_get_request_mode (GtkWidget *widget)
2156{
2157 GtkWidget *w;
2158 int wfh = 0, hfw = 0;
2159
2160 for (w = gtk_widget_get_first_child (widget);
2161 w != NULL;
2162 w = gtk_widget_get_next_sibling (widget: w))
2163 {
2164 GtkSizeRequestMode mode = gtk_widget_get_request_mode (widget: w);
2165
2166 switch (mode)
2167 {
2168 case GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH:
2169 hfw ++;
2170 break;
2171 case GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT:
2172 wfh ++;
2173 break;
2174 case GTK_SIZE_REQUEST_CONSTANT_SIZE:
2175 default:
2176 break;
2177 }
2178 }
2179
2180 if (hfw == 0 && wfh == 0)
2181 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
2182 else
2183 return wfh > hfw ?
2184 GTK_SIZE_REQUEST_WIDTH_FOR_HEIGHT :
2185 GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
2186}
2187
2188static void
2189gtk_stack_snapshot_crossfade (GtkWidget *widget,
2190 GtkSnapshot *snapshot)
2191{
2192 GtkStack *stack = GTK_STACK (widget);
2193 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2194 double progress = gtk_progress_tracker_get_progress (tracker: &priv->tracker, FALSE);
2195
2196 gtk_snapshot_push_cross_fade (snapshot, progress);
2197
2198 if (priv->last_visible_child)
2199 {
2200 gtk_widget_snapshot_child (widget,
2201 child: priv->last_visible_child->widget,
2202 snapshot);
2203 }
2204 gtk_snapshot_pop (snapshot);
2205
2206 gtk_widget_snapshot_child (widget,
2207 child: priv->visible_child->widget,
2208 snapshot);
2209 gtk_snapshot_pop (snapshot);
2210}
2211
2212static void
2213gtk_stack_snapshot_under (GtkWidget *widget,
2214 GtkSnapshot *snapshot)
2215{
2216 GtkStack *stack = GTK_STACK (widget);
2217 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2218 int widget_width, widget_height;
2219 int x, y, width, height, pos_x, pos_y;
2220
2221
2222 x = y = 0;
2223 width = widget_width = gtk_widget_get_width (widget);
2224 height = widget_height = gtk_widget_get_height (widget);
2225
2226 pos_x = pos_y = 0;
2227
2228 switch ((guint) priv->active_transition_type)
2229 {
2230 case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
2231 y = 0;
2232 height = widget_height * (gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
2233 pos_y = height;
2234 break;
2235 case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
2236 y = widget_height * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
2237 height = widget_height - y;
2238 pos_y = y - widget_height;
2239 break;
2240 case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
2241 x = widget_width * (1 - gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
2242 width = widget_width - x;
2243 pos_x = x - widget_width;
2244 break;
2245 case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
2246 x = 0;
2247 width = widget_width * (gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE));
2248 pos_x = width;
2249 break;
2250 default:
2251 g_assert_not_reached ();
2252 }
2253
2254 gtk_snapshot_push_clip (snapshot, bounds: &GRAPHENE_RECT_INIT(x, y, width, height));
2255
2256 gtk_widget_snapshot_child (widget,
2257 child: priv->visible_child->widget,
2258 snapshot);
2259
2260 gtk_snapshot_pop (snapshot);
2261
2262 if (priv->last_visible_child)
2263 {
2264 gtk_snapshot_save (snapshot);
2265 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (pos_x, pos_y));
2266 gtk_widget_snapshot_child (widget, child: priv->last_visible_child->widget, snapshot);
2267 gtk_snapshot_restore (snapshot);
2268 }
2269}
2270
2271static void
2272gtk_stack_snapshot_cube (GtkWidget *widget,
2273 GtkSnapshot *snapshot)
2274{
2275 GtkStack *stack = GTK_STACK (widget);
2276 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2277 double progress = gtk_progress_tracker_get_progress (tracker: &priv->tracker, FALSE);
2278
2279 g_assert (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT ||
2280 priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT);
2281
2282 if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT)
2283 progress = 1 - progress;
2284
2285 if (priv->last_visible_child && progress > 0.5)
2286 {
2287 gtk_snapshot_save (snapshot);
2288 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2289 gtk_widget_get_width (widget) / 2.f,
2290 gtk_widget_get_height (widget) / 2.f,
2291 0));
2292 gtk_snapshot_perspective (snapshot, depth: 2 * gtk_widget_get_width (widget) / 1.f);
2293 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2294 0, 0,
2295 - gtk_widget_get_width (widget) / 2.f));
2296 gtk_snapshot_rotate_3d (snapshot, angle: -90 * progress, axis: graphene_vec3_y_axis());
2297 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2298 - gtk_widget_get_width (widget) / 2.f,
2299 - gtk_widget_get_height (widget) / 2.f,
2300 gtk_widget_get_width (widget) / 2.f));
2301 if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
2302 gtk_widget_snapshot_child (widget, child: priv->last_visible_child->widget, snapshot);
2303 else
2304 gtk_widget_snapshot_child (widget, child: priv->visible_child->widget, snapshot);
2305 gtk_snapshot_restore (snapshot);
2306 }
2307
2308 gtk_snapshot_save (snapshot);
2309 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2310 gtk_widget_get_width (widget) / 2.f,
2311 gtk_widget_get_height (widget) / 2.f,
2312 0));
2313 gtk_snapshot_perspective (snapshot, depth: 2 * gtk_widget_get_width (widget) / 1.f);
2314 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2315 0, 0,
2316 - gtk_widget_get_width (widget) / 2.f));
2317 gtk_snapshot_rotate_3d (snapshot, angle: 90 * (1.0 - progress), axis: graphene_vec3_y_axis());
2318 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2319 - gtk_widget_get_width (widget) / 2.f,
2320 - gtk_widget_get_height (widget) / 2.f,
2321 gtk_widget_get_width (widget) / 2.f));
2322
2323 if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
2324 gtk_widget_snapshot_child (widget, child: priv->visible_child->widget, snapshot);
2325 else if (priv->last_visible_child)
2326 gtk_widget_snapshot_child (widget, child: priv->last_visible_child->widget, snapshot);
2327 gtk_snapshot_restore (snapshot);
2328
2329 if (priv->last_visible_child && progress <= 0.5)
2330 {
2331 gtk_snapshot_save (snapshot);
2332 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2333 gtk_widget_get_width (widget) / 2.f,
2334 gtk_widget_get_height (widget) / 2.f,
2335 0));
2336 gtk_snapshot_perspective (snapshot, depth: 2 * gtk_widget_get_width (widget) / 1.f);
2337 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2338 0, 0,
2339 - gtk_widget_get_width (widget) / 2.f));
2340 gtk_snapshot_rotate_3d (snapshot, angle: -90 * progress, axis: graphene_vec3_y_axis());
2341 gtk_snapshot_translate_3d (snapshot, point: &GRAPHENE_POINT3D_INIT (
2342 - gtk_widget_get_width (widget) / 2.f,
2343 - gtk_widget_get_height (widget) / 2.f,
2344 gtk_widget_get_width (widget) / 2.f));
2345 if (priv->active_transition_type == GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT)
2346 gtk_widget_snapshot_child (widget, child: priv->last_visible_child->widget, snapshot);
2347 else
2348 gtk_widget_snapshot_child (widget, child: priv->visible_child->widget, snapshot);
2349 gtk_snapshot_restore (snapshot);
2350 }
2351}
2352
2353static void
2354gtk_stack_snapshot_slide (GtkWidget *widget,
2355 GtkSnapshot *snapshot)
2356{
2357 GtkStack *stack = GTK_STACK (widget);
2358 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2359
2360 if (priv->last_visible_child)
2361 {
2362 int x, y;
2363 int width, height;
2364
2365 width = gtk_widget_get_width (widget);
2366 height = gtk_widget_get_height (widget);
2367
2368 x = get_bin_window_x (stack);
2369 y = get_bin_window_y (stack);
2370
2371 switch ((guint) priv->active_transition_type)
2372 {
2373 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
2374 x -= width;
2375 break;
2376 case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
2377 x += width;
2378 break;
2379 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
2380 y -= height;
2381 break;
2382 case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
2383 y += height;
2384 break;
2385 case GTK_STACK_TRANSITION_TYPE_OVER_UP:
2386 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
2387 y = 0;
2388 break;
2389 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
2390 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
2391 x = 0;
2392 break;
2393 default:
2394 g_assert_not_reached ();
2395 break;
2396 }
2397
2398 if (priv->last_visible_child != NULL)
2399 {
2400 if (gtk_widget_get_valign (widget: priv->last_visible_child->widget) == GTK_ALIGN_END &&
2401 priv->last_visible_widget_height > height)
2402 y -= priv->last_visible_widget_height - height;
2403 else if (gtk_widget_get_valign (widget: priv->last_visible_child->widget) == GTK_ALIGN_CENTER)
2404 y -= (priv->last_visible_widget_height - height) / 2;
2405 }
2406
2407 gtk_snapshot_save (snapshot);
2408 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y));
2409 gtk_widget_snapshot_child (widget, child: priv->last_visible_child->widget, snapshot);
2410 gtk_snapshot_restore (snapshot);
2411 }
2412
2413 gtk_widget_snapshot_child (widget,
2414 child: priv->visible_child->widget,
2415 snapshot);
2416}
2417
2418static void
2419gtk_stack_snapshot (GtkWidget *widget,
2420 GtkSnapshot *snapshot)
2421{
2422 GtkStack *stack = GTK_STACK (widget);
2423 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2424
2425 if (priv->visible_child)
2426 {
2427 if (gtk_progress_tracker_get_state (tracker: &priv->tracker) != GTK_PROGRESS_STATE_AFTER)
2428 {
2429 gtk_snapshot_push_clip (snapshot,
2430 bounds: &GRAPHENE_RECT_INIT(
2431 0, 0,
2432 gtk_widget_get_width (widget),
2433 gtk_widget_get_height (widget)
2434 ));
2435
2436 switch (priv->active_transition_type)
2437 {
2438 case GTK_STACK_TRANSITION_TYPE_CROSSFADE:
2439 gtk_stack_snapshot_crossfade (widget, snapshot);
2440 break;
2441 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
2442 case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
2443 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP:
2444 case GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN:
2445 case GTK_STACK_TRANSITION_TYPE_OVER_UP:
2446 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN:
2447 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
2448 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
2449 gtk_stack_snapshot_slide (widget, snapshot);
2450 break;
2451 case GTK_STACK_TRANSITION_TYPE_UNDER_UP:
2452 case GTK_STACK_TRANSITION_TYPE_UNDER_DOWN:
2453 case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
2454 case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
2455 gtk_stack_snapshot_under (widget, snapshot);
2456 break;
2457 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT:
2458 case GTK_STACK_TRANSITION_TYPE_ROTATE_RIGHT:
2459 gtk_stack_snapshot_cube (widget, snapshot);
2460 break;
2461 case GTK_STACK_TRANSITION_TYPE_NONE:
2462 case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
2463 case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
2464 case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
2465 case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
2466 case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
2467 case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
2468 case GTK_STACK_TRANSITION_TYPE_ROTATE_LEFT_RIGHT:
2469 default:
2470 g_assert_not_reached ();
2471 }
2472
2473 gtk_snapshot_pop (snapshot);
2474 }
2475 else
2476 gtk_widget_snapshot_child (widget,
2477 child: priv->visible_child->widget,
2478 snapshot);
2479 }
2480}
2481
2482static void
2483gtk_stack_size_allocate (GtkWidget *widget,
2484 int width,
2485 int height,
2486 int baseline)
2487{
2488 GtkStack *stack = GTK_STACK (widget);
2489 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2490 GtkAllocation child_allocation;
2491
2492 if (priv->last_visible_child)
2493 {
2494 int child_width, child_height;
2495 int min, nat;
2496
2497 gtk_widget_measure (widget: priv->last_visible_child->widget, orientation: GTK_ORIENTATION_HORIZONTAL,
2498 for_size: -1,
2499 minimum: &min, natural: &nat, NULL, NULL);
2500 child_width = MAX (min, width);
2501 gtk_widget_measure (widget: priv->last_visible_child->widget, orientation: GTK_ORIENTATION_VERTICAL,
2502 for_size: child_width,
2503 minimum: &min, natural: &nat, NULL, NULL);
2504 child_height = MAX (min, height);
2505
2506 gtk_widget_size_allocate (widget: priv->last_visible_child->widget,
2507 allocation: &(GtkAllocation) { 0, 0, child_width, child_height }, baseline: -1);
2508 }
2509
2510 child_allocation.x = get_bin_window_x (stack);
2511 child_allocation.y = get_bin_window_y (stack);
2512 child_allocation.width = width;
2513 child_allocation.height = height;
2514
2515 if (priv->visible_child)
2516 {
2517 int min_width;
2518 int min_height;
2519
2520 gtk_widget_measure (widget: priv->visible_child->widget, orientation: GTK_ORIENTATION_HORIZONTAL,
2521 for_size: height, minimum: &min_width, NULL, NULL, NULL);
2522 child_allocation.width = MAX (child_allocation.width, min_width);
2523
2524 gtk_widget_measure (widget: priv->visible_child->widget, orientation: GTK_ORIENTATION_VERTICAL,
2525 for_size: child_allocation.width, minimum: &min_height, NULL, NULL, NULL);
2526 child_allocation.height = MAX (child_allocation.height, min_height);
2527
2528 if (child_allocation.width > width)
2529 {
2530 GtkAlign halign = gtk_widget_get_halign (widget: priv->visible_child->widget);
2531
2532 if (halign == GTK_ALIGN_CENTER || halign == GTK_ALIGN_FILL)
2533 child_allocation.x = (width - child_allocation.width) / 2;
2534 else if (halign == GTK_ALIGN_END)
2535 child_allocation.x = (width - child_allocation.width);
2536 }
2537
2538 if (child_allocation.height > height)
2539 {
2540 GtkAlign valign = gtk_widget_get_valign (widget: priv->visible_child->widget);
2541
2542 if (valign == GTK_ALIGN_CENTER || valign == GTK_ALIGN_FILL)
2543 child_allocation.y = (height - child_allocation.height) / 2;
2544 else if (valign == GTK_ALIGN_END)
2545 child_allocation.y = (height - child_allocation.height);
2546 }
2547
2548 gtk_widget_size_allocate (widget: priv->visible_child->widget, allocation: &child_allocation, baseline: -1);
2549 }
2550}
2551
2552#define LERP(a, b, t) ((a) + (((b) - (a)) * (1.0 - (t))))
2553static void
2554gtk_stack_measure (GtkWidget *widget,
2555 GtkOrientation orientation,
2556 int for_size,
2557 int *minimum,
2558 int *natural,
2559 int *minimum_baseline,
2560 int *natural_baseline)
2561{
2562 GtkStack *stack = GTK_STACK (widget);
2563 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2564 GtkStackPage *child_info;
2565 GtkWidget *child;
2566 int child_min, child_nat;
2567 GList *l;
2568
2569 *minimum = 0;
2570 *natural = 0;
2571
2572 for (l = priv->children; l != NULL; l = l->next)
2573 {
2574 child_info = l->data;
2575 child = child_info->widget;
2576
2577 if (!priv->homogeneous[orientation] &&
2578 priv->visible_child != child_info)
2579 continue;
2580
2581 if (gtk_widget_get_visible (widget: child))
2582 {
2583 if (!priv->homogeneous[OPPOSITE_ORIENTATION(orientation)] && priv->visible_child != child_info)
2584 {
2585 int min_for_size;
2586
2587 gtk_widget_measure (widget: child, OPPOSITE_ORIENTATION (orientation), for_size: -1, minimum: &min_for_size, NULL, NULL, NULL);
2588
2589 gtk_widget_measure (widget: child, orientation, MAX (min_for_size, for_size), minimum: &child_min, natural: &child_nat, NULL, NULL);
2590 }
2591 else
2592 gtk_widget_measure (widget: child, orientation, for_size, minimum: &child_min, natural: &child_nat, NULL, NULL);
2593
2594 *minimum = MAX (*minimum, child_min);
2595 *natural = MAX (*natural, child_nat);
2596 }
2597 }
2598
2599 if (priv->last_visible_child != NULL && !priv->homogeneous[orientation])
2600 {
2601 double t = priv->interpolate_size ? gtk_progress_tracker_get_ease_out_cubic (tracker: &priv->tracker, FALSE) : 1.0;
2602 int last_size;
2603
2604 if (orientation == GTK_ORIENTATION_HORIZONTAL)
2605 last_size = priv->last_visible_widget_width;
2606 else
2607 last_size = priv->last_visible_widget_height;
2608
2609 *minimum = LERP (*minimum, last_size, t);
2610 *natural = LERP (*natural, last_size, t);
2611 }
2612}
2613
2614static void
2615gtk_stack_init (GtkStack *stack)
2616{
2617 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2618
2619 priv->homogeneous[GTK_ORIENTATION_VERTICAL] = TRUE;
2620 priv->homogeneous[GTK_ORIENTATION_HORIZONTAL] = TRUE;
2621 priv->transition_duration = 200;
2622 priv->transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
2623}
2624
2625/**
2626 * gtk_stack_get_pages: (attributes org.gtk.Method.get_property=pages)
2627 * @stack: a `GtkStack`
2628 *
2629 * Returns a `GListModel` that contains the pages of the stack.
2630 *
2631 * This can be used to keep an up-to-date view. The model also
2632 * implements [iface@Gtk.SelectionModel] and can be used to track
2633 * and modify the visible page.
2634 *
2635 * Returns: (transfer full): a `GtkSelectionModel` for the stack's children
2636 */
2637GtkSelectionModel *
2638gtk_stack_get_pages (GtkStack *stack)
2639{
2640 GtkStackPrivate *priv = gtk_stack_get_instance_private (self: stack);
2641
2642 g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
2643
2644 if (priv->pages)
2645 return g_object_ref (priv->pages);
2646
2647 priv->pages = GTK_SELECTION_MODEL (ptr: gtk_stack_pages_new (stack));
2648 g_object_add_weak_pointer (G_OBJECT (priv->pages), weak_pointer_location: (gpointer *)&priv->pages);
2649
2650 return priv->pages;
2651}
2652
2653/**
2654 * gtk_stack_page_get_visible: (attributes org.gtk.Method.get_property=visible)
2655 * @self: a `GtkStackPage`
2656 *
2657 * Returns whether @page is visible in its `GtkStack`.
2658 *
2659 * This is independent from the [property@Gtk.Widget:visible]
2660 * property of its widget.
2661 *
2662 * Returns: %TRUE if @page is visible
2663 */
2664gboolean
2665gtk_stack_page_get_visible (GtkStackPage *self)
2666{
2667 g_return_val_if_fail (GTK_IS_STACK_PAGE (self), FALSE);
2668
2669 return self->visible;
2670}
2671
2672/**
2673 * gtk_stack_page_set_visible: (attributes org.gtk.Method.set_property=visible)
2674 * @self: a `GtkStackPage`
2675 * @visible: The new property value
2676 *
2677 * Sets whether @page is visible in its `GtkStack`.
2678 */
2679void
2680gtk_stack_page_set_visible (GtkStackPage *self,
2681 gboolean visible)
2682{
2683 g_return_if_fail (GTK_IS_STACK_PAGE (self));
2684
2685 visible = !!visible;
2686
2687 if (visible == self->visible)
2688 return;
2689
2690 self->visible = visible;
2691
2692 if (self->widget && gtk_widget_get_parent (widget: self->widget))
2693 update_child_visible (GTK_STACK (gtk_widget_get_parent (self->widget)), child_info: self);
2694
2695 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_VISIBLE]);
2696}
2697
2698/**
2699 * gtk_stack_page_get_needs_attention: (attributes org.gtk.Method.get_property=needs-attention)
2700 * @self: a `GtkStackPage`
2701 *
2702 * Returns whether the page is marked as “needs attention”.
2703 *
2704 * Returns: The value of the [property@Gtk.StackPage:needs-attention]
2705 * property.
2706 */
2707gboolean
2708gtk_stack_page_get_needs_attention (GtkStackPage *self)
2709{
2710 return self->needs_attention;
2711}
2712
2713/**
2714 * gtk_stack_page_set_needs_attention: (attributes org.gtk.Method.set_property=needs-attention)
2715 * @self: a `GtkStackPage`
2716 * @setting: the new value to set
2717 *
2718 * Sets whether the page is marked as “needs attention”.
2719 */
2720void
2721gtk_stack_page_set_needs_attention (GtkStackPage *self,
2722 gboolean setting)
2723{
2724 setting = !!setting;
2725
2726 if (setting == self->needs_attention)
2727 return;
2728
2729 self->needs_attention = setting;
2730 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_NEEDS_ATTENTION]);
2731}
2732
2733/**
2734 * gtk_stack_page_get_use_underline: (attributes org.gtk.Method.get_property=use-underline)
2735 * @self: a `GtkStackPage`
2736 *
2737 * Gets whether underlines in the page title indicate mnemonics.
2738 *
2739 * Returns: The value of the [property@Gtk.StackPage:use-underline] property
2740 */
2741gboolean
2742gtk_stack_page_get_use_underline (GtkStackPage *self)
2743{
2744 return self->use_underline;
2745}
2746
2747/**
2748 * gtk_stack_page_set_use_underline: (attributes org.gtk.Method.set_property=use-underline)
2749 * @self: a `GtkStackPage`
2750 * @setting: the new value to set
2751 *
2752 * Sets whether underlines in the page title indicate mnemonics.
2753 */
2754void
2755gtk_stack_page_set_use_underline (GtkStackPage *self,
2756 gboolean setting)
2757{
2758 setting = !!setting;
2759
2760 if (setting == self->use_underline)
2761 return;
2762
2763 self->use_underline = setting;
2764 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_USE_UNDERLINE]);
2765}
2766
2767
2768/**
2769 * gtk_stack_page_get_name: (attributes org.gtk.Method.get_property=name)
2770 * @self: a `GtkStackPage`
2771 *
2772 * Returns the name of the page.
2773 *
2774 * Returns: (nullable): The value of the [property@Gtk.StackPage:name] property
2775 */
2776const char *
2777gtk_stack_page_get_name (GtkStackPage *self)
2778{
2779 g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);
2780
2781 return self->name;
2782}
2783
2784/**
2785 * gtk_stack_page_set_name: (attributes org.gtk.Method.set_property=name)
2786 * @self: a `GtkStackPage`
2787 * @setting: (transfer none): the new value to set
2788 *
2789 * Sets the name of the page.
2790 */
2791void
2792gtk_stack_page_set_name (GtkStackPage *self,
2793 const char *setting)
2794{
2795 GtkStack *stack = NULL;
2796 GtkStackPrivate *priv = NULL;
2797
2798 g_return_if_fail (GTK_IS_STACK_PAGE (self));
2799
2800 if (self->widget &&
2801 gtk_widget_get_parent (widget: self->widget) &&
2802 GTK_IS_STACK (gtk_widget_get_parent (self->widget)))
2803 {
2804 GList *l;
2805
2806 stack = GTK_STACK (gtk_widget_get_parent (self->widget));
2807 priv = gtk_stack_get_instance_private (self: stack);
2808
2809 for (l = priv->children; l != NULL; l = l->next)
2810 {
2811 GtkStackPage *info2 = l->data;
2812 if (self == info2)
2813 continue;
2814
2815 if (g_strcmp0 (str1: info2->name, str2: setting) == 0)
2816 {
2817 g_warning ("Duplicate child name in GtkStack: %s", setting);
2818 break;
2819 }
2820 }
2821 }
2822
2823 if (setting == self->name)
2824 return;
2825
2826 g_free (mem: self->name);
2827 self->name = g_strdup (str: setting);
2828 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_NAME]);
2829
2830 if (priv && priv->visible_child == self)
2831 g_object_notify_by_pspec (G_OBJECT (stack),
2832 pspec: stack_props[PROP_VISIBLE_CHILD_NAME]);
2833}
2834
2835/**
2836 * gtk_stack_page_get_title: (attributes org.gtk.Method.get_property=title)
2837 * @self: a `GtkStackPage`
2838 *
2839 * Gets the page title.
2840 *
2841 * Returns: (nullable): The value of the [property@Gtk.StackPage:title] property
2842 */
2843const char *
2844gtk_stack_page_get_title (GtkStackPage *self)
2845{
2846 g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);
2847
2848 return self->title;
2849}
2850
2851/**
2852 * gtk_stack_page_set_title: (attributes org.gtk.Method.set_property=title)
2853 * @self: a `GtkStackPage`
2854 * @setting: (transfer none): the new value to set
2855 *
2856 * Sets the page title.
2857 */
2858void
2859gtk_stack_page_set_title (GtkStackPage *self,
2860 const char *setting)
2861{
2862 g_return_if_fail (GTK_IS_STACK_PAGE (self));
2863
2864 if (setting == self->title)
2865 return;
2866
2867 g_free (mem: self->title);
2868 self->title = g_strdup (str: setting);
2869 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_TITLE]);
2870
2871 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
2872 first_property: GTK_ACCESSIBLE_PROPERTY_LABEL, self->title,
2873 -1);
2874}
2875
2876/**
2877 * gtk_stack_page_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
2878 * @self: a `GtkStackPage`
2879 *
2880 * Returns the icon name of the page.
2881 *
2882 * Returns: (nullable): The value of the [property@Gtk.StackPage:icon-name] property
2883 */
2884const char *
2885gtk_stack_page_get_icon_name (GtkStackPage *self)
2886{
2887 g_return_val_if_fail (GTK_IS_STACK_PAGE (self), NULL);
2888
2889 return self->icon_name;
2890}
2891
2892/**
2893 * gtk_stack_page_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
2894 * @self: a `GtkStackPage`
2895 * @setting: (transfer none): the new value to set
2896 *
2897 * Sets the icon name of the page.
2898 */
2899void
2900gtk_stack_page_set_icon_name (GtkStackPage *self,
2901 const char *setting)
2902{
2903 g_return_if_fail (GTK_IS_STACK_PAGE (self));
2904
2905 if (setting == self->icon_name)
2906 return;
2907
2908 g_free (mem: self->icon_name);
2909 self->icon_name = g_strdup (str: setting);
2910 g_object_notify_by_pspec (G_OBJECT (self), pspec: stack_page_props[CHILD_PROP_ICON_NAME]);
2911}
2912

source code of gtk/gtk/gtkstack.c