adw-carousel.c 44.8 KB
Newer Older
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1
2
3
/*
 * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659@gmail.com>
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
5
6
7
8
 */

#include "config.h"

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
9
#include "adw-carousel.h"
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
10

11
#include "adw-animation-util-private.h"
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
12
13
14
15
#include "adw-animation-private.h"
#include "adw-navigation-direction.h"
#include "adw-swipe-tracker.h"
#include "adw-swipeable.h"
16
#include "adw-widget-utils-private.h"
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
17
18
19
20
21
22

#include <math.h>

#define DEFAULT_DURATION 250

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
23
 * AdwCarousel:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
24
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
25
26
27
 * A paginated scrolling widget.
 *
 * The `AdwCarousel` widget can be used to display a set of pages with
28
 * swipe-based navigation between them.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
29
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
30
31
32
33
 * [class@Adw.CarouselIndicatorDots] and [class@Adw.CarouselIndicatorLines] can
 * be used to provide page indicators for `AdwCarousel`.
 *
 * ## CSS nodes
34
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
35
 * `AdwCarousel` has a single CSS node with name `carousel`.
36
 *
37
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
38
39
 */

40
41
42
43
44
45
46
47
48
49
50
51
52
typedef struct {
  GtkWidget *widget;
  int position;
  gboolean visible;
  double size;
  double snap_point;
  gboolean adding;
  gboolean removing;

  gboolean shift_position;
  AdwAnimation *resize_animation;
} ChildInfo;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
53
struct _AdwCarousel
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
54
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
55
  GtkWidget parent_instance;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
56

57
58
59
60
61
62
63
64
65
66
67
  GList *children;
  double distance;
  double position;
  guint spacing;
  GtkOrientation orientation;
  guint animation_duration;
  guint reveal_duration;

  double animation_source_position;
  AdwAnimation *animation;
  ChildInfo *animation_target_child;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
68

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
69
  AdwSwipeTracker *tracker;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
70

71
72
  gboolean allow_scroll_wheel;

73
  double position_shift;
74
75
76

  gulong scroll_timeout_id;
  gboolean can_scroll;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
77
78
};

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
79
80
static void adw_carousel_buildable_init (GtkBuildableIface *iface);
static void adw_carousel_swipeable_init (AdwSwipeableInterface *iface);
81

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
82
G_DEFINE_TYPE_WITH_CODE (AdwCarousel, adw_carousel, GTK_TYPE_WIDGET,
83
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
84
85
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, adw_carousel_buildable_init)
                         G_IMPLEMENT_INTERFACE (ADW_TYPE_SWIPEABLE, adw_carousel_swipeable_init))
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
86

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
87
88
static GtkBuildableIface *parent_buildable_iface;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
89
90
91
92
93
94
95
enum {
  PROP_0,
  PROP_N_PAGES,
  PROP_POSITION,
  PROP_INTERACTIVE,
  PROP_SPACING,
  PROP_ANIMATION_DURATION,
96
  PROP_ALLOW_MOUSE_DRAG,
97
  PROP_ALLOW_SCROLL_WHEEL,
98
  PROP_ALLOW_LONG_SWIPES,
99
  PROP_REVEAL_DURATION,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
100
101
102

  /* GtkOrientable */
  PROP_ORIENTATION,
103
  LAST_PROP = PROP_REVEAL_DURATION + 1,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
104
105
106
107
};

static GParamSpec *props[LAST_PROP];

108
109
110
111
112
113
enum {
  SIGNAL_PAGE_CHANGED,
  SIGNAL_LAST_SIGNAL,
};
static guint signals[SIGNAL_LAST_SIGNAL];

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
static ChildInfo *
find_child_info (AdwCarousel *self,
                 GtkWidget   *widget)
{
  GList *l;

  for (l = self->children; l; l = l->next) {
    ChildInfo *info = l->data;

    if (widget == info->widget)
      return info;
  }

  return NULL;
}

static int
find_child_index (AdwCarousel *self,
                  GtkWidget   *widget,
                  gboolean     count_removing)
{
  GList *l;
  int i;

  i = 0;
  for (l = self->children; l; l = l->next) {
    ChildInfo *info = l->data;

    if (info->removing && !count_removing)
      continue;

    if (widget == info->widget)
      return i;

    i++;
  }

  return -1;
}

static GList *
get_nth_link (AdwCarousel *self,
              int          n)
{

  GList *l;
  int i;

  i = n;
  for (l = self->children; l; l = l->next) {
    ChildInfo *info = l->data;

    if (info->removing)
      continue;

    if (i-- == 0)
      return l;
  }

  return NULL;
}

