[libhandy] avatar: Add loading from GLoadableIcon



commit f60a2b8cd2f4ed6272fbd6df46acabf0c7ba899f
Author: Julian Sparber <julian sparber net>
Date:   Fri Oct 30 16:28:20 2020 +0100

    avatar: Add loading from GLoadableIcon

 debian/libhandy-1-0.symbols |   2 +
 src/hdy-avatar.c            | 537 +++++++++++++++++++++++++++++++++++++++-----
 src/hdy-avatar.h            |   5 +
 3 files changed, 494 insertions(+), 50 deletions(-)
---
diff --git a/debian/libhandy-1-0.symbols b/debian/libhandy-1-0.symbols
index cab1fa7e..c4f095ad 100644
--- a/debian/libhandy-1-0.symbols
+++ b/debian/libhandy-1-0.symbols
@@ -23,6 +23,7 @@ libhandy-1.so.0 libhandy-1-0 #MINVER#
  hdy_avatar_get_show_initials@LIBHANDY_1_0 0.80.0
  hdy_avatar_get_size@LIBHANDY_1_0 0.80.0
  hdy_avatar_get_text@LIBHANDY_1_0 0.80.0
+ hdy_avatar_get_loadable_icon@LIBHANDY_1_0 1.1.0
  hdy_avatar_get_type@LIBHANDY_1_0 0.80.0
  hdy_avatar_new@LIBHANDY_1_0 0.80.0
  hdy_avatar_set_icon_name@LIBHANDY_1_0 0.85.0
@@ -30,6 +31,7 @@ libhandy-1.so.0 libhandy-1-0 #MINVER#
  hdy_avatar_set_show_initials@LIBHANDY_1_0 0.80.0
  hdy_avatar_set_size@LIBHANDY_1_0 0.80.0
  hdy_avatar_set_text@LIBHANDY_1_0 0.80.0
+ hdy_avatar_set_loadable_icon@LIBHANDY_1_0 1.1.0
  hdy_carousel_get_allow_long_swipes@LIBHANDY_1_0 1.1.0
  hdy_carousel_get_allow_mouse_drag@LIBHANDY_1_0 0.80.0
  hdy_carousel_get_animation_duration@LIBHANDY_1_0 0.80.0
diff --git a/src/hdy-avatar.c b/src/hdy-avatar.c
index 08a66efb..75166278 100644
--- a/src/hdy-avatar.c
+++ b/src/hdy-avatar.c
@@ -14,10 +14,12 @@
 #include <math.h>
 
 #include "hdy-avatar.h"
+#include "hdy-avatar-icon-private.h"
 #include "hdy-cairo-private.h"
 #include "hdy-css-private.h"
 
 #define NUMBER_OF_COLORS 14
