adw-carousel.c 45.2 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.h"
12
#include "adw-macros-private.h"
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
13
14
15
#include "adw-navigation-direction.h"
#include "adw-swipe-tracker.h"
#include "adw-swipeable.h"
Manuel Genovés's avatar
Manuel Genovés committed
16
#include "adw-timed-animation.h"
17
#include "adw-widget-utils-private.h"
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
18
19
20
21
22
23

#include <math.h>

#define DEFAULT_DURATION 250

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

41
42
43
44
45
46
47
48
49
50
51
52
53
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
54
struct _AdwCarousel
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
55
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
56
  GtkWidget parent_instance;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
57

58
59
60
61
62
63
64
65
66
67
68
  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
69

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

72
73
  gboolean allow_scroll_wheel;

74
  double position_shift;
75

76
  guint scroll_timeout_id;
77
  gboolean can_scroll;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
78
79
};

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

83
84
85
86
G_DEFINE_FINAL_TYPE_WITH_CODE (AdwCarousel, adw_carousel, GTK_TYPE_WIDGET,
                               G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
                               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
87

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

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

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

static GParamSpec *props[LAST_PROP];

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

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
236
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;
}
237

238
static void
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
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
282
283
resize_animation_value_cb (ChildInfo *child,
                           double     value)
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
{
  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));

301
  g_clear_object (&child->resize_animation);
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318

  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,
319
                      guint        duration)
320
{
321
  AdwAnimationTarget *target;
322
323
324
325
326
  double old_size = child->size;

  update_shift_position_flag (self, child);

  if (child->resize_animation)
327
    adw_animation_skip (child->resize_animation);
328

329
330
331
  target = adw_callback_animation_target_new ((AdwAnimationTargetFunc)
                                              resize_animation_value_cb,
                                              child, NULL);
332
  child->resize_animation =
333
334
    adw_timed_animation_new (GTK_WIDGET (self), old_size,
                             value, duration, target);
335

336
337
  g_signal_connect_swapped (child->resize_animation, "done",
                            G_CALLBACK (resize_animation_done_cb), child);
338

339
  adw_animation_play (child->resize_animation);
340
341
342
343
344
345
346
347
348
349
350
}

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

static void
351
352
scroll_animation_value_cb (AdwCarousel *self,
                           double       value)
353
354
355
356
357
358
359
360
361
362
363
364
{
  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)
365
366
{
  GtkWidget *child;
367
368
369
370
371
372
373
374
375
376
377
378
379
380
  int index;

  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,
381
           guint        duration)
382
{
383
384
  self->animation_target_child = find_child_info (self, widget);

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

  self->animation_source_position = self->position;

390
391
  adw_timed_animation_set_duration (ADW_TIMED_ANIMATION (self->animation),
                                    duration);
392
  adw_animation_play (self->animation);
393
394
395
396
397
398
399
}

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

401
402
  if (!closest_child)
    return 0;
403

404
  return closest_child->snap_point;
405
406
407
}

static void
408
409
begin_swipe_cb (AdwSwipeTracker *tracker,
                AdwCarousel     *self)