static ChildInfo *
get_closest_child_at (AdwCarousel *self,
                      double       position,
                      gboolean     count_adding,
                      gboolean     count_removing)
{
  GList *l;
  ChildInfo *closest_child = NULL;

  for (l = self->children; l; l = l->next) {
    ChildInfo *child = l->data;

    if (child->adding && !count_adding)
      continue;

    if (child->removing && !count_removing)
      continue;

    if (!closest_child ||
        ABS (closest_child->snap_point - position) >
        ABS (child->snap_point - position))
      closest_child = child;
  }

  return closest_child;
}

static inline void
get_range (AdwCarousel *self,
           double      *lower,
           double      *upper)
{
  GList *l = g_list_last (self->children);
  ChildInfo *child = l ? l->data : NULL;

  if (lower)
    *lower = 0;

  if (upper)
    *upper = child ? child->snap_point : 0;
}

static GtkWidget *
get_page_at_position (AdwCarousel *self,
                      double       position)
{
  double lower = 0, upper = 0;
  ChildInfo *child;

  get_range (self, &lower, &upper);

  position = CLAMP (position, lower, upper);

  child = get_closest_child_at (self, position, TRUE, FALSE);

  if (!child)
    return NULL;

  return child->widget;
}
236

237
static void
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
update_shift_position_flag (AdwCarousel *self,
                            ChildInfo   *child)
{
  ChildInfo *closest_child;
  int animating_index, closest_index;

  /* We want to still shift position when the active child is being removed */
  closest_child = get_closest_child_at (self, self->position, FALSE, TRUE);

  if (!closest_child)
    return;

  animating_index = g_list_index (self->children, child);
  closest_index = g_list_index (self->children, closest_child);

  child->shift_position = (closest_index >= animating_index);
}

static void
set_position (AdwCarousel *self,
              double       position)
{
  GList *l;
  double lower = 0, upper = 0;

  get_range (self, &lower, &upper);

  position = CLAMP (position, lower, upper);

  self->position = position;
  gtk_widget_queue_allocate (GTK_WIDGET (self));

  for (l = self->children; l; l = l->next) {
    ChildInfo *child = l->data;

    if (child->adding || child->removing)
      update_shift_position_flag (self, child);
  }

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_POSITION]);
}

static void
resize_animation_value_cb (double     value,
                           ChildInfo *child)
{
  AdwCarousel *self = ADW_CAROUSEL (adw_animation_get_widget (child->resize_animation));
  double delta = value - child->size;

  child->size = value;

  if (child->shift_position)
    self->position_shift += delta;

  gtk_widget_queue_allocate (GTK_WIDGET (self));
}

static void
resize_animation_done_cb (ChildInfo *child)
{
  AdwCarousel *self = ADW_CAROUSEL (adw_animation_get_widget (child->resize_animation));

300
  g_clear_object (&child->resize_animation);
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328

  if (child->adding)
    child->adding = FALSE;

  if (child->removing) {
    self->children = g_list_remove (self->children, child);

    g_free (child);
  }

  gtk_widget_queue_allocate (GTK_WIDGET (self));
}

static void
animate_child_resize (AdwCarousel *self,
                      ChildInfo   *child,
                      double       value,
                      gint64       duration)
{
  double old_size = child->size;

  update_shift_position_flag (self, child);

  if (child->resize_animation)
    adw_animation_stop (child->resize_animation);

  child->resize_animation =
    adw_animation_new (GTK_WIDGET (self), old_size, value, duration,
329
                       (AdwAnimationTargetFunc) resize_animation_value_cb,
330
331
                       child);

Manuel Genovés's avatar
Fixes    
Manuel Genovés committed
332
  g_signal_connect_swapped (child->resize_animation, "done", G_CALLBACK (resize_animation_done_cb), child);
333

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
  adw_animation_start (child->resize_animation);
}

static void
shift_position (AdwCarousel *self,
                double       delta)
{
  set_position (self, self->position + delta);
  adw_swipe_tracker_shift_position (self->tracker, delta);
}

static void
scroll_animation_value_cb (double       value,
                           AdwCarousel *self)
{
  double position = adw_lerp (self->animation_source_position,
                              self->animation_target_child->snap_point,
                              value);

  set_position (self, position);

  gtk_widget_queue_allocate (GTK_WIDGET (self));
}

static void
scroll_animation_done_cb (AdwCarousel *self)
360
361
{
  GtkWidget *child;
362
363
  int index;

364
  g_clear_object (&self->animation);
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
  self->animation_source_position = 0;
  self->animation_target_child = NULL;

  child = get_page_at_position (self, self->position);
  index = find_child_index (self, child, FALSE);

  g_signal_emit (self, signals[SIGNAL_PAGE_CHANGED], 0, index);
}

