1 /*
2  * Copyright (C) 2018 Purism SPC
3  * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
4  *
5  * SPDX-License-Identifier: LGPL-2.1+
6  */
7 
8 #include "config.h"
9 #include <glib/gi18n-lib.h>
10 
11 #include "gtkprogresstrackerprivate.h"
12 #include "hdy-animation-private.h"
13 #include "hdy-enums-private.h"
14 #include "hdy-stackable-box-private.h"
15 #include "hdy-shadow-helper-private.h"
16 #include "hdy-swipe-tracker-private.h"
17 #include "hdy-swipeable.h"
18 
19 /**
20  * PRIVATE:hdy-stackable-box
21  * @short_description: An adaptive container acting like a box or a stack.
22  * @Title: HdyStackableBox
23  * @stability: Private
24  * @See_also: #HdyDeck, #HdyLeaflet
25  *
26  * The #HdyStackableBox object can arrange the widgets it manages like #GtkBox
27  * does or like a #GtkStack does, adapting to size changes by switching between
28  * the two modes. These modes are named respectively “unfoled” and “folded”.
29  *
30  * When there is enough space the children are displayed side by side, otherwise
31  * only one is displayed. The threshold is dictated by the preferred minimum
32  * sizes of the children.
33  *
34  * #HdyStackableBox is used as an internal implementation of #HdyDeck and
35  * #HdyLeaflet.
36  *
37  * Since: 1.0
38  */
39 
40 /**
41  * HdyStackableBoxTransitionType:
42  * @HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER: Cover the old page or uncover the new page, sliding from or towards the end according to orientation, text direction and children order
43  * @HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER: Uncover the new page or cover the old page, sliding from or towards the start according to orientation, text direction and children order
44  * @HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order
45  *
46  * This enumeration value describes the possible transitions between modes and
47  * children in a #HdyStackableBox widget.
48  *
49  * New values may be added to this enumeration over time.
50  *
51  * Since: 1.0
52  */
53 
54 enum {
55   PROP_0,
56   PROP_FOLDED,
57   PROP_HHOMOGENEOUS_FOLDED,
58   PROP_VHOMOGENEOUS_FOLDED,
59   PROP_HHOMOGENEOUS_UNFOLDED,
60   PROP_VHOMOGENEOUS_UNFOLDED,
61   PROP_VISIBLE_CHILD,
62   PROP_VISIBLE_CHILD_NAME,
63   PROP_TRANSITION_TYPE,
64   PROP_MODE_TRANSITION_DURATION,
65   PROP_CHILD_TRANSITION_DURATION,
66   PROP_CHILD_TRANSITION_RUNNING,
67   PROP_INTERPOLATE_SIZE,
68   PROP_CAN_SWIPE_BACK,
69   PROP_CAN_SWIPE_FORWARD,
70   PROP_ORIENTATION,
71   LAST_PROP,
72 };
73 
74 #define HDY_FOLD_UNFOLDED FALSE
75 #define HDY_FOLD_FOLDED TRUE
76 #define HDY_FOLD_MAX 2
77 #define GTK_ORIENTATION_MAX 2
78 
79 typedef struct _HdyStackableBoxChildInfo HdyStackableBoxChildInfo;
80 
81 struct _HdyStackableBoxChildInfo
82 {
83   GtkWidget *widget;
84   GdkWindow *window;
85   gchar *name;
86   gboolean navigatable;
87 
88   /* Convenience storage for per-child temporary frequently computed values. */
89   GtkAllocation alloc;
90   GtkRequisition min;
91   GtkRequisition nat;
92   gboolean visible;
93 };
94 
95 struct _HdyStackableBox
96 {
97   GObject parent;
98 
99   GtkContainer *container;
100   GtkContainerClass *klass;
101   gboolean can_unfold;
102 
103   GList *children;
104   /* It is probably cheaper to store and maintain a reversed copy of the
105    * children list that to reverse the list every time we need to allocate or
106    * draw children for RTL languages on a horizontal widget.
107    */
108   GList *children_reversed;
109   HdyStackableBoxChildInfo *visible_child;
110   HdyStackableBoxChildInfo *last_visible_child;
111 
112   gboolean folded;
113 
114   gboolean homogeneous[HDY_FOLD_MAX][GTK_ORIENTATION_MAX];
115 
116   GtkOrientation orientation;
117 
118   HdyStackableBoxTransitionType transition_type;
119 
120   HdySwipeTracker *tracker;
121 
122   struct {
123     guint duration;
124 
125     gdouble current_pos;
126     gdouble source_pos;
127     gdouble target_pos;
128 
129     gdouble start_progress;
130     gdouble end_progress;
131     guint tick_id;
132     GtkProgressTracker tracker;
133   } mode_transition;
134 
135   /* Child transition variables. */
136   struct {
137     guint duration;
138 
139     gdouble progress;
140     gdouble start_progress;
141     gdouble end_progress;
142 
143     gboolean is_gesture_active;
144     gboolean is_cancelled;
145 
146     guint tick_id;
147     GtkProgressTracker tracker;
148     gboolean first_frame_skipped;
149 
150     gboolean interpolate_size;
151     gboolean can_swipe_back;
152     gboolean can_swipe_forward;
153 
154     GtkPanDirection active_direction;
155     gboolean is_direct_swipe;
156     gint swipe_direction;
157   } child_transition;
158 
159   HdyShadowHelper *shadow_helper;
160 };
161 
162 static GParamSpec *props[LAST_PROP];
163 
164 static gint HOMOGENEOUS_PROP[HDY_FOLD_MAX][GTK_ORIENTATION_MAX] = {
165   { PROP_HHOMOGENEOUS_UNFOLDED, PROP_VHOMOGENEOUS_UNFOLDED},
166   { PROP_HHOMOGENEOUS_FOLDED, PROP_VHOMOGENEOUS_FOLDED},
167 };
168 
169 G_DEFINE_TYPE (HdyStackableBox, hdy_stackable_box, G_TYPE_OBJECT);
170 
171 static void
free_child_info(HdyStackableBoxChildInfo * child_info)172 free_child_info (HdyStackableBoxChildInfo *child_info)
173 {
174   g_free (child_info->name);
175   g_free (child_info);
176 }
177 
G_DEFINE_AUTOPTR_CLEANUP_FUNC(HdyStackableBoxChildInfo,free_child_info)178 G_DEFINE_AUTOPTR_CLEANUP_FUNC (HdyStackableBoxChildInfo, free_child_info)
179 
180 static HdyStackableBoxChildInfo *
181 find_child_info_for_widget (HdyStackableBox *self,
182                             GtkWidget       *widget)
183 {
184   GList *children;
185   HdyStackableBoxChildInfo *child_info;
186 
187   for (children = self->children; children; children = children->next) {
188     child_info = children->data;
189 
190     if (child_info->widget == widget)
191       return child_info;
192   }
193 
194   return NULL;
195 }
196 
197 static HdyStackableBoxChildInfo *
find_child_info_for_name(HdyStackableBox * self,const gchar * name)198 find_child_info_for_name (HdyStackableBox *self,
199                           const gchar     *name)
200 {
201   GList *children;
202   HdyStackableBoxChildInfo *child_info;
203 
204   for (children = self->children; children; children = children->next) {
205     child_info = children->data;
206 
207     if (g_strcmp0 (child_info->name, name) == 0)
208       return child_info;
209   }
210 
211   return NULL;
212 }
213 
214 static GList *
get_directed_children(HdyStackableBox * self)215 get_directed_children (HdyStackableBox *self)
216 {
217   return self->orientation == GTK_ORIENTATION_HORIZONTAL &&
218          gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL ?
219          self->children_reversed : self->children;
220 }
221 
222 static GtkPanDirection
get_pan_direction(HdyStackableBox * self,gboolean new_child_first)223 get_pan_direction (HdyStackableBox *self,
224                    gboolean         new_child_first)
225 {
226   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
227     if (gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL)
228       return new_child_first ? GTK_PAN_DIRECTION_LEFT : GTK_PAN_DIRECTION_RIGHT;
229     else
230       return new_child_first ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT;
231   }
232   else
233     return new_child_first ? GTK_PAN_DIRECTION_DOWN : GTK_PAN_DIRECTION_UP;
234 }
235 
236 static gint
get_child_window_x(HdyStackableBox * self,HdyStackableBoxChildInfo * child_info,gint width)237 get_child_window_x (HdyStackableBox          *self,
238                     HdyStackableBoxChildInfo *child_info,
239                     gint                      width)
240 {
241   gboolean is_rtl;
242   gint rtl_multiplier;
243 
244   if (!self->child_transition.is_gesture_active &&
245       gtk_progress_tracker_get_state (&self->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER)
246     return 0;
247 
248   if (self->child_transition.active_direction != GTK_PAN_DIRECTION_LEFT &&
249       self->child_transition.active_direction != GTK_PAN_DIRECTION_RIGHT)
250     return 0;
251 
252   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL;
253   rtl_multiplier = is_rtl ? -1 : 1;
254 
255   if ((self->child_transition.active_direction == GTK_PAN_DIRECTION_RIGHT) == is_rtl) {
256     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER ||
257          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
258         child_info == self->visible_child)
259       return width * (1 - self->child_transition.progress) * rtl_multiplier;
260 
261     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER ||
262          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
263         child_info == self->last_visible_child)
264       return -width * self->child_transition.progress * rtl_multiplier;
265   } else {
266     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER ||
267          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
268         child_info == self->visible_child)
269       return -width * (1 - self->child_transition.progress) * rtl_multiplier;
270 
271     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER ||
272          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
273         child_info == self->last_visible_child)
274       return width * self->child_transition.progress * rtl_multiplier;
275   }
276 
277   return 0;
278 }
279 
280 static gint
get_child_window_y(HdyStackableBox * self,HdyStackableBoxChildInfo * child_info,gint height)281 get_child_window_y (HdyStackableBox          *self,
282                     HdyStackableBoxChildInfo *child_info,
283                     gint                      height)
284 {
285   if (!self->child_transition.is_gesture_active &&
286       gtk_progress_tracker_get_state (&self->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER)
287     return 0;
288 
289   if (self->child_transition.active_direction != GTK_PAN_DIRECTION_UP &&
290       self->child_transition.active_direction != GTK_PAN_DIRECTION_DOWN)
291     return 0;
292 
293   if (self->child_transition.active_direction == GTK_PAN_DIRECTION_UP) {
294     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER ||
295          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
296         child_info == self->visible_child)
297       return height * (1 - self->child_transition.progress);
298 
299     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER ||
300          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
301         child_info == self->last_visible_child)
302       return -height * self->child_transition.progress;
303   } else {
304     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER ||
305          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
306         child_info == self->visible_child)
307       return -height * (1 - self->child_transition.progress);
308 
309     if ((self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER ||
310          self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE) &&
311         child_info == self->last_visible_child)
312       return height * self->child_transition.progress;
313   }
314 
315   return 0;
316 }
317 
318 static void
hdy_stackable_box_child_progress_updated(HdyStackableBox * self)319 hdy_stackable_box_child_progress_updated (HdyStackableBox *self)
320 {
321   gtk_widget_queue_draw (GTK_WIDGET (self->container));
322 
323   if (!self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] ||
324       !self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL])
325     gtk_widget_queue_resize (GTK_WIDGET (self->container));
326   else
327     gtk_widget_queue_allocate (GTK_WIDGET (self->container));
328 
329   if (!self->child_transition.is_gesture_active &&
330       gtk_progress_tracker_get_state (&self->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER) {
331     if (self->child_transition.is_cancelled) {
332       if (self->last_visible_child != NULL) {
333         if (self->folded) {
334           gtk_widget_set_child_visible (self->last_visible_child->widget, TRUE);
335           gtk_widget_set_child_visible (self->visible_child->widget, FALSE);
336         }
337         self->visible_child = self->last_visible_child;
338         self->last_visible_child = NULL;
339       }
340 
341       self->child_transition.is_cancelled = FALSE;
342 
343       g_object_freeze_notify (G_OBJECT (self));
344       g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
345       g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]);
346       g_object_thaw_notify (G_OBJECT (self));
347     } else {
348       if (self->last_visible_child != NULL) {
349         if (self->folded)
350           gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
351         self->last_visible_child = NULL;
352       }
353     }
354 
355     gtk_widget_queue_allocate (GTK_WIDGET (self->container));
356     self->child_transition.swipe_direction = 0;
357     hdy_shadow_helper_clear_cache (self->shadow_helper);
358   }
359 }
360 
361 static gboolean
hdy_stackable_box_child_transition_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)362 hdy_stackable_box_child_transition_cb (GtkWidget     *widget,
363                                        GdkFrameClock *frame_clock,
364                                        gpointer       user_data)
365 {
366   HdyStackableBox *self = HDY_STACKABLE_BOX (user_data);
367   gdouble progress;
368 
369   if (self->child_transition.first_frame_skipped) {
370     gtk_progress_tracker_advance_frame (&self->child_transition.tracker,
371                                         gdk_frame_clock_get_frame_time (frame_clock));
372     progress = gtk_progress_tracker_get_ease_out_cubic (&self->child_transition.tracker, FALSE);
373     self->child_transition.progress =
374       hdy_lerp (self->child_transition.start_progress,
375                 self->child_transition.end_progress, progress);
376   } else
377     self->child_transition.first_frame_skipped = TRUE;
378 
379   /* Finish animation early if not mapped anymore */
380   if (!gtk_widget_get_mapped (widget))
381     gtk_progress_tracker_finish (&self->child_transition.tracker);
382 
383   hdy_stackable_box_child_progress_updated (self);
384 
385   if (gtk_progress_tracker_get_state (&self->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER) {
386     self->child_transition.tick_id = 0;
387     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]);
388 
389     return FALSE;
390   }
391 
392   return TRUE;
393 }
394 
395 static void
hdy_stackable_box_schedule_child_ticks(HdyStackableBox * self)396 hdy_stackable_box_schedule_child_ticks (HdyStackableBox *self)
397 {
398   if (self->child_transition.tick_id == 0) {
399     self->child_transition.tick_id =
400       gtk_widget_add_tick_callback (GTK_WIDGET (self->container),
401                                     hdy_stackable_box_child_transition_cb,
402                                     self, NULL);
403     if (!self->child_transition.is_gesture_active)
404       g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]);
405   }
406 }
407 
408 static void
hdy_stackable_box_unschedule_child_ticks(HdyStackableBox * self)409 hdy_stackable_box_unschedule_child_ticks (HdyStackableBox *self)
410 {
411   if (self->child_transition.tick_id != 0) {
412     gtk_widget_remove_tick_callback (GTK_WIDGET (self->container), self->child_transition.tick_id);
413     self->child_transition.tick_id = 0;
414     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]);
415   }
416 }
417 
418 static void
hdy_stackable_box_stop_child_transition(HdyStackableBox * self)419 hdy_stackable_box_stop_child_transition (HdyStackableBox *self)
420 {
421   hdy_stackable_box_unschedule_child_ticks (self);
422   gtk_progress_tracker_finish (&self->child_transition.tracker);
423   if (self->last_visible_child != NULL) {
424     gtk_widget_set_child_visible (self->last_visible_child->widget, FALSE);
425     self->last_visible_child = NULL;
426   }
427 
428   self->child_transition.swipe_direction = 0;
429   hdy_shadow_helper_clear_cache (self->shadow_helper);
430 }
431 
432 static void
hdy_stackable_box_start_child_transition(HdyStackableBox * self,guint transition_duration,GtkPanDirection transition_direction)433 hdy_stackable_box_start_child_transition (HdyStackableBox *self,
434                                           guint            transition_duration,
435                                           GtkPanDirection  transition_direction)
436 {
437   GtkWidget *widget = GTK_WIDGET (self->container);
438 
439   if (gtk_widget_get_mapped (widget) &&
440       ((hdy_get_enable_animations (widget) &&
441         transition_duration != 0) ||
442        self->child_transition.is_gesture_active) &&
443       self->last_visible_child != NULL &&
444       /* Don't animate child transition when a mode transition is ongoing. */
445       self->mode_transition.tick_id == 0) {
446     self->child_transition.active_direction = transition_direction;
447     self->child_transition.first_frame_skipped = FALSE;
448     self->child_transition.start_progress = 0;
449     self->child_transition.end_progress = 1;
450     self->child_transition.progress = 0;
451     self->child_transition.is_cancelled = FALSE;
452 
453     if (!self->child_transition.is_gesture_active) {
454       hdy_stackable_box_schedule_child_ticks (self);
455       gtk_progress_tracker_start (&self->child_transition.tracker,
456                                   transition_duration * 1000,
457                                   0,
458                                   1.0);
459     }
460   }
461   else {
462     hdy_stackable_box_unschedule_child_ticks (self);
463     gtk_progress_tracker_finish (&self->child_transition.tracker);
464   }
465 
466   hdy_stackable_box_child_progress_updated (self);
467 }
468 
469 static void
set_visible_child_info(HdyStackableBox * self,HdyStackableBoxChildInfo * new_visible_child,HdyStackableBoxTransitionType transition_type,guint transition_duration,gboolean emit_child_switched)470 set_visible_child_info (HdyStackableBox               *self,
471                         HdyStackableBoxChildInfo      *new_visible_child,
472                         HdyStackableBoxTransitionType  transition_type,
473                         guint                          transition_duration,
474                         gboolean                       emit_child_switched)
475 {
476   GtkWidget *widget = GTK_WIDGET (self->container);
477   GList *children;
478   HdyStackableBoxChildInfo *child_info;
479   GtkPanDirection transition_direction = GTK_PAN_DIRECTION_LEFT;
480 
481   /* If we are being destroyed, do not bother with transitions and
482    * notifications.
483    */
484   if (gtk_widget_in_destruction (widget))
485     return;
486 
487   /* If none, pick first visible. */
488   if (new_visible_child == NULL) {
489     for (children = self->children; children; children = children->next) {
490       child_info = children->data;
491 
492       if (gtk_widget_get_visible (child_info->widget)) {
493         new_visible_child = child_info;
494 
495         break;
496       }
497     }
498   }
499 
500   if (new_visible_child == self->visible_child)
501     return;
502 
503   /* FIXME Probably copied from Gtk Stack, should check whether it's needed. */
504   /* toplevel = gtk_widget_get_toplevel (widget); */
505   /* if (GTK_IS_WINDOW (toplevel)) { */
506   /*   focus = gtk_window_get_focus (GTK_WINDOW (toplevel)); */
507   /*   if (focus && */
508   /*       self->visible_child && */
509   /*       self->visible_child->widget && */
510   /*       gtk_widget_is_ancestor (focus, self->visible_child->widget)) { */
511   /*     contains_focus = TRUE; */
512 
513   /*     if (self->visible_child->last_focus) */
514   /*       g_object_remove_weak_pointer (G_OBJECT (self->visible_child->last_focus), */
515   /*                                     (gpointer *)&self->visible_child->last_focus); */
516   /*     self->visible_child->last_focus = focus; */
517   /*     g_object_add_weak_pointer (G_OBJECT (self->visible_child->last_focus), */
518   /*                                (gpointer *)&self->visible_child->last_focus); */
519   /*   } */
520   /* } */
521 
522   if (self->last_visible_child)
523     gtk_widget_set_child_visible (self->last_visible_child->widget, !self->folded);
524   self->last_visible_child = NULL;
525 
526   hdy_shadow_helper_clear_cache (self->shadow_helper);
527 
528   if (self->visible_child && self->visible_child->widget) {
529     if (gtk_widget_is_visible (widget))
530       self->last_visible_child = self->visible_child;
531     else
532       gtk_widget_set_child_visible (self->visible_child->widget, !self->folded);
533   }
534 
535   /* FIXME This comes from GtkStack and should be adapted. */
536   /* hdy_stackable_box_accessible_update_visible_child (stack, */
537   /*                                              self->visible_child ? self->visible_child->widget : NULL, */
538   /*                                              new_visible_child ? new_visible_child->widget : NULL); */
539 
540   self->visible_child = new_visible_child;
541 
542   if (new_visible_child) {
543     gtk_widget_set_child_visible (new_visible_child->widget, TRUE);
544 
545     /* FIXME This comes from GtkStack and should be adapted. */
546     /* if (contains_focus) { */
547     /*   if (new_visible_child->last_focus) */
548     /*     gtk_widget_grab_focus (new_visible_child->last_focus); */
549     /*   else */
550     /*     gtk_widget_child_focus (new_visible_child->widget, GTK_DIR_TAB_FORWARD); */
551     /* } */
552   }
553 
554   if (new_visible_child == NULL || self->last_visible_child == NULL)
555     transition_duration = 0;
556   else {
557     gboolean new_first = FALSE;
558     for (children = self->children; children; children = children->next) {
559       if (new_visible_child == children->data) {
560         new_first = TRUE;
561 
562         break;
563       }
564       if (self->last_visible_child == children->data)
565         break;
566     }
567 
568     transition_direction = get_pan_direction (self, new_first);
569   }
570 
571   if (self->folded) {
572     if (self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] &&
573         self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL])
574       gtk_widget_queue_allocate (widget);
575     else
576       gtk_widget_queue_resize (widget);
577 
578     hdy_stackable_box_start_child_transition (self, transition_duration, transition_direction);
579   }
580 
581   if (emit_child_switched) {
582     gint index = 0;
583 
584     for (children = self->children; children; children = children->next) {
585       child_info = children->data;
586 
587       if (!child_info->navigatable)
588         continue;
589 
590       if (child_info == new_visible_child)
591         break;
592 
593       index++;
594     }
595 
596     hdy_swipeable_emit_child_switched (HDY_SWIPEABLE (self->container), index,
597                                        transition_duration);
598   }
599 
600   g_object_freeze_notify (G_OBJECT (self));
601   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD]);
602   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VISIBLE_CHILD_NAME]);
603   g_object_thaw_notify (G_OBJECT (self));
604 }
605 
606 static void
hdy_stackable_box_set_position(HdyStackableBox * self,gdouble pos)607 hdy_stackable_box_set_position (HdyStackableBox *self,
608                                 gdouble          pos)
609 {
610   self->mode_transition.current_pos = pos;
611 
612   gtk_widget_queue_allocate (GTK_WIDGET (self->container));
613 }
614 
615 static void
hdy_stackable_box_mode_progress_updated(HdyStackableBox * self)616 hdy_stackable_box_mode_progress_updated (HdyStackableBox *self)
617 {
618   if (gtk_progress_tracker_get_state (&self->mode_transition.tracker) == GTK_PROGRESS_STATE_AFTER)
619     hdy_shadow_helper_clear_cache (self->shadow_helper);
620 }
621 
622 static gboolean
hdy_stackable_box_mode_transition_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer user_data)623 hdy_stackable_box_mode_transition_cb (GtkWidget     *widget,
624                                       GdkFrameClock *frame_clock,
625                                       gpointer       user_data)
626 {
627   HdyStackableBox *self = HDY_STACKABLE_BOX (user_data);
628   gdouble ease;
629 
630   gtk_progress_tracker_advance_frame (&self->mode_transition.tracker,
631                                       gdk_frame_clock_get_frame_time (frame_clock));
632   ease = gtk_progress_tracker_get_ease_out_cubic (&self->mode_transition.tracker, FALSE);
633   hdy_stackable_box_set_position (self,
634                                   self->mode_transition.source_pos + (ease * (self->mode_transition.target_pos - self->mode_transition.source_pos)));
635 
636   hdy_stackable_box_mode_progress_updated (self);
637 
638   if (gtk_progress_tracker_get_state (&self->mode_transition.tracker) == GTK_PROGRESS_STATE_AFTER) {
639     self->mode_transition.tick_id = 0;
640     return FALSE;
641   }
642 
643   return TRUE;
644 }
645 
646 static void
hdy_stackable_box_start_mode_transition(HdyStackableBox * self,gdouble target)647 hdy_stackable_box_start_mode_transition (HdyStackableBox *self,
648                                          gdouble          target)
649 {
650   GtkWidget *widget = GTK_WIDGET (self->container);
651 
652   if (self->mode_transition.target_pos == target)
653     return;
654 
655   self->mode_transition.target_pos = target;
656   /* FIXME PROP_REVEAL_CHILD needs to be implemented. */
657   /* g_object_notify_by_pspec (G_OBJECT (revealer), props[PROP_REVEAL_CHILD]); */
658 
659   hdy_stackable_box_stop_child_transition (self);
660 
661   if (gtk_widget_get_mapped (widget) &&
662       self->mode_transition.duration != 0 &&
663       hdy_get_enable_animations (widget) &&
664       self->can_unfold) {
665     self->mode_transition.source_pos = self->mode_transition.current_pos;
666     if (self->mode_transition.tick_id == 0)
667       self->mode_transition.tick_id = gtk_widget_add_tick_callback (widget, hdy_stackable_box_mode_transition_cb, self, NULL);
668     gtk_progress_tracker_start (&self->mode_transition.tracker,
669                                 self->mode_transition.duration * 1000,
670                                 0,
671                                 1.0);
672   }
673   else
674     hdy_stackable_box_set_position (self, target);
675 }
676 
677 /* FIXME Use this to stop the mode transition animation when it makes sense (see *
678  * GtkRevealer for exmples).
679  */
680 /* static void */
681 /* hdy_stackable_box_stop_mode_animation (HdyStackableBox *self) */
682 /* { */
683 /*   if (self->mode_transition.current_pos != self->mode_transition.target_pos) { */
684 /*     self->mode_transition.current_pos = self->mode_transition.target_pos; */
685     /* g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_REVEALED]); */
686 /*   } */
687 /*   if (self->mode_transition.tick_id != 0) { */
688 /*     gtk_widget_remove_tick_callback (GTK_WIDGET (self->container), self->mode_transition.tick_id); */
689 /*     self->mode_transition.tick_id = 0; */
690 /*   } */
691 /* } */
692 
693 /**
694  * hdy_stackable_box_get_folded:
695  * @self: a #HdyStackableBox
696  *
697  * Gets whether @self is folded.
698  *
699  * Returns: whether @self is folded.
700  *
701  * Since: 1.0
702  */
703 gboolean
hdy_stackable_box_get_folded(HdyStackableBox * self)704 hdy_stackable_box_get_folded (HdyStackableBox *self)
705 {
706   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
707 
708   return self->folded;
709 }
710 
711 static void
hdy_stackable_box_set_folded(HdyStackableBox * self,gboolean folded)712 hdy_stackable_box_set_folded (HdyStackableBox *self,
713                               gboolean         folded)
714 {
715   GtkStyleContext *context;
716 
717   if (self->folded == folded)
718     return;
719 
720   self->folded = folded;
721 
722   hdy_stackable_box_start_mode_transition (self, folded ? 0.0 : 1.0);
723 
724   if (self->can_unfold) {
725     context = gtk_widget_get_style_context (GTK_WIDGET (self->container));
726     if (folded) {
727       gtk_style_context_add_class (context, "folded");
728       gtk_style_context_remove_class (context, "unfolded");
729     } else {
730       gtk_style_context_remove_class (context, "folded");
731       gtk_style_context_add_class (context, "unfolded");
732     }
733   }
734 
735   g_object_notify_by_pspec (G_OBJECT (self),
736                             props[PROP_FOLDED]);
737 }
738 
739 /**
740  * hdy_stackable_box_set_homogeneous:
741  * @self: a #HdyStackableBox
742  * @folded: the fold
743  * @orientation: the orientation
744  * @homogeneous: %TRUE to make @self homogeneous
745  *
746  * Sets the #HdyStackableBox to be homogeneous or not for the given fold and orientation.
747  * If it is homogeneous, the #HdyStackableBox will request the same
748  * width or height for all its children depending on the orientation.
749  * If it isn't and it is folded, the widget may change width or height
750  * when a different child becomes visible.
751  *
752  * Since: 1.0
753  */
754 void
hdy_stackable_box_set_homogeneous(HdyStackableBox * self,gboolean folded,GtkOrientation orientation,gboolean homogeneous)755 hdy_stackable_box_set_homogeneous (HdyStackableBox *self,
756                                    gboolean         folded,
757                                    GtkOrientation   orientation,
758                                    gboolean         homogeneous)
759 {
760   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
761 
762   folded = !!folded;
763   homogeneous = !!homogeneous;
764 
765   if (self->homogeneous[folded][orientation] == homogeneous)
766     return;
767 
768   self->homogeneous[folded][orientation] = homogeneous;
769 
770   if (gtk_widget_get_visible (GTK_WIDGET (self->container)))
771     gtk_widget_queue_resize (GTK_WIDGET (self->container));
772 
773   g_object_notify_by_pspec (G_OBJECT (self), props[HOMOGENEOUS_PROP[folded][orientation]]);
774 }
775 
776 /**
777  * hdy_stackable_box_get_homogeneous:
778  * @self: a #HdyStackableBox
779  * @folded: the fold
780  * @orientation: the orientation
781  *
782  * Gets whether @self is homogeneous for the given fold and orientation.
783  * See hdy_stackable_box_set_homogeneous().
784  *
785  * Returns: whether @self is homogeneous for the given fold and orientation.
786  *
787  * Since: 1.0
788  */
789 gboolean
hdy_stackable_box_get_homogeneous(HdyStackableBox * self,gboolean folded,GtkOrientation orientation)790 hdy_stackable_box_get_homogeneous (HdyStackableBox *self,
791                                    gboolean         folded,
792                                    GtkOrientation   orientation)
793 {
794   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
795 
796   folded = !!folded;
797 
798   return self->homogeneous[folded][orientation];
799 }
800 
801 /**
802  * hdy_stackable_box_get_transition_type:
803  * @self: a #HdyStackableBox
804  *
805  * Gets the type of animation that will be used
806  * for transitions between modes and children in @self.
807  *
808  * Returns: the current transition type of @self
809  *
810  * Since: 1.0
811  */
812 HdyStackableBoxTransitionType
hdy_stackable_box_get_transition_type(HdyStackableBox * self)813 hdy_stackable_box_get_transition_type (HdyStackableBox *self)
814 {
815   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER);
816 
817   return self->transition_type;
818 }
819 
820 /**
821  * hdy_stackable_box_set_transition_type:
822  * @self: a #HdyStackableBox
823  * @transition: the new transition type
824  *
825  * Sets the type of animation that will be used for transitions between modes
826  * and children in @self.
827  *
828  * The transition type can be changed without problems at runtime, so it is
829  * possible to change the animation based on the mode or child that is about to
830  * become current.
831  *
832  * Since: 1.0
833  */
834 void
hdy_stackable_box_set_transition_type(HdyStackableBox * self,HdyStackableBoxTransitionType transition)835 hdy_stackable_box_set_transition_type (HdyStackableBox               *self,
836                                        HdyStackableBoxTransitionType  transition)
837 {
838   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
839 
840   if (self->transition_type == transition)
841     return;
842 
843   self->transition_type = transition;
844   g_object_notify_by_pspec (G_OBJECT (self),
845                             props[PROP_TRANSITION_TYPE]);
846 }
847 
848 /**
849  * hdy_stackable_box_get_mode_transition_duration:
850  * @self: a #HdyStackableBox
851  *
852  * Returns the amount of time (in milliseconds) that
853  * transitions between modes in @self will take.
854  *
855  * Returns: the mode transition duration
856  *
857  * Since: 1.0
858  */
859 guint
hdy_stackable_box_get_mode_transition_duration(HdyStackableBox * self)860 hdy_stackable_box_get_mode_transition_duration (HdyStackableBox *self)
861 {
862   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), 0);
863 
864   return self->mode_transition.duration;
865 }
866 
867 /**
868  * hdy_stackable_box_set_mode_transition_duration:
869  * @self: a #HdyStackableBox
870  * @duration: the new duration, in milliseconds
871  *
872  * Sets the duration that transitions between modes in @self
873  * will take.
874  *
875  * Since: 1.0
876  */
877 void
hdy_stackable_box_set_mode_transition_duration(HdyStackableBox * self,guint duration)878 hdy_stackable_box_set_mode_transition_duration (HdyStackableBox *self,
879                                                 guint            duration)
880 {
881   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
882 
883   if (self->mode_transition.duration == duration)
884     return;
885 
886   self->mode_transition.duration = duration;
887   g_object_notify_by_pspec (G_OBJECT (self),
888                             props[PROP_MODE_TRANSITION_DURATION]);
889 }
890 
891 /**
892  * hdy_stackable_box_get_child_transition_duration:
893  * @self: a #HdyStackableBox
894  *
895  * Returns the amount of time (in milliseconds) that
896  * transitions between children in @self will take.
897  *
898  * Returns: the child transition duration
899  *
900  * Since: 1.0
901  */
902 guint
hdy_stackable_box_get_child_transition_duration(HdyStackableBox * self)903 hdy_stackable_box_get_child_transition_duration (HdyStackableBox *self)
904 {
905   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), 0);
906 
907   return self->child_transition.duration;
908 }
909 
910 /**
911  * hdy_stackable_box_set_child_transition_duration:
912  * @self: a #HdyStackableBox
913  * @duration: the new duration, in milliseconds
914  *
915  * Sets the duration that transitions between children in @self
916  * will take.
917  *
918  * Since: 1.0
919  */
920 void
hdy_stackable_box_set_child_transition_duration(HdyStackableBox * self,guint duration)921 hdy_stackable_box_set_child_transition_duration (HdyStackableBox *self,
922                                                  guint            duration)
923 {
924   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
925 
926   if (self->child_transition.duration == duration)
927     return;
928 
929   self->child_transition.duration = duration;
930   g_object_notify_by_pspec (G_OBJECT (self),
931                             props[PROP_CHILD_TRANSITION_DURATION]);
932 }
933 
934 /**
935  * hdy_stackable_box_get_visible_child:
936  * @self: a #HdyStackableBox
937  *
938  * Gets the visible child widget.
939  *
940  * Returns: (transfer none): the visible child widget
941  *
942  * Since: 1.0
943  */
944 GtkWidget *
hdy_stackable_box_get_visible_child(HdyStackableBox * self)945 hdy_stackable_box_get_visible_child (HdyStackableBox *self)
946 {
947   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), NULL);
948 
949   if (self->visible_child == NULL)
950     return NULL;
951 
952   return self->visible_child->widget;
953 }
954 
955 /**
956  * hdy_stackable_box_set_visible_child:
957  * @self: a #HdyStackableBox
958  * @visible_child: the new child
959  *
960  * Makes @visible_child visible using a transition determined by
961  * HdyStackableBox:transition-type and HdyStackableBox:child-transition-duration.
962  * The transition can be cancelled by the user, in which case visible child will
963  * change back to the previously visible child.
964  *
965  * Since: 1.0
966  */
967 void
hdy_stackable_box_set_visible_child(HdyStackableBox * self,GtkWidget * visible_child)968 hdy_stackable_box_set_visible_child (HdyStackableBox *self,
969                                      GtkWidget       *visible_child)
970 {
971   HdyStackableBoxChildInfo *child_info;
972   gboolean contains_child;
973 
974   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
975   g_return_if_fail (GTK_IS_WIDGET (visible_child));
976 
977   child_info = find_child_info_for_widget (self, visible_child);
978   contains_child = child_info != NULL;
979 
980   g_return_if_fail (contains_child);
981 
982   set_visible_child_info (self, child_info, self->transition_type, self->child_transition.duration, TRUE);
983 }
984 
985 /**
986  * hdy_stackable_box_get_visible_child_name:
987  * @self: a #HdyStackableBox
988  *
989  * Gets the name of the currently visible child widget.
990  *
991  * Returns: (transfer none): the name of the visible child
992  *
993  * Since: 1.0
994  */
995 const gchar *
hdy_stackable_box_get_visible_child_name(HdyStackableBox * self)996 hdy_stackable_box_get_visible_child_name (HdyStackableBox *self)
997 {
998   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), NULL);
999 
1000   if (self->visible_child == NULL)
1001     return NULL;
1002 
1003   return self->visible_child->name;
1004 }
1005 
1006 /**
1007  * hdy_stackable_box_set_visible_child_name:
1008  * @self: a #HdyStackableBox
1009  * @name: the name of a child
1010  *
1011  * Makes the child with the name @name visible.
1012  *
1013  * See hdy_stackable_box_set_visible_child() for more details.
1014  *
1015  * Since: 1.0
1016  */
1017 void
hdy_stackable_box_set_visible_child_name(HdyStackableBox * self,const gchar * name)1018 hdy_stackable_box_set_visible_child_name (HdyStackableBox *self,
1019                                           const gchar     *name)
1020 {
1021   HdyStackableBoxChildInfo *child_info;
1022   gboolean contains_child;
1023 
1024   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
1025   g_return_if_fail (name != NULL);
1026 
1027   child_info = find_child_info_for_name (self, name);
1028   contains_child = child_info != NULL;
1029 
1030   g_return_if_fail (contains_child);
1031 
1032   set_visible_child_info (self, child_info, self->transition_type, self->child_transition.duration, TRUE);
1033 }
1034 
1035 /**
1036  * hdy_stackable_box_get_child_transition_running:
1037  * @self: a #HdyStackableBox
1038  *
1039  * Returns whether @self is currently in a transition from one page to
1040  * another.
1041  *
1042  * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
1043  *
1044  * Since: 1.0
1045  */
1046 gboolean
hdy_stackable_box_get_child_transition_running(HdyStackableBox * self)1047 hdy_stackable_box_get_child_transition_running (HdyStackableBox *self)
1048 {
1049   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
1050 
1051   return (self->child_transition.tick_id != 0 ||
1052           self->child_transition.is_gesture_active);
1053 }
1054 
1055 /**
1056  * hdy_stackable_box_set_interpolate_size:
1057  * @self: a #HdyStackableBox
1058  * @interpolate_size: the new value
1059  *
1060  * Sets whether or not @self will interpolate its size when
1061  * changing the visible child. If the #HdyStackableBox:interpolate-size
1062  * property is set to %TRUE, @self will interpolate its size between
1063  * the current one and the one it'll take after changing the
1064  * visible child, according to the set transition duration.
1065  *
1066  * Since: 1.0
1067  */
1068 void
hdy_stackable_box_set_interpolate_size(HdyStackableBox * self,gboolean interpolate_size)1069 hdy_stackable_box_set_interpolate_size (HdyStackableBox *self,
1070                                         gboolean         interpolate_size)
1071 {
1072   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
1073 
1074   interpolate_size = !!interpolate_size;
1075 
1076   if (self->child_transition.interpolate_size == interpolate_size)
1077     return;
1078 
1079   self->child_transition.interpolate_size = interpolate_size;
1080   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INTERPOLATE_SIZE]);
1081 }
1082 
1083 /**
1084  * hdy_stackable_box_get_interpolate_size:
1085  * @self: a #HdyStackableBox
1086  *
1087  * Returns whether the #HdyStackableBox is set up to interpolate between
1088  * the sizes of children on page switch.
1089  *
1090  * Returns: %TRUE if child sizes are interpolated
1091  *
1092  * Since: 1.0
1093  */
1094 gboolean
hdy_stackable_box_get_interpolate_size(HdyStackableBox * self)1095 hdy_stackable_box_get_interpolate_size (HdyStackableBox *self)
1096 {
1097   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
1098 
1099   return self->child_transition.interpolate_size;
1100 }
1101 
1102 /**
1103  * hdy_stackable_box_set_can_swipe_back:
1104  * @self: a #HdyStackableBox
1105  * @can_swipe_back: the new value
1106  *
1107  * Sets whether or not @self allows switching to the previous child that has
1108  * 'navigatable' child property set to %TRUE via a swipe gesture
1109  *
1110  * Since: 1.0
1111  */
1112 void
hdy_stackable_box_set_can_swipe_back(HdyStackableBox * self,gboolean can_swipe_back)1113 hdy_stackable_box_set_can_swipe_back (HdyStackableBox *self,
1114                                       gboolean         can_swipe_back)
1115 {
1116   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
1117 
1118   can_swipe_back = !!can_swipe_back;
1119 
1120   if (self->child_transition.can_swipe_back == can_swipe_back)
1121     return;
1122 
1123   self->child_transition.can_swipe_back = can_swipe_back;
1124   hdy_swipe_tracker_set_enabled (self->tracker, can_swipe_back || self->child_transition.can_swipe_forward);
1125 
1126   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CAN_SWIPE_BACK]);
1127 }
1128 
1129 /**
1130  * hdy_stackable_box_get_can_swipe_back
1131  * @self: a #HdyStackableBox
1132  *
1133  * Returns whether the #HdyStackableBox allows swiping to the previous child.
1134  *
1135  * Returns: %TRUE if back swipe is enabled.
1136  *
1137  * Since: 1.0
1138  */
1139 gboolean
hdy_stackable_box_get_can_swipe_back(HdyStackableBox * self)1140 hdy_stackable_box_get_can_swipe_back (HdyStackableBox *self)
1141 {
1142   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
1143 
1144   return self->child_transition.can_swipe_back;
1145 }
1146 
1147 /**
1148  * hdy_stackable_box_set_can_swipe_forward:
1149  * @self: a #HdyStackableBox
1150  * @can_swipe_forward: the new value
1151  *
1152  * Sets whether or not @self allows switching to the next child that has
1153  * 'navigatable' child property set to %TRUE via a swipe gesture.
1154  *
1155  * Since: 1.0
1156  */
1157 void
hdy_stackable_box_set_can_swipe_forward(HdyStackableBox * self,gboolean can_swipe_forward)1158 hdy_stackable_box_set_can_swipe_forward (HdyStackableBox *self,
1159                                          gboolean         can_swipe_forward)
1160 {
1161   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
1162 
1163   can_swipe_forward = !!can_swipe_forward;
1164 
1165   if (self->child_transition.can_swipe_forward == can_swipe_forward)
1166     return;
1167 
1168   self->child_transition.can_swipe_forward = can_swipe_forward;
1169   hdy_swipe_tracker_set_enabled (self->tracker, self->child_transition.can_swipe_back || can_swipe_forward);
1170 
1171   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CAN_SWIPE_FORWARD]);
1172 }
1173 
1174 /**
1175  * hdy_stackable_box_get_can_swipe_forward
1176  * @self: a #HdyStackableBox
1177  *
1178  * Returns whether the #HdyStackableBox allows swiping to the next child.
1179  *
1180  * Returns: %TRUE if forward swipe is enabled.
1181  *
1182  * Since: 1.0
1183  */
1184 gboolean
hdy_stackable_box_get_can_swipe_forward(HdyStackableBox * self)1185 hdy_stackable_box_get_can_swipe_forward (HdyStackableBox *self)
1186 {
1187   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
1188 
1189   return self->child_transition.can_swipe_forward;
1190 }
1191 
1192 static HdyStackableBoxChildInfo *
find_swipeable_child(HdyStackableBox * self,HdyNavigationDirection direction)1193 find_swipeable_child (HdyStackableBox        *self,
1194                       HdyNavigationDirection  direction)
1195 {
1196   GList *children;
1197   HdyStackableBoxChildInfo *child = NULL;
1198 
1199   children = g_list_find (self->children, self->visible_child);
1200   do {
1201     children = (direction == HDY_NAVIGATION_DIRECTION_BACK) ? children->prev : children->next;
1202 
1203     if (children == NULL)
1204       break;
1205 
1206     child = children->data;
1207   } while (child && !child->navigatable);
1208 
1209   return child;
1210 }
1211 
1212 /**
1213  * hdy_stackable_box_get_adjacent_child
1214  * @self: a #HdyStackableBox
1215  * @direction: the direction
1216  *
1217  * Gets the previous or next child that doesn't have 'navigatable' child
1218  * property set to %FALSE, or %NULL if it doesn't exist. This will be the same
1219  * widget hdy_stackable_box_navigate() will navigate to.
1220  *
1221  * Returns: (nullable) (transfer none): the previous or next child, or
1222  *   %NULL if it doesn't exist.
1223  *
1224  * Since: 1.0
1225  */
1226 GtkWidget *
hdy_stackable_box_get_adjacent_child(HdyStackableBox * self,HdyNavigationDirection direction)1227 hdy_stackable_box_get_adjacent_child (HdyStackableBox        *self,
1228                                       HdyNavigationDirection  direction)
1229 {
1230   HdyStackableBoxChildInfo *child;
1231 
1232   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), NULL);
1233 
1234   child = find_swipeable_child (self, direction);
1235 
1236   if (!child)
1237     return NULL;
1238 
1239   return child->widget;
1240 }
1241 
1242 /**
1243  * hdy_stackable_box_navigate
1244  * @self: a #HdyStackableBox
1245  * @direction: the direction
1246  *
1247  * Switches to the previous or next child that doesn't have 'navigatable'
1248  * child property set to %FALSE, similar to performing a swipe gesture to go
1249  * in @direction.
1250  *
1251  * Returns: %TRUE if visible child was changed, %FALSE otherwise.
1252  *
1253  * Since: 1.0
1254  */
1255 gboolean
hdy_stackable_box_navigate(HdyStackableBox * self,HdyNavigationDirection direction)1256 hdy_stackable_box_navigate (HdyStackableBox        *self,
1257                             HdyNavigationDirection  direction)
1258 {
1259   HdyStackableBoxChildInfo *child;
1260 
1261   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), FALSE);
1262 
1263   child = find_swipeable_child (self, direction);
1264 
1265   if (!child)
1266     return FALSE;
1267 
1268   set_visible_child_info (self, child, self->transition_type, self->child_transition.duration, TRUE);
1269 
1270   return TRUE;
1271 }
1272 
1273 /**
1274  * hdy_stackable_box_get_child_by_name:
1275  * @self: a #HdyStackableBox
1276  * @name: the name of the child to find
1277  *
1278  * Finds the child of @self with the name given as the argument. Returns %NULL
1279  * if there is no child with this name.
1280  *
1281  * Returns: (transfer none) (nullable): the requested child of @self
1282  *
1283  * Since: 1.0
1284  */
1285 GtkWidget *
hdy_stackable_box_get_child_by_name(HdyStackableBox * self,const gchar * name)1286 hdy_stackable_box_get_child_by_name (HdyStackableBox *self,
1287                                      const gchar     *name)
1288 {
1289   HdyStackableBoxChildInfo *child_info;
1290 
1291   g_return_val_if_fail (HDY_IS_STACKABLE_BOX (self), NULL);
1292   g_return_val_if_fail (name != NULL, NULL);
1293 
1294   child_info = find_child_info_for_name (self, name);
1295 
1296   return child_info ? child_info->widget : NULL;
1297 }
1298 
1299 static void
get_preferred_size(gint * min,gint * nat,gboolean same_orientation,gboolean homogeneous_folded,gboolean homogeneous_unfolded,gint visible_children,gdouble visible_child_progress,gint sum_nat,gint max_min,gint max_nat,gint visible_min,gint last_visible_min)1300 get_preferred_size (gint     *min,
1301                     gint     *nat,
1302                     gboolean  same_orientation,
1303                     gboolean  homogeneous_folded,
1304                     gboolean  homogeneous_unfolded,
1305                     gint      visible_children,
1306                     gdouble   visible_child_progress,
1307                     gint      sum_nat,
1308                     gint      max_min,
1309                     gint      max_nat,
1310                     gint      visible_min,
1311                     gint      last_visible_min)
1312 {
1313   if (same_orientation) {
1314     *min = homogeneous_folded ?
1315              max_min :
1316              hdy_lerp (last_visible_min, visible_min, visible_child_progress);
1317     *nat = homogeneous_unfolded ?
1318              max_nat * visible_children :
1319              sum_nat;
1320   }
1321   else {
1322     *min = homogeneous_folded ?
1323              max_min :
1324              hdy_lerp (last_visible_min, visible_min, visible_child_progress);
1325     *nat = max_nat;
1326   }
1327 }
1328 
1329 void
hdy_stackable_box_measure(HdyStackableBox * self,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)1330 hdy_stackable_box_measure (HdyStackableBox *self,
1331                            GtkOrientation   orientation,
1332                            int              for_size,
1333                            int             *minimum,
1334                            int             *natural,
1335                            int             *minimum_baseline,
1336                            int             *natural_baseline)
1337 {
1338   GList *children;
1339   HdyStackableBoxChildInfo *child_info;
1340   gint visible_children;
1341   gdouble visible_child_progress;
1342   gint child_min, max_min, visible_min, last_visible_min;
1343   gint child_nat, max_nat, sum_nat;
1344   gboolean same_orientation;
1345   void (*get_preferred_size_static) (GtkWidget *widget,
1346                                      gint      *minimum_width,
1347                                      gint      *natural_width);
1348   void (*get_preferred_size_for_size) (GtkWidget *widget,
1349                                        gint       height,
1350                                        gint      *minimum_width,
1351                                        gint      *natural_width);
1352 
1353   get_preferred_size_static = orientation == GTK_ORIENTATION_HORIZONTAL ?
1354     gtk_widget_get_preferred_width :
1355     gtk_widget_get_preferred_height;
1356   get_preferred_size_for_size = orientation == GTK_ORIENTATION_HORIZONTAL ?
1357     gtk_widget_get_preferred_width_for_height :
1358     gtk_widget_get_preferred_height_for_width;
1359 
1360   visible_children = 0;
1361   child_min = max_min = visible_min = last_visible_min = 0;
1362   child_nat = max_nat = sum_nat = 0;
1363   for (children = self->children; children; children = children->next) {
1364     child_info = children->data;
1365 
1366     if (child_info->widget == NULL || !gtk_widget_get_visible (child_info->widget))
1367       continue;
1368 
1369     visible_children++;
1370     if (for_size < 0)
1371       get_preferred_size_static (child_info->widget,
1372                                  &child_min, &child_nat);
1373     else
1374       get_preferred_size_for_size (child_info->widget, for_size,
1375                                    &child_min, &child_nat);
1376 
1377     max_min = MAX (max_min, child_min);
1378     max_nat = MAX (max_nat, child_nat);
1379     sum_nat += child_nat;
1380   }
1381 
1382   if (self->visible_child != NULL) {
1383     if (for_size < 0)
1384       get_preferred_size_static (self->visible_child->widget,
1385                                  &visible_min, NULL);
1386     else
1387       get_preferred_size_for_size (self->visible_child->widget, for_size,
1388                                    &visible_min, NULL);
1389   }
1390 
1391   if (self->last_visible_child != NULL) {
1392     if (for_size < 0)
1393       get_preferred_size_static (self->last_visible_child->widget,
1394                                  &last_visible_min, NULL);
1395     else
1396       get_preferred_size_for_size (self->last_visible_child->widget, for_size,
1397                                    &last_visible_min, NULL);
1398   } else {
1399     last_visible_min = visible_min;
1400   }
1401 
1402   visible_child_progress = self->child_transition.interpolate_size ? self->child_transition.progress : 1.0;
1403 
1404   same_orientation =
1405     orientation == gtk_orientable_get_orientation (GTK_ORIENTABLE (self->container));
1406 
1407   get_preferred_size (minimum, natural,
1408                       same_orientation && self->can_unfold,
1409                       self->homogeneous[HDY_FOLD_FOLDED][orientation],
1410                       self->homogeneous[HDY_FOLD_UNFOLDED][orientation],
1411                       visible_children, visible_child_progress,
1412                       sum_nat, max_min, max_nat, visible_min, last_visible_min);
1413 }
1414 
1415 static void
hdy_stackable_box_size_allocate_folded(HdyStackableBox * self,GtkAllocation * allocation)1416 hdy_stackable_box_size_allocate_folded (HdyStackableBox *self,
1417                                         GtkAllocation   *allocation)
1418 {
1419   GtkWidget *widget = GTK_WIDGET (self->container);
1420   GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
1421   GList *directed_children, *children;
1422   HdyStackableBoxChildInfo *child_info, *visible_child;
1423   gint start_size, end_size, visible_size;
1424   gint remaining_start_size, remaining_end_size, remaining_size;
1425   gint current_pad;
1426   gint max_child_size = 0;
1427   gint start_position, end_position;
1428   gboolean box_homogeneous;
1429   HdyStackableBoxTransitionType mode_transition_type;
1430   GtkTextDirection direction;
1431   gboolean under;
1432 
1433   directed_children = get_directed_children (self);
1434   visible_child = self->visible_child;
1435 
1436   if (!visible_child)
1437     return;
1438 
1439   for (children = directed_children; children; children = children->next) {
1440     child_info = children->data;
1441 
1442     if (!child_info->widget)
1443       continue;
1444 
1445     if (child_info->widget == visible_child->widget)
1446       continue;
1447 
1448     if (self->last_visible_child &&
1449         child_info->widget == self->last_visible_child->widget)
1450       continue;
1451 
1452     child_info->visible = FALSE;
1453   }
1454 
1455   if (visible_child->widget == NULL)
1456     return;
1457 
1458   /* FIXME is this needed? */
1459   if (!gtk_widget_get_visible (visible_child->widget)) {
1460     visible_child->visible = FALSE;
1461 
1462     return;
1463   }
1464 
1465   visible_child->visible = TRUE;
1466 
1467   mode_transition_type = self->transition_type;
1468 
1469   /* Avoid useless computations and allow visible child transitions. */
1470   if (self->mode_transition.current_pos <= 0.0) {
1471     /* Child transitions should be applied only when folded and when no mode
1472      * transition is ongoing.
1473      */
1474     for (children = directed_children; children; children = children->next) {
1475       child_info = children->data;
1476 
1477       if (child_info != visible_child &&
1478           child_info != self->last_visible_child) {
1479         child_info->visible = FALSE;
1480 
1481         continue;
1482       }
1483 
1484       child_info->alloc.x = get_child_window_x (self, child_info, allocation->width);
1485       child_info->alloc.y = get_child_window_y (self, child_info, allocation->height);
1486       child_info->alloc.width = allocation->width;
1487       child_info->alloc.height = allocation->height;
1488       child_info->visible = TRUE;
1489     }
1490 
1491     return;
1492   }
1493 
1494   /* Compute visible child size. */
1495   visible_size = orientation == GTK_ORIENTATION_HORIZONTAL ?
1496     MIN (allocation->width, MAX (visible_child->nat.width, (gint) (allocation->width * (1.0 - self->mode_transition.current_pos)))) :
1497     MIN (allocation->height, MAX (visible_child->nat.height, (gint) (allocation->height * (1.0 - self->mode_transition.current_pos))));
1498 
1499   /* Compute homogeneous box child size. */
1500   box_homogeneous = (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] && orientation == GTK_ORIENTATION_HORIZONTAL) ||
1501                     (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] && orientation == GTK_ORIENTATION_VERTICAL);
1502   if (box_homogeneous) {
1503     for (children = directed_children; children; children = children->next) {
1504       child_info = children->data;
1505 
1506       max_child_size = orientation == GTK_ORIENTATION_HORIZONTAL ?
1507         MAX (max_child_size, child_info->nat.width) :
1508         MAX (max_child_size, child_info->nat.height);
1509     }
1510   }
1511 
1512   /* Compute the start size. */
1513   start_size = 0;
1514   for (children = directed_children; children; children = children->next) {
1515     child_info = children->data;
1516 
1517     if (child_info == visible_child)
1518       break;
1519 
1520     start_size += orientation == GTK_ORIENTATION_HORIZONTAL ?
1521       (box_homogeneous ? max_child_size : child_info->nat.width) :
1522       (box_homogeneous ? max_child_size : child_info->nat.height);
1523   }
1524 
1525   /* Compute the end size. */
1526   end_size = 0;
1527   for (children = g_list_last (directed_children); children; children = children->prev) {
1528     child_info = children->data;
1529 
1530     if (child_info == visible_child)
1531       break;
1532 
1533     end_size += orientation == GTK_ORIENTATION_HORIZONTAL ?
1534       (box_homogeneous ? max_child_size : child_info->nat.width) :
1535       (box_homogeneous ? max_child_size : child_info->nat.height);
1536   }
1537 
1538   /* Compute pads. */
1539   remaining_size = orientation == GTK_ORIENTATION_HORIZONTAL ?
1540     allocation->width - visible_size :
1541     allocation->height - visible_size;
1542   remaining_start_size = (gint) (remaining_size * ((gdouble) start_size / (gdouble) (start_size + end_size)));
1543   remaining_end_size = remaining_size - remaining_start_size;
1544 
1545   /* Store start and end allocations. */
1546   switch (orientation) {
1547   case GTK_ORIENTATION_HORIZONTAL:
1548     direction = gtk_widget_get_direction (GTK_WIDGET (self->container));
1549     under = (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_LTR) ||
1550             (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_RTL);
1551     start_position = under ? 0 : remaining_start_size - start_size;
1552     self->mode_transition.start_progress = under ? (gdouble) remaining_size / start_size : 1;
1553     under = (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_LTR) ||
1554             (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_RTL);
1555     end_position = under ? allocation->width - end_size : remaining_start_size + visible_size;
1556     self->mode_transition.end_progress = under ? (gdouble) remaining_end_size / end_size : 1;
1557     break;
1558   case GTK_ORIENTATION_VERTICAL:
1559     under = mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER;
1560     start_position = under ? 0 : remaining_start_size - start_size;
1561     self->mode_transition.start_progress = under ? (gdouble) remaining_size / start_size : 1;
1562     under = mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER;
1563     end_position = remaining_start_size + visible_size;
1564     self->mode_transition.end_progress = under ? (gdouble) remaining_end_size / end_size : 1;
1565     break;
1566   default:
1567     g_assert_not_reached ();
1568   }
1569 
1570   /* Allocate visible child. */
1571   if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1572     visible_child->alloc.width = visible_size;
1573     visible_child->alloc.height = allocation->height;
1574     visible_child->alloc.x = remaining_start_size;
1575     visible_child->alloc.y = 0;
1576     visible_child->visible = TRUE;
1577   }
1578   else {
1579     visible_child->alloc.width = allocation->width;
1580     visible_child->alloc.height = visible_size;
1581     visible_child->alloc.x = 0;
1582     visible_child->alloc.y = remaining_start_size;
1583     visible_child->visible = TRUE;
1584   }
1585 
1586   /* Allocate starting children. */
1587   current_pad = start_position;
1588 
1589   for (children = directed_children; children; children = children->next) {
1590     child_info = children->data;
1591 
1592     if (child_info == visible_child)
1593       break;
1594 
1595     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1596       child_info->alloc.width = box_homogeneous ?
1597         max_child_size :
1598         child_info->nat.width;
1599       child_info->alloc.height = allocation->height;
1600       child_info->alloc.x = current_pad;
1601       child_info->alloc.y = 0;
1602       child_info->visible = child_info->alloc.x + child_info->alloc.width > 0;
1603 
1604       current_pad += child_info->alloc.width;
1605     }
1606     else {
1607       child_info->alloc.width = allocation->width;
1608       child_info->alloc.height = box_homogeneous ?
1609         max_child_size :
1610         child_info->nat.height;
1611       child_info->alloc.x = 0;
1612       child_info->alloc.y = current_pad;
1613       child_info->visible = child_info->alloc.y + child_info->alloc.height > 0;
1614 
1615       current_pad += child_info->alloc.height;
1616     }
1617   }
1618 
1619   /* Allocate ending children. */
1620   current_pad = end_position;
1621 
1622   if (!children || !children->next)
1623     return;
1624 
1625   for (children = children->next; children; children = children->next) {
1626     child_info = children->data;
1627 
1628     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1629       child_info->alloc.width = box_homogeneous ?
1630         max_child_size :
1631         child_info->nat.width;
1632       child_info->alloc.height = allocation->height;
1633       child_info->alloc.x = current_pad;
1634       child_info->alloc.y = 0;
1635       child_info->visible = child_info->alloc.x < allocation->width;
1636 
1637       current_pad += child_info->alloc.width;
1638     }
1639     else {
1640       child_info->alloc.width = allocation->width;
1641       child_info->alloc.height = box_homogeneous ?
1642         max_child_size :
1643         child_info->nat.height;
1644       child_info->alloc.x = 0;
1645       child_info->alloc.y = current_pad;
1646       child_info->visible = child_info->alloc.y < allocation->height;
1647 
1648       current_pad += child_info->alloc.height;
1649     }
1650   }
1651 }
1652 
1653 static void
hdy_stackable_box_size_allocate_unfolded(HdyStackableBox * self,GtkAllocation * allocation)1654 hdy_stackable_box_size_allocate_unfolded (HdyStackableBox *self,
1655                                           GtkAllocation   *allocation)
1656 {
1657   GtkWidget *widget = GTK_WIDGET (self->container);
1658   GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
1659   GtkAllocation remaining_alloc;
1660   GList *directed_children, *children;
1661   HdyStackableBoxChildInfo *child_info, *visible_child;
1662   gint homogeneous_size = 0, min_size, extra_size;
1663   gint per_child_extra, n_extra_widgets;
1664   gint n_visible_children, n_expand_children;
1665   gint start_pad = 0, end_pad = 0;
1666   gboolean box_homogeneous;
1667   HdyStackableBoxTransitionType mode_transition_type;
1668   GtkTextDirection direction;
1669   gboolean under;
1670 
1671   directed_children = get_directed_children (self);
1672   visible_child = self->visible_child;
1673 
1674   box_homogeneous = (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] && orientation == GTK_ORIENTATION_HORIZONTAL) ||
1675                     (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] && orientation == GTK_ORIENTATION_VERTICAL);
1676 
1677   n_visible_children = n_expand_children = 0;
1678   for (children = directed_children; children; children = children->next) {
1679     child_info = children->data;
1680 
1681     child_info->visible = child_info->widget != NULL && gtk_widget_get_visible (child_info->widget);
1682 
1683     if (child_info->visible) {
1684       n_visible_children++;
1685       if (gtk_widget_compute_expand (child_info->widget, orientation))
1686         n_expand_children++;
1687     }
1688     else {
1689       child_info->min.width = child_info->min.height = 0;
1690       child_info->nat.width = child_info->nat.height = 0;
1691     }
1692   }
1693 
1694   /* Compute repartition of extra space. */
1695 
1696   if (box_homogeneous) {
1697     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1698       homogeneous_size = n_visible_children > 0 ? allocation->width / n_visible_children : 0;
1699       n_expand_children = n_visible_children > 0 ? allocation->width % n_visible_children : 0;
1700       min_size = allocation->width - n_expand_children;
1701     }
1702     else {
1703       homogeneous_size = n_visible_children > 0 ? allocation->height / n_visible_children : 0;
1704       n_expand_children = n_visible_children > 0 ? allocation->height % n_visible_children : 0;
1705       min_size = allocation->height - n_expand_children;
1706     }
1707   }
1708   else {
1709     min_size = 0;
1710     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1711       for (children = directed_children; children; children = children->next) {
1712         child_info = children->data;
1713 
1714         min_size += child_info->nat.width;
1715       }
1716     }
1717     else {
1718       for (children = directed_children; children; children = children->next) {
1719         child_info = children->data;
1720 
1721         min_size += child_info->nat.height;
1722       }
1723     }
1724   }
1725 
1726   remaining_alloc.x = 0;
1727   remaining_alloc.y = 0;
1728   remaining_alloc.width = allocation->width;
1729   remaining_alloc.height = allocation->height;
1730 
1731   extra_size = orientation == GTK_ORIENTATION_HORIZONTAL ?
1732     remaining_alloc.width - min_size :
1733     remaining_alloc.height - min_size;
1734 
1735   per_child_extra = 0, n_extra_widgets = 0;
1736   if (n_expand_children > 0) {
1737     per_child_extra = extra_size / n_expand_children;
1738     n_extra_widgets = extra_size % n_expand_children;
1739   }
1740 
1741   /* Compute children allocation */
1742   for (children = directed_children; children; children = children->next) {
1743     child_info = children->data;
1744 
1745     if (!child_info->visible)
1746       continue;
1747 
1748     child_info->alloc.x = remaining_alloc.x;
1749     child_info->alloc.y = remaining_alloc.y;
1750 
1751     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1752       if (box_homogeneous) {
1753         child_info->alloc.width = homogeneous_size;
1754         if (n_extra_widgets > 0) {
1755           child_info->alloc.width++;
1756           n_extra_widgets--;
1757         }
1758       }
1759       else {
1760         child_info->alloc.width = child_info->nat.width;
1761         if (gtk_widget_compute_expand (child_info->widget, orientation)) {
1762           child_info->alloc.width += per_child_extra;
1763           if (n_extra_widgets > 0) {
1764             child_info->alloc.width++;
1765             n_extra_widgets--;
1766           }
1767         }
1768       }
1769       child_info->alloc.height = remaining_alloc.height;
1770 
1771       remaining_alloc.x += child_info->alloc.width;
1772       remaining_alloc.width -= child_info->alloc.width;
1773     }
1774     else {
1775       if (box_homogeneous) {
1776         child_info->alloc.height = homogeneous_size;
1777         if (n_extra_widgets > 0) {
1778           child_info->alloc.height++;
1779           n_extra_widgets--;
1780         }
1781       }
1782       else {
1783         child_info->alloc.height = child_info->nat.height;
1784         if (gtk_widget_compute_expand (child_info->widget, orientation)) {
1785           child_info->alloc.height += per_child_extra;
1786           if (n_extra_widgets > 0) {
1787             child_info->alloc.height++;
1788             n_extra_widgets--;
1789           }
1790         }
1791       }
1792       child_info->alloc.width = remaining_alloc.width;
1793 
1794       remaining_alloc.y += child_info->alloc.height;
1795       remaining_alloc.height -= child_info->alloc.height;
1796     }
1797   }
1798 
1799   /* Apply animations. */
1800 
1801   if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1802     start_pad = (gint) ((visible_child->alloc.x) * (1.0 - self->mode_transition.current_pos));
1803     end_pad = (gint) ((allocation->width - (visible_child->alloc.x + visible_child->alloc.width)) * (1.0 - self->mode_transition.current_pos));
1804   }
1805   else {
1806     start_pad = (gint) ((visible_child->alloc.y) * (1.0 - self->mode_transition.current_pos));
1807     end_pad = (gint) ((allocation->height - (visible_child->alloc.y + visible_child->alloc.height)) * (1.0 - self->mode_transition.current_pos));
1808   }
1809 
1810   mode_transition_type = self->transition_type;
1811   direction = gtk_widget_get_direction (GTK_WIDGET (self->container));
1812 
1813   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1814     under = (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_LTR) ||
1815             (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_RTL);
1816   else
1817     under = mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER;
1818   for (children = directed_children; children; children = children->next) {
1819     child_info = children->data;
1820 
1821     if (child_info == visible_child)
1822       break;
1823 
1824     if (!child_info->visible)
1825       continue;
1826 
1827     if (under)
1828       continue;
1829 
1830     if (orientation == GTK_ORIENTATION_HORIZONTAL)
1831       child_info->alloc.x -= start_pad;
1832     else
1833       child_info->alloc.y -= start_pad;
1834   }
1835 
1836   self->mode_transition.start_progress = under ? self->mode_transition.current_pos : 1;
1837 
1838   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1839     under = (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER && direction == GTK_TEXT_DIR_LTR) ||
1840             (mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER && direction == GTK_TEXT_DIR_RTL);
1841   else
1842     under = mode_transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER;
1843   for (children = g_list_last (directed_children); children; children = children->prev) {
1844     child_info = children->data;
1845 
1846     if (child_info == visible_child)
1847       break;
1848 
1849     if (!child_info->visible)
1850       continue;
1851 
1852     if (under)
1853       continue;
1854 
1855     if (orientation == GTK_ORIENTATION_HORIZONTAL)
1856       child_info->alloc.x += end_pad;
1857     else
1858       child_info->alloc.y += end_pad;
1859   }
1860 
1861   self->mode_transition.end_progress = under ? self->mode_transition.current_pos : 1;
1862 
1863   if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1864     visible_child->alloc.x -= start_pad;
1865     visible_child->alloc.width += start_pad + end_pad;
1866   }
1867   else {
1868     visible_child->alloc.y -= start_pad;
1869     visible_child->alloc.height += start_pad + end_pad;
1870   }
1871 }
1872 
1873 static HdyStackableBoxChildInfo *
get_top_overlap_child(HdyStackableBox * self)1874 get_top_overlap_child (HdyStackableBox *self)
1875 {
1876   gboolean is_rtl, start;
1877 
1878   if (!self->last_visible_child)
1879     return self->visible_child;
1880 
1881   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL;
1882 
1883   start = (self->child_transition.active_direction == GTK_PAN_DIRECTION_LEFT && !is_rtl) ||
1884           (self->child_transition.active_direction == GTK_PAN_DIRECTION_RIGHT && is_rtl) ||
1885            self->child_transition.active_direction == GTK_PAN_DIRECTION_UP;
1886 
1887   switch (self->transition_type) {
1888   case HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE:
1889     // Nothing overlaps in this case
1890     return NULL;
1891   case HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER:
1892     return start ? self->visible_child : self->last_visible_child;
1893   case HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER:
1894     return start ? self->last_visible_child : self->visible_child;
1895   default:
1896     g_assert_not_reached ();
1897   }
1898 }
1899 
1900 static void
restack_windows(HdyStackableBox * self)1901 restack_windows (HdyStackableBox *self)
1902 {
1903   HdyStackableBoxChildInfo *child_info, *overlap_child;
1904   GList *l;
1905 
1906   overlap_child = get_top_overlap_child (self);
1907 
1908   switch (self->transition_type) {
1909   case HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE:
1910     // Nothing overlaps in this case
1911     return;
1912   case HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER:
1913     for (l = g_list_last (self->children); l; l = l->prev) {
1914       child_info = l->data;
1915 
1916       if (child_info->window)
1917         gdk_window_raise (child_info->window);
1918 
1919       if (child_info == overlap_child)
1920         break;
1921     }
1922 
1923     break;
1924   case HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER:
1925     for (l = self->children; l; l = l->next) {
1926       child_info = l->data;
1927 
1928       if (child_info->window)
1929         gdk_window_raise (child_info->window);
1930 
1931       if (child_info == overlap_child)
1932         break;
1933     }
1934 
1935     break;
1936   default:
1937     g_assert_not_reached ();
1938   }
1939 }
1940 
1941 void
hdy_stackable_box_size_allocate(HdyStackableBox * self,GtkAllocation * allocation)1942 hdy_stackable_box_size_allocate (HdyStackableBox *self,
1943                                  GtkAllocation   *allocation)
1944 {
1945   GtkWidget *widget = GTK_WIDGET (self->container);
1946   GtkOrientation orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
1947   GList *directed_children, *children;
1948   HdyStackableBoxChildInfo *child_info;
1949   gboolean folded;
1950 
1951   directed_children = get_directed_children (self);
1952 
1953   gtk_widget_set_allocation (widget, allocation);
1954 
1955   if (gtk_widget_get_realized (widget)) {
1956     gdk_window_move_resize (gtk_widget_get_window (widget),
1957                             allocation->x, allocation->y,
1958                             allocation->width, allocation->height);
1959   }
1960 
1961   /* Prepare children information. */
1962   for (children = directed_children; children; children = children->next) {
1963     child_info = children->data;
1964 
1965     gtk_widget_get_preferred_size (child_info->widget, &child_info->min, &child_info->nat);
1966     child_info->alloc.x = child_info->alloc.y = child_info->alloc.width = child_info->alloc.height = 0;
1967     child_info->visible = FALSE;
1968   }
1969 
1970   /* Check whether the children should be stacked or not. */
1971   if (self->can_unfold) {
1972     gint nat_box_size = 0, nat_max_size = 0, visible_children = 0;
1973 
1974     if (orientation == GTK_ORIENTATION_HORIZONTAL) {
1975 
1976       for (children = directed_children; children; children = children->next) {
1977         child_info = children->data;
1978 
1979         /* FIXME Check the child is visible. */
1980         if (!child_info->widget)
1981           continue;
1982 
1983         if (child_info->nat.width <= 0)
1984           continue;
1985 
1986         nat_box_size += child_info->nat.width;
1987         nat_max_size = MAX (nat_max_size, child_info->nat.width);
1988         visible_children++;
1989       }
1990       if (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL])
1991         nat_box_size = nat_max_size * visible_children;
1992       folded = visible_children > 1 && allocation->width < nat_box_size;
1993     }
1994     else {
1995       for (children = directed_children; children; children = children->next) {
1996         child_info = children->data;
1997 
1998         /* FIXME Check the child is visible. */
1999         if (!child_info->widget)
2000           continue;
2001 
2002         if (child_info->nat.height <= 0)
2003           continue;
2004 
2005         nat_box_size += child_info->nat.height;
2006         nat_max_size = MAX (nat_max_size, child_info->nat.height);
2007         visible_children++;
2008       }
2009       if (self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL])
2010         nat_box_size = nat_max_size * visible_children;
2011       folded = visible_children > 1 && allocation->height < nat_box_size;
2012     }
2013   } else {
2014     folded = TRUE;
2015   }
2016 
2017   hdy_stackable_box_set_folded (self, folded);
2018 
2019   /* Allocate size to the children. */
2020   if (folded)
2021     hdy_stackable_box_size_allocate_folded (self, allocation);
2022   else
2023     hdy_stackable_box_size_allocate_unfolded (self, allocation);
2024 
2025   /* Apply visibility and allocation. */
2026   for (children = directed_children; children; children = children->next) {
2027     GtkAllocation alloc;
2028 
2029     child_info = children->data;
2030 
2031     gtk_widget_set_child_visible (child_info->widget, child_info->visible);
2032 
2033     if (child_info->window &&
2034         child_info->visible != gdk_window_is_visible (child_info->window)) {
2035       if (child_info->visible)
2036         gdk_window_show (child_info->window);
2037       else
2038         gdk_window_hide (child_info->window);
2039     }
2040 
2041     if (!child_info->visible)
2042       continue;
2043 
2044     if (child_info->window)
2045       gdk_window_move_resize (child_info->window,
2046                               child_info->alloc.x,
2047                               child_info->alloc.y,
2048                               child_info->alloc.width,
2049                               child_info->alloc.height);
2050 
2051     alloc.x = 0;
2052     alloc.y = 0;
2053     alloc.width = child_info->alloc.width;
2054     alloc.height = child_info->alloc.height;
2055     gtk_widget_size_allocate (child_info->widget, &alloc);
2056 
2057     if (gtk_widget_get_realized (widget))
2058       gtk_widget_show (child_info->widget);
2059   }
2060 
2061   restack_windows (self);
2062 }
2063 
2064 gboolean
hdy_stackable_box_draw(HdyStackableBox * self,cairo_t * cr)2065 hdy_stackable_box_draw (HdyStackableBox *self,
2066                         cairo_t         *cr)
2067 {
2068   GtkWidget *widget = GTK_WIDGET (self->container);
2069   GList *stacked_children, *l;
2070   HdyStackableBoxChildInfo *child_info, *overlap_child;
2071   gboolean is_transition;
2072   gboolean is_vertical;
2073   gboolean is_rtl;
2074   gboolean is_over;
2075   GtkAllocation shadow_rect;
2076   gdouble shadow_progress, mode_progress;
2077   GtkPanDirection shadow_direction;
2078 
2079   overlap_child = get_top_overlap_child (self);
2080 
2081   is_transition = self->child_transition.is_gesture_active ||
2082                   gtk_progress_tracker_get_state (&self->child_transition.tracker) != GTK_PROGRESS_STATE_AFTER ||
2083                   gtk_progress_tracker_get_state (&self->mode_transition.tracker) != GTK_PROGRESS_STATE_AFTER;
2084 
2085   if (!is_transition ||
2086       self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE ||
2087       !overlap_child) {
2088     for (l = self->children; l; l = l->next) {
2089       child_info = l->data;
2090 
2091       if (!gtk_cairo_should_draw_window (cr, child_info->window))
2092         continue;
2093 
2094       gtk_container_propagate_draw (self->container,
2095                                     child_info->widget,
2096                                     cr);
2097     }
2098 
2099     return GDK_EVENT_PROPAGATE;
2100   }
2101 
2102   stacked_children = self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER ?
2103                      self->children_reversed : self->children;
2104 
2105   is_vertical = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget)) == GTK_ORIENTATION_VERTICAL;
2106   is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
2107   is_over = self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER;
2108 
2109   cairo_save (cr);
2110 
2111   shadow_rect.x = 0;
2112   shadow_rect.y = 0;
2113   shadow_rect.width = gtk_widget_get_allocated_width (widget);
2114   shadow_rect.height = gtk_widget_get_allocated_height (widget);
2115 
2116   if (is_vertical) {
2117     if (!is_over) {
2118       shadow_rect.y = overlap_child->alloc.y + overlap_child->alloc.height;
2119       shadow_rect.height -= shadow_rect.y;
2120       shadow_direction = GTK_PAN_DIRECTION_UP;
2121       mode_progress = self->mode_transition.end_progress;
2122     } else {
2123       shadow_rect.height = overlap_child->alloc.y;
2124       shadow_direction = GTK_PAN_DIRECTION_DOWN;
2125       mode_progress = self->mode_transition.start_progress;
2126     }
2127   } else {
2128     if (is_over == is_rtl) {
2129       shadow_rect.x = overlap_child->alloc.x + overlap_child->alloc.width;
2130       shadow_rect.width -= shadow_rect.x;
2131       shadow_direction = GTK_PAN_DIRECTION_LEFT;
2132       mode_progress = self->mode_transition.end_progress;
2133     } else {
2134       shadow_rect.width = overlap_child->alloc.x;
2135       shadow_direction = GTK_PAN_DIRECTION_RIGHT;
2136       mode_progress = self->mode_transition.start_progress;
2137     }
2138   }
2139 
2140   if (gtk_progress_tracker_get_state (&self->mode_transition.tracker) != GTK_PROGRESS_STATE_AFTER) {
2141     shadow_progress = mode_progress;
2142   } else {
2143     GtkPanDirection direction = self->child_transition.active_direction;
2144     GtkPanDirection left_or_right = is_rtl ? GTK_PAN_DIRECTION_RIGHT : GTK_PAN_DIRECTION_LEFT;
2145     gint width = gtk_widget_get_allocated_width (widget);
2146     gint height = gtk_widget_get_allocated_height (widget);
2147 
2148     if (direction == GTK_PAN_DIRECTION_UP || direction == left_or_right)
2149       shadow_progress = self->child_transition.progress;
2150     else
2151       shadow_progress = 1 - self->child_transition.progress;
2152 
2153     if (is_over)
2154       shadow_progress = 1 - shadow_progress;
2155 
2156     /* Normalize the shadow rect size so that we can cache the shadow */
2157     if (shadow_direction == GTK_PAN_DIRECTION_RIGHT)
2158       shadow_rect.x -= (width - shadow_rect.width);
2159     else if (shadow_direction == GTK_PAN_DIRECTION_DOWN)
2160       shadow_rect.y -= (height - shadow_rect.height);
2161 
2162     shadow_rect.width = width;
2163     shadow_rect.height = height;
2164   }
2165 
2166   cairo_rectangle (cr, shadow_rect.x, shadow_rect.y, shadow_rect.width, shadow_rect.height);
2167   cairo_clip (cr);
2168 
2169   for (l = stacked_children; l; l = l->next) {
2170     child_info = l->data;
2171 
2172     if (!gtk_cairo_should_draw_window (cr, child_info->window))
2173       continue;
2174 
2175     if (child_info == overlap_child)
2176       cairo_restore (cr);
2177 
2178     gtk_container_propagate_draw (self->container,
2179                                   child_info->widget,
2180                                   cr);
2181   }
2182 
2183   if (shadow_progress > 0) {
2184     cairo_save (cr);
2185     cairo_translate (cr, shadow_rect.x, shadow_rect.y);
2186     hdy_shadow_helper_draw_shadow (self->shadow_helper, cr,
2187                                    shadow_rect.width, shadow_rect.height,
2188                                    shadow_progress, shadow_direction);
2189     cairo_restore (cr);
2190   }
2191 
2192   return GDK_EVENT_PROPAGATE;
2193 }
2194 
2195 static void
update_tracker_orientation(HdyStackableBox * self)2196 update_tracker_orientation (HdyStackableBox *self)
2197 {
2198   gboolean reverse;
2199 
2200   reverse = (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
2201              gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL);
2202 
2203   g_object_set (self->tracker,
2204                 "orientation", self->orientation,
2205                 "reversed", reverse,
2206                 NULL);
2207 }
2208 
2209 void
hdy_stackable_box_direction_changed(HdyStackableBox * self,GtkTextDirection previous_direction)2210 hdy_stackable_box_direction_changed (HdyStackableBox  *self,
2211                                      GtkTextDirection  previous_direction)
2212 {
2213   update_tracker_orientation (self);
2214 }
2215 
2216 static void
hdy_stackable_box_child_visibility_notify_cb(GObject * obj,GParamSpec * pspec,gpointer user_data)2217 hdy_stackable_box_child_visibility_notify_cb (GObject    *obj,
2218                                               GParamSpec *pspec,
2219                                               gpointer    user_data)
2220 {
2221   HdyStackableBox *self = HDY_STACKABLE_BOX (user_data);
2222   GtkWidget *widget = GTK_WIDGET (obj);
2223   HdyStackableBoxChildInfo *child_info;
2224 
2225   child_info = find_child_info_for_widget (self, widget);
2226 
2227   if (self->visible_child == NULL && gtk_widget_get_visible (widget))
2228     set_visible_child_info (self, child_info, self->transition_type, self->child_transition.duration, TRUE);
2229   else if (self->visible_child == child_info && !gtk_widget_get_visible (widget))
2230     set_visible_child_info (self, NULL, self->transition_type, self->child_transition.duration, TRUE);
2231 
2232   if (child_info == self->last_visible_child) {
2233     gtk_widget_set_child_visible (self->last_visible_child->widget, !self->folded);
2234     self->last_visible_child = NULL;
2235   }
2236 }
2237 
2238 static void
register_window(HdyStackableBox * self,HdyStackableBoxChildInfo * child)2239 register_window (HdyStackableBox          *self,
2240                  HdyStackableBoxChildInfo *child)
2241 {
2242   GtkWidget *widget = GTK_WIDGET (self->container);
2243   GdkWindowAttr attributes = { 0 };
2244   GdkWindowAttributesType attributes_mask;
2245 
2246   attributes.x = child->alloc.x;
2247   attributes.y = child->alloc.y;
2248   attributes.width = child->alloc.width;
2249   attributes.height = child->alloc.height;
2250   attributes.window_type = GDK_WINDOW_CHILD;
2251   attributes.wclass = GDK_INPUT_OUTPUT;
2252   attributes.visual = gtk_widget_get_visual (widget);
2253   attributes.event_mask = gtk_widget_get_events (widget);
2254   attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
2255 
2256   attributes.event_mask = gtk_widget_get_events (widget) |
2257                           gtk_widget_get_events (child->widget);
2258 
2259   child->window = gdk_window_new (gtk_widget_get_window (widget), &attributes, attributes_mask);
2260   gtk_widget_register_window (widget, child->window);
2261 
2262   gtk_widget_set_parent_window (child->widget, child->window);
2263 
2264   gdk_window_show (child->window);
2265 }
2266 
2267 static void
unregister_window(HdyStackableBox * self,HdyStackableBoxChildInfo * child)2268 unregister_window (HdyStackableBox          *self,
2269                    HdyStackableBoxChildInfo *child)
2270 {
2271   GtkWidget *widget = GTK_WIDGET (self->container);
2272 
2273   if (!child->window)
2274     return;
2275 
2276   gtk_widget_unregister_window (widget, child->window);
2277   gdk_window_destroy (child->window);
2278   child->window = NULL;
2279 }
2280 
2281 void
hdy_stackable_box_add(HdyStackableBox * self,GtkWidget * widget)2282 hdy_stackable_box_add (HdyStackableBox *self,
2283                        GtkWidget       *widget)
2284 {
2285   if (self->children == NULL) {
2286     hdy_stackable_box_insert_child_after (self, widget, NULL);
2287   } else {
2288     HdyStackableBoxChildInfo *last_child_info = g_list_last (self->children)->data;
2289 
2290     hdy_stackable_box_insert_child_after (self, widget, last_child_info->widget);
2291   }
2292 }
2293 
2294 void
hdy_stackable_box_remove(HdyStackableBox * self,GtkWidget * widget)2295 hdy_stackable_box_remove (HdyStackableBox *self,
2296                           GtkWidget       *widget)
2297 {
2298   g_autoptr (HdyStackableBoxChildInfo) child_info = find_child_info_for_widget (self, widget);
2299   gboolean contains_child = child_info != NULL;
2300 
2301   g_return_if_fail (contains_child);
2302 
2303   self->children = g_list_remove (self->children, child_info);
2304   self->children_reversed = g_list_remove (self->children_reversed, child_info);
2305 
2306   g_signal_handlers_disconnect_by_func (widget,
2307                                         hdy_stackable_box_child_visibility_notify_cb,
2308                                         self);
2309 
2310   if (hdy_stackable_box_get_visible_child (self) == widget)
2311     set_visible_child_info (self, NULL, self->transition_type, self->child_transition.duration, TRUE);
2312 
2313   if (child_info == self->last_visible_child)
2314     self->last_visible_child = NULL;
2315 
2316   if (gtk_widget_get_visible (widget))
2317     gtk_widget_queue_resize (GTK_WIDGET (self->container));
2318 
2319   unregister_window (self, child_info);
2320 
2321   gtk_widget_unparent (widget);
2322 }
2323 
2324 void
hdy_stackable_box_forall(HdyStackableBox * self,gboolean include_internals,GtkCallback callback,gpointer callback_data)2325 hdy_stackable_box_forall (HdyStackableBox *self,
2326                           gboolean         include_internals,
2327                           GtkCallback      callback,
2328                           gpointer         callback_data)
2329 {
2330   /* This shallow copy is needed when the callback changes the list while we are
2331    * looping through it, for example by calling hdy_stackable_box_remove() on all
2332    * children when destroying the HdyStackableBox_private_offset.
2333    */
2334   g_autoptr (GList) children_copy = g_list_copy (self->children);
2335   GList *children;
2336   HdyStackableBoxChildInfo *child_info;
2337 
2338   for (children = children_copy; children; children = children->next) {
2339     child_info = children->data;
2340 
2341     (* callback) (child_info->widget, callback_data);
2342   }
2343 
2344   g_list_free (self->children_reversed);
2345   self->children_reversed = g_list_copy (self->children);
2346   self->children_reversed = g_list_reverse (self->children_reversed);
2347 }
2348 
2349 static void
hdy_stackable_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)2350 hdy_stackable_box_get_property (GObject    *object,
2351                                 guint       prop_id,
2352                                 GValue     *value,
2353                                 GParamSpec *pspec)
2354 {
2355   HdyStackableBox *self = HDY_STACKABLE_BOX (object);
2356 
2357   switch (prop_id) {
2358   case PROP_FOLDED:
2359     g_value_set_boolean (value, hdy_stackable_box_get_folded (self));
2360     break;
2361   case PROP_HHOMOGENEOUS_FOLDED:
2362     g_value_set_boolean (value, hdy_stackable_box_get_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL));
2363     break;
2364   case PROP_VHOMOGENEOUS_FOLDED:
2365     g_value_set_boolean (value, hdy_stackable_box_get_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL));
2366     break;
2367   case PROP_HHOMOGENEOUS_UNFOLDED:
2368     g_value_set_boolean (value, hdy_stackable_box_get_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL));
2369     break;
2370   case PROP_VHOMOGENEOUS_UNFOLDED:
2371     g_value_set_boolean (value, hdy_stackable_box_get_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL));
2372     break;
2373   case PROP_VISIBLE_CHILD:
2374     g_value_set_object (value, hdy_stackable_box_get_visible_child (self));
2375     break;
2376   case PROP_VISIBLE_CHILD_NAME:
2377     g_value_set_string (value, hdy_stackable_box_get_visible_child_name (self));
2378     break;
2379   case PROP_TRANSITION_TYPE:
2380     g_value_set_enum (value, hdy_stackable_box_get_transition_type (self));
2381     break;
2382   case PROP_MODE_TRANSITION_DURATION:
2383     g_value_set_uint (value, hdy_stackable_box_get_mode_transition_duration (self));
2384     break;
2385   case PROP_CHILD_TRANSITION_DURATION:
2386     g_value_set_uint (value, hdy_stackable_box_get_child_transition_duration (self));
2387     break;
2388   case PROP_CHILD_TRANSITION_RUNNING:
2389     g_value_set_boolean (value, hdy_stackable_box_get_child_transition_running (self));
2390     break;
2391   case PROP_INTERPOLATE_SIZE:
2392     g_value_set_boolean (value, hdy_stackable_box_get_interpolate_size (self));
2393     break;
2394   case PROP_CAN_SWIPE_BACK:
2395     g_value_set_boolean (value, hdy_stackable_box_get_can_swipe_back (self));
2396     break;
2397   case PROP_CAN_SWIPE_FORWARD:
2398     g_value_set_boolean (value, hdy_stackable_box_get_can_swipe_forward (self));
2399     break;
2400   case PROP_ORIENTATION:
2401     g_value_set_enum (value, hdy_stackable_box_get_orientation (self));
2402     break;
2403   default:
2404     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2405   }
2406 }
2407 
2408 static void
hdy_stackable_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)2409 hdy_stackable_box_set_property (GObject      *object,
2410                                 guint         prop_id,
2411                                 const GValue *value,
2412                                 GParamSpec   *pspec)
2413 {
2414   HdyStackableBox *self = HDY_STACKABLE_BOX (object);
2415 
2416   switch (prop_id) {
2417   case PROP_HHOMOGENEOUS_FOLDED:
2418     hdy_stackable_box_set_homogeneous (self, TRUE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value));
2419     break;
2420   case PROP_VHOMOGENEOUS_FOLDED:
2421     hdy_stackable_box_set_homogeneous (self, TRUE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value));
2422     break;
2423   case PROP_HHOMOGENEOUS_UNFOLDED:
2424     hdy_stackable_box_set_homogeneous (self, FALSE, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value));
2425     break;
2426   case PROP_VHOMOGENEOUS_UNFOLDED:
2427     hdy_stackable_box_set_homogeneous (self, FALSE, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value));
2428     break;
2429   case PROP_VISIBLE_CHILD:
2430     hdy_stackable_box_set_visible_child (self, g_value_get_object (value));
2431     break;
2432   case PROP_VISIBLE_CHILD_NAME:
2433     hdy_stackable_box_set_visible_child_name (self, g_value_get_string (value));
2434     break;
2435   case PROP_TRANSITION_TYPE:
2436     hdy_stackable_box_set_transition_type (self, g_value_get_enum (value));
2437     break;
2438   case PROP_MODE_TRANSITION_DURATION:
2439     hdy_stackable_box_set_mode_transition_duration (self, g_value_get_uint (value));
2440     break;
2441   case PROP_CHILD_TRANSITION_DURATION:
2442     hdy_stackable_box_set_child_transition_duration (self, g_value_get_uint (value));
2443     break;
2444   case PROP_INTERPOLATE_SIZE:
2445     hdy_stackable_box_set_interpolate_size (self, g_value_get_boolean (value));
2446     break;
2447   case PROP_CAN_SWIPE_BACK:
2448     hdy_stackable_box_set_can_swipe_back (self, g_value_get_boolean (value));
2449     break;
2450   case PROP_CAN_SWIPE_FORWARD:
2451     hdy_stackable_box_set_can_swipe_forward (self, g_value_get_boolean (value));
2452     break;
2453   case PROP_ORIENTATION:
2454     hdy_stackable_box_set_orientation (self, g_value_get_enum (value));
2455     break;
2456   default:
2457     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
2458   }
2459 }
2460 
2461 static void
hdy_stackable_box_finalize(GObject * object)2462 hdy_stackable_box_finalize (GObject *object)
2463 {
2464   HdyStackableBox *self = HDY_STACKABLE_BOX (object);
2465 
2466   self->visible_child = NULL;
2467 
2468   if (self->shadow_helper)
2469     g_clear_object (&self->shadow_helper);
2470 
2471   hdy_stackable_box_unschedule_child_ticks (self);
2472 
2473   G_OBJECT_CLASS (hdy_stackable_box_parent_class)->finalize (object);
2474 }
2475 
2476 void
hdy_stackable_box_realize(HdyStackableBox * self)2477 hdy_stackable_box_realize (HdyStackableBox *self)
2478 {
2479   GtkWidget *widget = GTK_WIDGET (self->container);
2480   GtkAllocation allocation;
2481   GdkWindowAttr attributes = { 0 };
2482   GdkWindowAttributesType attributes_mask;
2483   GList *children;
2484   GdkWindow *window;
2485 
2486   gtk_widget_set_realized (widget, TRUE);
2487 
2488   gtk_widget_get_allocation (widget, &allocation);
2489 
2490   attributes.x = allocation.x;
2491   attributes.y = allocation.y;
2492   attributes.width = allocation.width;
2493   attributes.height = allocation.height;
2494   attributes.window_type = GDK_WINDOW_CHILD;
2495   attributes.wclass = GDK_INPUT_OUTPUT;
2496   attributes.visual = gtk_widget_get_visual (widget);
2497   attributes.event_mask = gtk_widget_get_events (widget);
2498   attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
2499 
2500   window = gdk_window_new (gtk_widget_get_parent_window (widget),
2501                            &attributes, attributes_mask);
2502   gtk_widget_set_window (widget, window);
2503   gtk_widget_register_window (widget, window);
2504 
2505   for (children = self->children; children != NULL; children = children->next)
2506     register_window (self, children->data);
2507 }
2508 
2509 void
hdy_stackable_box_unrealize(HdyStackableBox * self)2510 hdy_stackable_box_unrealize (HdyStackableBox *self)
2511 {
2512   GtkWidget *widget = GTK_WIDGET (self->container);
2513   GList *children;
2514 
2515   for (children = self->children; children != NULL; children = children->next)
2516     unregister_window (self, children->data);
2517 
2518   GTK_WIDGET_CLASS (self->klass)->unrealize (widget);
2519 }
2520 
2521 HdySwipeTracker *
hdy_stackable_box_get_swipe_tracker(HdyStackableBox * self)2522 hdy_stackable_box_get_swipe_tracker (HdyStackableBox *self)
2523 {
2524   return self->tracker;
2525 }
2526 
2527 gdouble
hdy_stackable_box_get_distance(HdyStackableBox * self)2528 hdy_stackable_box_get_distance (HdyStackableBox *self)
2529 {
2530   if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
2531     return gtk_widget_get_allocated_width (GTK_WIDGET (self->container));
2532   else
2533     return gtk_widget_get_allocated_height (GTK_WIDGET (self->container));
2534 }
2535 
2536 static gboolean
can_swipe_in_direction(HdyStackableBox * self,HdyNavigationDirection direction)2537 can_swipe_in_direction (HdyStackableBox        *self,
2538                         HdyNavigationDirection  direction)
2539 {
2540   switch (direction) {
2541   case HDY_NAVIGATION_DIRECTION_BACK:
2542     return self->child_transition.can_swipe_back;
2543   case HDY_NAVIGATION_DIRECTION_FORWARD:
2544     return self->child_transition.can_swipe_forward;
2545   default:
2546     g_assert_not_reached ();
2547   }
2548 }
2549 
2550 gdouble *
hdy_stackable_box_get_snap_points(HdyStackableBox * self,gint * n_snap_points)2551 hdy_stackable_box_get_snap_points (HdyStackableBox *self,
2552                                    gint            *n_snap_points)
2553 {
2554   gint n;
2555   gdouble *points, lower, upper;
2556 
2557   if (self->child_transition.tick_id > 0 ||
2558       self->child_transition.is_gesture_active) {
2559     gint current_direction;
2560     gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL;
2561 
2562     switch (self->child_transition.active_direction) {
2563     case GTK_PAN_DIRECTION_UP:
2564       current_direction = 1;
2565       break;
2566     case GTK_PAN_DIRECTION_DOWN:
2567       current_direction = -1;
2568       break;
2569     case GTK_PAN_DIRECTION_LEFT:
2570       current_direction = is_rtl ? -1 : 1;
2571       break;
2572     case GTK_PAN_DIRECTION_RIGHT:
2573       current_direction = is_rtl ? 1 : -1;
2574       break;
2575     default:
2576       g_assert_not_reached ();
2577     }
2578 
2579     lower = MIN (0, current_direction);
2580     upper = MAX (0, current_direction);
2581   } else {
2582     HdyStackableBoxChildInfo *child = NULL;
2583 
2584     if ((can_swipe_in_direction (self, self->child_transition.swipe_direction) ||
2585          !self->child_transition.is_direct_swipe) && self->folded)
2586       child = find_swipeable_child (self, self->child_transition.swipe_direction);
2587 
2588     lower = MIN (0, child ? self->child_transition.swipe_direction : 0);
2589     upper = MAX (0, child ? self->child_transition.swipe_direction : 0);
2590   }
2591 
2592   n = (lower != upper) ? 2 : 1;
2593 
2594   points = g_new0 (gdouble, n);
2595   points[0] = lower;
2596   points[n - 1] = upper;
2597 
2598   if (n_snap_points)
2599     *n_snap_points = n;
2600 
2601   return points;
2602 }
2603 
2604 gdouble
hdy_stackable_box_get_progress(HdyStackableBox * self)2605 hdy_stackable_box_get_progress (HdyStackableBox *self)
2606 {
2607   gboolean new_first = FALSE;
2608   GList *children;
2609 
2610   if (!self->child_transition.is_gesture_active &&
2611       gtk_progress_tracker_get_state (&self->child_transition.tracker) == GTK_PROGRESS_STATE_AFTER)
2612     return 0;
2613 
2614   for (children = self->children; children; children = children->next) {
2615     if (self->last_visible_child == children->data) {
2616       new_first = TRUE;
2617 
2618       break;
2619     }
2620     if (self->visible_child == children->data)
2621       break;
2622   }
2623 
2624   return self->child_transition.progress * (new_first ? 1 : -1);
2625 }
2626 
2627 gdouble
hdy_stackable_box_get_cancel_progress(HdyStackableBox * self)2628 hdy_stackable_box_get_cancel_progress (HdyStackableBox *self)
2629 {
2630   return 0;
2631 }
2632 
2633 void
hdy_stackable_box_get_swipe_area(HdyStackableBox * self,HdyNavigationDirection navigation_direction,gboolean is_drag,GdkRectangle * rect)2634 hdy_stackable_box_get_swipe_area (HdyStackableBox        *self,
2635                                   HdyNavigationDirection  navigation_direction,
2636                                   gboolean                is_drag,
2637                                   GdkRectangle           *rect)
2638 {
2639   gint width = gtk_widget_get_allocated_width (GTK_WIDGET (self->container));
2640   gint height = gtk_widget_get_allocated_height (GTK_WIDGET (self->container));
2641   gdouble progress = 0;
2642 
2643   rect->x = 0;
2644   rect->y = 0;
2645   rect->width = width;
2646   rect->height = height;
2647 
2648   if (!is_drag)
2649     return;
2650 
2651   if (self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE)
2652     return;
2653 
2654   if (self->child_transition.is_gesture_active ||
2655       gtk_progress_tracker_get_state (&self->child_transition.tracker) != GTK_PROGRESS_STATE_AFTER)
2656     progress = self->child_transition.progress;
2657 
2658   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
2659     gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self->container)) == GTK_TEXT_DIR_RTL;
2660 
2661     if (self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER &&
2662          navigation_direction == HDY_NAVIGATION_DIRECTION_FORWARD) {
2663       rect->width = MAX (progress * width, HDY_SWIPE_BORDER);
2664       rect->x = is_rtl ? 0 : width - rect->width;
2665     } else if (self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER &&
2666                navigation_direction == HDY_NAVIGATION_DIRECTION_BACK) {
2667       rect->width = MAX (progress * width, HDY_SWIPE_BORDER);
2668       rect->x = is_rtl ? width - rect->width : 0;
2669     }
2670   } else {
2671     if (self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER &&
2672         navigation_direction == HDY_NAVIGATION_DIRECTION_FORWARD) {
2673       rect->height = MAX (progress * height, HDY_SWIPE_BORDER);
2674       rect->y = height - rect->height;
2675     } else if (self->transition_type == HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER &&
2676                navigation_direction == HDY_NAVIGATION_DIRECTION_BACK) {
2677       rect->height = MAX (progress * height, HDY_SWIPE_BORDER);
2678       rect->y = 0;
2679     }
2680   }
2681 }
2682 
2683 void
hdy_stackable_box_switch_child(HdyStackableBox * self,guint index,gint64 duration)2684 hdy_stackable_box_switch_child (HdyStackableBox *self,
2685                                 guint            index,
2686                                 gint64           duration)
2687 {
2688   HdyStackableBoxChildInfo *child_info = NULL;
2689   GList *children;
2690   guint i = 0;
2691 
2692   for (children = self->children; children; children = children->next) {
2693     child_info = children->data;
2694 
2695     if (!child_info->navigatable)
2696       continue;
2697 
2698     if (i == index)
2699       break;
2700 
2701     i++;
2702   }
2703 
2704   if (child_info == NULL) {
2705     g_critical ("Couldn't find eligible child with index %u", index);
2706     return;
2707   }
2708 
2709   set_visible_child_info (self, child_info, self->transition_type,
2710                           duration, FALSE);
2711 }
2712 
2713 static void
begin_swipe_cb(HdySwipeTracker * tracker,HdyNavigationDirection direction,gboolean direct,HdyStackableBox * self)2714 begin_swipe_cb (HdySwipeTracker        *tracker,
2715                 HdyNavigationDirection  direction,
2716                 gboolean                direct,
2717                 HdyStackableBox        *self)
2718 {
2719   self->child_transition.is_direct_swipe = direct;
2720   self->child_transition.swipe_direction = direction;
2721 
2722   if (self->child_transition.tick_id > 0) {
2723     gtk_widget_remove_tick_callback (GTK_WIDGET (self->container),
2724                                      self->child_transition.tick_id);
2725     self->child_transition.tick_id = 0;
2726     self->child_transition.is_gesture_active = TRUE;
2727     self->child_transition.is_cancelled = FALSE;
2728   } else {
2729     HdyStackableBoxChildInfo *child;
2730 
2731     if ((can_swipe_in_direction (self, direction) || !direct) && self->folded)
2732       child = find_swipeable_child (self, direction);
2733     else
2734       child = NULL;
2735 
2736     if (child) {
2737       self->child_transition.is_gesture_active = TRUE;
2738       set_visible_child_info (self, child, self->transition_type,
2739                               self->child_transition.duration, FALSE);
2740 
2741       g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CHILD_TRANSITION_RUNNING]);
2742     }
2743   }
2744 }
2745 
2746 static void
update_swipe_cb(HdySwipeTracker * tracker,gdouble progress,HdyStackableBox * self)2747 update_swipe_cb (HdySwipeTracker *tracker,
2748                  gdouble          progress,
2749                  HdyStackableBox *self)
2750 {
2751   self->child_transition.progress = ABS (progress);
2752   hdy_stackable_box_child_progress_updated (self);
2753 }
2754 
2755 static void
end_swipe_cb(HdySwipeTracker * tracker,gint64 duration,gdouble to,HdyStackableBox * self)2756 end_swipe_cb (HdySwipeTracker *tracker,
2757               gint64           duration,
2758               gdouble          to,
2759               HdyStackableBox *self)
2760 {
2761  if (!self->child_transition.is_gesture_active)
2762     return;
2763 
2764   self->child_transition.start_progress = self->child_transition.progress;
2765   self->child_transition.end_progress = ABS (to);
2766   self->child_transition.is_cancelled = (to == 0);
2767   self->child_transition.first_frame_skipped = TRUE;
2768 
2769   hdy_stackable_box_schedule_child_ticks (self);
2770   if (hdy_get_enable_animations (GTK_WIDGET (self->container)) && duration != 0) {
2771     gtk_progress_tracker_start (&self->child_transition.tracker,
2772                                 duration * 1000,
2773                                 0,
2774                                 1.0);
2775   } else {
2776     self->child_transition.progress = self->child_transition.end_progress;
2777     gtk_progress_tracker_finish (&self->child_transition.tracker);
2778   }
2779 
2780   self->child_transition.is_gesture_active = FALSE;
2781   hdy_stackable_box_child_progress_updated (self);
2782 
2783   gtk_widget_queue_draw (GTK_WIDGET (self->container));
2784 }
2785 
2786 GtkOrientation
hdy_stackable_box_get_orientation(HdyStackableBox * self)2787 hdy_stackable_box_get_orientation (HdyStackableBox *self)
2788 {
2789   return self->orientation;
2790 }
2791 
2792 void
hdy_stackable_box_set_orientation(HdyStackableBox * self,GtkOrientation orientation)2793 hdy_stackable_box_set_orientation (HdyStackableBox *self,
2794                                    GtkOrientation   orientation)
2795 {
2796   if (self->orientation == orientation)
2797     return;
2798 
2799   self->orientation = orientation;
2800   update_tracker_orientation (self);
2801   gtk_widget_queue_resize (GTK_WIDGET (self->container));
2802   g_object_notify (G_OBJECT (self), "orientation");
2803 }
2804 
2805 const gchar *
hdy_stackable_box_get_child_name(HdyStackableBox * self,GtkWidget * widget)2806 hdy_stackable_box_get_child_name (HdyStackableBox *self,
2807                                   GtkWidget       *widget)
2808 {
2809   HdyStackableBoxChildInfo *child_info;
2810 
2811   child_info = find_child_info_for_widget (self, widget);
2812 
2813   g_return_val_if_fail (child_info != NULL, NULL);
2814 
2815   return child_info->name;
2816 }
2817 
2818 void
hdy_stackable_box_set_child_name(HdyStackableBox * self,GtkWidget * widget,const gchar * name)2819 hdy_stackable_box_set_child_name (HdyStackableBox *self,
2820                                   GtkWidget       *widget,
2821                                   const gchar     *name)
2822 {
2823   HdyStackableBoxChildInfo *child_info;
2824   HdyStackableBoxChildInfo *child_info2;
2825   GList *children;
2826 
2827   child_info = find_child_info_for_widget (self, widget);
2828 
2829   g_return_if_fail (child_info != NULL);
2830 
2831   for (children = self->children; children; children = children->next) {
2832     child_info2 = children->data;
2833 
2834     if (child_info == child_info2)
2835       continue;
2836     if (g_strcmp0 (child_info2->name, name) == 0) {
2837       g_warning ("Duplicate child name in HdyStackableBox: %s", name);
2838 
2839       break;
2840     }
2841   }
2842 
2843   g_free (child_info->name);
2844   child_info->name = g_strdup (name);
2845 
2846   if (self->visible_child == child_info)
2847     g_object_notify_by_pspec (G_OBJECT (self),
2848                               props[PROP_VISIBLE_CHILD_NAME]);
2849 }
2850 
2851 gboolean
hdy_stackable_box_get_child_navigatable(HdyStackableBox * self,GtkWidget * widget)2852 hdy_stackable_box_get_child_navigatable (HdyStackableBox *self,
2853                                          GtkWidget       *widget)
2854 {
2855   HdyStackableBoxChildInfo *child_info;
2856 
2857   child_info = find_child_info_for_widget (self, widget);
2858 
2859   g_return_val_if_fail (child_info != NULL, FALSE);
2860 
2861   return child_info->navigatable;
2862 }
2863 
2864 void
hdy_stackable_box_set_child_navigatable(HdyStackableBox * self,GtkWidget * widget,gboolean navigatable)2865 hdy_stackable_box_set_child_navigatable (HdyStackableBox *self,
2866                                          GtkWidget       *widget,
2867                                          gboolean         navigatable)
2868 {
2869   HdyStackableBoxChildInfo *child_info;
2870 
2871   child_info = find_child_info_for_widget (self, widget);
2872 
2873   g_return_if_fail (child_info != NULL);
2874 
2875   child_info->navigatable = navigatable;
2876 
2877   if (!child_info->navigatable &&
2878       hdy_stackable_box_get_visible_child (self) == widget)
2879     set_visible_child_info (self, NULL, self->transition_type, self->child_transition.duration, TRUE);
2880 }
2881 
2882 void
hdy_stackable_box_prepend(HdyStackableBox * self,GtkWidget * child)2883 hdy_stackable_box_prepend (HdyStackableBox *self,
2884                            GtkWidget       *child)
2885 {
2886   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
2887   g_return_if_fail (GTK_IS_WIDGET (child));
2888   g_return_if_fail (gtk_widget_get_parent (child) == NULL);
2889 
2890   hdy_stackable_box_insert_child_after (self, child, NULL);
2891 }
2892 
2893 void
hdy_stackable_box_insert_child_after(HdyStackableBox * self,GtkWidget * child,GtkWidget * sibling)2894 hdy_stackable_box_insert_child_after (HdyStackableBox *self,
2895                                       GtkWidget       *child,
2896                                       GtkWidget       *sibling)
2897 {
2898   HdyStackableBoxChildInfo *child_info;
2899   gint visible_child_pos_before_insert = -1;
2900   gint visible_child_pos_after_insert = -1;
2901 
2902   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
2903   g_return_if_fail (GTK_IS_WIDGET (child));
2904   g_return_if_fail (sibling == NULL || GTK_IS_WIDGET (sibling));
2905 
2906   g_return_if_fail (gtk_widget_get_parent (child) == NULL);
2907   g_return_if_fail (sibling == NULL || gtk_widget_get_parent (sibling) == GTK_WIDGET (self->container));
2908 
2909   child_info = g_new0 (HdyStackableBoxChildInfo, 1);
2910   child_info->widget = child;
2911   child_info->navigatable = TRUE;
2912 
2913   if (self->visible_child)
2914     visible_child_pos_before_insert = g_list_index (self->children, self->visible_child);
2915 
2916   if (!sibling) {
2917     self->children = g_list_prepend (self->children, child_info);
2918     self->children_reversed = g_list_append (self->children_reversed, child_info);
2919   } else {
2920     HdyStackableBoxChildInfo *sibling_info = find_child_info_for_widget (self, sibling);
2921     gint sibling_info_pos = g_list_index (self->children, sibling_info);
2922 
2923     self->children =
2924       g_list_insert (self->children, child_info,
2925                      sibling_info_pos + 1);
2926     self->children_reversed =
2927       g_list_insert (self->children_reversed, child_info,
2928                      g_list_length (self->children) - sibling_info_pos - 1);
2929   }
2930 
2931   if (self->visible_child)
2932     visible_child_pos_after_insert = g_list_index (self->children, self->visible_child);
2933 
2934   if (gtk_widget_get_realized (GTK_WIDGET (self->container)))
2935     register_window (self, child_info);
2936 
2937   gtk_widget_set_child_visible (child, FALSE);
2938   gtk_widget_set_parent (child, GTK_WIDGET (self->container));
2939 
2940   g_signal_connect (child, "notify::visible",
2941                     G_CALLBACK (hdy_stackable_box_child_visibility_notify_cb), self);
2942 
2943   if (!hdy_stackable_box_get_visible_child (self) &&
2944       gtk_widget_get_visible (child))
2945     set_visible_child_info (self,
2946                             child_info,
2947                             self->transition_type,
2948                             self->child_transition.duration,
2949                             FALSE);
2950   else if (visible_child_pos_before_insert != visible_child_pos_after_insert)
2951     hdy_swipeable_emit_child_switched (HDY_SWIPEABLE (self->container),
2952                                        visible_child_pos_after_insert,
2953                                        0);
2954 
2955   if (!self->folded ||
2956       (self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] ||
2957        self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] ||
2958        self->visible_child == child_info))
2959     gtk_widget_queue_resize (GTK_WIDGET (self->container));
2960 }
2961 
2962 void
hdy_stackable_box_reorder_child_after(HdyStackableBox * self,GtkWidget * child,GtkWidget * sibling)2963 hdy_stackable_box_reorder_child_after (HdyStackableBox *self,
2964                                        GtkWidget       *child,
2965                                        GtkWidget       *sibling)
2966 {
2967   HdyStackableBoxChildInfo *child_info;
2968   HdyStackableBoxChildInfo *sibling_info;
2969   gint sibling_info_pos;
2970   gint visible_child_pos_before_reorder;
2971   gint visible_child_pos_after_reorder;
2972 
2973   g_return_if_fail (HDY_IS_STACKABLE_BOX (self));
2974   g_return_if_fail (GTK_IS_WIDGET (child));
2975   g_return_if_fail (sibling == NULL || GTK_IS_WIDGET (sibling));
2976 
2977   g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (self->container));
2978   g_return_if_fail (sibling == NULL || gtk_widget_get_parent (sibling) == GTK_WIDGET (self->container));
2979 
2980   if (child == sibling)
2981     return;
2982 
2983   visible_child_pos_before_reorder = g_list_index (self->children, self->visible_child);
2984 
2985   /* Cancel a gesture if there's one in progress */
2986   hdy_swipe_tracker_emit_end_swipe (self->tracker, 0, 0.0);
2987 
2988   child_info = find_child_info_for_widget (self, child);
2989   self->children = g_list_remove (self->children, child_info);
2990   self->children_reversed = g_list_remove (self->children_reversed, child_info);
2991 
2992   sibling_info = find_child_info_for_widget (self, sibling);
2993   sibling_info_pos = g_list_index (self->children, sibling_info);
2994 
2995   self->children =
2996     g_list_insert (self->children, child_info,
2997                    sibling_info_pos + 1);
2998   self->children_reversed =
2999     g_list_insert (self->children_reversed, child_info,
3000                    g_list_length (self->children) - sibling_info_pos - 1);
3001 
3002   visible_child_pos_after_reorder = g_list_index (self->children, self->visible_child);
3003 
3004   if (visible_child_pos_before_reorder != visible_child_pos_after_reorder)
3005     hdy_swipeable_emit_child_switched (HDY_SWIPEABLE (self->container), visible_child_pos_after_reorder, 0);
3006 }
3007 
3008 static void
hdy_stackable_box_class_init(HdyStackableBoxClass * klass)3009 hdy_stackable_box_class_init (HdyStackableBoxClass *klass)
3010 {
3011   GObjectClass *object_class = G_OBJECT_CLASS (klass);
3012 
3013   object_class->get_property = hdy_stackable_box_get_property;
3014   object_class->set_property = hdy_stackable_box_set_property;
3015   object_class->finalize = hdy_stackable_box_finalize;
3016 
3017   /**
3018    * HdyStackableBox:folded:
3019    *
3020    * %TRUE if the widget is folded.
3021    *
3022    * The #HdyStackableBox will be folded if the size allocated to it is smaller
3023    * than the sum of the natural size of its children, it will be unfolded
3024    * otherwise.
3025    */
3026   props[PROP_FOLDED] =
3027     g_param_spec_boolean ("folded",
3028                           _("Folded"),
3029                           _("Whether the widget is folded"),
3030                           FALSE,
3031                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3032 
3033   /**
3034    * HdyStackableBox:hhomogeneous_folded:
3035    *
3036    * %TRUE if the widget allocates the same width for all children when folded.
3037    */
3038   props[PROP_HHOMOGENEOUS_FOLDED] =
3039     g_param_spec_boolean ("hhomogeneous-folded",
3040                           _("Horizontally homogeneous folded"),
3041                           _("Horizontally homogeneous sizing when the widget is folded"),
3042                           TRUE,
3043                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3044 
3045   /**
3046    * HdyStackableBox:vhomogeneous_folded:
3047    *
3048    * %TRUE if the widget allocates the same height for all children when folded.
3049    */
3050   props[PROP_VHOMOGENEOUS_FOLDED] =
3051     g_param_spec_boolean ("vhomogeneous-folded",
3052                           _("Vertically homogeneous folded"),
3053                           _("Vertically homogeneous sizing when the widget is folded"),
3054                           TRUE,
3055                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3056 
3057   /**
3058    * HdyStackableBox:hhomogeneous_unfolded:
3059    *
3060    * %TRUE if the widget allocates the same width for all children when unfolded.
3061    */
3062   props[PROP_HHOMOGENEOUS_UNFOLDED] =
3063     g_param_spec_boolean ("hhomogeneous-unfolded",
3064                           _("Box horizontally homogeneous"),
3065                           _("Horizontally homogeneous sizing when the widget is unfolded"),
3066                           FALSE,
3067                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3068 
3069   /**
3070    * HdyStackableBox:vhomogeneous_unfolded:
3071    *
3072    * %TRUE if the widget allocates the same height for all children when unfolded.
3073    */
3074   props[PROP_VHOMOGENEOUS_UNFOLDED] =
3075     g_param_spec_boolean ("vhomogeneous-unfolded",
3076                           _("Box vertically homogeneous"),
3077                           _("Vertically homogeneous sizing when the widget is unfolded"),
3078                           FALSE,
3079                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3080 
3081   props[PROP_VISIBLE_CHILD] =
3082     g_param_spec_object ("visible-child",
3083                          _("Visible child"),
3084                          _("The widget currently visible when the widget is folded"),
3085                          GTK_TYPE_WIDGET,
3086                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3087 
3088   props[PROP_VISIBLE_CHILD_NAME] =
3089     g_param_spec_string ("visible-child-name",
3090                          _("Name of visible child"),
3091                          _("The name of the widget currently visible when the children are stacked"),
3092                          NULL,
3093                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3094 
3095   /**
3096    * HdyStackableBox:transition-type:
3097    *
3098    * The type of animation that will be used for transitions between modes and
3099    * children.
3100    *
3101    * The transition type can be changed without problems at runtime, so it is
3102    * possible to change the animation based on the mode or child that is about
3103    * to become current.
3104    *
3105    * Since: 1.0
3106    */
3107   props[PROP_TRANSITION_TYPE] =
3108     g_param_spec_enum ("transition-type",
3109                        _("Transition type"),
3110                        _("The type of animation used to transition between modes and children"),
3111                        HDY_TYPE_STACKABLE_BOX_TRANSITION_TYPE, HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER,
3112                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3113 
3114   props[PROP_MODE_TRANSITION_DURATION] =
3115     g_param_spec_uint ("mode-transition-duration",
3116                        _("Mode transition duration"),
3117                        _("The mode transition animation duration, in milliseconds"),
3118                        0, G_MAXUINT, 250,
3119                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3120 
3121   props[PROP_CHILD_TRANSITION_DURATION] =
3122     g_param_spec_uint ("child-transition-duration",
3123                        _("Child transition duration"),
3124                        _("The child transition animation duration, in milliseconds"),
3125                        0, G_MAXUINT, 200,
3126                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3127 
3128   props[PROP_CHILD_TRANSITION_RUNNING] =
3129       g_param_spec_boolean ("child-transition-running",
3130                             _("Child transition running"),
3131                             _("Whether or not the child transition is currently running"),
3132                             FALSE,
3133                             G_PARAM_READABLE);
3134 
3135   props[PROP_INTERPOLATE_SIZE] =
3136       g_param_spec_boolean ("interpolate-size",
3137                             _("Interpolate size"),
3138                             _("Whether or not the size should smoothly change when changing between differently sized children"),
3139                             FALSE,
3140                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3141 
3142   /**
3143    * HdyStackableBox:can-swipe-back:
3144    *
3145    * Whether or not the widget allows switching to the previous child that has
3146    * 'navigatable' child property set to %TRUE via a swipe gesture.
3147    *
3148    * Since: 1.0
3149    */
3150   props[PROP_CAN_SWIPE_BACK] =
3151       g_param_spec_boolean ("can-swipe-back",
3152                             _("Can swipe back"),
3153                             _("Whether or not swipe gesture can be used to switch to the previous child"),
3154                             FALSE,
3155                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3156 
3157   /**
3158    * HdyStackableBox:can-swipe-forward:
3159    *
3160    * Whether or not the widget allows switching to the next child that has
3161    * 'navigatable' child property set to %TRUE via a swipe gesture.
3162    *
3163    * Since: 1.0
3164    */
3165   props[PROP_CAN_SWIPE_FORWARD] =
3166       g_param_spec_boolean ("can-swipe-forward",
3167                             _("Can swipe forward"),
3168                             _("Whether or not swipe gesture can be used to switch to the next child"),
3169                             FALSE,
3170                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3171 
3172   props[PROP_ORIENTATION] =
3173       g_param_spec_enum ("orientation",
3174                          _("Orientation"),
3175                          _("Orientation"),
3176                          GTK_TYPE_ORIENTATION,
3177                          GTK_ORIENTATION_HORIZONTAL,
3178                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3179 
3180   g_object_class_install_properties (object_class, LAST_PROP, props);
3181 }
3182 
3183 HdyStackableBox *
hdy_stackable_box_new(GtkContainer * container,GtkContainerClass * klass,gboolean can_unfold)3184 hdy_stackable_box_new (GtkContainer      *container,
3185                        GtkContainerClass *klass,
3186                        gboolean           can_unfold)
3187 {
3188   GtkWidget *widget;
3189   HdyStackableBox *self;
3190 
3191   g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
3192   g_return_val_if_fail (GTK_IS_ORIENTABLE (container), NULL);
3193   g_return_val_if_fail (GTK_IS_CONTAINER_CLASS (klass), NULL);
3194 
3195   widget = GTK_WIDGET (container);
3196   self = g_object_new (HDY_TYPE_STACKABLE_BOX, NULL);
3197 
3198   self->container = container;
3199   self->klass = klass;
3200   self->can_unfold = can_unfold;
3201 
3202   self->children = NULL;
3203   self->children_reversed = NULL;
3204   self->visible_child = NULL;
3205   self->folded = FALSE;
3206   self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_HORIZONTAL] = FALSE;
3207   self->homogeneous[HDY_FOLD_UNFOLDED][GTK_ORIENTATION_VERTICAL] = FALSE;
3208   self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_HORIZONTAL] = TRUE;
3209   self->homogeneous[HDY_FOLD_FOLDED][GTK_ORIENTATION_VERTICAL] = TRUE;
3210   self->transition_type = HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER;
3211   self->mode_transition.duration = 250;
3212   self->child_transition.duration = 200;
3213   self->mode_transition.current_pos = 1.0;
3214   self->mode_transition.target_pos = 1.0;
3215 
3216   self->tracker = hdy_swipe_tracker_new (HDY_SWIPEABLE (self->container));
3217 
3218   g_object_set (self->tracker, "orientation", self->orientation, "enabled", FALSE, NULL);
3219 
3220   g_signal_connect_object (self->tracker, "begin-swipe", G_CALLBACK (begin_swipe_cb), self, 0);
3221   g_signal_connect_object (self->tracker, "update-swipe", G_CALLBACK (update_swipe_cb), self, 0);
3222   g_signal_connect_object (self->tracker, "end-swipe", G_CALLBACK (end_swipe_cb), self, 0);
3223 
3224   self->shadow_helper = hdy_shadow_helper_new (widget);
3225 
3226   gtk_widget_set_can_focus (widget, FALSE);
3227   gtk_widget_set_redraw_on_allocate (widget, FALSE);
3228 
3229   if (can_unfold) {
3230     GtkStyleContext *context = gtk_widget_get_style_context (widget);
3231     gtk_style_context_add_class (context, "unfolded");
3232   }
3233 
3234   return self;
3235 }
3236 
3237 static void
hdy_stackable_box_init(HdyStackableBox * self)3238 hdy_stackable_box_init (HdyStackableBox *self)
3239 {
3240 }
3241