[libadwaita/wip/exalm/tab-overview: 7/15] tab: Extract AdwTabItem




commit 81be5a6fe3c319477bd0814ad1ac2a027ef7c690
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Sat Aug 21 03:57:08 2021 +0500

    tab: Extract AdwTabItem

 src/adw-tab-item-private.h |  67 ++++++
 src/adw-tab-item.c         | 566 +++++++++++++++++++++++++++++++++++++++++++
 src/adw-tab-list-base.c    |  89 +++----
 src/adw-tab-private.h      |  32 +--
 src/adw-tab.c              | 582 +++++++++------------------------------------
 src/adw-tab.ui             |   5 +-
 6 files changed, 805 insertions(+), 536 deletions(-)
---
diff --git a/src/adw-tab-item-private.h b/src/adw-tab-item-private.h
new file mode 100644
index 00000000..ce085b6e
--- /dev/null
+++ b/src/adw-tab-item-private.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#pragma once
+
+#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION)
+#error "Only <adwaita.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include "adw-tab-view.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_TAB_ITEM (adw_tab_item_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (AdwTabItem, adw_tab_item, ADW, TAB_ITEM, GtkWidget)
+
+struct _AdwTabItemClass
+{
+  GtkWidgetClass parent_class;
+
+  void (*connect_page)    (AdwTabItem *self);
+  void (*disconnect_page) (AdwTabItem *self);
+};
+
+AdwTabView *adw_tab_item_get_view (AdwTabItem *self);
+
+AdwTabPage *adw_tab_item_get_page (AdwTabItem *self);
+void        adw_tab_item_set_page (AdwTabItem *self,
+                                   AdwTabPage *page);
+
+gboolean adw_tab_item_get_pinned (AdwTabItem *self);
+
+int  adw_tab_item_get_display_width (AdwTabItem *self);
+void adw_tab_item_set_display_width (AdwTabItem *self,
+                                     int         width);
+
+gboolean adw_tab_item_get_hovering (AdwTabItem *self);
+
+gboolean adw_tab_item_get_dragging (AdwTabItem *self);
+void     adw_tab_item_set_dragging (AdwTabItem *self,
+                                    gboolean    dragging);
+
+gboolean adw_tab_item_get_inverted (AdwTabItem *self);
+void     adw_tab_item_set_inverted (AdwTabItem *self,
+                                    gboolean    inverted);
+
+gboolean adw_tab_item_get_fully_visible (AdwTabItem *self);
+void     adw_tab_item_set_fully_visible (AdwTabItem *self,
+                                         gboolean    fully_visible);
+
+void adw_tab_item_setup_extra_drop_target (AdwTabItem    *self,
+                                           GdkDragAction  actions,
+                                           GType         *types,
+                                           gsize          n_types);
+
+void adw_tab_item_close (AdwTabItem *self);
+
+void adw_tab_item_activate_indicator (AdwTabItem *self);
+
+G_END_DECLS
diff --git a/src/adw-tab-item.c b/src/adw-tab-item.c
new file mode 100644
index 00000000..e9234f27
--- /dev/null
+++ b/src/adw-tab-item.c
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2020-2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+#include "adw-tab-item-private.h"
+
+typedef struct {
+  GtkDropTarget *drop_target;
+
+  AdwTabView *view;
+  AdwTabPage *page;
+  gboolean pinned;
+  gboolean hovering;
+  gboolean dragging;
+  gboolean fully_visible;
+  int display_width;
+  gboolean inverted;
+} AdwTabItemPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (AdwTabItem, adw_tab_item, GTK_TYPE_WIDGET)
+
+enum {
+  PROP_0,
+  PROP_VIEW,
+  PROP_PAGE,
+  PROP_PINNED,
+  PROP_HOVERING,
+  PROP_DRAGGING,
+  PROP_FULLY_VISIBLE,
+  PROP_DISPLAY_WIDTH,
+  PROP_INVERTED,
+  LAST_PROP
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+  SIGNAL_EXTRA_DRAG_DROP,
+  SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static gboolean
+close_idle_cb (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+
+  adw_tab_view_close_page (priv->view, priv->page);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+motion_cb (AdwTabItem         *self,
+           double              x,
+           double              y,
+           GtkEventController *controller)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+  GdkDevice *device = gtk_event_controller_get_current_event_device (controller);
+  GdkInputSource input_source = gdk_device_get_source (device);
+
+  if (input_source == GDK_SOURCE_TOUCHSCREEN)
+    return;
+
+  if (priv->hovering)
+    return;
+
+  priv->hovering = TRUE;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOVERING]);
+}
+
+static void
+leave_cb (AdwTabItem         *self,
+          GtkEventController *controller)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+
+  if (priv->hovering)
+    return;
+
+  priv->hovering = FALSE;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_HOVERING]);
+}
+
+static gboolean
+drop_cb (AdwTabItem *self,
+         GValue     *value)
+{
+  gboolean ret = GDK_EVENT_PROPAGATE;
+
+  g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DROP], 0, value, &ret);
+
+  return ret;
+}
+
+static gboolean
+activate_cb (AdwTabItem *self,
+             GVariant   *args)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+  GtkWidget *child;
+
+  if (!priv->page || !priv->view)
+    return GDK_EVENT_PROPAGATE;
+
+  child = adw_tab_page_get_child (priv->page);
+
+  gtk_widget_grab_focus (child);
+
+  return GDK_EVENT_STOP;
+}
+
+static void
+adw_tab_item_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  AdwTabItem *self = ADW_TAB_ITEM (object);
+
+  switch (prop_id) {
+  case PROP_VIEW:
+    g_value_set_object (value, adw_tab_item_get_view (self));
+    break;
+
+  case PROP_PAGE:
+    g_value_set_object (value, adw_tab_item_get_page (self));
+    break;
+
+  case PROP_PINNED:
+    g_value_set_boolean (value, adw_tab_item_get_pinned (self));
+    break;
+
+  case PROP_HOVERING:
+    g_value_set_boolean (value, adw_tab_item_get_hovering (self));
+    break;
+
+  case PROP_DRAGGING:
+    g_value_set_boolean (value, adw_tab_item_get_dragging (self));
+    break;
+
+  case PROP_FULLY_VISIBLE:
+    g_value_set_boolean (value, adw_tab_item_get_fully_visible (self));
+    break;
+
+  case PROP_DISPLAY_WIDTH:
+    g_value_set_int (value, adw_tab_item_get_display_width (self));
+    break;
+
+  case PROP_INVERTED:
+    g_value_set_boolean (value, adw_tab_item_get_inverted (self));
+    break;
+
+    default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_tab_item_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  AdwTabItem *self = ADW_TAB_ITEM (object);
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+
+  switch (prop_id) {
+  case PROP_VIEW:
+    priv->view = g_value_get_object (value);
+    break;
+
+  case PROP_PAGE:
+    adw_tab_item_set_page (self, g_value_get_object (value));
+    break;
+
+  case PROP_PINNED:
+    priv->pinned = g_value_get_boolean (value);
+    break;
+
+  case PROP_DRAGGING:
+    adw_tab_item_set_dragging (self, g_value_get_boolean (value));
+    break;
+
+  case PROP_FULLY_VISIBLE:
+    adw_tab_item_set_fully_visible (self, g_value_get_boolean (value));
+    break;
+
+  case PROP_DISPLAY_WIDTH:
+    adw_tab_item_set_display_width (self, g_value_get_int (value));
+    break;
+
+  case PROP_INVERTED:
+    adw_tab_item_set_inverted (self, g_value_get_boolean (value));
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_tab_item_dispose (GObject *object)
+{
+  AdwTabItem *self = ADW_TAB_ITEM (object);
+
+  adw_tab_item_set_page (self, NULL);
+
+  G_OBJECT_CLASS (adw_tab_item_parent_class)->dispose (object);
+}
+
+static void
+adw_tab_item_class_init (AdwTabItemClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  object_class->dispose = adw_tab_item_dispose;
+  object_class->get_property = adw_tab_item_get_property;
+  object_class->set_property = adw_tab_item_set_property;
+
+  props[PROP_VIEW] =
+    g_param_spec_object ("view",
+                         "View",
+                         "View",
+                         ADW_TYPE_TAB_VIEW,
+                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  props[PROP_PAGE] =
+    g_param_spec_object ("page",
+                         "Page",
+                         "Page",
+                         ADW_TYPE_TAB_PAGE,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_PINNED] =
+    g_param_spec_boolean ("pinned",
+                          "Pinned",
+                          "Pinned",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+
+  props[PROP_HOVERING] =
+    g_param_spec_boolean ("hovering",
+                          "Hovering",
+                          "Hovering",
+                          FALSE,
+                          G_PARAM_READABLE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_DRAGGING] =
+    g_param_spec_boolean ("dragging",
+                          "Dragging",
+                          "Dragging",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_FULLY_VISIBLE] =
+    g_param_spec_boolean ("fully-visible",
+                          "Fully visible",
+                          "Fully visible",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_DISPLAY_WIDTH] =
+    g_param_spec_int ("display-width",
+                      "Display Width",
+                      "Display Width",
+                      0, G_MAXINT, 0,
+                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  props[PROP_INVERTED] =
+    g_param_spec_boolean ("inverted",
+                          "Inverted",
+                          "Inverted",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  signals[SIGNAL_EXTRA_DRAG_DROP] =
+    g_signal_new ("extra-drag-drop",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  g_signal_accumulator_first_wins, NULL, NULL,
+                  G_TYPE_BOOLEAN,
+                  1,
+                  G_TYPE_VALUE);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_space,     0, (GtkShortcutFunc) activate_cb, NULL);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Space,  0, (GtkShortcutFunc) activate_cb, NULL);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_Return,    0, (GtkShortcutFunc) activate_cb, NULL);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_ISO_Enter, 0, (GtkShortcutFunc) activate_cb, NULL);
+  gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Enter,  0, (GtkShortcutFunc) activate_cb, NULL);
+}
+
+static void
+adw_tab_item_init (AdwTabItem *self)
+{
+  GtkEventController *controller = gtk_event_controller_motion_new ();
+  g_signal_connect_swapped (controller, "motion", G_CALLBACK (motion_cb), self);
+  g_signal_connect_swapped (controller, "leave", G_CALLBACK (leave_cb), self);
+  gtk_widget_add_controller (GTK_WIDGET (self), controller);
+}
+
+AdwTabView *
+adw_tab_item_get_view (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), NULL);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->view;
+}
+
+AdwTabPage *
+adw_tab_item_get_page (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), NULL);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->page;
+}
+
+void
+adw_tab_item_set_page (AdwTabItem *self,
+                       AdwTabPage *page)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+  g_return_if_fail (page == NULL || ADW_IS_TAB_PAGE (page));
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  if (priv->page == page)
+    return;
+
+  if (priv->page)
+    ADW_TAB_ITEM_GET_CLASS (self)->disconnect_page (self);
+
+  g_set_object (&priv->page, page);
+
+  if (priv->page)
+    ADW_TAB_ITEM_GET_CLASS (self)->connect_page (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PAGE]);
+}
+
+gboolean
+adw_tab_item_get_pinned (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), FALSE);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->pinned;
+}
+
+int
+adw_tab_item_get_display_width (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), 0);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->display_width;
+}
+
+void
+adw_tab_item_set_display_width (AdwTabItem *self,
+                                int         width)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+  g_return_if_fail (width >= 0);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  if (priv->display_width == width)
+    return;
+
+  priv->display_width = width;
+
+  gtk_widget_queue_resize (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DISPLAY_WIDTH]);
+}
+
+gboolean
+adw_tab_item_get_hovering (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), FALSE);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->hovering;
+}
+
+gboolean
+adw_tab_item_get_dragging (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), FALSE);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->dragging;
+}
+
+void
+adw_tab_item_set_dragging (AdwTabItem *self,
+                           gboolean    dragging)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  dragging = !!dragging;
+
+  if (priv->dragging == dragging)
+    return;
+
+  priv->dragging = dragging;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DRAGGING]);
+}
+
+gboolean
+adw_tab_item_get_inverted (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), FALSE);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->inverted;
+}
+
+void
+adw_tab_item_set_inverted (AdwTabItem *self,
+                           gboolean    inverted)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  inverted = !!inverted;
+
+  if (priv->inverted == inverted)
+    return;
+
+  priv->inverted = inverted;
+
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INVERTED]);
+}
+
+gboolean
+adw_tab_item_get_fully_visible (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_val_if_fail (ADW_IS_TAB_ITEM (self), FALSE);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  return priv->fully_visible;
+}
+
+void
+adw_tab_item_set_fully_visible (AdwTabItem *self,
+                                gboolean    fully_visible)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  fully_visible = !!fully_visible;
+
+  if (priv->fully_visible == fully_visible)
+    return;
+
+  priv->fully_visible = fully_visible;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_FULLY_VISIBLE]);
+}
+
+void
+adw_tab_item_setup_extra_drop_target (AdwTabItem    *self,
+                                      GdkDragAction  actions,
+                                      GType         *types,
+                                      gsize          n_types)
+{
+  AdwTabItemPrivate *priv;
+
+  g_return_if_fail (ADW_IS_TAB_ITEM (self));
+  g_return_if_fail (n_types == 0 || types != NULL);
+
+  priv = adw_tab_item_get_instance_private (self);
+
+  if (!priv->drop_target) {
+    priv->drop_target = gtk_drop_target_new (G_TYPE_INVALID, actions);
+    g_signal_connect_swapped (priv->drop_target, "drop", G_CALLBACK (drop_cb), self);
+    gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (priv->drop_target));
+  } else {
+    gtk_drop_target_set_actions (priv->drop_target, actions);
+  }
+
+  gtk_drop_target_set_gtypes (priv->drop_target, types, n_types);
+}
+
+void
+adw_tab_item_close (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+
+  if (!priv->page)
+    return;
+
+  /* When animations are disabled, we don't want to immediately remove the
+   * whole tab mid-click. Instead, defer it until the click has happened.
+   */
+  g_idle_add ((GSourceFunc) close_idle_cb, self);
+}
+
+void
+adw_tab_item_activate_indicator (AdwTabItem *self)
+{
+  AdwTabItemPrivate *priv = adw_tab_item_get_instance_private (self);
+
+  if (!priv->page)
+    return;
+
+  g_signal_emit_by_name (priv->view, "indicator-activated", priv->page);
+}
diff --git a/src/adw-tab-list-base.c b/src/adw-tab-list-base.c
index ddf453f5..ab96a0b6 100644
--- a/src/adw-tab-list-base.c
+++ b/src/adw-tab-list-base.c
@@ -14,6 +14,7 @@
 #include "adw-animation-private.h"
 #include "adw-gizmo-private.h"
 #include "adw-tab-private.h"