static void
scroll_to (AdwCarousel *self,
           GtkWidget   *widget,
           gint64       duration)
{
  if (self->animation)
    adw_animation_stop (self->animation);

382
383
  self->animation_target_child = find_child_info (self, widget);

384
385
386
387
388
  if (self->animation_target_child == NULL)
    return;

  self->animation_source_position = self->position;

389
390
  self->animation =
    adw_animation_new (GTK_WIDGET (self), 0, 1, duration,
391
                       (AdwAnimationTargetFunc) scroll_animation_value_cb,
392
393
                       self);

Manuel Genovés's avatar
Fixes    
Manuel Genovés committed
394
  g_signal_connect_swapped (self->animation, "done", G_CALLBACK (scroll_animation_done_cb), self);
395

396
397
398
399
400
401
402
403
  adw_animation_start (self->animation);
}

static inline double
get_closest_snap_point (AdwCarousel *self)
{
  ChildInfo *closest_child =
    get_closest_child_at (self, self->position, TRUE, TRUE);
404

405
406
  if (!closest_child)
    return 0;
407

408
  return closest_child->snap_point;
409
410
411
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
412
413
414
begin_swipe_cb (AdwSwipeTracker        *tracker,
                AdwNavigationDirection  direction,
                AdwCarousel            *self)
415
{
416
417
  if (self->animation)
    adw_animation_stop (self->animation);
418
419
420
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
421
update_swipe_cb (AdwSwipeTracker *tracker,
Christopher Davis's avatar
Christopher Davis committed
422
                 double           progress,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
423
                 AdwCarousel     *self)
424
{
425
  set_position (self, progress);
426
427
428
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
429
end_swipe_cb (AdwSwipeTracker *tracker,
430
              gint64           duration,
Christopher Davis's avatar
Christopher Davis committed
431
              double           to,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
432
              AdwCarousel     *self)
433
{
434
435
436
437
438
  GtkWidget *child = get_page_at_position (self, to);

  scroll_to (self, child, duration);
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
439
440
441
442
443
/* Copied from GtkOrientable. Orientable widgets are supposed
 * to do this manually via a private GTK function. */
static void
set_orientable_style_classes (GtkOrientable *orientable)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
444
445
  GtkOrientation orientation = gtk_orientable_get_orientation (orientable);
  GtkWidget *widget = GTK_WIDGET (orientable);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
446

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
447
448
449
450
451
452
453
  if (orientation == GTK_ORIENTATION_HORIZONTAL) {
    gtk_widget_add_css_class (widget, "horizontal");
    gtk_widget_remove_css_class (widget, "vertical");
  } else {
    gtk_widget_add_css_class (widget, "vertical");
    gtk_widget_remove_css_class (widget, "horizontal");
  }
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
454
455
456
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
457
update_orientation (AdwCarousel *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
458
{
459
460
  gboolean reversed =
    self->orientation == GTK_ORIENTATION_HORIZONTAL &&
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
461
462
    gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;

463
464
465
466
  gtk_orientable_set_orientation (GTK_ORIENTABLE (self->tracker),
                                  self->orientation);
  adw_swipe_tracker_set_reversed (self->tracker,
                                  reversed);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
467
468
469
470

  set_orientable_style_classes (GTK_ORIENTABLE (self));
}

471
static gboolean
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
472
scroll_timeout_cb (AdwCarousel *self)
473
474
475
476
477
478
{
  self->can_scroll = TRUE;
  return G_SOURCE_REMOVE;
}

static gboolean
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
479
scroll_cb (AdwCarousel              *self,
Christopher Davis's avatar
Christopher Davis committed
480
481
           double                    dx,
           double                    dy,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
482
           GtkEventControllerScroll *controller)
483
484
485
{
  GdkDevice *source_device;
  GdkInputSource input_source;
Christopher Davis's avatar
Christopher Davis committed
486
  int index;
487
488
489
  gboolean allow_vertical;
  GtkOrientation orientation;
  guint duration;
490
  GtkWidget *child;
491

492
493
494
  if (!self->allow_scroll_wheel)
    return GDK_EVENT_PROPAGATE;

495
496
497
  if (!self->can_scroll)
    return GDK_EVENT_PROPAGATE;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
498
  if (!adw_carousel_get_interactive (self))
499
500
    return GDK_EVENT_PROPAGATE;

501
502
503
  if (adw_carousel_get_n_pages (self) == 0)
    return GDK_EVENT_PROPAGATE;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
504
  source_device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (controller));
505
  input_source = gdk_device_get_source (source_device);
506
  if (input_source == GDK_SOURCE_TOUCHPAD)
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
    return GDK_EVENT_PROPAGATE;

  /* Mice often don't have easily accessible horizontal scrolling,
   * hence allow vertical mouse scrolling regardless of orientation */
  allow_vertical = (input_source == GDK_SOURCE_MOUSE);

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
  index = 0;

  if (orientation == GTK_ORIENTATION_VERTICAL || allow_vertical) {
    if (dy > 0)
      index++;
    else if (dy < 0)
      index--;
  }

  if (orientation == GTK_ORIENTATION_HORIZONTAL && index == 0) {
    if (dx > 0)
      index++;
    else if (dx < 0)
      index--;
  }

  if (index == 0)
    return GDK_EVENT_PROPAGATE;

533
534
535
  child = get_page_at_position (self, self->position);

  index += find_child_index (self, child, FALSE);
Christopher Davis's avatar
Christopher Davis committed
536
  index = CLAMP (index, 0, (int) adw_carousel_get_n_pages (self) - 1);
537

538
  scroll_to (self, adw_carousel_get_nth_page (self, index), self->animation_duration);
539
540
541
542
543
544
545
546
547
548

  /* Don't allow the delay to go lower than 250ms */
  duration = MIN (self->animation_duration, DEFAULT_DURATION);

  self->can_scroll = FALSE;
  g_timeout_add (duration, (GSourceFunc) scroll_timeout_cb, self);

  return GDK_EVENT_STOP;
}

549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
static void
adw_carousel_measure (GtkWidget      *widget,
                      GtkOrientation  orientation,
                      int             for_size,
                      int            *minimum,
                      int            *natural,
                      int            *minimum_baseline,
                      int            *natural_baseline)
{
  AdwCarousel *self = ADW_CAROUSEL (widget);
  GList *children;

  if (minimum)
    *minimum = 0;
  if (natural)
    *natural = 0;

  if (minimum_baseline)
    *minimum_baseline = -1;
  if (natural_baseline)
    *natural_baseline = -1;

  for (children = self->children; children; children = children->next) {
    ChildInfo *child_info = children->data;
    GtkWidget *child = child_info->widget;
    int child_min, child_nat;

    if (child_info->removing)
      continue;

    if (!gtk_widget_get_visible (child))
      continue;

    gtk_widget_measure (child, orientation, for_size,
                        &child_min, &child_nat, NULL, NULL);

    if (minimum)
      *minimum = MAX (*minimum, child_min);
    if (natural)
      *natural = MAX (*natural, child_nat);
  }
}

static void
adw_carousel_size_allocate (GtkWidget *widget,
                            int        width,
                            int        height,
                            int        baseline)
{
  AdwCarousel *self = ADW_CAROUSEL (widget);
  int size, child_width, child_height;
  GList *children;
  double x, y, offset;
  gboolean is_rtl;
  double snap_point;

  if (self->position_shift != 0) {
    shift_position (self, self->position_shift);
    self->position_shift = 0;
  }

  size = 0;
  for (children = self->children; children; children = children->next) {
    ChildInfo *child_info = children->data;
    GtkWidget *child = child_info->widget;
    int min, nat;
    int child_size;

    if (child_info->removing)
      continue;

    if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
      gtk_widget_measure (child, self->orientation,
                          height, &min, &nat, NULL, NULL);
      if (gtk_widget_get_hexpand (child))
624
        child_size = width;
625
      else
626
        child_size = CLAMP (nat, min, width);
627
628
629
630
    } else {
      gtk_widget_measure (child, self->orientation,
                          width, &min, &nat, NULL, NULL);
      if (gtk_widget_get_vexpand (child))
631
        child_size = height;
632
      else
633
        child_size = CLAMP (nat, min, height);
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
    }

    size = MAX (size, child_size);
  }

  self->distance = size + self->spacing;

  if (self->orientation == GTK_ORIENTATION_HORIZONTAL) {
    child_width = size;
    child_height = height;
  } else {
    child_width = width;
    child_height = size;
  }

  snap_point = 0;

  for (children = self->children; children; children = children->next) {
    ChildInfo *child_info = children->data;

    child_info->snap_point = snap_point + child_info->size - 1;

    snap_point += child_info->size;
  }

  if (!gtk_widget_get_realized (GTK_WIDGET (self)))
    return;

  x = 0;
  y = 0;

  is_rtl = (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL);

  if (self->orientation == GTK_ORIENTATION_VERTICAL)
    offset = (self->distance * self->position) - (height - child_height) / 2.0;
  else if (is_rtl)
    offset = -(self->distance * self->position) - (width - child_width) / 2.0;
  else
    offset = (self->distance * self->position) - (width - child_width) / 2.0;

  if (self->orientation == GTK_ORIENTATION_VERTICAL)
    y -= offset;
  else
    x -= offset;

  for (children = self->children; children; children = children->next) {
    ChildInfo *child_info = children->data;
    GskTransform *transform = gsk_transform_new ();

    if (!child_info->removing) {
      if (!gtk_widget_get_visible (child_info->widget))
        continue;

      if (self->orientation == GTK_ORIENTATION_VERTICAL) {
        child_info->position = y;
        child_info->visible = child_info->position < height &&
                              child_info->position + child_height > 0;

        transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (0, child_info->position));
      } else {
        child_info->position = x;
        child_info->visible = child_info->position < width &&
                              child_info->position + child_width > 0;

        transform = gsk_transform_translate (transform, &GRAPHENE_POINT_INIT (child_info->position, 0));
      }

      gtk_widget_allocate (child_info->widget, child_width, child_height, baseline, transform);
    }

    if (self->orientation == GTK_ORIENTATION_VERTICAL)
      y += self->distance * child_info->size;
    else if (is_rtl)
      x -= self->distance * child_info->size;
    else
      x += self->distance * child_info->size;
  }
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
713
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
714
adw_carousel_direction_changed (GtkWidget        *widget,
715
                                GtkTextDirection  previous_direction)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
716
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
717
  AdwCarousel *self = ADW_CAROUSEL (widget);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
718
719
720
721
722

  update_orientation (self);
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
723
adw_carousel_constructed (GObject *object)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
724
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
725
  AdwCarousel *self = (AdwCarousel *)object;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
726
727
728

  update_orientation (self);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
729
  G_OBJECT_CLASS (adw_carousel_parent_class)->constructed (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
730
731
732
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
733
adw_carousel_dispose (GObject *object)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
734
{
735
  AdwCarousel *self = ADW_CAROUSEL (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
736

737
  g_clear_object (&self->tracker);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
738

739
740
741
742
743
  if (self->scroll_timeout_id != 0) {
    g_source_remove (self->scroll_timeout_id);
    self->scroll_timeout_id = 0;
  }

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
744
  G_OBJECT_CLASS (adw_carousel_parent_class)->dispose (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
745
746
}

747
748
749
750
751
752
753
754
755
756
static void
adw_carousel_finalize (GObject *object)
{
  AdwCarousel *self = ADW_CAROUSEL (object);

  g_list_free_full (self->children, (GDestroyNotify) g_free);

  G_OBJECT_CLASS (adw_carousel_parent_class)->finalize (object);
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
757
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
758
adw_carousel_get_property (GObject    *object,
759
760
761
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
762
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
763
  AdwCarousel *self = ADW_CAROUSEL (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
764
765
766

  switch (prop_id) {
  case PROP_N_PAGES:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
767
    g_value_set_uint (value, adw_carousel_get_n_pages (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
768
769
770
    break;

  case PROP_POSITION:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
771
    g_value_set_double (value, adw_carousel_get_position (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
772
773
774
    break;

  case PROP_INTERACTIVE:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
775
    g_value_set_boolean (value, adw_carousel_get_interactive (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
776
777
778
    break;

  case PROP_SPACING:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
779
    g_value_set_uint (value, adw_carousel_get_spacing (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
780
781
    break;

782
  case PROP_ALLOW_MOUSE_DRAG:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
783
    g_value_set_boolean (value, adw_carousel_get_allow_mouse_drag (self));
784
785
    break;

786
787
788
789
  case PROP_ALLOW_SCROLL_WHEEL:
    g_value_set_boolean (value, adw_carousel_get_allow_scroll_wheel (self));
    break;

790
791
792
793
  case PROP_ALLOW_LONG_SWIPES:
    g_value_set_boolean (value, adw_carousel_get_allow_long_swipes (self));
    break;

794
  case PROP_REVEAL_DURATION:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
795
    g_value_set_uint (value, adw_carousel_get_reveal_duration (self));
796
797
    break;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
798
799
800
801
802
  case PROP_ORIENTATION:
    g_value_set_enum (value, self->orientation);
    break;

  case PROP_ANIMATION_DURATION:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
803
    g_value_set_uint (value, adw_carousel_get_animation_duration (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
804
805
806
807
808
809
810
811
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
812
adw_carousel_set_property (GObject      *object,
813
814
815
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
816
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
817
  AdwCarousel *self = ADW_CAROUSEL (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
818
819
820

  switch (prop_id) {
  case PROP_INTERACTIVE:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
821
    adw_carousel_set_interactive (self, g_value_get_boolean (value));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
822
823
824
    break;

  case PROP_SPACING:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
825
    adw_carousel_set_spacing (self, g_value_get_uint (value));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
826
827
828
    break;

  case PROP_ANIMATION_DURATION:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
829
    adw_carousel_set_animation_duration (self, g_value_get_uint (value));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
830
831
    break;

832
  case PROP_REVEAL_DURATION:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
833
    adw_carousel_set_reveal_duration (self, g_value_get_uint (value));
834
835
    break;

836
  case PROP_ALLOW_MOUSE_DRAG:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
837
    adw_carousel_set_allow_mouse_drag (self, g_value_get_boolean (value));
838
839
    break;

840
841
842
843
  case PROP_ALLOW_SCROLL_WHEEL:
    adw_carousel_set_allow_scroll_wheel (self, g_value_get_boolean (value));
    break;

844
845
846
847
  case PROP_ALLOW_LONG_SWIPES:
    adw_carousel_set_allow_long_swipes (self, g_value_get_boolean (value));
    break;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
848
849
850
851
852
853
  case PROP_ORIENTATION:
    {
      GtkOrientation orientation = g_value_get_enum (value);
      if (orientation != self->orientation) {
        self->orientation = orientation;
        update_orientation (self);
854
        gtk_widget_queue_resize (GTK_WIDGET (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
855
856
857
858
859
860
861
862
863
864
865
        g_object_notify (G_OBJECT (self), "orientation");
      }
    }
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
866
adw_carousel_class_init (AdwCarouselClass *klass)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
867
868
869
870
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
871
872
  object_class->constructed = adw_carousel_constructed;
  object_class->dispose = adw_carousel_dispose;
873
  object_class->finalize = adw_carousel_finalize;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
874
875
  object_class->get_property = adw_carousel_get_property;
  object_class->set_property = adw_carousel_set_property;
876
877
878

  widget_class->measure = adw_carousel_measure;
  widget_class->size_allocate = adw_carousel_size_allocate;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
879
  widget_class->direction_changed = adw_carousel_direction_changed;
880
  widget_class->get_request_mode = adw_widget_get_request_mode;
881
  widget_class->compute_expand = adw_widget_compute_expand;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
882
883

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
884
   * AdwCarousel:n-pages: (attributes org.gtk.Property.get=adw_carousel_get_n_pages)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
885
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
886
   * The number of pages in a `AdwCarousel`.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
887
   *
888
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
889
890
891
   */
  props[PROP_N_PAGES] =
    g_param_spec_uint ("n-pages",
892
893
                       "Number of pages",
                       "Number of pages",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
894
895
896
897
898
899
                       0,
                       G_MAXUINT,
                       0,
                       G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
900
901
902
   * AdwCarousel:position: (attributes org.gtk.Property.get=adw_carousel_get_position)
   *
   * Current scrolling position, unitless.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
903
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
904
   * 1 matches 1 page. Use [method@Adw.Carousel.scroll_to] for changing it.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
905
   *
906
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
907
908
909
   */
  props[PROP_POSITION] =
    g_param_spec_double ("position",
910
911
                         "Position",
                         "Current scrolling position",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
912
913
914
915
916
917
                         0,
                         G_MAXDOUBLE,
                         0,
                         G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
918
   * AdwCarousel:interactive: (attributes org.gtk.Property.get=adw_carousel_get_interactive org.gtk.Property.set=adw_carousel_set_interactive)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
919
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
920
921
922
923
   * Whether the carousel can be navigated.
   *
   * This can be used to temporarily disable a `AdwCarousel` to only allow
   * navigating it in a certain state.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
924
   *
925
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
926
927
928
   */
  props[PROP_INTERACTIVE] =
    g_param_spec_boolean ("interactive",
929
930
                          "Interactive",
                          "Whether the widget can be swiped",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
931
932
933
934
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
935
   * AdwCarousel:spacing: (attributes org.gtk.Property.get=adw_carousel_get_spacing org.gtk.Property.set=adw_carousel_set_spacing)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
936
937
938
   *
   * Spacing between pages in pixels.
   *
939
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
940
941
942
   */
  props[PROP_SPACING] =
    g_param_spec_uint ("spacing",
943
944
                       "Spacing",
                       "Spacing between pages",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
945
946
947
                       0,
                       G_MAXUINT,
                       0,
948
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
949
950

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
951
   * AdwCarousel:animation-duration: (attributes org.gtk.Property.get=adw_carousel_get_animation_duration org.gtk.Property.set=adw_carousel_set_animation_duration)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
952
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
953
   * Animation duration in milliseconds, used by [method@Adw.Carousel.scroll_to].
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
954
   *
955
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
956
957
958
   */
  props[PROP_ANIMATION_DURATION] =
    g_param_spec_uint ("animation-duration",
959
960
                       "Animation duration",
                       "Default animation duration",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
961
962
963
                       0, G_MAXUINT, DEFAULT_DURATION,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

964
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
965
966
967
   * AdwCarousel:allow-mouse-drag: (attributes org.gtk.Property.get=adw_carousel_get_allow_mouse_drag org.gtk.Property.set=adw_carousel_set_allow_mouse_drag)
   *
   * Sets whether the `AdwCarousel` can be dragged with mouse pointer.
968
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
969
   * If the value is `FALSE`, dragging is only available on touch.
970
   *
971
   * Since: 1.0
972
973
974
   */
  props[PROP_ALLOW_MOUSE_DRAG] =
    g_param_spec_boolean ("allow-mouse-drag",
975
976
                          "Allow mouse drag",
                          "Whether to allow dragging with mouse pointer",
977
                          TRUE,
978
979
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

980
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
981
   * AdwCarousel:allow-scroll-wheel: (attributes org.gtk.Property.get=adw_carousel_get_allow_scroll_wheel org.gtk.Property.set=adw_carousel_set_allow_scroll_wheel)
982
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
983
984
985
   * Whether the widget will respond to scroll wheel events.
   *
   * If the value is `FALSE`, wheel events will be ignored.
986
987
988
989
990
991
992
993
994
995
   *
   * Since: 1.0
   */
  props[PROP_ALLOW_SCROLL_WHEEL] =
    g_param_spec_boolean ("allow-scroll-wheel",
                          "Allow scroll wheel",
                          "Whether the widget will respond to scroll wheel events",
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

996
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
997
998
999
   * AdwCarousel:allow-long-swipes: (attributes org.gtk.Property.get=adw_carousel_get_allow_long_swipes org.gtk.Property.set=adw_carousel_set_allow_long_swipes)
   *
   * Whether to allow swiping for more than one page at a time.
1000
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1001
   * If the value is `FALSE`, each swipe can only move to the adjacent pages.
1002
1003
1004
1005
1006
   *
   * Since: 1.0
   */
  props[PROP_ALLOW_LONG_SWIPES] =
    g_param_spec_boolean ("allow-long-swipes",
1007
1008
                          "Allow long swipes",
                          "Whether to allow swiping for more than one page at a time",
1009
1010
1011
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

1012
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1013
   * AdwCarousel:reveal-duration:
1014
1015
1016
1017
1018
1019
1020
   *
   * Page reveal duration in milliseconds.
   *
   * Since: 1.0
   */
  props[PROP_REVEAL_DURATION] =
    g_param_spec_uint ("reveal-duration",
1021
1022
                       "Reveal duration",
                       "Page reveal duration",
1023
1024
1025
1026
1027
                       0,
                       G_MAXUINT,
                       0,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1028
1029
1030
1031
1032
1033
  g_object_class_override_property (object_class,
                                    PROP_ORIENTATION,
                                    "orientation");

  g_object_class_install_properties (object_class, LAST_PROP, props);

1034
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1035
   * AdwCarousel::page-changed:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1036
1037
1038
1039
   * @self: a `AdwCarousel`
   * @index: current page
   *
   * This signal is emitted after a page has been changed.
1040
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1041
1042
   * It can be used to implement "infinite scrolling" by amending the pages
   * after every scroll.
1043
   *
1044
   * Since: 1.0
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
   */
  signals[SIGNAL_PAGE_CHANGED] =
    g_signal_new ("page-changed",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_UINT);

1056
  gtk_widget_class_set_css_name (widget_class, "carousel");
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1057
1058
1059
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1060
adw_carousel_init (AdwCarousel *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1061
{
1062
  GtkEventController *controller;
1063
1064
  self->allow_scroll_wheel = TRUE;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1065
1066
  gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);

1067
1068
  self->orientation = GTK_ORIENTATION_HORIZONTAL;
  self->reveal_duration = 0;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1069
  self->animation_duration = DEFAULT_DURATION;
1070
  self->can_scroll = TRUE;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1071

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1072
1073
  self->tracker = adw_swipe_tracker_new (ADW_SWIPEABLE (self));
  adw_swipe_tracker_set_allow_mouse_drag (self->tracker, TRUE);
1074
1075
1076
1077
1078

  g_signal_connect_object (self->tracker, "begin-swipe", G_CALLBACK (begin_swipe_cb), self, 0);
  g_signal_connect_object (self->tracker, "update-swipe", G_CALLBACK (update_swipe_cb), self, 0);
  g_signal_connect_object (self->tracker, "end-swipe", G_CALLBACK (end_swipe_cb), self, 0);

1079
1080
1081
  controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
  g_signal_connect_swapped (controller, "scroll", G_CALLBACK (scroll_cb), self);
  gtk_widget_add_controller (GTK_WIDGET (self), controller);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1082
1083
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1084
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1085
adw_carousel_buildable_add_child (GtkBuildable *buildable,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1086
1087
1088
1089
1090
                                  GtkBuilder   *builder,
                                  GObject      *child,
                                  const char   *type)
{
  if (GTK_IS_WIDGET (child))
1091
    adw_carousel_append (ADW_CAROUSEL (buildable), GTK_WIDGET (child));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1092
1093
1094
1095
1096
  else
    parent_buildable_iface->add_child (buildable, builder, child, type);
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1097
adw_carousel_buildable_init (GtkBuildableIface *iface)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1098
1099
1100
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1101
  iface->add_child = adw_carousel_buildable_add_child;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1102
1103
}

1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
static double
adw_carousel_get_distance (AdwSwipeable *swipeable)
{
  AdwCarousel *self = ADW_CAROUSEL (swipeable);

  return self->distance;
}

static double *
adw_carousel_get_snap_points (AdwSwipeable *swipeable,
                              int          *n_snap_points)
{
  AdwCarousel *self = ADW_CAROUSEL (swipeable);
  guint i, n_pages;
  double *points;
  GList *l;

  n_pages = MAX (g_list_length (self->children), 1);
  points = g_new0 (double, n_pages);

  i = 0;
  for (l = self->children; l; l = l->next) {
    ChildInfo *info = l->data;

    points[i++] = info->snap_point;
  }

  if (n_snap_points)
    *n_snap_points = n_pages;

  return points;
}

static double
adw_carousel_get_progress (AdwSwipeable *swipeable)
{
  AdwCarousel *self = ADW_CAROUSEL (swipeable);

  return adw_carousel_get_position (self);
}

static double
adw_carousel_get_cancel_progress (AdwSwipeable *swipeable)
{
  AdwCarousel *self = ADW_CAROUSEL (swipeable);

  return get_closest_snap_point (self);
}

static void
adw_carousel_swipeable_init (AdwSwipeableInterface *iface)
{
  iface->get_distance = adw_carousel_get_distance;
  iface->get_snap_points = adw_carousel_get_snap_points;
  iface->get_progress = adw_carousel_get_progress;
  iface->get_cancel_progress = adw_carousel_get_cancel_progress;
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1162
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1163
 * adw_carousel_new:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1164
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1165
 * Creates a new `AdwCarousel`.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1166
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1167
 * Returns: the newly created `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1168
 *
1169
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1170
 */
Ujjwal Kumar's avatar
Ujjwal Kumar committed
1171
GtkWidget *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1172
adw_carousel_new (void)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1173
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1174
  return g_object_new (ADW_TYPE_CAROUSEL, NULL);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1175
1176
1177
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1178
 * adw_carousel_prepend:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1179
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1180
1181
 * @child: a widget to add
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1182
 * Prepends @child to @self.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1183
 *
1184
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1185
1186
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1187
adw_carousel_prepend (AdwCarousel *self,
1188
                      GtkWidget   *widget)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1189
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1190
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1191
  g_return_if_fail (GTK_IS_WIDGET (widget));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1192

1193
  adw_carousel_insert (self, widget, 0);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1194
1195
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1196
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1197
 * adw_carousel_append:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1198
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1199
1200
 * @child: a widget to add
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1201
 * Appends @child to @self.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1202
1203
1204
1205
 *
 * Since: 1.0
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1206
adw_carousel_append (AdwCarousel *self,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1207
1208
                     GtkWidget   *widget)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1209
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1210
  g_return_if_fail (GTK_IS_WIDGET (widget));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1211

1212
  adw_carousel_insert (self, widget, -1);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1213
1214
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1215
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1216
 * adw_carousel_insert:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1217
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1218
 * @child: a widget to add
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1219
 * @position: the position to insert @child at
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1220
1221
1222
1223
1224
1225
 *
 * Inserts @child into @self at position @position.
 *
 * If position is -1, or larger than the number of pages,
 * @child will be appended to the end.
 *
1226
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1227
1228
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1229
adw_carousel_insert (AdwCarousel *self,
1230
                     GtkWidget   *widget,
Christopher Davis's avatar
Christopher Davis committed
1231
                     int          position)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1232
{
1233
1234
1235
  ChildInfo *info;
  GList *prev_link = NULL;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1236
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1237
  g_return_if_fail (GTK_IS_WIDGET (widget));
1238
  g_return_if_fail (position >= -1);
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248

  info = g_new0 (ChildInfo, 1);
  info->widget = widget;
  info->size = 0;
  info->adding = TRUE;

  if (position >= 0)
    prev_link = get_nth_link (self, position);

  self->children = g_list_insert_before (self->children, prev_link, info);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1249

1250
1251
1252
1253
1254
1255
1256
  gtk_widget_set_parent (widget, GTK_WIDGET (self));

  gtk_widget_queue_allocate (GTK_WIDGET (self));

  animate_child_resize (self, info, 1, self->reveal_duration);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_N_PAGES]);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1257
1258
}
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1259
 * adw_carousel_reorder:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1260
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1261
 * @child: a widget to add
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1262
 * @position: the position to move @child to
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1263
1264
1265
1266
 *
 * Moves @child into position @position.
 *
 * If position is -1, or larger than the number of pages, @child will be moved
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1267
 * at the end.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1268
 *
1269
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1270
1271
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1272
adw_carousel_reorder (AdwCarousel *self,
1273
                      GtkWidget   *child,
Christopher Davis's avatar
Christopher Davis committed
1274
                      int          position)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1275
{
1276
1277
1278
1279
1280
  ChildInfo *info, *prev_info;
  GList *link, *prev_link;
  int old_position;
  double closest_point, old_point,