[libadwaita/wip/exalm/avatar-paintable] avatar: Replace image load func with paintables




commit 75b203d8ff45ff8807cca151ae5f5675b7595968
Author: Alexander Mikhaylenko <alexm gnome org>
Date:   Mon Apr 26 14:49:37 2021 +0500

    avatar: Replace image load func with paintables
    
    Introduce #AdwAvatar:custom-image. The original motivation for having image
    load functions was to make it work with HiDPI which paintables already can
    do.
    
    Since then, libhandy has introduced a GLoadableIcon-based API to allow
    async loading, but paintables are generic enough that it should be possible
    to implement it as well.

 .gitlab-ci.yml             |   2 +-
 examples/adw-demo-window.c |  42 +++--------
 src/adw-avatar.c           | 182 ++++++++++++++++-----------------------------
 src/adw-avatar.h           |  22 +-----
 4 files changed, 81 insertions(+), 167 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e2bfd11..ddb4db0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -50,7 +50,7 @@ abi-check:
   image: $ABI_CHECKER_IMAGE
   stage: build
   variables:
-    LAST_ABI_BREAK: "c6ded459e635154ec8b8e2c175b1937f9618fd01"
+    LAST_ABI_BREAK: "e5f87fb68d8c544512e25b3078b9cde13a0ada01"
   script:
     - ./.gitlab-ci/check-abi ${LAST_ABI_BREAK} $(git rev-parse HEAD)
 
diff --git a/examples/adw-demo-window.c b/examples/adw-demo-window.c
index 90f8572..54bac18 100644
--- a/examples/adw-demo-window.c
+++ b/examples/adw-demo-window.c
@@ -234,37 +234,7 @@ avatar_file_remove_cb (AdwDemoWindow *self)
 
   gtk_label_set_label (self->avatar_file_chooser_label, _("(None)"));
   gtk_widget_set_sensitive (GTK_WIDGET (self->avatar_remove_button), FALSE);
-  adw_avatar_set_image_load_func (self->avatar, NULL, NULL, NULL);
-}
-
-static GdkPixbuf *
-avatar_load_file (int size, AdwDemoWindow *self)
-{
-  g_autoptr (GError) error = NULL;
-  g_autoptr (GdkPixbuf) pixbuf = NULL;
-  g_autoptr (GFile) file = NULL;
-  g_autofree char *filename = NULL;
-  int width, height;
-
-  g_assert (ADW_IS_DEMO_WINDOW (self));
-
-  file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (self->avatar_file_chooser));
-  filename = g_file_get_path (file);
-
-  gdk_pixbuf_get_file_info (filename, &width, &height);
-
-  pixbuf = gdk_pixbuf_new_from_file_at_scale (filename,
-                                              (width <= height) ? size : -1,
-                                              (width >= height) ? size : -1,
-                                              TRUE,
-                                              &error);
-  if (error != NULL) {
-    g_critical ("Failed to create pixbuf from file: %s", error->message);
-
-    return NULL;
-  }
-
-  return g_steal_pointer (&pixbuf);
+  adw_avatar_set_custom_image (self->avatar, NULL);
 }
 
 static void
