1 /*
2  * Copyright (C) 2020 Felix Häcker <haeckerfelix@gnome.org>
3  * Copyright (C) 2020 Purism SPC
4  *
5  * SPDX-License-Identifier: LGPL-2.1+
6  */
7 
8 #include "config.h"
9 #include "hdy-flap.h"
10 
11 #include <glib/gi18n-lib.h>
12 #include <math.h>
13 
14 #include "hdy-animation-private.h"
15 #include "hdy-shadow-helper-private.h"
16 #include "hdy-swipeable.h"
17 #include "hdy-swipe-tracker-private.h"
18 
19 /**
20  * SECTION:hdy-flap
21  * @short_description: An adaptive container acting like a box or an overlay.
22  * @Title: HdyFlap
23  *
24  * The #HdyFlap widget can display its children like a #GtkBox does or like a
25  * #GtkOverlay does, according to the #HdyFlap:fold-policy value.
26  *
27  * #HdyFlap has at most three children: #HdyFlap:content, #HdyFlap:flap and
28  * #HdyFlap:separator. Content is the primary child, flap is displayed next to
29  * it when unfolded, or overlays it when folded. Flap can be shown or hidden by
30  * changing the #HdyFlap:reveal-flap value, as well as via swipe gestures if
31  * #HdyFlap:swipe-to-open and/or #HdyFlap:swipe-to-close are set to %TRUE.
32  *
33  * Optionally, a separator can be provided, which would be displayed between
34  * the content and the flap when there's no shadow to separate them, depending
35  * on the transition type.
36  *
37  * #HdyFlap:flap is transparent by default; add the .background style class to
38  * it if this is unwanted.
39  *
40  * If #HdyFlap:modal is set to %TRUE, content becomes completely inaccessible
41  * when the flap is revealed when folded.
42  *
43  * The position of the flap and separator children relative to the content is
44  * determined by orientation, as well as  #HdyFlap:flap-position value.
45  *
46  * Folding the flap will automatically hide the flap widget, and unfolding it
47  * will automatically reveal it. If this behavior is not desired, the
48  * #HdyFlap:locked property can be used to override it.
49  *
50  * Common use cases include sidebars, header bars that need to be able to
51  * overlap the window content (for example, in fullscreen mode) and bottom
52  * sheets.
53  *
54  * # HdyFlap as GtkBuildable
55  *
56  * The #HdyFlap implementation of the #GtkBuildable interface supports setting
57  * the flap child by specifying “flap” as the “type” attribute of a
58  * &lt;child&gt; element, and separator by specifying “separator”. Specifying
59  * “content” child type or omitting it results in setting the content child.
60  *
61  * # CSS nodes
62  *
63  * #HdyFlap has a single CSS node with name flap. The node will get the style
64  * classes .folded when it is folded, and .unfolded when it's not.
65  *
66  * Since: 1.2
67  */
68 
69 /**
70  * HdyFlapFoldPolicy:
71  * @HDY_FLAP_FOLD_POLICY_NEVER: Disable folding, the flap cannot reach narrow
72  *   sizes.
73  * @HDY_FLAP_FOLD_POLICY_ALWAYS: Keep the flap always folded.
74  * @HDY_FLAP_FOLD_POLICY_AUTO: Fold and unfold the flap based on available
75  *   space.
76  *
77  * These enumeration values describe the possible folding behavior in a #HdyFlap
78  * widget.
79  *
80  * Since: 1.2
81  */
82 
83 /**
84  * HdyFlapTransitionType:
85  * @HDY_FLAP_TRANSITION_TYPE_OVER: The flap slides over the content, which is
86  *   dimmed. When folded, only the flap can be swiped.
87  * @HDY_FLAP_TRANSITION_TYPE_UNDER: The content slides over the flap. Only the
88  *   content can be swiped.
89  * @HDY_FLAP_TRANSITION_TYPE_SLIDE: The flap slides offscreen when hidden,
90  *   neither the flap nor content overlap each other. Both widgets can be
91  *   swiped.
92  *
93  * These enumeration values describe the possible transitions between children
94  * in a #HdyFlap widget, as well as which areas can be swiped via
95  * #HdyFlap:swipe-to-open and #HdyFlap:swipe-to-close.
96  *
97  * New values may be added to this enum over time.
98  *
99  * Since: 1.2
100  */
101 
102 typedef struct {
103   GtkWidget *widget;
104   GdkWindow *window;
105   GtkAllocation allocation;
106 } ChildInfo;
107 
108 struct _HdyFlap
109 {
110   GtkContainer parent_instance;
111 
112   ChildInfo content;
113   ChildInfo flap;
114   ChildInfo separator;
115 
116   HdyFlapFoldPolicy fold_policy;
117   HdyFlapTransitionType transition_type;
118   GtkPackType flap_position;
119   gboolean reveal_flap;
120   gboolean locked;
121   gboolean folded;
122 
123   guint fold_duration;
124   gdouble fold_progress;
125   HdyAnimation *fold_animation;
126 
127   guint reveal_duration;
128   gdouble reveal_progress;
129   HdyAnimation *reveal_animation;
130 
131   gboolean schedule_fold;
132 
133   GtkOrientation orientation;
134 
135   HdyShadowHelper *shadow_helper;
136 
137   gboolean swipe_to_open;
138   gboolean swipe_to_close;
139   HdySwipeTracker *tracker;
140   gboolean swipe_active;
141 
142   gboolean modal;
143   GtkGesture *click_gesture;
144   GtkEventController *key_controller;
145 };
146 
147 static void hdy_flap_buildable_init (GtkBuildableIface *iface);
148 static void hdy_flap_swipeable_init (HdySwipeableInterface *iface);
149 
150 G_DEFINE_TYPE_WITH_CODE (HdyFlap, hdy_flap, GTK_TYPE_CONTAINER,
151                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
152                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, hdy_flap_buildable_init)
153                          G_IMPLEMENT_INTERFACE (HDY_TYPE_SWIPEABLE, hdy_flap_swipeable_init))
154 
155 enum {
156   PROP_0,
157   PROP_CONTENT,
158   PROP_FLAP,
159   PROP_SEPARATOR,
160   PROP_FLAP_POSITION,
161   PROP_REVEAL_FLAP,
162   PROP_REVEAL_DURATION,
163   PROP_REVEAL_PROGRESS,
164   PROP_FOLD_POLICY,
165   PROP_FOLD_DURATION,
166   PROP_FOLDED,
167   PROP_LOCKED,
168   PROP_TRANSITION_TYPE,
169   PROP_MODAL,
170   PROP_SWIPE_TO_OPEN,
171   PROP_SWIPE_TO_CLOSE,
172 
173   /* Overridden properties */
174   PROP_ORIENTATION,
175 
176   LAST_PROP = PROP_ORIENTATION,
177 };
178 
179 static GParamSpec *props[LAST_PROP];
180 
181 static void
update_swipe_tracker(HdyFlap * self)182 update_swipe_tracker (HdyFlap *self)
183 {
184   gboolean reverse = self->flap_position == GTK_PACK_START;
185 
186   if (!self->tracker)
187     return;
188 
189   if (self->orientation == GTK_ORIENTATION_HORIZONTAL &&
190       gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
191     reverse = !reverse;
192 
193   hdy_swipe_tracker_set_enabled (self->tracker, self->flap.widget &&
194                                  (self->swipe_to_open || self->swipe_to_close));
195   hdy_swipe_tracker_set_reversed (self->tracker, reverse);
196   gtk_orientable_set_orientation (GTK_ORIENTABLE (self->tracker),
197                                   self->orientation);
198 }
199 
200 static void
set_orientation(HdyFlap * self,GtkOrientation orientation)201 set_orientation (HdyFlap        *self,
202                  GtkOrientation  orientation)
203 {
204   if (self->orientation == orientation)
205     return;
206 
207   self->orientation = orientation;
208 
209   gtk_widget_queue_resize (GTK_WIDGET (self));
210   update_swipe_tracker (self);
211 
212   g_object_notify (G_OBJECT (self), "orientation");
213 }
214 
215 static void
update_child_visibility(HdyFlap * self)216 update_child_visibility (HdyFlap *self)
217 {
218   gboolean visible = self->reveal_progress > 0;
219 
220   if (self->flap.widget)
221     gtk_widget_set_child_visible (self->flap.widget, visible);
222 
223   if (self->separator.widget)
224     gtk_widget_set_child_visible (self->separator.widget, visible);
225 
226   if (!gtk_widget_get_realized (GTK_WIDGET (self)))
227     return;
228 
229   if (self->flap.widget) {
230     if (visible)
231       gdk_window_show (self->flap.window);
232     else
233       gdk_window_hide (self->flap.window);
234   }
235 
236   if (self->separator.widget) {
237     if (visible)
238       gdk_window_show (self->separator.window);
239     else
240       gdk_window_hide (self->separator.window);
241   }
242 
243   gtk_widget_queue_resize (GTK_WIDGET (self));
244 }
245 
246 static void
set_reveal_progress(HdyFlap * self,gdouble progress)247 set_reveal_progress (HdyFlap *self,
248                      gdouble  progress)
249 {
250   self->reveal_progress = progress;
251 
252   update_child_visibility (self);
253 
254   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_PROGRESS]);
255 }
256 
257 static void
fold_animation_value_cb(gdouble value,HdyFlap * self)258 fold_animation_value_cb (gdouble  value,
259                          HdyFlap *self)
260 {
261   self->fold_progress = value;
262 
263   gtk_widget_queue_resize (GTK_WIDGET (self));
264 }
265 
266 static void
fold_animation_done_cb(HdyFlap * self)267 fold_animation_done_cb (HdyFlap *self)
268 {
269   g_clear_pointer (&self->fold_animation, hdy_animation_unref);
270 }
271 
272 static void
animate_fold(HdyFlap * self)273 animate_fold (HdyFlap *self)
274 {
275   if (self->fold_animation)
276     hdy_animation_stop (self->fold_animation);
277 
278   self->fold_animation =
279     hdy_animation_new (GTK_WIDGET (self),
280                        self->fold_progress,
281                        self->folded ? 1 : 0,
282                        /* When the flap is completely hidden, we can skip animation */
283                        (self->reveal_progress > 0) ? self->fold_duration : 0,
284                        hdy_ease_out_cubic,
285                        (HdyAnimationValueCallback) fold_animation_value_cb,
286                        (HdyAnimationDoneCallback) fold_animation_done_cb,
287                        self);
288 
289   hdy_animation_start (self->fold_animation);
290 }
291 
292 static void
reveal_animation_value_cb(gdouble value,HdyFlap * self)293 reveal_animation_value_cb (gdouble  value,
294                            HdyFlap *self)
295 {
296   set_reveal_progress (self, value);
297 }
298 
299 static void
reveal_animation_done_cb(HdyFlap * self)300 reveal_animation_done_cb (HdyFlap *self)
301 {
302   g_clear_pointer (&self->reveal_animation, hdy_animation_unref);
303 
304   if (self->reveal_progress <= 0 ||
305       self->transition_type == HDY_FLAP_TRANSITION_TYPE_UNDER)
306     hdy_shadow_helper_clear_cache (self->shadow_helper);
307 
308   if (self->schedule_fold) {
309     self->schedule_fold = FALSE;
310 
311     animate_fold (self);
312   }
313 
314   gtk_widget_queue_allocate (GTK_WIDGET (self));
315 }
316 
317 static void
animate_reveal(HdyFlap * self,gdouble to,gint64 duration)318 animate_reveal (HdyFlap *self,
319                 gdouble  to,
320                 gint64   duration)
321 {
322   if (self->reveal_animation)
323     hdy_animation_stop (self->reveal_animation);
324 
325   self->reveal_animation =
326     hdy_animation_new (GTK_WIDGET (self),
327                        self->reveal_progress,
328                        to,
329                        duration,
330                        hdy_ease_out_cubic,
331                        (HdyAnimationValueCallback) reveal_animation_value_cb,
332                        (HdyAnimationDoneCallback) reveal_animation_done_cb,
333                        self);
334 
335   hdy_animation_start (self->reveal_animation);
336 }
337 
338 static void
set_reveal_flap(HdyFlap * self,gboolean reveal_flap,guint64 duration,gboolean emit_child_switched)339 set_reveal_flap (HdyFlap  *self,
340                  gboolean  reveal_flap,
341                  guint64   duration,
342                  gboolean  emit_child_switched)
343 {
344   reveal_flap = !!reveal_flap;
345 
346   if (self->reveal_flap == reveal_flap)
347     return;
348 
349   self->reveal_flap = reveal_flap;
350 
351   if (!self->swipe_active) {
352     animate_reveal (self, reveal_flap ? 1 : 0, duration);
353 
354     if (emit_child_switched)
355       hdy_swipeable_emit_child_switched (HDY_SWIPEABLE (self), reveal_flap ? 1 : 0, duration);
356   }
357 
358   if (self->reveal_flap &&
359       self->content.widget &&
360       self->flap.widget &&
361       self->modal &&
362       self->fold_progress > 0 &&
363       gtk_widget_get_mapped (GTK_WIDGET (self))) {
364     GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
365     GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
366 
367     if (focus && gtk_widget_is_ancestor (focus, self->content.widget))
368       gtk_widget_child_focus (GTK_WIDGET (self), GTK_DIR_TAB_FORWARD);
369   }
370 
371   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_FLAP]);
372 }
373 
374 static void
set_folded(HdyFlap * self,gboolean folded)375 set_folded (HdyFlap  *self,
376             gboolean  folded)
377 {
378   GtkStyleContext *context;
379 
380   folded = !!folded;
381 
382   if (self->folded == folded)
383     return;
384 
385   self->folded = folded;
386 
387   gtk_widget_queue_allocate (GTK_WIDGET (self));
388 
389    /* When unlocked, folding should also hide flap. We don't want two concurrent
390     * animations in this case, instead only animate reveal and schedule a fold
391     * after it finishes, which will be skipped because the flap is fuly hidden.
392     * Meanwhile if it's unfolding, animate folding immediately. */
393   if (!self->locked && folded)
394     self->schedule_fold = TRUE;
395   else
396     animate_fold (self);
397 
398   if (!self->locked)
399     set_reveal_flap (self, !self->folded, self->fold_duration, TRUE);
400 
401   context = gtk_widget_get_style_context (GTK_WIDGET (self));
402   if (folded) {
403     gtk_style_context_add_class (context, "folded");
404     gtk_style_context_remove_class (context, "unfolded");
405   } else {
406     gtk_style_context_remove_class (context, "folded");
407     gtk_style_context_add_class (context, "unfolded");
408   }
409 
410   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLDED]);
411 }
412 
413 static inline GtkPackType
get_start_or_end(HdyFlap * self)414 get_start_or_end (HdyFlap *self)
415 {
416   GtkTextDirection direction = gtk_widget_get_direction (GTK_WIDGET (self));
417   gboolean is_rtl = direction == GTK_TEXT_DIR_RTL;
418   gboolean is_horiz = self->orientation == GTK_ORIENTATION_HORIZONTAL;
419 
420   return (is_rtl && is_horiz) ? GTK_PACK_END : GTK_PACK_START;
421 }
422 
423 static void
begin_swipe_cb(HdySwipeTracker * tracker,HdyNavigationDirection direction,gboolean direct,HdyFlap * self)424 begin_swipe_cb (HdySwipeTracker        *tracker,
425                 HdyNavigationDirection  direction,
426                 gboolean                direct,
427                 HdyFlap                *self)
428 {
429   if (self->reveal_progress <= 0 && !self->swipe_to_open)
430     return;
431 
432   if (self->reveal_progress >= 1 && !self->swipe_to_close)
433     return;
434 
435   if (self->reveal_animation)
436     hdy_animation_stop (self->reveal_animation);
437 
438   self->swipe_active = TRUE;
439 }
440 
441 static void
update_swipe_cb(HdySwipeTracker * tracker,gdouble progress,HdyFlap * self)442 update_swipe_cb (HdySwipeTracker *tracker,
443                  gdouble          progress,
444                  HdyFlap         *self)
445 {
446   if (!self->swipe_active)
447     return;
448 
449   set_reveal_progress (self, progress);
450 }
451 
452 static void
end_swipe_cb(HdySwipeTracker * tracker,gint64 duration,gdouble to,HdyFlap * self)453 end_swipe_cb (HdySwipeTracker *tracker,
454               gint64           duration,
455               gdouble          to,
456               HdyFlap         *self)
457 {
458   if (!self->swipe_active)
459     return;
460 
461   self->swipe_active = FALSE;
462 
463   if ((to > 0) == self->reveal_flap)
464     animate_reveal (self, to, duration);
465   else
466     set_reveal_flap (self, to > 0, duration, FALSE);
467 }
468 
469 static void
released_cb(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,HdyFlap * self)470 released_cb (GtkGestureMultiPress *gesture,
471              gint                  n_press,
472              gdouble               x,
473              gdouble               y,
474              HdyFlap              *self)
475 {
476   if (self->reveal_progress <= 0 || self->fold_progress <= 0) {
477     gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
478 
479     return;
480   }
481 
482   if (x >= self->flap.allocation.x &&
483       x <= self->flap.allocation.x + self->flap.allocation.width &&
484       y >= self->flap.allocation.y &&
485       y <= self->flap.allocation.y + self->flap.allocation.height) {
486     gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_DENIED);
487 
488     return;
489   }
490 
491   hdy_flap_set_reveal_flap (self, FALSE);
492 }
493 
494 static gboolean
key_pressed_cb(GtkEventControllerKey * controller,guint keyval,guint keycode,GdkModifierType modifiers,HdyFlap * self)495 key_pressed_cb (GtkEventControllerKey *controller,
496                 guint                  keyval,
497                 guint                  keycode,
498                 GdkModifierType        modifiers,
499                 HdyFlap               *self)
500 {
501   if (keyval == GDK_KEY_Escape &&
502       self->reveal_progress > 0 &&
503       self->fold_progress > 0) {
504     hdy_flap_set_reveal_flap (self, FALSE);
505 
506     return GDK_EVENT_STOP;
507   }
508 
509   return GDK_EVENT_PROPAGATE;
510 }
511 
512 static void
register_window(HdyFlap * self,ChildInfo * info)513 register_window (HdyFlap   *self,
514                  ChildInfo *info)
515 {
516   GdkWindowAttr attributes = { 0 };
517   GdkWindowAttributesType attributes_mask;
518 
519   if (!info->widget)
520     return;
521 
522   attributes.x = info->allocation.x;
523   attributes.y = info->allocation.y;
524   attributes.width = info->allocation.width;
525   attributes.height = info->allocation.height;
526   attributes.window_type = GDK_WINDOW_CHILD;
527   attributes.wclass = GDK_INPUT_OUTPUT;
528   attributes.visual = gtk_widget_get_visual (info->widget);
529   attributes.event_mask = gtk_widget_get_events (info->widget);
530   attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;
531 
532   attributes.event_mask = gtk_widget_get_events (GTK_WIDGET (self)) |
533                           gtk_widget_get_events (info->widget);
534 
535   info->window = gdk_window_new (gtk_widget_get_window (GTK_WIDGET (self)),
536                                  &attributes, attributes_mask);
537   gtk_widget_register_window (GTK_WIDGET (self), info->window);
538 
539   gtk_widget_set_parent_window (info->widget, info->window);
540 
541   gdk_window_show (info->window);
542 }
543 
544 static void
unregister_window(HdyFlap * self,ChildInfo * info)545 unregister_window (HdyFlap   *self,
546                    ChildInfo *info)
547 {
548   if (!info->window)
549     return;
550 
551   gtk_widget_unregister_window (GTK_WIDGET (self), info->window);
552   gdk_window_destroy (info->window);
553   info->window = NULL;
554 }
555 
556 static gboolean
transition_is_content_above_flap(HdyFlap * self)557 transition_is_content_above_flap (HdyFlap *self)
558 {
559   switch (self->transition_type) {
560   case HDY_FLAP_TRANSITION_TYPE_OVER:
561     return FALSE;
562 
563   case HDY_FLAP_TRANSITION_TYPE_UNDER:
564   case HDY_FLAP_TRANSITION_TYPE_SLIDE:
565     return TRUE;
566 
567   default:
568     g_assert_not_reached ();
569   }
570 }
571 
572 static gboolean
transition_should_clip(HdyFlap * self)573 transition_should_clip (HdyFlap *self)
574 {
575   switch (self->transition_type) {
576   case HDY_FLAP_TRANSITION_TYPE_OVER:
577   case HDY_FLAP_TRANSITION_TYPE_SLIDE:
578     return FALSE;
579 
580   case HDY_FLAP_TRANSITION_TYPE_UNDER:
581     return TRUE;
582 
583   default:
584     g_assert_not_reached ();
585   }
586 }
587 
588 static gdouble
transition_get_content_motion_factor(HdyFlap * self)589 transition_get_content_motion_factor (HdyFlap *self)
590 {
591   switch (self->transition_type) {
592   case HDY_FLAP_TRANSITION_TYPE_OVER:
593     return 0;
594 
595   case HDY_FLAP_TRANSITION_TYPE_UNDER:
596   case HDY_FLAP_TRANSITION_TYPE_SLIDE:
597     return 1;
598 
599   default:
600     g_assert_not_reached ();
601   }
602 }
603 
604 static gdouble
transition_get_flap_motion_factor(HdyFlap * self)605 transition_get_flap_motion_factor (HdyFlap *self)
606 {
607   switch (self->transition_type) {
608   case HDY_FLAP_TRANSITION_TYPE_OVER:
609   case HDY_FLAP_TRANSITION_TYPE_SLIDE:
610     return 1;
611 
612   case HDY_FLAP_TRANSITION_TYPE_UNDER:
613     return 0;
614 
615   default:
616     g_assert_not_reached ();
617   }
618 }
619 
620 static void
restack_windows(HdyFlap * self)621 restack_windows (HdyFlap *self)
622 {
623   gboolean content_above_flap = transition_is_content_above_flap (self);
624 
625   if (!content_above_flap) {
626     if (self->content.window)
627       gdk_window_raise (self->content.window);
628 
629     if (self->separator.window)
630       gdk_window_raise (self->separator.window);
631   }
632 
633   if (self->flap.window)
634     gdk_window_raise (self->flap.window);
635 
636   if (content_above_flap) {
637     if (self->separator.window)
638       gdk_window_raise (self->separator.window);
639 
640     if (self->content.window)
641       gdk_window_raise (self->content.window);
642   }
643 }
644 
645 static void
add_child(HdyFlap * self,ChildInfo * info)646 add_child (HdyFlap   *self,
647            ChildInfo *info)
648 {
649   if (gtk_widget_get_realized (GTK_WIDGET (self))) {
650     register_window (self, info);
651     restack_windows (self);
652   }
653 
654   gtk_widget_set_parent (info->widget, GTK_WIDGET (self));
655 }
656 
657 static void
remove_child(HdyFlap * self,ChildInfo * info)658 remove_child (HdyFlap   *self,
659               ChildInfo *info)
660 {
661   if (gtk_widget_get_realized (GTK_WIDGET (self)))
662     unregister_window (self, info);
663 
664   gtk_widget_unparent (info->widget);
665 }
666 
667 static inline void
get_preferred_size(GtkWidget * widget,GtkOrientation orientation,gint * min,gint * nat)668 get_preferred_size (GtkWidget      *widget,
669                     GtkOrientation  orientation,
670                     gint           *min,
671                     gint           *nat)
672 {
673   if (orientation == GTK_ORIENTATION_HORIZONTAL)
674     gtk_widget_get_preferred_width (widget, min, nat);
675   else
676     gtk_widget_get_preferred_height (widget, min, nat);
677 }
678 
679 static void
compute_sizes(HdyFlap * self,GtkAllocation * alloc,gboolean folded,gboolean revealed,gint * flap_size,gint * content_size,gint * separator_size)680 compute_sizes (HdyFlap       *self,
681                GtkAllocation *alloc,
682                gboolean       folded,
683                gboolean       revealed,
684                gint          *flap_size,
685                gint          *content_size,
686                gint          *separator_size)
687 {
688   gboolean flap_expand, content_expand;
689   gint total, extra;
690   gint flap_nat, content_nat;
691 
692   if (!self->flap.widget && !self->content.widget)
693     return;
694 
695   if (self->separator.widget)
696     get_preferred_size (self->separator.widget, self->orientation, separator_size, NULL);
697   else
698     *separator_size = 0;
699 
700   if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
701     total = alloc->width;
702   else
703     total = alloc->height;
704 
705   if (!self->flap.widget) {
706     *content_size = total;
707     *flap_size = 0;
708 
709     return;
710   }
711 
712   if (!self->content.widget) {
713     *content_size = 0;
714     *flap_size = total;
715 
716     return;
717   }
718 
719   get_preferred_size (self->flap.widget, self->orientation, flap_size, &flap_nat);
720   get_preferred_size (self->content.widget, self->orientation, content_size, &content_nat);
721 
722   flap_expand = gtk_widget_compute_expand (self->flap.widget, self->orientation);
723   content_expand = gtk_widget_compute_expand (self->content.widget, self->orientation);
724 
725   if (folded) {
726     *content_size = total;
727 
728     if (flap_expand) {
729       *flap_size = total;
730     } else {
731       get_preferred_size (self->flap.widget, self->orientation, NULL, flap_size);
732       *flap_size = MIN (*flap_size, total);
733     }
734 
735     return;
736   }
737 
738   if (revealed)
739     total -= *separator_size;
740 
741   if (flap_expand && content_expand) {
742     *flap_size = MAX (total / 2, *flap_size);
743 
744     if (!revealed)
745       *content_size = total;
746     else
747       *content_size = total - *flap_size;
748 
749     return;
750   }
751 
752   extra = total - *content_size - *flap_size;
753 
754   if (extra > 0 && flap_expand) {
755     *flap_size += extra;
756 
757     if (!revealed)
758       *content_size = total;
759 
760     return;
761   }
762 
763   if (extra > 0 && content_expand) {
764     *content_size += extra;
765     extra = 0;
766   }
767 
768   if (extra > 0) {
769     GtkRequestedSize sizes[2];
770 
771     sizes[0].data = self->flap.widget;
772     sizes[0].minimum_size = *flap_size;
773     sizes[0].natural_size = flap_nat;
774 
775     sizes[1].data = self->content.widget;
776     sizes[1].minimum_size = *content_size;
777     sizes[1].natural_size = content_nat;
778 
779     extra = gtk_distribute_natural_allocation (extra, 2, sizes);
780 
781     *flap_size = sizes[0].minimum_size;
782     *content_size = sizes[1].minimum_size + extra;
783   }
784 
785   if (!revealed)
786     *content_size = total;
787 }
788 
789 static inline void
interpolate_reveal(HdyFlap * self,GtkAllocation * alloc,gboolean folded,gint * flap_size,gint * content_size,gint * separator_size)790 interpolate_reveal (HdyFlap       *self,
791                     GtkAllocation *alloc,
792                     gboolean       folded,
793                     gint          *flap_size,
794                     gint          *content_size,
795                     gint          *separator_size)
796 {
797   if (self->reveal_progress <= 0) {
798     compute_sizes (self, alloc, folded, FALSE, flap_size, content_size, separator_size);
799   } else if (self->reveal_progress >= 1) {
800     compute_sizes (self, alloc, folded, TRUE, flap_size, content_size, separator_size);
801   } else {
802     gint flap_revealed, content_revealed, separator_revealed;
803     gint flap_hidden, content_hidden, separator_hidden;
804 
805     compute_sizes (self, alloc, folded, TRUE, &flap_revealed, &content_revealed, &separator_revealed);
806     compute_sizes (self, alloc, folded, FALSE, &flap_hidden, &content_hidden, &separator_hidden);
807 
808     *flap_size =
809       (gint) round (hdy_lerp (flap_hidden, flap_revealed,
810                               self->reveal_progress));
811     *content_size =
812       (gint) round (hdy_lerp (content_hidden, content_revealed,
813                               self->reveal_progress));
814     *separator_size =
815       (gint) round (hdy_lerp (separator_hidden, separator_revealed,
816                               self->reveal_progress));
817   }
818 }
819 
820 static inline void
interpolate_fold(HdyFlap * self,GtkAllocation * alloc,gint * flap_size,gint * content_size,gint * separator_size)821 interpolate_fold (HdyFlap       *self,
822                   GtkAllocation *alloc,
823                   gint          *flap_size,
824                   gint          *content_size,
825                   gint          *separator_size)
826 {
827   if (self->fold_progress <= 0) {
828     interpolate_reveal (self, alloc, FALSE, flap_size, content_size, separator_size);
829   } else if (self->fold_progress >= 1) {
830     interpolate_reveal (self, alloc, TRUE, flap_size, content_size, separator_size);
831   } else {
832     gint flap_folded, content_folded, separator_folded;
833     gint flap_unfolded, content_unfolded, separator_unfolded;
834 
835     interpolate_reveal (self, alloc, TRUE, &flap_folded, &content_folded, &separator_folded);
836     interpolate_reveal (self, alloc, FALSE, &flap_unfolded, &content_unfolded, &separator_unfolded);
837 
838     *flap_size =
839       (gint) round (hdy_lerp (flap_unfolded, flap_folded,
840                               self->fold_progress));
841     *content_size =
842       (gint) round (hdy_lerp (content_unfolded, content_folded,
843                               self->fold_progress));
844     *separator_size =
845       (gint) round (hdy_lerp (separator_unfolded, separator_folded,
846                               self->fold_progress));
847   }
848 }
849 
850 static void
compute_allocation(HdyFlap * self,GtkAllocation * alloc,GtkAllocation * flap_alloc,GtkAllocation * content_alloc,GtkAllocation * separator_alloc)851 compute_allocation (HdyFlap       *self,
852                     GtkAllocation *alloc,
853                     GtkAllocation *flap_alloc,
854                     GtkAllocation *content_alloc,
855                     GtkAllocation *separator_alloc)
856 {
857   gdouble distance;
858   gint content_size, flap_size, separator_size;
859   gint total, content_pos, flap_pos, separator_pos;
860   gboolean content_above_flap = transition_is_content_above_flap (self);
861 
862   if (!self->flap.widget && !self->content.widget && !self->separator.widget)
863     return;
864 
865   content_alloc->x = 0;
866   content_alloc->y = 0;
867   flap_alloc->x = 0;
868   flap_alloc->y = 0;
869   separator_alloc->x = 0;
870   separator_alloc->y = 0;
871 
872   interpolate_fold (self, alloc, &flap_size, &content_size, &separator_size);
873 
874   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
875     flap_alloc->width = flap_size;
876     content_alloc->width = content_size;
877     separator_alloc->width = separator_size;
878     flap_alloc->height = content_alloc->height = separator_alloc->height = alloc->height;
879     total = alloc->width;
880   } else {
881     flap_alloc->height = flap_size;
882     content_alloc->height = content_size;
883     separator_alloc->height = separator_size;
884     flap_alloc->width = content_alloc->width = separator_alloc->width = alloc->width;
885     total = alloc->height;
886   }
887 
888   if (!self->flap.widget)
889     return;
890 
891   if (content_above_flap)
892     distance = flap_size + separator_size;
893   else
894     distance = flap_size + separator_size * (1 - self->fold_progress);
895 
896   flap_pos = -(gint) round ((1 - self->reveal_progress) * transition_get_flap_motion_factor (self) * distance);
897 
898   if (content_above_flap) {
899     content_pos = (gint) round (self->reveal_progress * transition_get_content_motion_factor (self) * distance);
900     separator_pos = flap_pos + flap_size;
901   } else {
902     content_pos = total - content_size + (gint) round (self->reveal_progress * self->fold_progress * transition_get_content_motion_factor (self) * distance);
903     separator_pos = content_pos - separator_size;
904   }
905 
906   if (self->flap_position != get_start_or_end (self)) {
907     flap_pos = total - flap_pos - flap_size;
908     separator_pos = total - separator_pos - separator_size;
909     content_pos = total - content_pos - content_size;
910   }
911 
912   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
913     content_alloc->x = content_pos;
914     flap_alloc->x = flap_pos;
915     separator_alloc->x = separator_pos;
916   } else {
917     content_alloc->y = content_pos;
918     flap_alloc->y = flap_pos;
919     separator_alloc->y = separator_pos;
920   }
921 }
922 
923 static inline void
allocate_child(HdyFlap * self,ChildInfo * info,gboolean expand_window)924 allocate_child (HdyFlap   *self,
925                 ChildInfo *info,
926                 gboolean   expand_window)
927 {
928   GtkAllocation child_alloc;
929 
930   if (!info->widget)
931     return;
932 
933   if (gtk_widget_get_realized (GTK_WIDGET (self))) {
934     if (expand_window)
935       gdk_window_move_resize (info->window,
936                               0, 0,
937                               gtk_widget_get_allocated_width (GTK_WIDGET (self)),
938                               gtk_widget_get_allocated_height (GTK_WIDGET (self)));
939     else
940       gdk_window_move_resize (info->window,
941                               info->allocation.x,
942                               info->allocation.y,
943                               info->allocation.width,
944                               info->allocation.height);
945   }
946 
947   child_alloc.x = expand_window ? info->allocation.x : 0;
948   child_alloc.y = expand_window ? info->allocation.y : 0;
949   child_alloc.width = info->allocation.width;
950   child_alloc.height = info->allocation.height;
951 
952   gtk_widget_size_allocate (info->widget, &child_alloc);
953 }
954 
955 static void
hdy_flap_size_allocate(GtkWidget * widget,GtkAllocation * alloc)956 hdy_flap_size_allocate (GtkWidget     *widget,
957                         GtkAllocation *alloc)
958 {
959   HdyFlap *self = HDY_FLAP (widget);
960 
961   gtk_widget_set_allocation (widget, alloc);
962 
963   if (gtk_widget_get_realized (widget))
964     gdk_window_move_resize (gtk_widget_get_window (widget),
965                             alloc->x, alloc->y, alloc->width, alloc->height);
966 
967   if (self->fold_policy == HDY_FLAP_FOLD_POLICY_AUTO) {
968     GtkRequisition flap_min = { 0, 0 };
969     GtkRequisition content_min = { 0, 0 };
970     GtkRequisition separator_min = { 0, 0 };
971 
972     if (self->flap.widget)
973       gtk_widget_get_preferred_size (self->flap.widget, &flap_min, NULL);
974     if (self->content.widget)
975       gtk_widget_get_preferred_size (self->content.widget, &content_min, NULL);
976     if (self->separator.widget)
977       gtk_widget_get_preferred_size (self->separator.widget, &separator_min, NULL);
978 
979     if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
980       set_folded (self, alloc->width < content_min.width + flap_min.width + separator_min.width);
981     else
982       set_folded (self, alloc->height < content_min.height + flap_min.height + separator_min.height);
983   }
984 
985   compute_allocation (self,
986                       alloc,
987                       &self->flap.allocation,
988                       &self->content.allocation,
989                       &self->separator.allocation);
990 
991   allocate_child (self, &self->content, FALSE);
992   allocate_child (self, &self->separator, FALSE);
993   allocate_child (self, &self->flap,
994                   self->modal &&
995                   self->reveal_progress > 0 &&
996                   self->fold_progress > 0);
997 
998   gtk_widget_set_clip (widget, alloc);
999   gtk_widget_queue_draw (widget);
1000 }
1001 
1002 /* This private method is prefixed by the call name because it will be a virtual
1003  * method in GTK 4.
1004  */
1005 static void
hdy_flap_measure(GtkWidget * widget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)1006 hdy_flap_measure (GtkWidget      *widget,
1007                   GtkOrientation  orientation,
1008                   gint            for_size,
1009                   gint           *minimum,
1010                   gint           *natural,
1011                   gint           *minimum_baseline,
1012                   gint           *natural_baseline)
1013 {
1014   HdyFlap *self = HDY_FLAP (widget);
1015 
1016   gint content_min = 0, content_nat = 0;
1017   gint flap_min = 0, flap_nat = 0;
1018   gint separator_min = 0, separator_nat = 0;
1019   gint min, nat;
1020 
1021   if (self->content.widget)
1022     get_preferred_size (self->content.widget, orientation, &content_min, &content_nat);
1023 
1024   if (self->flap.widget)
1025     get_preferred_size (self->flap.widget, orientation, &flap_min, &flap_nat);
1026 
1027   if (self->separator.widget)
1028     get_preferred_size (self->separator.widget, orientation, &separator_min, &separator_nat);
1029 
1030   if (self->orientation == orientation) {
1031     gdouble min_progress, nat_progress;
1032 
1033     switch (self->fold_policy) {
1034     case HDY_FLAP_FOLD_POLICY_NEVER:
1035       min_progress = (1 - self->fold_progress) * self->reveal_progress;
1036       nat_progress = 1;
1037       break;
1038 
1039     case HDY_FLAP_FOLD_POLICY_ALWAYS:
1040       min_progress = 0;
1041       nat_progress = 0;
1042       break;
1043 
1044     case HDY_FLAP_FOLD_POLICY_AUTO:
1045       min_progress = 0;
1046       nat_progress = self->locked ? self->reveal_progress : 1;
1047       break;
1048 
1049     default:
1050       g_assert_not_reached ();
1051     }
1052 
1053     min = MAX (content_min + (gint) round ((flap_min + separator_min) * min_progress), flap_min);
1054     nat = MAX (content_nat + (gint) round ((flap_nat + separator_min) * nat_progress), flap_nat);
1055   } else {
1056     min = MAX (MAX (content_min, flap_min), separator_min);
1057     nat = MAX (MAX (content_nat, flap_nat), separator_nat);
1058   }
1059 
1060   if (minimum)
1061     *minimum = min;
1062   if (natural)
1063     *natural = nat;
1064   if (minimum_baseline)
1065     *minimum_baseline = -1;
1066   if (natural_baseline)
1067     *natural_baseline = -1;
1068 }
1069 
1070 static void
hdy_flap_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum,gint * natural)1071 hdy_flap_get_preferred_width_for_height (GtkWidget *widget,
1072                                          gint       height,
1073                                          gint      *minimum,
1074                                          gint      *natural)
1075 {
1076   hdy_flap_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
1077                     minimum, natural, NULL, NULL);
1078 }
1079 
1080 static void
hdy_flap_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)1081 hdy_flap_get_preferred_width (GtkWidget *widget,
1082                               gint      *minimum,
1083                               gint      *natural)
1084 {
1085   hdy_flap_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
1086                     minimum, natural, NULL, NULL);
1087 }
1088 
1089 
1090 static void
hdy_flap_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural)1091 hdy_flap_get_preferred_height_for_width (GtkWidget *widget,
1092                                          gint       width,
1093                                          gint      *minimum,
1094                                          gint      *natural)
1095 {
1096   hdy_flap_measure (widget, GTK_ORIENTATION_VERTICAL, width,
1097                     minimum, natural, NULL, NULL);
1098 }
1099 
1100 static void
hdy_flap_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)1101 hdy_flap_get_preferred_height (GtkWidget *widget,
1102                                gint      *minimum,
1103                                gint      *natural)
1104 {
1105   hdy_flap_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
1106                     minimum, natural, NULL, NULL);
1107 }
1108 
1109 static gboolean
hdy_flap_draw(GtkWidget * widget,cairo_t * cr)1110 hdy_flap_draw (GtkWidget *widget,
1111                cairo_t   *cr)
1112 {
1113   HdyFlap *self = HDY_FLAP (widget);
1114   gint width, height;
1115   gint shadow_x = 0, shadow_y = 0;
1116   gdouble shadow_progress;
1117   GtkPanDirection shadow_direction;
1118   gboolean content_above_flap = transition_is_content_above_flap (self);
1119   GtkAllocation *shadow_alloc;
1120   gboolean should_clip;
1121 
1122   shadow_alloc = content_above_flap ? &self->content.allocation : &self->flap.allocation;
1123 
1124   width = gtk_widget_get_allocated_width (widget);
1125   height = gtk_widget_get_allocated_height (widget);
1126 
1127   if (self->orientation == GTK_ORIENTATION_VERTICAL) {
1128     if ((self->flap_position == GTK_PACK_START) != content_above_flap) {
1129       shadow_direction = GTK_PAN_DIRECTION_UP;
1130       shadow_y = shadow_alloc->y + shadow_alloc->height;
1131     } else {
1132       shadow_direction = GTK_PAN_DIRECTION_DOWN;
1133       shadow_y = shadow_alloc->y - height;
1134     }
1135   } else {
1136     if ((self->flap_position == get_start_or_end (self)) != content_above_flap) {
1137       shadow_direction = GTK_PAN_DIRECTION_LEFT;
1138       shadow_x = shadow_alloc->x + shadow_alloc->width;
1139     } else {
1140       shadow_direction = GTK_PAN_DIRECTION_RIGHT;
1141       shadow_x = shadow_alloc->x - width;
1142     }
1143   }
1144 
1145   switch (self->transition_type) {
1146   case HDY_FLAP_TRANSITION_TYPE_OVER:
1147     shadow_progress = 1 - MIN (self->reveal_progress, self->fold_progress);
1148     break;
1149 
1150   case HDY_FLAP_TRANSITION_TYPE_UNDER:
1151     shadow_progress = self->reveal_progress;
1152     break;
1153 
1154   case HDY_FLAP_TRANSITION_TYPE_SLIDE:
1155     shadow_progress = 1;
1156     break;
1157 
1158   default:
1159     g_assert_not_reached ();
1160   }
1161 
1162   should_clip = transition_should_clip (self) &&
1163                 shadow_progress < 1 &&
1164                 self->reveal_progress > 0;
1165 
1166   if (should_clip) {
1167     cairo_save (cr);
1168     cairo_rectangle (cr, shadow_x, shadow_y, width, height);
1169     cairo_clip (cr);
1170   }
1171 
1172   if (!content_above_flap) {
1173     if (self->content.widget)
1174       gtk_container_propagate_draw (GTK_CONTAINER (self),
1175                                     self->content.widget,
1176                                     cr);
1177 
1178     if (self->separator.widget)
1179       gtk_container_propagate_draw (GTK_CONTAINER (self),
1180                                     self->separator.widget,
1181                                     cr);
1182 
1183     if (should_clip)
1184       cairo_restore (cr);
1185   }
1186 
1187   if (self->flap.widget)
1188     gtk_container_propagate_draw (GTK_CONTAINER (self),
1189                                   self->flap.widget,
1190                                   cr);
1191 
1192   if (content_above_flap) {
1193     if (self->separator.widget)
1194       gtk_container_propagate_draw (GTK_CONTAINER (self),
1195                                     self->separator.widget,
1196                                     cr);
1197 
1198     if (should_clip)
1199       cairo_restore (cr);
1200 
1201     if (self->content.widget)
1202       gtk_container_propagate_draw (GTK_CONTAINER (self),
1203                                     self->content.widget,
1204                                     cr);
1205   }
1206 
1207   if (!self->flap.widget)
1208     return GDK_EVENT_PROPAGATE;
1209 
1210   if (shadow_progress < 1 && gtk_widget_get_mapped (self->flap.widget)) {
1211     cairo_save (cr);
1212     cairo_translate (cr, shadow_x, shadow_y);
1213     hdy_shadow_helper_draw_shadow (self->shadow_helper, cr, width, height,
1214                                    shadow_progress, shadow_direction);
1215     cairo_restore (cr);
1216   }
1217 
1218   return GDK_EVENT_PROPAGATE;
1219 }
1220 
1221 static void
hdy_flap_realize(GtkWidget * widget)1222 hdy_flap_realize (GtkWidget *widget)
1223 {
1224   HdyFlap *self = HDY_FLAP (widget);
1225   GtkAllocation allocation;
1226   GdkWindowAttr attributes;
1227   gint attributes_mask;
1228   GdkWindow *window;
1229 
1230   gtk_widget_get_allocation (widget, &allocation);
1231   gtk_widget_set_realized (widget, TRUE);
1232 
1233   attributes.x = allocation.x;
1234   attributes.y = allocation.y;
1235   attributes.width = allocation.width;
1236   attributes.height = allocation.height;
1237   attributes.window_type = GDK_WINDOW_CHILD;
1238   attributes.event_mask = gtk_widget_get_events (widget);
1239   attributes.visual = gtk_widget_get_visual (widget);
1240   attributes.wclass = GDK_INPUT_OUTPUT;
1241   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
1242 
1243   window = gdk_window_new (gtk_widget_get_parent_window (widget),
1244                            &attributes,
1245                            attributes_mask);
1246   gtk_widget_set_window (widget, window);
1247   gtk_widget_register_window (widget, window);
1248 
1249   register_window (self, &self->content);
1250   register_window (self, &self->separator);
1251   register_window (self, &self->flap);
1252 
1253   update_child_visibility (self);
1254   restack_windows (self);
1255 }
1256 
1257 static void
hdy_flap_unrealize(GtkWidget * widget)1258 hdy_flap_unrealize (GtkWidget *widget)
1259 {
1260   HdyFlap *self = HDY_FLAP (widget);
1261 
1262   unregister_window (self, &self->content);
1263   unregister_window (self, &self->separator);
1264   unregister_window (self, &self->flap);
1265 
1266   GTK_WIDGET_CLASS (hdy_flap_parent_class)->unrealize (widget);
1267 }
1268 
1269 static void
hdy_flap_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)1270 hdy_flap_direction_changed (GtkWidget        *widget,
1271                             GtkTextDirection  previous_direction)
1272 {
1273   HdyFlap *self = HDY_FLAP (widget);
1274 
1275   update_swipe_tracker (self);
1276 
1277   GTK_WIDGET_CLASS (hdy_flap_parent_class)->direction_changed (widget,
1278                                                                previous_direction);
1279 }
1280 
1281 static gboolean
hdy_flap_focus(GtkWidget * widget,GtkDirectionType direction)1282 hdy_flap_focus (GtkWidget        *widget,
1283                 GtkDirectionType  direction)
1284 {
1285   HdyFlap *self = HDY_FLAP (widget);
1286 
1287   if (!gtk_widget_get_can_focus (widget) &&
1288       self->content.widget &&
1289       self->flap.widget &&
1290       self->modal &&
1291       self->reveal_progress > 0 &&
1292       self->fold_progress > 0) {
1293     if (gtk_widget_child_focus (GTK_WIDGET (self->flap.widget), direction))
1294       return GDK_EVENT_STOP;
1295 
1296     if (self->separator.widget)
1297       return gtk_widget_child_focus (GTK_WIDGET (self->separator.widget), direction);
1298 
1299     return GDK_EVENT_PROPAGATE;
1300   }
1301 
1302   return GTK_WIDGET_CLASS (hdy_flap_parent_class)->focus (widget, direction);
1303 }
1304 
1305 static void
hdy_flap_forall(GtkContainer * container,gboolean include_internals,GtkCallback callback,gpointer callback_data)1306 hdy_flap_forall (GtkContainer *container,
1307                  gboolean      include_internals,
1308                  GtkCallback   callback,
1309                  gpointer      callback_data)
1310 {
1311   HdyFlap *self = HDY_FLAP (container);
1312 
1313   if (self->content.widget)
1314     callback (self->content.widget, callback_data);
1315 
1316   if (self->separator.widget)
1317     callback (self->separator.widget, callback_data);
1318 
1319   if (self->flap.widget)
1320     callback (self->flap.widget, callback_data);
1321 }
1322 
1323 static void
hdy_flap_add(GtkContainer * container,GtkWidget * widget)1324 hdy_flap_add (GtkContainer *container,
1325               GtkWidget    *widget)
1326 {
1327   HdyFlap *self = HDY_FLAP (container);
1328 
1329   if (self->content.widget) {
1330     g_warning ("Attempting to add a widget with type %s to a %s, "
1331                "but %s can only contain one widget at a time; "
1332                "it already contains a widget of type %s",
1333                g_type_name (G_OBJECT_TYPE (widget)),
1334                g_type_name (G_OBJECT_TYPE (self)),
1335                g_type_name (G_OBJECT_TYPE (self)),
1336                g_type_name (G_OBJECT_TYPE (self->content.widget)));
1337 
1338     return;
1339   }
1340 
1341   hdy_flap_set_content (self, widget);
1342 }
1343 
1344 static void
hdy_flap_remove(GtkContainer * container,GtkWidget * widget)1345 hdy_flap_remove (GtkContainer *container,
1346                  GtkWidget    *widget)
1347 {
1348   HdyFlap *self = HDY_FLAP (container);
1349 
1350   if (widget == self->flap.widget)
1351     hdy_flap_set_flap (self, NULL);
1352   else if (widget == self->separator.widget)
1353     hdy_flap_set_separator (self, NULL);
1354   else if (widget == self->content.widget)
1355     hdy_flap_set_content (self, NULL);
1356   else
1357     g_return_if_reached ();
1358 }
1359 
1360 static void
hdy_flap_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1361 hdy_flap_get_property (GObject    *object,
1362                        guint       prop_id,
1363                        GValue     *value,
1364                        GParamSpec *pspec)
1365 {
1366   HdyFlap *self = HDY_FLAP (object);
1367 
1368   switch (prop_id) {
1369   case PROP_CONTENT:
1370     g_value_set_object (value, hdy_flap_get_content (self));
1371     break;
1372   case PROP_FLAP:
1373     g_value_set_object (value, hdy_flap_get_flap (self));
1374     break;
1375   case PROP_SEPARATOR:
1376     g_value_set_object (value, hdy_flap_get_separator (self));
1377     break;
1378   case PROP_FLAP_POSITION:
1379     g_value_set_enum (value, hdy_flap_get_flap_position (self));
1380     break;
1381   case PROP_REVEAL_FLAP:
1382     g_value_set_boolean (value, hdy_flap_get_reveal_flap (self));
1383     break;
1384   case PROP_REVEAL_DURATION:
1385     g_value_set_uint (value, hdy_flap_get_reveal_duration (self));
1386     break;
1387   case PROP_REVEAL_PROGRESS:
1388     g_value_set_double (value, hdy_flap_get_reveal_progress (self));
1389     break;
1390   case PROP_FOLD_POLICY:
1391     g_value_set_enum (value, hdy_flap_get_fold_policy (self));
1392     break;
1393   case PROP_FOLD_DURATION:
1394     g_value_set_uint (value, hdy_flap_get_fold_duration (self));
1395     break;
1396   case PROP_FOLDED:
1397     g_value_set_boolean (value, hdy_flap_get_folded (self));
1398     break;
1399   case PROP_LOCKED:
1400     g_value_set_boolean (value, hdy_flap_get_locked (self));
1401     break;
1402   case PROP_TRANSITION_TYPE:
1403     g_value_set_enum (value, hdy_flap_get_transition_type (self));
1404     break;
1405   case PROP_MODAL:
1406     g_value_set_boolean (value, hdy_flap_get_modal (self));
1407     break;
1408   case PROP_SWIPE_TO_OPEN:
1409     g_value_set_boolean (value, hdy_flap_get_swipe_to_open (self));
1410     break;
1411   case PROP_SWIPE_TO_CLOSE:
1412     g_value_set_boolean (value, hdy_flap_get_swipe_to_close (self));
1413     break;
1414   case PROP_ORIENTATION:
1415     g_value_set_enum (value, self->orientation);
1416     break;
1417   default:
1418     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1419   }
1420 }
1421 
1422 static void
hdy_flap_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1423 hdy_flap_set_property (GObject      *object,
1424                        guint         prop_id,
1425                        const GValue *value,
1426                        GParamSpec   *pspec)
1427 {
1428   HdyFlap *self = HDY_FLAP (object);
1429 
1430   switch (prop_id) {
1431   case PROP_CONTENT:
1432     hdy_flap_set_content (self, g_value_get_object (value));
1433     break;
1434   case PROP_FLAP:
1435     hdy_flap_set_flap (self, g_value_get_object (value));
1436     break;
1437   case PROP_SEPARATOR:
1438     hdy_flap_set_separator (self, g_value_get_object (value));
1439     break;
1440   case PROP_FLAP_POSITION:
1441     hdy_flap_set_flap_position (self, g_value_get_enum (value));
1442     break;
1443   case PROP_REVEAL_FLAP:
1444     hdy_flap_set_reveal_flap (self, g_value_get_boolean (value));
1445     break;
1446   case PROP_REVEAL_DURATION:
1447     hdy_flap_set_reveal_duration (self, g_value_get_uint (value));
1448     break;
1449   case PROP_FOLD_POLICY:
1450     hdy_flap_set_fold_policy (self, g_value_get_enum (value));
1451     break;
1452   case PROP_FOLD_DURATION:
1453     hdy_flap_set_fold_duration (self, g_value_get_uint (value));
1454     break;
1455   case PROP_LOCKED:
1456     hdy_flap_set_locked (self, g_value_get_boolean (value));
1457     break;
1458   case PROP_TRANSITION_TYPE:
1459     hdy_flap_set_transition_type (self, g_value_get_enum (value));
1460     break;
1461   case PROP_MODAL:
1462     hdy_flap_set_modal (self, g_value_get_boolean (value));
1463     break;
1464   case PROP_SWIPE_TO_OPEN:
1465     hdy_flap_set_swipe_to_open (self, g_value_get_boolean (value));
1466     break;
1467   case PROP_SWIPE_TO_CLOSE:
1468     hdy_flap_set_swipe_to_close (self, g_value_get_boolean (value));
1469     break;
1470   case PROP_ORIENTATION:
1471     set_orientation (self, g_value_get_enum (value));
1472     break;
1473   default:
1474     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1475   }
1476 }
1477 
1478 static void
hdy_flap_dispose(GObject * object)1479 hdy_flap_dispose (GObject *object)
1480 {
1481   HdyFlap *self = HDY_FLAP (object);
1482 
1483   g_clear_object (&self->shadow_helper);
1484   g_clear_object (&self->tracker);
1485   g_clear_object (&self->click_gesture);
1486   g_clear_object (&self->key_controller);
1487 
1488   G_OBJECT_CLASS (hdy_flap_parent_class)->dispose (object);
1489 }
1490 
1491 static void
hdy_flap_class_init(HdyFlapClass * klass)1492 hdy_flap_class_init (HdyFlapClass *klass)
1493 {
1494   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1495   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1496   GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
1497 
1498   object_class->get_property = hdy_flap_get_property;
1499   object_class->set_property = hdy_flap_set_property;
1500   object_class->dispose = hdy_flap_dispose;
1501 
1502   widget_class->get_preferred_width = hdy_flap_get_preferred_width;
1503   widget_class->get_preferred_width_for_height = hdy_flap_get_preferred_width_for_height;
1504   widget_class->get_preferred_height = hdy_flap_get_preferred_height;
1505   widget_class->get_preferred_height_for_width = hdy_flap_get_preferred_height_for_width;
1506   widget_class->size_allocate = hdy_flap_size_allocate;
1507   widget_class->draw = hdy_flap_draw;
1508   widget_class->realize = hdy_flap_realize;
1509   widget_class->unrealize = hdy_flap_unrealize;
1510   widget_class->direction_changed = hdy_flap_direction_changed;
1511   widget_class->focus = hdy_flap_focus;
1512 
1513   container_class->remove = hdy_flap_remove;
1514   container_class->add = hdy_flap_add;
1515   container_class->forall = hdy_flap_forall;
1516 
1517   /**
1518    * HdyFlap:content:
1519    *
1520    * The content widget, always displayed when unfolded, and partially visible
1521    * when folded.
1522    *
1523    * Since: 1.2
1524    */
1525   props[PROP_CONTENT] =
1526     g_param_spec_object ("content",
1527                          _("Content"),
1528                          _("The content Widget"),
1529                          GTK_TYPE_WIDGET,
1530                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1531 
1532   /**
1533    * HdyFlap:flap:
1534    *
1535    * The flap widget, only visible when #HdyFlap:reveal-progress is greater than
1536    * 0.
1537    *
1538    * Since: 1.2
1539    */
1540   props[PROP_FLAP] =
1541     g_param_spec_object ("flap",
1542                          _("Flap"),
1543                          _("The flap widget"),
1544                          GTK_TYPE_WIDGET,
1545                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1546 
1547   /**
1548    * HdyFlap:separator:
1549    *
1550    * The separator widget, displayed between content and flap when there's no
1551    * shadow to display. When exactly it's visible depends on the
1552    * #HdyFlap:transition-type value. If %NULL, no separator will be used.
1553    *
1554    * Since: 1.2
1555    */
1556   props[PROP_SEPARATOR] =
1557     g_param_spec_object ("separator",
1558                          _("Separator"),
1559                          _("The separator widget"),
1560                          GTK_TYPE_WIDGET,
1561                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1562 
1563   /**
1564    * HdyFlap:flap-position:
1565    *
1566    * The flap position for @self. If @GTK_PACK_START, the flap is displayed
1567    * before the content, if @GTK_PACK_END, it's displayed after the content.
1568    *
1569    * Since: 1.2
1570    */
1571   props[PROP_FLAP_POSITION] =
1572     g_param_spec_enum ("flap-position",
1573                        _("Flap Position"),
1574                        _("The flap position"),
1575                        GTK_TYPE_PACK_TYPE,
1576                        GTK_PACK_START,
1577                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1578 
1579   /**
1580    * HdyFlap:reveal-flap:
1581    *
1582    * Whether the flap widget is revealed.
1583    *
1584    * Since: 1.2
1585    */
1586   props[PROP_REVEAL_FLAP] =
1587     g_param_spec_boolean ("reveal-flap",
1588                           _("Reveal Flap"),
1589                           _("Whether the flap is revealed"),
1590                           TRUE,
1591                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1592 
1593   /**
1594    * HdyFlap:reveal-duration:
1595    *
1596    * The reveal transition animation duration, in milliseconds.
1597    *
1598    * Since: 1.2
1599    */
1600   props[PROP_REVEAL_DURATION] =
1601     g_param_spec_uint ("reveal-duration",
1602                        _("Reveal Duration"),
1603                        _("The reveal transition animation duration, in milliseconds"),
1604                        0, G_MAXINT,
1605                        250,
1606                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1607 
1608   /**
1609    * HdyFlap:reveal-progress:
1610    *
1611    * The current reveal transition progress. 0 means fully hidden, 1 means fully
1612    * revealed See #HdyFlap:reveal-flap.
1613    *
1614    * Since: 1.2
1615    */
1616   props[PROP_REVEAL_PROGRESS] =
1617     g_param_spec_double ("reveal-progress",
1618                           _("Reveal Progress"),
1619                           _("The current reveal transition progress"),
1620                           0.0, 1.0, 1.0,
1621                           G_PARAM_READABLE);
1622 
1623   /**
1624    * HdyFlap:fold-policy:
1625    *
1626    * The current fold policy. See #HdyFlapFoldPolicy for available
1627    * policies.
1628    *
1629    * Since: 1.2
1630    */
1631   props[PROP_FOLD_POLICY] =
1632     g_param_spec_enum ("fold-policy",
1633                        _("Fold Policy"),
1634                        _("The current fold policy"),
1635                        HDY_TYPE_FLAP_FOLD_POLICY,
1636                        HDY_FLAP_FOLD_POLICY_AUTO,
1637                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1638 
1639   /**
1640    * HdyFlap:fold-duration:
1641    *
1642    * The fold transition animation duration, in milliseconds.
1643    *
1644    * Since: 1.2
1645    */
1646   props[PROP_FOLD_DURATION] =
1647     g_param_spec_uint ("fold-duration",
1648                        _("Fold Duration"),
1649                        _("The fold transition animation duration, in milliseconds"),
1650                        0, G_MAXINT,
1651                        250,
1652                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1653 
1654   /**
1655    * HdyFlap:folded:
1656    *
1657    * Whether the flap is currently folded.
1658    *
1659    * See #HdyFlap:fold-policy.
1660    *
1661    * Since: 1.2
1662    */
1663   props[PROP_FOLDED] =
1664     g_param_spec_boolean ("folded",
1665                           _("Folded"),
1666                           _("Whether the flap is currently folded"),
1667                           FALSE,
1668                           G_PARAM_READABLE);
1669 
1670   /**
1671    * HdyFlap:locked:
1672    *
1673    * Whether the flap is locked.
1674    *
1675    * If %FALSE, folding when the flap is revealed automatically closes it, and
1676    * unfolding it when the flap is not revealed opens it. If %TRUE,
1677    * #HdyFlap:reveal-flap value never changes on its own.
1678    *
1679    * Since: 1.2
1680    */
1681   props[PROP_LOCKED] =
1682     g_param_spec_boolean ("locked",
1683                           _("Locked"),
1684                           _("Whether the flap is locked"),
1685                           FALSE,
1686                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1687 
1688   /**
1689    * HdyFlap:transition-type:
1690    *
1691    * The type of animation that will be used for reveal and fold transitions
1692    * in @self.
1693    *
1694    * #HdyFlap:flap is transparent by default, which means the content will be
1695    * seen through it with %HDY_FLAP_TRANSITION_TYPE_OVER transitions; add the
1696    * .background style class to it if this is unwanted.
1697    *
1698    * Since: 1.2
1699    */
1700   props[PROP_TRANSITION_TYPE] =
1701     g_param_spec_enum ("transition-type",
1702                        _("Transition Type"),
1703                        _("The type of animation used for reveal and fold transitions"),
1704                        HDY_TYPE_FLAP_TRANSITION_TYPE,
1705                        HDY_FLAP_TRANSITION_TYPE_OVER,
1706                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1707 
1708   /**
1709    * HdyFlap:modal:
1710    *
1711    * Whether the flap is modal.
1712    *
1713    * If %TRUE, clicking the content widget while flap is revealed, as well as
1714    * pressing Escape key, will close the flap. If %FALSE, clicks are passed
1715    * through to the content widget.
1716    *
1717    * Since: 1.2
1718    */
1719   props[PROP_MODAL] =
1720     g_param_spec_boolean ("modal",
1721                           _("Modal"),
1722                           _("Whether the flap is modal"),
1723                           TRUE,
1724                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1725 
1726   /**
1727    * HdyFlap:swipe-to-open:
1728    *
1729    * Whether the flap can be opened with a swipe gesture.
1730    *
1731    * The area that can be swiped depends on the #HdyFlap:transition-type value.
1732    *
1733    * Since: 1.2
1734    */
1735   props[PROP_SWIPE_TO_OPEN] =
1736     g_param_spec_boolean ("swipe-to-open",
1737                           _("Swipe to Open"),
1738                           _("Whether the flap can be opened with a swipe gesture"),
1739                           TRUE,
1740                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1741 
1742   /**
1743    * HdyFlap:swipe-to-close:
1744    *
1745    * Whether the flap can be closed with a swipe gesture.
1746    *
1747    * The area that can be swiped depends on the #HdyFlap:transition-type value.
1748    *
1749    * Since: 1.2
1750    */
1751   props[PROP_SWIPE_TO_CLOSE] =
1752     g_param_spec_boolean ("swipe-to-close",
1753                           _("Swipe to Close"),
1754                           _("Whether the flap can be closed with a swipe gesture"),
1755                           TRUE,
1756                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
1757 
1758   g_object_class_install_properties (object_class, LAST_PROP, props);
1759 
1760   g_object_class_override_property (object_class,
1761                                     PROP_ORIENTATION,
1762                                     "orientation");
1763 
1764   gtk_widget_class_set_css_name (widget_class, "flap");
1765 }
1766 
1767 static void
hdy_flap_init(HdyFlap * self)1768 hdy_flap_init (HdyFlap *self)
1769 {
1770   GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
1771 
1772   gtk_widget_add_events (GTK_WIDGET (self), GDK_KEY_PRESS_MASK);
1773 
1774   self->orientation = GTK_ORIENTATION_HORIZONTAL;
1775   self->flap_position = GTK_PACK_START;
1776   self->fold_policy = HDY_FLAP_FOLD_POLICY_AUTO;
1777   self->transition_type = HDY_FLAP_TRANSITION_TYPE_OVER;
1778   self->reveal_flap = TRUE;
1779   self->locked = FALSE;
1780   self->reveal_progress = 1;
1781   self->folded = FALSE;
1782   self->fold_progress = 0;
1783   self->fold_duration = 250;
1784   self->reveal_duration = 250;
1785   self->modal = TRUE;
1786   self->swipe_to_open = TRUE;
1787   self->swipe_to_close = TRUE;
1788 
1789   self->shadow_helper = hdy_shadow_helper_new (GTK_WIDGET (self));
1790   self->tracker = hdy_swipe_tracker_new (HDY_SWIPEABLE (self));
1791   hdy_swipe_tracker_set_enabled (self->tracker, FALSE);
1792 
1793   g_signal_connect_object (self->tracker, "begin-swipe", G_CALLBACK (begin_swipe_cb), self, 0);
1794   g_signal_connect_object (self->tracker, "update-swipe", G_CALLBACK (update_swipe_cb), self, 0);
1795   g_signal_connect_object (self->tracker, "end-swipe", G_CALLBACK (end_swipe_cb), self, 0);
1796 
1797   update_swipe_tracker (self);
1798 
1799   self->click_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self));
1800   gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (self->click_gesture), TRUE);
1801   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (self->click_gesture), GDK_BUTTON_PRIMARY);
1802   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
1803                                               GTK_PHASE_CAPTURE);
1804   g_signal_connect_object (self->click_gesture, "released", G_CALLBACK (released_cb), self, 0);
1805 
1806   self->key_controller = gtk_event_controller_key_new (GTK_WIDGET (self));
1807   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->key_controller),
1808                                               GTK_PHASE_BUBBLE);
1809   g_signal_connect_object (self->key_controller, "key-pressed", G_CALLBACK (key_pressed_cb), self, 0);
1810 
1811   gtk_style_context_add_class (context, "unfolded");
1812 }
1813 
1814 static void
hdy_flap_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const gchar * type)1815 hdy_flap_add_child (GtkBuildable *buildable,
1816                     GtkBuilder   *builder,
1817                     GObject      *child,
1818                     const gchar  *type)
1819 {
1820   if (!type || !g_strcmp0 (type, "content"))
1821     hdy_flap_set_content (HDY_FLAP (buildable), GTK_WIDGET (child));
1822   else if (!g_strcmp0 (type, "flap"))
1823     hdy_flap_set_flap (HDY_FLAP (buildable), GTK_WIDGET (child));
1824   else if (!g_strcmp0 (type, "separator"))
1825     hdy_flap_set_separator (HDY_FLAP (buildable), GTK_WIDGET (child));
1826   else
1827     GTK_BUILDER_WARN_INVALID_CHILD_TYPE (HDY_FLAP (buildable), type);
1828 }
1829 
1830 static void
hdy_flap_buildable_init(GtkBuildableIface * iface)1831 hdy_flap_buildable_init (GtkBuildableIface *iface)
1832 {
1833   iface->add_child = hdy_flap_add_child;
1834 }
1835 
1836 static void
hdy_flap_switch_child(HdySwipeable * swipeable,guint index,gint64 duration)1837 hdy_flap_switch_child (HdySwipeable *swipeable,
1838                        guint         index,
1839                        gint64        duration)
1840 {
1841   HdyFlap *self = HDY_FLAP (swipeable);
1842 
1843   set_reveal_flap (self, index > 0, duration, FALSE);
1844 }
1845 
1846 static HdySwipeTracker *
hdy_flap_get_swipe_tracker(HdySwipeable * swipeable)1847 hdy_flap_get_swipe_tracker (HdySwipeable *swipeable)
1848 {
1849   HdyFlap *self = HDY_FLAP (swipeable);
1850 
1851   return self->tracker;
1852 }
1853 
1854 static gdouble
hdy_flap_get_distance(HdySwipeable * swipeable)1855 hdy_flap_get_distance (HdySwipeable *swipeable)
1856 {
1857   HdyFlap *self = HDY_FLAP (swipeable);
1858   gint flap, separator;
1859 
1860   if (!self->flap.widget)
1861     return 0;
1862 
1863   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
1864     flap = self->flap.allocation.width;
1865     separator = self->separator.allocation.width;
1866   } else {
1867     flap = self->flap.allocation.height;
1868     separator = self->separator.allocation.height;
1869   }
1870 
1871   if (transition_is_content_above_flap (self))
1872     return flap + separator;
1873 
1874   return flap + separator * (1 - self->fold_progress);
1875 }
1876 
1877 static gdouble *
hdy_flap_get_snap_points(HdySwipeable * swipeable,gint * n_snap_points)1878 hdy_flap_get_snap_points (HdySwipeable *swipeable,
1879                           gint         *n_snap_points)
1880 {
1881   HdyFlap *self = HDY_FLAP (swipeable);
1882   gboolean can_open = self->reveal_progress > 0 || self->swipe_to_open || self->swipe_active;
1883   gboolean can_close = self->reveal_progress < 1 || self->swipe_to_close || self->swipe_active;
1884   gdouble *points;
1885 
1886   if (!can_open && !can_close)
1887     return NULL;
1888 
1889   if (can_open && can_close) {
1890     points = g_new0 (gdouble, 2);
1891 
1892     if (n_snap_points)
1893       *n_snap_points = 2;
1894 
1895     points[0] = 0;
1896     points[1] = 1;
1897 
1898     return points;
1899   }
1900 
1901   points = g_new0 (gdouble, 1);
1902 
1903   if (n_snap_points)
1904     *n_snap_points = 1;
1905 
1906   points[0] = can_open ? 1 : 0;
1907 
1908   return points;
1909 }
1910 
1911 static gdouble
hdy_flap_get_progress(HdySwipeable * swipeable)1912 hdy_flap_get_progress (HdySwipeable *swipeable)
1913 {
1914   HdyFlap *self = HDY_FLAP (swipeable);
1915 
1916   return self->reveal_progress;
1917 }
1918 
1919 static gdouble
hdy_flap_get_cancel_progress(HdySwipeable * swipeable)1920 hdy_flap_get_cancel_progress (HdySwipeable *swipeable)
1921 {
1922   HdyFlap *self = HDY_FLAP (swipeable);
1923 
1924   return round (self->reveal_progress);
1925 }
1926 
1927 static void
hdy_flap_get_swipe_area(HdySwipeable * swipeable,HdyNavigationDirection navigation_direction,gboolean is_drag,GdkRectangle * rect)1928 hdy_flap_get_swipe_area (HdySwipeable           *swipeable,
1929                          HdyNavigationDirection  navigation_direction,
1930                          gboolean                is_drag,
1931                          GdkRectangle           *rect)
1932 {
1933   HdyFlap *self = HDY_FLAP (swipeable);
1934   GtkAllocation *alloc;
1935   gint width, height;
1936   gdouble flap_factor, content_factor;
1937   gboolean content_above_flap;
1938 
1939   if (!self->flap.widget) {
1940     rect->x = 0;
1941     rect->y = 0;
1942     rect->width = 0;
1943     rect->height = 0;
1944 
1945     return;
1946   }
1947 
1948   width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
1949   height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
1950 
1951   content_above_flap = transition_is_content_above_flap (self);
1952   flap_factor = transition_get_flap_motion_factor (self);
1953   content_factor = transition_get_content_motion_factor (self);
1954 
1955   if (!is_drag ||
1956       (flap_factor >= 1 && content_factor >= 1) ||
1957       (self->fold_progress < 1 && flap_factor > 0)) {
1958     rect->x = 0;
1959     rect->y = 0;
1960     rect->width = width;
1961     rect->height = height;
1962 
1963     return;
1964   }
1965 
1966   alloc = content_above_flap
1967     ? &self->content.allocation
1968     : &self->flap.allocation;
1969 
1970   if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
1971     if (alloc->x <= 0) {
1972       rect->x = 0;
1973       rect->width = MAX (alloc->width + alloc->x, HDY_SWIPE_BORDER);
1974     } else if (alloc->x + alloc->width >= width) {
1975       rect->width = MAX (width - alloc->x, HDY_SWIPE_BORDER);
1976       rect->x = width - rect->width;
1977     } else {
1978       g_assert_not_reached ();
1979     }
1980 
1981     rect->y = alloc->y;
1982     rect->height = alloc->height;
1983   } else {
1984     if (alloc->y <= 0) {
1985       rect->y = 0;
1986       rect->height = MAX (alloc->height + alloc->y, HDY_SWIPE_BORDER);
1987     } else if (alloc->y + alloc->height >= height) {
1988       rect->height = MAX (height - alloc->y, HDY_SWIPE_BORDER);
1989       rect->y = height - rect->height;
1990     } else {
1991       g_assert_not_reached ();
1992     }
1993 
1994     rect->x = alloc->x;
1995     rect->width = alloc->width;
1996   }
1997 }
1998 
1999 static void
hdy_flap_swipeable_init(HdySwipeableInterface * iface)2000 hdy_flap_swipeable_init (HdySwipeableInterface *iface)
2001 {
2002   iface->switch_child = hdy_flap_switch_child;
2003   iface->get_swipe_tracker = hdy_flap_get_swipe_tracker;
2004   iface->get_distance = hdy_flap_get_distance;
2005   iface->get_snap_points = hdy_flap_get_snap_points;
2006   iface->get_progress = hdy_flap_get_progress;
2007   iface->get_cancel_progress = hdy_flap_get_cancel_progress;
2008   iface->get_swipe_area = hdy_flap_get_swipe_area;
2009 }
2010 
2011 /**
2012  * hdy_flap_new:
2013  *
2014  * Creates a new #HdyFlap.
2015  *
2016  * Returns: a new #HdyFlap
2017  *
2018  * Since: 1.2
2019  */
2020 GtkWidget *
hdy_flap_new(void)2021 hdy_flap_new (void)
2022 {
2023   return g_object_new (HDY_TYPE_FLAP, NULL);
2024 }
2025 
2026 /**
2027  * hdy_flap_get_content:
2028  * @self: a #HdyFlap
2029  *
2030  * Gets the content widget for @self
2031  *
2032  * Returns: (transfer none) (nullable): the content widget for @self
2033  *
2034  * Since: 1.2
2035  */
2036 GtkWidget *
hdy_flap_get_content(HdyFlap * self)2037 hdy_flap_get_content (HdyFlap *self)
2038 {
2039   g_return_val_if_fail (HDY_IS_FLAP (self), NULL);
2040 
2041   return self->content.widget;
2042 }
2043 
2044 /**
2045  * hdy_flap_set_content:
2046  * @self: a #HdyFlap
2047  * @content: (nullable): the content widget, or %NULL
2048  *
2049  * Sets the content widget for @self, always displayed when unfolded, and
2050  * partially visible when folded.
2051  *
2052  * Since: 1.2
2053  */
2054 void
hdy_flap_set_content(HdyFlap * self,GtkWidget * content)2055 hdy_flap_set_content (HdyFlap   *self,
2056                       GtkWidget *content)
2057 {
2058   g_return_if_fail (HDY_IS_FLAP (self));
2059   g_return_if_fail (GTK_IS_WIDGET (content) || content == NULL);
2060 
2061   if (self->content.widget == content)
2062     return;
2063 
2064   if (self->content.widget)
2065     remove_child (self, &self->content);
2066 
2067   self->content.widget = content;
2068 
2069   if (self->content.widget)
2070     add_child (self, &self->content);
2071 
2072   update_child_visibility (self);
2073 
2074   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CONTENT]);
2075 }
2076 
2077 /**
2078  * hdy_flap_get_flap:
2079  * @self: a #HdyFlap
2080  *
2081  * Gets the flap widget for @self
2082  *
2083  * Returns: (transfer none) (nullable): the flap widget for @self
2084  *
2085  * Since: 1.2
2086  */
2087 GtkWidget *
hdy_flap_get_flap(HdyFlap * self)2088 hdy_flap_get_flap (HdyFlap *self)
2089 {
2090   g_return_val_if_fail (HDY_IS_FLAP (self), NULL);
2091 
2092   return self->flap.widget;
2093 }
2094 
2095 /**
2096  * hdy_flap_set_flap:
2097  * @self: a #HdyFlap
2098  * @flap: (nullable): the flap widget, or %NULL
2099  *
2100  * Sets the flap widget for @self, only visible when #HdyFlap:reveal-progress is
2101  * greater than 0.
2102  *
2103  * Since: 1.2
2104  */
2105 void
hdy_flap_set_flap(HdyFlap * self,GtkWidget * flap)2106 hdy_flap_set_flap (HdyFlap   *self,
2107                    GtkWidget *flap)
2108 {
2109   g_return_if_fail (HDY_IS_FLAP (self));
2110   g_return_if_fail (GTK_IS_WIDGET (flap) || flap == NULL);
2111 
2112   if (self->flap.widget == flap)
2113     return;
2114 
2115   if (self->flap.widget)
2116     remove_child (self, &self->flap);
2117 
2118   self->flap.widget = flap;
2119 
2120   if (self->flap.widget)
2121     add_child (self, &self->flap);
2122 
2123   update_swipe_tracker (self);
2124   update_child_visibility (self);
2125 
2126   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FLAP]);
2127 }
2128 
2129 /**
2130  * hdy_flap_get_separator:
2131  * @self: a #HdyFlap
2132  *
2133  * Gets the separator widget for @self.
2134  *
2135  * Returns: (transfer none) (nullable): the separator widget for @self
2136  *
2137  * Since: 1.2
2138  */
2139 GtkWidget *
hdy_flap_get_separator(HdyFlap * self)2140 hdy_flap_get_separator (HdyFlap *self)
2141 {
2142   g_return_val_if_fail (HDY_IS_FLAP (self), NULL);
2143 
2144   return self->separator.widget;
2145 }
2146 
2147 /**
2148  * hdy_flap_set_separator:
2149  * @self: a #HdyFlap
2150  * @separator: (nullable): the separator widget, or %NULL
2151  *
2152  * Sets the separator widget for @self, displayed between content and flap when
2153  * there's no shadow to display. When exactly it's visible depends on the
2154  * #HdyFlap:transition-type value. If %NULL, no separator will be used.
2155  *
2156  * Since: 1.2
2157  */
2158 void
hdy_flap_set_separator(HdyFlap * self,GtkWidget * separator)2159 hdy_flap_set_separator (HdyFlap   *self,
2160                         GtkWidget *separator)
2161 {
2162   g_return_if_fail (HDY_IS_FLAP (self));
2163   g_return_if_fail (GTK_IS_WIDGET (separator) || separator == NULL);
2164 
2165   if (self->separator.widget == separator)
2166     return;
2167 
2168   if (self->separator.widget)
2169     remove_child (self, &self->separator);
2170 
2171   self->separator.widget = separator;
2172 
2173   if (self->separator.widget)
2174     add_child (self, &self->separator);
2175 
2176   update_child_visibility (self);
2177 
2178   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SEPARATOR]);
2179 }
2180 
2181 /**
2182  * hdy_flap_get_flap_position:
2183  * @self: a #HdyFlap
2184  *
2185  * Gets the flap position for @self.
2186  *
2187  * Returns: the flap position for @self
2188  *
2189  * Since: 1.2
2190  */
2191 GtkPackType
hdy_flap_get_flap_position(HdyFlap * self)2192 hdy_flap_get_flap_position (HdyFlap *self)
2193 {
2194   g_return_val_if_fail (HDY_IS_FLAP (self), GTK_PACK_START);
2195 
2196   return self->flap_position;
2197 }
2198 
2199 /**
2200  * hdy_flap_set_flap_position:
2201  * @self: a #HdyFlap
2202  * @position: the new value
2203  *
2204  * Sets the flap position for @self. If @GTK_PACK_START, the flap is displayed
2205  * before the content, if @GTK_PACK_END, it's displayed after the content.
2206  *
2207  * Since: 1.2
2208  */
2209 void
hdy_flap_set_flap_position(HdyFlap * self,GtkPackType position)2210 hdy_flap_set_flap_position (HdyFlap     *self,
2211                             GtkPackType  position)
2212 {
2213   g_return_if_fail (HDY_IS_FLAP (self));
2214   g_return_if_fail (position <= GTK_PACK_END);
2215 
2216   if (self->flap_position == position)
2217     return;
2218 
2219   self->flap_position = position;
2220 
2221   gtk_widget_queue_allocate (GTK_WIDGET (self));
2222   hdy_shadow_helper_clear_cache (self->shadow_helper);
2223   update_swipe_tracker (self);
2224 
2225   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FLAP_POSITION]);
2226 }
2227 
2228 /**
2229  * hdy_flap_get_reveal_flap:
2230  * @self: a #HdyFlap
2231  *
2232  * Gets whether the flap widget is revealed for @self.
2233  *
2234  * Returns: %TRUE if the flap widget is revealed, %FALSE otherwise.
2235  *
2236  * Since: 1.2
2237  */
2238 gboolean
hdy_flap_get_reveal_flap(HdyFlap * self)2239 hdy_flap_get_reveal_flap (HdyFlap *self)
2240 {
2241   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2242 
2243   return self->reveal_flap;
2244 }
2245 
2246 /**
2247  * hdy_flap_set_reveal_flap:
2248  * @self: a #HdyFlap
2249  * @reveal_flap: %TRUE to reveal the flap widget, %FALSE otherwise
2250  *
2251  * Sets whether the flap widget is revealed for @self.
2252  *
2253  * Since: 1.2
2254  */
2255 void
hdy_flap_set_reveal_flap(HdyFlap * self,gboolean reveal_flap)2256 hdy_flap_set_reveal_flap (HdyFlap  *self,
2257                           gboolean  reveal_flap)
2258 {
2259   g_return_if_fail (HDY_IS_FLAP (self));
2260 
2261   set_reveal_flap (self, reveal_flap, self->reveal_duration, TRUE);
2262 }
2263 
2264 /**
2265  * hdy_flap_get_reveal_duration:
2266  * @self: a #HdyFlap
2267  *
2268  * Returns the amount of time (in milliseconds) that reveal transitions in @self
2269  * will take.
2270  *
2271  * Returns: the reveal transition duration
2272  *
2273  * Since: 1.2
2274  */
2275 guint
hdy_flap_get_reveal_duration(HdyFlap * self)2276 hdy_flap_get_reveal_duration (HdyFlap *self)
2277 {
2278   g_return_val_if_fail (HDY_IS_FLAP (self), 0);
2279 
2280   return self->reveal_duration;
2281 }
2282 
2283 /**
2284  * hdy_flap_set_reveal_duration:
2285  * @self: a #HdyFlap
2286  * @duration: the new duration, in milliseconds
2287  *
2288  * Sets the duration that reveal transitions in @self will take.
2289  *
2290  * Since: 1.2
2291  */
2292 void
hdy_flap_set_reveal_duration(HdyFlap * self,guint duration)2293 hdy_flap_set_reveal_duration (HdyFlap *self,
2294                               guint    duration)
2295 {
2296   g_return_if_fail (HDY_IS_FLAP (self));
2297 
2298   if (self->reveal_duration == duration)
2299     return;
2300 
2301   self->reveal_duration = duration;
2302 
2303   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEAL_DURATION]);
2304 }
2305 
2306 /**
2307  * hdy_flap_get_reveal_progress:
2308  * @self: a #HdyFlap
2309  *
2310  * Gets the current reveal transition progress for @self. 0 means fully hidden,
2311  * 1 means fully revealed. See #HdyFlap:reveal-flap.
2312  *
2313  * Returns: the current reveal progress for @self
2314  *
2315  * Since: 1.2
2316  */
2317 gdouble
hdy_flap_get_reveal_progress(HdyFlap * self)2318 hdy_flap_get_reveal_progress (HdyFlap *self)
2319 {
2320   g_return_val_if_fail (HDY_IS_FLAP (self), 0.0);
2321 
2322   return self->reveal_progress;
2323 }
2324 
2325 /**
2326  * hdy_flap_get_fold_policy:
2327  * @self: a #HdyFlap
2328  *
2329  * Gets the current fold policy of @self. See hdy_flap_set_fold_policy().
2330  *
2331  * Returns: the current fold policy of @self
2332  *
2333  * Since: 1.2
2334  */
2335 HdyFlapFoldPolicy
hdy_flap_get_fold_policy(HdyFlap * self)2336 hdy_flap_get_fold_policy (HdyFlap *self)
2337 {
2338   g_return_val_if_fail (HDY_IS_FLAP (self), HDY_FLAP_FOLD_POLICY_NEVER);
2339 
2340   return self->fold_policy;
2341 }
2342 
2343 /**
2344  * hdy_flap_set_fold_policy:
2345  * @self: a #HdyFlap
2346  * @policy: Fold policy
2347  *
2348  * Sets the current fold policy for @self. See #HdyFlapFoldPolicy for available
2349  * policies.
2350  *
2351  * Since: 1.2
2352  */
2353 void
hdy_flap_set_fold_policy(HdyFlap * self,HdyFlapFoldPolicy policy)2354 hdy_flap_set_fold_policy (HdyFlap           *self,
2355                           HdyFlapFoldPolicy  policy)
2356 {
2357   g_return_if_fail (HDY_IS_FLAP (self));
2358   g_return_if_fail (policy <= HDY_FLAP_FOLD_POLICY_AUTO);
2359 
2360   if (self->fold_policy == policy)
2361     return;
2362 
2363   self->fold_policy = policy;
2364 
2365   switch (self->fold_policy) {
2366   case HDY_FLAP_FOLD_POLICY_NEVER:
2367     set_folded (self, FALSE);
2368     break;
2369 
2370   case HDY_FLAP_FOLD_POLICY_ALWAYS:
2371     set_folded (self, TRUE);
2372     break;
2373 
2374   case HDY_FLAP_FOLD_POLICY_AUTO:
2375     gtk_widget_queue_allocate (GTK_WIDGET (self));
2376     break;
2377 
2378   default:
2379     g_assert_not_reached ();
2380   }
2381 
2382   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLD_POLICY]);
2383 }
2384 
2385 /**
2386  * hdy_flap_get_fold_duration:
2387  * @self: a #HdyFlap
2388  *
2389  * Returns the amount of time (in milliseconds) that fold transitions in @self
2390  * will take.
2391  *
2392  * Returns: the fold transition duration
2393  *
2394  * Since: 1.2
2395  */
2396 guint
hdy_flap_get_fold_duration(HdyFlap * self)2397 hdy_flap_get_fold_duration (HdyFlap *self)
2398 {
2399   g_return_val_if_fail (HDY_IS_FLAP (self), 0);
2400 
2401   return self->fold_duration;
2402 }
2403 
2404 /**
2405  * hdy_flap_set_fold_duration:
2406  * @self: a #HdyFlap
2407  * @duration: the new duration, in milliseconds
2408  *
2409  * Sets the duration that fold transitions in @self will take.
2410  *
2411  * Since: 1.2
2412  */
2413 void
hdy_flap_set_fold_duration(HdyFlap * self,guint duration)2414 hdy_flap_set_fold_duration (HdyFlap *self,
2415                             guint    duration)
2416 {
2417   g_return_if_fail (HDY_IS_FLAP (self));
2418 
2419   if (self->fold_duration == duration)
2420     return;
2421 
2422   self->fold_duration = duration;
2423 
2424   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FOLD_DURATION]);
2425 }
2426 
2427 /**
2428  * hdy_flap_get_folded:
2429  * @self: a #HdyFlap
2430  *
2431  * Gets whether @self is currently folded.
2432  *
2433  * See #HdyFlap:fold-policy.
2434  *
2435  * Returns: %TRUE if @self is currently folded, %FALSE otherwise
2436  *
2437  * Since: 1.2
2438  */
2439 gboolean
hdy_flap_get_folded(HdyFlap * self)2440 hdy_flap_get_folded (HdyFlap *self)
2441 {
2442   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2443 
2444   return self->folded;
2445 }
2446 
2447 /**
2448  * hdy_flap_get_locked:
2449  * @self: a #HdyFlap
2450  *
2451  * Gets whether @self is locked.
2452  *
2453  * Returns: %TRUE if @self is locked, %FALSE otherwise
2454  *
2455  * Since: 1.2
2456  */
2457 gboolean
hdy_flap_get_locked(HdyFlap * self)2458 hdy_flap_get_locked (HdyFlap *self)
2459 {
2460   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2461 
2462   return self->locked;
2463 }
2464 
2465 /**
2466  * hdy_flap_set_locked:
2467  * @self: a #HdyFlap
2468  * @locked: the new value
2469  *
2470  * Sets whether @self is locked.
2471  *
2472  * If %FALSE, folding @self when the flap is revealed automatically closes it,
2473  * and unfolding it when the flap is not revealed opens it. If %TRUE,
2474  * #HdyFlap:reveal-flap value never changes on its own.
2475  *
2476  * Since: 1.2
2477  */
2478 void
hdy_flap_set_locked(HdyFlap * self,gboolean locked)2479 hdy_flap_set_locked (HdyFlap  *self,
2480                      gboolean  locked)
2481 {
2482   g_return_if_fail (HDY_IS_FLAP (self));
2483 
2484   locked = !!locked;
2485 
2486   if (self->locked == locked)
2487     return;
2488 
2489   self->locked = locked;
2490 
2491   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOCKED]);
2492 }
2493 
2494 /**
2495  * hdy_flap_get_transition_type:
2496  * @self: a #HdyFlap
2497  *
2498  * Gets the type of animation that will be used for reveal and fold transitions
2499  * in @self.
2500  *
2501  * Returns: the current transition type of @self
2502  *
2503  * Since: 1.2
2504  */
2505 HdyFlapTransitionType
hdy_flap_get_transition_type(HdyFlap * self)2506 hdy_flap_get_transition_type (HdyFlap *self)
2507 {
2508   g_return_val_if_fail (HDY_IS_FLAP (self), HDY_FLAP_TRANSITION_TYPE_OVER);
2509 
2510   return self->transition_type;
2511 }
2512 
2513 /**
2514  * hdy_flap_set_transition_type:
2515  * @self: a #HdyFlap
2516  * @transition_type: the new transition type
2517  *
2518  * Sets the type of animation that will be used for reveal and fold transitions
2519  * in @self.
2520  *
2521  * #HdyFlap:flap is transparent by default, which means the content will be seen
2522  * through it with %HDY_FLAP_TRANSITION_TYPE_OVER transitions; add the
2523  * .background style class to it if this is unwanted.
2524  *
2525  * Since: 1.2
2526  */
2527 void
hdy_flap_set_transition_type(HdyFlap * self,HdyFlapTransitionType transition_type)2528 hdy_flap_set_transition_type (HdyFlap               *self,
2529                               HdyFlapTransitionType  transition_type)
2530 {
2531   g_return_if_fail (HDY_IS_FLAP (self));
2532   g_return_if_fail (transition_type <= HDY_FLAP_TRANSITION_TYPE_SLIDE);
2533 
2534   if (self->transition_type == transition_type)
2535     return;
2536 
2537   self->transition_type = transition_type;
2538 
2539   restack_windows (self);
2540 
2541   if (self->reveal_progress > 0 || (self->fold_progress > 0 && self->fold_progress < 1))
2542     gtk_widget_queue_allocate (GTK_WIDGET (self));
2543 
2544   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSITION_TYPE]);
2545 }
2546 
2547 /**
2548  * hdy_flap_get_modal:
2549  * @self: a #HdyFlap
2550  *
2551  * Gets whether the @self is modal. See hdy_flap_set_modal().
2552  *
2553  * Returns: %TRUE if @self is modal
2554  *
2555  * Since: 1.2
2556  */
2557 gboolean
hdy_flap_get_modal(HdyFlap * self)2558 hdy_flap_get_modal (HdyFlap *self)
2559 {
2560   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2561 
2562   return self->modal;
2563 }
2564 
2565 /**
2566  * hdy_flap_set_modal:
2567  * @self: a #HdyFlap
2568  * @modal: Whether @self can be closed with a click
2569  *
2570  * Sets whether the @self can be closed with a click.
2571  *
2572  * If @modal is %TRUE, clicking the content widget while flap is revealed, or
2573  * pressing Escape key, will close the flap. If %FALSE, clicks are passed
2574  * through to the content widget.
2575  *
2576  * Since: 1.2
2577  */
2578 void
hdy_flap_set_modal(HdyFlap * self,gboolean modal)2579 hdy_flap_set_modal (HdyFlap  *self,
2580                     gboolean  modal)
2581 {
2582   g_return_if_fail (HDY_IS_FLAP (self));
2583 
2584   modal = !!modal;
2585 
2586   if (self->modal == modal)
2587     return;
2588 
2589   self->modal = modal;
2590 
2591   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (self->click_gesture),
2592                                               modal ? GTK_PHASE_CAPTURE : GTK_PHASE_NONE);
2593   gtk_event_controller_set_propagation_phase (self->key_controller,
2594                                               modal ? GTK_PHASE_BUBBLE : GTK_PHASE_NONE);
2595 
2596   gtk_widget_queue_allocate (GTK_WIDGET (self));
2597 
2598   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_MODAL]);
2599 }
2600 
2601 /**
2602  * hdy_flap_get_swipe_to_open:
2603  * @self: a #HdyFlap
2604  *
2605  * Gets whether @self can be opened with a swipe gesture.
2606  *
2607  * Returns: %TRUE if @self can be opened with a swipe gesture
2608  *
2609  * Since: 1.2
2610  */
2611 gboolean
hdy_flap_get_swipe_to_open(HdyFlap * self)2612 hdy_flap_get_swipe_to_open (HdyFlap *self)
2613 {
2614   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2615 
2616   return self->swipe_to_open;
2617 }
2618 
2619 /**
2620  * hdy_flap_set_swipe_to_open:
2621  * @self: a #HdyFlap
2622  * @swipe_to_open: Whether @self can be opened with a swipe gesture
2623  *
2624  * Sets whether @self can be opened with a swipe gesture.
2625  *
2626  * The area that can be swiped depends on the #HdyFlap:transition-type value.
2627  *
2628  * Since: 1.2
2629  */
2630 void
hdy_flap_set_swipe_to_open(HdyFlap * self,gboolean swipe_to_open)2631 hdy_flap_set_swipe_to_open (HdyFlap  *self,
2632                             gboolean  swipe_to_open)
2633 {
2634   g_return_if_fail (HDY_IS_FLAP (self));
2635 
2636   swipe_to_open = !!swipe_to_open;
2637 
2638   if (self->swipe_to_open == swipe_to_open)
2639     return;
2640 
2641   self->swipe_to_open = swipe_to_open;
2642 
2643   update_swipe_tracker (self);
2644 
2645   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SWIPE_TO_OPEN]);
2646 }
2647 
2648 /**
2649  * hdy_flap_get_swipe_to_close:
2650  * @self: a #HdyFlap
2651  *
2652  * Gets whether @self can be closed with a swipe gesture.
2653  *
2654  * Returns: %TRUE if @self can be closed with a swipe gesture
2655  *
2656  * Since: 1.2
2657  */
2658 gboolean
hdy_flap_get_swipe_to_close(HdyFlap * self)2659 hdy_flap_get_swipe_to_close (HdyFlap *self)
2660 {
2661   g_return_val_if_fail (HDY_IS_FLAP (self), FALSE);
2662 
2663   return self->swipe_to_close;
2664 }
2665 
2666 /**
2667  * hdy_flap_set_swipe_to_close:
2668  * @self: a #HdyFlap
2669  * @swipe_to_close: Whether @self can be closed with a swipe gesture
2670  *
2671  * Sets whether @self can be closed with a swipe gesture.
2672  *
2673  * The area that can be swiped depends on the #HdyFlap:transition-type value.
2674  *
2675  * Since: 1.2
2676  */
2677 void
hdy_flap_set_swipe_to_close(HdyFlap * self,gboolean swipe_to_close)2678 hdy_flap_set_swipe_to_close (HdyFlap  *self,
2679                              gboolean  swipe_to_close)
2680 {
2681   g_return_if_fail (HDY_IS_FLAP (self));
2682 
2683   swipe_to_close = !!swipe_to_close;
2684 
2685   if (self->swipe_to_close == swipe_to_close)
2686     return;
2687 
2688   self->swipe_to_close = swipe_to_close;
2689 
2690   update_swipe_tracker (self);
2691 
2692   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SWIPE_TO_CLOSE]);
2693 }
2694