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