410
{
411
  adw_animation_pause (self->animation);
412
413
414
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
415
update_swipe_cb (AdwSwipeTracker *tracker,
Christopher Davis's avatar
Christopher Davis committed
416
                 double           progress,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
417
                 AdwCarousel     *self)
418
{
419
  set_position (self, progress);
420
421
422
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
423
end_swipe_cb (AdwSwipeTracker *tracker,
424
              guint            duration,
Christopher Davis's avatar
Christopher Davis committed
425
              double           to,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
426
              AdwCarousel     *self)
427
{
428
429
430
431
432
  GtkWidget *child = get_page_at_position (self, to);

  scroll_to (self, child, duration);
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
433
434
435
436
437
/* 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
438
439
  GtkOrientation orientation = gtk_orientable_get_orientation (orientable);
  GtkWidget *widget = GTK_WIDGET (orientable);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
440

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
441
442
443
444
445
446
447
  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
448
449
450
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
451
update_orientation (AdwCarousel *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
452
{
453
454
  gboolean reversed =
    self->orientation == GTK_ORIENTATION_HORIZONTAL &&
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
455
456
    gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;

457
458
459
460
  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
461
462
463
464

  set_orientable_style_classes (GTK_ORIENTABLE (self));
}

465
static gboolean
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
466
scroll_timeout_cb (AdwCarousel *self)
467
468
469
470
471
472
{
  self->can_scroll = TRUE;
  return G_SOURCE_REMOVE;
}

static gboolean
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
473
scroll_cb (AdwCarousel              *self,
Christopher Davis's avatar
Christopher Davis committed
474
475
           double                    dx,
           double                    dy,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
476
           GtkEventControllerScroll *controller)
477
478
479
{
  GdkDevice *source_device;
  GdkInputSource input_source;
Christopher Davis's avatar
Christopher Davis committed
480
  int index;
481
482
483
  gboolean allow_vertical;
  GtkOrientation orientation;
  guint duration;
484
  GtkWidget *child;
485

486
487
488
  if (!self->allow_scroll_wheel)
    return GDK_EVENT_PROPAGATE;

489
490
491
  if (!self->can_scroll)
    return GDK_EVENT_PROPAGATE;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
492
  if (!adw_carousel_get_interactive (self))
493
494
    return GDK_EVENT_PROPAGATE;

495
496
497
  if (adw_carousel_get_n_pages (self) == 0)
    return GDK_EVENT_PROPAGATE;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
498
  source_device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (controller));
499
  input_source = gdk_device_get_source (source_device);
500
  if (input_source == GDK_SOURCE_TOUCHPAD)
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
    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;

527
528
529
  child = get_page_at_position (self, self->position);

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

532
  scroll_to (self, adw_carousel_get_nth_page (self, index), self->animation_duration);
533
534
535
536
537

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

  self->can_scroll = FALSE;
538
539
  self->scroll_timeout_id =
   g_timeout_add (duration, (GSourceFunc) scroll_timeout_cb, self);
540
541
542
543

  return GDK_EVENT_STOP;
}

544
545
546
547
548
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
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))
619
        child_size = width;
620
      else
621
        child_size = CLAMP (nat, min, width);
622
623
624
625
    } else {
      gtk_widget_measure (child, self->orientation,
                          width, &min, &nat, NULL, NULL);
      if (gtk_widget_get_vexpand (child))
626
        child_size = height;
627
      else
628
        child_size = CLAMP (nat, min, height);
629
630
631
632
633
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
    }

    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
708
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
709
adw_carousel_direction_changed (GtkWidget        *widget,
710
                                GtkTextDirection  previous_direction)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
711
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
712
  AdwCarousel *self = ADW_CAROUSEL (widget);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
713
714
715
716
717

  update_orientation (self);
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
718
adw_carousel_constructed (GObject *object)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
719
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
720
  AdwCarousel *self = (AdwCarousel *)object;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
721
722
723

  update_orientation (self);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
724
  G_OBJECT_CLASS (adw_carousel_parent_class)->constructed (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
725
726
727
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
728
adw_carousel_dispose (GObject *object)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
729
{
730
  AdwCarousel *self = ADW_CAROUSEL (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
731

732
733
734
735
736
737
  while (self->children) {
    ChildInfo *info = self->children->data;

    adw_carousel_remove (self, info->widget);
  }

738
  g_clear_object (&self->tracker);
739
  g_clear_object (&self->animation);
740
  g_clear_handle_id (&self->scroll_timeout_id, g_source_remove);
741

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

745
746
747
748
749
750
751
752
753
754
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
755
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
756
adw_carousel_get_property (GObject    *object,
757
758
759
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
760
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
761
  AdwCarousel *self = ADW_CAROUSEL (object);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
762
763
764

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

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

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

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

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

784
785
786
787
  case PROP_ALLOW_SCROLL_WHEEL:
    g_value_set_boolean (value, adw_carousel_get_allow_scroll_wheel (self));
    break;

788
789
790
791
  case PROP_ALLOW_LONG_SWIPES:
    g_value_set_boolean (value, adw_carousel_get_allow_long_swipes (self));
    break;

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

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

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

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

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

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

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

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

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

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

838
839
840
841
  case PROP_ALLOW_SCROLL_WHEEL:
    adw_carousel_set_allow_scroll_wheel (self, g_value_get_boolean (value));
    break;

842
843
844
845
  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
846
847
848
849
850
851
  case PROP_ORIENTATION:
    {
      GtkOrientation orientation = g_value_get_enum (value);
      if (orientation != self->orientation) {
        self->orientation = orientation;
        update_orientation (self);
852
        gtk_widget_queue_resize (GTK_WIDGET (self));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
853
854
855
856
857
858
859
860
861
862
863
        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
864
adw_carousel_class_init (AdwCarouselClass *klass)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
865
866
867
868
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

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

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

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

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

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
916
   * 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
917
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
918
919
920
921
   * 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
922
   *
923
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
924
925
926
   */
  props[PROP_INTERACTIVE] =
    g_param_spec_boolean ("interactive",
927
928
                          "Interactive",
                          "Whether the widget can be swiped",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
929
930
931
932
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
933
   * 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
934
935
936
   *
   * Spacing between pages in pixels.
   *
937
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
938
939
940
   */
  props[PROP_SPACING] =
    g_param_spec_uint ("spacing",
941
942
                       "Spacing",
                       "Spacing between pages",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
943
944
945
                       0,
                       G_MAXUINT,
                       0,
946
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
947
948

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
949
   * 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
950
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
951
   * Animation duration in milliseconds, used by [method@Adw.Carousel.scroll_to].
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
952
   *
953
   * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
954
955
956
   */
  props[PROP_ANIMATION_DURATION] =
    g_param_spec_uint ("animation-duration",
957
958
                       "Animation duration",
                       "Default animation duration",
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
959
960
961
                       0, G_MAXUINT, DEFAULT_DURATION,
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

962
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
963
964
965
   * 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.
966
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
967
   * If the value is `FALSE`, dragging is only available on touch.
968
   *
969
   * Since: 1.0
970
971
972
   */
  props[PROP_ALLOW_MOUSE_DRAG] =
    g_param_spec_boolean ("allow-mouse-drag",
973
974
                          "Allow mouse drag",
                          "Whether to allow dragging with mouse pointer",
975
                          TRUE,
976
977
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

978
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
979
   * 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)
980
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
981
982
983
   * Whether the widget will respond to scroll wheel events.
   *
   * If the value is `FALSE`, wheel events will be ignored.
984
985
986
987
988
989
990
991
992
993
   *
   * 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);

994
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
995
996
997
   * 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.
998
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
999
   * If the value is `FALSE`, each swipe can only move to the adjacent pages.
1000
1001
1002
1003
1004
   *
   * Since: 1.0
   */
  props[PROP_ALLOW_LONG_SWIPES] =
    g_param_spec_boolean ("allow-long-swipes",
1005
1006
                          "Allow long swipes",
                          "Whether to allow swiping for more than one page at a time",
1007
1008
1009
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

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

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

  g_object_class_install_properties (object_class, LAST_PROP, props);

1032
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1033
   * AdwCarousel::page-changed:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1034
1035
1036
1037
   * @self: a `AdwCarousel`
   * @index: current page
   *
   * This signal is emitted after a page has been changed.
1038
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1039
1040
   * It can be used to implement "infinite scrolling" by amending the pages
   * after every scroll.
1041
   *
1042
   * Since: 1.0
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
   */
  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);

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

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1058
adw_carousel_init (AdwCarousel *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1059
{
1060
  GtkEventController *controller;
1061
1062
  AdwAnimationTarget *target;

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);
1082
1083
1084
1085
1086

  target = adw_callback_animation_target_new ((AdwAnimationTargetFunc)
                                              scroll_animation_value_cb,
                                              self, NULL);
  self->animation =
1087
    adw_timed_animation_new (GTK_WIDGET (self), 0, 1, 0, target);
1088
1089
1090

  g_signal_connect_swapped (self->animation, "done",
                            G_CALLBACK (scroll_animation_done_cb), self);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1091
1092
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1093
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1094
adw_carousel_buildable_add_child (GtkBuildable *buildable,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1095
1096
1097
1098
1099
                                  GtkBuilder   *builder,
                                  GObject      *child,
                                  const char   *type)
{
  if (GTK_IS_WIDGET (child))
1100
    adw_carousel_append (ADW_CAROUSEL (buildable), GTK_WIDGET (child));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1101
1102
1103
1104
1105
  else
    parent_buildable_iface->add_child (buildable, builder, child, type);
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1106
adw_carousel_buildable_init (GtkBuildableIface *iface)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1107
1108
1109
{
  parent_buildable_iface = g_type_interface_peek_parent (iface);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1110
  iface->add_child = adw_carousel_buildable_add_child;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
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
1162
1163
1164
1165
1166
1167
1168
1169
1170
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
1171
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1172
 * adw_carousel_new:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1173
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1174
 * Creates a new `AdwCarousel`.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1175
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1176
 * Returns: the newly created `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1177
 *
1178
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1179
 */
Ujjwal Kumar's avatar
Ujjwal Kumar committed
1180
GtkWidget *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1181
adw_carousel_new (void)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1182
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1183
  return g_object_new (ADW_TYPE_CAROUSEL, NULL);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1184
1185
1186
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1187
 * adw_carousel_prepend:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1188
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1189
1190
 * @child: a widget to add
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1191
 * Prepends @child to @self.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1192
 *
1193
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1194
1195
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1196
adw_carousel_prepend (AdwCarousel *self,
1197
                      GtkWidget   *widget)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1198
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1199
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1200
  g_return_if_fail (GTK_IS_WIDGET (widget));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1201

1202
  adw_carousel_insert (self, widget, 0);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1203
1204
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1205
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1206
 * adw_carousel_append:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1207
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1208
1209
 * @child: a widget to add
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1210
 * Appends @child to @self.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1211
1212
1213
1214
 *
 * Since: 1.0
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1215
adw_carousel_append (AdwCarousel *self,
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1216
1217
                     GtkWidget   *widget)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1218
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1219
  g_return_if_fail (GTK_IS_WIDGET (widget));
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1220

1221
  adw_carousel_insert (self, widget, -1);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1222
1223
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1224
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1225
 * adw_carousel_insert:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1226
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1227
 * @child: a widget to add
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1228
 * @position: the position to insert @child at
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1229
1230
1231
1232
1233
1234
 *
 * 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.
 *
1235
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1236
1237
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1238
adw_carousel_insert (AdwCarousel *self,
1239
                     GtkWidget   *widget,
Christopher Davis's avatar
Christopher Davis committed
1240
                     int          position)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1241
{
1242
1243
1244
  ChildInfo *info;
  GList *prev_link = NULL;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1245
  g_return_if_fail (ADW_IS_CAROUSEL (self));
1246
  g_return_if_fail (GTK_IS_WIDGET (widget));
1247
  g_return_if_fail (position >= -1);
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257

  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
1258

1259
1260
1261
1262
1263
1264
1265
  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
1266
1267
}
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1268
 * adw_carousel_reorder:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1269
 * @self: a `AdwCarousel`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1270
 * @child: a widget to add
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1271
 * @position: the position to move @child to
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1272
1273
1274
1275
 *
 * 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
1276
 * at the end.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1277
 *
1278
 * Since: 1.0
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1279
1280
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
1281
adw_carousel_reorder (AdwCarousel *self,
1282
                      GtkWidget   *child,
Christopher Davis's avatar
Christopher Davis committed
1283
                      int          position)