+#define LOAD_BUFFER_SIZE 65536
 /**
  * SECTION:hdy-avatar
  * @short_description: A widget displaying an image, with a generated fallback.
@@ -75,12 +77,13 @@ struct _HdyAvatar
   gboolean show_initials;
   guint color_class;
   gint size;
-  cairo_surface_t *round_image;
-  gint round_image_size;
+  GdkPixbuf *round_image;
 
-  HdyAvatarImageLoadFunc load_image_func;
-  gpointer load_image_func_target;
-  GDestroyNotify load_image_func_target_destroy_notify;
+  HdyAvatarIcon *load_func_icon;
+  GLoadableIcon *icon;
+  GCancellable *cancellable;
+  guint currently_loading_size;
+  gboolean loading_error;
 };
 
 G_DEFINE_TYPE (HdyAvatar, hdy_avatar, GTK_TYPE_DRAWING_AREA);
@@ -91,13 +94,36 @@ enum {
   PROP_TEXT,
   PROP_SHOW_INITIALS,
   PROP_SIZE,
+  PROP_LOADABLE_ICON,
   PROP_LAST_PROP,
 };
 static GParamSpec *props[PROP_LAST_PROP];
 
-static cairo_surface_t *
-round_image (GdkPixbuf *pixbuf,
-             gdouble    size)
+static void
+load_icon_async (GLoadableIcon       *icon,
+                 gint                 size,
+                 GCancellable        *cancellable,
+                 GAsyncReadyCallback  callback,
+                 gpointer             user_data);
+
+static inline GLoadableIcon *
+get_icon (HdyAvatar *self)
+{
+  if (self->icon)
+    return self->icon;
+
+  return G_LOADABLE_ICON (self->load_func_icon);
+}
+
+static inline gboolean
+is_scaled (GdkPixbuf *pixbuf)
+{
+  return (pixbuf && g_object_get_data (G_OBJECT (pixbuf), "scaled") != NULL);
+}
+
+static GdkPixbuf *
+make_round_image (GdkPixbuf *pixbuf,
+                  gdouble    size)
 {
   g_autoptr (cairo_surface_t) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
   g_autoptr (cairo_t) cr = cairo_create (surface);
@@ -112,7 +138,7 @@ round_image (GdkPixbuf *pixbuf,
   gdk_cairo_set_source_pixbuf (cr, pixbuf, (size - width) / 2, (size - height) / 2);
   cairo_paint (cr);
 
-  return g_steal_pointer (&surface);
+  return gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
 }
 
 static gchar *
@@ -143,36 +169,280 @@ extract_initials_from_text (const gchar *text)
   return g_string_free (initials, FALSE);
 }
 
+static GdkPixbuf *
+update_custom_image (GLoadableIcon *icon,
+                     GdkPixbuf     *pixbuf_from_icon,
+                     GdkPixbuf     *round_image,
+                     gint           new_size)
+{
+  if (icon == NULL)
+    return NULL;
+
+  if (round_image &&
+      gdk_pixbuf_get_width (round_image) == new_size &&
+      !is_scaled (round_image))
+    return g_object_ref (round_image);
+
+  if (pixbuf_from_icon) {
+    gint pixbuf_from_icon_size = MIN (gdk_pixbuf_get_width (pixbuf_from_icon),
+                                      gdk_pixbuf_get_height (pixbuf_from_icon));
+    if (pixbuf_from_icon_size == new_size)
+      return make_round_image (pixbuf_from_icon, new_size);
+  }
+
+  if (round_image) {
+    /* Use a scaled image till we get the new image from async loading */
+    GdkPixbuf *pixbuf = gdk_pixbuf_scale_simple (round_image,
+                                                 new_size,
+                                                 new_size,
+                                                 GDK_INTERP_BILINEAR);
+    g_object_set_data (G_OBJECT (pixbuf), "scaled", GINT_TO_POINTER (TRUE));
+
+    return pixbuf;
+  }
+
+  return NULL;
+}
+
+static void
+size_prepared_cb (GdkPixbufLoader *loader,
+                  gint             width,
+                  gint             height,
+                  gpointer         user_data)
+{
+  gint size = GPOINTER_TO_INT (user_data);
+  gdouble ratio = (gdouble) width / (gdouble) height;
+
+  if (width < height) {
+    width = size;
+    height = size / ratio;
+  } else {
+    width = size * ratio;
+    height = size;
+  }
+
+  gdk_pixbuf_loader_set_size (loader, width, height);
+}
+
+/* This function is copied from the gdk-pixbuf project,
+ * from the file gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-io.c.
+ * It was modified to fit libhandy's code style.
+ */
+static void
+load_from_stream_async_cb (GObject      *stream,
+                           GAsyncResult *res,
+                           gpointer      data)
+{
+  g_autoptr (GTask) task = data;
+  GdkPixbufLoader *loader = g_task_get_task_data (task);
+  g_autoptr (GBytes) bytes = NULL;
+  GError *error = NULL;
+
+  bytes = g_input_stream_read_bytes_finish (G_INPUT_STREAM (stream), res, &error);
+  if (bytes == NULL) {
+    gdk_pixbuf_loader_close (loader, NULL);
+    g_task_return_error (task, error);
+
+    return;
+  }
+
+  if (g_bytes_get_size (bytes) == 0) {
+    if (!gdk_pixbuf_loader_close (loader, &error)) {
+      g_task_return_error (task, error);
+
+      return;
+    }
+
+    g_task_return_pointer (task,
+                           g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)),
+                           g_object_unref);
+
+    return;
+  }
+
+  if (!gdk_pixbuf_loader_write (loader,
+                                g_bytes_get_data (bytes, NULL),
+                                g_bytes_get_size (bytes),
+                                &error)) {
+    gdk_pixbuf_loader_close (loader, NULL);
+    g_task_return_error (task, error);
+
+    return;
+  }
+
+  g_input_stream_read_bytes_async (G_INPUT_STREAM (stream),
+                                   LOAD_BUFFER_SIZE,
+                                   G_PRIORITY_DEFAULT,
+                                   g_task_get_cancellable (task),
+                                   load_from_stream_async_cb,
+                                   g_object_ref (task));
+}
+
 static void
