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 * <child> 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