1 /*
2  * Copyright (C) 2020-2021 Purism SPC
3  *
4  * SPDX-License-Identifier: LGPL-2.1-or-later
5  *
6  * Author: Alexander Mikhaylenko <alexander.mikhaylenko@puri.sm>
7  */
8 
9 #include "config.h"
10 
11 #include "adw-tab-box-private.h"
12 #include "adw-animation-util-private.h"
13 #include "adw-animation-private.h"
14 #include "adw-tab-private.h"
15 #include "adw-tab-bar-private.h"
16 #include "adw-tab-view-private.h"
17 #include <math.h>
18 
19 /* Border collapsing without glitches */
20 #define OVERLAP 1
21 #define DND_THRESHOLD_MULTIPLIER 4
22 #define DROP_SWITCH_TIMEOUT 500
23 
24 #define AUTOSCROLL_SPEED 2.5
25 
26 #define OPEN_ANIMATION_DURATION 200
27 #define CLOSE_ANIMATION_DURATION 200
28 #define FOCUS_ANIMATION_DURATION 200
29 #define SCROLL_ANIMATION_DURATION 200
30 #define RESIZE_ANIMATION_DURATION 200
31 #define REORDER_ANIMATION_DURATION 250
32 #define ICON_RESIZE_ANIMATION_DURATION 200
33 
34 #define MAX_TAB_WIDTH_NON_EXPAND 220
35 
36 typedef enum {
37   TAB_RESIZE_NORMAL,
38   TAB_RESIZE_FIXED_TAB_WIDTH,
39   TAB_RESIZE_FIXED_END_PADDING
40 } TabResizeMode;
41 
42 typedef struct {
43   GdkDrag *drag;
44 
45   AdwTab *tab;
46   GtkBorder tab_margin;
47 
48   int hotspot_x;
49   int hotspot_y;
50 
51   int width;
52   int target_width;
53   AdwAnimation *resize_animation;
54 } DragIcon;
55 
56 typedef struct {
57   AdwTabPage *page;
58   AdwTab *tab;
59 
60   int pos;
61   int width;
62   int last_width;
63 
64   double end_reorder_offset;
65   double reorder_offset;
66 
67   AdwAnimation *reorder_animation;
68   gboolean reorder_ignore_bounds;
69 
70   double appear_progress;
71   AdwAnimation *appear_animation;
72 
73   gulong notify_needs_attention_id;
74 } TabInfo;
75 
76 struct _AdwTabBox
77 {
78   GtkWidget parent_instance;
79 
80   gboolean pinned;
81   AdwTabBar *tab_bar;
82   AdwTabView *view;
83   GtkAdjustment *adjustment;
84   gboolean needs_attention_left;
85   gboolean needs_attention_right;
86   gboolean expand_tabs;
87   gboolean inverted;
88 
89   GtkEventController *view_drop_target;
90   GtkGesture *drag_gesture;
91 
92   GList *tabs;
93   int n_tabs;
94 
95   GtkPopover *context_menu;
96 
97   int allocated_width;
98   int last_width;
99   int end_padding;
100   int initial_end_padding;
101   TabResizeMode tab_resize_mode;
102   AdwAnimation *resize_animation;
103 
104   TabInfo *selected_tab;
105 
106   gboolean hovering;
107   TabInfo *pressed_tab;
108   TabInfo *reordered_tab;
109   AdwAnimation *reorder_animation;
110 
111   int reorder_start_pos;
112   int reorder_x;
113   int reorder_y;
114   int reorder_index;
115   int reorder_window_x;
116   gboolean continue_reorder;
117   gboolean indirect_reordering;
118 
119   gboolean dragging;
120   double drag_offset_x;
121   double drag_offset_y;
122 
123   guint drag_autoscroll_cb_id;
124   gint64 drag_autoscroll_prev_time;
125 
126   AdwTabPage *detached_page;
127   int detached_index;
128   TabInfo *reorder_placeholder;
129   AdwTabPage *placeholder_page;
130   int placeholder_scroll_offset;
131   gboolean can_remove_placeholder;
132   DragIcon *drag_icon;
133   gboolean should_detach_into_new_window;
134 
135   TabInfo *drop_target_tab;
136   guint drop_switch_timeout_id;
137   guint reset_drop_target_tab_id;
138   double drop_target_x;
139 
140   struct {
141     TabInfo *info;
142     int pos;
143     gint64 duration;
144     gboolean keep_selected_visible;
145   } scheduled_scroll;
146 
147   AdwAnimation *scroll_animation;
148   gboolean scroll_animation_done;
149   double scroll_animation_from;
150   double scroll_animation_offset;
151   TabInfo *scroll_animation_tab;
152   gboolean block_scrolling;
153   double adjustment_prev_value;
154 
155   GdkDragAction extra_drag_actions;
156   GType *extra_drag_types;
157   gsize extra_drag_n_types;
158 };
159 
160 G_DEFINE_TYPE_WITH_CODE (AdwTabBox, adw_tab_box, GTK_TYPE_WIDGET,
161                          G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
162 
163 enum {
164   PROP_0,
165   PROP_PINNED,
166   PROP_TAB_BAR,
167   PROP_VIEW,
168   PROP_NEEDS_ATTENTION_LEFT,
169   PROP_NEEDS_ATTENTION_RIGHT,
170   PROP_RESIZE_FROZEN,
171   PROP_HADJUSTMENT,
172   PROP_VADJUSTMENT,
173   PROP_HSCROLL_POLICY,
174   PROP_VSCROLL_POLICY,
175   LAST_PROP = PROP_HADJUSTMENT
176 };
177 
178 static GParamSpec *props[LAST_PROP];
179 
180 enum {
181   SIGNAL_STOP_KINETIC_SCROLLING,
182   SIGNAL_EXTRA_DRAG_DROP,
183   SIGNAL_LAST_SIGNAL,
184 };
185 
186 static guint signals[SIGNAL_LAST_SIGNAL];
187 
188 /* Helpers */
189 
190 static void
remove_and_free_tab_info(TabInfo * info)191 remove_and_free_tab_info (TabInfo *info)
192 {
193   gtk_widget_unparent (GTK_WIDGET (info->tab));
194 
195   g_free (info);
196 }
197 
198 static inline int
get_tab_position(AdwTabBox * self,TabInfo * info)199 get_tab_position (AdwTabBox *self,
200                   TabInfo   *info)
201 {
202   if (info == self->reordered_tab)
203     return self->reorder_window_x;
204 
205   return info->pos;
206 }
207 
208 static inline TabInfo *
find_tab_info_at(AdwTabBox * self,double x)209 find_tab_info_at (AdwTabBox *self,
210                   double     x)
211 {
212   GList *l;
213 
214   if (self->reordered_tab) {
215     int pos = get_tab_position (self, self->reordered_tab);
216 
217     if (pos <= x && x < pos + self->reordered_tab->width)
218       return self->reordered_tab;
219   }
220 
221   for (l = self->tabs; l; l = l->next) {
222     TabInfo *info = l->data;
223 
224     if (info != self->reordered_tab &&
225         info->pos <= x && x < info->pos + info->width)
226       return info;
227   }
228 
229   return NULL;
230 }
231 
232 static inline GList *
find_link_for_page(AdwTabBox * self,AdwTabPage * page)233 find_link_for_page (AdwTabBox  *self,
234                     AdwTabPage *page)
235 {
236   GList *l;
237 
238   for (l = self->tabs; l; l = l->next) {
239     TabInfo *info = l->data;
240 
241     if (info->page == page)
242       return l;
243   }
244 
245   return NULL;
246 }
247 
248 static inline TabInfo *
find_info_for_page(AdwTabBox * self,AdwTabPage * page)249 find_info_for_page (AdwTabBox  *self,
250                     AdwTabPage *page)
251 {
252   GList *l = find_link_for_page (self, page);
253 
254   return l ? l->data : NULL;
255 }
256 
257 static GList *
find_nth_alive_tab(AdwTabBox * self,guint position)258 find_nth_alive_tab (AdwTabBox *self,
259                     guint      position)
260 {
261   GList *l;
262 
263   for (l = self->tabs; l; l = l->next) {
264     TabInfo *info = l->data;
265 
266     if (!info->page)
267         continue;
268 
269     if (!position--)
270         return l;
271   }
272 
273   return NULL;
274 }
275 
276 static inline int
calculate_tab_width(TabInfo * info,int base_width)277 calculate_tab_width (TabInfo *info,
278                      int      base_width)
279 {
280   return OVERLAP + (int) floor ((base_width - OVERLAP) * info->appear_progress);
281 }
282 
283 static int
get_base_tab_width(AdwTabBox * self,gboolean target)284 get_base_tab_width (AdwTabBox *self,
285                     gboolean   target)
286 {
287   double max_progress = 0;
288   double n = 0;
289   double used_width;
290   GList *l;
291   int ret;
292 
293   for (l = self->tabs; l; l = l->next) {
294     TabInfo *info = l->data;
295 
296     max_progress = MAX (max_progress, info->appear_progress);
297     n += info->appear_progress;
298   }
299 
300   used_width = (self->allocated_width + (n + 1) * OVERLAP - (target ? 0 : self->end_padding)) * max_progress;
301 
302   ret = (int) ceil (used_width / n);
303 
304   if (!self->expand_tabs)
305     ret = MIN (ret, MAX_TAB_WIDTH_NON_EXPAND + OVERLAP);
306 
307   return ret;
308 }
309 
310 static int
predict_tab_width(AdwTabBox * self,TabInfo * info,gboolean assume_placeholder)311 predict_tab_width (AdwTabBox *self,
312                    TabInfo   *info,
313                    gboolean   assume_placeholder)
314 {
315   int n;
316   int width = self->allocated_width;
317   int min;
318 
319   if (self->pinned)
320     n = adw_tab_view_get_n_pinned_pages (self->view);
321   else
322     n = adw_tab_view_get_n_pages (self->view) - adw_tab_view_get_n_pinned_pages (self->view);
323 
324   if (assume_placeholder)
325       n++;
326 
327   width += OVERLAP * (n + 1) - self->end_padding;
328 
329   /* Tabs have 0 minimum width, we need natural width instead */
330   gtk_widget_measure (GTK_WIDGET (info->tab), GTK_ORIENTATION_HORIZONTAL, -1,
331                       NULL, &min, NULL, NULL);
332 
333   if (self->expand_tabs)
334     return MAX ((int) floor (width / (double) n), min);
335   else
336     return CLAMP ((int) floor (width / (double) n), min, MAX_TAB_WIDTH_NON_EXPAND);
337 }
338 
339 static int
calculate_tab_offset(AdwTabBox * self,TabInfo * info,gboolean target)340 calculate_tab_offset (AdwTabBox *self,
341                       TabInfo   *info,
342                       gboolean target)
343 {
344   int width;
345 
346   if (!self->reordered_tab)
347       return 0;
348 
349   width = (target ? adw_tab_get_display_width (self->reordered_tab->tab) : self->reordered_tab->width) - OVERLAP;
350 
351   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
352       width = -width;
353 
354   return (int) round (width * (target ? info->end_reorder_offset : info->reorder_offset));
355 }
356 
357 static void
get_visible_range(AdwTabBox * self,int * lower,int * upper)358 get_visible_range (AdwTabBox *self,
359                    int       *lower,
360                    int       *upper)
361 {
362   int min = -OVERLAP;
363   int max = self->allocated_width + OVERLAP;
364 
365   if (self->pinned) {
366     if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
367       min += OVERLAP;
368     else
369       max -= OVERLAP;
370   }
371 
372   if (self->adjustment) {
373     double value = gtk_adjustment_get_value (self->adjustment);
374     double page_size = gtk_adjustment_get_page_size (self->adjustment);
375 
376     min = MAX (min, (int) floor (value) - OVERLAP);
377     max = MIN (max, (int) ceil (value + page_size) + OVERLAP);
378   }
379 
380   if (lower)
381     *lower = min;
382 
383   if (upper)
384     *upper = max;
385 }
386 
387 static inline gboolean
is_touchscreen(GtkGesture * gesture)388 is_touchscreen (GtkGesture *gesture)
389 {
390   GtkEventController *controller = GTK_EVENT_CONTROLLER (gesture);
391   GdkDevice *device = gtk_event_controller_get_current_event_device (controller);
392   GdkInputSource input_source = gdk_device_get_source (device);
393 
394   return input_source == GDK_SOURCE_TOUCHSCREEN;
395 }
396 
397 /* Tab resize delay */
398 
399 static void
resize_animation_value_cb(double value,gpointer user_data)400 resize_animation_value_cb (double   value,
401                            gpointer user_data)
402 {
403   AdwTabBox *self = ADW_TAB_BOX (user_data);
404   double target_end_padding = 0;
405 
406   if (!self->expand_tabs) {
407     int predicted_tab_width = get_base_tab_width (self, TRUE);
408     GList *l;
409 
410     target_end_padding = self->allocated_width + OVERLAP;
411 
412     for (l = self->tabs; l; l = l->next) {
413       TabInfo *info = l->data;
414 
415       target_end_padding -= calculate_tab_width (info, predicted_tab_width) - OVERLAP;
416     }
417 
418     target_end_padding = MAX (target_end_padding, 0);
419   }
420 
421   self->end_padding = (int) floor (adw_lerp (self->initial_end_padding, target_end_padding, value));
422 
423   gtk_widget_queue_resize (GTK_WIDGET (self));
424 }
425 
426 static void
resize_animation_done_cb(gpointer user_data)427 resize_animation_done_cb (gpointer user_data)
428 {
429   AdwTabBox *self = ADW_TAB_BOX (user_data);
430 
431   self->end_padding = 0;
432   gtk_widget_queue_resize (GTK_WIDGET (self));
433 
434   g_clear_object (&self->resize_animation);
435 }
436 
437 static void
set_tab_resize_mode(AdwTabBox * self,TabResizeMode mode)438 set_tab_resize_mode (AdwTabBox     *self,
439                      TabResizeMode  mode)
440 {
441   gboolean notify;
442 
443   if (self->tab_resize_mode == mode)
444     return;
445 
446   if (mode == TAB_RESIZE_FIXED_TAB_WIDTH) {
447     GList *l;
448 
449     self->last_width = self->allocated_width;
450 
451     for (l = self->tabs; l; l = l->next) {
452       TabInfo *info = l->data;
453 
454       if (info->appear_animation)
455         info->last_width = adw_tab_get_display_width (info->tab);
456       else
457         info->last_width = info->width;
458     }
459   } else {
460     self->last_width = 0;
461   }
462 
463   if (mode == TAB_RESIZE_NORMAL) {
464     self->initial_end_padding = self->end_padding;
465 
466     self->resize_animation =
467       adw_animation_new (GTK_WIDGET (self), 0, 1,
468                          RESIZE_ANIMATION_DURATION,
469                          resize_animation_value_cb,
470                          self);
471 
472     g_signal_connect_swapped (self->resize_animation, "done", G_CALLBACK (resize_animation_done_cb), self);
473 
474     adw_animation_start (self->resize_animation);
475   }
476 
477   notify = (self->tab_resize_mode == TAB_RESIZE_NORMAL) !=
478            (mode == TAB_RESIZE_NORMAL);
479 
480   self->tab_resize_mode = mode;
481 
482   if (notify)
483     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RESIZE_FROZEN]);
484 }
485 
486 /* Hover */
487 
488 static void
update_hover(AdwTabBox * self)489 update_hover (AdwTabBox *self)
490 {
491   if (!self->dragging && !self->hovering)
492     set_tab_resize_mode (self, TAB_RESIZE_NORMAL);
493 }
494 
495 static void
motion_cb(AdwTabBox * self,double x,double y,GtkEventController * controller)496 motion_cb (AdwTabBox          *self,
497            double              x,
498            double              y,
499            GtkEventController *controller)
500 {
501   GdkDevice *device = gtk_event_controller_get_current_event_device (controller);
502   GdkInputSource input_source = gdk_device_get_source (device);
503 
504   if (input_source == GDK_SOURCE_TOUCHSCREEN)
505     return;
506 
507   if (self->hovering)
508     return;
509 
510   self->hovering = TRUE;
511 
512   update_hover (self);
513 }
514 
515 static void
leave_cb(AdwTabBox * self,GtkEventController * controller)516 leave_cb (AdwTabBox          *self,
517           GtkEventController *controller)
518 {
519   self->hovering = FALSE;
520 
521   update_hover (self);
522 }
523 
524 /* Keybindings */
525 
526 static void
focus_tab_cb(AdwTabBox * self,GVariant * args)527 focus_tab_cb (AdwTabBox *self,
528               GVariant  *args)
529 {
530   GtkDirectionType direction;
531   gboolean last, is_rtl, success;
532 
533   if (!self->view || !self->selected_tab)
534     return;
535 
536   g_variant_get (args, "(hb)", &direction, &last);
537 
538   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
539   success = last;
540 
541   if (direction == GTK_DIR_LEFT)
542     direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
543   else if (direction == GTK_DIR_RIGHT)
544     direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
545 
546   if (direction == GTK_DIR_TAB_BACKWARD) {
547     if (last)
548       success = adw_tab_view_select_first_page (self->view);
549     else
550       success = adw_tab_view_select_previous_page (self->view);
551   } else if (direction == GTK_DIR_TAB_FORWARD) {
552     if (last)
553       success = adw_tab_view_select_last_page (self->view);
554     else
555       success = adw_tab_view_select_next_page (self->view);
556   }
557 
558   if (!success)
559     gtk_widget_error_bell (GTK_WIDGET (self));
560 }
561 
562 static void
reorder_tab_cb(AdwTabBox * self,GVariant * args)563 reorder_tab_cb (AdwTabBox *self,
564                 GVariant  *args)
565 {
566   GtkDirectionType direction;
567   gboolean last, is_rtl, success;
568 
569   if (!self->view || !self->selected_tab || !self->selected_tab->page)
570     return;
571 
572   g_variant_get (args, "(hb)", &direction, &last);
573 
574   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
575   success = last;
576 
577   if (direction == GTK_DIR_LEFT)
578     direction = is_rtl ? GTK_DIR_TAB_FORWARD : GTK_DIR_TAB_BACKWARD;
579   else if (direction == GTK_DIR_RIGHT)
580     direction = is_rtl ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD;
581 
582   if (direction == GTK_DIR_TAB_BACKWARD) {
583     if (last)
584       success = adw_tab_view_reorder_first (self->view, self->selected_tab->page);
585     else
586       success = adw_tab_view_reorder_backward (self->view, self->selected_tab->page);
587   } else if (direction == GTK_DIR_TAB_FORWARD) {
588     if (last)
589       success = adw_tab_view_reorder_last (self->view, self->selected_tab->page);
590     else
591       success = adw_tab_view_reorder_forward (self->view, self->selected_tab->page);
592   }
593 
594   if (!success)
595     gtk_widget_error_bell (GTK_WIDGET (self));
596 }
597 
598 static void
add_focus_bindings(GtkWidgetClass * widget_class,guint keysym,GtkDirectionType direction,gboolean last)599 add_focus_bindings (GtkWidgetClass   *widget_class,
600                     guint             keysym,
601                     GtkDirectionType  direction,
602                     gboolean          last)
603 {
604   /* All keypad keysyms are aligned at the same order as non-keypad ones */
605   guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
606 
607   gtk_widget_class_add_binding (widget_class, keysym, 0,
608                                 (GtkShortcutFunc) focus_tab_cb,
609                                 "(hb)", direction, last);
610   gtk_widget_class_add_binding (widget_class, keypad_keysym, 0,
611                                 (GtkShortcutFunc) focus_tab_cb,
612                                 "(hb)", direction, last);
613 }
614 
615 static void
add_reorder_bindings(GtkWidgetClass * widget_class,guint keysym,GtkDirectionType direction,gboolean last)616 add_reorder_bindings (GtkWidgetClass   *widget_class,
617                       guint             keysym,
618                       GtkDirectionType  direction,
619                       gboolean          last)
620 {
621   /* All keypad keysyms are aligned at the same order as non-keypad ones */
622   guint keypad_keysym = keysym - GDK_KEY_Left + GDK_KEY_KP_Left;
623 
624   gtk_widget_class_add_binding (widget_class, keysym, GDK_SHIFT_MASK,
625                                 (GtkShortcutFunc) reorder_tab_cb,
626                                 "(hb)", direction, last);
627   gtk_widget_class_add_binding (widget_class, keypad_keysym, GDK_SHIFT_MASK,
628                                 (GtkShortcutFunc) reorder_tab_cb,
629                                 "(hb)", direction, last);
630 }
631 
632 static void
activate_tab(AdwTabBox * self)633 activate_tab (AdwTabBox *self)
634 {
635   GtkWidget *child;
636 
637   if (!self->selected_tab || !self->selected_tab->page)
638     return;
639 
640   child = adw_tab_page_get_child (self->selected_tab->page);
641 
642   gtk_widget_grab_focus (child);
643 }
644 
645 /* Scrolling */
646 
647 static void
update_visible(AdwTabBox * self)648 update_visible (AdwTabBox *self)
649 {
650   gboolean left = FALSE, right = FALSE;
651   GList *l;
652   double value, page_size;
653 
654   if (!self->adjustment)
655     return;
656 
657   value = gtk_adjustment_get_value (self->adjustment);
658   page_size = gtk_adjustment_get_page_size (self->adjustment);
659 
660   if (!self->adjustment)
661       return;
662 
663   for (l = self->tabs; l; l = l->next) {
664     TabInfo *info = l->data;
665     int pos;
666 
667     if (!info->page)
668       continue;
669 
670     pos = get_tab_position (self, info);
671 
672     adw_tab_set_fully_visible (info->tab,
673                                pos + OVERLAP >= value &&
674                                pos + info->width - OVERLAP <= value + page_size);
675 
676     if (!adw_tab_page_get_needs_attention (info->page))
677       continue;
678 
679     if (pos + info->width / 2.0 <= value)
680       left = TRUE;
681 
682     if (pos + info->width / 2.0 >= value + page_size)
683       right = TRUE;
684   }
685 
686   if (self->needs_attention_left != left) {
687     self->needs_attention_left = left;
688     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION_LEFT]);
689   }
690 
691   if (self->needs_attention_right != right) {
692     self->needs_attention_right = right;
693     g_object_notify_by_pspec (G_OBJECT (self), props[PROP_NEEDS_ATTENTION_RIGHT]);
694   }
695 }
696 
697 static double
get_scroll_animation_value(AdwTabBox * self)698 get_scroll_animation_value (AdwTabBox *self)
699 {
700   double to, value;
701 
702   g_assert (self->scroll_animation);
703 
704   to = self->scroll_animation_offset;
705 
706   if (self->scroll_animation_tab) {
707     double page_size = gtk_adjustment_get_page_size (self->adjustment);
708 
709     to += get_tab_position (self, self->scroll_animation_tab);
710     to = CLAMP (to, 0, self->allocated_width - page_size);
711   }
712 
713   value = adw_animation_get_value (self->scroll_animation);
714 
715   return round (adw_lerp (self->scroll_animation_from, to, value));
716 }
717 
718 static gboolean
drop_switch_timeout_cb(AdwTabBox * self)719 drop_switch_timeout_cb (AdwTabBox *self)
720 {
721   self->drop_switch_timeout_id = 0;
722   adw_tab_view_set_selected_page (self->view,
723                                   self->drop_target_tab->page);
724 
725   return G_SOURCE_REMOVE;
726 }
727 
728 static void
set_drop_target_tab(AdwTabBox * self,TabInfo * info)729 set_drop_target_tab (AdwTabBox *self,
730                      TabInfo   *info)
731 {
732   if (self->drop_target_tab == info)
733     return;
734 
735   if (self->drop_target_tab)
736     g_clear_handle_id (&self->drop_switch_timeout_id, g_source_remove);
737 
738   self->drop_target_tab = info;
739 
740   if (self->drop_target_tab) {
741     self->drop_switch_timeout_id =
742       g_timeout_add (DROP_SWITCH_TIMEOUT,
743                      (GSourceFunc) drop_switch_timeout_cb,
744                      self);
745   }
746 }
747 
748 static void
adjustment_value_changed_cb(AdwTabBox * self)749 adjustment_value_changed_cb (AdwTabBox *self)
750 {
751   double value = gtk_adjustment_get_value (self->adjustment);
752 
753   update_visible (self);
754 
755   if (self->drop_target_tab) {
756     self->drop_target_x += (value - self->adjustment_prev_value);
757     set_drop_target_tab (self, find_tab_info_at (self, self->drop_target_x));
758   }
759 
760   self->adjustment_prev_value = value;
761 
762   if (self->block_scrolling)
763       return;
764 
765   if (self->scroll_animation)
766     adw_animation_stop (self->scroll_animation);
767 
768   gtk_widget_queue_allocate (GTK_WIDGET (self));
769 }
770 
771 static void
scroll_animation_value_cb(double value,gpointer user_data)772 scroll_animation_value_cb (double   value,
773                            gpointer user_data)
774 {
775   gtk_widget_queue_resize (GTK_WIDGET (user_data));
776 }
777 
778 static void
scroll_animation_done_cb(gpointer user_data)779 scroll_animation_done_cb (gpointer user_data)
780 {
781   AdwTabBox *self = ADW_TAB_BOX (user_data);
782 
783   self->scroll_animation_done = TRUE;
784   gtk_widget_queue_resize (GTK_WIDGET (self));
785 }
786 
787 static void
animate_scroll(AdwTabBox * self,TabInfo * info,double offset,gint64 duration)788 animate_scroll (AdwTabBox *self,
789                 TabInfo   *info,
790                 double     offset,
791                 gint64     duration)
792 {
793   if (!self->adjustment)
794     return;
795 
796   g_signal_emit (self, signals[SIGNAL_STOP_KINETIC_SCROLLING], 0);
797 
798   if (self->scroll_animation)
799     adw_animation_stop (self->scroll_animation);
800 
801   g_clear_object (&self->scroll_animation);
802   self->scroll_animation_done = FALSE;
803   self->scroll_animation_from = gtk_adjustment_get_value (self->adjustment);
804   self->scroll_animation_tab = info;
805   self->scroll_animation_offset = offset;
806 
807   /* The actual update will be done in size_allocate(). After the animation
808    * finishes, don't remove it right away, it will be done in size-allocate as
809    * well after one last update, so that we don't miss the last frame.
810    */
811 
812   self->scroll_animation =
813     adw_animation_new (GTK_WIDGET (self), 0, 1, duration,
814                        scroll_animation_value_cb,
815                        self);
816 
817   g_signal_connect_swapped (self->scroll_animation, "done", G_CALLBACK (scroll_animation_done_cb), self);
818 
819   adw_animation_start (self->scroll_animation);
820 }
821 
822 static void
animate_scroll_relative(AdwTabBox * self,double delta,gint64 duration)823 animate_scroll_relative (AdwTabBox *self,
824                          double     delta,
825                          gint64     duration)
826 {
827   double current_value = gtk_adjustment_get_value (self->adjustment);
828 
829   if (self->scroll_animation) {
830     current_value = self->scroll_animation_offset;
831 
832     if (self->scroll_animation_tab)
833       current_value += get_tab_position (self, self->scroll_animation_tab);
834   }
835 
836   animate_scroll (self, NULL, current_value + delta, duration);
837 }
838 
839 static void
scroll_to_tab_full(AdwTabBox * self,TabInfo * info,int pos,gint64 duration,gboolean keep_selected_visible)840 scroll_to_tab_full (AdwTabBox *self,
841                     TabInfo   *info,
842                     int        pos,
843                     gint64     duration,
844                     gboolean   keep_selected_visible)
845 {
846   int tab_width;
847   double padding, value, page_size;
848 
849   if (!self->adjustment)
850     return;
851 
852   tab_width = info->width;
853 
854   if (tab_width < 0) {
855     self->scheduled_scroll.info = info;
856     self->scheduled_scroll.pos = pos;
857     self->scheduled_scroll.duration = duration;
858     self->scheduled_scroll.keep_selected_visible = keep_selected_visible;
859 
860     gtk_widget_queue_allocate (GTK_WIDGET (self));
861 
862     return;
863   }
864 
865   if (info->appear_animation)
866     tab_width = adw_tab_get_display_width (info->tab);
867 
868   value = gtk_adjustment_get_value (self->adjustment);
869   page_size = gtk_adjustment_get_page_size (self->adjustment);
870 
871   padding = MIN (tab_width, page_size - tab_width) / 2.0;
872 
873   if (pos < 0)
874     pos = get_tab_position (self, info);
875 
876   if (pos + OVERLAP < value)
877     animate_scroll (self, info, -padding, duration);
878   else if (pos + tab_width - OVERLAP > value + page_size)
879     animate_scroll (self, info, tab_width + padding - page_size, duration);
880 }
881 
882 static void
scroll_to_tab(AdwTabBox * self,TabInfo * info,gint64 duration)883 scroll_to_tab (AdwTabBox *self,
884                TabInfo   *info,
885                gint64     duration)
886 {
887   scroll_to_tab_full (self, info, -1, duration, FALSE);
888 }
889 
890 static gboolean
scroll_cb(AdwTabBox * self,double dx,double dy,GtkEventController * controller)891 scroll_cb (AdwTabBox          *self,
892            double              dx,
893            double              dy,
894            GtkEventController *controller)
895 {
896   double page_size, pow_unit, scroll_unit;
897   GdkDevice *source_device;
898   GdkInputSource input_source;
899 
900   if (!self->adjustment)
901     return GDK_EVENT_PROPAGATE;
902 
903   source_device = gtk_event_controller_get_current_event_device (controller);
904   input_source = gdk_device_get_source (source_device);
905 
906   if (input_source != GDK_SOURCE_MOUSE)
907     return GDK_EVENT_PROPAGATE;
908 
909   page_size = gtk_adjustment_get_page_size (self->adjustment);
910 
911   /* Copied from gtkrange.c, _gtk_range_get_wheel_delta() */
912   pow_unit = pow (page_size, 2.0 / 3.0);
913   scroll_unit = MIN (pow_unit, page_size / 2.0);
914 
915   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
916     dy = -dy;
917 
918   animate_scroll_relative (self, dy * scroll_unit, SCROLL_ANIMATION_DURATION);
919 
920   return GDK_EVENT_STOP;
921 }
922 
923 /* Reordering */
924 
925 static void
force_end_reordering(AdwTabBox * self)926 force_end_reordering (AdwTabBox *self)
927 {
928   GList *l;
929 
930   if (self->dragging || !self->reordered_tab)
931     return;
932 
933   if (self->reorder_animation)
934     adw_animation_stop (self->reorder_animation);
935 
936   for (l = self->tabs; l; l = l->next) {
937     TabInfo *info = l->data;
938 
939     if (info->reorder_animation)
940       adw_animation_stop (info->reorder_animation);
941   }
942 }
943 
944 static void
check_end_reordering(AdwTabBox * self)945 check_end_reordering (AdwTabBox *self)
946 {
947   GList *l;
948 
949   if (self->dragging || !self->reordered_tab || self->continue_reorder)
950     return;
951 
952   if (self->reorder_animation)
953     return;
954 
955   for (l = self->tabs; l; l = l->next) {
956     TabInfo *info = l->data;
957 
958     if (info->reorder_animation)
959       return;
960   }
961 
962   for (l = self->tabs; l; l = l->next) {
963     TabInfo *info = l->data;
964 
965     info->end_reorder_offset = 0;
966     info->reorder_offset = 0;
967   }
968 
969   self->reordered_tab->reorder_ignore_bounds = FALSE;
970 
971   self->tabs = g_list_remove (self->tabs, self->reordered_tab);
972   self->tabs = g_list_insert (self->tabs, self->reordered_tab, self->reorder_index);
973 
974   gtk_widget_queue_allocate (GTK_WIDGET (self));
975 
976   self->reordered_tab = NULL;
977 }
978 
979 static void
start_reordering(AdwTabBox * self,TabInfo * info)980 start_reordering (AdwTabBox *self,
981                   TabInfo   *info)
982 {
983   self->reordered_tab = info;
984 
985   /* The reordered tab should be displayed above everything else */
986   gtk_widget_insert_before (GTK_WIDGET (self->reordered_tab->tab),
987                             GTK_WIDGET (self), NULL);
988 
989   gtk_widget_queue_allocate (GTK_WIDGET (self));
990 }
991 
992 static int
get_reorder_position(AdwTabBox * self)993 get_reorder_position (AdwTabBox *self)
994 {
995   int lower, upper;
996 
997   if (self->reordered_tab->reorder_ignore_bounds)
998     return self->reorder_x;
999 
1000   get_visible_range (self, &lower, &upper);
1001 
1002   return CLAMP (self->reorder_x, lower, upper - self->reordered_tab->width);
1003 }
1004 
1005 static void
reorder_animation_value_cb(double value,gpointer user_data)1006 reorder_animation_value_cb (double   value,
1007                             gpointer user_data)
1008 {
1009   TabInfo *dest_tab = user_data;
1010   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (dest_tab->tab));
1011   AdwTabBox *self = ADW_TAB_BOX (parent);
1012   gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1013   double x1, x2;
1014 
1015   x1 = get_reorder_position (self);
1016   x2 = dest_tab->pos - calculate_tab_offset (self, dest_tab, FALSE);
1017 
1018   if (dest_tab->end_reorder_offset * (is_rtl ? 1 : -1) > 0)
1019     x2 += dest_tab->width - self->reordered_tab->width;
1020 
1021   self->reorder_window_x = (int) round (adw_lerp (x1, x2, value));
1022 
1023   gtk_widget_queue_allocate (GTK_WIDGET (self));
1024 }
1025 
1026 static void
reorder_animation_done_cb(gpointer user_data)1027 reorder_animation_done_cb (gpointer user_data)
1028 {
1029   TabInfo *dest_tab = user_data;
1030   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (dest_tab->tab));
1031   AdwTabBox *self = ADW_TAB_BOX (parent);
1032 
1033   g_clear_object (&self->reorder_animation);
1034   check_end_reordering (self);
1035 }
1036 
1037 static void
animate_reordering(AdwTabBox * self,TabInfo * dest_tab)1038 animate_reordering (AdwTabBox *self,
1039                     TabInfo   *dest_tab)
1040 {
1041   if (self->reorder_animation)
1042     adw_animation_stop (self->reorder_animation);
1043 
1044   self->reorder_animation =
1045     adw_animation_new (GTK_WIDGET (self), 0, 1,
1046                        REORDER_ANIMATION_DURATION,
1047                        reorder_animation_value_cb,
1048                        dest_tab);
1049 
1050   g_signal_connect_swapped (self->reorder_animation, "done", G_CALLBACK (reorder_animation_done_cb), dest_tab);
1051 
1052   adw_animation_start (self->reorder_animation);
1053 
1054   check_end_reordering (self);
1055 }
1056 
1057 static void
reorder_offset_animation_value_cb(double value,gpointer user_data)1058 reorder_offset_animation_value_cb (double   value,
1059                                    gpointer user_data)
1060 {
1061   TabInfo *info = user_data;
1062   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1063 
1064   info->reorder_offset = value;
1065   gtk_widget_queue_allocate (parent);
1066 }
1067 
1068 static void
reorder_offset_animation_done_cb(gpointer user_data)1069 reorder_offset_animation_done_cb (gpointer user_data)
1070 {
1071   TabInfo *info = user_data;
1072   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1073   AdwTabBox *self = ADW_TAB_BOX (parent);
1074 
1075   g_clear_object (&info->reorder_animation);
1076   check_end_reordering (self);
1077 }
1078 
1079 static void
animate_reorder_offset(AdwTabBox * self,TabInfo * info,double offset)1080 animate_reorder_offset (AdwTabBox *self,
1081                         TabInfo   *info,
1082                         double     offset)
1083 {
1084   gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1085 
1086   offset *= (is_rtl ? -1 : 1);
1087 
1088   if (info->end_reorder_offset == offset)
1089     return;
1090 
1091   info->end_reorder_offset = offset;
1092 
1093   if (info->reorder_animation)
1094     adw_animation_stop (info->reorder_animation);
1095 
1096   info->reorder_animation =
1097     adw_animation_new (GTK_WIDGET (self), info->reorder_offset, offset,
1098                        REORDER_ANIMATION_DURATION,
1099                        reorder_offset_animation_value_cb,
1100                        info);
1101 
1102   g_signal_connect_swapped (info->reorder_animation, "done", G_CALLBACK (reorder_offset_animation_done_cb), info);
1103 
1104   adw_animation_start (info->reorder_animation);
1105 }
1106 
1107 static void
reset_reorder_animations(AdwTabBox * self)1108 reset_reorder_animations (AdwTabBox *self)
1109 {
1110   int i, original_index;
1111   GList *l;
1112 
1113   if (!adw_get_enable_animations (GTK_WIDGET (self)))
1114       return;
1115 
1116   l = find_link_for_page (self, self->reordered_tab->page);
1117   original_index = g_list_position (self->tabs, l);
1118 
1119   if (self->reorder_index > original_index)
1120     for (i = 0; i < self->reorder_index - original_index; i++) {
1121       l = l->next;
1122       animate_reorder_offset (self, l->data, 0);
1123     }
1124 
1125   if (self->reorder_index < original_index)
1126     for (i = 0; i < original_index - self->reorder_index; i++) {
1127       l = l->prev;
1128       animate_reorder_offset (self, l->data, 0);
1129     }
1130 }
1131 
1132 static void
page_reordered_cb(AdwTabBox * self,AdwTabPage * page,int index)1133 page_reordered_cb (AdwTabBox  *self,
1134                    AdwTabPage *page,
1135                    int         index)
1136 {
1137   GList *link;
1138   int original_index;
1139   TabInfo *info, *dest_tab;
1140   gboolean is_rtl;
1141 
1142   if (adw_tab_page_get_pinned (page) != self->pinned)
1143     return;
1144 
1145   self->continue_reorder = self->reordered_tab && page == self->reordered_tab->page;
1146 
1147   if (self->continue_reorder)
1148     reset_reorder_animations (self);
1149   else
1150     force_end_reordering (self);
1151 
1152   link = find_link_for_page (self, page);
1153   info = link->data;
1154   original_index = g_list_position (self->tabs, link);
1155 
1156   if (!self->continue_reorder)
1157     start_reordering (self, info);
1158 
1159   if (self->continue_reorder)
1160     self->reorder_x = self->reorder_window_x;
1161   else
1162     self->reorder_x = info->pos;
1163 
1164   self->reorder_index = index;
1165 
1166   if (!self->pinned)
1167     self->reorder_index -= adw_tab_view_get_n_pinned_pages (self->view);
1168 
1169   dest_tab = g_list_nth_data (self->tabs, self->reorder_index);
1170 
1171   if (info == self->selected_tab)
1172     scroll_to_tab_full (self, self->selected_tab, dest_tab->pos, REORDER_ANIMATION_DURATION, FALSE);
1173 
1174   animate_reordering (self, dest_tab);
1175 
1176   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1177 
1178   /* If animations are disabled, animate_reordering() animation will have
1179    * already finished and called check_end_reordering () by this point, so
1180    * it's too late to animate these, so we get a crash.
1181    */
1182 
1183   if (adw_get_enable_animations (GTK_WIDGET (self)) &&
1184       gtk_widget_get_mapped (GTK_WIDGET (self))) {
1185     int i;
1186 
1187     if (self->reorder_index > original_index)
1188       for (i = 0; i < self->reorder_index - original_index; i++) {
1189         link = link->next;
1190         animate_reorder_offset (self, link->data, is_rtl ? 1 : -1);
1191       }
1192 
1193     if (self->reorder_index < original_index)
1194       for (i = 0; i < original_index - self->reorder_index; i++) {
1195         link = link->prev;
1196         animate_reorder_offset (self, link->data, is_rtl ? -1 : 1);
1197       }
1198   }
1199 
1200   self->continue_reorder = FALSE;
1201 }
1202 
1203 static void
update_drag_reodering(AdwTabBox * self)1204 update_drag_reodering (AdwTabBox *self)
1205 {
1206   gboolean is_rtl, after_selected, found_index;
1207   int x;
1208   int i = 0;
1209   int width;
1210   GList *l;
1211 
1212   if (!self->dragging)
1213     return;
1214 
1215   x = get_reorder_position (self);
1216 
1217   width = adw_tab_get_display_width (self->reordered_tab->tab);
1218 
1219   self->reorder_window_x = x;
1220 
1221   gtk_widget_queue_allocate (GTK_WIDGET (self));
1222 
1223   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1224   after_selected = FALSE;
1225   found_index = FALSE;
1226 
1227   for (l = self->tabs; l; l = l->next) {
1228     TabInfo *info = l->data;
1229     int center = info->pos - calculate_tab_offset (self, info, FALSE) + info->width / 2;
1230     double offset = 0;
1231 
1232     if (x + width > center && center > x &&
1233         (!found_index || after_selected)) {
1234       self->reorder_index = i;
1235       found_index = TRUE;
1236     }
1237 
1238     i++;
1239 
1240     if (info == self->reordered_tab) {
1241       after_selected = TRUE;
1242       continue;
1243     }
1244 
1245     if (after_selected != is_rtl && x + width > center)
1246       offset = -1;
1247     else if (after_selected == is_rtl && x < center)
1248       offset = 1;
1249 
1250     animate_reorder_offset (self, info, offset);
1251   }
1252 }
1253 
1254 static gboolean
drag_autoscroll_cb(GtkWidget * widget,GdkFrameClock * frame_clock,AdwTabBox * self)1255 drag_autoscroll_cb (GtkWidget     *widget,
1256                     GdkFrameClock *frame_clock,
1257                     AdwTabBox     *self)
1258 {
1259   double value, page_size;
1260   double x, delta_ms, start_threshold, end_threshold, autoscroll_factor;
1261   gint64 time;
1262   int offset = 0;
1263   int tab_width = 0;
1264   int autoscroll_area = 0;
1265 
1266   if (self->reordered_tab) {
1267     gtk_widget_measure (GTK_WIDGET (self->reordered_tab->tab),
1268                         GTK_ORIENTATION_HORIZONTAL, -1,
1269                         NULL, &tab_width, NULL, NULL);
1270     tab_width -= 2 * OVERLAP;
1271     x = (double) self->reorder_x + OVERLAP;
1272   } else if (self->drop_target_tab) {
1273     gtk_widget_measure (GTK_WIDGET (self->drop_target_tab->tab),
1274                         GTK_ORIENTATION_HORIZONTAL, -1,
1275                         NULL, &tab_width, NULL, NULL);
1276     tab_width -= 2 * OVERLAP;
1277     x = (double) self->drop_target_x + OVERLAP - tab_width / 2;
1278   } else {
1279     return G_SOURCE_CONTINUE;
1280   }
1281 
1282   value = gtk_adjustment_get_value (self->adjustment);
1283   page_size = gtk_adjustment_get_page_size (self->adjustment);
1284   autoscroll_area = tab_width / 2;
1285 
1286   x = CLAMP (x,
1287              autoscroll_area,
1288              self->allocated_width - tab_width - autoscroll_area);
1289 
1290   time = gdk_frame_clock_get_frame_time (frame_clock);
1291   delta_ms = (time - self->drag_autoscroll_prev_time) / 1000.0;
1292 
1293   start_threshold = value + autoscroll_area;
1294   end_threshold = value + page_size - tab_width - autoscroll_area;
1295   autoscroll_factor = 0;
1296 
1297   if (x < start_threshold)
1298     autoscroll_factor = -(start_threshold - x) / autoscroll_area;
1299   else if (x > end_threshold)
1300     autoscroll_factor = (x - end_threshold) / autoscroll_area;
1301 
1302   autoscroll_factor = CLAMP (autoscroll_factor, -1, 1);
1303   autoscroll_factor = adw_ease_in_cubic (autoscroll_factor);
1304   self->drag_autoscroll_prev_time = time;
1305 
1306   if (autoscroll_factor == 0)
1307     return G_SOURCE_CONTINUE;
1308 
1309   if (autoscroll_factor > 0)
1310     offset = (int) ceil (autoscroll_factor * delta_ms * AUTOSCROLL_SPEED);
1311   else
1312     offset = (int) floor (autoscroll_factor * delta_ms * AUTOSCROLL_SPEED);
1313 
1314   self->reorder_x += offset;
1315   gtk_adjustment_set_value (self->adjustment, value + offset);
1316   update_drag_reodering (self);
1317 
1318   return G_SOURCE_CONTINUE;
1319 }
1320 
1321 static void
start_autoscroll(AdwTabBox * self)1322 start_autoscroll (AdwTabBox *self)
1323 {
1324   GdkFrameClock *frame_clock;
1325 
1326   if (!self->adjustment)
1327     return;
1328 
1329   if (self->drag_autoscroll_cb_id)
1330     return;
1331 
1332   frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
1333 
1334   self->drag_autoscroll_prev_time = gdk_frame_clock_get_frame_time (frame_clock);
1335   self->drag_autoscroll_cb_id =
1336     gtk_widget_add_tick_callback (GTK_WIDGET (self),
1337                                   (GtkTickCallback) drag_autoscroll_cb,
1338                                   self, NULL);
1339 }
1340 
1341 static void
end_autoscroll(AdwTabBox * self)1342 end_autoscroll (AdwTabBox *self)
1343 {
1344   if (self->drag_autoscroll_cb_id) {
1345     gtk_widget_remove_tick_callback (GTK_WIDGET (self),
1346                                      self->drag_autoscroll_cb_id);
1347     self->drag_autoscroll_cb_id = 0;
1348   }
1349 }
1350 
1351 static void
start_drag_reodering(AdwTabBox * self,TabInfo * info,double x,double y)1352 start_drag_reodering (AdwTabBox *self,
1353                       TabInfo   *info,
1354                       double     x,
1355                       double     y)
1356 {
1357   if (self->dragging)
1358     return;
1359 
1360   if (!info)
1361     return;
1362 
1363   self->continue_reorder = info == self->reordered_tab;
1364 
1365   if (self->continue_reorder) {
1366     if (self->reorder_animation)
1367       adw_animation_stop (self->reorder_animation);
1368 
1369     reset_reorder_animations (self);
1370 
1371     self->reorder_x = (int) round (x - self->drag_offset_x);
1372     self->reorder_y = (int) round (y - self->drag_offset_y);
1373   } else
1374     force_end_reordering (self);
1375 
1376   start_autoscroll (self);
1377   self->dragging = TRUE;
1378 
1379   if (!self->continue_reorder)
1380     start_reordering (self, info);
1381 }
1382 
1383 static void
end_drag_reodering(AdwTabBox * self)1384 end_drag_reodering (AdwTabBox *self)
1385 {
1386   TabInfo *dest_tab;
1387 
1388   if (!self->dragging)
1389     return;
1390 
1391   self->dragging = FALSE;
1392 
1393   end_autoscroll (self);
1394 
1395   dest_tab = g_list_nth_data (self->tabs, self->reorder_index);
1396 
1397   if (!self->indirect_reordering) {
1398     int index = self->reorder_index;
1399 
1400     if (!self->pinned)
1401       index += adw_tab_view_get_n_pinned_pages (self->view);
1402 
1403     /* We've already reordered the tab here, no need to do it again */
1404     g_signal_handlers_block_by_func (self->view, page_reordered_cb, self);
1405 
1406     adw_tab_view_reorder_page (self->view, self->reordered_tab->page, index);
1407 
1408     g_signal_handlers_unblock_by_func (self->view, page_reordered_cb, self);
1409   }
1410 
1411   animate_reordering (self, dest_tab);
1412 
1413   self->continue_reorder = FALSE;
1414 }
1415 
1416 static void
reorder_begin_cb(AdwTabBox * self,double start_x,double start_y,GtkGesture * gesture)1417 reorder_begin_cb (AdwTabBox  *self,
1418                   double      start_x,
1419                   double      start_y,
1420                   GtkGesture *gesture)
1421 {
1422   self->reorder_start_pos = gtk_adjustment_get_value (self->adjustment);
1423 
1424   start_x += self->reorder_start_pos;
1425 
1426   self->pressed_tab = find_tab_info_at (self, start_x);
1427 
1428   self->drag_offset_x = start_x - get_tab_position (self, self->pressed_tab);
1429   self->drag_offset_y = start_y;
1430 
1431   if (!self->reorder_animation) {
1432     self->reorder_x = (int) round (start_x - self->drag_offset_x);
1433     self->reorder_y = (int) round (start_y - self->drag_offset_y);
1434   }
1435 }
1436 
1437 /* Copied from gtkdragsource.c */
1438 static gboolean
gtk_drag_check_threshold_double(GtkWidget * widget,double start_x,double start_y,double current_x,double current_y)1439 gtk_drag_check_threshold_double (GtkWidget *widget,
1440                                  double     start_x,
1441                                  double     start_y,
1442                                  double     current_x,
1443                                  double     current_y)
1444 {
1445   int drag_threshold;
1446 
1447   g_object_get (gtk_widget_get_settings (widget),
1448                 "gtk-dnd-drag-threshold", &drag_threshold,
1449                 NULL);
1450 
1451   return (ABS (current_x - start_x) > drag_threshold ||
1452           ABS (current_y - start_y) > drag_threshold);
1453 }
1454 
1455 static gboolean
check_dnd_threshold(AdwTabBox * self,double x,double y)1456 check_dnd_threshold (AdwTabBox *self,
1457                      double     x,
1458                      double     y)
1459 {
1460   int threshold;
1461   graphene_rect_t rect;
1462 
1463   g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)),
1464                 "gtk-dnd-drag-threshold", &threshold,
1465                 NULL);
1466 
1467   threshold *= DND_THRESHOLD_MULTIPLIER;
1468 
1469   graphene_rect_init (&rect, 0, 0,
1470                       self->allocated_width,
1471                       gtk_widget_get_height (GTK_WIDGET (self)));
1472   graphene_rect_inset (&rect, -threshold, -threshold);
1473 
1474   return !graphene_rect_contains_point (&rect, &GRAPHENE_POINT_INIT (x, y));
1475 }
1476 
1477 static void begin_drag (AdwTabBox *self,
1478                         GdkDevice *device);
1479 
1480 static void
reorder_update_cb(AdwTabBox * self,double offset_x,double offset_y,GtkGesture * gesture)1481 reorder_update_cb (AdwTabBox  *self,
1482                    double      offset_x,
1483                    double      offset_y,
1484                    GtkGesture *gesture)
1485 {
1486   double start_x, start_y, x, y;
1487   GdkDevice *device;
1488 
1489   if (!self->pressed_tab) {
1490     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1491     return;
1492   }
1493 
1494   if (!self->dragging &&
1495       !gtk_drag_check_threshold_double (GTK_WIDGET (self), 0, 0,
1496                                         offset_x, offset_y))
1497     return;
1498 
1499   gtk_gesture_drag_get_start_point (GTK_GESTURE_DRAG (gesture),
1500                                     &start_x, &start_y);
1501 
1502   x = start_x + gtk_adjustment_get_value (self->adjustment) + offset_x;
1503   y = start_y + offset_y;
1504 
1505   start_drag_reodering (self, self->pressed_tab, x, y);
1506 
1507   if (self->dragging) {
1508     adw_tab_view_set_selected_page (self->view, self->pressed_tab->page);
1509     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
1510   } else {
1511     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1512     return;
1513   }
1514 
1515   self->reorder_x = (int) round (x - self->drag_offset_x);
1516   self->reorder_y = (int) round (y - self->drag_offset_y);
1517 
1518   device = gtk_event_controller_get_current_event_device (GTK_EVENT_CONTROLLER (gesture));
1519 
1520   if (!self->pinned &&
1521       self->pressed_tab &&
1522       self->pressed_tab != self->reorder_placeholder &&
1523       self->pressed_tab->page &&
1524       !is_touchscreen (gesture) &&
1525       adw_tab_view_get_n_pages (self->view) > 1 &&
1526       check_dnd_threshold (self, x, y)) {
1527     begin_drag (self, device);
1528 
1529     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
1530 
1531     return;
1532   }
1533 
1534   update_drag_reodering (self);
1535 }
1536 
1537 static void
reorder_end_cb(AdwTabBox * self,double offset_x,double offset_y,GtkGesture * gesture)1538 reorder_end_cb (AdwTabBox  *self,
1539                 double      offset_x,
1540                 double      offset_y,
1541                 GtkGesture *gesture)
1542 {
1543   end_drag_reodering (self);
1544 }
1545 
1546 /* Selection */
1547 
1548 static void
reset_focus(AdwTabBox * self)1549 reset_focus (AdwTabBox *self)
1550 {
1551   GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self));
1552 
1553   gtk_widget_set_focus_child (GTK_WIDGET (self), NULL);
1554 
1555   if (root)
1556     gtk_root_set_focus (root, NULL);
1557 }
1558 
1559 static void
select_page(AdwTabBox * self,AdwTabPage * page)1560 select_page (AdwTabBox  *self,
1561              AdwTabPage *page)
1562 {
1563   if (!page) {
1564     self->selected_tab = NULL;
1565 
1566     reset_focus (self);
1567 
1568     return;
1569   }
1570 
1571   self->selected_tab = find_info_for_page (self, page);
1572 
1573   if (!self->selected_tab) {
1574     if (gtk_widget_get_focus_child (GTK_WIDGET (self)))
1575       reset_focus (self);
1576 
1577     return;
1578   }
1579 
1580   if (adw_tab_bar_tabs_have_visible_focus (self->tab_bar))
1581     gtk_widget_grab_focus (GTK_WIDGET (self->selected_tab->tab));
1582 
1583   gtk_widget_set_focus_child (GTK_WIDGET (self),
1584                               GTK_WIDGET (self->selected_tab->tab));
1585 
1586   if (self->selected_tab->width >= 0)
1587     scroll_to_tab (self, self->selected_tab, FOCUS_ANIMATION_DURATION);
1588 }
1589 
1590 /* Opening */
1591 
1592 static gboolean
extra_drag_drop_cb(AdwTab * tab,GValue * value,AdwTabBox * self)1593 extra_drag_drop_cb (AdwTab    *tab,
1594                     GValue    *value,
1595                     AdwTabBox *self)
1596 {
1597   gboolean ret = GDK_EVENT_PROPAGATE;
1598   AdwTabPage *page = adw_tab_get_page (tab);
1599 
1600   g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DROP], 0, page, value, &ret);
1601 
1602   return ret;
1603 }
1604 
1605 static void
appear_animation_value_cb(double value,gpointer user_data)1606 appear_animation_value_cb (double   value,
1607                            gpointer user_data)
1608 {
1609   TabInfo *info = user_data;
1610 
1611   info->appear_progress = value;
1612 
1613   if (GTK_IS_WIDGET (info->tab))
1614     gtk_widget_queue_resize (GTK_WIDGET (info->tab));
1615 }
1616 
1617 static void
open_animation_done_cb(gpointer user_data)1618 open_animation_done_cb (gpointer user_data)
1619 {
1620   TabInfo *info = user_data;
1621 
1622   g_clear_object (&info->appear_animation);
1623 }
1624 
1625 static TabInfo *
create_tab_info(AdwTabBox * self,AdwTabPage * page)1626 create_tab_info (AdwTabBox  *self,
1627                  AdwTabPage *page)
1628 {
1629   TabInfo *info;
1630 
1631   info = g_new0 (TabInfo, 1);
1632   info->page = page;
1633   info->pos = -1;
1634   info->width = -1;
1635   info->tab = adw_tab_new (self->view, self->pinned);
1636 
1637   adw_tab_set_page (info->tab, page);
1638   adw_tab_set_inverted (info->tab, self->inverted);
1639   adw_tab_setup_extra_drop_target (info->tab,
1640                                    self->extra_drag_actions,
1641                                    self->extra_drag_types,
1642                                    self->extra_drag_n_types);
1643 
1644   gtk_widget_set_parent (GTK_WIDGET (info->tab), GTK_WIDGET (self));
1645 
1646   g_signal_connect_object (info->tab, "extra-drag-drop", G_CALLBACK (extra_drag_drop_cb), self, 0);
1647 
1648   return info;
1649 }
1650 
1651 static void
page_attached_cb(AdwTabBox * self,AdwTabPage * page,int position)1652 page_attached_cb (AdwTabBox  *self,
1653                   AdwTabPage *page,
1654                   int         position)
1655 {
1656   TabInfo *info;
1657   GList *l;
1658 
1659   if (adw_tab_page_get_pinned (page) != self->pinned)
1660     return;
1661 
1662   if (!self->pinned)
1663     position -= adw_tab_view_get_n_pinned_pages (self->view);
1664 
1665   set_tab_resize_mode (self, TAB_RESIZE_NORMAL);
1666   force_end_reordering (self);
1667 
1668   info = create_tab_info (self, page);
1669 
1670   info->notify_needs_attention_id =
1671     g_signal_connect_object (page,
1672                              "notify::needs-attention",
1673                              G_CALLBACK (update_visible),
1674                              self,
1675                              G_CONNECT_SWAPPED);
1676 
1677   info->appear_animation =
1678     adw_animation_new (GTK_WIDGET (self), 0, 1,
1679                        OPEN_ANIMATION_DURATION,
1680                        appear_animation_value_cb,
1681                        info);
1682 
1683   g_signal_connect_swapped (info->appear_animation, "done", G_CALLBACK (open_animation_done_cb), info);
1684 
1685   l = find_nth_alive_tab (self, position);
1686   self->tabs = g_list_insert_before (self->tabs, l, info);
1687 
1688   self->n_tabs++;
1689 
1690   adw_animation_start (info->appear_animation);
1691 
1692   if (page == adw_tab_view_get_selected_page (self->view))
1693     adw_tab_box_select_page (self, page);
1694   else
1695     scroll_to_tab_full (self, info, -1, FOCUS_ANIMATION_DURATION, TRUE);
1696 }
1697 
1698 /* Closing */
1699 
1700 static void
close_animation_done_cb(gpointer user_data)1701 close_animation_done_cb (gpointer user_data)
1702 {
1703   TabInfo *info = user_data;
1704   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1705   AdwTabBox *self = ADW_TAB_BOX (parent);
1706 
1707   g_clear_object (&info->appear_animation);
1708 
1709   self->tabs = g_list_remove (self->tabs, info);
1710 
1711   if (info->reorder_animation)
1712     adw_animation_stop (info->reorder_animation);
1713 
1714   if (self->reorder_animation)
1715     adw_animation_stop (self->reorder_animation);
1716 
1717   if (self->pressed_tab == info)
1718     self->pressed_tab = NULL;
1719 
1720   if (self->reordered_tab == info)
1721     self->reordered_tab = NULL;
1722 
1723   remove_and_free_tab_info (info);
1724 
1725   self->n_tabs--;
1726 }
1727 
1728 static void
page_detached_cb(AdwTabBox * self,AdwTabPage * page)1729 page_detached_cb (AdwTabBox  *self,
1730                   AdwTabPage *page)
1731 {
1732   TabInfo *info;
1733   GList *page_link;
1734 
1735   page_link = find_link_for_page (self, page);
1736 
1737   if (!page_link)
1738     return;
1739 
1740   info = page_link->data;
1741   page_link = page_link->next;
1742 
1743   force_end_reordering (self);
1744 
1745   if (self->hovering && !self->pinned) {
1746     gboolean is_last = TRUE;
1747 
1748     while (page_link) {
1749       TabInfo *i = page_link->data;
1750       page_link = page_link->next;
1751 
1752       if (i->page) {
1753         is_last = FALSE;
1754         break;
1755       }
1756     }
1757 
1758     if (is_last)
1759       set_tab_resize_mode (self, self->inverted ? TAB_RESIZE_NORMAL : TAB_RESIZE_FIXED_END_PADDING);
1760     else
1761       set_tab_resize_mode (self, TAB_RESIZE_FIXED_TAB_WIDTH);
1762   }
1763 
1764   g_assert (info->page);
1765 
1766   if (gtk_widget_is_focus (GTK_WIDGET (info->tab)))
1767     adw_tab_box_try_focus_selected_tab (self);
1768 
1769   if (info == self->selected_tab)
1770     adw_tab_box_select_page (self, NULL);
1771 
1772   adw_tab_set_page (info->tab, NULL);
1773 
1774   if (info->notify_needs_attention_id > 0) {
1775     g_signal_handler_disconnect (info->page, info->notify_needs_attention_id);
1776     info->notify_needs_attention_id = 0;
1777   }
1778 
1779   info->page = NULL;
1780 
1781   if (info->appear_animation)
1782     adw_animation_stop (info->appear_animation);
1783 
1784   info->appear_animation =
1785     adw_animation_new (GTK_WIDGET (self), info->appear_progress, 0,
1786                        CLOSE_ANIMATION_DURATION,
1787                        appear_animation_value_cb,
1788                        info);
1789 
1790   g_signal_connect_swapped (info->appear_animation, "done", G_CALLBACK (close_animation_done_cb), info);
1791 
1792   adw_animation_start (info->appear_animation);
1793 }
1794 
1795 /* Tab DND */
1796 
1797 #define ADW_TYPE_TAB_BOX_ROOT_CONTENT (adw_tab_box_root_content_get_type ())
1798 
1799 G_DECLARE_FINAL_TYPE (AdwTabBoxRootContent, adw_tab_box_root_content, ADW, TAB_BOX_ROOT_CONTENT, GdkContentProvider)
1800 
1801 struct _AdwTabBoxRootContent
1802 {
1803   GdkContentProvider parent_instance;
1804 
1805   AdwTabBox *tab_box;
1806 };
1807 
G_DEFINE_TYPE(AdwTabBoxRootContent,adw_tab_box_root_content,GDK_TYPE_CONTENT_PROVIDER)1808 G_DEFINE_TYPE (AdwTabBoxRootContent, adw_tab_box_root_content, GDK_TYPE_CONTENT_PROVIDER)
1809 
1810 static GdkContentFormats *
1811 adw_tab_box_root_content_ref_formats (GdkContentProvider *provider)
1812 {
1813   return gdk_content_formats_new ((const char *[1]) { "application/x-rootwindow-drop" }, 1);
1814 }
1815 
1816 static void
adw_tab_box_root_content_write_mime_type_async(GdkContentProvider * provider,const char * mime_type,GOutputStream * stream,int io_priority,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1817 adw_tab_box_root_content_write_mime_type_async (GdkContentProvider  *provider,
1818                                                 const char          *mime_type,
1819                                                 GOutputStream       *stream,
1820                                                 int                  io_priority,
1821                                                 GCancellable        *cancellable,
1822                                                 GAsyncReadyCallback  callback,
1823                                                 gpointer             user_data)
1824 {
1825   AdwTabBoxRootContent *self = ADW_TAB_BOX_ROOT_CONTENT (provider);
1826   g_autoptr (GTask) task = NULL;
1827 
1828   self->tab_box->should_detach_into_new_window = TRUE;
1829 
1830   task = g_task_new (self, cancellable, callback, user_data);
1831   g_task_set_priority (task, io_priority);
1832   g_task_set_source_tag (task, adw_tab_box_root_content_write_mime_type_async);
1833   g_task_return_boolean (task, TRUE);
1834 }
1835 
1836 static gboolean
adw_tab_box_root_content_write_mime_type_finish(GdkContentProvider * provider,GAsyncResult * result,GError ** error)1837 adw_tab_box_root_content_write_mime_type_finish (GdkContentProvider  *provider,
1838                                                  GAsyncResult        *result,
1839                                                  GError             **error)
1840 {
1841   return g_task_propagate_boolean (G_TASK (result), error);
1842 }
1843 
1844 static void
adw_tab_box_root_content_finalize(GObject * object)1845 adw_tab_box_root_content_finalize (GObject *object)
1846 {
1847   AdwTabBoxRootContent *self = ADW_TAB_BOX_ROOT_CONTENT (object);
1848 
1849   g_clear_object (&self->tab_box);
1850 
1851   G_OBJECT_CLASS (adw_tab_box_root_content_parent_class)->finalize (object);
1852 }
1853 
1854 static void
adw_tab_box_root_content_class_init(AdwTabBoxRootContentClass * klass)1855 adw_tab_box_root_content_class_init (AdwTabBoxRootContentClass *klass)
1856 {
1857   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1858   GdkContentProviderClass *provider_class = GDK_CONTENT_PROVIDER_CLASS (klass);
1859 
1860   object_class->finalize = adw_tab_box_root_content_finalize;
1861 
1862   provider_class->ref_formats = adw_tab_box_root_content_ref_formats;
1863   provider_class->write_mime_type_async = adw_tab_box_root_content_write_mime_type_async;
1864   provider_class->write_mime_type_finish = adw_tab_box_root_content_write_mime_type_finish;
1865 }
1866 
1867 static void
adw_tab_box_root_content_init(AdwTabBoxRootContent * self)1868 adw_tab_box_root_content_init (AdwTabBoxRootContent *self)
1869 {
1870 }
1871 
1872 static GdkContentProvider *
adw_tab_box_root_content_new(AdwTabBox * tab_box)1873 adw_tab_box_root_content_new (AdwTabBox *tab_box)
1874 {
1875   AdwTabBoxRootContent *self = g_object_new (ADW_TYPE_TAB_BOX_ROOT_CONTENT, NULL);
1876 
1877   self->tab_box = g_object_ref (tab_box);
1878 
1879   return GDK_CONTENT_PROVIDER (self);
1880 }
1881 
1882 static int
calculate_placeholder_index(AdwTabBox * self,int x)1883 calculate_placeholder_index (AdwTabBox *self,
1884                              int        x)
1885 {
1886   int lower, upper, pos, i;
1887   gboolean is_rtl;
1888   GList *l;
1889 
1890   get_visible_range (self, &lower, &upper);
1891 
1892   x = CLAMP (x, lower, upper);
1893 
1894   is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
1895 
1896   pos = (is_rtl ? self->allocated_width + OVERLAP : -OVERLAP);
1897   i = 0;
1898 
1899   for (l = self->tabs; l; l = l->next) {
1900     TabInfo *info = l->data;
1901     int tab_width = predict_tab_width (self, info, TRUE) * (is_rtl ? -1 : 1);
1902 
1903     int end = pos + tab_width + calculate_tab_offset (self, info, FALSE);
1904 
1905     if ((x <= end && !is_rtl) || (x >= end && is_rtl))
1906       break;
1907 
1908     pos += tab_width + (is_rtl ? OVERLAP : -OVERLAP);
1909     i++;
1910   }
1911 
1912   return i;
1913 }
1914 
1915 static void
insert_animation_value_cb(double value,gpointer user_data)1916 insert_animation_value_cb (double   value,
1917                            gpointer user_data)
1918 {
1919   TabInfo *info = user_data;
1920   AdwTabBox *self = ADW_TAB_BOX (gtk_widget_get_parent (GTK_WIDGET (info->tab)));
1921 
1922   appear_animation_value_cb (value, info);
1923 
1924   update_drag_reodering (self);
1925 }
1926 
1927 static void
insert_placeholder(AdwTabBox * self,AdwTabPage * page,int pos)1928 insert_placeholder (AdwTabBox  *self,
1929                     AdwTabPage *page,
1930                     int         pos)
1931 {
1932   TabInfo *info = self->reorder_placeholder;
1933   double initial_progress = 0;
1934 
1935   if (info) {
1936     initial_progress = info->appear_progress;
1937 
1938     if (info->appear_animation)
1939       adw_animation_stop (info->appear_animation);
1940   } else {
1941     int index;
1942 
1943     self->placeholder_page = page;
1944 
1945     info = create_tab_info (self, page);
1946 
1947     gtk_widget_set_opacity (GTK_WIDGET (info->tab), 0);
1948 
1949     adw_tab_set_dragging (info->tab, TRUE);
1950 
1951     info->reorder_ignore_bounds = TRUE;
1952 
1953     if (self->adjustment) {
1954       double page_size = gtk_adjustment_get_page_size (self->adjustment);
1955 
1956       if (self->allocated_width > page_size) {
1957         gtk_widget_measure (GTK_WIDGET (info->tab), GTK_ORIENTATION_HORIZONTAL, -1,
1958                             NULL, &self->placeholder_scroll_offset, NULL, NULL);
1959 
1960         self->placeholder_scroll_offset /= 2;
1961       } else {
1962         self->placeholder_scroll_offset = 0;
1963       }
1964     }
1965 
1966     index = calculate_placeholder_index (self, pos + self->placeholder_scroll_offset);
1967 
1968     self->tabs = g_list_insert (self->tabs, info, index);
1969     self->n_tabs++;
1970 
1971     self->reorder_placeholder = info;
1972     self->reorder_index = g_list_index (self->tabs, info);
1973 
1974     animate_scroll_relative (self, self->placeholder_scroll_offset, OPEN_ANIMATION_DURATION);
1975   }
1976 
1977   info->appear_animation =
1978     adw_animation_new (GTK_WIDGET (self), initial_progress, 1,
1979                        OPEN_ANIMATION_DURATION,
1980                        insert_animation_value_cb,
1981                        info);
1982 
1983   g_signal_connect_swapped (info->appear_animation, "done", G_CALLBACK (open_animation_done_cb), info);
1984 
1985   adw_animation_start (info->appear_animation);
1986 }
1987 
1988 static void
replace_animation_done_cb(gpointer user_data)1989 replace_animation_done_cb (gpointer user_data)
1990 {
1991   TabInfo *info = user_data;
1992   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
1993   AdwTabBox *self = ADW_TAB_BOX (parent);
1994 
1995   g_clear_object (&info->appear_animation);
1996   self->reorder_placeholder = NULL;
1997   self->can_remove_placeholder = TRUE;
1998 }
1999 
2000 static void
replace_placeholder(AdwTabBox * self,AdwTabPage * page)2001 replace_placeholder (AdwTabBox  *self,
2002                      AdwTabPage *page)
2003 {
2004   TabInfo *info = self->reorder_placeholder;
2005   double initial_progress;
2006 
2007   self->placeholder_scroll_offset = 0;
2008   gtk_widget_set_opacity (GTK_WIDGET (self->reorder_placeholder->tab), 1);
2009   adw_tab_set_dragging (info->tab, FALSE);
2010 
2011   if (!info->appear_animation) {
2012     self->reorder_placeholder = NULL;
2013 
2014     return;
2015   }
2016 
2017   initial_progress = info->appear_progress;
2018 
2019   self->can_remove_placeholder = FALSE;
2020 
2021   adw_tab_set_page (info->tab, page);
2022   info->page = page;
2023 
2024   adw_animation_stop (info->appear_animation);
2025 
2026   info->appear_animation =
2027     adw_animation_new (GTK_WIDGET (self), initial_progress, 1,
2028                        OPEN_ANIMATION_DURATION,
2029                        appear_animation_value_cb,
2030                        info);
2031 
2032   g_signal_connect_swapped (info->appear_animation, "done", G_CALLBACK (replace_animation_done_cb), info);
2033 
2034   adw_animation_start (info->appear_animation);
2035 }
2036 
2037 static void
remove_animation_done_cb(gpointer user_data)2038 remove_animation_done_cb (gpointer user_data)
2039 {
2040   TabInfo *info = user_data;
2041   GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (info->tab));
2042   AdwTabBox *self = ADW_TAB_BOX (parent);
2043 
2044   g_clear_object (&info->appear_animation);
2045 
2046   if (!self->can_remove_placeholder) {
2047     adw_tab_set_page (info->tab, self->placeholder_page);
2048     info->page = self->placeholder_page;
2049 
2050     return;
2051   }
2052 
2053   if (self->reordered_tab == info) {
2054     force_end_reordering (self);
2055 
2056     if (self->reorder_animation)
2057       adw_animation_stop (info->reorder_animation);
2058 
2059     self->reordered_tab = NULL;
2060   }
2061 
2062   if (self->pressed_tab == info)
2063     self->pressed_tab = NULL;
2064 
2065   self->tabs = g_list_remove (self->tabs, info);
2066 
2067   remove_and_free_tab_info (info);
2068 
2069   self->n_tabs--;
2070 
2071   self->reorder_placeholder = NULL;
2072 }
2073 
2074 static gboolean
remove_placeholder_scroll_cb(AdwTabBox * self)2075 remove_placeholder_scroll_cb (AdwTabBox *self)
2076 {
2077   animate_scroll_relative (self, -self->placeholder_scroll_offset, CLOSE_ANIMATION_DURATION);
2078   self->placeholder_scroll_offset = 0;
2079 
2080   return G_SOURCE_REMOVE;
2081 }
2082 
2083 static void
remove_placeholder(AdwTabBox * self)2084 remove_placeholder (AdwTabBox *self)
2085 {
2086   TabInfo *info = self->reorder_placeholder;
2087 
2088   if (!info || !info->page)
2089     return;
2090 
2091   adw_tab_set_page (info->tab, NULL);
2092   info->page = NULL;
2093 
2094   if (info->appear_animation)
2095     adw_animation_stop (info->appear_animation);
2096 
2097   g_idle_add ((GSourceFunc) remove_placeholder_scroll_cb, self);
2098 
2099   info->appear_animation =
2100     adw_animation_new (GTK_WIDGET (self), info->appear_progress, 0,
2101                        CLOSE_ANIMATION_DURATION,
2102                        appear_animation_value_cb,
2103                        info);
2104 
2105   g_signal_connect_swapped (info->appear_animation, "done", G_CALLBACK (remove_animation_done_cb), info);
2106 
2107   adw_animation_start (info->appear_animation);
2108 }
2109 
2110 static inline AdwTabBox *
get_source_tab_box(GtkDropTarget * target)2111 get_source_tab_box (GtkDropTarget *target)
2112 {
2113   GdkDrop *drop = gtk_drop_target_get_drop (target);
2114   GdkDrag *drag = gdk_drop_get_drag (drop);
2115 
2116   if (!drag)
2117     return NULL;
2118 
2119   return ADW_TAB_BOX (g_object_get_data (G_OBJECT (drag),
2120                       "adw-tab-bar-drag-origin"));
2121 }
2122 
2123 static void
do_drag_drop(AdwTabBox * self,AdwTabBox * source_tab_box)2124 do_drag_drop (AdwTabBox *self,
2125               AdwTabBox *source_tab_box)
2126 {
2127   AdwTabPage *page = source_tab_box->detached_page;
2128   int offset = (self->pinned ? 0 : adw_tab_view_get_n_pinned_pages (self->view));
2129 
2130   if (self->reorder_placeholder) {
2131     replace_placeholder (self, page);
2132     end_drag_reodering (self);
2133 
2134     g_signal_handlers_block_by_func (self->view, page_attached_cb, self);
2135 
2136     adw_tab_view_attach_page (self->view, page, self->reorder_index + offset);
2137 
2138     g_signal_handlers_unblock_by_func (self->view, page_attached_cb, self);
2139   } else {
2140     adw_tab_view_attach_page (self->view, page, self->reorder_index + offset);
2141   }
2142 
2143   source_tab_box->should_detach_into_new_window = FALSE;
2144   source_tab_box->detached_page = NULL;
2145 
2146   self->indirect_reordering = FALSE;
2147 }
2148 
2149 static void
detach_into_new_window(AdwTabBox * self)2150 detach_into_new_window (AdwTabBox *self)
2151 {
2152   AdwTabPage *page;
2153   AdwTabView *new_view;
2154 
2155   page = self->detached_page;
2156 
2157   new_view = adw_tab_view_create_window (self->view);
2158 
2159   if (ADW_IS_TAB_VIEW (new_view))
2160     adw_tab_view_attach_page (new_view, page, 0);
2161   else
2162     adw_tab_view_attach_page (self->view, page, self->detached_index);
2163 
2164   self->should_detach_into_new_window = FALSE;
2165 }
2166 
2167 static gboolean
is_view_in_the_same_group(AdwTabBox * self,AdwTabView * other_view)2168 is_view_in_the_same_group (AdwTabBox  *self,
2169                            AdwTabView *other_view)
2170 {
2171   /* TODO when we have groups, this should do the actual check */
2172   return TRUE;
2173 }
2174 
2175 static void
drag_end(AdwTabBox * self,GdkDrag * drag,gboolean success)2176 drag_end (AdwTabBox *self,
2177           GdkDrag   *drag,
2178           gboolean   success)
2179 {
2180   g_signal_handlers_disconnect_by_data (drag, self);
2181 
2182   gdk_drag_drop_done (drag, success);
2183 
2184   if (!success) {
2185     adw_tab_view_attach_page (self->view,
2186                               self->detached_page,
2187                               self->detached_index);
2188 
2189     self->indirect_reordering = FALSE;
2190   }
2191 
2192   self->detached_page = NULL;
2193 
2194   if (self->drag_icon)
2195     g_clear_pointer (&self->drag_icon, g_free);
2196 
2197   g_object_unref (drag);
2198 }
2199 
2200 static void
tab_drop_performed_cb(AdwTabBox * self,GdkDrag * drag)2201 tab_drop_performed_cb (AdwTabBox *self,
2202                        GdkDrag   *drag)
2203 {
2204   /* Catch drops into our windows, but outside of tab views. If this is a false
2205    * positive, it will be set to FALSE in do_drag_drop(). */
2206   self->should_detach_into_new_window = TRUE;
2207 }
2208 
2209 static void
tab_dnd_finished_cb(AdwTabBox * self,GdkDrag * drag)2210 tab_dnd_finished_cb (AdwTabBox *self,
2211                      GdkDrag   *drag)
2212 {
2213   if (self->should_detach_into_new_window)
2214     detach_into_new_window (self);
2215 
2216   drag_end (self, drag, TRUE);
2217 }
2218 
2219 static void
tab_drag_cancel_cb(AdwTabBox * self,GdkDragCancelReason reason,GdkDrag * drag)2220 tab_drag_cancel_cb (AdwTabBox           *self,
2221                     GdkDragCancelReason  reason,
2222                     GdkDrag             *drag)
2223 {
2224   if (reason == GDK_DRAG_CANCEL_NO_TARGET) {
2225     detach_into_new_window (self);
2226     drag_end (self, drag, TRUE);
2227 
2228     return;
2229   }
2230 
2231   self->should_detach_into_new_window = FALSE;
2232   drag_end (self, drag, FALSE);
2233 }
2234 
2235 static void
create_drag_icon(AdwTabBox * self,GdkDrag * drag)2236 create_drag_icon (AdwTabBox *self,
2237                   GdkDrag   *drag)
2238 {
2239   DragIcon *icon;
2240 
2241   icon = g_new0 (DragIcon, 1);
2242 
2243   icon->drag = drag;
2244 
2245   icon->width = predict_tab_width (self, self->reordered_tab, FALSE);
2246   icon->target_width = icon->width;
2247 
2248   icon->tab = adw_tab_new (self->view, FALSE);
2249   adw_tab_set_page (icon->tab, self->reordered_tab->page);
2250   adw_tab_set_dragging (icon->tab, TRUE);
2251   adw_tab_set_inverted (icon->tab, self->inverted);
2252   adw_tab_set_display_width (icon->tab, icon->width);
2253   gtk_widget_set_halign (GTK_WIDGET (icon->tab), GTK_ALIGN_START);
2254 
2255   gtk_drag_icon_set_child (GTK_DRAG_ICON (gtk_drag_icon_get_for_drag (drag)),
2256                            GTK_WIDGET (icon->tab));
2257 
2258   gtk_style_context_get_margin (gtk_widget_get_style_context (GTK_WIDGET (icon->tab)),
2259                                 &icon->tab_margin);
2260 
2261   gtk_widget_set_size_request (GTK_WIDGET (icon->tab),
2262                                icon->width + icon->tab_margin.left + icon->tab_margin.right,
2263                                -1);
2264 
2265   icon->hotspot_x = (int) self->drag_offset_x;
2266   icon->hotspot_y = (int) self->drag_offset_y;
2267 
2268   gdk_drag_set_hotspot (drag,
2269                         icon->hotspot_x + icon->tab_margin.left,
2270                         icon->hotspot_y + icon->tab_margin.top);
2271 
2272   self->drag_icon = icon;
2273 }
2274 
2275 static void
icon_resize_animation_value_cb(double value,gpointer user_data)2276 icon_resize_animation_value_cb (double   value,
2277                                 gpointer user_data)
2278 {
2279   DragIcon *icon = user_data;
2280   double relative_pos;
2281 
2282   relative_pos = (double) icon->hotspot_x / icon->width;
2283 
2284   icon->width = (int) round (value);
2285 
2286   adw_tab_set_display_width (icon->tab, icon->width);
2287   gtk_widget_set_size_request (GTK_WIDGET (icon->tab),
2288                                icon->width + icon->tab_margin.left + icon->tab_margin.right,
2289                                -1);
2290 
2291   icon->hotspot_x = (int) round (icon->width * relative_pos);
2292 
2293   gdk_drag_set_hotspot (icon->drag,
2294                         icon->hotspot_x + icon->tab_margin.left,
2295                         icon->hotspot_y + icon->tab_margin.top);
2296 
2297   gtk_widget_queue_resize (GTK_WIDGET (icon->tab));
2298 }
2299 
2300 static void
icon_resize_animation_done_cb(gpointer user_data)2301 icon_resize_animation_done_cb (gpointer user_data)
2302 {
2303   DragIcon *icon = user_data;
2304 
2305   g_clear_object (&icon->resize_animation);
2306 }
2307 
2308 static void
resize_drag_icon(AdwTabBox * self,int width)2309 resize_drag_icon (AdwTabBox *self,
2310                   int        width)
2311 {
2312   DragIcon *icon = self->drag_icon;
2313 
2314   if (width == icon->target_width)
2315     return;
2316 
2317   if (icon->resize_animation)
2318     adw_animation_stop (icon->resize_animation);
2319 
2320   icon->target_width = width;
2321 
2322   icon->resize_animation =
2323     adw_animation_new (GTK_WIDGET (icon->tab), icon->width, width,
2324                        ICON_RESIZE_ANIMATION_DURATION,
2325                        icon_resize_animation_value_cb,
2326                        icon);
2327 
2328   g_signal_connect_swapped (icon->resize_animation, "done", G_CALLBACK (icon_resize_animation_done_cb), icon);
2329 
2330   adw_animation_start (icon->resize_animation);
2331 }
2332 
2333 static void
begin_drag(AdwTabBox * self,GdkDevice * device)2334 begin_drag (AdwTabBox *self,
2335             GdkDevice *device)
2336 {
2337   GtkNative *native;
2338   GdkSurface *surface;
2339   GdkContentProvider *content;
2340   GdkDrag *drag;
2341   TabInfo *detached_info;
2342   AdwTab *detached_tab;
2343 
2344   native = gtk_widget_get_native (GTK_WIDGET (self));
2345   surface = gtk_native_get_surface (native);
2346 
2347   self->hovering = TRUE;
2348   self->pressed_tab = NULL;
2349 
2350   detached_info = self->reordered_tab;
2351   detached_tab = g_object_ref (detached_info->tab);
2352   self->detached_page = detached_info->page;
2353 
2354   self->indirect_reordering = TRUE;
2355 
2356   content = gdk_content_provider_new_union ((GdkContentProvider *[2]) {
2357                                               adw_tab_box_root_content_new (self),
2358                                               gdk_content_provider_new_typed (ADW_TYPE_TAB_PAGE, detached_info->page)
2359                                             }, 2);
2360 
2361   drag = gdk_drag_begin (surface, device, content, GDK_ACTION_MOVE,
2362                          self->reorder_x, self->reorder_y);
2363 
2364   g_object_set_data (G_OBJECT (drag), "adw-tab-bar-drag-origin", self);
2365 
2366   g_signal_connect_swapped (drag, "drop-performed",
2367                             G_CALLBACK (tab_drop_performed_cb), self);
2368   g_signal_connect_swapped (drag, "dnd-finished",
2369                             G_CALLBACK (tab_dnd_finished_cb), self);
2370   g_signal_connect_swapped (drag, "cancel",
2371                             G_CALLBACK (tab_drag_cancel_cb), self);
2372 
2373   create_drag_icon (self, drag);
2374 
2375   end_drag_reodering (self);
2376   update_hover (self);
2377 
2378   gtk_widget_set_opacity (GTK_WIDGET (detached_tab), 0);
2379   self->detached_index = adw_tab_view_get_page_position (self->view, self->detached_page);
2380 
2381   adw_tab_view_detach_page (self->view, self->detached_page);
2382 
2383   self->indirect_reordering = FALSE;
2384 
2385   gtk_widget_measure (GTK_WIDGET (detached_tab),
2386                       GTK_ORIENTATION_HORIZONTAL, -1,
2387                       NULL, &self->placeholder_scroll_offset, NULL, NULL);
2388   self->placeholder_scroll_offset /= 2;
2389 
2390   animate_scroll_relative (self, -self->placeholder_scroll_offset, CLOSE_ANIMATION_DURATION);
2391 
2392   g_object_unref (detached_tab);
2393 }
2394 
2395 static GdkDragAction
tab_drag_enter_motion_cb(AdwTabBox * self,double x,double y,GtkDropTarget * target)2396 tab_drag_enter_motion_cb (AdwTabBox     *self,
2397                           double         x,
2398                           double         y,
2399                           GtkDropTarget *target)
2400 {
2401   AdwTabBox *source_tab_box;
2402 
2403   if (self->pinned)
2404     return 0;
2405 
2406   source_tab_box = get_source_tab_box (target);
2407 
2408   if (!source_tab_box)
2409     return 0;
2410 
2411   if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
2412     return 0;
2413 
2414   x += gtk_adjustment_get_value (self->adjustment);
2415 
2416   self->can_remove_placeholder = FALSE;
2417 
2418   if (!self->reorder_placeholder || !self->reorder_placeholder->page) {
2419     AdwTabPage *page = source_tab_box->detached_page;
2420     double center = x - source_tab_box->drag_icon->hotspot_x + source_tab_box->drag_icon->width / 2;
2421 
2422     insert_placeholder (self, page, center);
2423 
2424     self->indirect_reordering = TRUE;
2425 
2426     resize_drag_icon (source_tab_box, predict_tab_width (self, self->reorder_placeholder, TRUE));
2427     adw_tab_set_display_width (self->reorder_placeholder->tab, source_tab_box->drag_icon->target_width);
2428     adw_tab_set_inverted (source_tab_box->drag_icon->tab, self->inverted);
2429 
2430     self->drag_offset_x = source_tab_box->drag_icon->hotspot_x;
2431     self->drag_offset_y = source_tab_box->drag_icon->hotspot_y;
2432 
2433     self->reorder_x = (int) round (x - source_tab_box->drag_icon->hotspot_x);
2434 
2435     start_drag_reodering (self, self->reorder_placeholder, x, y);
2436 
2437     return GDK_ACTION_MOVE;
2438   }
2439 
2440   self->reorder_x = (int) round (x - source_tab_box->drag_icon->hotspot_x);
2441 
2442   update_drag_reodering (self);
2443 
2444   return GDK_ACTION_MOVE;
2445 }
2446 
2447 static void
tab_drag_leave_cb(AdwTabBox * self,GtkDropTarget * target)2448 tab_drag_leave_cb (AdwTabBox     *self,
2449                    GtkDropTarget *target)
2450 {
2451   AdwTabBox *source_tab_box;
2452 
2453   if (!self->indirect_reordering)
2454     return;
2455 
2456   if (self->pinned)
2457     return;
2458 
2459   source_tab_box = get_source_tab_box (target);
2460 
2461   if (!source_tab_box)
2462     return;
2463 
2464   if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
2465     return;
2466 
2467   self->can_remove_placeholder = TRUE;
2468 
2469   end_drag_reodering (self);
2470   remove_placeholder (self);
2471 
2472   self->indirect_reordering = FALSE;
2473 }
2474 
2475 static gboolean
tab_drag_drop_cb(AdwTabBox * self,const GValue * value,double x,double y,GtkDropTarget * target)2476 tab_drag_drop_cb (AdwTabBox     *self,
2477                   const GValue  *value,
2478                   double         x,
2479                   double         y,
2480                   GtkDropTarget *target)
2481 {
2482   AdwTabBox *source_tab_box;
2483 
2484   if (self->pinned)
2485     return GDK_EVENT_PROPAGATE;
2486 
2487   source_tab_box = get_source_tab_box (target);
2488 
2489   if (!source_tab_box)
2490     return GDK_EVENT_PROPAGATE;
2491 
2492   if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
2493     return GDK_EVENT_PROPAGATE;
2494 
2495   do_drag_drop (self, source_tab_box);
2496 
2497   return GDK_EVENT_STOP;
2498 }
2499 
2500 static gboolean
view_drag_drop_cb(AdwTabBox * self,const GValue * value,double x,double y,GtkDropTarget * target)2501 view_drag_drop_cb (AdwTabBox      *self,
2502                    const GValue  *value,
2503                    double         x,
2504                    double         y,
2505                    GtkDropTarget *target)
2506 {
2507   AdwTabBox *source_tab_box;
2508 
2509   if (self->pinned)
2510     return GDK_EVENT_PROPAGATE;
2511 
2512   source_tab_box = get_source_tab_box (target);
2513 
2514   if (!source_tab_box)
2515     return GDK_EVENT_PROPAGATE;
2516 
2517   if (!self->view || !is_view_in_the_same_group (self, source_tab_box->view))
2518     return GDK_EVENT_PROPAGATE;
2519 
2520   self->reorder_index = adw_tab_view_get_n_pages (self->view) -
2521                         adw_tab_view_get_n_pinned_pages (self->view);
2522 
2523   do_drag_drop (self, source_tab_box);
2524 
2525   return GDK_EVENT_STOP;
2526 }
2527 
2528 /* DND autoscrolling */
2529 
2530 static gboolean
reset_drop_target_tab_cb(AdwTabBox * self)2531 reset_drop_target_tab_cb (AdwTabBox *self)
2532 {
2533   self->reset_drop_target_tab_id = 0;
2534   set_drop_target_tab (self, NULL);
2535 
2536   return G_SOURCE_REMOVE;
2537 }
2538 
2539 static void
drag_leave_cb(AdwTabBox * self,GtkDropControllerMotion * controller)2540 drag_leave_cb (AdwTabBox               *self,
2541                GtkDropControllerMotion *controller)
2542 {
2543   GdkDrop *drop = gtk_drop_controller_motion_get_drop (controller);
2544   GdkDrag *drag = gdk_drop_get_drag (drop);
2545   AdwTabBox *source = ADW_TAB_BOX (g_object_get_data (G_OBJECT (drag),
2546                                                       "adw-tab-bar-drag-origin"));
2547 
2548   if (source)
2549     return;
2550 
2551   if (!self->reset_drop_target_tab_id)
2552     self->reset_drop_target_tab_id =
2553       g_idle_add ((GSourceFunc) reset_drop_target_tab_cb, self);
2554 
2555   end_autoscroll (self);
2556 }
2557 
2558 static void
drag_enter_motion_cb(AdwTabBox * self,double x,double y,GtkDropControllerMotion * controller)2559 drag_enter_motion_cb (AdwTabBox               *self,
2560                       double                   x,
2561                       double                   y,
2562                       GtkDropControllerMotion *controller)
2563 {
2564   TabInfo *info;
2565   GdkDrop *drop = gtk_drop_controller_motion_get_drop (controller);
2566   GdkDrag *drag = gdk_drop_get_drag (drop);
2567   AdwTabBox *source = ADW_TAB_BOX (g_object_get_data (G_OBJECT (drag),
2568                                                       "adw-tab-bar-drag-origin"));
2569 
2570   if (source)
2571     return;
2572 
2573   x += gtk_adjustment_get_value (self->adjustment);
2574 
2575   info = find_tab_info_at (self, x);
2576 
2577   if (!info) {
2578     drag_leave_cb (self, controller);
2579 
2580     return;
2581   }
2582 
2583   self->drop_target_x = x;
2584   set_drop_target_tab (self, info);
2585 
2586   start_autoscroll (self);
2587 }
2588 
2589 /* Context menu */
2590 
2591 static gboolean
reset_setup_menu_cb(AdwTabBox * self)2592 reset_setup_menu_cb (AdwTabBox *self)
2593 {
2594   g_signal_emit_by_name (self->view, "setup-menu", NULL);
2595 
2596   return G_SOURCE_REMOVE;
2597 }
2598 
2599 static void
touch_menu_notify_visible_cb(AdwTabBox * self)2600 touch_menu_notify_visible_cb (AdwTabBox *self)
2601 {
2602   if (!self->context_menu || gtk_widget_get_visible (GTK_WIDGET (self->context_menu)))
2603     return;
2604 
2605   self->hovering = FALSE;
2606   update_hover (self);
2607 
2608   g_idle_add ((GSourceFunc) reset_setup_menu_cb, self);
2609 }
2610 
2611 static void
do_popup(AdwTabBox * self,TabInfo * info,double x,double y)2612 do_popup (AdwTabBox *self,
2613           TabInfo   *info,
2614           double     x,
2615           double     y)
2616 {
2617   GMenuModel *model = adw_tab_view_get_menu_model (self->view);
2618   GdkRectangle rect;
2619 
2620   if (!G_IS_MENU_MODEL (model))
2621     return;
2622 
2623   g_signal_emit_by_name (self->view, "setup-menu", info->page);
2624 
2625   if (!self->context_menu) {
2626     self->context_menu = GTK_POPOVER (gtk_popover_menu_new_from_model (model));
2627     gtk_widget_set_parent (GTK_WIDGET (self->context_menu), GTK_WIDGET (self));
2628     gtk_popover_set_position (self->context_menu, GTK_POS_BOTTOM);
2629     gtk_popover_set_has_arrow (self->context_menu, FALSE);
2630 
2631     if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
2632       gtk_widget_set_halign (GTK_WIDGET (self->context_menu), GTK_ALIGN_END);
2633     else
2634       gtk_widget_set_halign (GTK_WIDGET (self->context_menu), GTK_ALIGN_START);
2635 
2636     g_signal_connect_object (self->context_menu, "notify::visible",
2637                              G_CALLBACK (touch_menu_notify_visible_cb), self,
2638                              G_CONNECT_AFTER | G_CONNECT_SWAPPED);
2639   }
2640 
2641   if (x >= 0 && y >= 0) {
2642     rect.x = x;
2643     rect.y = y;
2644   } else {
2645     rect.x = info->pos;
2646     rect.y = gtk_widget_get_allocated_height (GTK_WIDGET (info->tab));
2647 
2648     if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
2649       rect.x += info->width;
2650   }
2651 
2652   rect.x -= gtk_adjustment_get_value (self->adjustment);
2653   rect.width = 0;
2654   rect.height = 0;
2655 
2656   gtk_popover_set_pointing_to (self->context_menu, &rect);
2657 
2658   gtk_popover_popup (self->context_menu);
2659 }
2660 
2661 static void
long_pressed_cb(AdwTabBox * self,double x,double y,GtkGesture * gesture)2662 long_pressed_cb (AdwTabBox  *self,
2663                  double      x,
2664                  double      y,
2665                  GtkGesture *gesture)
2666 {
2667   TabInfo *info = find_tab_info_at (self, x);
2668 
2669   gtk_gesture_set_state (self->drag_gesture, GTK_EVENT_SEQUENCE_DENIED);
2670 
2671   if (!info || !info->page) {
2672     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
2673     return;
2674   }
2675 
2676   x += gtk_adjustment_get_value (self->adjustment);
2677 
2678   gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2679   do_popup (self, self->pressed_tab, x, y);
2680 }
2681 
2682 static void
popup_menu_cb(GtkWidget * widget,const char * action_name,GVariant * parameter)2683 popup_menu_cb (GtkWidget  *widget,
2684                const char *action_name,
2685                GVariant   *parameter)
2686 {
2687   AdwTabBox *self = ADW_TAB_BOX (widget);
2688 
2689   if (self->selected_tab && self->selected_tab->page)
2690     do_popup (self, self->selected_tab, -1, -1);
2691 }
2692 
2693 /* Clicking */
2694 
2695 static void
handle_click(AdwTabBox * self,TabInfo * info,GtkGesture * gesture)2696 handle_click (AdwTabBox  *self,
2697               TabInfo    *info,
2698               GtkGesture *gesture)
2699 {
2700   gboolean can_grab_focus;
2701 
2702   if (self->adjustment) {
2703     int pos = get_tab_position (self, info);
2704     double value = gtk_adjustment_get_value (self->adjustment);
2705     double page_size = gtk_adjustment_get_page_size (self->adjustment);
2706 
2707     if (pos + OVERLAP < value ||
2708         pos + info->width - OVERLAP > value + page_size) {
2709       gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2710 
2711       scroll_to_tab (self, info, SCROLL_ANIMATION_DURATION);
2712 
2713       return;
2714     }
2715   }
2716 
2717   can_grab_focus = adw_tab_bar_tabs_have_visible_focus (self->tab_bar);
2718 
2719   if (info == self->selected_tab)
2720     can_grab_focus = TRUE;
2721   else
2722     adw_tab_view_set_selected_page (self->view, info->page);
2723 
2724   if (can_grab_focus)
2725     gtk_widget_grab_focus (GTK_WIDGET (info->tab));
2726   else
2727     activate_tab (self);
2728 }
2729 
2730 static void
pressed_cb(AdwTabBox * self,int n_press,double x,double y,GtkGesture * gesture)2731 pressed_cb (AdwTabBox  *self,
2732             int         n_press,
2733             double      x,
2734             double      y,
2735             GtkGesture *gesture)
2736 {
2737   TabInfo *info;
2738   GdkEvent *event;
2739   GdkEventSequence *current;
2740   guint button;
2741 
2742   if (is_touchscreen (gesture))
2743     return;
2744 
2745   x += gtk_adjustment_get_value (self->adjustment);
2746 
2747   info = find_tab_info_at (self, x);
2748 
2749   if (!info || !info->page) {
2750     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
2751 
2752     return;
2753   }
2754 
2755   current = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
2756   event = gtk_gesture_get_last_event (gesture, current);
2757 
2758    if (gdk_event_triggers_context_menu (event)) {
2759     do_popup (self, info, x, y);
2760     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2761     gtk_event_controller_reset (GTK_EVENT_CONTROLLER (gesture));
2762 
2763     return;
2764   }
2765 
2766   button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
2767 
2768   if (button == GDK_BUTTON_MIDDLE) {
2769     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2770     adw_tab_view_close_page (self->view, info->page);
2771 
2772     return;
2773   }
2774 
2775   if (button != GDK_BUTTON_PRIMARY) {
2776     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
2777 
2778     return;
2779   }
2780 
2781   handle_click (self, info, gesture);
2782 }
2783 
2784 static void
released_cb(AdwTabBox * self,int n_press,double x,double y,GtkGesture * gesture)2785 released_cb (AdwTabBox  *self,
2786              int         n_press,
2787              double      x,
2788              double      y,
2789              GtkGesture *gesture)
2790 {
2791   TabInfo *info;
2792 
2793   if (!is_touchscreen (gesture))
2794     return;
2795 
2796   x += gtk_adjustment_get_value (self->adjustment);
2797 
2798   info = find_tab_info_at (self, x);
2799 
2800   if (!info || !info->page) {
2801     gtk_gesture_set_state (gesture, GTK_EVENT_SEQUENCE_DENIED);
2802 
2803     return;
2804   }
2805 
2806   handle_click (self, info, gesture);
2807 }
2808 
2809 /* Overrides */
2810 
2811 static void
adw_tab_box_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)2812 adw_tab_box_measure (GtkWidget      *widget,
2813                      GtkOrientation  orientation,
2814                      int             for_size,
2815                      int            *minimum,
2816                      int            *natural,
2817                      int            *minimum_baseline,
2818                      int            *natural_baseline)
2819 {
2820   AdwTabBox *self = ADW_TAB_BOX (widget);
2821   int min, nat;
2822 
2823   if (self->n_tabs == 0) {
2824     if (minimum)
2825       *minimum = 0;
2826 
2827     if (natural)
2828       *natural = 0;
2829 
2830     if (minimum_baseline)
2831       *minimum_baseline = -1;
2832 
2833     if (natural_baseline)
2834       *natural_baseline = -1;
2835 
2836     return;
2837   }
2838 
2839   if (orientation == GTK_ORIENTATION_HORIZONTAL) {
2840     int width = self->end_padding;
2841     GList *l;
2842 
2843     for (l = self->tabs; l; l = l->next) {
2844       TabInfo *info = l->data;
2845       int child_width;
2846 
2847       gtk_widget_measure (GTK_WIDGET (info->tab), orientation, -1,
2848                           NULL, &child_width, NULL, NULL);
2849 
2850       width += calculate_tab_width (info, child_width) - OVERLAP;
2851     }
2852 
2853     if (!self->pinned)
2854       width -= OVERLAP;
2855 
2856     min = nat = MAX (self->last_width, width);
2857   } else {
2858     GList *l;
2859 
2860     min = nat = 0;
2861 
2862     for (l = self->tabs; l; l = l->next) {
2863       TabInfo *info = l->data;
2864       int child_min, child_nat;
2865 
2866       gtk_widget_measure (GTK_WIDGET (info->tab), orientation, -1,
2867                           &child_min, &child_nat, NULL, NULL);
2868 
2869       if (child_min > min)
2870         min = child_min;
2871 
2872       if (child_nat > nat)
2873         nat = child_nat;
2874     }
2875   }
2876 
2877   if (minimum)
2878     *minimum = min;
2879 
2880   if (natural)
2881     *natural = nat;
2882 
2883   if (minimum_baseline)
2884     *minimum_baseline = -1;
2885 
2886   if (natural_baseline)
2887     *natural_baseline = -1;
2888 }
2889 
2890 static void
adw_tab_box_size_allocate(GtkWidget * widget,int width,int height,int baseline)2891 adw_tab_box_size_allocate (GtkWidget *widget,
2892                            int        width,
2893                            int        height,
2894                            int        baseline)
2895 {
2896   AdwTabBox *self = ADW_TAB_BOX (widget);
2897   gboolean is_rtl;
2898   GList *l;
2899   GtkAllocation child_allocation;
2900   int pos;
2901   double value;
2902 
2903   adw_tab_box_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
2904                        &self->allocated_width, NULL, NULL, NULL);
2905   self->allocated_width = MAX (self->allocated_width, width);
2906 
2907   value = gtk_adjustment_get_value (self->adjustment);
2908 
2909   gtk_adjustment_configure (self->adjustment,
2910                             value,
2911                             0,
2912                             self->allocated_width,
2913                             width * 0.1,
2914                             width * 0.9,
2915                             width);
2916 
2917   if (self->context_menu)
2918     gtk_popover_present (self->context_menu);
2919 
2920   if (!self->n_tabs)
2921     return;
2922 
2923   is_rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL;
2924 
2925   if (self->pinned) {
2926     for (l = self->tabs; l; l = l->next) {
2927       TabInfo *info = l->data;
2928       int child_width;
2929 
2930       gtk_widget_measure (GTK_WIDGET (info->tab), GTK_ORIENTATION_HORIZONTAL, -1,
2931                           NULL, &child_width, NULL, NULL);
2932 
2933       info->width = calculate_tab_width (info, child_width);
2934     }
2935   } else if (self->tab_resize_mode == TAB_RESIZE_FIXED_TAB_WIDTH) {
2936     self->end_padding = self->allocated_width + OVERLAP;
2937 
2938     for (l = self->tabs; l; l = l->next) {
2939       TabInfo *info = l->data;
2940 
2941       info->width = calculate_tab_width (info, info->last_width);
2942       self->end_padding -= info->width - OVERLAP;
2943     }
2944   } else {
2945     int tab_width = get_base_tab_width (self, FALSE);
2946     int excess = self->allocated_width + OVERLAP - self->end_padding;
2947 
2948     for (l = self->tabs; l; l = l->next) {
2949       TabInfo *info = l->data;
2950 
2951       info->width = calculate_tab_width (info, tab_width);
2952       excess -= info->width - OVERLAP;
2953     }
2954 
2955     /* Now spread excess width across the tabs */
2956     for (l = self->tabs; l; l = l->next) {
2957       TabInfo *info = l->data;
2958 
2959       if (excess >= 0)
2960           break;
2961 
2962       info->width--;
2963       excess++;
2964     }
2965   }
2966 
2967   pos = is_rtl ? self->allocated_width + OVERLAP : -OVERLAP;
2968 
2969   for (l = self->tabs; l; l = l->next) {
2970     TabInfo *info = l->data;
2971 
2972     if (!info->appear_animation)
2973       adw_tab_set_display_width (info->tab, info->width);
2974     else if (info->page && info != self->reorder_placeholder)
2975       adw_tab_set_display_width (info->tab, predict_tab_width (self, info, FALSE));
2976 
2977     info->pos = pos + calculate_tab_offset (self, info, FALSE);
2978 
2979     if (is_rtl)
2980       info->pos -= info->width;
2981 
2982     child_allocation.x = ((info == self->reordered_tab) ? self->reorder_window_x : info->pos) - value;
2983     child_allocation.y = 0;
2984     child_allocation.width = info->width;
2985     child_allocation.height = height;
2986 
2987     gtk_widget_size_allocate (GTK_WIDGET (info->tab), &child_allocation, baseline);
2988 
2989     pos += (is_rtl ? -1 : 1) * (info->width - OVERLAP);
2990   }
2991 
2992   if (self->scheduled_scroll.info) {
2993     scroll_to_tab_full (self,
2994                         self->scheduled_scroll.info,
2995                         self->scheduled_scroll.pos,
2996                         self->scheduled_scroll.duration,
2997                         self->scheduled_scroll.keep_selected_visible);
2998     self->scheduled_scroll.info = NULL;
2999   }
3000 
3001   if (self->scroll_animation) {
3002     self->block_scrolling = TRUE;
3003     gtk_adjustment_set_value (self->adjustment,
3004                               get_scroll_animation_value (self));
3005     self->block_scrolling = FALSE;
3006 
3007     if (self->scroll_animation_done) {
3008         self->scroll_animation_done = FALSE;
3009         self->scroll_animation_tab = NULL;
3010         g_clear_object (&self->scroll_animation);
3011     }
3012   }
3013 
3014   update_visible (self);
3015 }
3016 
3017 static gboolean
adw_tab_box_focus(GtkWidget * widget,GtkDirectionType direction)3018 adw_tab_box_focus (GtkWidget        *widget,
3019                    GtkDirectionType  direction)
3020 {
3021   AdwTabBox *self = ADW_TAB_BOX (widget);
3022 
3023   if (!self->selected_tab)
3024     return GDK_EVENT_PROPAGATE;
3025 
3026   return gtk_widget_child_focus (GTK_WIDGET (self->selected_tab->tab), direction);
3027 }
3028 
3029 static void
adw_tab_box_unrealize(GtkWidget * widget)3030 adw_tab_box_unrealize (GtkWidget *widget)
3031 {
3032   AdwTabBox *self = ADW_TAB_BOX (widget);
3033 
3034   g_clear_pointer ((GtkWidget **) &self->context_menu, gtk_widget_unparent);
3035 
3036   GTK_WIDGET_CLASS (adw_tab_box_parent_class)->unrealize (widget);
3037 }
3038 
3039 static void
adw_tab_box_unmap(GtkWidget * widget)3040 adw_tab_box_unmap (GtkWidget *widget)
3041 {
3042   AdwTabBox *self = ADW_TAB_BOX (widget);
3043 
3044   force_end_reordering (self);
3045 
3046   if (self->drag_autoscroll_cb_id) {
3047     gtk_widget_remove_tick_callback (widget, self->drag_autoscroll_cb_id);
3048     self->drag_autoscroll_cb_id = 0;
3049   }
3050 
3051   self->hovering = FALSE;
3052   update_hover (self);
3053 
3054   GTK_WIDGET_CLASS (adw_tab_box_parent_class)->unmap (widget);
3055 }
3056 
3057 static void
adw_tab_box_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)3058 adw_tab_box_direction_changed (GtkWidget        *widget,
3059                                GtkTextDirection  previous_direction)
3060 {
3061   AdwTabBox *self = ADW_TAB_BOX (widget);
3062   double upper, page_size;
3063 
3064   if (!self->adjustment)
3065     return;
3066 
3067   if (gtk_widget_get_direction (widget) == previous_direction)
3068     return;
3069 
3070   upper = gtk_adjustment_get_upper (self->adjustment);
3071   page_size = gtk_adjustment_get_page_size (self->adjustment);
3072 
3073   gtk_adjustment_set_value (self->adjustment,
3074                             upper - page_size - self->adjustment_prev_value);
3075 
3076   if (self->context_menu) {
3077     if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
3078       gtk_widget_set_halign (GTK_WIDGET (self->context_menu), GTK_ALIGN_END);
3079     else
3080       gtk_widget_set_halign (GTK_WIDGET (self->context_menu), GTK_ALIGN_START);
3081   }
3082 }
3083 
3084 static void
adw_tab_box_dispose(GObject * object)3085 adw_tab_box_dispose (GObject *object)
3086 {
3087   AdwTabBox *self = ADW_TAB_BOX (object);
3088 
3089   g_clear_handle_id (&self->drop_switch_timeout_id, g_source_remove);
3090 
3091   self->drag_gesture = NULL;
3092   self->tab_bar = NULL;
3093   adw_tab_box_set_view (self, NULL);
3094   adw_tab_box_set_adjustment (self, NULL);
3095 
3096   G_OBJECT_CLASS (adw_tab_box_parent_class)->dispose (object);
3097 }
3098 
3099 static void
adw_tab_box_finalize(GObject * object)3100 adw_tab_box_finalize (GObject *object)
3101 {
3102   AdwTabBox *self = (AdwTabBox *) object;
3103 
3104   g_clear_pointer (&self->extra_drag_types, g_free);
3105 
3106   G_OBJECT_CLASS (adw_tab_box_parent_class)->finalize (object);
3107 }
3108 
3109 static void
adw_tab_box_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)3110 adw_tab_box_get_property (GObject    *object,
3111                           guint       prop_id,
3112                           GValue     *value,
3113                           GParamSpec *pspec)
3114 {
3115   AdwTabBox *self = ADW_TAB_BOX (object);
3116 
3117   switch (prop_id) {
3118   case PROP_PINNED:
3119     g_value_set_boolean (value, self->pinned);
3120     break;
3121 
3122   case PROP_TAB_BAR:
3123     g_value_set_object (value, self->tab_bar);
3124     break;
3125 
3126   case PROP_VIEW:
3127     g_value_set_object (value, self->view);
3128     break;
3129 
3130   case PROP_NEEDS_ATTENTION_LEFT:
3131     g_value_set_boolean (value, self->needs_attention_left);
3132     break;
3133 
3134   case PROP_NEEDS_ATTENTION_RIGHT:
3135     g_value_set_boolean (value, self->needs_attention_right);
3136     break;
3137 
3138   case PROP_RESIZE_FROZEN:
3139     g_value_set_boolean (value, self->tab_resize_mode != TAB_RESIZE_NORMAL);
3140     break;
3141 
3142   case PROP_HADJUSTMENT:
3143     g_value_set_object (value, self->adjustment);
3144     break;
3145 
3146   case PROP_VADJUSTMENT:
3147   case PROP_HSCROLL_POLICY:
3148   case PROP_VSCROLL_POLICY:
3149     break;
3150 
3151   default:
3152     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3153   }
3154 }
3155 
3156 static void
adw_tab_box_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)3157 adw_tab_box_set_property (GObject      *object,
3158                           guint         prop_id,
3159                           const GValue *value,
3160                           GParamSpec   *pspec)
3161 {
3162   AdwTabBox *self = ADW_TAB_BOX (object);
3163 
3164   switch (prop_id) {
3165   case PROP_PINNED:
3166     self->pinned = g_value_get_boolean (value);
3167     break;
3168 
3169   case PROP_TAB_BAR:
3170     self->tab_bar = g_value_get_object (value);
3171     break;
3172 
3173   case PROP_VIEW:
3174     adw_tab_box_set_view (self, g_value_get_object (value));
3175     break;
3176 
3177   case PROP_HADJUSTMENT:
3178     adw_tab_box_set_adjustment (self, g_value_get_object (value));
3179     break;
3180 
3181   case PROP_VADJUSTMENT:
3182   case PROP_HSCROLL_POLICY:
3183   case PROP_VSCROLL_POLICY:
3184     break;
3185 
3186   default:
3187     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
3188   }
3189 }
3190 
3191 static void
adw_tab_box_class_init(AdwTabBoxClass * klass)3192 adw_tab_box_class_init (AdwTabBoxClass *klass)
3193 {
3194   GObjectClass *object_class = G_OBJECT_CLASS (klass);
3195   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
3196 
3197   object_class->dispose = adw_tab_box_dispose;
3198   object_class->finalize = adw_tab_box_finalize;
3199   object_class->get_property = adw_tab_box_get_property;
3200   object_class->set_property = adw_tab_box_set_property;
3201 
3202   widget_class->measure = adw_tab_box_measure;
3203   widget_class->size_allocate = adw_tab_box_size_allocate;
3204   widget_class->focus = adw_tab_box_focus;
3205   widget_class->unrealize = adw_tab_box_unrealize;
3206   widget_class->unmap = adw_tab_box_unmap;
3207   widget_class->direction_changed = adw_tab_box_direction_changed;
3208 
3209   props[PROP_PINNED] =
3210     g_param_spec_boolean ("pinned",
3211                           "Pinned",
3212                           "Pinned",
3213                           FALSE,
3214                           G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
3215 
3216   props[PROP_TAB_BAR] =
3217     g_param_spec_object ("tab-bar",
3218                          "Tab Bar",
3219                          "Tab Bar",
3220                          ADW_TYPE_TAB_BAR,
3221                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
3222 
3223   props[PROP_VIEW] =
3224     g_param_spec_object ("view",
3225                          "View",
3226                          "View",
3227                          ADW_TYPE_TAB_VIEW,
3228                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
3229 
3230   props[PROP_NEEDS_ATTENTION_LEFT] =
3231     g_param_spec_boolean ("needs-attention-left",
3232                           "Needs Attention Left",
3233                           "Needs Attention Left",
3234                           FALSE,
3235                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3236 
3237   props[PROP_NEEDS_ATTENTION_RIGHT] =
3238     g_param_spec_boolean ("needs-attention-right",
3239                           "Needs Attention Right",
3240                           "Needs Attention Right",
3241                           FALSE,
3242                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3243 
3244   props[PROP_RESIZE_FROZEN] =
3245     g_param_spec_boolean ("resize-frozen",
3246                           "Resize Frozen",
3247                           "Resize Frozen",
3248                           FALSE,
3249                           G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
3250 
3251   g_object_class_install_properties (object_class, LAST_PROP, props);
3252 
3253   g_object_class_override_property (object_class, PROP_HADJUSTMENT, "hadjustment");
3254   g_object_class_override_property (object_class, PROP_VADJUSTMENT, "vadjustment");
3255   g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
3256   g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
3257 
3258   signals[SIGNAL_STOP_KINETIC_SCROLLING] =
3259     g_signal_new ("stop-kinetic-scrolling",
3260                   G_TYPE_FROM_CLASS (klass),
3261                   G_SIGNAL_RUN_LAST,
3262                   0,
3263                   NULL, NULL, NULL,
3264                   G_TYPE_NONE,
3265                   0);
3266 
3267   signals[SIGNAL_EXTRA_DRAG_DROP] =
3268     g_signal_new ("extra-drag-drop",
3269                   G_TYPE_FROM_CLASS (klass),
3270                   G_SIGNAL_RUN_LAST,
3271                   0,
3272                   g_signal_accumulator_first_wins, NULL, NULL,
3273                   G_TYPE_BOOLEAN,
3274                   2,
3275                   ADW_TYPE_TAB_PAGE,
3276                   G_TYPE_VALUE);
3277 
3278   gtk_widget_class_install_action (widget_class, "menu.popup", NULL, popup_menu_cb);
3279 
3280   gtk_widget_class_add_binding_action (widget_class, GDK_KEY_F10, GDK_SHIFT_MASK, "menu.popup", NULL);
3281   gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Menu, 0, "menu.popup", NULL);
3282 
3283   add_focus_bindings (widget_class, GDK_KEY_Page_Up,   GTK_DIR_TAB_BACKWARD, FALSE);
3284   add_focus_bindings (widget_class, GDK_KEY_Page_Down, GTK_DIR_TAB_FORWARD,  FALSE);
3285   add_focus_bindings (widget_class, GDK_KEY_Home,      GTK_DIR_TAB_BACKWARD, TRUE);
3286   add_focus_bindings (widget_class, GDK_KEY_End,       GTK_DIR_TAB_FORWARD,  TRUE);
3287 
3288   add_reorder_bindings (widget_class, GDK_KEY_Left,      GTK_DIR_LEFT,         FALSE);
3289   add_reorder_bindings (widget_class, GDK_KEY_Right,     GTK_DIR_RIGHT,        FALSE);
3290   add_reorder_bindings (widget_class, GDK_KEY_Page_Up,   GTK_DIR_TAB_BACKWARD, FALSE);
3291   add_reorder_bindings (widget_class, GDK_KEY_Page_Down, GTK_DIR_TAB_FORWARD,  FALSE);
3292   add_reorder_bindings (widget_class, GDK_KEY_Home,      GTK_DIR_TAB_BACKWARD, TRUE);
3293   add_reorder_bindings (widget_class, GDK_KEY_End,       GTK_DIR_TAB_FORWARD,  TRUE);
3294 
3295   gtk_widget_class_set_css_name (widget_class, "tabbox");
3296 }
3297 
3298 static void
adw_tab_box_init(AdwTabBox * self)3299 adw_tab_box_init (AdwTabBox *self)
3300 {
3301   GtkEventController *controller;
3302 
3303   self->can_remove_placeholder = TRUE;
3304   self->expand_tabs = TRUE;
3305 
3306   gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
3307 
3308   controller = gtk_event_controller_motion_new ();
3309   g_signal_connect_swapped (controller, "motion", G_CALLBACK (motion_cb), self);
3310   g_signal_connect_swapped (controller, "leave", G_CALLBACK (leave_cb), self);
3311   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3312 
3313   controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
3314   g_signal_connect_swapped (controller, "scroll", G_CALLBACK (scroll_cb), self);
3315   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3316 
3317   controller = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
3318   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), 0);
3319   gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (controller), TRUE);
3320   g_signal_connect_swapped (controller, "pressed", G_CALLBACK (pressed_cb), self);
3321   g_signal_connect_swapped (controller, "released", G_CALLBACK (released_cb), self);
3322   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3323 
3324   controller = GTK_EVENT_CONTROLLER (gtk_gesture_long_press_new ());
3325   gtk_gesture_long_press_set_delay_factor (GTK_GESTURE_LONG_PRESS (controller), 2);
3326   gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (controller), TRUE);
3327   gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (controller), TRUE);
3328   g_signal_connect_swapped (controller, "pressed", G_CALLBACK (long_pressed_cb), self);
3329   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3330 
3331   controller = GTK_EVENT_CONTROLLER (gtk_gesture_drag_new ());
3332   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (controller), GDK_BUTTON_PRIMARY);
3333   gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (controller), TRUE);
3334   g_signal_connect_swapped (controller, "drag-begin", G_CALLBACK (reorder_begin_cb), self);
3335   g_signal_connect_swapped (controller, "drag-update", G_CALLBACK (reorder_update_cb), self);
3336   g_signal_connect_swapped (controller, "drag-end", G_CALLBACK (reorder_end_cb), self);
3337   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3338   self->drag_gesture = GTK_GESTURE (controller);
3339 
3340   controller = gtk_drop_controller_motion_new ();
3341   g_signal_connect_swapped (controller, "enter", G_CALLBACK (drag_enter_motion_cb), self);
3342   g_signal_connect_swapped (controller, "motion", G_CALLBACK (drag_enter_motion_cb), self);
3343   g_signal_connect_swapped (controller, "leave", G_CALLBACK (drag_leave_cb), self);
3344   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3345 
3346   controller = GTK_EVENT_CONTROLLER (gtk_drop_target_new (ADW_TYPE_TAB_PAGE, GDK_ACTION_MOVE));
3347   gtk_drop_target_set_preload (GTK_DROP_TARGET (controller), TRUE);
3348   g_signal_connect_swapped (controller, "enter", G_CALLBACK (tab_drag_enter_motion_cb), self);
3349   g_signal_connect_swapped (controller, "motion", G_CALLBACK (tab_drag_enter_motion_cb), self);
3350   g_signal_connect_swapped (controller, "leave", G_CALLBACK (tab_drag_leave_cb), self);
3351   g_signal_connect_swapped (controller, "drop", G_CALLBACK (tab_drag_drop_cb), self);
3352   gtk_widget_add_controller (GTK_WIDGET (self), controller);
3353 }
3354 
3355 void
adw_tab_box_set_view(AdwTabBox * self,AdwTabView * view)3356 adw_tab_box_set_view (AdwTabBox  *self,
3357                       AdwTabView *view)
3358 {
3359   g_return_if_fail (ADW_IS_TAB_BOX (self));
3360   g_return_if_fail (ADW_IS_TAB_VIEW (view) || view == NULL);
3361 
3362   if (view == self->view)
3363     return;
3364 
3365   if (self->view) {
3366     force_end_reordering (self);
3367     g_signal_handlers_disconnect_by_func (self->view, page_attached_cb, self);
3368     g_signal_handlers_disconnect_by_func (self->view, page_detached_cb, self);
3369     g_signal_handlers_disconnect_by_func (self->view, page_reordered_cb, self);
3370 
3371     if (!self->pinned) {
3372       gtk_widget_remove_controller (GTK_WIDGET (self->view), self->view_drop_target);
3373       self->view_drop_target = NULL;
3374     }
3375 
3376     g_list_free_full (self->tabs, (GDestroyNotify) remove_and_free_tab_info);
3377 
3378     self->tabs = NULL;
3379     self->n_tabs = 0;
3380   }
3381 
3382   self->view = view;
3383 
3384   if (self->view) {
3385     int i, n_pages = adw_tab_view_get_n_pages (self->view);
3386 
3387     for (i = n_pages - 1; i >= 0; i--)
3388       page_attached_cb (self, adw_tab_view_get_nth_page (self->view, i), 0);
3389 
3390     g_signal_connect_object (self->view, "page-attached", G_CALLBACK (page_attached_cb), self, G_CONNECT_SWAPPED);
3391     g_signal_connect_object (self->view, "page-detached", G_CALLBACK (page_detached_cb), self, G_CONNECT_SWAPPED);
3392     g_signal_connect_object (self->view, "page-reordered", G_CALLBACK (page_reordered_cb), self, G_CONNECT_SWAPPED);
3393 
3394     if (!self->pinned) {
3395       self->view_drop_target = GTK_EVENT_CONTROLLER (gtk_drop_target_new (ADW_TYPE_TAB_PAGE, GDK_ACTION_MOVE));
3396 
3397       g_signal_connect_object (self->view_drop_target, "drop", G_CALLBACK (view_drag_drop_cb), self, G_CONNECT_SWAPPED);
3398 
3399       gtk_widget_add_controller (GTK_WIDGET (self->view), self->view_drop_target);
3400     }
3401   }
3402 
3403   gtk_widget_queue_allocate (GTK_WIDGET (self));
3404 
3405   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW]);
3406 }
3407 
3408 void
adw_tab_box_set_adjustment(AdwTabBox * self,GtkAdjustment * adjustment)3409 adw_tab_box_set_adjustment (AdwTabBox     *self,
3410                             GtkAdjustment *adjustment)
3411 {
3412   g_return_if_fail (ADW_IS_TAB_BOX (self));
3413   g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment) || adjustment == NULL);
3414 
3415   if (adjustment == self->adjustment)
3416     return;
3417 
3418   if (self->adjustment) {
3419     g_signal_handlers_disconnect_by_func (self->adjustment, adjustment_value_changed_cb, self);
3420     g_signal_handlers_disconnect_by_func (self->adjustment, update_visible, self);
3421   }
3422 
3423   g_set_object (&self->adjustment, adjustment);
3424 
3425   if (self->adjustment) {
3426     g_signal_connect_object (self->adjustment, "value-changed", G_CALLBACK (adjustment_value_changed_cb), self, G_CONNECT_SWAPPED);
3427     g_signal_connect_object (self->adjustment, "notify::page-size", G_CALLBACK (update_visible), self, G_CONNECT_SWAPPED);
3428   }
3429 
3430   g_object_notify (G_OBJECT (self), "hadjustment");
3431 }
3432 
3433 void
adw_tab_box_attach_page(AdwTabBox * self,AdwTabPage * page,int position)3434 adw_tab_box_attach_page (AdwTabBox  *self,
3435                          AdwTabPage *page,
3436                          int         position)
3437 {
3438   g_return_if_fail (ADW_IS_TAB_BOX (self));
3439   g_return_if_fail (ADW_IS_TAB_PAGE (page));
3440 
3441   page_attached_cb (self, page, position);
3442 }
3443 
3444 void
adw_tab_box_detach_page(AdwTabBox * self,AdwTabPage * page)3445 adw_tab_box_detach_page (AdwTabBox  *self,
3446                          AdwTabPage *page)
3447 {
3448   g_return_if_fail (ADW_IS_TAB_BOX (self));
3449   g_return_if_fail (ADW_IS_TAB_PAGE (page));
3450 
3451   page_detached_cb (self, page);
3452 }
3453 
3454 void
adw_tab_box_select_page(AdwTabBox * self,AdwTabPage * page)3455 adw_tab_box_select_page (AdwTabBox  *self,
3456                          AdwTabPage *page)
3457 {
3458   g_return_if_fail (ADW_IS_TAB_BOX (self));
3459   g_return_if_fail (ADW_IS_TAB_PAGE (page) || page == NULL);
3460 
3461   select_page (self, page);
3462 }
3463 
3464 void
adw_tab_box_try_focus_selected_tab(AdwTabBox * self)3465 adw_tab_box_try_focus_selected_tab (AdwTabBox *self)
3466 {
3467   g_return_if_fail (ADW_IS_TAB_BOX (self));
3468 
3469   if (self->selected_tab)
3470     gtk_widget_grab_focus (GTK_WIDGET (self->selected_tab->tab));
3471 }
3472 
3473 gboolean
adw_tab_box_is_page_focused(AdwTabBox * self,AdwTabPage * page)3474 adw_tab_box_is_page_focused (AdwTabBox  *self,
3475                              AdwTabPage *page)
3476 {
3477   TabInfo *info;
3478 
3479   g_return_val_if_fail (ADW_IS_TAB_BOX (self), FALSE);
3480   g_return_val_if_fail (ADW_IS_TAB_PAGE (page), FALSE);
3481 
3482   info = find_info_for_page (self, page);
3483 
3484   return info && gtk_widget_is_focus (GTK_WIDGET (info->tab));
3485 }
3486 
3487 void
adw_tab_box_setup_extra_drop_target(AdwTabBox * self,GdkDragAction actions,GType * types,gsize n_types)3488 adw_tab_box_setup_extra_drop_target (AdwTabBox     *self,
3489                                      GdkDragAction  actions,
3490                                      GType         *types,
3491                                      gsize          n_types)
3492 {
3493   GList *l;
3494 
3495   g_return_if_fail (ADW_IS_TAB_BOX (self));
3496   g_return_if_fail (n_types == 0 || types != NULL);
3497 
3498   g_clear_pointer (&self->extra_drag_types, g_free);
3499 
3500   self->extra_drag_actions = actions;
3501 #if GLIB_CHECK_VERSION(2, 67, 3)
3502   self->extra_drag_types = g_memdup2 (types, sizeof (GType) * n_types);
3503 #else
3504   self->extra_drag_types = g_memdup (types, sizeof (GType) * n_types);
3505 #endif
3506   self->extra_drag_n_types = n_types;
3507 
3508   for (l = self->tabs; l; l = l->next) {
3509     TabInfo *info = l->data;
3510 
3511     adw_tab_setup_extra_drop_target (info->tab,
3512                                      self->extra_drag_actions,
3513                                      self->extra_drag_types,
3514                                      self->extra_drag_n_types);
3515   }
3516 }
3517 
3518 gboolean
adw_tab_box_get_expand_tabs(AdwTabBox * self)3519 adw_tab_box_get_expand_tabs (AdwTabBox *self)
3520 {
3521   g_return_val_if_fail (ADW_IS_TAB_BOX (self), FALSE);
3522 
3523   return self->expand_tabs;
3524 }
3525 
3526 void
adw_tab_box_set_expand_tabs(AdwTabBox * self,gboolean expand_tabs)3527 adw_tab_box_set_expand_tabs (AdwTabBox *self,
3528                              gboolean   expand_tabs)
3529 {
3530   g_return_if_fail (ADW_IS_TAB_BOX (self));
3531 
3532   expand_tabs = !!expand_tabs;
3533 
3534   if (expand_tabs == self->expand_tabs)
3535     return;
3536 
3537   self->expand_tabs = expand_tabs;
3538 
3539   gtk_widget_queue_resize (GTK_WIDGET (self));
3540 }
3541 
3542 gboolean
adw_tab_box_get_inverted(AdwTabBox * self)3543 adw_tab_box_get_inverted (AdwTabBox *self)
3544 {
3545   g_return_val_if_fail (ADW_IS_TAB_BOX (self), FALSE);
3546 
3547   return self->inverted;
3548 }
3549 
3550 void
adw_tab_box_set_inverted(AdwTabBox * self,gboolean inverted)3551 adw_tab_box_set_inverted (AdwTabBox *self,
3552                           gboolean   inverted)
3553 {
3554   GList *l;
3555 
3556   g_return_if_fail (ADW_IS_TAB_BOX (self));
3557 
3558   inverted = !!inverted;
3559 
3560   if (inverted == self->inverted)
3561     return;
3562 
3563   self->inverted = inverted;
3564 
3565   for (l = self->tabs; l; l = l->next) {
3566     TabInfo *info = l->data;
3567 
3568     adw_tab_set_inverted (info->tab, inverted);
3569   }
3570 }
3571