-update_custom_image (HdyAvatar *self,
-                     gint       width,
-                     gint       height,
-                     gint       scale_factor)
+icon_load_async_cb (GLoadableIcon *icon,
+                    GAsyncResult  *res,
+                    GTask         *task)
 {
+  g_autoptr (GInputStream) stream = NULL;
+  g_autoptr (GError) error = NULL;
+
+  stream = g_loadable_icon_load_finish (icon, res, NULL, &error);
+  if (stream == NULL) {
+    g_task_return_error (task, g_steal_pointer (&error));
+
+    return;
+  }
+
+  g_input_stream_read_bytes_async (stream,
+                                   LOAD_BUFFER_SIZE,
+                                   G_PRIORITY_DEFAULT,
+                                   g_task_get_cancellable (task),
+                                   load_from_stream_async_cb,
+                                   task);
+}
+
+static GdkPixbuf *
+load_from_gicon_async_finish (GAsyncResult  *async_result,
+                              GError       **error)
+{
+  GTask *task = G_TASK (async_result);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+static void
+load_from_gicon_async_for_display_cb (GLoadableIcon *icon,
+                                      GAsyncResult  *res,
+                                      gpointer      *user_data)
+{
+  HdyAvatar *self = HDY_AVATAR (user_data);
+  g_autoptr (GError) error = NULL;
   g_autoptr (GdkPixbuf) pixbuf = NULL;
-  gint new_size;
-  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
 
-  new_size = MIN (width, height) * scale_factor;
+  pixbuf = load_from_gicon_async_finish (res, &error);
 
-  if (self->round_image_size != new_size && self->round_image != NULL) {
-    g_clear_pointer (&self->round_image, cairo_surface_destroy);
-    self->round_image_size = -1;
+  if (error != NULL) {
+    if (g_error_matches (error, HDY_AVATAR_ICON_ERROR, HDY_AVATAR_ICON_ERROR_EMPTY)) {
+      self->loading_error = TRUE;
+    } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+      g_warning ("Failed to load icon: %s", error->message);
+      self->loading_error = TRUE;
+    }
   }
 
-  if (self->load_image_func != NULL && self->round_image == NULL) {
-    pixbuf = self->load_image_func (new_size, self->load_image_func_target);
-    if (pixbuf != NULL) {
-      self->round_image = round_image (pixbuf, (gdouble) new_size);
-      cairo_surface_set_device_scale (self->round_image, scale_factor, scale_factor);
-      self->round_image_size = new_size;
+  self->currently_loading_size = -1;
+
+  if (pixbuf) {
+    g_autoptr (GdkPixbuf) custom_image = NULL;
+    GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+    gint width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
+    gint height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
+    gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+    gint new_size = MIN (width, height) * scale_factor;
+
+    custom_image = update_custom_image (icon,
+                                        pixbuf,
+                                        NULL,
+                                        new_size);
+
+    if (!self->round_image && custom_image)
+      gtk_style_context_add_class (context, "image");
+
+    g_set_object (&self->round_image, custom_image);
+    gtk_widget_queue_draw (GTK_WIDGET (self));
+  }
+}
+
+static void
+load_icon_async (GLoadableIcon       *icon,
+                 gint                 size,
+                 GCancellable        *cancellable,
+                 GAsyncReadyCallback  callback,
+                 gpointer             user_data)
+{
+  GTask *task = g_task_new (icon, cancellable, callback, user_data);
+  GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
+
+  g_signal_connect (loader, "size-prepared",
+                    G_CALLBACK (size_prepared_cb),
+                    GINT_TO_POINTER (size));
+
+  g_task_set_task_data (task, loader, g_object_unref);
+
+  g_loadable_icon_load_async (icon, size, cancellable,
+                              (GAsyncReadyCallback) icon_load_async_cb,
+                              task);
+}
+
+/* This function is copied from the gdk-pixbuf project,
+ * from the file gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-io.c.
+ * It was modified to fit libhandy's code style.
+ */
+static GdkPixbuf *
+load_from_stream (GdkPixbufLoader  *loader,
+                  GInputStream     *stream,
+                  GCancellable     *cancellable,
+                  GError          **error)
+{
+  GdkPixbuf *pixbuf;
+  guchar buffer[LOAD_BUFFER_SIZE];
+
+  while (TRUE) {
+    gssize n_read = g_input_stream_read (stream, buffer, sizeof (buffer),
+                                         cancellable, error);
+
+    if (n_read < 0) {
+      gdk_pixbuf_loader_close (loader, NULL);
+
+      return NULL;
+    }
+
+    if (n_read == 0)
+      break;
+
+    if (!gdk_pixbuf_loader_write (loader, buffer, n_read, error)) {
+      gdk_pixbuf_loader_close (loader, NULL);
+
+      return NULL;
     }
   }
 
-  if (self->round_image)
-    gtk_style_context_add_class (context, "image");
-  else
-    gtk_style_context_remove_class (context, "image");
+  if (!gdk_pixbuf_loader_close (loader, error))
+    return NULL;
+
+  pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
+  if (pixbuf == NULL)
+    return NULL;
+
+  return g_object_ref (pixbuf);
+}
+
+static GdkPixbuf *
+load_icon_sync (GLoadableIcon *icon,
+                gint           size)
+{
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GInputStream) stream = g_loadable_icon_load (icon, size, NULL, NULL, &error);
+  g_autoptr (GdkPixbufLoader) loader = gdk_pixbuf_loader_new ();
+  g_autoptr (GdkPixbuf) pixbuf = NULL;
+
+  if (error) {
+    g_warning ("Failed to load icon: %s", error->message);
+    return NULL;
+  }
+
+  g_signal_connect (loader, "size-prepared",
+                    G_CALLBACK (size_prepared_cb),
+                    GINT_TO_POINTER (size));
+
+  pixbuf = load_from_stream (loader, stream, NULL, &error);
+
+  if (error) {
+    g_warning ("Failed to load pixbuf from GLoadableIcon: %s", error->message);
+    return NULL;
+  }
+
+  return g_steal_pointer (&pixbuf);
 }
 
 static void
@@ -291,6 +561,10 @@ hdy_avatar_get_property (GObject    *object,
     g_value_set_int (value, hdy_avatar_get_size (self));
     break;
 
+  case PROP_LOADABLE_ICON:
+    g_value_set_object (value, hdy_avatar_get_loadable_icon (self));
+    break;
+
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
@@ -322,12 +596,27 @@ hdy_avatar_set_property (GObject      *object,
     hdy_avatar_set_size (self, g_value_get_int (value));
     break;
 
+  case PROP_LOADABLE_ICON:
+    hdy_avatar_set_loadable_icon (self, g_value_get_object (value));
+    break;
+
   default:
     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     break;
   }
 }
 
+static void
+hdy_avatar_dispose (GObject *object)
+{
+  HdyAvatar *self = HDY_AVATAR (object);
+
+  g_cancellable_cancel (self->cancellable);
+  g_clear_object (&self->icon);
+
+  G_OBJECT_CLASS (hdy_avatar_parent_class)->dispose (object);
+}
+
 static void
 hdy_avatar_finalize (GObject *object)
 {
@@ -335,11 +624,9 @@ hdy_avatar_finalize (GObject *object)
 
   g_clear_pointer (&self->icon_name, g_free);
   g_clear_pointer (&self->text, g_free);
-  g_clear_pointer (&self->round_image, cairo_surface_destroy);
+  g_clear_object (&self->round_image);
   g_clear_object (&self->layout);
-
-  if (self->load_image_func_target_destroy_notify != NULL)
-    self->load_image_func_target_destroy_notify (self->load_image_func_target);
+  g_clear_object (&self->cancellable);
 
   G_OBJECT_CLASS (hdy_avatar_parent_class)->finalize (object);
 }
@@ -347,6 +634,7 @@ hdy_avatar_finalize (GObject *object)
 static void
 draw_for_size (HdyAvatar *self,
                cairo_t   *cr,
+               GdkPixbuf *custom_image,
                gint       width,
                gint       height,
                gint       scale_factor)
@@ -364,12 +652,10 @@ draw_for_size (HdyAvatar *self,
 
   set_class_contrasted (self, size);
 
-  update_custom_image (self, width, height, scale_factor);
-
-  if (self->round_image) {
-    cairo_set_source_surface (cr, self->round_image, x, y);
-    cairo_paint (cr);
-
+  if (custom_image) {
+    surface = gdk_cairo_surface_create_from_pixbuf (custom_image, scale_factor,
+                                                    gtk_widget_get_window (GTK_WIDGET (self)));
+    gtk_render_icon_surface (context, cr, surface, x, y);
     gtk_render_background (context, cr, x, y, size, size);
     gtk_render_frame (context, cr, x, y, size, size);
     return;
@@ -423,11 +709,46 @@ static gboolean
 hdy_avatar_draw (GtkWidget *widget,
                  cairo_t   *cr)
 {
+  HdyAvatar *self = HDY_AVATAR (widget);
+  GdkPixbuf *custom_image = NULL;
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
   gint width = gtk_widget_get_allocated_width (widget);
   gint height = gtk_widget_get_allocated_height (widget);
   gint scale_factor = gtk_widget_get_scale_factor (widget);
+  gint new_size = MIN (width, height) * scale_factor;
+
+  if (get_icon (self)) {
+    custom_image = update_custom_image (get_icon (self), NULL, self->round_image, new_size);
+
+    if ((!custom_image &&
+        !self->loading_error) ||
+        (self->currently_loading_size != new_size &&
+        is_scaled (custom_image))) {
+      self->currently_loading_size = new_size;
+      g_cancellable_cancel (self->cancellable);
+      g_set_object (&self->cancellable, g_cancellable_new ());
+      load_icon_async (get_icon (self),
+                       new_size,
+                       self->cancellable,
+                       (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
+                       self);
+    }
+
+    /* We don't want to draw a broken custom image, because it may be scaled
+       and we prefer to use the generated one in this case */
+    if (self->loading_error)
+      g_clear_object (&custom_image);
+  }
+
+  if (self->round_image && !custom_image)
+    gtk_style_context_remove_class (context, "image");
+
+  if (!self->round_image && custom_image)
+    gtk_style_context_add_class (context, "image");
+
+  g_set_object (&self->round_image, custom_image);
+  draw_for_size (self, cr, self->round_image, width, height, scale_factor);
 
-  draw_for_size (HDY_AVATAR (widget), cr, width, height, scale_factor);
   return FALSE;
 }
 
@@ -523,8 +844,8 @@ hdy_avatar_class_init (HdyAvatarClass *klass)
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
 
+  object_class->dispose = hdy_avatar_dispose;
   object_class->finalize = hdy_avatar_finalize;
-
   object_class->set_property = hdy_avatar_set_property;
   object_class->get_property = hdy_avatar_get_property;
 
@@ -592,6 +913,20 @@ hdy_avatar_class_init (HdyAvatarClass *klass)
                           FALSE,
                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * HdyAvatar:loadable-icon:
+   *
+   * A #GLoadableIcon used to load the avatar.
+   *
+   * Since: 1.1
+   */
+  props[PROP_LOADABLE_ICON] =
+    g_param_spec_object ("loadable-icon",
+                         "Loadable Icon",
+                         "The loadable icon used to load the avatar",
+                         G_TYPE_LOADABLE_ICON,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
   g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
 
   gtk_widget_class_set_css_name (widget_class, "avatar");
@@ -779,19 +1114,40 @@ hdy_avatar_set_image_load_func (HdyAvatar              *self,
                                 gpointer                user_data,
                                 GDestroyNotify          destroy)
 {
+  g_autoptr (HdyAvatarIcon) icon = NULL;
+
   g_return_if_fail (HDY_IS_AVATAR (self));
   g_return_if_fail (user_data != NULL || (user_data == NULL && destroy == NULL));
 
-  if (self->load_image_func_target_destroy_notify != NULL)
-    self->load_image_func_target_destroy_notify (self->load_image_func_target);
+  if (load_image != NULL)
+    icon = hdy_avatar_icon_new (load_image, user_data, destroy);
 
-  self->load_image_func = load_image;
-  self->load_image_func_target = user_data;
-  self->load_image_func_target_destroy_notify = destroy;
+  if (self->load_func_icon && !self->icon) {
+    g_cancellable_cancel (self->cancellable);
+    g_clear_object (&self->cancellable);
+    self->currently_loading_size = -1;
+    self->loading_error = FALSE;
+  }
 
-  g_clear_pointer (&self->round_image, cairo_surface_destroy);
-  self->round_image_size = -1;
-  gtk_widget_queue_draw (GTK_WIDGET (self));
+  g_set_object (&self->load_func_icon, icon);
+
+  /* Don't update the custom avatar when we have a user set GLoadableIcon */
+  if (self->icon)
+    return;
+
+  if (self->load_func_icon) {
+    gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+
+    self->cancellable = g_cancellable_new ();
+    self->currently_loading_size = self->size * scale_factor;
+    load_icon_async (G_LOADABLE_ICON (self->load_func_icon),
+                     self->currently_loading_size,
+                     self->cancellable,
+                     (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
+                     self);
+  } else {
+    gtk_widget_queue_draw (GTK_WIDGET (self));
+  }
 }
 
 /**
@@ -852,6 +1208,9 @@ hdy_avatar_draw_to_pixbuf (HdyAvatar *self,
 {
   g_autoptr (cairo_surface_t) surface = NULL;
   g_autoptr (cairo_t) cr = NULL;
+  g_autoptr (GdkPixbuf) custom_image = NULL;
+  g_autoptr (GdkPixbuf) pixbuf_from_icon = NULL;
+  gint scaled_size = size * scale_factor;
   GtkStyleContext *context;
   GtkAllocation bounds;
 
@@ -869,9 +1228,87 @@ hdy_avatar_draw_to_pixbuf (HdyAvatar *self,
   cr = cairo_create (surface);
 
   cairo_translate (cr, -bounds.x, -bounds.y);
-  draw_for_size (self, cr, size, size, scale_factor);
+
+  if (get_icon (self)) {
+    /* Only used the cached round_image if it fits the size and it isn't scaled*/
+    if (!self->round_image ||
+        is_scaled (self->round_image) ||
+        gdk_pixbuf_get_width (self->round_image) != scaled_size) {
+      pixbuf_from_icon = load_icon_sync (get_icon (self), scaled_size);
+      custom_image = update_custom_image (get_icon (self), pixbuf_from_icon, NULL, scaled_size);
+    } else {
+      custom_image = update_custom_image (get_icon (self), NULL, self->round_image, scaled_size);
+    }
+  }
+
+  draw_for_size (self, cr, custom_image, size, size, scale_factor);
 
   return gdk_pixbuf_get_from_surface (surface, 0, 0,
                                       bounds.width * scale_factor,
                                       bounds.height * scale_factor);
 }
+
+/**
+ * hdy_avatar_get_loadable_icon:
+ * @self: a #HdyAvatar
+ *
+ * Gets the #GLoadableIcon set via hdy_avatar_set_loadable_icon().
+ *
+ * Returns: (nullable) (transfer none): the #GLoadableIcon
+ *
+ * Since: 1.1
+ */
+GLoadableIcon *
+hdy_avatar_get_loadable_icon (HdyAvatar *self)
+{
+  g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
+
+  return self->icon;
+}
+
+/**
+ * hdy_avatar_set_loadable_icon:
+ * @self: a #HdyAvatar
+ * @icon: (nullable): a #GLoadableIcon
+ *
+ * Sets the #GLoadableIcon to use as an avatar.
+ * The previous avatar is displayed till the new avatar is loaded,
+ * to immediately remove the custom avatar set the loadable-icon to %NULL.
+ *
+ * The #GLoadableIcon set via this function is prefered over a set #HdyAvatarImageLoadFunc.
+ *
+ * Since: 1.1
+ */
+void
+hdy_avatar_set_loadable_icon (HdyAvatar     *self,
+                              GLoadableIcon *icon)
+{
+  g_return_if_fail (HDY_IS_AVATAR (self));
+  g_return_if_fail (icon == NULL || G_IS_LOADABLE_ICON (icon));
+
+  if (icon == self->icon)
+    return;
+
+  if (self->icon) {
+    g_cancellable_cancel (self->cancellable);
+    g_clear_object (&self->cancellable);
+    self->currently_loading_size = -1;
+    self->loading_error = FALSE;
+  }
+
+  g_set_object (&self->icon, icon);
+
+  if (self->icon) {
+    gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+    self->currently_loading_size = self->size * scale_factor;
+    load_icon_async (self->icon,
+                     self->currently_loading_size,
+                     self->cancellable,
+                     (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
+                     self);
+  } else {
+    gtk_widget_queue_draw (GTK_WIDGET (self));
+  }
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOADABLE_ICON]);
+}
diff --git a/src/hdy-avatar.h b/src/hdy-avatar.h
index e820f9a8..4cfccfa9 100644
--- a/src/hdy-avatar.h
+++ b/src/hdy-avatar.h
@@ -70,5 +70,10 @@ HDY_AVAILABLE_IN_ALL
 GdkPixbuf   *hdy_avatar_draw_to_pixbuf      (HdyAvatar              *self,
                                              gint                    size,
                                              gint                    scale_factor);
+HDY_AVAILABLE_IN_1_1
+GLoadableIcon *hdy_avatar_get_loadable_icon (HdyAvatar              *self);
+HDY_AVAILABLE_IN_1_1
+void           hdy_avatar_set_loadable_icon (HdyAvatar              *self,
+                                             GLoadableIcon          *icon);
 
 G_END_DECLS


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