@@ -273,6 +243,8 @@ avatar_file_chooser_response_cb (AdwDemoWindow *self,
 {
   g_autoptr (GFile) file = NULL;
   g_autoptr (GFileInfo) info = NULL;
+  g_autoptr (GdkTexture) texture = NULL;
+  g_autoptr (GError) error = NULL;
 
   g_assert (ADW_IS_DEMO_WINDOW (self));
 
@@ -291,7 +263,13 @@ avatar_file_chooser_response_cb (AdwDemoWindow *self,
                          g_file_info_get_display_name (info));
 
   gtk_widget_set_sensitive (GTK_WIDGET (self->avatar_remove_button), TRUE);
-  adw_avatar_set_image_load_func (self->avatar, (AdwAvatarImageLoadFunc) avatar_load_file, self, NULL);
+
+  texture = gdk_texture_new_from_file (file, &error);
+
+  if (error)
+    g_critical ("Failed to create texture from file: %s", error->message);
+
+  adw_avatar_set_custom_image (self->avatar, texture ? GDK_PAINTABLE (texture) : NULL);
 }
 
 static void
diff --git a/src/adw-avatar.c b/src/adw-avatar.c
index 3eaa201..a2b9a8e 100644
--- a/src/adw-avatar.c
+++ b/src/adw-avatar.c
@@ -29,34 +29,7 @@
  * The color is picked based on the hash of the #AdwAvatar:text.
  * If #AdwAvatar:show-initials is set to %FALSE, `avatar-default-symbolic` is
  * shown in place of the initials.
- * Use adw_avatar_set_image_load_func () to set a custom image.
- * Create a #AdwAvatarImageLoadFunc similar to this example:
- *
- * |[<!-- language="C" -->
- * static GdkPixbuf *
- * image_load_func (int size, gpointer user_data)
- * {
- *   g_autoptr (GError) error = NULL;
- *   g_autoptr (GdkPixbuf) pixbuf = NULL;
- *   g_autofree char *file = gtk_file_chooser_get_filename ("avatar.png");
- *   int width, height;
- *
- *   gdk_pixbuf_get_file_info (file, &width, &height);
- *
- *   pixbuf = gdk_pixbuf_new_from_file_at_scale (file,
- *                                              (width <= height) ? size : -1,
- *                                              (width >= height) ? size : -1,
- *                                              TRUE,
- *                                              error);
- *   if (error != NULL) {
- *    g_critical ("Failed to create pixbuf from file: %s", error->message);
- *
- *    return NULL;
- *   }
- *
- *   return pixbuf;
- * }
- * ]|
+ * Use #AdwAvatar:custom-image to set a custom image.
  *
  * # CSS nodes
  *
@@ -79,12 +52,7 @@ struct _AdwAvatar
   gboolean show_initials;
   guint color_class;
   int size;
-  GdkTexture *texture;
-  int round_image_size;
-
-  AdwAvatarImageLoadFunc load_image_func;
-  gpointer load_image_func_target;
-  GDestroyNotify load_image_func_target_destroy_notify;
+  GdkPaintable *custom_paintable;
 };
 
 G_DEFINE_TYPE (AdwAvatar, adw_avatar, GTK_TYPE_WIDGET);
@@ -94,6 +62,7 @@ enum {
   PROP_ICON_NAME,
   PROP_TEXT,
   PROP_SHOW_INITIALS,
+  PROP_CUSTOM_IMAGE,
   PROP_SIZE,
   PROP_LAST_PROP,
 };
@@ -130,7 +99,7 @@ extract_initials_from_text (const char *text)
 static void
 update_visibility (AdwAvatar *self)
 {
-  gboolean has_custom_image = self->texture != NULL;
+  gboolean has_custom_image = self->custom_paintable != NULL;
   gboolean has_initials = self->show_initials && self->text && strlen (self->text);
 
   gtk_widget_set_visible (GTK_WIDGET (self->label), !has_custom_image && has_initials);
@@ -138,53 +107,6 @@ update_visibility (AdwAvatar *self)
   gtk_widget_set_visible (GTK_WIDGET (self->custom_image), has_custom_image);
 }
 
