[libadwaita] toast: Add custom-title property
- From: Alexander Mikhaylenko <alexm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libadwaita] toast: Add custom-title property
- Date: Mon, 4 Apr 2022 12:25:19 +0000 (UTC)
commit d5e4e55dcf81ca032b09a3deadcd2d64f7801fad
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Sun Feb 27 10:21:04 2022 +0100
toast: Add custom-title property
Allow to change the title's GtkLabel to a custom widget.
Fixes https://gitlab.gnome.org/GNOME/libadwaita/-/issues/339
src/adw-toast-overlay.c | 13 +++--
src/adw-toast-widget.c | 71 ++++++++++++++++++++++--
src/adw-toast-widget.ui | 14 +----
src/adw-toast.c | 112 +++++++++++++++++++++++++++++++++++++-
src/adw-toast.h | 6 ++
src/stylesheet/widgets/_misc.scss | 2 +-
tests/test-toast.c | 62 +++++++++++++++++++++
7 files changed, 255 insertions(+), 25 deletions(-)
---
diff --git a/src/adw-toast-overlay.c b/src/adw-toast-overlay.c
index 544caaa3..fcf11553 100644
--- a/src/adw-toast-overlay.c
+++ b/src/adw-toast-overlay.c
@@ -42,7 +42,9 @@
* toastoverlay
* ├── [child]
* ├── toast
- * ┊ ├── label.heading
+ * ┊ ├── widget
+ * ┊ │ ├── [label.heading]
+ * │ ╰── [custom title]
* ├── [button]
* ╰── button.circular.flat
* ```
@@ -50,9 +52,12 @@
* `AdwToastOverlay`'s CSS node is called `toastoverlay`. It contains the child,
* as well as zero or more `toast` subnodes.
*
- * Each of the `toast` nodes contains a `label` subnode with the `.heading`
- * style class, optionally a `button` subnode, and another `button` subnode with
- * `.circular` and `.flat` style classes.
+ * Each of the `toast` nodes contains a `widget` subnode, optionally a `button`
+ * subnode, and another `button` subnode with `.circular` and `.flat` style
+ * classes.
+ *
+ * The `widget` subnode contains a `label` subnode with the `.heading` style
+ * class, or a custom widget provided by the application.
*
* ## Accessibility
*
diff --git a/src/adw-toast-widget.c b/src/adw-toast-widget.c
index b04f0d59..0b47724a 100644
--- a/src/adw-toast-widget.c
+++ b/src/adw-toast-widget.c
@@ -8,11 +8,14 @@
#include "adw-toast-widget-private.h"
+#include "adw-bin.h"
#include "adw-macros-private.h"
struct _AdwToastWidget {
GtkWidget parent_instance;
+ AdwBin *title_bin;
+
AdwToast *toast;
guint hide_timeout_id;
@@ -105,6 +108,64 @@ action_clicked_cb (AdwToastWidget *self)
g_idle_add (G_SOURCE_FUNC (close_idle_cb), g_object_ref (self));
}
+static void
+update_title_widget (AdwToastWidget *self)
+{
+ GtkWidget *custom_title;
+
+ if (!self->toast) {
+ adw_bin_set_child (self->title_bin, NULL);
+ return;
+ }
+
+ custom_title = adw_toast_get_custom_title (self->toast);
+
+ if (custom_title) {
+ adw_bin_set_child (self->title_bin, custom_title);
+ } else {
+ GtkWidget *title = gtk_label_new (NULL);
+
+ gtk_label_set_ellipsize (GTK_LABEL (title), PANGO_ELLIPSIZE_END);
+ gtk_label_set_xalign (GTK_LABEL (title), 0.0);
+ gtk_label_set_use_markup (GTK_LABEL (title), TRUE);
+ gtk_widget_add_css_class (title, "heading");
+
+ g_object_bind_property (self->toast, "title",
+ title, "label",
+ G_BINDING_SYNC_CREATE);
+
+ adw_bin_set_child (self->title_bin, title);
+ }
+}
+
+static void
+set_toast (AdwToastWidget *self,
+ AdwToast *toast)
+{
+ g_assert (ADW_IS_TOAST_WIDGET (self));
+ g_assert (toast == NULL || ADW_IS_TOAST (toast));
+
+ if (self->toast) {
+ end_timeout (self);
+
+ g_signal_handlers_disconnect_by_func (self->toast,
+ update_title_widget,
+ self);
+ }
+
+ g_set_object (&self->toast, toast);
+ update_title_widget (self);
+
+ if (self->toast) {
+ g_signal_connect_swapped (toast,
+ "notify::custom-title",
+ G_CALLBACK (update_title_widget),
+ self);
+
+ start_timeout (self);
+ }
+}
+
static void
adw_toast_widget_dispose (GObject *object)
{
@@ -113,11 +174,11 @@ adw_toast_widget_dispose (GObject *object)
end_timeout (self);
+ set_toast (self, NULL);
+
while ((child = gtk_widget_get_first_child (GTK_WIDGET (self))))
gtk_widget_unparent (child);
- g_clear_pointer (&self->toast, g_object_unref);
-
G_OBJECT_CLASS (adw_toast_widget_parent_class)->dispose (object);
}
@@ -148,9 +209,7 @@ adw_toast_widget_set_property (GObject *object,
switch (prop_id) {
case PROP_TOAST:
- g_set_object (&self->toast, g_value_get_object (value));
- end_timeout (self);
- start_timeout (self);
+ set_toast (self, g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -179,6 +238,8 @@ adw_toast_widget_class_init (AdwToastWidgetClass *klass)
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/Adwaita/ui/adw-toast-widget.ui");
+ gtk_widget_class_bind_template_child (widget_class, AdwToastWidget, title_bin);
+
gtk_widget_class_bind_template_callback (widget_class, string_is_not_empty);
gtk_widget_class_bind_template_callback (widget_class, action_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, dismiss);
diff --git a/src/adw-toast-widget.ui b/src/adw-toast-widget.ui
index 8a414492..e1d258bf 100644
--- a/src/adw-toast-widget.ui
+++ b/src/adw-toast-widget.ui
@@ -22,19 +22,9 @@
</object>
</child>
<child>
- <object class="GtkLabel" id="title">
- <property name="ellipsize">end</property>
- <property name="xalign">0</property>
+ <object class="AdwBin" id="title_bin">
<property name="hexpand">True</property>
- <property name="use-markup">True</property>
- <binding name="label">
- <lookup name="title" type="AdwToast">
- <lookup name="toast">AdwToastWidget</lookup>
- </lookup>
- </binding>
- <style>
- <class name="heading"/>
- </style>
+ <property name="halign">start</property>
</object>
</child>
<child>
diff --git a/src/adw-toast.c b/src/adw-toast.c
index e0de2d93..12dd67f8 100644
--- a/src/adw-toast.c
+++ b/src/adw-toast.c
@@ -46,6 +46,9 @@
* [property@Toast:priority] determines how it behaves if another toast is
* already being displayed.
*
+ * [property@Toast:custom-title] can be used to replace the title label with a
+ * custom widget.
+ *
* ## Actions
*
* Toasts can have one button on them, with a label and an attached
@@ -152,6 +155,7 @@ struct _AdwToast {
GVariant *action_target;
AdwToastPriority priority;
guint timeout;
+ GtkWidget *custom_title;
gboolean added;
};
@@ -164,6 +168,7 @@ enum {
PROP_ACTION_TARGET,
PROP_PRIORITY,
PROP_TIMEOUT,
+ PROP_CUSTOM_TITLE,
LAST_PROP,
};
@@ -184,6 +189,16 @@ dismissed_cb (AdwToast *self)
self->added = FALSE;
}
+static void
+adw_toast_dispose (GObject *object)
+{
+ AdwToast *self = ADW_TOAST (object);
+
+ g_clear_object (&self->custom_title);
+
+ G_OBJECT_CLASS (adw_toast_parent_class)->dispose (object);
+}
+
static void
adw_toast_finalize (GObject *object)
{
@@ -224,6 +239,9 @@ adw_toast_get_property (GObject *object,
case PROP_TIMEOUT:
g_value_set_uint (value, adw_toast_get_timeout (self));
break;
+ case PROP_CUSTOM_TITLE:
+ g_value_set_object (value, adw_toast_get_custom_title (self));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -256,6 +274,9 @@ adw_toast_set_property (GObject *object,
case PROP_TIMEOUT:
adw_toast_set_timeout (self, g_value_get_uint (value));
break;
+ case PROP_CUSTOM_TITLE:
+ adw_toast_set_custom_title (self, g_value_get_object (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -266,6 +287,7 @@ adw_toast_class_init (AdwToastClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ object_class->dispose = adw_toast_dispose;
object_class->finalize = adw_toast_finalize;
object_class->get_property = adw_toast_get_property;
object_class->set_property = adw_toast_set_property;
@@ -277,6 +299,10 @@ adw_toast_class_init (AdwToastClass *klass)
*
* The title can be marked up with the Pango text markup language.
*
+ * Setting a title will unset [property@Toast:custom-title].
+ *
+ * If [property@Toast:custom-title] is set, it will be used instead.
+ *
* Since: 1.0
*/
props[PROP_TITLE] =
@@ -384,6 +410,25 @@ adw_toast_class_init (AdwToastClass *klass)
0, G_MAXUINT, 5,
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+ /**
+ * AdwToast:custom-title: (attributes org.gtk.Property.get=adw_toast_get_custom_title
org.gtk.Property.set=adw_toast_set_custom_title)
+ *
+ * The custom title widget.
+ *
+ * It will be displayed instead of the title if set. In this case,
+ * [property@Toast:title] is ignored.
+ *
+ * Setting a custom title will unset [property@Toast:title].
+ *
+ * Since: 1.2
+ */
+ props[PROP_CUSTOM_TITLE] =
+ g_param_spec_object ("custom-title",
+ "Custom Title",
+ "The custom title widget",
+ GTK_TYPE_WIDGET,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
g_object_class_install_properties (object_class, LAST_PROP, props);
/**
@@ -408,6 +453,7 @@ adw_toast_init (AdwToast *self)
self->title = g_strdup ("");
self->priority = ADW_TOAST_PRIORITY_NORMAL;
self->timeout = 5;
+ self->custom_title = NULL;
g_signal_connect (self, "dismissed", G_CALLBACK (dismissed_cb), self);
}
@@ -442,7 +488,10 @@ adw_toast_new (const char *title)
*
* Gets the title that will be displayed on the toast.
*
- * Returns: the title
+ * If a custom title has been set with [method@Adw.Toast.set_custom_title]
+ * the return value will be %NULL.
+ *
+ * Returns: (nullable): the title
*
* Since: 1.0
*/
@@ -451,7 +500,10 @@ adw_toast_get_title (AdwToast *self)
{
g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
- return self->title;
+ if (self->custom_title == NULL)
+ return self->title;
+
+ return NULL;
}
/**
@@ -473,10 +525,16 @@ adw_toast_set_title (AdwToast *self,
if (!g_strcmp0 (self->title, title))
return;
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_toast_set_custom_title (self, NULL);
+
g_clear_pointer (&self->title, g_free);
self->title = g_strdup (title);
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
+
+ g_object_thaw_notify (G_OBJECT (self));
}
/**
@@ -801,7 +859,7 @@ adw_toast_dismiss (AdwToast *self)
if (!self->added) {
g_critical ("Trying to dismiss the toast '%s', but it isn't in an "
- "AdwToastOverlay yet", self->title);
+ "AdwToastOverlay yet", adw_toast_get_title (self));
return;
}
@@ -825,3 +883,51 @@ adw_toast_set_added (AdwToast *self,
self->added = !!added;
}
+
+/**
+ * adw_toast_set_custom_title: (attributes org.gtk.Method.set_property=custom-title)
+ * @self: a toast
+ * @widget: (nullable): the custom title widget
+ *
+ * Sets the custom title widget of @self.
+ *
+ * Since: 1.2
+ */
+void
+adw_toast_set_custom_title (AdwToast *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (ADW_IS_TOAST (self));
+ g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
+
+ if (self->custom_title == widget)
+ return;
+
+ g_object_freeze_notify (G_OBJECT (self));
+
+ adw_toast_set_title (self, "");
+
+ g_set_object (&self->custom_title, widget);
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_TITLE]);
+
+ g_object_thaw_notify (G_OBJECT (self));
+}
+
+/**
+ * adw_toast_get_custom_title: (attributes org.gtk.Method.get_property=custom-title)
+ * @self: a toast
+ *
+ * Gets the custom title widget of @self.
+ *
+ * Returns: (nullable) (transfer none): the custom title widget
+ *
+ * Since: 1.2
+ */
+GtkWidget *
+adw_toast_get_custom_title (AdwToast *self)
+{
+ g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
+
+ return self->custom_title;
+}
diff --git a/src/adw-toast.h b/src/adw-toast.h
index a6adfe39..af67851e 100644
--- a/src/adw-toast.h
+++ b/src/adw-toast.h
@@ -73,6 +73,12 @@ ADW_AVAILABLE_IN_ALL
void adw_toast_set_timeout (AdwToast *self,
guint timeout);
+ADW_AVAILABLE_IN_ALL
+GtkWidget *adw_toast_get_custom_title (AdwToast *self);
+ADW_AVAILABLE_IN_ALL
+void adw_toast_set_custom_title (AdwToast *self,
+ GtkWidget *widget);
+
ADW_AVAILABLE_IN_ALL
void adw_toast_dismiss (AdwToast *self);
diff --git a/src/stylesheet/widgets/_misc.scss b/src/stylesheet/widgets/_misc.scss
index ed88a3ef..6c4ff1f7 100644
--- a/src/stylesheet/widgets/_misc.scss
+++ b/src/stylesheet/widgets/_misc.scss
@@ -66,7 +66,7 @@ toast {
&:dir(ltr) { padding-left: 12px; }
&:dir(rtl) { padding-right: 12px; }
- > label {
+ > widget {
margin: 0 6px;
}
diff --git a/tests/test-toast.c b/tests/test-toast.c
index 26bdc59d..216959d3 100644
--- a/tests/test-toast.c
+++ b/tests/test-toast.c
@@ -216,6 +216,66 @@ test_adw_toast_dismiss (void)
g_assert_finalize_object (toast);
}
+static void
+test_adw_toast_custom_title (void)
+{
+ AdwToast *toast = adw_toast_new ("Title");
+ GtkWidget *widget = NULL;
+ char *title;
+
+ g_assert_nonnull (toast);
+
+ notified = 0;
+ g_signal_connect (toast, "notify::custom-title", G_CALLBACK (notify_cb), NULL);
+
+ g_object_get (toast, "title", &title, NULL);
+ g_assert_cmpstr (title, ==, "Title");
+ g_object_get (toast, "custom-title", &widget, NULL);
+ g_assert_null (widget);
+
+ adw_toast_set_title (toast, "Another title");
+ g_assert_cmpint (notified, ==, 0);
+
+ widget = g_object_ref_sink (gtk_label_new ("Custom title"));
+ adw_toast_set_custom_title (toast, widget);
+ g_assert_true (adw_toast_get_custom_title (toast) == widget);
+ g_assert_null (adw_toast_get_title (toast));
+ g_assert_cmpint (notified, ==, 1);
+
+ adw_toast_set_title (toast, "Final title");
+ g_assert_null (adw_toast_get_custom_title (toast));
+ g_assert_cmpstr (adw_toast_get_title (toast), ==, "Final title");
+ g_assert_cmpint (notified, ==, 2);
+
+ g_free (title);
+ g_assert_finalize_object (toast);
+ g_assert_finalize_object (widget);
+}
+
+static void
+test_adw_toast_custom_title_overlay (void)
+{
+ AdwToastOverlay *first_overlay = g_object_ref_sink (ADW_TOAST_OVERLAY (adw_toast_overlay_new ()));
+ AdwToastOverlay *second_overlay = g_object_ref_sink (ADW_TOAST_OVERLAY (adw_toast_overlay_new ()));
+ AdwToast *toast = adw_toast_new ("");
+ GtkWidget *widget = gtk_label_new ("Custom title");
+
+ g_assert_nonnull (first_overlay);
+ g_assert_nonnull (second_overlay);
+ g_assert_nonnull (toast);
+
+ adw_toast_set_custom_title (toast, g_object_ref (widget));
+
+ adw_toast_overlay_add_toast (first_overlay, g_object_ref (toast));
+ adw_toast_dismiss (toast);
+ adw_toast_overlay_add_toast (second_overlay, g_object_ref (toast));
+
+ g_assert_finalize_object (first_overlay);
+ g_assert_finalize_object (second_overlay);
+ g_assert_finalize_object (toast);
+ g_assert_finalize_object (widget);
+}
+
int
main (int argc,
char *argv[])
@@ -231,6 +291,8 @@ main (int argc,
g_test_add_func ("/Adwaita/Toast/priority", test_adw_toast_priority);
g_test_add_func ("/Adwaita/Toast/timeout", test_adw_toast_timeout);
g_test_add_func ("/Adwaita/Toast/dismiss", test_adw_toast_dismiss);
+ g_test_add_func ("/Adwaita/Toast/custom_title", test_adw_toast_custom_title);
+ g_test_add_func ("/Adwaita/Toast/custom_title_overlay", test_adw_toast_custom_title_overlay);
return g_test_run ();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]