[libadwaita/wip/exalm/tab-overview: 8/15] Add AdwTabButton
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita/wip/exalm/tab-overview: 8/15] Add AdwTabButton
- Date: Sat, 10 Sep 2022 23:28:26 +0000 (UTC)
commit 4a4b6cdd718e1971ddb11c60b1d821a3d8139c17
Author: Alexander Mikhaylenko <alexm gnome org>
Date: Thu Aug 19 18:42:03 2021 +0500
Add AdwTabButton
doc/images/tab-button-dark.png | Bin 0 -> 831 bytes
doc/images/tab-button.png | Bin 0 -> 841 bytes
doc/libadwaita.toml.in | 2 +
doc/tools/data/tab-button.ui | 151 ++++++
doc/visual-index.md | 7 +-
src/adw-tab-button.c | 524 +++++++++++++++++++++
src/adw-tab-button.h | 36 ++
src/adw-tab-button.ui | 37 ++
src/adw-tab-view.c | 10 +-
src/adwaita.gresources.xml | 3 +
src/adwaita.h | 1 +
.../scalable/status/adw-tab-counter-symbolic.svg | 1 +
.../scalable/status/adw-tab-overflow-symbolic.svg | 1 +
src/meson.build | 2 +
src/stylesheet/widgets/_buttons.scss | 16 +
src/stylesheet/widgets/_linked.scss | 1 +
tests/meson.build | 1 +
tests/test-tab-button.c | 59 +++
18 files changed, 849 insertions(+), 3 deletions(-)
---
diff --git a/doc/images/tab-button-dark.png b/doc/images/tab-button-dark.png
new file mode 100644
index 00000000..d6aed38f
Binary files /dev/null and b/doc/images/tab-button-dark.png differ
diff --git a/doc/images/tab-button.png b/doc/images/tab-button.png
new file mode 100644
index 00000000..85d5ac0b
Binary files /dev/null and b/doc/images/tab-button.png differ
diff --git a/doc/libadwaita.toml.in b/doc/libadwaita.toml.in
index 27de1b24..d75d00fd 100644
--- a/doc/libadwaita.toml.in
+++ b/doc/libadwaita.toml.in
@@ -198,6 +198,8 @@ content_images = [
"images/tab-bar-dark.png",
"images/tab-bar-inline.png",
"images/tab-bar-inline-dark.png",
+ "images/tab-button.png",
+ "images/tab-button-dark.png",
"images/toast-action.png",
"images/toast-action-dark.png",
"images/toast-overlay.png",
diff --git a/doc/tools/data/tab-button.ui b/doc/tools/data/tab-button.ui
new file mode 100644
index 00000000..b20057fb
--- /dev/null
+++ b/doc/tools/data/tab-button.ui
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <requires lib="libadwaita" version="1.0"/>
+ <object class="GtkBox" id="widget">
+ <property name="spacing">6</property>
+ <child>
+ <object class="AdwTabButton">
+ <property name="view">view</property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabButton">
+ <property name="view">view2</property>
+ </object>
+ </child>
+ </object>
+ <object class="AdwTabView" id="view">
+ <property name="vexpand">True</property>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ </object>
+ <object class="AdwTabView" id="view2">
+ <property name="vexpand">True</property>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="needs-attention">True</property>
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="AdwTabPage">
+ <property name="child">
+ <object class="AdwBin"/>
+ </property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/doc/visual-index.md b/doc/visual-index.md
index 97079e66..cd115e6c 100644
--- a/doc/visual-index.md
+++ b/doc/visual-index.md
@@ -133,13 +133,18 @@ Slug: visual-index
<img src="view-switcher-bar.png" alt="view-switcher-bar">
</picture>](class.ViewSwitcherBar.html)
-### Tab Bar
+### Tabs
[<picture>
<source srcset="tab-bar-dark.png" media="(prefers-color-scheme: dark)">
<img src="tab-bar.png" alt="tab-bar">
</picture>](class.TabBar.html)
+[<picture>
+ <source srcset="tab-button-dark.png" media="(prefers-color-scheme: dark)">
+ <img src="tab-button.png" alt="tab-button">
+</picture>](class.TabButton.html)
+
## Adaptive Containers
### Clamp
diff --git a/src/adw-tab-button.c b/src/adw-tab-button.c
new file mode 100644
index 00000000..2024cf89
--- /dev/null
+++ b/src/adw-tab-button.c
@@ -0,0 +1,524 @@
+/*
+ * Copyright (C) 2019 Alexander Mikhaylenko <exalm7659 gmail com>
+ * Copyright (C) 2021-2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include "adw-tab-button.h"
+
+#include "adw-indicator-bin-private.h"
+#include "adw-macros-private.h"
+
+/* Copied from GtkInspector code */
+#define XFT_DPI_MULTIPLIER (96.0 * PANGO_SCALE)
+
+/**
+ * AdwTabButton:
+ *
+ * A button that displays the number of [class@TabView] pages.
+ *
+ * <picture>
+ * <source srcset="tab-button-dark.png" media="(prefers-color-scheme: dark)">
+ * <img src="tab-button.png" alt="tab-button">
+ * </picture>
+ *
+ * `AdwTabButton` is a button that displays the number of pages in a given
+ * `AdwTabView`, as well as whether one of the inactive pages needs attention.
+ *
+ * It's intended to be used as a visible indicator when there's no visible tab
+ * bar, typically opening an [class@TabOverview] on click, e.g. via the
+ * `overview.open` action name:
+ *
+ * ```xml
+ * <object class="AdwTabButton">
+ * <property name="view">view</property>
+ * <property name="action-name">overview.open</property>
+ * </object>
+ * ```
+ *
+ * ## CSS nodes
+ *
+ * `AdwTabButton` has a main CSS node with name `tabbutton`.
+ *
+ * # Accessibility
+ *
+ * `AdwTabButton` uses the `GTK_ACCESSIBLE_ROLE_BUTTON` role.
+ *
+ * Since: 1.3
+ */
+
+struct _AdwTabButton
+{
+ GtkWidget parent_instance;
+
+ GtkWidget *button;
+ GtkLabel *label;
+ GtkImage *icon;
+ AdwIndicatorBin *indicator;
+
+ AdwTabView *view;
+
+ int needs_attention;
+};
+
+static void adw_tab_button_actionable_init (GtkActionableInterface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (AdwTabButton, adw_tab_button, GTK_TYPE_WIDGET,
+ G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, adw_tab_button_actionable_init))
+
+enum {
+ PROP_0,
+ PROP_VIEW,
+
+ /* actionable properties */
+ PROP_ACTION_NAME,
+ PROP_ACTION_TARGET,
+ LAST_PROP = PROP_ACTION_NAME
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+ SIGNAL_CLICKED,
+ SIGNAL_ACTIVATE,
+ SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+static void
+clicked_cb (AdwTabButton *self)
+{
+ g_signal_emit (self, signals[SIGNAL_CLICKED], 0);
+}
+
+static void
+activate_cb (AdwTabButton *self)
+{
+ g_signal_emit_by_name (self->button, "activate");
+}
+
+static void
+update_label_scale (AdwTabButton *self,
+ GtkSettings *settings)
+{
+ int xft_dpi;
+ PangoAttrList *attrs;
+ PangoAttribute *scale_attribute;
+
+ g_object_get (settings, "gtk-xft-dpi", &xft_dpi, NULL);
+
+ attrs = pango_attr_list_new ();
+
+ scale_attribute = pango_attr_scale_new (XFT_DPI_MULTIPLIER / (double) xft_dpi);
+
+ pango_attr_list_change (attrs, scale_attribute);
+
+ gtk_label_set_attributes (self->label, attrs);
+
+ pango_attr_list_unref (attrs);
+}
+
+static void
+xft_dpi_changed (AdwTabButton *self,
+ GParamSpec *pspec,
+ GtkSettings *settings)
+{
+ update_label_scale (self, settings);
+}
+
+static void
+update_icon (AdwTabButton *self)
+{
+ gboolean display_label = FALSE;
+ gboolean small_label = FALSE;
+ const char *icon_name = "adw-tab-counter-symbolic";
+ char *label_text = NULL;
+
+ if (self->view) {
+ guint n_pages = adw_tab_view_get_n_pages (self->view);
+
+ small_label = n_pages >= 10;
+
+ if (n_pages < 100) {
+ display_label = TRUE;
+ label_text = g_strdup_printf ("%u", n_pages);
+ } else {
+ icon_name = "adw-tab-overflow-symbolic";
+ }
+ }
+
+ if (small_label)
+ gtk_widget_add_css_class (GTK_WIDGET (self->label), "small");
+ else
+ gtk_widget_remove_css_class (GTK_WIDGET (self->label), "small");
+
+ gtk_widget_set_visible (GTK_WIDGET (self->label), display_label);
+ gtk_label_set_text (self->label, label_text);
+ gtk_image_set_from_icon_name (self->icon, icon_name);
+
+ g_free (label_text);
+}
+
+static void
+update_needs_attention (AdwTabButton *self)
+{
+ int needs_attention = self->needs_attention;
+
+ if (self->view) {
+ AdwTabPage *selected_page = adw_tab_view_get_selected_page (self->view);
+
+ if (selected_page && adw_tab_page_get_needs_attention (selected_page))
+ needs_attention--;
+ }
+
+ adw_indicator_bin_set_needs_attention (ADW_INDICATOR_BIN (self->indicator),
+ needs_attention > 0);
+}
+
+static void
+notify_needs_attention_cb (AdwTabButton *self,
+ GParamSpec *pspec,
+ AdwTabPage *page)
+{
+ if (adw_tab_page_get_needs_attention (page))
+ self->needs_attention++;
+ else
+ self->needs_attention--;
+
+ update_needs_attention (self);
+}
+
+static void
+page_attached_cb (AdwTabButton *self,
+ AdwTabPage *page)
+{
+ g_signal_connect_object (page, "notify::needs-attention",
+ G_CALLBACK (notify_needs_attention_cb), self,
+ G_CONNECT_SWAPPED);
+
+ if (adw_tab_page_get_needs_attention (page))
+ self->needs_attention++;
+
+ update_needs_attention (self);
+}
+
+static void
+page_detached_cb (AdwTabButton *self,
+ AdwTabPage *page)
+{
+ g_signal_handlers_disconnect_by_func (page, notify_needs_attention_cb, self);
+
+ if (adw_tab_page_get_needs_attention (page))
+ self->needs_attention--;
+
+ update_needs_attention (self);
+}
+
+static void
+adw_tab_button_dispose (GObject *object)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (object);
+
+ adw_tab_button_set_view (self, NULL);
+
+ gtk_widget_unparent (GTK_WIDGET (self->button));
+
+ G_OBJECT_CLASS (adw_tab_button_parent_class)->dispose (object);
+}
+
+static void
+adw_tab_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_VIEW:
+ g_value_set_object (value, adw_tab_button_get_view (self));
+ break;
+ case PROP_ACTION_NAME:
+ g_value_set_string (value, gtk_actionable_get_action_name (GTK_ACTIONABLE (self)));
+ break;
+ case PROP_ACTION_TARGET:
+ g_value_set_variant (value, gtk_actionable_get_action_target_value (GTK_ACTIONABLE (self)));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_tab_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_VIEW:
+ adw_tab_button_set_view (self, g_value_get_object (value));
+ break;
+ case PROP_ACTION_NAME:
+ gtk_actionable_set_action_name (GTK_ACTIONABLE (self), g_value_get_string (value));
+ break;
+ case PROP_ACTION_TARGET:
+ gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self), g_value_get_variant (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+adw_tab_button_class_init (AdwTabButtonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = adw_tab_button_dispose;
+ object_class->get_property = adw_tab_button_get_property;
+ object_class->set_property = adw_tab_button_set_property;
+
+ /**
+ * AdwTabButton:view: (attributes org.gtk.Property.get=adw_tab_button_get_view
org.gtk.Property.set=adw_tab_button_set_view)
+ *
+ * The view the tab button displays.
+ *
+ * Since: 1.3
+ */
+ props[PROP_VIEW] =
+ g_param_spec_object ("view", NULL, NULL,
+ ADW_TYPE_TAB_VIEW,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ g_object_class_install_properties (object_class, LAST_PROP, props);
+
+ g_object_class_override_property (object_class, PROP_ACTION_NAME, "action-name");
+ g_object_class_override_property (object_class, PROP_ACTION_TARGET, "action-target");
+
+ /**
+ * AdwTabButton::clicked:
+ * @self: the object that received the signal
+ *
+ * Emitted when the button has been activated (pressed and released).
+ *
+ * Since: 1.3
+ */
+ signals[SIGNAL_CLICKED] =
+ g_signal_new ("clicked",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * AdwTabButton::activate:
+ * @self: the object which received the signal.
+ *
+ * Emitted to animate press then release.
+ *
+ * This is an action signal. Applications should never connect to this signal,
+ * but use the [signal@TabButton::clicked] signal.
+ *
+ * Since: 1.3
+ */
+ signals[SIGNAL_ACTIVATE] =
+ g_signal_new ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ 0,
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 0);
+
+ gtk_widget_class_set_activate_signal (widget_class, signals[SIGNAL_ACTIVATE]);
+
+ g_signal_override_class_handler ("activate",
+ G_TYPE_FROM_CLASS (klass),
+ G_CALLBACK (activate_cb));
+
+ gtk_widget_class_set_template_from_resource (widget_class,
+ "/org/gnome/Adwaita/ui/adw-tab-button.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, AdwTabButton, button);
+ gtk_widget_class_bind_template_child (widget_class, AdwTabButton, label);
+ gtk_widget_class_bind_template_child (widget_class, AdwTabButton, icon);
+ gtk_widget_class_bind_template_child (widget_class, AdwTabButton, indicator);
+ gtk_widget_class_bind_template_callback (widget_class, clicked_cb);
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
+ gtk_widget_class_set_css_name (widget_class, "tabbutton");
+ gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_BUTTON);
+
+ g_type_ensure (ADW_TYPE_INDICATOR_BIN);
+}
+
+static void
+adw_tab_button_init (AdwTabButton *self)
+{
+ GtkSettings *settings;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ update_icon (self);
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (self));
+
+ update_label_scale (self, settings);
+ g_signal_connect_object (settings, "notify::gtk-xft-dpi",
+ G_CALLBACK (xft_dpi_changed), self,
+ G_CONNECT_SWAPPED);
+}
+
+static const char *
+adw_tab_button_get_action_name (GtkActionable *actionable)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (actionable);
+
+ return gtk_actionable_get_action_name (GTK_ACTIONABLE (self->button));
+}
+
+static void
+adw_tab_button_set_action_name (GtkActionable *actionable,
+ const char *action_name)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (actionable);
+
+ return gtk_actionable_set_action_name (GTK_ACTIONABLE (self->button),
+ action_name);
+}
+
+static GVariant *
+adw_tab_button_get_action_target_value (GtkActionable *actionable)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (actionable);
+
+ return gtk_actionable_get_action_target_value (GTK_ACTIONABLE (self->button));
+}
+
+static void
+adw_tab_button_set_action_target_value (GtkActionable *actionable,
+ GVariant *action_target)
+{
+ AdwTabButton *self = ADW_TAB_BUTTON (actionable);
+
+ return gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self->button),
+ action_target);
+}
+
+static void
+adw_tab_button_actionable_init (GtkActionableInterface *iface)
+{
+ iface->get_action_name = adw_tab_button_get_action_name;
+ iface->set_action_name = adw_tab_button_set_action_name;
+ iface->get_action_target_value = adw_tab_button_get_action_target_value;
+ iface->set_action_target_value = adw_tab_button_set_action_target_value;
+}
+
+/**
+ * adw_tab_button_new:
+ *
+ * Creates a new `AdwTabButton`.
+ *
+ * Returns: the newly created `AdwTabButton`
+ *
+ * Since: 1.3
+ */
+GtkWidget *
+adw_tab_button_new (void)
+{
+ return g_object_new (ADW_TYPE_TAB_BUTTON, NULL);
+}
+
+/**
+ * adw_tab_button_get_view: (attributes org.gtk.Method.get_property=view)
+ * @self: a tab button
+ *
+ * Gets the tab view @self displays.
+ *
+ * Returns: (transfer none) (nullable): the tab view
+ *
+ * Since: 1.3
+ */
+AdwTabView *
+adw_tab_button_get_view (AdwTabButton *self)
+{
+ g_return_val_if_fail (ADW_IS_TAB_BUTTON (self), NULL);
+
+ return self->view;
+}
+
+/**
+ * adw_tab_button_set_view: (attributes org.gtk.Method.set_property=view)
+ * @self: a tab button
+ * @view: (nullable): a tab view
+ *
+ * Sets the tab view to display.
+ *
+ * Since: 1.3
+ */
+void
+adw_tab_button_set_view (AdwTabButton *self,
+ AdwTabView *view)
+{
+ g_return_if_fail (ADW_IS_TAB_BUTTON (self));
+ g_return_if_fail (view == NULL || ADW_IS_TAB_VIEW (view));
+
+ if (self->view == view)
+ return;
+
+ if (self->view) {
+ int i, n;
+
+ g_signal_handlers_disconnect_by_func (self->view, update_icon, self);
+ g_signal_handlers_disconnect_by_func (self->view, update_needs_attention, self);
+ g_signal_handlers_disconnect_by_func (self->view, page_attached_cb, self);
+ g_signal_handlers_disconnect_by_func (self->view, page_detached_cb, self);
+
+ n = adw_tab_view_get_n_pages (self->view);
+
+ for (i = 0; i < n; i++)
+ page_detached_cb (self, adw_tab_view_get_nth_page (self->view, i));
+ }
+
+ g_set_object (&self->view, view);
+
+ if (self->view) {
+ int i, n;
+
+ g_signal_connect_object (self->view, "notify::n-pages",
+ G_CALLBACK (update_icon), self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->view, "notify::selected-page",
+ G_CALLBACK (update_needs_attention), self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->view, "page-attached",
+ G_CALLBACK (page_attached_cb), self,
+ G_CONNECT_SWAPPED);
+ g_signal_connect_object (self->view, "page-detached",
+ G_CALLBACK (page_detached_cb), self,
+ G_CONNECT_SWAPPED);
+
+ n = adw_tab_view_get_n_pages (self->view);
+
+ for (i = 0; i < n; i++)
+ page_attached_cb (self, adw_tab_view_get_nth_page (self->view, i));
+ }
+
+ update_icon (self);
+ update_needs_attention (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VIEW]);
+}
diff --git a/src/adw-tab-button.h b/src/adw-tab-button.h
new file mode 100644
index 00000000..cc467578
--- /dev/null
+++ b/src/adw-tab-button.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * 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 "adw-version.h"
+
+#include <gtk/gtk.h>
+#include "adw-tab-view.h"
+
+G_BEGIN_DECLS
+
+#define ADW_TYPE_TAB_BUTTON (adw_tab_button_get_type())
+
+ADW_AVAILABLE_IN_1_3
+G_DECLARE_FINAL_TYPE (AdwTabButton, adw_tab_button, ADW, TAB_BUTTON, GtkWidget)
+
+ADW_AVAILABLE_IN_1_3
+GtkWidget *adw_tab_button_new (void) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_1_3
+AdwTabView *adw_tab_button_get_view (AdwTabButton *self);
+ADW_AVAILABLE_IN_1_3
+void adw_tab_button_set_view (AdwTabButton *self,
+ AdwTabView *view);
+
+G_END_DECLS
diff --git a/src/adw-tab-button.ui b/src/adw-tab-button.ui
new file mode 100644
index 00000000..04aa227c
--- /dev/null
+++ b/src/adw-tab-button.ui
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <template class="AdwTabButton" parent="GtkWidget">
+ <child>
+ <object class="GtkButton" id="button">
+ <property name="tooltip-text" translatable="yes">View Open Tabs</property>
+ <signal name="clicked" handler="clicked_cb" swapped="yes"/>
+ <property name="child">
+ <object class="AdwIndicatorBin" id="indicator">
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="child">
+ <object class="GtkOverlay">
+ <child>
+ <object class="GtkImage" id="icon"/>
+ </child>
+ <child type="overlay">
+ <object class="GtkLabel" id="label">
+ <property name="halign">center</property>
+ <property name="justify">center</property>
+ <style>
+ <class name="numeric"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </property>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ </template>
+</interface>
diff --git a/src/adw-tab-view.c b/src/adw-tab-view.c
index a785f9d7..8470291d 100644
--- a/src/adw-tab-view.c
+++ b/src/adw-tab-view.c
@@ -25,8 +25,8 @@ static GSList *tab_view_list;
*
* `AdwTabView` is a container which shows one child at a time. While it
* provides keyboard shortcuts for switching between pages, it does not provide
- * a visible tab bar and relies on external widgets for that, such as
- * [class@TabBar].
+ * a visible tab switcher and relies on external widgets for that, such as
+ * [class@TabBar] and [class@TabButton].
*
* `AdwTabView` maintains a [class@TabPage] object for each page, which holds
* additional per-page properties. You can obtain the `AdwTabPage` for a page
@@ -627,6 +627,9 @@ adw_tab_page_class_init (AdwTabPageClass *klass)
* set to `TRUE`. If the tab is not visible, the corresponding edge of the tab
* bar will be highlighted.
*
+ * [class@TabButton] will display a dot if any of the pages that aren't
+ * selected have this property set to `TRUE`.
+ *
* Since: 1.0
*/
page_props[PAGE_PROP_NEEDS_ATTENTION] =
@@ -2334,6 +2337,9 @@ adw_tab_page_get_needs_attention (AdwTabPage *self)
* set to `TRUE`. If the tab is not visible, the corresponding edge of the tab
* bar will be highlighted.
*
+ * [class@TabButton] will display a dot if any of the pages that aren't
+ * selected have [property@TabPage:needs-attention] set to `TRUE`.
+ *
* Since: 1.0
*/
void
diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml
index 58a0d7c4..095d250e 100644
--- a/src/adwaita.gresources.xml
+++ b/src/adwaita.gresources.xml
@@ -8,7 +8,9 @@
<file preprocess="xml-stripblanks">icons/scalable/actions/adw-expander-arrow-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/adw-mail-send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/avatar-default-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/status/adw-tab-counter-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/adw-tab-icon-missing-symbolic.svg</file>
+ <file preprocess="xml-stripblanks">icons/scalable/status/adw-tab-overflow-symbolic.svg</file>
</gresource>
<gresource prefix="/org/gnome/Adwaita/ui">
<file preprocess="xml-stripblanks">adw-about-window.ui</file>
@@ -24,6 +26,7 @@
<file preprocess="xml-stripblanks">adw-status-page.ui</file>
<file preprocess="xml-stripblanks">adw-tab.ui</file>
<file preprocess="xml-stripblanks">adw-tab-bar.ui</file>
+ <file preprocess="xml-stripblanks">adw-tab-button.ui</file>
<file preprocess="xml-stripblanks">adw-toast-widget.ui</file>
<file preprocess="xml-stripblanks">adw-view-switcher-bar.ui</file>
<file preprocess="xml-stripblanks">adw-view-switcher-button.ui</file>
diff --git a/src/adwaita.h b/src/adwaita.h
index 509a9740..6707b57c 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -64,6 +64,7 @@ G_BEGIN_DECLS
#include "adw-swipe-tracker.h"
#include "adw-swipeable.h"
#include "adw-tab-bar.h"
+#include "adw-tab-button.h"
#include "adw-tab-view.h"
#include "adw-timed-animation.h"
#include "adw-toast-overlay.h"
diff --git a/src/icons/scalable/status/adw-tab-counter-symbolic.svg
b/src/icons/scalable/status/adw-tab-counter-symbolic.svg
new file mode 100644
index 00000000..3ad5d18e
--- /dev/null
+++ b/src/icons/scalable/status/adw-tab-counter-symbolic.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path d="M3.006 0c-1.645 0-3 1.355-3 3v10c0
1.645 1.355 3 3 3h10c1.645 0 3-1.355 3-3V3c0-1.645-1.355-3-3-3zm0 2h10c.571 0 1 .429 1 1v10c0 .571-.429 1-1
1h-10c-.571 0-1-.429-1-1V3c0-.571.429-1 1-1z" style="fill:#2e3436;fill-opacity:1"/></svg>
\ No newline at end of file
diff --git a/src/icons/scalable/status/adw-tab-overflow-symbolic.svg
b/src/icons/scalable/status/adw-tab-overflow-symbolic.svg
new file mode 100644
index 00000000..2f89e9ab
--- /dev/null
+++ b/src/icons/scalable/status/adw-tab-overflow-symbolic.svg
@@ -0,0 +1 @@
+<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><path style="fill:#2e3436;fill-opacity:1"
d="M3.006 0c-1.645 0-3 1.355-3 3v10c0 1.645 1.355 3 3 3h10c1.645 0 3-1.355 3-3V3c0-1.645-1.355-3-3-3h-10zm0
2h10c.571 0 1 .429 1 1v10c0 .571-.429 1-1 1h-10c-.571 0-1-.429-1-1V3c0-.571.429-1 1-1zm2.469 3.402c-.349
0-.673.074-.973.22a2.16 2.16 0 0 0-.77.58 2.755 2.755 0 0 0-.507.87A3.33 3.33 0 0 0 3.05 8.16c0
.397.057.765.174 1.104.125.329.295.62.507.87.213.243.47.436.77.58a2.311 2.311 0 0 0
2.191-.158c.407-.25.829-.639 1.264-1.161.426.56.852.963 1.277 1.205.436.242.866.363 1.291.363.349 0
.673-.068.973-.203.3-.145.557-.34.77-.582.212-.252.376-.546.492-.885.126-.339.19-.7.19-1.088
0-.387-.064-.751-.19-1.09a2.545 2.545 0 0 0-.492-.869 2.165 2.165 0 0 0-.77-.582 2.203 2.203 0 0
0-.973-.217c-.416 0-.828.126-1.234.377-.397.242-.813.624-1.248
1.147-.426-.561-.856-.963-1.291-1.205-.426-.242-.852-.364-1.277-.364zm.115 1.743c.193 0
.392.067.596.203.203.125.46.392.77.798a7.62 7.62 0 0 1-.43
6.522 2.505 2.505 0 0 1-.348.32 1.232 1.232 0 0 1-.305.145 1.013 1.013 0 0 1-.29.045.729.729 0 0
1-.58-.278c-.146-.183-.22-.43-.22-.74s.074-.556.22-.74a.74.74 0 0 1 .593-.275zm4.834.044c.232 0
.421.092.566.276.155.184.233.43.233.74s-.078.556-.233.74a.701.701 0 0 1-.58.276c-.193
0-.392-.064-.596-.19-.203-.135-.46-.406-.77-.812.165-.213.31-.386.436-.522a1.86 1.86 0 0 1 .348-.304 1.11
1.11 0 0 1 .291-.16c.097-.03.198-.044.305-.044z"/></svg>
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 54664c1e..dd6536a0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -125,6 +125,7 @@ src_headers = [
'adw-swipe-tracker.h',
'adw-swipeable.h',
'adw-tab-bar.h',
+ 'adw-tab-button.h',
'adw-tab-view.h',
'adw-timed-animation.h',
'adw-toast.h',
@@ -189,6 +190,7 @@ src_sources = [
'adw-swipe-tracker.c',
'adw-swipeable.c',
'adw-tab-bar.c',
+ 'adw-tab-button.c',
'adw-tab-view.c',
'adw-timed-animation.c',
'adw-toast.c',
diff --git a/src/stylesheet/widgets/_buttons.scss b/src/stylesheet/widgets/_buttons.scss
index 8ee170d4..d2ca6e81 100644
--- a/src/stylesheet/widgets/_buttons.scss
+++ b/src/stylesheet/widgets/_buttons.scss
@@ -594,3 +594,19 @@ buttoncontent {
}
}
}
+
+tabbutton {
+ label {
+ font-weight: 800;
+ font-size: 8pt;
+
+ &.small {
+ font-size: 6pt;
+ }
+ }
+
+ indicatorbin > indicator,
+ indicatorbin > mask {
+ transform: translate(-1px, 1px);
+ }
+}
diff --git a/src/stylesheet/widgets/_linked.scss b/src/stylesheet/widgets/_linked.scss
index 1e29c3bc..dba474b8 100644
--- a/src/stylesheet/widgets/_linked.scss
+++ b/src/stylesheet/widgets/_linked.scss
@@ -5,6 +5,7 @@ $_linked_widgets: ("%button", ""),
("dropdown", "> button"),
("colorbutton", "> button"),
("fontbutton", "> button"),
+ ("tabbutton", "> button"),
("combobox", "> box > button.combo"),
("appchooserbutton", "> combobox > box > button.combo"),
("%entry", ""),
diff --git a/tests/meson.build b/tests/meson.build
index 2f17a06c..9bddd07a 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -53,6 +53,7 @@ test_names = [
'test-status-page',
'test-style-manager',
'test-tab-bar',
+ 'test-tab-button',
'test-tab-view',
'test-timed-animation',
'test-toast',
diff --git a/tests/test-tab-button.c b/tests/test-tab-button.c
new file mode 100644
index 00000000..8bdae22d
--- /dev/null
+++ b/tests/test-tab-button.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 Purism SPC
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ *
+ * Author: Alexander Mikhaylenko <alexander mikhaylenko puri sm>
+ */
+
+#include <adwaita.h>
+
+int notified;
+
+static void
+notify_cb (GtkWidget *widget, gpointer data)
+{
+ notified++;
+}
+
+static void
+test_adw_tab_button_view (void)
+{
+ AdwTabButton *button = g_object_ref_sink (ADW_TAB_BUTTON (adw_tab_button_new ()));
+ AdwTabView *view;
+
+ g_assert_nonnull (button);
+
+ notified = 0;
+ g_signal_connect (button, "notify::view", G_CALLBACK (notify_cb), NULL);
+
+ g_object_get (button, "view", &view, NULL);
+ g_assert_null (view);
+
+ adw_tab_button_set_view (button, NULL);
+ g_assert_cmpint (notified, ==, 0);
+
+ view = g_object_ref_sink (ADW_TAB_VIEW (adw_tab_view_new ()));
+ adw_tab_button_set_view (button, view);
+ g_assert_true (adw_tab_button_get_view (button) == view);
+ g_assert_cmpint (notified, ==, 1);
+
+ g_object_set (button, "view", NULL, NULL);
+ g_assert_null (adw_tab_button_get_view (button));
+ g_assert_cmpint (notified, ==, 2);
+
+ g_assert_finalize_object (button);
+ g_assert_finalize_object (view);
+}
+
+int
+main (int argc,
+ char *argv[])
+{
+ gtk_test_init (&argc, &argv, NULL);
+ adw_init ();
+
+ g_test_add_func ("/Adwaita/TabButton/view", test_adw_tab_button_view);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]