diff --git a/demo/adw-demo-window.c b/demo/adw-demo-window.c index 58fe91ea625ad042301c2d49ad36659805661c62..b90a389f9174794ff90a58a5e12f40aa682f9be9 100644 --- a/demo/adw-demo-window.c +++ b/demo/adw-demo-window.c @@ -5,6 +5,7 @@ #include "pages/about/adw-demo-page-about.h" #include "pages/animations/adw-demo-page-animations.h" #include "pages/avatar/adw-demo-page-avatar.h" +#include "pages/banners/adw-demo-page-banners.h" #include "pages/buttons/adw-demo-page-buttons.h" #include "pages/carousel/adw-demo-page-carousel.h" #include "pages/clamp/adw-demo-page-clamp.h" @@ -119,6 +120,7 @@ adw_demo_window_init (AdwDemoWindow *self) g_type_ensure (ADW_TYPE_DEMO_PAGE_ABOUT); g_type_ensure (ADW_TYPE_DEMO_PAGE_ANIMATIONS); + g_type_ensure (ADW_TYPE_DEMO_PAGE_BANNERS); g_type_ensure (ADW_TYPE_DEMO_PAGE_AVATAR); g_type_ensure (ADW_TYPE_DEMO_PAGE_BUTTONS); g_type_ensure (ADW_TYPE_DEMO_PAGE_CAROUSEL); diff --git a/demo/adw-demo-window.ui b/demo/adw-demo-window.ui index 6cd37fc43da8b372c9756972db742b0ce251522d..2b991b1c7471e9722d264ca135771d8b13e1c813 100644 --- a/demo/adw-demo-window.ui +++ b/demo/adw-demo-window.ui @@ -236,6 +236,16 @@ + + + Banner + + + + + + + diff --git a/demo/adwaita-demo.gresources.xml b/demo/adwaita-demo.gresources.xml index f470e937e911eea0553e835544a375c011a39edf..eb839f9b1c45cb6ff205f6317b8036e086c4f2eb 100644 --- a/demo/adwaita-demo.gresources.xml +++ b/demo/adwaita-demo.gresources.xml @@ -26,6 +26,7 @@ icons/scalable/actions/view-sidebar-end-symbolic.svg icons/scalable/actions/view-sidebar-end-symbolic-rtl.svg icons/scalable/actions/widget-about-symbolic.svg + icons/scalable/actions/widget-banner-symbolic.svg icons/scalable/actions/widget-carousel-symbolic.svg icons/scalable/actions/widget-clamp-symbolic.svg icons/scalable/actions/widget-dialog-symbolic.svg @@ -48,6 +49,7 @@ pages/about/adw-demo-page-about.ui pages/animations/adw-demo-page-animations.ui pages/avatar/adw-demo-page-avatar.ui + pages/banners/adw-demo-page-banners.ui pages/buttons/adw-demo-page-buttons.ui pages/carousel/adw-demo-page-carousel.ui pages/clamp/adw-demo-page-clamp.ui diff --git a/demo/icons/scalable/actions/widget-banner-symbolic.svg b/demo/icons/scalable/actions/widget-banner-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..ccf644625267be5ffafed44bd94f86bcbfbf7702 --- /dev/null +++ b/demo/icons/scalable/actions/widget-banner-symbolic.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/demo/meson.build b/demo/meson.build index ee805e78efe973b55453fb87566abcf99bcdbdf6..8127a179117670db15a03287ef28023c1eb89218 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -26,6 +26,7 @@ adwaita_demo_sources = [ 'pages/about/adw-demo-page-about.c', 'pages/animations/adw-demo-page-animations.c', 'pages/avatar/adw-demo-page-avatar.c', + 'pages/banners/adw-demo-page-banners.c', 'pages/buttons/adw-demo-page-buttons.c', 'pages/carousel/adw-demo-page-carousel.c', 'pages/clamp/adw-demo-page-clamp.c', diff --git a/demo/pages/banners/adw-demo-page-banners.c b/demo/pages/banners/adw-demo-page-banners.c new file mode 100644 index 0000000000000000000000000000000000000000..fd1806a0bc10969ce46a21966b8a09a0c2fff874 --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.c @@ -0,0 +1,67 @@ +#include "adw-demo-page-banners.h" + +#include + +struct _AdwDemoPageBanners +{ + AdwBin parent_instance; + + AdwBanner *banner; + AdwEntryRow *button_label_row; +}; + +enum { + SIGNAL_ADD_TOAST, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +G_DEFINE_TYPE (AdwDemoPageBanners, adw_demo_page_banners, ADW_TYPE_BIN) + +static void +toggle_button_cb (AdwDemoPageBanners *self) +{ + if (g_strcmp0 (adw_banner_get_button_label (self->banner), "") == 0) { + adw_banner_set_button_label (self->banner, gtk_editable_get_text (GTK_EDITABLE (self->button_label_row))); + } else { + adw_banner_set_button_label (self->banner, NULL); + } +} + +static void +banner_activate_cb (AdwDemoPageBanners *self) +{ + AdwToast *toast = adw_toast_new (_("Banner action triggered")); + + g_signal_emit (self, signals[SIGNAL_ADD_TOAST], 0, toast); +} + +static void +adw_demo_page_banners_class_init (AdwDemoPageBannersClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + signals[SIGNAL_ADD_TOAST] = + g_signal_new ("add-toast", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + ADW_TYPE_TOAST); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita1/Demo/ui/pages/banners/adw-demo-page-banners.ui"); + gtk_widget_class_bind_template_child (widget_class, AdwDemoPageBanners, banner); + gtk_widget_class_bind_template_child (widget_class, AdwDemoPageBanners, button_label_row); + + gtk_widget_class_install_action (widget_class, "demo.toggle-button", NULL, (GtkWidgetActionActivateFunc) toggle_button_cb); + gtk_widget_class_install_action (widget_class, "demo.activate", NULL, (GtkWidgetActionActivateFunc) banner_activate_cb); +} + +static void +adw_demo_page_banners_init (AdwDemoPageBanners *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/demo/pages/banners/adw-demo-page-banners.h b/demo/pages/banners/adw-demo-page-banners.h new file mode 100644 index 0000000000000000000000000000000000000000..20787d663c45ebed2ffd3c8f9a01e8d4506e7cae --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define ADW_TYPE_DEMO_PAGE_BANNERS (adw_demo_page_banners_get_type()) + +G_DECLARE_FINAL_TYPE (AdwDemoPageBanners, adw_demo_page_banners, ADW, DEMO_PAGE_BANNERS, AdwBin) + +G_END_DECLS diff --git a/demo/pages/banners/adw-demo-page-banners.ui b/demo/pages/banners/adw-demo-page-banners.ui new file mode 100644 index 0000000000000000000000000000000000000000..e66a7ef743ad049d49f46a8eb0888b85beb8c579 --- /dev/null +++ b/demo/pages/banners/adw-demo-page-banners.ui @@ -0,0 +1,71 @@ + + + + + + diff --git a/doc/images/banner-dark.png b/doc/images/banner-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..795f4d63bdc8f5ac9037e24f1c58ded6717a70ca Binary files /dev/null and b/doc/images/banner-dark.png differ diff --git a/doc/images/banner.png b/doc/images/banner.png new file mode 100644 index 0000000000000000000000000000000000000000..75d4770325734e59ca9bbcd69570bca00abed7f1 Binary files /dev/null and b/doc/images/banner.png differ diff --git a/doc/tools/data/banner.ui b/doc/tools/data/banner.ui new file mode 100644 index 0000000000000000000000000000000000000000..bb2f3900bd4e265b2e9f8687198177efd8b3784b --- /dev/null +++ b/doc/tools/data/banner.ui @@ -0,0 +1,25 @@ + + + + + + Banner + 600 + 150 + + + vertical + + + + + + True + Unlock to change settings + Unlock + + + + + + diff --git a/doc/widget-gallery.md b/doc/widget-gallery.md index 88767e7a7f855b3aafa4e1d64c5e595b73a049c9..4145cf7f79652a5f45508e64ce06c82ba3ea24c3 100644 --- a/doc/widget-gallery.md +++ b/doc/widget-gallery.md @@ -19,6 +19,13 @@ Slug: widget-gallery toast-overlay ](class.ToastOverlay.html) +### Banner + +[ + + banner +](class.Banner.html) + ### Avatar [ diff --git a/src/adw-banner.c b/src/adw-banner.c new file mode 100644 index 0000000000000000000000000000000000000000..daeb71f3ca21ef272f24c72076c8b48a51bb14fd --- /dev/null +++ b/src/adw-banner.c @@ -0,0 +1,652 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include "adw-banner.h" +#include "adw-gizmo-private.h" + +#include "adw-macros-private.h" + +#define SPACING 6 +#define LABEL_MAX_WIDTH 500 +#define BUTTON_MAX_WIDTH 160 + +/** + * AdwBanner: + * + * A bar with contextual information. + * + * + * + * banner + * + * + * Banners are hidden by default, use [property@Banner:revealed] to show them. + * + * Banners have a title, set with [property@Banner:title]. Titles can be marked + * up with Pango markup, use [property@Banner:use-markup] to enable it. + * + * Title can be shown centered or left-aligned depending on available space. + * + * Banners can optionally have a button with text on it, set through + * [property@Banner:button-label]. The button can be used with a `GAction`, + * or with the [signal@Banner::button-clicked] signal. + * + * ## CSS nodes + * + * `AdwBanner` has a main CSS node with the name `banner`. + * + * Since: 1.3 + */ + +struct _AdwBanner +{ + GtkWidget parent_instance; + + AdwGizmo *gizmo; + GtkLabel *title; + GtkRevealer *revealer; + GtkButton *button; +}; + +static void adw_banner_actionable_init (GtkActionableInterface *iface); + +G_DEFINE_FINAL_TYPE_WITH_CODE (AdwBanner, adw_banner, GTK_TYPE_WIDGET, + G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE, adw_banner_actionable_init)) + +enum { + PROP_0, + PROP_TITLE, + PROP_BUTTON_LABEL, + PROP_REVEALED, + PROP_USE_MARKUP, + + /* Actionable properties */ + PROP_ACTION_NAME, + PROP_ACTION_TARGET, + LAST_PROP = PROP_ACTION_NAME, +}; + +static GParamSpec *props[LAST_PROP]; + +enum { + SIGNAL_BUTTON_CLICKED, + SIGNAL_LAST_SIGNAL +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +static void +button_clicked (AdwBanner *self) +{ + g_assert (ADW_IS_BANNER (self)); + + g_signal_emit (self, signals[SIGNAL_BUTTON_CLICKED], 0); +} + +static void +adw_banner_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AdwBanner *self = ADW_BANNER (object); + + switch (prop_id) { + case PROP_TITLE: + g_value_set_string (value, adw_banner_get_title (self)); + break; + case PROP_BUTTON_LABEL: + g_value_set_string (value, adw_banner_get_button_label (self)); + break; + case PROP_REVEALED: + g_value_set_boolean (value, adw_banner_get_revealed (self)); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, adw_banner_get_use_markup (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); + break; + } +} + +static void +adw_banner_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AdwBanner *self = ADW_BANNER (object); + + switch (prop_id) { + case PROP_TITLE: + adw_banner_set_title (self, g_value_get_string (value)); + break; + case PROP_BUTTON_LABEL: + adw_banner_set_button_label (self, g_value_get_string (value)); + break; + case PROP_REVEALED: + adw_banner_set_revealed (self, g_value_get_boolean (value)); + break; + case PROP_USE_MARKUP: + adw_banner_set_use_markup (self, g_value_get_boolean (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); + break; + } +} + +static void +adw_banner_dispose (GObject *object) +{ + AdwBanner *self = ADW_BANNER (object); + + gtk_widget_dispose_template (GTK_WIDGET (self), ADW_TYPE_BANNER); + + G_OBJECT_CLASS (adw_banner_parent_class)->dispose (object); +} + +static void +measure_content (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + AdwBanner *self = ADW_BANNER (gtk_widget_get_ancestor (widget, ADW_TYPE_BANNER)); + gboolean button_shown = gtk_widget_is_visible (GTK_WIDGET (self->button)); + int label_min, label_nat; + int button_min, button_nat; + int min = 0, nat = 0; + + gtk_widget_measure (GTK_WIDGET (self->title), orientation, for_size, + &label_min, &label_nat, NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), orientation, for_size, + &button_min, &button_nat, NULL, NULL); + + if (orientation == GTK_ORIENTATION_VERTICAL) { + if (button_shown) { + if (for_size > 0) { + int label_width_nat, button_width_min; + int avail; + + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_HORIZONTAL, -1, + NULL, &label_width_nat, NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_HORIZONTAL, -1, + &button_width_min, NULL, NULL, NULL); + + avail = (for_size - MIN (label_width_nat, for_size)); + + if (avail <= button_width_min + SPACING) { + min = label_min + SPACING + button_min; + nat = label_nat + SPACING + button_nat; + } else { + min = MAX (label_min, button_min); + nat = MAX (label_nat, button_nat); + } + } else { + min = MAX (label_min, button_min); + nat = MAX (label_nat, button_nat); + } + } else { + min = label_min; + nat = label_nat; + } + } else { + if (button_shown) { + min = MAX (label_min + SPACING + button_min, BUTTON_MAX_WIDTH); + nat = MAX (label_nat + SPACING + button_nat, BUTTON_MAX_WIDTH); + } else { + min = label_min; + nat = label_nat; + } + } + + if (minimum) + *minimum = min; + if (natural) + *natural = nat; + if (minimum_baseline) + *minimum_baseline = -1; + if (natural_baseline) + *natural_baseline = -1; +} + +static void +allocate_content (GtkWidget *widget, + int width, + int height, + int baseline) +{ + AdwBanner *self = ADW_BANNER (gtk_widget_get_ancestor (widget, ADW_TYPE_BANNER)); + gboolean button_shown = gtk_widget_is_visible (GTK_WIDGET (self->button)); + int button_width, button_height; + int button_x, button_y; + int label_width_min, label_width, label_height; + int label_x, label_y; + int avail; + gboolean is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; + + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_HORIZONTAL, + -1, &label_width_min, &label_width, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_HORIZONTAL, + -1, &button_width, NULL, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->title), GTK_ORIENTATION_VERTICAL, + width, NULL, &label_height, + NULL, NULL); + gtk_widget_measure (GTK_WIDGET (self->button), GTK_ORIENTATION_VERTICAL, + width, &button_height, NULL, + NULL, NULL); + + label_width = MIN (label_width, width); + label_x = (width / 2) - (label_width / 2); + label_y = (height / 2) - (label_height / 2); + button_x = is_rtl ? 0 : width - button_width; + button_y = (height / 2) - (button_height / 2); + + avail = (width - label_width) / 2; + if (avail <= button_width + SPACING && button_shown) { + label_x = is_rtl ? (width - label_width - SPACING) : SPACING; + + avail = (width - label_width); + if (avail <= button_width + SPACING) { + button_width = CLAMP (button_width, BUTTON_MAX_WIDTH, width); + label_x = (width - label_width) / 2; + label_y = 0; + button_x = (width / 2) - (button_width / 2); + button_y = height - button_height; + } + } + + gtk_widget_allocate (GTK_WIDGET (self->title), + label_width, label_height, -1, + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (label_x, label_y))); + + gtk_widget_allocate (GTK_WIDGET (self->button), + button_width, button_height, -1, + gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (button_x, button_y))); +} + +static GtkSizeRequestMode +get_content_request_mode (GtkWidget *widget) +{ + return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; +} + +static void +adw_banner_class_init (AdwBannerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = adw_banner_get_property; + object_class->set_property = adw_banner_set_property; + object_class->dispose = adw_banner_dispose; + + /** + * AdwBanner:title: (attributes org.gtk.Property.get=adw_banner_get_title org.gtk.Property.set=adw_banner_set_title) + * + * The title for this banner. + * + * See also: [property@Banner:use-markup]. + * + * Since: 1.3 + */ + props[PROP_TITLE] = + g_param_spec_string ("title", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:button-label: (attributes org.gtk.Property.get=adw_banner_get_button_label org.gtk.Property.set=adw_banner_set_button_label) + * + * The label to show on the button. + * + * If set to `""` or `NULL`, the button won't be shown. + * + * The button can be used with a `GAction`, or with the + * [signal@Banner::button-clicked] signal. + * + * Since: 1.3 + */ + props[PROP_BUTTON_LABEL] = + g_param_spec_string ("button-label", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:use-markup: (attributes org.gtk.Property.get=adw_banner_get_use_markup org.gtk.Property.set=adw_banner_set_use_markup) + * + * Whether to use Pango markup for the banner title. + * + * See also [func@Pango.parse_markup]. + * + * Since: 1.3 + */ + props[PROP_USE_MARKUP] = + g_param_spec_boolean ("use-markup", NULL, NULL, + TRUE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner:revealed: (attributes org.gtk.Property.get=adw_banner_get_revealed org.gtk.Property.set=adw_banner_set_revealed) + * + * Whether the banner is currently revealed. + * + * Since: 1.3 + */ + props[PROP_REVEALED] = + g_param_spec_boolean ("revealed", NULL, NULL, + FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwBanner::button-clicked: + * + * This signal is emitted after the action button has been clicked. + * + * It can be used as an alternative to setting an action. + * + * Since: 1.3 + */ + signals[SIGNAL_BUTTON_CLICKED] = + g_signal_new ("button-clicked", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, + 0); + + 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"); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita/ui/adw-banner.ui"); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, gizmo); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, title); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, revealer); + gtk_widget_class_bind_template_child (widget_class, AdwBanner, button); + + gtk_widget_class_bind_template_callback (widget_class, button_clicked); + + gtk_widget_class_set_css_name (widget_class, "banner"); + gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT); + gtk_widget_class_set_accessible_role (widget_class, GTK_ACCESSIBLE_ROLE_GROUP); + + g_type_ensure (ADW_TYPE_GIZMO); +} + +static const char * +adw_banner_get_action_name (GtkActionable *actionable) +{ + AdwBanner *self = ADW_BANNER (actionable); + + return gtk_actionable_get_action_name (GTK_ACTIONABLE (self->button)); +} + +static void +adw_banner_set_action_name (GtkActionable *actionable, + const char *action_name) +{ + AdwBanner *self = ADW_BANNER (actionable); + + gtk_actionable_set_action_name (GTK_ACTIONABLE (self->button), action_name); +} + +static GVariant * +adw_banner_get_action_target_value (GtkActionable *actionable) +{ + AdwBanner *self = ADW_BANNER (actionable); + + return gtk_actionable_get_action_target_value (GTK_ACTIONABLE (self->button)); +} + +static void +adw_banner_set_action_target_value (GtkActionable *actionable, + GVariant *action_target) +{ + AdwBanner *self = ADW_BANNER (actionable); + + gtk_actionable_set_action_target_value (GTK_ACTIONABLE (self->button), action_target); +} + +static void +adw_banner_actionable_init (GtkActionableInterface *iface) +{ + iface->get_action_name = adw_banner_get_action_name; + iface->set_action_name = adw_banner_set_action_name; + iface->get_action_target_value = adw_banner_get_action_target_value; + iface->set_action_target_value = adw_banner_set_action_target_value; +} + +static void +adw_banner_init (AdwBanner *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + gtk_widget_set_layout_manager (GTK_WIDGET (self->gizmo), gtk_custom_layout_new (get_content_request_mode, + measure_content, + allocate_content)); +} + +/** + * adw_banner_new: + * @title: the banner title + * + * Creates a new `AdwBanner`. + * + * Returns: the newly created `AdwBanner` + * + * Since: 1.3 + */ +GtkWidget * +adw_banner_new (const char *title) +{ + g_return_val_if_fail (title != NULL, NULL); + + return g_object_new (ADW_TYPE_BANNER, + "title", title, + NULL); +} + +/** + * adw_banner_get_title: (attributes org.gtk.Method.get_property=title) + * @self: a banner + * + * Gets the title for @self. + * + * Returns: the title for @self + * + * Since: 1.3 + */ +const char * +adw_banner_get_title (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), NULL); + + return gtk_label_get_label (self->title); +} + +/** + * adw_banner_set_title: (attributes org.gtk.Method.set_property=title) + * @self: a banner + * @title: the title + * + * Sets the title for this banner. + * + * See also: [property@Banner:use-markup]. + * + * Since: 1.3 + */ +void +adw_banner_set_title (AdwBanner *self, + const char *title) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + g_return_if_fail (title != NULL); + + if (g_strcmp0 (gtk_label_get_label (self->title), title) == 0) + return; + + gtk_label_set_label (self->title, title); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]); +} + +/** + * adw_banner_get_button_label: (attributes org.gtk.Method.get_property=button-label) + * @self: a banner + * + * Gets the button label for @self. + * + * Returns: (nullable): the button label for @self + * + * Since: 1.3 + */ +const char * +adw_banner_get_button_label (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), NULL); + + return gtk_button_get_label (self->button); +} + +/** + * adw_banner_set_button_label: (attributes org.gtk.Method.set_property=button-label) + * @self: a banner + * @label: (nullable): the label + * + * Sets the button label for @self. + * + * If set to `""` or `NULL`, the button won't be shown. + * + * The button can be used with a `GAction`, or with the + * [signal@Banner::button-clicked] signal. + * + * Since: 1.3 + */ +void +adw_banner_set_button_label (AdwBanner *self, + const char *label) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + if (g_strcmp0 (gtk_button_get_label (self->button), label) == 0) + return; + + gtk_widget_set_visible (GTK_WIDGET (self->button), label && label[0]); + + gtk_button_set_label (self->button, label); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUTTON_LABEL]); +} + +/** + * adw_banner_get_use_markup: (attributes org.gtk.Method.get_property=use-markup) + * @self: a banner + * + * Gets whether to use Pango markup for the banner title. + * + * Returns: whether to use markup + * + * Since: 1.3 + */ +gboolean +adw_banner_get_use_markup (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), FALSE); + + return gtk_label_get_use_markup (self->title); +} + +/** + * adw_banner_set_use_markup: (attributes org.gtk.Method.set_property=use-markup) + * @self: a banner + * @use_markup: whether to use markup + * + * Sets whether to use Pango markup for the banner title. + * + * See also [func@Pango.parse_markup]. + * + * Since: 1.3 + */ +void +adw_banner_set_use_markup (AdwBanner *self, + gboolean use_markup) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + use_markup = !!use_markup; + + if (gtk_label_get_use_markup (self->title) == use_markup) + return; + + gtk_label_set_use_markup (self->title, use_markup); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USE_MARKUP]); +} + +/** + * adw_banner_get_revealed: (attributes org.gtk.Method.get_property=revealed) + * @self: a banner + * + * Gets if a banner is revealed + * + * Returns: Whether a banner is revealed + * + * Since: 1.3 + */ +gboolean +adw_banner_get_revealed (AdwBanner *self) +{ + g_return_val_if_fail (ADW_IS_BANNER (self), FALSE); + + return gtk_revealer_get_reveal_child (GTK_REVEALER (self->revealer)); +} + +/** + * adw_banner_set_revealed: (attributes org.gtk.Method.get_property=revealed) + * @self: a banner + * @revealed: whether a banner should be revealed + * + * Sets whether a banner should be revealed + * + * Since: 1.3 + */ +void +adw_banner_set_revealed (AdwBanner *self, + gboolean revealed) +{ + g_return_if_fail (ADW_IS_BANNER (self)); + + revealed = !!revealed; + + if (gtk_revealer_get_reveal_child (GTK_REVEALER (self->revealer)) == revealed) + return; + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), revealed); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REVEALED]); +} diff --git a/src/adw-banner.h b/src/adw-banner.h new file mode 100644 index 0000000000000000000000000000000000000000..57525a2cdc6398aa88ae3789945f2b0e57fd4f14 --- /dev/null +++ b/src/adw-banner.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-version.h" + +#include +#include "adw-enums.h" + +G_BEGIN_DECLS + +#define ADW_TYPE_BANNER (adw_banner_get_type()) + +ADW_AVAILABLE_IN_1_3 +G_DECLARE_FINAL_TYPE (AdwBanner, adw_banner, ADW, BANNER, GtkWidget) + +ADW_AVAILABLE_IN_1_3 +GtkWidget *adw_banner_new (const char *title) G_GNUC_WARN_UNUSED_RESULT; + +ADW_AVAILABLE_IN_1_3 +const char *adw_banner_get_title (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_title (AdwBanner *self, + const char *title); + +ADW_AVAILABLE_IN_1_3 +const char *adw_banner_get_button_label (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_button_label (AdwBanner *self, + const char *label); + +ADW_AVAILABLE_IN_1_3 +gboolean adw_banner_get_revealed (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_revealed (AdwBanner *self, + gboolean revealed); + +ADW_AVAILABLE_IN_1_3 +gboolean adw_banner_get_use_markup (AdwBanner *self); +ADW_AVAILABLE_IN_1_3 +void adw_banner_set_use_markup (AdwBanner *self, + gboolean use_markup); + +G_END_DECLS diff --git a/src/adw-banner.ui b/src/adw-banner.ui new file mode 100644 index 0000000000000000000000000000000000000000..04e7c615a3ec0c4e3948cb91692eed9ff94dcd3b --- /dev/null +++ b/src/adw-banner.ui @@ -0,0 +1,42 @@ + + + + + diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml index 159fc0e7238dcf6bfd832d1748f8e3e90e5bf37d..4169e278f1ec2cbca2481046ebcabeb50ff109a5 100644 --- a/src/adwaita.gresources.xml +++ b/src/adwaita.gresources.xml @@ -16,6 +16,7 @@ adw-about-window.ui adw-action-row.ui + adw-banner.ui adw-combo-row.ui adw-entry-row.ui adw-expander-row.ui diff --git a/src/adwaita.h b/src/adwaita.h index b70b2f29da51f7b4013f6ff3c39b1c99ba51e153..4524b8b274f399df6dd399286679416bd19b6bc1 100644 --- a/src/adwaita.h +++ b/src/adwaita.h @@ -29,6 +29,7 @@ G_BEGIN_DECLS #include "adw-application.h" #include "adw-application-window.h" #include "adw-avatar.h" +#include "adw-banner.h" #include "adw-bin.h" #include "adw-button-content.h" #include "adw-carousel.h" diff --git a/src/meson.build b/src/meson.build index 4701a2cd40d74e1b2100f1a1623bf40945c32f55..1c8e7234494f321f1abe7c9502b4bdde4c9ea31b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -11,6 +11,7 @@ libadwaita_resources = gnome.compile_resources( adw_public_enum_headers = [ 'adw-animation.h', + 'adw-banner.h', 'adw-flap.h', 'adw-fold-threshold-policy.h', 'adw-easing.h', @@ -90,6 +91,7 @@ src_headers = [ 'adw-application.h', 'adw-application-window.h', 'adw-avatar.h', + 'adw-banner.h', 'adw-bin.h', 'adw-button-content.h', 'adw-carousel.h', @@ -156,6 +158,7 @@ src_sources = [ 'adw-application.c', 'adw-application-window.c', 'adw-avatar.c', + 'adw-banner.c', 'adw-bin.c', 'adw-button-content.c', 'adw-carousel.c', diff --git a/src/stylesheet/widgets/_misc.scss b/src/stylesheet/widgets/_misc.scss index d3c30ee22b8515b81a4469e956ba3524b14568af..8d4465f91ae71aa49aaf4c2ce6e26e1bc31bb4ce 100644 --- a/src/stylesheet/widgets/_misc.scss +++ b/src/stylesheet/widgets/_misc.scss @@ -1,3 +1,15 @@ +/*********** + * Banners * + ***********/ +banner { + background-color: gtkmix($accent_bg_color, $window_bg_color, 30%); + color: $window_fg_color; + + > revealer > widget { + padding: 6px; + } +} + /********** * Frames * **********/ diff --git a/tests/meson.build b/tests/meson.build index 393719ee91e6865d2a497b02f9a781d05ccc406f..f1dbfe9b9a143254539d471c0651d5ec09c73d53 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -32,6 +32,7 @@ test_names = [ 'test-animation-target', 'test-application-window', 'test-avatar', + 'test-banner', 'test-bin', 'test-button-content', 'test-carousel', diff --git a/tests/test-banner.c b/tests/test-banner.c new file mode 100644 index 0000000000000000000000000000000000000000..53676c847fdfb219e12ef5113800481b33fd5da6 --- /dev/null +++ b/tests/test-banner.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2022 Jamie Murphy + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include + +static void +test_adw_banner_revealed (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + + g_assert_nonnull (banner); + + g_assert_false (adw_banner_get_revealed (banner)); + + adw_banner_set_revealed (banner, TRUE); + g_assert_true (adw_banner_get_revealed (banner)); + + adw_banner_set_revealed (banner, FALSE); + g_assert_false (adw_banner_get_revealed (banner)); + + g_assert_finalize_object (banner); +} + +static void +test_adw_banner_title (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + + g_assert_nonnull (banner); + + g_assert_cmpstr (adw_banner_get_title (banner), ==, ""); + + adw_banner_set_title (banner, "Dummy title"); + g_assert_cmpstr (adw_banner_get_title (banner), ==, "Dummy title"); + + adw_banner_set_use_markup (banner, FALSE); + adw_banner_set_title (banner, "Invalid markup"); + g_assert_cmpstr (adw_banner_get_title (banner), ==, "Invalid markup"); + + g_assert_finalize_object (banner); +} + +static void +test_adw_banner_button_label (void) +{ + AdwBanner *banner = g_object_ref_sink (ADW_BANNER (adw_banner_new (""))); + char *button_label; + + g_assert_nonnull (banner); + + g_object_get (banner, "button-label", &button_label, NULL); + g_assert_null (button_label); + + adw_banner_set_button_label (banner, "Dummy label"); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, "Dummy label"); + + adw_banner_set_button_label (banner, NULL); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, ""); + + g_object_set (banner, "button-label", "Button 2", NULL); + g_assert_cmpstr (adw_banner_get_button_label (banner), ==, "Button 2"); + + g_assert_finalize_object (banner); +} + +int +main (int argc, + char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + adw_init (); + + g_test_add_func ("/Adwaita/Banner/revealed", test_adw_banner_revealed); + g_test_add_func ("/Adwaita/Banner/title", test_adw_banner_title); + g_test_add_func ("/Adwaita/Banner/button_label", test_adw_banner_button_label); + + return g_test_run (); +}