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