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 "hdy-deck.h"
12 #include "hdy-stackable-box-private.h"
13 #include "hdy-swipeable.h"
14 
15 /**
16  * SECTION:hdy-deck
17  * @short_description: A swipeable widget showing one of the visible children at a time.
18  * @Title: HdyDeck
19  *
20  * The #HdyDeck widget displays one of the visible children, similar to a
21  * #GtkStack. The children are strictly ordered and can be navigated using
22  * swipe gestures.
23  *
24  * The “over” and “under” stack the children one on top of the other, while the
25  * “slide” transition puts the children side by side. While navigating to a
26  * child on the side or below can be performed by swiping the current child
27  * away, navigating to an upper child requires dragging it from the edge where
28  * it resides. This doesn't affect non-dragging swipes.
29  *
30  * The “over” and “under” transitions can draw their shadow on top of the
31  * window's transparent areas, like the rounded corners. This is a side-effect
32  * of allowing shadows to be drawn on top of OpenGL areas. It can be mitigated
33  * by using #HdyWindow or #HdyApplicationWindow as they will crop anything drawn
34  * beyond the rounded corners.
35  *
36  * # CSS nodes
37  *
38  * #HdyDeck has a single CSS node with name deck.
39  *
40  * Since: 1.0
41  */
42 
43 /**
44  * HdyDeckTransitionType:
45  * @HDY_DECK_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
46  * @HDY_DECK_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
47  * @HDY_DECK_TRANSITION_TYPE_SLIDE: Slide from left, right, up or down according to the orientation, text direction and the children order
48  *
49  * This enumeration value describes the possible transitions between children
50  * in a #HdyDeck widget.
51  *
52  * New values may be added to this enumeration over time.
53  *
54  * Since: 1.0
55  */
56 
57 enum {
58   PROP_0,
59   PROP_HHOMOGENEOUS,
60   PROP_VHOMOGENEOUS,
61   PROP_VISIBLE_CHILD,
62   PROP_VISIBLE_CHILD_NAME,
63   PROP_TRANSITION_TYPE,
64   PROP_TRANSITION_DURATION,
65   PROP_TRANSITION_RUNNING,
66   PROP_INTERPOLATE_SIZE,
67   PROP_CAN_SWIPE_BACK,
68   PROP_CAN_SWIPE_FORWARD,
69 
70   /* orientable */
71   PROP_ORIENTATION,
72   LAST_PROP = PROP_ORIENTATION,
73 };
74 
75 enum {
76   CHILD_PROP_0,
77   CHILD_PROP_NAME,
78   LAST_CHILD_PROP,
79 };
80 
81 typedef struct
82 {
83   HdyStackableBox *box;
84 } HdyDeckPrivate;
85 
86 static GParamSpec *props[LAST_PROP];
87 static GParamSpec *child_props[LAST_CHILD_PROP];
88 
89 static void hdy_deck_swipeable_init (HdySwipeableInterface *iface);
90 
G_DEFINE_TYPE_WITH_CODE(HdyDeck,hdy_deck,GTK_TYPE_CONTAINER,G_ADD_PRIVATE (HdyDeck)G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,NULL)G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE,hdy_deck_swipeable_init))91 G_DEFINE_TYPE_WITH_CODE (HdyDeck, hdy_deck, GTK_TYPE_CONTAINER,
92                          G_ADD_PRIVATE (HdyDeck)
93                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
94                          G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE, hdy_deck_swipeable_init))
95 
96 #define HDY_GET_HELPER(obj) (((HdyDeckPrivate *) hdy_deck_get_instance_private (HDY_DECK (obj)))->box)
97 
98 /**
99  * hdy_deck_set_homogeneous:
100  * @self: a #HdyDeck
101  * @orientation: the orientation
102  * @homogeneous: %TRUE to make @self homogeneous
103  *
104  * Sets the #HdyDeck to be homogeneous or not for the given orientation.
105  * If it is homogeneous, the #HdyDeck will request the same
106  * width or height for all its children depending on the orientation.
107  * If it isn't, the deck may change width or height when a different child
108  * becomes visible.
109  *
110  * Since: 1.0
111  */
112 void
113 hdy_deck_set_homogeneous (HdyDeck        *self,
114                           GtkOrientation  orientation,
115                           gboolean        homogeneous)
116 {
117   g_return_if_fail (HDY_IS_DECK (self));
118 
119   hdy_stackable_box_set_homogeneous (HDY_GET_HELPER (self), TRUE, orientation, homogeneous);
120 }
121 
122 /**
123  * hdy_deck_get_homogeneous:
124  * @self: a #HdyDeck
125  * @orientation: the orientation
126  *
127  * Gets whether @self is homogeneous for the given orientation.
128  * See hdy_deck_set_homogeneous().
129  *
130  * Returns: whether @self is homogeneous for the given orientation.
131  *
132  * Since: 1.0
133  */
134 gboolean
hdy_deck_get_homogeneous(HdyDeck * self,GtkOrientation orientation)135 hdy_deck_get_homogeneous (HdyDeck        *self,
136                           GtkOrientation  orientation)
137 {
138   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
139 
140   return hdy_stackable_box_get_homogeneous (HDY_GET_HELPER (self), TRUE, orientation);
141 }
142 
143 /**
144  * hdy_deck_get_transition_type:
145  * @self: a #HdyDeck
146  *
147  * Gets the type of animation that will be used
148  * for transitions between children in @self.
149  *
150  * Returns: the current transition type of @self
151  *
152  * Since: 1.0
153  */
154 HdyDeckTransitionType
hdy_deck_get_transition_type(HdyDeck * self)155 hdy_deck_get_transition_type (HdyDeck *self)
156 {
157   HdyStackableBoxTransitionType type;
158 
159   g_return_val_if_fail (HDY_IS_DECK (self), HDY_DECK_TRANSITION_TYPE_OVER);
160 
161   type = hdy_stackable_box_get_transition_type (HDY_GET_HELPER (self));
162 
163   switch (type) {
164   case HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER:
165     return HDY_DECK_TRANSITION_TYPE_OVER;
166 
167   case HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER:
168     return HDY_DECK_TRANSITION_TYPE_UNDER;
169 
170   case HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE:
171     return HDY_DECK_TRANSITION_TYPE_SLIDE;
172 
173   default:
174     g_assert_not_reached ();
175   }
176 }
177 
178 /**
179  * hdy_deck_set_transition_type:
180  * @self: a #HdyDeck
181  * @transition: the new transition type
182  *
183  * Sets the type of animation that will be used for transitions between children
184  * in @self.
185  *
186  * The transition type can be changed without problems at runtime, so it is
187  * possible to change the animation based on the child that is about to become
188  * current.
189  *
190  * Since: 1.0
191  */
192 void
hdy_deck_set_transition_type(HdyDeck * self,HdyDeckTransitionType transition)193 hdy_deck_set_transition_type (HdyDeck               *self,
194                               HdyDeckTransitionType  transition)
195 {
196   HdyStackableBoxTransitionType type;
197 
198   g_return_if_fail (HDY_IS_DECK (self));
199   g_return_if_fail (transition <= HDY_DECK_TRANSITION_TYPE_SLIDE);
200 
201   switch (transition) {
202   case HDY_DECK_TRANSITION_TYPE_OVER:
203     type = HDY_STACKABLE_BOX_TRANSITION_TYPE_OVER;
204     break;
205 
206   case HDY_DECK_TRANSITION_TYPE_UNDER:
207     type = HDY_STACKABLE_BOX_TRANSITION_TYPE_UNDER;
208     break;
209 
210   case HDY_DECK_TRANSITION_TYPE_SLIDE:
211     type = HDY_STACKABLE_BOX_TRANSITION_TYPE_SLIDE;
212     break;
213 
214   default:
215     g_assert_not_reached ();
216   }
217 
218   hdy_stackable_box_set_transition_type (HDY_GET_HELPER (self), type);
219 }
220 
221 /**
222  * hdy_deck_get_transition_duration:
223  * @self: a #HdyDeck
224  *
225  * Returns the amount of time (in milliseconds) that
226  * transitions between children in @self will take.
227  *
228  * Returns: the child transition duration
229  *
230  * Since: 1.0
231  */
232 guint
hdy_deck_get_transition_duration(HdyDeck * self)233 hdy_deck_get_transition_duration (HdyDeck *self)
234 {
235   g_return_val_if_fail (HDY_IS_DECK (self), 0);
236 
237   return hdy_stackable_box_get_child_transition_duration (HDY_GET_HELPER (self));
238 }
239 
240 /**
241  * hdy_deck_set_transition_duration:
242  * @self: a #HdyDeck
243  * @duration: the new duration, in milliseconds
244  *
245  * Sets the duration that transitions between children in @self
246  * will take.
247  *
248  * Since: 1.0
249  */
250 void
hdy_deck_set_transition_duration(HdyDeck * self,guint duration)251 hdy_deck_set_transition_duration (HdyDeck *self,
252                                   guint    duration)
253 {
254   g_return_if_fail (HDY_IS_DECK (self));
255 
256   hdy_stackable_box_set_child_transition_duration (HDY_GET_HELPER (self), duration);
257 }
258 
259 /**
260  * hdy_deck_get_visible_child:
261  * @self: a #HdyDeck
262  *
263  * Gets the visible child widget.
264  *
265  * Returns: (transfer none): the visible child widget
266  *
267  * Since: 1.0
268  */
269 GtkWidget *
hdy_deck_get_visible_child(HdyDeck * self)270 hdy_deck_get_visible_child (HdyDeck *self)
271 {
272   g_return_val_if_fail (HDY_IS_DECK (self), NULL);
273 
274   return hdy_stackable_box_get_visible_child (HDY_GET_HELPER (self));
275 }
276 
277 /**
278  * hdy_deck_set_visible_child:
279  * @self: a #HdyDeck
280  * @visible_child: the new child
281  *
282  * Makes @visible_child visible using a transition determined by
283  * HdyDeck:transition-type and HdyDeck:transition-duration. The transition can
284  * be cancelled by the user, in which case visible child will change back to
285  * the previously visible child.
286  *
287  * Since: 1.0
288  */
289 void
hdy_deck_set_visible_child(HdyDeck * self,GtkWidget * visible_child)290 hdy_deck_set_visible_child (HdyDeck   *self,
291                             GtkWidget *visible_child)
292 {
293   g_return_if_fail (HDY_IS_DECK (self));
294 
295   hdy_stackable_box_set_visible_child (HDY_GET_HELPER (self), visible_child);
296 }
297 
298 /**
299  * hdy_deck_get_visible_child_name:
300  * @self: a #HdyDeck
301  *
302  * Gets the name of the currently visible child widget.
303  *
304  * Returns: (transfer none): the name of the visible child
305  *
306  * Since: 1.0
307  */
308 const gchar *
hdy_deck_get_visible_child_name(HdyDeck * self)309 hdy_deck_get_visible_child_name (HdyDeck *self)
310 {
311   g_return_val_if_fail (HDY_IS_DECK (self), NULL);
312 
313   return hdy_stackable_box_get_visible_child_name (HDY_GET_HELPER (self));
314 }
315 
316 /**
317  * hdy_deck_set_visible_child_name:
318  * @self: a #HdyDeck
319  * @name: the name of a child
320  *
321  * Makes the child with the name @name visible.
322  *
323  * See hdy_deck_set_visible_child() for more details.
324  *
325  * Since: 1.0
326  */
327 void
hdy_deck_set_visible_child_name(HdyDeck * self,const gchar * name)328 hdy_deck_set_visible_child_name (HdyDeck     *self,
329                                  const gchar *name)
330 {
331   g_return_if_fail (HDY_IS_DECK (self));
332 
333   hdy_stackable_box_set_visible_child_name (HDY_GET_HELPER (self), name);
334 }
335 
336 /**
337  * hdy_deck_get_transition_running:
338  * @self: a #HdyDeck
339  *
340  * Returns whether @self is currently in a transition from one page to
341  * another.
342  *
343  * Returns: %TRUE if the transition is currently running, %FALSE otherwise.
344  *
345  * Since: 1.0
346  */
347 gboolean
hdy_deck_get_transition_running(HdyDeck * self)348 hdy_deck_get_transition_running (HdyDeck *self)
349 {
350   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
351 
352   return hdy_stackable_box_get_child_transition_running (HDY_GET_HELPER (self));
353 }
354 
355 /**
356  * hdy_deck_set_interpolate_size:
357  * @self: a #HdyDeck
358  * @interpolate_size: the new value
359  *
360  * Sets whether or not @self will interpolate its size when
361  * changing the visible child. If the #HdyDeck:interpolate-size
362  * property is set to %TRUE, @self will interpolate its size between
363  * the current one and the one it'll take after changing the
364  * visible child, according to the set transition duration.
365  *
366  * Since: 1.0
367  */
368 void
hdy_deck_set_interpolate_size(HdyDeck * self,gboolean interpolate_size)369 hdy_deck_set_interpolate_size (HdyDeck  *self,
370                                gboolean  interpolate_size)
371 {
372   g_return_if_fail (HDY_IS_DECK (self));
373 
374   hdy_stackable_box_set_interpolate_size (HDY_GET_HELPER (self), interpolate_size);
375 }
376 
377 /**
378  * hdy_deck_get_interpolate_size:
379  * @self: a #HdyDeck
380  *
381  * Returns whether the #HdyDeck is set up to interpolate between
382  * the sizes of children on page switch.
383  *
384  * Returns: %TRUE if child sizes are interpolated
385  *
386  * Since: 1.0
387  */
388 gboolean
hdy_deck_get_interpolate_size(HdyDeck * self)389 hdy_deck_get_interpolate_size (HdyDeck *self)
390 {
391   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
392 
393   return hdy_stackable_box_get_interpolate_size (HDY_GET_HELPER (self));
394 }
395 
396 /**
397  * hdy_deck_set_can_swipe_back:
398  * @self: a #HdyDeck
399  * @can_swipe_back: the new value
400  *
401  * Sets whether or not @self allows switching to the previous child via a swipe
402  * gesture.
403  *
404  * Since: 1.0
405  */
406 void
hdy_deck_set_can_swipe_back(HdyDeck * self,gboolean can_swipe_back)407 hdy_deck_set_can_swipe_back (HdyDeck  *self,
408                              gboolean  can_swipe_back)
409 {
410   g_return_if_fail (HDY_IS_DECK (self));
411 
412   hdy_stackable_box_set_can_swipe_back (HDY_GET_HELPER (self), can_swipe_back);
413 }
414 
415 /**
416  * hdy_deck_get_can_swipe_back
417  * @self: a #HdyDeck
418  *
419  * Returns whether the #HdyDeck allows swiping to the previous child.
420  *
421  * Returns: %TRUE if back swipe is enabled.
422  *
423  * Since: 1.0
424  */
425 gboolean
hdy_deck_get_can_swipe_back(HdyDeck * self)426 hdy_deck_get_can_swipe_back (HdyDeck *self)
427 {
428   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
429 
430   return hdy_stackable_box_get_can_swipe_back (HDY_GET_HELPER (self));
431 }
432 
433 /**
434  * hdy_deck_set_can_swipe_forward:
435  * @self: a #HdyDeck
436  * @can_swipe_forward: the new value
437  *
438  * Sets whether or not @self allows switching to the next child via a swipe
439  * gesture.
440  *
441  * Since: 1.0
442  */
443 void
hdy_deck_set_can_swipe_forward(HdyDeck * self,gboolean can_swipe_forward)444 hdy_deck_set_can_swipe_forward (HdyDeck  *self,
445                                 gboolean  can_swipe_forward)
446 {
447   g_return_if_fail (HDY_IS_DECK (self));
448 
449   hdy_stackable_box_set_can_swipe_forward (HDY_GET_HELPER (self), can_swipe_forward);
450 }
451 
452 /**
453  * hdy_deck_get_can_swipe_forward
454  * @self: a #HdyDeck
455  *
456  * Returns whether the #HdyDeck allows swiping to the next child.
457  *
458  * Returns: %TRUE if forward swipe is enabled.
459  *
460  * Since: 1.0
461  */
462 gboolean
hdy_deck_get_can_swipe_forward(HdyDeck * self)463 hdy_deck_get_can_swipe_forward (HdyDeck *self)
464 {
465   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
466 
467   return hdy_stackable_box_get_can_swipe_forward (HDY_GET_HELPER (self));
468 }
469 
470 /**
471  * hdy_deck_get_adjacent_child
472  * @self: a #HdyDeck
473  * @direction: the direction
474  *
475  * Gets the previous or next child, or %NULL if it doesn't exist. This will be
476  * the same widget hdy_deck_navigate() will navigate to.
477  *
478  * Returns: (nullable) (transfer none): the previous or next child, or
479  *   %NULL if it doesn't exist.
480  *
481  * Since: 1.0
482  */
483 GtkWidget *
hdy_deck_get_adjacent_child(HdyDeck * self,HdyNavigationDirection direction)484 hdy_deck_get_adjacent_child (HdyDeck                *self,
485                              HdyNavigationDirection  direction)
486 {
487   g_return_val_if_fail (HDY_IS_DECK (self), NULL);
488 
489   return hdy_stackable_box_get_adjacent_child (HDY_GET_HELPER (self), direction);
490 }
491 
492 /**
493  * hdy_deck_navigate
494  * @self: a #HdyDeck
495  * @direction: the direction
496  *
497  * Switches to the previous or next child, similar to performing a swipe
498  * gesture to go in @direction.
499  *
500  * Returns: %TRUE if visible child was changed, %FALSE otherwise.
501  *
502  * Since: 1.0
503  */
504 gboolean
hdy_deck_navigate(HdyDeck * self,HdyNavigationDirection direction)505 hdy_deck_navigate (HdyDeck                *self,
506                    HdyNavigationDirection  direction)
507 {
508   g_return_val_if_fail (HDY_IS_DECK (self), FALSE);
509 
510   return hdy_stackable_box_navigate (HDY_GET_HELPER (self), direction);
511 }
512 
513 /**
514  * hdy_deck_get_child_by_name:
515  * @self: a #HdyDeck
516  * @name: the name of the child to find
517  *
518  * Finds the child of @self with the name given as the argument. Returns %NULL
519  * if there is no child with this name.
520  *
521  * Returns: (transfer none) (nullable): the requested child of @self
522  *
523  * Since: 1.0
524  */
525 GtkWidget *
hdy_deck_get_child_by_name(HdyDeck * self,const gchar * name)526 hdy_deck_get_child_by_name (HdyDeck     *self,
527                             const gchar *name)
528 {
529   g_return_val_if_fail (HDY_IS_DECK (self), NULL);
530 
531   return hdy_stackable_box_get_child_by_name (HDY_GET_HELPER (self), name);
532 }
533 
534 /**
535  * hdy_deck_prepend:
536  * @self: a #HdyDeck
537  * @child: the #GtkWidget to prepend
538  *
539  * Inserts @child at the first position in @self.
540  *
541  * Since: 1.2
542  */
543 void
hdy_deck_prepend(HdyDeck * self,GtkWidget * child)544 hdy_deck_prepend (HdyDeck   *self,
545                   GtkWidget *child)
546 {
547   g_return_if_fail (HDY_IS_DECK (self));
548   g_return_if_fail (GTK_IS_WIDGET (child));
549   g_return_if_fail (gtk_widget_get_parent (child) == NULL);
550 
551   hdy_stackable_box_prepend (HDY_GET_HELPER (self), child);
552 }
553 
554 /**
555  * hdy_deck_insert_child_after:
556  * @self: a #HdyDeck
557  * @child: the #GtkWidget to insert
558  * @sibling: (nullable): the sibling after which to insert @child
559  *
560  * Inserts @child in the position after @sibling in the list of children.
561  * If @sibling is %NULL, insert @child at the first position.
562  *
563  * Since: 1.2
564  */
565 void
hdy_deck_insert_child_after(HdyDeck * self,GtkWidget * child,GtkWidget * sibling)566 hdy_deck_insert_child_after (HdyDeck   *self,
567                              GtkWidget *child,
568                              GtkWidget *sibling)
569 {
570   g_return_if_fail (HDY_IS_DECK (self));
571   g_return_if_fail (GTK_IS_WIDGET (child));
572   g_return_if_fail (sibling == NULL || GTK_IS_WIDGET (sibling));
573 
574   g_return_if_fail (gtk_widget_get_parent (child) == NULL);
575   g_return_if_fail (sibling == NULL || gtk_widget_get_parent (sibling) == GTK_WIDGET (self));
576 
577   hdy_stackable_box_insert_child_after (HDY_GET_HELPER (self), child, sibling);
578 }
579 
580 /**
581  * hdy_deck_reorder_child_after:
582  * @self: a #HdyDeck
583  * @child: the #GtkWidget to move, must be a child of @self
584  * @sibling: (nullable): the sibling to move @child after, or %NULL
585  *
586  * Moves @child to the position after @sibling in the list of children.
587  * If @sibling is %NULL, move @child to the first position.
588  *
589  * Since: 1.2
590  */
591 void
hdy_deck_reorder_child_after(HdyDeck * self,GtkWidget * child,GtkWidget * sibling)592 hdy_deck_reorder_child_after (HdyDeck   *self,
593                               GtkWidget *child,
594                               GtkWidget *sibling)
595 {
596   g_return_if_fail (HDY_IS_DECK (self));
597   g_return_if_fail (GTK_IS_WIDGET (child));
598   g_return_if_fail (sibling == NULL || GTK_IS_WIDGET (sibling));
599 
600   g_return_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (self));
601   g_return_if_fail (sibling == NULL || gtk_widget_get_parent (sibling) == GTK_WIDGET (self));
602 
603   if (child == sibling)
604     return;
605 
606   hdy_stackable_box_reorder_child_after (HDY_GET_HELPER (self), child, sibling);
607 }
608 
609 /* This private method is prefixed by the call name because it will be a virtual
610  * method in GTK 4.
611  */
612 static void
hdy_deck_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)613 hdy_deck_measure (GtkWidget      *widget,
614                   GtkOrientation  orientation,
615                   int             for_size,
616                   int            *minimum,
617                   int            *natural,
618                   int            *minimum_baseline,
619                   int            *natural_baseline)
620 {
621   hdy_stackable_box_measure (HDY_GET_HELPER (widget),
622                              orientation, for_size,
623                              minimum, natural,
624                              minimum_baseline, natural_baseline);
625 }
626 
627 static void
hdy_deck_get_preferred_width(GtkWidget * widget,gint * minimum_width,gint * natural_width)628 hdy_deck_get_preferred_width (GtkWidget *widget,
629                               gint      *minimum_width,
630                               gint      *natural_width)
631 {
632   hdy_deck_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
633                     minimum_width, natural_width, NULL, NULL);
634 }
635 
636 static void
hdy_deck_get_preferred_height(GtkWidget * widget,gint * minimum_height,gint * natural_height)637 hdy_deck_get_preferred_height (GtkWidget *widget,
638                                gint      *minimum_height,
639                                gint      *natural_height)
640 {
641   hdy_deck_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
642                     minimum_height, natural_height, NULL, NULL);
643 }
644 
645 static void
hdy_deck_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum_width,gint * natural_width)646 hdy_deck_get_preferred_width_for_height (GtkWidget *widget,
647                                          gint       height,
648                                          gint      *minimum_width,
649                                          gint      *natural_width)
650 {
651   hdy_deck_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
652                     minimum_width, natural_width, NULL, NULL);
653 }
654 
655 static void
hdy_deck_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum_height,gint * natural_height)656 hdy_deck_get_preferred_height_for_width (GtkWidget *widget,
657                                          gint       width,
658                                          gint      *minimum_height,
659                                          gint      *natural_height)
660 {
661   hdy_deck_measure (widget, GTK_ORIENTATION_VERTICAL, width,
662                     minimum_height, natural_height, NULL, NULL);
663 }
664 
665 static void
hdy_deck_size_allocate(GtkWidget * widget,GtkAllocation * allocation)666 hdy_deck_size_allocate (GtkWidget     *widget,
667                         GtkAllocation *allocation)
668 {
669   hdy_stackable_box_size_allocate (HDY_GET_HELPER (widget), allocation);
670 }
671 
672 static gboolean
hdy_deck_draw(GtkWidget * widget,cairo_t * cr)673 hdy_deck_draw (GtkWidget *widget,
674                cairo_t   *cr)
675 {
676   return hdy_stackable_box_draw (HDY_GET_HELPER (widget), cr);
677 }
678 
679 static void
hdy_deck_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)680 hdy_deck_direction_changed (GtkWidget        *widget,
681                             GtkTextDirection  previous_direction)
682 {
683   hdy_stackable_box_direction_changed (HDY_GET_HELPER (widget), previous_direction);
684 }
685 
686 static void
hdy_deck_add(GtkContainer * container,GtkWidget * widget)687 hdy_deck_add (GtkContainer *container,
688               GtkWidget    *widget)
689 {
690   hdy_stackable_box_add (HDY_GET_HELPER (container), widget);
691 }
692 
693 static void
hdy_deck_remove(GtkContainer * container,GtkWidget * widget)694 hdy_deck_remove (GtkContainer *container,
695                  GtkWidget    *widget)
696 {
697   hdy_stackable_box_remove (HDY_GET_HELPER (container), widget);
698 }
699 
700 static void
hdy_deck_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)701 hdy_deck_forall (GtkContainer *container,
702                  gboolean      include_internals,
703                  GtkCallback   callback,
704                  gpointer      callback_data)
705 {
706   hdy_stackable_box_forall (HDY_GET_HELPER (container), include_internals, callback, callback_data);
707 }
708 
709 static void
hdy_deck_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)710 hdy_deck_get_property (GObject    *object,
711                        guint       prop_id,
712                        GValue     *value,
713                        GParamSpec *pspec)
714 {
715   HdyDeck *self = HDY_DECK (object);
716 
717   switch (prop_id) {
718   case PROP_HHOMOGENEOUS:
719     g_value_set_boolean (value, hdy_deck_get_homogeneous (self, GTK_ORIENTATION_HORIZONTAL));
720     break;
721   case PROP_VHOMOGENEOUS:
722     g_value_set_boolean (value, hdy_deck_get_homogeneous (self, GTK_ORIENTATION_VERTICAL));
723     break;
724   case PROP_VISIBLE_CHILD:
725     g_value_set_object (value, hdy_deck_get_visible_child (self));
726     break;
727   case PROP_VISIBLE_CHILD_NAME:
728     g_value_set_string (value, hdy_deck_get_visible_child_name (self));
729     break;
730   case PROP_TRANSITION_TYPE:
731     g_value_set_enum (value, hdy_deck_get_transition_type (self));
732     break;
733   case PROP_TRANSITION_DURATION:
734     g_value_set_uint (value, hdy_deck_get_transition_duration (self));
735     break;
736   case PROP_TRANSITION_RUNNING:
737     g_value_set_boolean (value, hdy_deck_get_transition_running (self));
738     break;
739   case PROP_INTERPOLATE_SIZE:
740     g_value_set_boolean (value, hdy_deck_get_interpolate_size (self));
741     break;
742   case PROP_CAN_SWIPE_BACK:
743     g_value_set_boolean (value, hdy_deck_get_can_swipe_back (self));
744     break;
745   case PROP_CAN_SWIPE_FORWARD:
746     g_value_set_boolean (value, hdy_deck_get_can_swipe_forward (self));
747     break;
748   case PROP_ORIENTATION:
749     g_value_set_enum (value, hdy_stackable_box_get_orientation (HDY_GET_HELPER (self)));
750     break;
751   default:
752     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
753   }
754 }
755 
756 static void
hdy_deck_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)757 hdy_deck_set_property (GObject      *object,
758                        guint         prop_id,
759                        const GValue *value,
760                        GParamSpec   *pspec)
761 {
762   HdyDeck *self = HDY_DECK (object);
763 
764   switch (prop_id) {
765   case PROP_HHOMOGENEOUS:
766     hdy_deck_set_homogeneous (self, GTK_ORIENTATION_HORIZONTAL, g_value_get_boolean (value));
767     break;
768   case PROP_VHOMOGENEOUS:
769     hdy_deck_set_homogeneous (self, GTK_ORIENTATION_VERTICAL, g_value_get_boolean (value));
770     break;
771   case PROP_VISIBLE_CHILD:
772     hdy_deck_set_visible_child (self, g_value_get_object (value));
773     break;
774   case PROP_VISIBLE_CHILD_NAME:
775     hdy_deck_set_visible_child_name (self, g_value_get_string (value));
776     break;
777   case PROP_TRANSITION_TYPE:
778     hdy_deck_set_transition_type (self, g_value_get_enum (value));
779     break;
780   case PROP_TRANSITION_DURATION:
781     hdy_deck_set_transition_duration (self, g_value_get_uint (value));
782     break;
783   case PROP_INTERPOLATE_SIZE:
784     hdy_deck_set_interpolate_size (self, g_value_get_boolean (value));
785     break;
786   case PROP_CAN_SWIPE_BACK:
787     hdy_deck_set_can_swipe_back (self, g_value_get_boolean (value));
788     break;
789   case PROP_CAN_SWIPE_FORWARD:
790     hdy_deck_set_can_swipe_forward (self, g_value_get_boolean (value));
791     break;
792   case PROP_ORIENTATION:
793     hdy_stackable_box_set_orientation (HDY_GET_HELPER (self), g_value_get_enum (value));
794     break;
795   default:
796     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
797   }
798 }
799 
800 static void
hdy_deck_finalize(GObject * object)801 hdy_deck_finalize (GObject *object)
802 {
803   HdyDeck *self = HDY_DECK (object);
804   HdyDeckPrivate *priv = hdy_deck_get_instance_private (self);
805 
806   g_clear_object (&priv->box);
807 
808   G_OBJECT_CLASS (hdy_deck_parent_class)->finalize (object);
809 }
810 
811 static void
hdy_deck_get_child_property(GtkContainer * container,GtkWidget * widget,guint property_id,GValue * value,GParamSpec * pspec)812 hdy_deck_get_child_property (GtkContainer *container,
813                              GtkWidget    *widget,
814                              guint         property_id,
815                              GValue       *value,
816                              GParamSpec   *pspec)
817 {
818   switch (property_id) {
819   case CHILD_PROP_NAME:
820     g_value_set_string (value, hdy_stackable_box_get_child_name (HDY_GET_HELPER (container), widget));
821     break;
822 
823   default:
824     GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
825     break;
826   }
827 }
828 
829 static void
hdy_deck_set_child_property(GtkContainer * container,GtkWidget * widget,guint property_id,const GValue * value,GParamSpec * pspec)830 hdy_deck_set_child_property (GtkContainer *container,
831                              GtkWidget    *widget,
832                              guint         property_id,
833                              const GValue *value,
834                              GParamSpec   *pspec)
835 {
836   switch (property_id) {
837   case CHILD_PROP_NAME:
838     hdy_stackable_box_set_child_name (HDY_GET_HELPER (container), widget, g_value_get_string (value));
839     gtk_container_child_notify_by_pspec (container, widget, pspec);
840     break;
841 
842   default:
843     GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
844     break;
845   }
846 }
847 
848 static void
hdy_deck_realize(GtkWidget * widget)849 hdy_deck_realize (GtkWidget *widget)
850 {
851   hdy_stackable_box_realize (HDY_GET_HELPER (widget));
852 }
853 
854 static void
hdy_deck_unrealize(GtkWidget * widget)855 hdy_deck_unrealize (GtkWidget *widget)
856 {
857   hdy_stackable_box_unrealize (HDY_GET_HELPER (widget));
858 }
859 
860 static void
hdy_deck_switch_child(HdySwipeable * swipeable,guint index,gint64 duration)861 hdy_deck_switch_child (HdySwipeable *swipeable,
862                        guint         index,
863                        gint64        duration)
864 {
865   hdy_stackable_box_switch_child (HDY_GET_HELPER (swipeable), index, duration);
866 }
867 
868 static HdySwipeTracker *
hdy_deck_get_swipe_tracker(HdySwipeable * swipeable)869 hdy_deck_get_swipe_tracker (HdySwipeable *swipeable)
870 {
871   return hdy_stackable_box_get_swipe_tracker (HDY_GET_HELPER (swipeable));
872 }
873 
874 static gdouble
hdy_deck_get_distance(HdySwipeable * swipeable)875 hdy_deck_get_distance (HdySwipeable *swipeable)
876 {
877   return hdy_stackable_box_get_distance (HDY_GET_HELPER (swipeable));
878 }
879 
880 static gdouble *
hdy_deck_get_snap_points(HdySwipeable * swipeable,gint * n_snap_points)881 hdy_deck_get_snap_points (HdySwipeable *swipeable,
882                           gint         *n_snap_points)
883 {
884   return hdy_stackable_box_get_snap_points (HDY_GET_HELPER (swipeable), n_snap_points);
885 }
886 
887 static gdouble
hdy_deck_get_progress(HdySwipeable * swipeable)888 hdy_deck_get_progress (HdySwipeable *swipeable)
889 {
890   return hdy_stackable_box_get_progress (HDY_GET_HELPER (swipeable));
891 }
892 
893 static gdouble
hdy_deck_get_cancel_progress(HdySwipeable * swipeable)894 hdy_deck_get_cancel_progress (HdySwipeable *swipeable)
895 {
896   return hdy_stackable_box_get_cancel_progress (HDY_GET_HELPER (swipeable));
897 }
898 
899 static void
hdy_deck_get_swipe_area(HdySwipeable * swipeable,HdyNavigationDirection navigation_direction,gboolean is_drag,GdkRectangle * rect)900 hdy_deck_get_swipe_area (HdySwipeable           *swipeable,
901                          HdyNavigationDirection  navigation_direction,
902                          gboolean                is_drag,
903                          GdkRectangle           *rect)
904 {
905   hdy_stackable_box_get_swipe_area (HDY_GET_HELPER (swipeable), navigation_direction, is_drag, rect);
906 }
907 
908 static void
hdy_deck_class_init(HdyDeckClass * klass)909 hdy_deck_class_init (HdyDeckClass *klass)
910 {
911   GObjectClass *object_class = G_OBJECT_CLASS (klass);
912   GtkWidgetClass *widget_class = (GtkWidgetClass*) klass;
913   GtkContainerClass *container_class = (GtkContainerClass*) klass;
914 
915   object_class->get_property = hdy_deck_get_property;
916   object_class->set_property = hdy_deck_set_property;
917   object_class->finalize = hdy_deck_finalize;
918 
919   widget_class->realize = hdy_deck_realize;
920   widget_class->unrealize = hdy_deck_unrealize;
921   widget_class->get_preferred_width = hdy_deck_get_preferred_width;
922   widget_class->get_preferred_height = hdy_deck_get_preferred_height;
923   widget_class->get_preferred_width_for_height = hdy_deck_get_preferred_width_for_height;
924   widget_class->get_preferred_height_for_width = hdy_deck_get_preferred_height_for_width;
925   widget_class->size_allocate = hdy_deck_size_allocate;
926   widget_class->draw = hdy_deck_draw;
927   widget_class->direction_changed = hdy_deck_direction_changed;
928 
929   container_class->add = hdy_deck_add;
930   container_class->remove = hdy_deck_remove;
931   container_class->forall = hdy_deck_forall;
932   container_class->set_child_property = hdy_deck_set_child_property;
933   container_class->get_child_property = hdy_deck_get_child_property;
934   gtk_container_class_handle_border_width (container_class);
935 
936   g_object_class_override_property (object_class,
937                                     PROP_ORIENTATION,
938                                     "orientation");
939 
940   /**
941    * HdyDeck:hhomogeneous:
942    *
943    * Horizontally homogeneous sizing.
944    *
945    * Since: 1.0
946    */
947   props[PROP_HHOMOGENEOUS] =
948     g_param_spec_boolean ("hhomogeneous",
949                           _("Horizontally homogeneous"),
950                           _("Horizontally homogeneous sizing"),
951                           TRUE,
952                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
953 
954   /**
955    * HdyDeck:vhomogeneous:
956    *
957    * Vertically homogeneous sizing.
958    *
959    * Since: 1.0
960    */
961   props[PROP_VHOMOGENEOUS] =
962     g_param_spec_boolean ("vhomogeneous",
963                           _("Vertically homogeneous"),
964                           _("Vertically homogeneous sizing"),
965                           TRUE,
966                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
967 
968   /**
969    * HdyDeck:visible-child:
970    *
971    * The widget currently visible.
972    *
973    * Since: 1.0
974    */
975   props[PROP_VISIBLE_CHILD] =
976     g_param_spec_object ("visible-child",
977                          _("Visible child"),
978                          _("The widget currently visible"),
979                          GTK_TYPE_WIDGET,
980                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
981 
982   /**
983    * HdyDeck:visible-child-name:
984    *
985    * The name of the widget currently visible.
986    *
987    * Since: 1.0
988    */
989   props[PROP_VISIBLE_CHILD_NAME] =
990     g_param_spec_string ("visible-child-name",
991                          _("Name of visible child"),
992                          _("The name of the widget currently visible"),
993                          NULL,
994                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
995 
996   /**
997    * HdyDeck:transition-type:
998    *
999    * The type of animation that will be used for transitions between
1000    * children.
1001    *
1002    * The transition type can be changed without problems at runtime, so it is
1003    * possible to change the animation based on the child that is about
1004    * to become current.
1005    *
1006    * Since: 1.0
1007    */
1008   props[PROP_TRANSITION_TYPE] =
1009     g_param_spec_enum ("transition-type",
1010                        _("Transition type"),
1011                        _("The type of animation used to transition between children"),
1012                        HDY_TYPE_DECK_TRANSITION_TYPE, HDY_DECK_TRANSITION_TYPE_OVER,
1013                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1014 
1015   /**
1016    * HdyDeck:transition-duration:
1017    *
1018    * The transition animation duration, in milliseconds.
1019    *
1020    * Since: 1.0
1021    */
1022   props[PROP_TRANSITION_DURATION] =
1023     g_param_spec_uint ("transition-duration",
1024                        _("Transition duration"),
1025                        _("The transition animation duration, in milliseconds"),
1026                        0, G_MAXUINT, 200,
1027                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1028 
1029   /**
1030    * HdyDeck:transition-running:
1031    *
1032    * Whether or not the transition is currently running.
1033    *
1034    * Since: 1.0
1035    */
1036   props[PROP_TRANSITION_RUNNING] =
1037       g_param_spec_boolean ("transition-running",
1038                             _("Transition running"),
1039                             _("Whether or not the transition is currently running"),
1040                             FALSE,
1041                             G_PARAM_READABLE);
1042 
1043   /**
1044    * HdyDeck:interpolate-size:
1045    *
1046    * Whether or not the size should smoothly change when changing between
1047    * differently sized children.
1048    *
1049    * Since: 1.0
1050    */
1051   props[PROP_INTERPOLATE_SIZE] =
1052       g_param_spec_boolean ("interpolate-size",
1053                             _("Interpolate size"),
1054                             _("Whether or not the size should smoothly change when changing between differently sized children"),
1055                             FALSE,
1056                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1057 
1058   /**
1059    * HdyDeck:can-swipe-back:
1060    *
1061    * Whether or not the deck allows switching to the previous child via a swipe
1062    * gesture.
1063    *
1064    * Since: 1.0
1065    */
1066   props[PROP_CAN_SWIPE_BACK] =
1067       g_param_spec_boolean ("can-swipe-back",
1068                             _("Can swipe back"),
1069                             _("Whether or not swipe gesture can be used to switch to the previous child"),
1070                             FALSE,
1071                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1072 
1073   /**
1074    * HdyDeck:can-swipe-forward:
1075    *
1076    * Whether or not the deck allows switching to the next child via a swipe
1077    * gesture.
1078    *
1079    * Since: 1.0
1080    */
1081   props[PROP_CAN_SWIPE_FORWARD] =
1082       g_param_spec_boolean ("can-swipe-forward",
1083                             _("Can swipe forward"),
1084                             _("Whether or not swipe gesture can be used to switch to the next child"),
1085                             FALSE,
1086                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1087 
1088   g_object_class_install_properties (object_class, LAST_PROP, props);
1089 
1090   child_props[CHILD_PROP_NAME] =
1091     g_param_spec_string ("name",
1092                          _("Name"),
1093                          _("The name of the child page"),
1094                          NULL,
1095                          G_PARAM_READWRITE);
1096 
1097   gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, child_props);
1098 
1099   gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_PANEL);
1100   gtk_widget_class_set_css_name (widget_class, "deck");
1101 }
1102 
1103 GtkWidget *
hdy_deck_new(void)1104 hdy_deck_new (void)
1105 {
1106   return g_object_new (HDY_TYPE_DECK, NULL);
1107 }
1108 
1109 #define NOTIFY(func, prop) \
1110 static void \
1111 func (HdyDeck *self) { \
1112   g_object_notify_by_pspec (G_OBJECT (self), props[prop]); \
1113 }
1114 
1115 NOTIFY (notify_hhomogeneous_folded_cb, PROP_HHOMOGENEOUS);
1116 NOTIFY (notify_vhomogeneous_folded_cb, PROP_VHOMOGENEOUS);
1117 NOTIFY (notify_visible_child_cb, PROP_VISIBLE_CHILD);
1118 NOTIFY (notify_visible_child_name_cb, PROP_VISIBLE_CHILD_NAME);
1119 NOTIFY (notify_transition_type_cb, PROP_TRANSITION_TYPE);
1120 NOTIFY (notify_child_transition_duration_cb, PROP_TRANSITION_DURATION);
1121 NOTIFY (notify_child_transition_running_cb, PROP_TRANSITION_RUNNING);
1122 NOTIFY (notify_interpolate_size_cb, PROP_INTERPOLATE_SIZE);
1123 NOTIFY (notify_can_swipe_back_cb, PROP_CAN_SWIPE_BACK);
1124 NOTIFY (notify_can_swipe_forward_cb, PROP_CAN_SWIPE_FORWARD);
1125 
1126 static void
notify_orientation_cb(HdyDeck * self)1127 notify_orientation_cb (HdyDeck *self)
1128 {
1129   g_object_notify (G_OBJECT (self), "orientation");
1130 }
1131 
1132 static void
hdy_deck_init(HdyDeck * self)1133 hdy_deck_init (HdyDeck *self)
1134 {
1135   HdyDeckPrivate *priv = hdy_deck_get_instance_private (self);
1136 
1137   priv->box = hdy_stackable_box_new (GTK_CONTAINER (self),
1138                                      GTK_CONTAINER_CLASS (hdy_deck_parent_class),
1139                                      FALSE);
1140 
1141   g_signal_connect_object (priv->box, "notify::hhomogeneous-folded", G_CALLBACK (notify_hhomogeneous_folded_cb), self, G_CONNECT_SWAPPED);
1142   g_signal_connect_object (priv->box, "notify::vhomogeneous-folded", G_CALLBACK (notify_vhomogeneous_folded_cb), self, G_CONNECT_SWAPPED);
1143   g_signal_connect_object (priv->box, "notify::visible-child", G_CALLBACK (notify_visible_child_cb), self, G_CONNECT_SWAPPED);
1144   g_signal_connect_object (priv->box, "notify::visible-child-name", G_CALLBACK (notify_visible_child_name_cb), self, G_CONNECT_SWAPPED);
1145   g_signal_connect_object (priv->box, "notify::transition-type", G_CALLBACK (notify_transition_type_cb), self, G_CONNECT_SWAPPED);
1146   g_signal_connect_object (priv->box, "notify::child-transition-duration", G_CALLBACK (notify_child_transition_duration_cb), self, G_CONNECT_SWAPPED);
1147   g_signal_connect_object (priv->box, "notify::child-transition-running", G_CALLBACK (notify_child_transition_running_cb), self, G_CONNECT_SWAPPED);
1148   g_signal_connect_object (priv->box, "notify::interpolate-size", G_CALLBACK (notify_interpolate_size_cb), self, G_CONNECT_SWAPPED);
1149   g_signal_connect_object (priv->box, "notify::can-swipe-back", G_CALLBACK (notify_can_swipe_back_cb), self, G_CONNECT_SWAPPED);
1150   g_signal_connect_object (priv->box, "notify::can-swipe-forward", G_CALLBACK (notify_can_swipe_forward_cb), self, G_CONNECT_SWAPPED);
1151   g_signal_connect_object (priv->box, "notify::orientation", G_CALLBACK (notify_orientation_cb), self, G_CONNECT_SWAPPED);
1152 }
1153 
1154 static void
hdy_deck_swipeable_init(HdySwipeableInterface * iface)1155 hdy_deck_swipeable_init (HdySwipeableInterface *iface)
1156 {
1157   iface->switch_child = hdy_deck_switch_child;
1158   iface->get_swipe_tracker = hdy_deck_get_swipe_tracker;
1159   iface->get_distance = hdy_deck_get_distance;
1160   iface->get_snap_points = hdy_deck_get_snap_points;
1161   iface->get_progress = hdy_deck_get_progress;
1162   iface->get_cancel_progress = hdy_deck_get_cancel_progress;
1163   iface->get_swipe_area = hdy_deck_get_swipe_area;
1164 }
1165