[libadwaita] Add AdwToast



commit 2e6d13483f05283afd53f8ac479acf72edd6bacc
Author: Maximiliano Sandoval R <msandova gnome org>
Date:   Sat Nov 6 22:06:31 2021 +0500

    Add AdwToast
    
    Co-authored-by: Alexander Mikhaylenko <alexm gnome org>

 src/adw-enums.c.in      |   1 +
 src/adw-toast-private.h |  23 ++
 src/adw-toast.c         | 725 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/adw-toast.h         |  73 +++++
 src/adwaita.h           |   1 +
 src/meson.build         |   4 +-
 tests/meson.build       |   1 +
 tests/test-toast.c      | 205 ++++++++++++++
 8 files changed, 1032 insertions(+), 1 deletion(-)
---
diff --git a/src/adw-enums.c.in b/src/adw-enums.c.in
index b151b1ed..c36ff689 100644
--- a/src/adw-enums.c.in
+++ b/src/adw-enums.c.in
@@ -9,6 +9,7 @@
 #include "adw-navigation-direction.h"
 #include "adw-squeezer.h"
 #include "adw-style-manager.h"
+#include "adw-toast.h"
 #include "adw-view-switcher.h"
 
 /*** END file-header ***/
diff --git a/src/adw-toast-private.h b/src/adw-toast-private.h
new file mode 100644
index 00000000..b9fa3907
--- /dev/null
+++ b/src/adw-toast-private.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2021 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 "adw-toast.h"
+
+G_BEGIN_DECLS
+
+gboolean adw_toast_get_added (AdwToast *self);
+void     adw_toast_set_added (AdwToast *self,
+                              gboolean  added);
+
+G_END_DECLS
diff --git a/src/adw-toast.c b/src/adw-toast.c
new file mode 100644
index 00000000..f8f3276a
--- /dev/null
+++ b/src/adw-toast.c
@@ -0,0 +1,725 @@
+/*
+ * Copyright (C) 2021 Maximiliano Sandoval <msandova gnome org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "adw-toast-private.h"
+
+/**
+ * AdwToastPriority:
+ * @ADW_TOAST_PRIORITY_NORMAL: the toast will be queued if another toast is
+ *   already displayed.
+ * @ADW_TOAST_PRIORITY_HIGH: the toast will be displayed immediately, pushing
+ *   the previous toast into the queue instead.
+ *
+ * [class@Adw.Toast] behavior when another toast is already displayed.
+ *
+ * Since: 1.0
+ */
+
+/**
+ * AdwToast:
+ *
+ * A helper object for [class@Adw.ToastOverlay].
+ *
+ * Toasts are meant to be passed into [method@Adw.ToastOverlay.add_toast] as
+ * follows:
+ *
+ * ```c
+ * adw_toast_overlay_add_toast (overlay, adw_toast_new (_("Simple Toast"));
+ * ```
+ *
+ * Toasts always have a close button and a timeout. In both cases, they emit the
+ * [signal@Adw.Toast::dismissed] signal when disappearing.
+ *
+ * [property@Adw.Toast:priority] determines how the toast behaves if another
+ * toast is already being displayed.
+ *
+ * ## Actions
+ *
+ * Toasts can have one button on them, with a label and an attached
+ * [iface@Gio.Action].
+ *
+ * ```c
+ * AdwToast *toast = adw_toast_new (_("Toast with Action"));
+ *
+ * adw_toast_set_button_label (toast, _("Example"));
+ * adw_toast_set_action_name (toast, "win.example");
+ *
+ * adw_toast_overlay_add_toast (overlay, toast);
+ * ```
+ *
+ * ## Modifying toasts
+ *
+ * Toasts can be modified after they have been shown. For this, an `AdwToast`
+ * reference must be kept around while the toast is visible.
+ *
+ * A common use case for this is using toasts as undo prompts that stack with
+ * each other, allowing to batch undo the last deleted items:
+ *
+ * ```c
+ *
+ * static void
+ * toast_undo_cb (GtkWidget  *sender,
+ *                const char *action,
+ *                GVariant   *param)
+ * {
+ *   // Undo the deletion
+ * }
+ *
+ * static void
+ * dismissed_cb (MyWindow *self)
+ * {
+ *   self->undo_toast = NULL;
+ *
+ *   // Permanently delete the items
+ * }
+ *
+ * static void
+ * delete_item (MyWindow *self,
+ *              MyItem   *item)
+ * {
+ *   g_autofree char *title = NULL;
+ *   int n_items;
+ *
+ *   // Mark the item as waiting for deletion
+ *   n_items = ... // The number of waiting items
+ *
+ *   if (!self->undo_toast) {
+ *     title = g_strdup_printf (_("ā€˜%sā€™ deleted"), ...);
+ *
+ *     self->undo_toast = adw_toast_new (title);
+ *
+ *     adw_toast_set_priority (self->undo_toast, ADW_TOAST_PRIORITY_HIGH);
+ *     adw_toast_set_button_label (self->undo_toast, _("Undo"));
+ *     adw_toast_set_action_name (self->undo_toast, "toast.undo");
+ *
+ *     g_signal_connect_swapped (self->undo_toast, "dismissed",
+ *                               G_CALLBACK (dismissed_cb), self);
+ *
+ *     adw_toast_overlay_add_toast (self->toast_overlay, self->undo_toast);
+ *
+ *     return;
+ *   }
+ *
+ *   title =
+ *     g_strdup_printf (ngettext ("<span font_features='tnum=1'>%d</span> item deleted",
+ *                                "<span font_features='tnum=1'>%d</span> items deleted",
+ *                                n_items), n_items);
+ *
+ *   adw_toast_set_title (self->undo_toast, title);
+ * }
+ *
+ * static void
+ * my_window_class_init (MyWindowClass *klass)
+ * {
+ *   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ *
+ *   gtk_widget_class_install_action (widget_class, "toast.undo", NULL, toast_undo_cb);
+ * }
+ * ```
+ *
+ * Since: 1.0
+ */
+
+struct _AdwToast {
+  GObject parent_instance;
+
+  char *title;
+  char *button_label;
+  char *action_name;
+  GVariant *action_target;
+  AdwToastPriority priority;
+
+  gboolean added;
+};
+
+enum {
+  PROP_0,
+  PROP_TITLE,
+  PROP_BUTTON_LABEL,
+  PROP_ACTION_NAME,
+  PROP_ACTION_TARGET,
+  PROP_PRIORITY,
+  LAST_PROP,
+};
+
+static GParamSpec *props[LAST_PROP];
+
+enum {
+  SIGNAL_DISMISSED,
+  SIGNAL_LAST_SIGNAL,
+};
+
+static guint signals[SIGNAL_LAST_SIGNAL];
+
+G_DEFINE_TYPE (AdwToast, adw_toast, G_TYPE_OBJECT)
+
+static void
+dismissed_cb (AdwToast *self)
+{
+  self->added = FALSE;
+}
+
+static void
+adw_toast_finalize (GObject *object)
+{
+  AdwToast *self = ADW_TOAST (object);
+
+  g_clear_pointer (&self->title, g_free);
+  g_clear_pointer (&self->button_label, g_free);
+  g_clear_pointer (&self->action_name, g_free);
+  g_clear_pointer (&self->action_target, g_variant_unref);
+
+  G_OBJECT_CLASS (adw_toast_parent_class)->finalize (object);
+}
+
+static void
+adw_toast_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  AdwToast *self = ADW_TOAST (object);
+
+  switch (prop_id) {
+  case PROP_TITLE:
+    g_value_set_string (value, adw_toast_get_title (self));
+    break;
+  case PROP_BUTTON_LABEL:
+    g_value_set_string (value, adw_toast_get_button_label (self));
+    break;
+  case PROP_ACTION_NAME:
+    g_value_set_string (value, adw_toast_get_action_name (self));
+    break;
+  case PROP_ACTION_TARGET:
+    g_value_set_variant (value, adw_toast_get_action_target_value (self));
+    break;
+  case PROP_PRIORITY:
+    g_value_set_enum (value, adw_toast_get_priority (self));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_toast_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  AdwToast *self = ADW_TOAST (object);
+
+  switch (prop_id) {
+  case PROP_TITLE:
+    adw_toast_set_title (self, g_value_get_string (value));
+    break;
+  case PROP_BUTTON_LABEL:
+    adw_toast_set_button_label (self, g_value_get_string (value));
+    break;
+  case PROP_ACTION_NAME:
+    adw_toast_set_action_name (self, g_value_get_string (value));
+    break;
+  case PROP_ACTION_TARGET:
+    adw_toast_set_action_target_value (self, g_value_get_variant (value));
+    break;
+  case PROP_PRIORITY:
+    adw_toast_set_priority (self, g_value_get_enum (value));
+    break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+  }
+}
+
+static void
+adw_toast_class_init (AdwToastClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = adw_toast_finalize;
+  object_class->get_property = adw_toast_get_property;
+  object_class->set_property = adw_toast_set_property;
+
+  /**
+   * AdwToast:title: (attributes org.gtk.Property.get=adw_toast_get_title 
org.gtk.Property.set=adw_toast_set_title)
+   *
+   * The title of the toast.
+   *
+   * The title can be marked up with the Pango text markup language.
+   *
+   * Since: 1.0
+   */
+  props[PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "The title of the toast",
+                         "",
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwToast:button-label: (attributes org.gtk.Property.get=adw_toast_get_button_label 
org.gtk.Property.set=adw_toast_set_button_label)
+   *
+   * The label to show on the button.
+   *
+   * If set to `NULL`, the button won't be shown.
+   *
+   * See [property@Adw.Toast:action-name].
+   *
+   * Since: 1.0
+   */
+  props[PROP_BUTTON_LABEL] =
+    g_param_spec_string ("button-label",
+                         "Button Label",
+                         "The label to show on the button",
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwToast:action-name: (attributes org.gtk.Property.get=adw_toast_get_action_name 
org.gtk.Property.set=adw_toast_set_action_name)
+   *
+   * The name of the associated action.
+   *
+   * It will be activated when clicking the button.
+   *
+   * See [property@Adw.Toast:action-target].
+   *
+   * Since: 1.0
+   */
+  props[PROP_ACTION_NAME] =
+    g_param_spec_string ("action-name",
+                         "Action Name",
+                         "The name of the associated action",
+                         NULL,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwToast:action-target: (attributes org.gtk.Property.get=adw_toast_get_action_target_value 
org.gtk.Property.set=adw_toast_set_action_target_value)
+   *
+   * The parameter for action invocations.
+   *
+   * See [property@Adw.Toast:action-name].
+   *
+   * Since: 1.0
+   */
+  props[PROP_ACTION_TARGET] =
+    g_param_spec_variant ("action-target",
+                          "Action Target Value",
+                          "The parameter for action invocations",
+                          G_VARIANT_TYPE_ANY,
+                          NULL,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  /**
+   * AdwToast:priority: (attributes org.gtk.Property.get=adw_toast_get_priority 
org.gtk.Property.set=adw_toast_set_priority)
+   *
+   * The priority of the toast.
+   *
+   * Priority controls how the toast behaves when another toast is already
+   * being displayed.
+   *
+   * If the priority is `ADW_TOAST_PRIORITY_NORMAL`, the toast will be queued.
+   *
+   * If the priority is `ADW_TOAST_PRIORITY_HIGH`, the toast will be displayed
+   * immediately, pushing the previous toast into the queue instead.
+   *
+   * Since: 1.0
+   */
+  props[PROP_PRIORITY] =
+    g_param_spec_enum ("priority",
+                       "Priority",
+                       "The priority of the toast",
+                       ADW_TYPE_TOAST_PRIORITY,
+                       ADW_TOAST_PRIORITY_NORMAL,
+                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+  g_object_class_install_properties (object_class, LAST_PROP, props);
+
+  /**
+   * AdwToast::dismissed:
+   *
+   * Emitted when the toast has been dismissed.
+   *
+   * Since: 1.0
+   */
+  signals[SIGNAL_DISMISSED] =
+    g_signal_new ("dismissed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_FIRST,
+                  0,
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE, 0);
+}
+
+static void
+adw_toast_init (AdwToast *self)
+{
+  self->title = g_strdup ("");
+  self->priority = ADW_TOAST_PRIORITY_NORMAL;
+
+  g_signal_connect (self, "dismissed", G_CALLBACK (dismissed_cb), self);
+}
+
+/**
+ * adw_toast_new:
+ * @title: the title to be displayed
+ *
+ * Creates a new `AdwToast`.
+ *
+ * The toast will use @title as its title.
+ *
+ * @title can be marked up with the Pango text markup language.
+ *
+ * Returns: the new created `AdwToast`
+ *
+ * Since: 1.0
+ */
+AdwToast *
+adw_toast_new (const char *title)
+{
+  g_return_val_if_fail (title != NULL, NULL);
+
+  return g_object_new (ADW_TYPE_TOAST,
+                       "title", title,
+                       NULL);
+}
+
+/**
+ * adw_toast_get_title: (attributes org.gtk.Method.get_property=title)
+ * @self: a `AdwToast`
+ *
+ * Gets the title that will be displayed on the toast.
+ *
+ * Returns: the title
+ *
+ * Since: 1.0
+ */
+const char *
+adw_toast_get_title (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
+
+  return self->title;
+}
+
+/**
+ * adw_toast_set_title: (attributes org.gtk.Method.set_property=title)
+ * @self: a `AdwToast`
+ * @title: a title
+ *
+ * Sets the title that will be displayed on the toast.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_title (AdwToast   *self,
+                     const char *title)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+  g_return_if_fail (title != NULL);
+
+  if (!g_strcmp0 (self->title, title))
+    return;
+
+  g_clear_pointer (&self->title, g_free);
+  self->title = g_strdup (title);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TITLE]);
+}
+
+/**
+ * adw_toast_get_button_label: (attributes org.gtk.Method.get_property=button-label)
+ * @self: a `AdwToast`
+ *
+ * Gets the label to show on the button.
+ *
+ * Returns: (nullable): the button label
+ *
+ * Since: 1.0
+ */
+const char *
+adw_toast_get_button_label (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
+
+  return self->button_label;
+}
+
+/**
+ * adw_toast_set_button_label: (attributes org.gtk.Method.set_property=button-label)
+ * @self: a `AdwToast`
+ * @button_label: (nullable): a button label
+ *
+ * Sets the label to show on the button.
+ *
+ * It set to `NULL`, the button won't be shown.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_button_label (AdwToast   *self,
+                            const char *button_label)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  if (!g_strcmp0 (self->button_label, button_label))
+    return;
+
+  g_clear_pointer (&self->button_label, g_free);
+  self->button_label = g_strdup (button_label);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_BUTTON_LABEL]);
+}
+
+/**
+ * adw_toast_get_action_name: (attributes org.gtk.Method.get_property=action-name)
+ * @self: a `AdwToast`
+ *
+ * Gets the name of the associated action.
+ *
+ * Returns: (nullable): the action name
+ *
+ * Since: 1.0
+ */
+const char *
+adw_toast_get_action_name (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
+
+  return self->action_name;
+}
+
+/**
+ * adw_toast_set_action_name: (attributes org.gtk.Method.set_property=action-name)
+ * @self: a `AdwToast`
+ * @action_name: (nullable): the action name
+ *
+ * Sets the name of the associated action.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_action_name (AdwToast   *self,
+                           const char *action_name)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  if (!g_strcmp0 (self->action_name, action_name))
+    return;
+
+  g_clear_pointer (&self->action_name, g_free);
+  self->action_name = g_strdup (action_name);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTION_NAME]);
+}
+
+/**
+ * adw_toast_get_action_target_value: (attributes org.gtk.Method.get_property=action-target)
+ * @self: a `AdwToast`
+ *
+ * Gets the parameter for action invocations.
+ *
+ * Returns: (transfer none) (nullable): the action target
+ *
+ * Since: 1.0
+ */
+GVariant *
+adw_toast_get_action_target_value (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), NULL);
+
+  return self->action_target;
+}
+
+/**
+ * adw_toast_set_action_target_value: (attributes org.gtk.Method.get_property=action-target)
+ * @self: a `AdwToast`
+ * @action_target: (nullable): the action target
+ *
+ * Sets the parameter for action invocations.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_action_target_value (AdwToast *self,
+                                   GVariant *action_target)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  if (action_target == self->action_target)
+    return;
+
+  if (action_target && self->action_target &&
+      g_variant_equal (action_target, self->action_target))
+    return;
+
+  g_clear_pointer (&self->action_target, g_variant_unref);
+  self->action_target = g_variant_ref (action_target);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ACTION_TARGET]);
+}
+
+/**
+ * adw_toast_set_action_target: (skip)
+ * @self: a `AdwToast`
+ * @format_string: (nullable): a [struct@GLib.Variant] format string
+ * @...: arguments appropriate for @target_format
+ *
+ * Sets the parameter for action invocations.
+ *
+ * This is a convenience function that calls [ctor GLib Variant new] for
+ * @format_string and uses the result to call
+ * [method@Adw.Toast.set_action_target_value].
+ *
+ * If you are setting a string-valued target and want to set
+ * the action name at the same time, you can use
+ * [method@Adw.Toast.set_detailed_action_name].
+
+ * Since: 1.0
+ */
+void
+adw_toast_set_action_target (AdwToast   *self,
+                             const char *format_string,
+                             ...)
+{
+  va_list args;
+
+  va_start (args, format_string);
+  adw_toast_set_action_target_value (self,
+                                     g_variant_new_va (format_string,
+                                                       NULL, &args));
+  va_end (args);
+}
+
+/**
+ * adw_toast_set_detailed_action_name:
+ * @self: a `AdwToast`
+ * @detailed_action_name: (nullable): the detailed action name
+ *
+ * Sets the action name and its parameter.
+ *
+ * @detailed_action_name is a string in the format accepted by
+ * [func@Gio.Action.parse_detailed_name].
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_detailed_action_name (AdwToast   *self,
+                                    const char *detailed_action_name)
+{
+  g_autofree char *name = NULL;
+  g_autoptr (GVariant) target = NULL;
+  g_autoptr (GError) error = NULL;
+
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  if (!detailed_action_name) {
+    adw_toast_set_action_name (self, NULL);
+    adw_toast_set_action_target_value (self, NULL);
+
+    return;
+  }
+
+  if (!g_action_parse_detailed_name (detailed_action_name, &name, &target, &error)) {
+    g_critical ("Couldn't parse detailed action name: %s", error->message);
+
+    return;
+  }
+
+  adw_toast_set_action_name (self, name);
+  adw_toast_set_action_target_value (self, target);
+}
+
+/**
+ * adw_toast_get_priority: (attributes org.gtk.Method.get_property=priority)
+ * @self: a `AdwToast`
+ *
+ * Gets the toast priority.
+ *
+ * Returns: the priority
+ *
+ * Since: 1.0
+ */
+AdwToastPriority
+adw_toast_get_priority (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), ADW_TOAST_PRIORITY_NORMAL);
+
+  return self->priority;
+}
+
+/**
+ * adw_toast_set_priority: (attributes org.gtk.Method.set_property=priority)
+ * @self: a `AdwToast`
+ * @priority: the priority
+ *
+ * Sets the toast priority.
+ *
+ * Priority controls how the toast behaves when another toast is already
+ * being displayed.
+ *
+ * If @priority is `ADW_TOAST_PRIORITY_NORMAL`, the toast will be queued.
+ *
+ * If @priority is `ADW_TOAST_PRIORITY_HIGH`, the toast will be displayed immediately,
+ * pushing the previous toast into the queue instead.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_set_priority (AdwToast         *self,
+                        AdwToastPriority  priority)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+  g_return_if_fail (priority >= ADW_TOAST_PRIORITY_NORMAL &&
+                    priority <= ADW_TOAST_PRIORITY_HIGH);
+
+  if (self->priority == priority)
+    return;
+
+  self->priority = priority;
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_PRIORITY]);
+}
+
+/**
+ * adw_toast_dismiss:
+ * @self: a `AdwToast`
+ *
+ * Dismisses @self.
+ *
+ * Since: 1.0
+ */
+void
+adw_toast_dismiss (AdwToast *self)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  if (!self->added) {
+    g_critical ("Trying to dismiss the toast '%s', but it isn't in an "
+                "AdwToastOverlay yet", self->title);
+
+    return;
+  }
+
+  g_signal_emit (self, signals[SIGNAL_DISMISSED], 0, NULL);
+}
+
+gboolean
+adw_toast_get_added (AdwToast *self)
+{
+  g_return_val_if_fail (ADW_IS_TOAST (self), FALSE);
+
+  return self->added;
+}
+
+void
+adw_toast_set_added (AdwToast *self,
+                     gboolean  added)
+{
+  g_return_if_fail (ADW_IS_TOAST (self));
+
+  self->added = !!added;
+}
diff --git a/src/adw-toast.h b/src/adw-toast.h
new file mode 100644
index 00000000..4111a088
--- /dev/null
+++ b/src/adw-toast.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 Maximiliano Sandoval <msandova gnome org>
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#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-enums.h"
+
+G_BEGIN_DECLS
+
+typedef enum {
+  ADW_TOAST_PRIORITY_NORMAL,
+  ADW_TOAST_PRIORITY_HIGH,
+} AdwToastPriority;
+
+#define ADW_TYPE_TOAST (adw_toast_get_type())
+
+ADW_AVAILABLE_IN_ALL
+G_DECLARE_FINAL_TYPE (AdwToast, adw_toast, ADW, TOAST, GObject)
+
+ADW_AVAILABLE_IN_ALL
+AdwToast *adw_toast_new (const char *title) G_GNUC_WARN_UNUSED_RESULT;
+
+ADW_AVAILABLE_IN_ALL
+const char *adw_toast_get_title (AdwToast   *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_toast_set_title (AdwToast   *self,
+                                 const char *title);
+
+ADW_AVAILABLE_IN_ALL
+const char *adw_toast_get_button_label (AdwToast   *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_toast_set_button_label (AdwToast   *self,
+                                        const char *button_label);
+
+ADW_AVAILABLE_IN_ALL
+const char *adw_toast_get_action_name (AdwToast *self);
+ADW_AVAILABLE_IN_ALL
+void        adw_toast_set_action_name (AdwToast   *self,
+                                       const char *action_name);
+
+ADW_AVAILABLE_IN_ALL
+GVariant *adw_toast_get_action_target_value  (AdwToast   *self);
+ADW_AVAILABLE_IN_ALL
+void      adw_toast_set_action_target_value  (AdwToast   *self,
+                                              GVariant   *action_target);
+ADW_AVAILABLE_IN_ALL
+void      adw_toast_set_action_target        (AdwToast   *self,
+                                              const char *format_string,
+                                              ...);
+ADW_AVAILABLE_IN_ALL
+void      adw_toast_set_detailed_action_name (AdwToast   *self,
+                                              const char *detailed_action_name);
+
+ADW_AVAILABLE_IN_ALL
+AdwToastPriority adw_toast_get_priority (AdwToast         *self);
+ADW_AVAILABLE_IN_ALL
+void             adw_toast_set_priority (AdwToast         *self,
+                                         AdwToastPriority  priority);
+
+ADW_AVAILABLE_IN_ALL
+void adw_toast_dismiss (AdwToast *self);
+
+G_END_DECLS
diff --git a/src/adwaita.h b/src/adwaita.h
index 0fca7f8d..7f7e9c36 100644
--- a/src/adwaita.h
+++ b/src/adwaita.h
@@ -56,6 +56,7 @@ G_BEGIN_DECLS
 #include "adw-swipeable.h"
 #include "adw-tab-bar.h"
 #include "adw-tab-view.h"
+#include "adw-toast.h"
 #include "adw-view-stack.h"
 #include "adw-view-switcher.h"
 #include "adw-view-switcher-bar.h"
diff --git a/src/meson.build b/src/meson.build
index 6d3b5e7a..48871090 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -17,7 +17,7 @@ adw_public_enum_headers = [
   'adw-navigation-direction.h',
   'adw-style-manager.h',
   'adw-squeezer.h',
-  'adw-tab-bar.h',
+  'adw-toast.h',
   'adw-view-switcher.h',
 ]
 
@@ -105,6 +105,7 @@ src_headers = [
   'adw-swipeable.h',
   'adw-tab-bar.h',
   'adw-tab-view.h',
+  'adw-toast.h',
   'adw-view-stack.h',
   'adw-view-switcher.h',
   'adw-view-switcher-bar.h',
@@ -168,6 +169,7 @@ src_sources = [
   'adw-tab-bar.c',
   'adw-tab-box.c',
   'adw-tab-view.c',
+  'adw-toast.c',
   'adw-version.c',
   'adw-view-stack.c',
   'adw-view-switcher.c',
diff --git a/tests/meson.build b/tests/meson.build
index 83c40b69..2357f801 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -43,6 +43,7 @@ test_names = [
   'test-style-manager',
   'test-tab-bar',
   'test-tab-view',
+  'test-toast',
   'test-view-switcher',
   'test-view-switcher-bar',
   'test-window',
diff --git a/tests/test-toast.c b/tests/test-toast.c
new file mode 100644
index 00000000..0ce6364b
--- /dev/null
+++ b/tests/test-toast.c
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2021 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_toast_title (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  g_autofree char *title = NULL;
+
+  g_assert_nonnull (toast);
+
+  notified = 0;
+  g_signal_connect (toast, "notify::title", G_CALLBACK (notify_cb), NULL);
+
+  g_object_get (toast, "title", &title, NULL);
+  g_assert_cmpstr (title, ==, "Title");
+
+  adw_toast_set_title (toast, "Another title");
+  g_assert_cmpstr (adw_toast_get_title (toast), ==, "Another title");
+  g_assert_cmpint (notified, ==, 1);
+
+  g_object_set (toast, "title", "Title", NULL);
+  g_assert_cmpstr (adw_toast_get_title (toast), ==, "Title");
+  g_assert_cmpint (notified, ==, 2);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_button_label (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  char *button_label;
+
+  g_assert_nonnull (toast);
+
+  notified = 0;
+  g_signal_connect (toast, "notify::button-label", G_CALLBACK (notify_cb), NULL);
+
+  g_object_get (toast, "button-label", &button_label, NULL);
+  g_assert_null (button_label);
+
+  adw_toast_set_button_label (toast, "Button");
+  g_assert_cmpstr (adw_toast_get_button_label (toast), ==, "Button");
+  g_assert_cmpint (notified, ==, 1);
+
+  g_object_set (toast, "button-label", "Button 2", NULL);
+  g_assert_cmpstr (adw_toast_get_button_label (toast), ==, "Button 2");
+  g_assert_cmpint (notified, ==, 2);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_action_name (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  char *action_name;
+
+  g_assert_nonnull (toast);
+
+  notified = 0;
+  g_signal_connect (toast, "notify::action-name", G_CALLBACK (notify_cb), NULL);
+
+  g_object_get (toast, "action-name", &action_name, NULL);
+  g_assert_null (action_name);
+
+  adw_toast_set_action_name (toast, "win.something");
+  g_assert_cmpstr (adw_toast_get_action_name (toast), ==, "win.something");
+  g_assert_cmpint (notified, ==, 1);
+
+  g_object_set (toast, "action-name", "win.something-else", NULL);
+  g_assert_cmpstr (adw_toast_get_action_name (toast), ==, "win.something-else");
+  g_assert_cmpint (notified, ==, 2);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_action_target (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  GVariant *action_target;
+  g_autoptr (GVariant) variant1 = g_variant_ref_sink (g_variant_new_int32 (1));
+  g_autoptr (GVariant) variant2 = g_variant_ref_sink (g_variant_new_int32 (2));
+  g_autoptr (GVariant) variant3 = g_variant_ref_sink (g_variant_new_int32 (3));
+
+  g_assert_nonnull (toast);
+
+  notified = 0;
+  g_signal_connect (toast, "notify::action-target", G_CALLBACK (notify_cb), NULL);
+
+  g_object_get (toast, "action-target", &action_target, NULL);
+  g_assert_null (action_target);
+
+  adw_toast_set_action_target_value (toast, g_variant_new_int32 (1));
+  g_assert_cmpvariant (adw_toast_get_action_target_value (toast), variant1);
+  g_assert_cmpint (notified, ==, 1);
+
+  g_object_set (toast, "action-target", g_variant_new_int32 (2), NULL);
+  g_assert_cmpvariant (adw_toast_get_action_target_value (toast), variant2);
+  g_assert_cmpint (notified, ==, 2);
+
+  adw_toast_set_action_target (toast, "i", 3);
+  g_assert_cmpvariant (adw_toast_get_action_target_value (toast), variant3);
+  g_assert_cmpint (notified, ==, 3);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_detailed_action_name (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  g_autoptr (GVariant) variant = g_variant_ref_sink (g_variant_new_int32 (2));
+
+  g_assert_nonnull (toast);
+
+  g_assert_null (adw_toast_get_action_name (toast));
+  g_assert_null (adw_toast_get_action_target_value (toast));
+
+  adw_toast_set_detailed_action_name (toast, "win.something");
+  g_assert_cmpstr (adw_toast_get_action_name (toast), ==, "win.something");
+  g_assert_null (adw_toast_get_action_target_value (toast));
+
+  adw_toast_set_detailed_action_name (toast, "win.something(2)");
+  g_assert_cmpstr (adw_toast_get_action_name (toast), ==, "win.something");
+  g_assert_cmpvariant (adw_toast_get_action_target_value (toast), variant);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_priority (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  AdwToastPriority priority;
+
+  g_assert_nonnull (toast);
+
+  notified = 0;
+  g_signal_connect (toast, "notify::priority", G_CALLBACK (notify_cb), NULL);
+
+  g_object_get (toast, "priority", &priority, NULL);
+  g_assert_cmpint (priority, ==, ADW_TOAST_PRIORITY_NORMAL);
+
+  adw_toast_set_priority (toast, ADW_TOAST_PRIORITY_HIGH);
+  g_assert_cmpint (adw_toast_get_priority (toast), ==, ADW_TOAST_PRIORITY_HIGH);
+  g_assert_cmpint (notified, ==, 1);
+
+  g_object_set (toast, "priority", ADW_TOAST_PRIORITY_NORMAL, NULL);
+  g_assert_cmpint (adw_toast_get_priority (toast), ==, ADW_TOAST_PRIORITY_NORMAL);
+  g_assert_cmpint (notified, ==, 2);
+
+  g_assert_finalize_object (toast);
+}
+
+static void
+test_adw_toast_dismiss (void)
+{
+  AdwToast *toast = adw_toast_new ("Title");
+  AdwToastOverlay *overlay = g_object_ref_sink (ADW_TOAST_OVERLAY (adw_toast_overlay_new ()));
+
+  g_assert_nonnull (overlay);
+  g_assert_nonnull (toast);
+
+  adw_toast_overlay_add_toast (overlay, g_object_ref (toast));
+  adw_toast_dismiss (toast);
+
+  g_assert_finalize_object (overlay);
+  g_assert_finalize_object (toast);
+}
+
+int
+main (int   argc,
+      char *argv[])
+{
+  gtk_test_init (&argc, &argv, NULL);
+  adw_init ();
+
+  g_test_add_func ("/Adwaita/Toast/title", test_adw_toast_title);
+  g_test_add_func ("/Adwaita/Toast/button_label", test_adw_toast_button_label);
+  g_test_add_func ("/Adwaita/Toast/action_name", test_adw_toast_action_name);
+  g_test_add_func ("/Adwaita/Toast/action_target", test_adw_toast_action_target);
+  g_test_add_func ("/Adwaita/Toast/detailed_action_name", test_adw_toast_detailed_action_name);
+  g_test_add_func ("/Adwaita/Toast/priority", test_adw_toast_priority);
+  g_test_add_func ("/Adwaita/Toast/dismiss", test_adw_toast_dismiss);
+
+  return g_test_run ();
+}


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