-static void
-update_custom_image (AdwAvatar *self,
-                     int        width,
-                     int        height,
-                     int        scale_factor)
-{
-  g_autoptr (GdkPixbuf) pixbuf = NULL;
-  int new_size;
-
-  new_size = MIN (width, height) * scale_factor;
-
-  if (self->round_image_size != new_size && self->texture != NULL) {
-    self->round_image_size = -1;
-  }
-  g_clear_object (&self->texture);
-
-  if (self->load_image_func != NULL && self->texture == NULL) {
-    pixbuf = self->load_image_func (new_size, self->load_image_func_target);
-    if (pixbuf != NULL) {
-      if (width != height) {
-        GdkPixbuf *subpixbuf;
-
-        subpixbuf = gdk_pixbuf_new_subpixbuf (pixbuf,
-                                              (width - new_size) / 2,
-                                              (height - new_size) / 2,
-                                              new_size,
-                                              new_size);
-
-        g_object_unref (pixbuf);
-
-        pixbuf = subpixbuf;
-      }
-
-      self->texture = gdk_texture_new_for_pixbuf (pixbuf);
-      self->round_image_size = new_size;
-    }
-  }
-
-  if (self->texture)
-    gtk_widget_add_css_class (self->gizmo, "image");
-  else
-    gtk_widget_remove_css_class (self->gizmo, "image");
-
-  gtk_image_set_from_paintable (self->custom_image, GDK_PAINTABLE (self->texture));
-  update_visibility (self);
-}
-
 static void
 set_class_color (AdwAvatar *self)
 {
@@ -212,7 +134,7 @@ update_initials (AdwAvatar *self)
 {
   g_autofree char *initials = NULL;
 
-  if (self->texture != NULL ||
+  if (self->custom_paintable != NULL ||
       !self->show_initials ||
       self->text == NULL ||
       strlen (self->text) == 0)
@@ -242,7 +164,7 @@ update_font_size (AdwAvatar *self)
   double new_font_size;
   PangoAttrList *attributes;
 
-  if (self->texture != NULL ||
+  if (self->custom_paintable != NULL ||
       !self->show_initials ||
       self->text == NULL ||
       strlen (self->text) == 0)
@@ -291,7 +213,11 @@ adw_avatar_get_property (GObject    *object,
     g_value_set_boolean (value, adw_avatar_get_show_initials (self));
     break;
 
-  case PROP_SIZE:
+  case PROP_CUSTOM_IMAGE:
+    g_value_set_object (value, adw_avatar_get_custom_image (self));
+    break;
+
+    case PROP_SIZE:
     g_value_set_int (value, adw_avatar_get_size (self));
     break;
 
@@ -322,6 +248,10 @@ adw_avatar_set_property (GObject      *object,
     adw_avatar_set_show_initials (self, g_value_get_boolean (value));
     break;
 
+  case PROP_CUSTOM_IMAGE:
+    adw_avatar_set_custom_image (self, g_value_get_object (value));
+    break;
+
   case PROP_SIZE:
     adw_avatar_set_size (self, g_value_get_int (value));
     break;
@@ -353,10 +283,7 @@ adw_avatar_finalize (GObject *object)
 
   g_clear_pointer (&self->icon_name, g_free);
   g_clear_pointer (&self->text, g_free);
-  g_clear_object (&self->texture);
-
-  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->custom_paintable);
 
   G_OBJECT_CLASS (adw_avatar_parent_class)->finalize (object);
 }
@@ -420,6 +347,20 @@ adw_avatar_class_init (AdwAvatarClass *klass)
                           FALSE,
                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
 
+  /**
+   * AdwAvatar:custom-image:
+   *
+   * A custom image to use instead of initials or icon.
+   *
+   * Since: 1.0
+   */
+  props[PROP_CUSTOM_IMAGE] =
+    g_param_spec_object ("custom-image",
+                         "Custom image",
+                         "A custom image to use instead of initials or icon",
+                         GDK_TYPE_PAINTABLE,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
   /**
    * AdwAvatar:size:
    *
@@ -464,7 +405,6 @@ adw_avatar_init (AdwAvatar *self)
   update_icon (self);
   update_visibility (self);
 
-  g_signal_connect (self, "notify::scale-factor", G_CALLBACK (update_custom_image), NULL);
   g_signal_connect (self, "notify::root", G_CALLBACK (update_font_size), NULL);
 }
 
@@ -641,37 +581,53 @@ adw_avatar_set_show_initials (AdwAvatar *self,
 }
 
 /**
- * adw_avatar_set_image_load_func:
+ * adw_avatar_get_custom_image
+ * @self: a #AdwAvatar
+ *
+ * Gets a #GdkPaintable used as a custom image instead of initials or icon.
+ *
+ * Returns: (nullable) (transfer none): the custom image for @self
+ *
+ * Since: 1.0
+ */
+GdkPaintable *
+adw_avatar_get_custom_image (AdwAvatar *self)
+{
+  g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
+
+  return self->custom_paintable;
+}
+
+/**
+ * adw_avatar_set_custom_image:
  * @self: a #AdwAvatar
- * @load_image: (closure user_data) (nullable): callback to set a custom image
- * @user_data: (nullable): user data passed to @load_image
- * @destroy: (nullable): destroy notifier for @user_data
+ * @custom_image: (nullable) (transfer none): a custom image
  *
- * A callback which is called when the custom image need to be reloaded for some
- * reason (e.g. scale-factor changes).
+ * Sets a #GdkPaintable to use as a custom image instead of initials or icon.
  *
  * Since: 1.0
  */
 void
-adw_avatar_set_image_load_func (AdwAvatar              *self,
-                                AdwAvatarImageLoadFunc  load_image,
-                                gpointer                user_data,
-                                GDestroyNotify          destroy)
+adw_avatar_set_custom_image (AdwAvatar    *self,
+                             GdkPaintable *custom_image)
 {
   g_return_if_fail (ADW_IS_AVATAR (self));
-  g_return_if_fail (user_data != NULL || (user_data == NULL && destroy == NULL));
+  g_return_if_fail (GDK_IS_PAINTABLE (custom_image) || custom_image == NULL);
 
-  if (self->load_image_func_target_destroy_notify != NULL)
-    self->load_image_func_target_destroy_notify (self->load_image_func_target);
+  if (self->custom_paintable == custom_image)
+    return;
 
-  self->load_image_func = load_image;
-  self->load_image_func_target = user_data;
-  self->load_image_func_target_destroy_notify = destroy;
+  g_set_object (&self->custom_paintable, custom_image);
 
-  update_custom_image (self,
-                       gtk_widget_get_width (GTK_WIDGET (self)),
-                       gtk_widget_get_height (GTK_WIDGET (self)),
-                       gtk_widget_get_scale_factor (GTK_WIDGET (self)));
+  if (self->custom_paintable)
+    gtk_widget_add_css_class (self->gizmo, "image");
+  else
+    gtk_widget_remove_css_class (self->gizmo, "image");
+
+  gtk_image_set_from_paintable (self->custom_image, custom_image);
+  update_visibility (self);
+
+  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_IMAGE]);
 }
 
 /**
@@ -722,10 +678,6 @@ adw_avatar_set_size (AdwAvatar *self,
     gtk_widget_remove_css_class (self->gizmo, "contrasted");
 
   update_font_size (self);
-  update_custom_image (self,
-                       gtk_widget_get_width (GTK_WIDGET (self)),
-                       gtk_widget_get_height (GTK_WIDGET (self)),
-                       gtk_widget_get_scale_factor (GTK_WIDGET (self)));
 
   gtk_widget_queue_resize (GTK_WIDGET (self));
   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIZE]);
@@ -758,8 +710,6 @@ adw_avatar_draw_to_pixbuf (AdwAvatar *self,
   g_return_val_if_fail (size > 0, NULL);
   g_return_val_if_fail (scale_factor > 0, NULL);
 
-  update_custom_image (self, size, size, scale_factor);
-
   snapshot = gtk_snapshot_new ();
   GTK_WIDGET_GET_CLASS (self)->snapshot (GTK_WIDGET (self), snapshot);
 
diff --git a/src/adw-avatar.h b/src/adw-avatar.h
index 6069122..ab5682e 100644
--- a/src/adw-avatar.h
+++ b/src/adw-avatar.h
@@ -22,20 +22,6 @@ G_BEGIN_DECLS
 ADW_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (AdwAvatar, adw_avatar, ADW, AVATAR, GtkWidget)
 
-/**
- * AdwAvatarImageLoadFunc:
- * @size: the required size of the avatar
- * @user_data: (closure): user data
- *
- * The returned #GdkPixbuf is expected to be square with width and height set
- * to @size. The image is cropped to a circle without any scaling or transformation.
- *
- * Returns: (nullable) (transfer full): the #GdkPixbuf to use as a custom avatar
- * or %NULL to fallback to the generated avatar.
- */
-typedef GdkPixbuf *(*AdwAvatarImageLoadFunc) (int      size,
-                                              gpointer user_data);
-
 ADW_AVAILABLE_IN_ALL
 GtkWidget *adw_avatar_new (int         size,
                            const char *text,
@@ -60,10 +46,10 @@ void     adw_avatar_set_show_initials (AdwAvatar *self,
                                        gboolean   show_initials);
 
 ADW_AVAILABLE_IN_ALL
-void adw_avatar_set_image_load_func (AdwAvatar              *self,
-                                     AdwAvatarImageLoadFunc  load_image,
-                                     gpointer                user_data,
-                                     GDestroyNotify          destroy);
+GdkPaintable *adw_avatar_get_custom_image (AdwAvatar *self);
+ADW_AVAILABLE_IN_ALL
+void          adw_avatar_set_custom_image (AdwAvatar    *self,
+                                           GdkPaintable *custom_image);
 
 ADW_AVAILABLE_IN_ALL
 int  adw_avatar_get_size (AdwAvatar *self);


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