+#include "adw-tab-item-private.h"
 #include "adw-tab-view-private.h"
 #include <math.h>
 
@@ -43,7 +44,7 @@ typedef enum {
 typedef struct {
   GdkDrag *drag;
 
-  AdwTab *tab;
+  AdwTabItem *tab;
   GtkBorder tab_margin;
 
   int hotspot_x;
@@ -56,7 +57,7 @@ typedef struct {
 
 typedef struct {
   AdwTabPage *page;
-  AdwTab *tab;
+  AdwTabItem *tab;
 
   int pos;
   int width;
@@ -352,7 +353,7 @@ calculate_tab_offset (AdwTabListBase *self,
   if (!priv->reordered_tab)
       return 0;
 
-  width = (target ? adw_tab_get_display_width (priv->reordered_tab->tab) : priv->reordered_tab->width) - 
OVERLAP;
+  width = (target ? adw_tab_item_get_display_width (priv->reordered_tab->tab) : priv->reordered_tab->width) 
- OVERLAP;
 
   if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
       width = -width;
@@ -462,7 +463,7 @@ set_tab_resize_mode (AdwTabListBase *self,
       TabInfo *info = l->data;
 
       if (info->appear_animation)
-        info->last_width = adw_tab_get_display_width (info->tab);
+        info->last_width = adw_tab_item_get_display_width (info->tab);
       else
         info->last_width = info->width;
     }
@@ -688,9 +689,9 @@ update_visible (AdwTabListBase *self)
 
     pos = get_tab_position (self, info);
 
-    adw_tab_set_fully_visible (info->tab,
-                               pos + OVERLAP >= value &&
-                               pos + info->width - OVERLAP <= value + page_size);
+    adw_tab_item_set_fully_visible (info->tab,
+                                    pos + OVERLAP >= value &&
+                                    pos + info->width - OVERLAP <= value + page_size);
 
     if (!adw_tab_page_get_needs_attention (info->page))
       continue;
@@ -893,7 +894,7 @@ scroll_to_tab_full (AdwTabListBase *self,
   }
 
   if (info->appear_animation)
-    tab_width = adw_tab_get_display_width (info->tab);
+    tab_width = adw_tab_item_get_display_width (info->tab);
 
   value = gtk_adjustment_get_value (priv->adjustment);
   page_size = gtk_adjustment_get_page_size (priv->adjustment);
@@ -1281,7 +1282,7 @@ update_drag_reodering (AdwTabListBase *self)
 
   x = get_reorder_position (self);
 
-  width = adw_tab_get_display_width (priv->reordered_tab->tab);
+  width = adw_tab_item_get_display_width (priv->reordered_tab->tab);
 
   priv->reorder_window_x = x;
 
@@ -1670,12 +1671,12 @@ select_page (AdwTabListBase *self,
 /* Opening */
 
 static gboolean
-extra_drag_drop_cb (AdwTab         *tab,
+extra_drag_drop_cb (AdwTabItem     *tab,
                     GValue         *value,
                     AdwTabListBase *self)
 {
   gboolean ret = GDK_EVENT_PROPAGATE;
-  AdwTabPage *page = adw_tab_get_page (tab);
+  AdwTabPage *page = adw_tab_item_get_page (tab);
 
   g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DROP], 0, page, value, &ret);
 
@@ -1713,14 +1714,17 @@ create_tab_info (AdwTabListBase *self,
   info->page = page;
   info->pos = -1;
   info->width = -1;
-  info->tab = adw_tab_new (priv->view, priv->pinned);
-
-  adw_tab_set_page (info->tab, page);
-  adw_tab_set_inverted (info->tab, priv->inverted);
-  adw_tab_setup_extra_drop_target (info->tab,
-                                   priv->extra_drag_actions,
-                                   priv->extra_drag_types,
-                                   priv->extra_drag_n_types);
+  info->tab = g_object_new (ADW_TYPE_TAB,
+                            "view", priv->view,
+                            "pinned", priv->pinned,
+                            NULL);
+
+  adw_tab_item_set_page (info->tab, page);
+  adw_tab_item_set_inverted (info->tab, priv->inverted);
+  adw_tab_item_setup_extra_drop_target (info->tab,
+                                        priv->extra_drag_actions,
+                                        priv->extra_drag_types,
+                                        priv->extra_drag_n_types);
 
   gtk_widget_set_parent (GTK_WIDGET (info->tab), GTK_WIDGET (self));
 
@@ -1853,7 +1857,7 @@ page_detached_cb (AdwTabListBase *self,
   if (info == priv->selected_tab)
     adw_tab_list_base_select_page (self, NULL);
 
-  adw_tab_set_page (info->tab, NULL);
+  adw_tab_item_set_page (info->tab, NULL);
 
   if (info->notify_needs_attention_id > 0) {
     g_signal_handler_disconnect (info->page, info->notify_needs_attention_id);
@@ -2033,7 +2037,7 @@ insert_placeholder (AdwTabListBase *self,
 
     gtk_widget_set_opacity (GTK_WIDGET (info->tab), 0);
 
-    adw_tab_set_dragging (info->tab, TRUE);
+    adw_tab_item_set_dragging (info->tab, TRUE);
 
     info->reorder_ignore_bounds = TRUE;
 
@@ -2095,7 +2099,7 @@ replace_placeholder (AdwTabListBase *self,
 
   priv->placeholder_scroll_offset = 0;
   gtk_widget_set_opacity (GTK_WIDGET (priv->reorder_placeholder->tab), 1);
-  adw_tab_set_dragging (info->tab, FALSE);
+  adw_tab_item_set_dragging (info->tab, FALSE);
 
   if (!info->appear_animation) {
     priv->reorder_placeholder = NULL;
@@ -2107,7 +2111,7 @@ replace_placeholder (AdwTabListBase *self,
 
   priv->can_remove_placeholder = FALSE;
 
-  adw_tab_set_page (info->tab, page);
+  adw_tab_item_set_page (info->tab, page);
   info->page = page;
 
   adw_animation_stop (info->appear_animation);
@@ -2134,7 +2138,7 @@ remove_animation_done_cb (gpointer user_data)
   g_clear_object (&info->appear_animation);
 
   if (!priv->can_remove_placeholder) {
-    adw_tab_set_page (info->tab, priv->placeholder_page);
+    adw_tab_item_set_page (info->tab, priv->placeholder_page);
     info->page = priv->placeholder_page;
 
     return;
@@ -2181,7 +2185,7 @@ remove_placeholder (AdwTabListBase *self)
   if (!info || !info->page)
     return;
 
-  adw_tab_set_page (info->tab, NULL);
+  adw_tab_item_set_page (info->tab, NULL);
   info->page = NULL;
 
   if (info->appear_animation)
@@ -2349,11 +2353,14 @@ create_drag_icon (AdwTabListBase *self,
   icon->width = predict_tab_width (self, priv->reordered_tab, FALSE);
   icon->target_width = icon->width;
 
-  icon->tab = adw_tab_new (priv->view, FALSE);
-  adw_tab_set_page (icon->tab, priv->reordered_tab->page);
-  adw_tab_set_dragging (icon->tab, TRUE);
-  adw_tab_set_inverted (icon->tab, priv->inverted);
-  adw_tab_set_display_width (icon->tab, icon->width);
+  icon->tab = g_object_new (ADW_TYPE_TAB,
+                            "view", priv->view,
+                            "pinned", FALSE,
+                            NULL);
+  adw_tab_item_set_page (icon->tab, priv->reordered_tab->page);
+  adw_tab_item_set_dragging (icon->tab, TRUE);
+  adw_tab_item_set_inverted (icon->tab, priv->inverted);
+  adw_tab_item_set_display_width (icon->tab, icon->width);
   gtk_widget_set_halign (GTK_WIDGET (icon->tab), GTK_ALIGN_START);
 
   gtk_drag_icon_set_child (GTK_DRAG_ICON (gtk_drag_icon_get_for_drag (drag)),
@@ -2387,7 +2394,7 @@ icon_resize_animation_value_cb (double   value,
 
   icon->width = (int) round (value);
 
-  adw_tab_set_display_width (icon->tab, icon->width);
+  adw_tab_item_set_display_width (icon->tab, icon->width);
   gtk_widget_set_size_request (GTK_WIDGET (icon->tab),
                                icon->width + icon->tab_margin.left + icon->tab_margin.right,
                                -1);
@@ -2445,7 +2452,7 @@ begin_drag (AdwTabListBase *self,
   GdkContentProvider *content;
   GdkDrag *drag;
   TabInfo *detached_info;
-  AdwTab *detached_tab;
+  AdwTabItem *detached_tab;
 
   native = gtk_widget_get_native (GTK_WIDGET (self));
   surface = gtk_native_get_surface (native);
@@ -2534,8 +2541,8 @@ tab_drag_enter_motion_cb (AdwTabListBase *self,
     priv->indirect_reordering = TRUE;
 
     resize_drag_icon (source_list, predict_tab_width (self, priv->reorder_placeholder, TRUE));
-    adw_tab_set_display_width (priv->reorder_placeholder->tab, source_priv->drag_icon->target_width);
-    adw_tab_set_inverted (source_priv->drag_icon->tab, priv->inverted);
+    adw_tab_item_set_display_width (priv->reorder_placeholder->tab, source_priv->drag_icon->target_width);
+    adw_tab_item_set_inverted (source_priv->drag_icon->tab, priv->inverted);
 
     priv->drag_offset_x = source_priv->drag_icon->hotspot_x;
     priv->drag_offset_y = source_priv->drag_icon->hotspot_y;
@@ -3105,9 +3112,9 @@ adw_tab_list_base_size_allocate (GtkWidget *widget,
     TabInfo *info = l->data;
 
     if (!info->appear_animation)
-      adw_tab_set_display_width (info->tab, info->width);
+      adw_tab_item_set_display_width (info->tab, info->width);
     else if (info->page && info != priv->reorder_placeholder)
-      adw_tab_set_display_width (info->tab, predict_tab_width (self, info, FALSE));
+      adw_tab_item_set_display_width (info->tab, predict_tab_width (self, info, FALSE));
 
     info->pos = pos + calculate_tab_offset (self, info, FALSE);
 
@@ -3726,10 +3733,10 @@ adw_tab_list_base_setup_extra_drop_target (AdwTabListBase *self,
   for (l = priv->tabs; l; l = l->next) {
     TabInfo *info = l->data;
 
-    adw_tab_setup_extra_drop_target (info->tab,
-                                     priv->extra_drag_actions,
-                                     priv->extra_drag_types,
-                                     priv->extra_drag_n_types);
+    adw_tab_item_setup_extra_drop_target (info->tab,
+                                          priv->extra_drag_actions,
+                                          priv->extra_drag_types,
+                                          priv->extra_drag_n_types);
   }
 }
 
@@ -3798,6 +3805,6 @@ adw_tab_list_base_set_inverted (AdwTabListBase *self,
   for (l = priv->tabs; l; l = l->next) {
     TabInfo *info = l->data;
 
-    adw_tab_set_inverted (info->tab, inverted);
+    adw_tab_item_set_inverted (info->tab, inverted);
   }
 }
diff --git a/src/adw-tab-private.h b/src/adw-tab-private.h
index 11668612..cb162b27 100644
--- a/src/adw-tab-private.h
+++ b/src/adw-tab-private.h
@@ -12,40 +12,12 @@
 #error "Only <adwaita.h> can be included directly."
 #endif
 
-#include <gtk/gtk.h>
-#include "adw-tab-view.h"
+#include "adw-tab-item-private.h"
 
 G_BEGIN_DECLS
 
 #define ADW_TYPE_TAB (adw_tab_get_type())
 
-G_DECLARE_FINAL_TYPE (AdwTab, adw_tab, ADW, TAB, GtkWidget)
-
-AdwTab *adw_tab_new (AdwTabView *view,
-                     gboolean    pinned) G_GNUC_WARN_UNUSED_RESULT;
-
-AdwTabPage *adw_tab_get_page (AdwTab     *self);
-void        adw_tab_set_page (AdwTab     *self,
-                              AdwTabPage *page);
-
-int  adw_tab_get_display_width (AdwTab *self);
-void adw_tab_set_display_width (AdwTab *self,
-                                int     width);
-
-gboolean adw_tab_get_dragging (AdwTab   *self);
-void     adw_tab_set_dragging (AdwTab   *self,
-                               gboolean  dragging);
-
-gboolean adw_tab_get_inverted (AdwTab   *self);
-void     adw_tab_set_inverted (AdwTab   *self,
-                               gboolean  inverted);
-
-void adw_tab_set_fully_visible (AdwTab   *self,
-                                gboolean  fully_visible);
-
-void adw_tab_setup_extra_drop_target (AdwTab        *self,
-                                      GdkDragAction  actions,
-                                      GType         *types,
-                                      gsize          n_types);
+G_DECLARE_FINAL_TYPE (AdwTab, adw_tab, ADW, TAB, AdwTabItem)
 
 G_END_DECLS
diff --git a/src/adw-tab.c b/src/adw-tab.c
index 4f323c6c..6d505291 100644
--- a/src/adw-tab.c
+++ b/src/adw-tab.c
@@ -22,7 +22,7 @@
 
 struct _AdwTab
 {
-  GtkWidget parent_instance;
+  AdwTabItem parent_instance;
 
   GtkWidget *title;
   GtkWidget *icon_stack;
@@ -33,19 +33,10 @@ struct _AdwTab
   GtkWidget *close_btn;
   GtkDropTarget *drop_target;
 
-  AdwTabView *view;
-  AdwTabPage *page;
-  gboolean pinned;
-  gboolean dragging;
-  int display_width;
-
-  gboolean hovering;
   gboolean selected;
-  gboolean inverted;
   gboolean title_inverted;
   gboolean close_overlap;
   gboolean show_close;
-  gboolean fully_visible;
 
   AdwAnimation *close_btn_animation;
 
@@ -53,27 +44,7 @@ struct _AdwTab
   gboolean shader_compiled;
 };
 
-G_DEFINE_TYPE (AdwTab, adw_tab, GTK_TYPE_WIDGET)
-
-enum {
-  PROP_0,
-  PROP_VIEW,
-  PROP_PINNED,
-  PROP_DRAGGING,
-  PROP_PAGE,
-  PROP_DISPLAY_WIDTH,
-  PROP_INVERTED,
-  LAST_PROP
-};
-
-static GParamSpec *props[LAST_PROP];
-
-enum {
-  SIGNAL_EXTRA_DRAG_DROP,
-  SIGNAL_LAST_SIGNAL,
-};
-
-static guint signals[SIGNAL_LAST_SIGNAL];
+G_DEFINE_TYPE (AdwTab, adw_tab, ADW_TYPE_TAB_ITEM)
 
 static inline void
 set_style_class (GtkWidget  *widget,
@@ -108,19 +79,22 @@ update_state (AdwTab *self)
 {
   GtkStateFlags new_state;
   gboolean show_close;
+  gboolean dragging = adw_tab_item_get_dragging (ADW_TAB_ITEM (self));
+  gboolean hovering = adw_tab_item_get_hovering (ADW_TAB_ITEM (self));
+  gboolean fully_visible = adw_tab_item_get_fully_visible (ADW_TAB_ITEM (self));
 
   new_state = gtk_widget_get_state_flags (GTK_WIDGET (self)) &
     ~(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_CHECKED);
 
-  if (self->dragging)
+  if (dragging)
     new_state |= GTK_STATE_FLAG_PRELIGHT;
 
-  if (self->selected || self->dragging)
+  if (self->selected || dragging)
     new_state |= GTK_STATE_FLAG_CHECKED;
 
   gtk_widget_set_state_flags (GTK_WIDGET (self), new_state, TRUE);
 
-  show_close = (self->hovering && self->fully_visible) || self->selected || self->dragging;
+  show_close = (hovering && fully_visible) || self->selected || dragging;
 
   if (self->show_close != show_close) {
     double opacity = gtk_widget_get_opacity (self->close_btn);
@@ -150,19 +124,21 @@ update_state (AdwTab *self)
 static void
 update_tooltip (AdwTab *self)
 {
-  const char *tooltip = adw_tab_page_get_tooltip (self->page);
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+  const char *tooltip = adw_tab_page_get_tooltip (page);
 
   if (tooltip && g_strcmp0 (tooltip, "") != 0)
     gtk_widget_set_tooltip_markup (GTK_WIDGET (self), tooltip);
   else
     gtk_widget_set_tooltip_text (GTK_WIDGET (self),
-                                 adw_tab_page_get_title (self->page));
+                                 adw_tab_page_get_title (page));
 }
 
 static void
 update_title (AdwTab *self)
 {
-  const char *title = adw_tab_page_get_title (self->page);
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+  const char *title = adw_tab_page_get_title (page);
   PangoDirection title_direction = PANGO_DIRECTION_NEUTRAL;
   GtkTextDirection direction = gtk_widget_get_direction (GTK_WIDGET (self));
   gboolean title_inverted;
@@ -185,7 +161,8 @@ update_title (AdwTab *self)
 static void
 update_spinner (AdwTab *self)
 {
-  gboolean loading = self->page && adw_tab_page_get_loading (self->page);
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+  gboolean loading = page && adw_tab_page_get_loading (page);
   gboolean mapped = gtk_widget_get_mapped (GTK_WIDGET (self));
 
   /* Don't use CPU when not needed */
@@ -195,18 +172,21 @@ update_spinner (AdwTab *self)
 static void
 update_icons (AdwTab *self)
 {
-  GIcon *gicon = adw_tab_page_get_icon (self->page);
-  gboolean loading = adw_tab_page_get_loading (self->page);
-  GIcon *indicator = adw_tab_page_get_indicator_icon (self->page);
+  AdwTabView *view = adw_tab_item_get_view (ADW_TAB_ITEM (self));
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+  gboolean pinned = adw_tab_item_get_pinned (ADW_TAB_ITEM (self));
+  GIcon *gicon = adw_tab_page_get_icon (page);
+  gboolean loading = adw_tab_page_get_loading (page);
+  GIcon *indicator = adw_tab_page_get_indicator_icon (page);
   const char *name = loading ? "spinner" : "icon";
 
-  if (self->pinned && !gicon)
-    gicon = adw_tab_view_get_default_icon (self->view);
+  if (pinned && !gicon)
+    gicon = adw_tab_view_get_default_icon (view);
 
   gtk_image_set_from_gicon (self->icon, gicon);
   gtk_widget_set_visible (self->icon_stack,
                           (gicon != NULL || loading) &&
-                          (!self->pinned || indicator == NULL));
+                          (!pinned || indicator == NULL));
   gtk_stack_set_visible_child_name (GTK_STACK (self->icon_stack), name);
 
   gtk_widget_set_visible (self->indicator_btn, indicator != NULL);
@@ -215,8 +195,11 @@ update_icons (AdwTab *self)
 static void
 update_indicator (AdwTab *self)
 {
-  gboolean activatable = self->page && adw_tab_page_get_indicator_activatable (self->page);
-  gboolean clickable = activatable && (self->selected || (!self->pinned && self->fully_visible));
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+  gboolean pinned = adw_tab_item_get_pinned (ADW_TAB_ITEM (self));
+  gboolean fully_visible = adw_tab_item_get_fully_visible (ADW_TAB_ITEM (self));
+  gboolean activatable = page && adw_tab_page_get_indicator_activatable (page);
+  gboolean clickable = activatable && (self->selected || (!pinned && fully_visible));
 
   gtk_widget_set_can_target (self->indicator_btn, clickable);
 }
@@ -224,98 +207,61 @@ update_indicator (AdwTab *self)
 static void
 update_needs_attention (AdwTab *self)
 {
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+
   set_style_class (GTK_WIDGET (self), "needs-attention",
-                   adw_tab_page_get_needs_attention (self->page));
+                   adw_tab_page_get_needs_attention (page));
 }
 
 static void
 update_loading (AdwTab *self)
 {
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+
   update_icons (self);
   update_spinner (self);
   set_style_class (GTK_WIDGET (self), "loading",
-                   adw_tab_page_get_loading (self->page));
+                   adw_tab_page_get_loading (page));
 }
 
 static void
 update_selected (AdwTab *self)
 {
-  self->selected = self->dragging;
+  AdwTabPage *page = adw_tab_item_get_page (ADW_TAB_ITEM (self));
+
+  self->selected = adw_tab_item_get_dragging (ADW_TAB_ITEM (self));
 
-  if (self->page)
-    self->selected |= adw_tab_page_get_selected (self->page);
+  if (page)
+    self->selected |= adw_tab_page_get_selected (page);
 
   update_state (self);
   update_indicator (self);
 }
 
-static gboolean
-close_idle_cb (AdwTab *self)
-{
-  adw_tab_view_close_page (self->view, self->page);
-
-  return G_SOURCE_REMOVE;
-}
-
-static void
-close_clicked_cb (AdwTab *self)
-{
-  if (!self->page)
-    return;
-
-  /* When animations are disabled, we don't want to immediately remove the
-   * whole tab mid-click. Instead, defer it until the click has happened.
-   */
-  g_idle_add ((GSourceFunc) close_idle_cb, self);
-}
-
 static void
-indicator_clicked_cb (AdwTab *self)
+notify_dragging_cb (AdwTab *self)
 {
-  if (!self->page)
-    return;
-
-  g_signal_emit_by_name (self->view, "indicator-activated", self->page);
+  update_state (self);
+  update_selected (self);
 }
 
 static void
-motion_cb (AdwTab             *self,
-           double              x,
-           double              y,
-           GtkEventController *controller)
+notify_fully_visible_cb (AdwTab *self)
 {
-  GdkDevice *device = gtk_event_controller_get_current_event_device (controller);
-  GdkInputSource input_source = gdk_device_get_source (device);
-
-  if (input_source == GDK_SOURCE_TOUCHSCREEN)
-    return;
-
-  if (self->hovering)
-    return;
-
-  self->hovering = TRUE;
-
   update_state (self);
+  update_indicator (self);
 }
 
 static void
-leave_cb (AdwTab             *self,
-          GtkEventController *controller)
+close_clicked_cb (AdwTab *self)
 {
-  self->hovering = FALSE;
-
-  update_state (self);
+  adw_tab_item_close (ADW_TAB_ITEM (self));
 }
 
-static gboolean
-drop_cb (AdwTab *self,
-         GValue *value)
+static void
+indicator_clicked_cb (AdwTab *self)
 {
-  gboolean ret = GDK_EVENT_PROPAGATE;
-
-  g_signal_emit (self, signals[SIGNAL_EXTRA_DRAG_DROP], 0, value, &ret);
-
-  return ret;
+  adw_tab_item_activate_indicator (ADW_TAB_ITEM (self));
 }
 
 static void
@@ -343,20 +289,61 @@ ensure_shader (AdwTab *self)
   }
 }
 
-static gboolean
-activate_cb (AdwTab   *self,
-             GVariant *args)
+static void
+adw_tab_connect_page (AdwTabItem *item)
 {
-  GtkWidget *child;
+  AdwTab *self = ADW_TAB (item);
+  AdwTabPage *page = adw_tab_item_get_page (item);
 
-  if (!self->page || !self->view)
-    return GDK_EVENT_PROPAGATE;
+  update_selected (self);
+  update_state (self);
+  update_title (self);
+  update_tooltip (self);
+  update_spinner (self);
+  update_icons (self);
+  update_indicator (self);
+  update_needs_attention (self);
+  update_loading (self);
 
-  child = adw_tab_page_get_child (self->page);
+  g_signal_connect_object (page, "notify::selected",
+                           G_CALLBACK (update_selected), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::title",
+                           G_CALLBACK (update_title), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::tooltip",
+                           G_CALLBACK (update_tooltip), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::icon",
+                           G_CALLBACK (update_icons), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::indicator-icon",
+                           G_CALLBACK (update_icons), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::indicator-activatable",
+                           G_CALLBACK (update_indicator), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::needs-attention",
+                           G_CALLBACK (update_needs_attention), self,
+                           G_CONNECT_SWAPPED);
+  g_signal_connect_object (page, "notify::loading",
+                           G_CALLBACK (update_loading), self,
+                           G_CONNECT_SWAPPED);
+}
 
-  gtk_widget_grab_focus (child);
+static void
+adw_tab_disconnect_page (AdwTabItem *item)
+{
+  AdwTab *self = ADW_TAB (item);
+  AdwTabPage *page = adw_tab_item_get_page (item);
 
-  return GDK_EVENT_STOP;
+  g_signal_handlers_disconnect_by_func (page, update_selected, self);
+  g_signal_handlers_disconnect_by_func (page, update_title, self);
+  g_signal_handlers_disconnect_by_func (page, update_tooltip, self);
+  g_signal_handlers_disconnect_by_func (page, update_icons, self);
+  g_signal_handlers_disconnect_by_func (page, update_indicator, self);
+  g_signal_handlers_disconnect_by_func (page, update_needs_attention, self);
+  g_signal_handlers_disconnect_by_func (page, update_loading, self);
 }
 
 static void
@@ -369,10 +356,11 @@ adw_tab_measure (GtkWidget      *widget,
                  int            *natural_baseline)
 {
   AdwTab *self = ADW_TAB (widget);
+  gboolean pinned = adw_tab_item_get_pinned (ADW_TAB_ITEM (self));
   int min = 0, nat = 0;
 
   if (orientation == GTK_ORIENTATION_HORIZONTAL) {
-    nat = self->pinned ? BASE_WIDTH_PINNED : BASE_WIDTH;
+    nat = pinned ? BASE_WIDTH_PINNED : BASE_WIDTH;
   } else {
     int child_min, child_nat;
 
@@ -445,6 +433,7 @@ allocate_contents (AdwTab        *self,
   int indicator_width, close_width, icon_width, title_width;
   int center_x, center_width = 0;
   int start_width = 0, end_width = 0;
+  gboolean inverted = adw_tab_item_get_inverted (ADW_TAB_ITEM (self));
 
   measure_child (self->icon_stack, alloc->height, &icon_width);
   measure_child (self->title, alloc->height, &title_width);
@@ -452,12 +441,12 @@ allocate_contents (AdwTab        *self,
   measure_child (self->close_btn, alloc->height, &close_width);
 
   if (gtk_widget_get_visible (self->indicator_btn)) {
-    if (self->pinned) {
+    if (adw_tab_item_get_pinned (ADW_TAB_ITEM (self))) {
       /* Center it in a pinned tab */
       allocate_child (self->indicator_btn, alloc,
                       (alloc->width - indicator_width) / 2, indicator_width,
                       baseline);
-    } else if (self->inverted) {
+    } else if (inverted) {
       allocate_child (self->indicator_btn, alloc,
                       alloc->width - indicator_width, indicator_width,
                       baseline);
@@ -471,7 +460,7 @@ allocate_contents (AdwTab        *self,
   }
 
   if (gtk_widget_get_visible (self->close_btn)) {
-    if (self->inverted) {
+    if (inverted) {
       allocate_child (self->close_btn, alloc, 0, close_width, baseline);
 
       start_width = close_width;
@@ -490,7 +479,7 @@ allocate_contents (AdwTab        *self,
                     start_width,
                     alloc->width - center_width - end_width);
 
-  self->close_overlap = !self->inverted &&
+  self->close_overlap = !inverted &&
                         !self->title_inverted &&
                         gtk_widget_get_visible (self->title) &&
                         gtk_widget_get_visible (self->close_btn) &&
@@ -514,6 +503,7 @@ adw_tab_size_allocate (GtkWidget *widget,
                        int        baseline)
 {
   AdwTab *self = ADW_TAB (widget);
+  int display_width = adw_tab_item_get_display_width (ADW_TAB_ITEM (self));
   GtkAllocation child_alloc;
   int allocated_width, width_diff;
 
@@ -524,7 +514,7 @@ adw_tab_size_allocate (GtkWidget *widget,
     return;
 
   allocated_width = gtk_widget_get_allocated_width (widget);
-  width_diff = MAX (0, self->display_width - allocated_width);
+  width_diff = MAX (0, display_width - allocated_width);
 
   child_alloc.x = -width_diff / 2;
   child_alloc.y = 0;
@@ -634,10 +624,11 @@ static void
 adw_tab_constructed (GObject *object)
 {
   AdwTab *self = ADW_TAB (object);
+  AdwTabView *view = adw_tab_item_get_view (ADW_TAB_ITEM (self));
 
   G_OBJECT_CLASS (adw_tab_parent_class)->constructed (object);
 
-  if (self->pinned) {
+  if (adw_tab_item_get_pinned (ADW_TAB_ITEM (self))) {
     gtk_widget_add_css_class (GTK_WIDGET (self), "pinned");
     gtk_widget_hide (self->title);
     gtk_widget_hide (self->close_btn);
@@ -645,94 +636,16 @@ adw_tab_constructed (GObject *object)
     gtk_widget_set_margin_end (self->icon_stack, 0);
   }
 
-  g_signal_connect_object (self->view, "notify::default-icon",
+  g_signal_connect_object (view, "notify::default-icon",
                            G_CALLBACK (update_icons), self,
                            G_CONNECT_SWAPPED);
 }
 
-static void
-adw_tab_get_property (GObject    *object,
-                      guint       prop_id,
-                      GValue     *value,
-                      GParamSpec *pspec)
-{
-  AdwTab *self = ADW_TAB (object);
-
-  switch (prop_id) {
-  case PROP_VIEW:
-    g_value_set_object (value, self->view);
-    break;
-
-  case PROP_PAGE:
-    g_value_set_object (value, adw_tab_get_page (self));
-    break;
-
-  case PROP_PINNED:
-    g_value_set_boolean (value, self->pinned);
-    break;
-
-  case PROP_DRAGGING:
-    g_value_set_boolean (value, adw_tab_get_dragging (self));
-    break;
-
-  case PROP_DISPLAY_WIDTH:
-    g_value_set_int (value, adw_tab_get_display_width (self));
-    break;
-
-  case PROP_INVERTED:
-    g_value_set_boolean (value, adw_tab_get_inverted (self));
-    break;
-
-    default:
-    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-  }
-}
-
-static void
-adw_tab_set_property (GObject      *object,
-                      guint         prop_id,
-                      const GValue *value,
-                      GParamSpec   *pspec)
-{
-  AdwTab *self = ADW_TAB (object);
-
-  switch (prop_id) {
-  case PROP_VIEW:
-    self->view = g_value_get_object (value);
-    break;
-
-  case PROP_PAGE:
-    adw_tab_set_page (self, g_value_get_object (value));
-    break;
-
-  case PROP_PINNED:
-    self->pinned = g_value_get_boolean (value);
-    break;
-
-  case PROP_DRAGGING:
-    adw_tab_set_dragging (self, g_value_get_boolean (value));
-    break;
-
-  case PROP_DISPLAY_WIDTH:
-    adw_tab_set_display_width (self, g_value_get_int (value));
-    break;
-
-  case PROP_INVERTED:
-    adw_tab_set_inverted (self, g_value_get_boolean (value));
-    break;
-
-  default:
-    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
-  }
-}
-
 static void
 adw_tab_dispose (GObject *object)
 {
   AdwTab *self = ADW_TAB (object);
 
-  adw_tab_set_page (self, NULL);
-
   g_clear_object (&self->shader);
   gtk_widget_unparent (self->indicator_btn);
   gtk_widget_unparent (self->icon_stack);
@@ -747,11 +660,10 @@ adw_tab_class_init (AdwTabClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+  AdwTabItemClass *item_class = ADW_TAB_ITEM_CLASS (klass);
 
   object_class->dispose = adw_tab_dispose;
   object_class->constructed = adw_tab_constructed;
-  object_class->get_property = adw_tab_get_property;
-  object_class->set_property = adw_tab_set_property;
 
   widget_class->measure = adw_tab_measure;
   widget_class->size_allocate = adw_tab_size_allocate;
@@ -761,59 +673,8 @@ adw_tab_class_init (AdwTabClass *klass)
   widget_class->direction_changed = adw_tab_direction_changed;
   widget_class->unrealize = adw_tab_unrealize;
 
-  props[PROP_VIEW] =
-    g_param_spec_object ("view",
-                         "View",
-                         "View",
-                         ADW_TYPE_TAB_VIEW,
-                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
-
-  props[PROP_PINNED] =
-    g_param_spec_boolean ("pinned",
-                          "Pinned",
-                          "Pinned",
-                          FALSE,
-                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
-
-  props[PROP_DRAGGING] =
-    g_param_spec_boolean ("dragging",
-                          "Dragging",
-                          "Dragging",
-                          FALSE,
-                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
-  props[PROP_PAGE] =
-    g_param_spec_object ("page",
-                         "Page",
-                         "Page",
-                         ADW_TYPE_TAB_PAGE,
-                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
-  props[PROP_DISPLAY_WIDTH] =
-    g_param_spec_int ("display-width",
-                      "Display Width",
-                      "Display Width",
-                      0, G_MAXINT, 0,
-                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
-  props[PROP_INVERTED] =
-    g_param_spec_boolean ("inverted",
-                          "Inverted",
-                          "Inverted",
-                          FALSE,
-                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
-
-  signals[SIGNAL_EXTRA_DRAG_DROP] =
-    g_signal_new ("extra-drag-drop",
-                  G_TYPE_FROM_CLASS (klass),
-                  G_SIGNAL_RUN_LAST,
-                  0,
-                  g_signal_accumulator_first_wins, NULL, NULL,
-                  G_TYPE_BOOLEAN,
-                  1,
-                  G_TYPE_VALUE);
-
-  g_object_class_install_properties (object_class, LAST_PROP, props);
+  item_class->connect_page = adw_tab_connect_page;
+  item_class->disconnect_page = adw_tab_disconnect_page;
 
   gtk_widget_class_set_template_from_resource (widget_class,
                                                "/org/gnome/Adwaita/ui/adw-tab.ui");
@@ -824,226 +685,19 @@ adw_tab_class_init (AdwTabClass *klass)
   gtk_widget_class_bind_template_child (widget_class, AdwTab, indicator_icon);
   gtk_widget_class_bind_template_child (widget_class, AdwTab, indicator_btn);
   gtk_widget_class_bind_template_child (widget_class, AdwTab, close_btn);
+  gtk_widget_class_bind_template_callback (widget_class, update_state);
+  gtk_widget_class_bind_template_callback (widget_class, notify_dragging_cb);
+  gtk_widget_class_bind_template_callback (widget_class, notify_fully_visible_cb);
   gtk_widget_class_bind_template_callback (widget_class, close_clicked_cb);
   gtk_widget_class_bind_template_callback (widget_class, indicator_clicked_cb);
 
-  gtk_widget_class_add_binding (widget_class, GDK_KEY_space,     0, (GtkShortcutFunc) activate_cb, NULL);
-  gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Space,  0, (GtkShortcutFunc) activate_cb, NULL);
-  gtk_widget_class_add_binding (widget_class, GDK_KEY_Return,    0, (GtkShortcutFunc) activate_cb, NULL);
-  gtk_widget_class_add_binding (widget_class, GDK_KEY_ISO_Enter, 0, (GtkShortcutFunc) activate_cb, NULL);
-  gtk_widget_class_add_binding (widget_class, GDK_KEY_KP_Enter,  0, (GtkShortcutFunc) activate_cb, NULL);
-
   gtk_widget_class_set_css_name (widget_class, "tab");
 }
 
 static void
 adw_tab_init (AdwTab *self)
 {
-  GtkEventController *controller;
-
   g_type_ensure (ADW_TYPE_FADING_LABEL);
 
   gtk_widget_init_template (GTK_WIDGET (self));
-
-  controller = gtk_event_controller_motion_new ();
-  g_signal_connect_swapped (controller, "motion", G_CALLBACK (motion_cb), self);
-  g_signal_connect_swapped (controller, "leave", G_CALLBACK (leave_cb), self);
-  gtk_widget_add_controller (GTK_WIDGET (self), controller);
-}
-
-AdwTab *
-adw_tab_new (AdwTabView *view,
-             gboolean    pinned)
-{
-  g_return_val_if_fail (ADW_IS_TAB_VIEW (view), NULL);
-
-  return g_object_new (ADW_TYPE_TAB,
-                       "view", view,
-                       "pinned", pinned,
-                       NULL);
-}
-
-AdwTabPage *
-adw_tab_get_page (AdwTab *self)
-{
-  g_return_val_if_fail (ADW_IS_TAB (self), NULL);
-
-  return self->page;
-}
-
-void
-adw_tab_set_page (AdwTab     *self,
-                  AdwTabPage *page)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-  g_return_if_fail (page == NULL || ADW_IS_TAB_PAGE (page));
-
-  if (self->page == page)
-    return;
-
-  if (self->page) {
-    g_signal_handlers_disconnect_by_func (self->page, update_selected, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_title, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_tooltip, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_icons, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_indicator, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_needs_attention, self);
-    g_signal_handlers_disconnect_by_func (self->page, update_loading, self);
-  }
-
-  g_set_object (&self->page, page);
-
-  if (self->page) {
-    update_selected (self);
-    update_state (self);
-    update_title (self);
-    update_tooltip (self);
-    update_spinner (self);
-    update_icons (self);
-    update_indicator (self);
-    update_needs_attention (self);
-    update_loading (self);
-
-    g_signal_connect_object (self->page, "notify::selected",
-                             G_CALLBACK (update_selected), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::title",
-                             G_CALLBACK (update_title), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::tooltip",
-                             G_CALLBACK (update_tooltip), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::icon",
-                             G_CALLBACK (update_icons), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::indicator-icon",
-                             G_CALLBACK (update_icons), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::indicator-activatable",
-                             G_CALLBACK (update_indicator), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::needs-attention",
-                             G_CALLBACK (update_needs_attention), self,
-                             G_CONNECT_SWAPPED);
-    g_signal_connect_object (self->page, "notify::loading",
-                             G_CALLBACK (update_loading), self,
-                             G_CONNECT_SWAPPED);
-  }
-
-  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PAGE]);
-}
-
-int
-adw_tab_get_display_width (AdwTab *self)
-{
-  g_return_val_if_fail (ADW_IS_TAB (self), 0);
-
-  return self->display_width;
-}
-
-void
-adw_tab_set_display_width (AdwTab *self,
-                           int     width)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-  g_return_if_fail (width >= 0);
-
-  if (self->display_width == width)
-    return;
-
-  self->display_width = width;
-
-  gtk_widget_queue_resize (GTK_WIDGET (self));
-
-  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DISPLAY_WIDTH]);
-}
-
-gboolean
-adw_tab_get_dragging (AdwTab *self)
-{
-  g_return_val_if_fail (ADW_IS_TAB (self), FALSE);
-
-  return self->dragging;
-}
-
-void
-adw_tab_set_dragging (AdwTab   *self,
-                      gboolean  dragging)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-
-  dragging = !!dragging;
-
-  if (self->dragging == dragging)
-    return;
-
-  self->dragging = dragging;
-
-  update_state (self);
-  update_selected (self);
-
-  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DRAGGING]);
-}
-
-gboolean
-adw_tab_get_inverted (AdwTab *self)
-{
-  g_return_val_if_fail (ADW_IS_TAB (self), FALSE);
-
-  return self->inverted;
-}
-
-void
-adw_tab_set_inverted (AdwTab   *self,
-                      gboolean  inverted)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-
-  inverted = !!inverted;
-
-  if (self->inverted == inverted)
-    return;
-
-  self->inverted = inverted;
-
-  gtk_widget_queue_allocate (GTK_WIDGET (self));
-
-  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_INVERTED]);
-}
-
-void
-adw_tab_set_fully_visible (AdwTab   *self,
-                           gboolean  fully_visible)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-
-  fully_visible = !!fully_visible;
-
-  if (self->fully_visible == fully_visible)
-    return;
-
-  self->fully_visible = fully_visible;
-
-  update_state (self);
-  update_indicator (self);
-}
-
-void
-adw_tab_setup_extra_drop_target (AdwTab        *self,
-                                 GdkDragAction  actions,
-                                 GType         *types,
-                                 gsize          n_types)
-{
-  g_return_if_fail (ADW_IS_TAB (self));
-  g_return_if_fail (n_types == 0 || types != NULL);
-
-  if (!self->drop_target) {
-    self->drop_target = gtk_drop_target_new (G_TYPE_INVALID, actions);
-    g_signal_connect_swapped (self->drop_target, "drop", G_CALLBACK (drop_cb), self);
-    gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (self->drop_target));
-  } else {
-    gtk_drop_target_set_actions (self->drop_target, actions);
-  }
-
-  gtk_drop_target_set_gtypes (self->drop_target, types, n_types);
 }
diff --git a/src/adw-tab.ui b/src/adw-tab.ui
index ef11e4b8..2e45ecd6 100644
--- a/src/adw-tab.ui
+++ b/src/adw-tab.ui
@@ -1,9 +1,12 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface domain="libadwaita">
   <requires lib="gtk" version="4.0"/>
-  <template class="AdwTab" parent="GtkWidget">
+  <template class="AdwTab" parent="AdwTabItem">
     <property name="focusable">True</property>
     <property name="overflow">hidden</property>
+    <signal name="notify::hovering" handler="update_state" swapped="true"/>
+    <signal name="notify::dragging" handler="notify_dragging_cb" swapped="true"/>
+    <signal name="notify::fully-visible" handler="notify_fully_visible_cb" swapped="true"/>
     <child>
       <object class="GtkButton" id="indicator_btn">
         <property name="can-focus">False</property>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]