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