summaryrefslogtreecommitdiffstats
path: root/subprojects/libhandy/src/hdy-avatar.c
diff options
context:
space:
mode:
Diffstat (limited to 'subprojects/libhandy/src/hdy-avatar.c')
-rw-r--r--subprojects/libhandy/src/hdy-avatar.c811
1 files changed, 811 insertions, 0 deletions
diff --git a/subprojects/libhandy/src/hdy-avatar.c b/subprojects/libhandy/src/hdy-avatar.c
new file mode 100644
index 0000000..9dcdcdf
--- /dev/null
+++ b/subprojects/libhandy/src/hdy-avatar.c
@@ -0,0 +1,811 @@
+/*
+ * Copyright (C) 2020 Purism SPC
+ * Copyright (C) 2020 Felipe Borges
+ *
+ * Authors:
+ * Felipe Borges <felipeborges@gnome.org>
+ * Julian Sparber <julian@sparber.net>
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ */
+
+#include "config.h"
+#include <math.h>
+
+#include "hdy-avatar.h"
+#include "hdy-cairo-private.h"
+
+#define NUMBER_OF_COLORS 14
+/**
+ * SECTION:hdy-avatar
+ * @short_description: A widget displaying an image, with a generated fallback.
+ * @Title: HdyAvatar
+ *
+ * #HdyAvatar is a widget to display a round avatar.
+ * A provided image is made round before displaying, if no image is given this
+ * widget generates a round fallback with the initials of the #HdyAvatar:text
+ * on top of a colord background.
+ * The color is picked based on the hash of the #HdyAvatar:text.
+ * If #HdyAvatar:show-initials is set to %FALSE, `avatar-default-symbolic` is
+ * shown in place of the initials.
+ * Use hdy_avatar_set_image_load_func () to set a custom image.
+ * Create a #HdyAvatarImageLoadFunc similar to this example:
+ *
+ * |[<!-- language="C" -->
+ * static GdkPixbuf *
+ * image_load_func (gint size, gpointer user_data)
+ * {
+ * g_autoptr (GError) error = NULL;
+ * g_autoptr (GdkPixbuf) pixbuf = NULL;
+ * g_autofree gchar *file = gtk_file_chooser_get_filename ("avatar.png");
+ * gint 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;
+ * }
+ * ]|
+ *
+ * # CSS nodes
+ *
+ * #HdyAvatar has a single CSS node with name avatar.
+ *
+ */
+
+struct _HdyAvatar
+{
+ GtkDrawingArea parent_instance;
+
+ gchar *icon_name;
+ gchar *text;
+ PangoLayout *layout;
+ gboolean show_initials;
+ guint color_class;
+ gint size;
+ cairo_surface_t *round_image;
+
+ HdyAvatarImageLoadFunc load_image_func;
+ gpointer load_image_func_target;
+ GDestroyNotify load_image_func_target_destroy_notify;
+};
+
+G_DEFINE_TYPE (HdyAvatar, hdy_avatar, GTK_TYPE_DRAWING_AREA);
+
+enum {
+ PROP_0,
+ PROP_ICON_NAME,
+ PROP_TEXT,
+ PROP_SHOW_INITIALS,
+ PROP_SIZE,
+ PROP_LAST_PROP,
+};
+static GParamSpec *props[PROP_LAST_PROP];
+
+static cairo_surface_t *
+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);
+
+ /* Clip a circle */
+ cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0, 2 * G_PI);
+ cairo_clip (cr);
+ cairo_new_path (cr);
+
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ cairo_paint (cr);
+
+ return g_steal_pointer (&surface);
+}
+
+static gchar *
+extract_initials_from_text (const gchar *text)
+{
+ GString *initials;
+ g_autofree gchar *p = g_utf8_strup (text, -1);
+ g_autofree gchar *normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ gunichar unichar;
+ gchar *q = NULL;
+
+ if (normalized == NULL)
+ return NULL;
+
+ initials = g_string_new ("");
+
+ unichar = g_utf8_get_char (normalized);
+ g_string_append_unichar (initials, unichar);
+
+ q = g_utf8_strrchr (normalized, -1, ' ');
+ if (q != NULL && g_utf8_next_char (q) != NULL) {
+ q = g_utf8_next_char (q);
+
+ unichar = g_utf8_get_char (q);
+ g_string_append_unichar (initials, unichar);
+ }
+
+ return g_string_free (initials, FALSE);
+}
+
+static void
+update_custom_image (HdyAvatar *self)
+{
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ gint scale_factor;
+ gint size;
+ gboolean was_custom = FALSE;
+
+ if (self->round_image != NULL) {
+ g_clear_pointer (&self->round_image, cairo_surface_destroy);
+ was_custom = TRUE;
+ }
+
+ if (self->load_image_func != NULL) {
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
+ size = MIN (gtk_widget_get_allocated_width (GTK_WIDGET (self)),
+ gtk_widget_get_allocated_height (GTK_WIDGET (self)));
+ pixbuf = self->load_image_func (size * scale_factor, self->load_image_func_target);
+ if (pixbuf != NULL) {
+ self->round_image = round_image (pixbuf, (gdouble) size * scale_factor);
+ cairo_surface_set_device_scale (self->round_image, scale_factor, scale_factor);
+ }
+ }
+
+ if (was_custom || self->round_image != NULL)
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+static void
+set_class_color (HdyAvatar *self)
+{
+ GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ g_autofree GRand *rand = NULL;
+ g_autofree gchar *new_class = NULL;
+ g_autofree gchar *old_class = g_strdup_printf ("color%d", self->color_class);
+
+ gtk_style_context_remove_class (context, old_class);
+
+ if (self->text == NULL || strlen (self->text) == 0) {
+ /* Use a random color if we don't have a text */
+ rand = g_rand_new ();
+ self->color_class = g_rand_int_range (rand, 1, NUMBER_OF_COLORS);
+ } else {
+ self->color_class = (g_str_hash (self->text) % NUMBER_OF_COLORS) + 1;
+ }
+
+ new_class = g_strdup_printf ("color%d", self->color_class);
+ gtk_style_context_add_class (context, new_class);
+}
+
+static void
+set_class_contrasted (HdyAvatar *self, gint size)
+{
+ GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+ if (size < 25)
+ gtk_style_context_add_class (context, "contrasted");
+ else
+ gtk_style_context_remove_class (context, "contrasted");
+}
+
+static void
+clear_pango_layout (HdyAvatar *self)
+{
+ g_clear_object (&self->layout);
+}
+
+static void
+ensure_pango_layout (HdyAvatar *self)
+{
+ g_autofree gchar *initials = NULL;
+
+ if (self->layout != NULL || self->text == NULL || strlen (self->text) == 0)
+ return;
+
+ initials = extract_initials_from_text (self->text);
+ self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), initials);
+}
+
+static void
+set_font_size (HdyAvatar *self,
+ gint size)
+{
+ GtkStyleContext *context;
+ PangoFontDescription *font_desc;
+ gint width, height;
+ gdouble padding;
+ gdouble sqr_size;
+ gdouble max_size;
+ gdouble new_font_size;
+
+ if (self->round_image != NULL || self->layout == NULL)
+ return;
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (self));
+ gtk_style_context_get (context, gtk_style_context_get_state (context),
+ "font", &font_desc, NULL);
+
+ pango_layout_set_font_description (self->layout, font_desc);
+ pango_layout_get_pixel_size (self->layout, &width, &height);
+
+ /* This is the size of the biggest square fitting inside the circle */
+ sqr_size = (gdouble)size / 1.4142;
+ /* The padding has to be a function of the overall size.
+ * The 0.4 is how steep the linear function grows and the -5 is just
+ * an adjustment for smaller sizes which doesn't have a big impact on bigger sizes.
+ * Make also sure we don't have a negative padding */
+ padding = MAX (size * 0.4 - 5, 0);
+ max_size = sqr_size - padding;
+ new_font_size = (gdouble)height * (max_size / (gdouble)width);
+
+ font_desc = pango_font_description_copy (font_desc);
+ pango_font_description_set_absolute_size (font_desc,
+ CLAMP (new_font_size, 0, max_size) * PANGO_SCALE);
+ pango_layout_set_font_description (self->layout, font_desc);
+ pango_font_description_free (font_desc);
+}
+
+static void
+hdy_avatar_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ HdyAvatar *self = HDY_AVATAR (object);
+
+ switch (property_id) {
+ case PROP_ICON_NAME:
+ g_value_set_string (value, hdy_avatar_get_icon_name (self));
+ break;
+
+ case PROP_TEXT:
+ g_value_set_string (value, hdy_avatar_get_text (self));
+ break;
+
+ case PROP_SHOW_INITIALS:
+ g_value_set_boolean (value, hdy_avatar_get_show_initials (self));
+ break;
+
+ case PROP_SIZE:
+ g_value_set_int (value, hdy_avatar_get_size (self));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_avatar_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ HdyAvatar *self = HDY_AVATAR (object);
+
+ switch (property_id) {
+ case PROP_ICON_NAME:
+ hdy_avatar_set_icon_name (self, g_value_get_string (value));
+ break;
+
+ case PROP_TEXT:
+ hdy_avatar_set_text (self, g_value_get_string (value));
+ break;
+
+ case PROP_SHOW_INITIALS:
+ hdy_avatar_set_show_initials (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_SIZE:
+ hdy_avatar_set_size (self, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+hdy_avatar_finalize (GObject *object)
+{
+ HdyAvatar *self = HDY_AVATAR (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->layout);
+
+ if (self->load_image_func_target_destroy_notify != NULL)
+ self->load_image_func_target_destroy_notify (self->load_image_func_target);
+
+ G_OBJECT_CLASS (hdy_avatar_parent_class)->finalize (object);
+}
+
+static gboolean
+hdy_avatar_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ HdyAvatar *self = HDY_AVATAR (widget);
+ 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 size = MIN (width, height);
+ gdouble x = (gdouble)(width - size) / 2.0;
+ gdouble y = (gdouble)(height - size) / 2.0;
+ const gchar *icon_name;
+ gint scale;
+ GdkRGBA color;
+ g_autoptr (GtkIconInfo) icon = NULL;
+ g_autoptr (GdkPixbuf) pixbuf = NULL;
+ g_autoptr (GError) error = NULL;
+ g_autoptr (cairo_surface_t) surface = NULL;
+
+ set_class_contrasted (HDY_AVATAR (widget), size);
+
+ gtk_render_frame (context, cr, x, y, size, size);
+
+ if (self->round_image) {
+ cairo_set_source_surface (cr, self->round_image, x, y);
+ cairo_paint (cr);
+
+ return FALSE;
+ }
+
+ gtk_render_background (context, cr, x, y, size, size);
+ ensure_pango_layout (HDY_AVATAR (widget));
+
+ if (self->show_initials && self->layout != NULL) {
+ set_font_size (HDY_AVATAR (widget), size);
+ pango_layout_get_pixel_size (self->layout, &width, &height);
+
+ gtk_render_layout (context, cr,
+ ((gdouble)(size - width) / 2.0) + x,
+ ((gdouble)(size - height) / 2.0) + y,
+ self->layout);
+
+ return FALSE;
+ }
+
+ icon_name = self->icon_name && *self->icon_name != '\0' ?
+ self->icon_name : "avatar-default-symbolic";
+ scale = gtk_widget_get_scale_factor (widget);
+ icon = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
+ icon_name,
+ size / 2, scale,
+ GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
+ if (icon == NULL) {
+ g_critical ("Failed to load icon `%s'", icon_name);
+
+ return FALSE;
+ }
+
+ gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color);
+ pixbuf = gtk_icon_info_load_symbolic (icon, &color, NULL, NULL, NULL, NULL, &error);
+ if (error != NULL) {
+ g_critical ("Failed to load icon `%s': %s", icon_name, error->message);
+
+ return FALSE;
+ }
+
+ surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale,
+ gtk_widget_get_window (widget));
+
+ width = cairo_image_surface_get_width (surface);
+ height = cairo_image_surface_get_height (surface);
+ gtk_render_icon_surface (context, cr, surface,
+ (((gdouble)size - ((gdouble)width / (gdouble)scale)) / 2.0) + x,
+ (((gdouble)size - ((gdouble)height / (gdouble)scale)) / 2.0) + y);
+
+ return FALSE;
+}
+
+/* This private method is prefixed by the class name because it will be a
+ * virtual method in GTK 4.
+ */
+static void
+hdy_avatar_measure (GtkWidget *widget,
+ GtkOrientation orientation,
+ int for_size,
+ int *minimum,
+ int *natural,
+ int *minimum_baseline,
+ int *natural_baseline)
+{
+ HdyAvatar *self = HDY_AVATAR (widget);
+
+ if (minimum)
+ *minimum = self->size;
+ if (natural)
+ *natural = self->size;
+}
+
+static void
+hdy_avatar_get_preferred_width (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_avatar_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_avatar_get_preferred_width_for_height (GtkWidget *widget,
+ gint height,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_avatar_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_avatar_get_preferred_height (GtkWidget *widget,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_avatar_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
+ minimum, natural, NULL, NULL);
+}
+
+static void
+hdy_avatar_get_preferred_height_for_width (GtkWidget *widget,
+ gint width,
+ gint *minimum,
+ gint *natural)
+{
+ hdy_avatar_measure (widget, GTK_ORIENTATION_VERTICAL, width,
+ minimum, natural, NULL, NULL);
+}
+
+static GtkSizeRequestMode
+hdy_avatar_get_request_mode (GtkWidget *widget)
+{
+ return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
+}
+
+static void
+hdy_avatar_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GtkAllocation clip;
+
+ gtk_render_background_get_clip (gtk_widget_get_style_context (widget),
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height,
+ &clip);
+
+ GTK_WIDGET_CLASS (hdy_avatar_parent_class)->size_allocate (widget, allocation);
+ gtk_widget_set_clip (widget, &clip);
+}
+
+static void
+hdy_avatar_class_init (HdyAvatarClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = hdy_avatar_finalize;
+
+ object_class->set_property = hdy_avatar_set_property;
+ object_class->get_property = hdy_avatar_get_property;
+
+ widget_class->draw = hdy_avatar_draw;
+ widget_class->get_request_mode = hdy_avatar_get_request_mode;
+ widget_class->get_preferred_width = hdy_avatar_get_preferred_width;
+ widget_class->get_preferred_height = hdy_avatar_get_preferred_height;
+ widget_class->get_preferred_width_for_height = hdy_avatar_get_preferred_width_for_height;
+ widget_class->get_preferred_height_for_width = hdy_avatar_get_preferred_height_for_width;
+ widget_class->size_allocate = hdy_avatar_size_allocate;
+
+ /**
+ * HdyAvatar:size:
+ *
+ * The avatar size of the avatar.
+ */
+ props[PROP_SIZE] =
+ g_param_spec_int ("size",
+ "Size",
+ "The size of the avatar",
+ -1, INT_MAX, -1,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdyAvatar:icon-name:
+ *
+ * The name of the icon in the icon theme to use when the icon should be
+ * displayed.
+ * If no name is set, the avatar-default-symbolic icon will be used.
+ * If the name doesn't match a valid icon, it is an error and no icon will be
+ * displayed.
+ * If the icon theme is changed, the image will be updated automatically.
+ *
+ * Since: 1.0
+ */
+ props[PROP_ICON_NAME] =
+ g_param_spec_string ("icon-name",
+ "Icon name",
+ "The name of the icon from the icon theme",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdyAvatar:text:
+ *
+ * The text used for the initials and for generating the color.
+ * If #HdyAvatar:show-initials is %FALSE it's only used to generate the color.
+ */
+ props[PROP_TEXT] =
+ g_param_spec_string ("text",
+ "Text",
+ "The text used to generate the color and the initials",
+ NULL,
+ G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * HdyAvatar:show_initials:
+ *
+ * Whether to show the initials or the fallback icon on the generated avatar.
+ */
+ props[PROP_SHOW_INITIALS] =
+ g_param_spec_boolean ("show-initials",
+ "Show initials",
+ "Whether to show the initials",
+ FALSE,
+ 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");
+}
+
+static void
+hdy_avatar_init (HdyAvatar *self)
+{
+ set_class_color (self);
+ g_signal_connect (self, "notify::scale-factor", G_CALLBACK (update_custom_image), NULL);
+ g_signal_connect (self, "size-allocate", G_CALLBACK (update_custom_image), NULL);
+ g_signal_connect (self, "screen-changed", G_CALLBACK (clear_pango_layout), NULL);
+}
+
+/**
+ * hdy_avatar_new:
+ * @size: The size of the avatar
+ * @text: (nullable): The text used to generate the color and initials if
+ * @show_initials is %TRUE. The color is selected at random if @text is empty.
+ * @show_initials: whether to show the initials or the fallback icon on
+ * top of the color generated based on @text.
+ *
+ * Creates a new #HdyAvatar.
+ *
+ * Returns: the newly created #HdyAvatar
+ */
+GtkWidget *
+hdy_avatar_new (gint size,
+ const gchar *text,
+ gboolean show_initials)
+{
+ return g_object_new (HDY_TYPE_AVATAR,
+ "size", size,
+ "text", text,
+ "show-initials", show_initials,
+ NULL);
+}
+
+/**
+ * hdy_avatar_get_icon_name:
+ * @self: a #HdyAvatar
+ *
+ * Gets the name of the icon in the icon theme to use when the icon should be
+ * displayed.
+ *
+ * Returns: (nullable) (transfer none): the name of the icon from the icon theme.
+ *
+ * Since: 1.0
+ */
+const gchar *
+hdy_avatar_get_icon_name (HdyAvatar *self)
+{
+ g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
+
+ return self->icon_name;
+}
+
+/**
+ * hdy_avatar_set_icon_name:
+ * @self: a #HdyAvatar
+ * @icon_name: (nullable): the name of the icon from the icon theme
+ *
+ * Sets the name of the icon in the icon theme to use when the icon should be
+ * displayed.
+ * If no name is set, the avatar-default-symbolic icon will be used.
+ * If the name doesn't match a valid icon, it is an error and no icon will be
+ * displayed.
+ * If the icon theme is changed, the image will be updated automatically.
+ *
+ * Since: 1.0
+ */
+void
+hdy_avatar_set_icon_name (HdyAvatar *self,
+ const gchar *icon_name)
+{
+ g_return_if_fail (HDY_IS_AVATAR (self));
+
+ if (g_strcmp0 (self->icon_name, icon_name) == 0)
+ return;
+
+ g_clear_pointer (&self->icon_name, g_free);
+ self->icon_name = g_strdup (icon_name);
+
+ if (!self->round_image &&
+ (!self->show_initials || self->layout == NULL))
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
+}
+
+/**
+ * hdy_avatar_get_text:
+ * @self: a #HdyAvatar
+ *
+ * Get the text used to generate the fallback initials and color
+ *
+ * Returns: (nullable) (transfer none): returns the text used to generate
+ * the fallback initials. This is the internal string used by
+ * the #HdyAvatar, and must not be modified.
+ */
+const gchar *
+hdy_avatar_get_text (HdyAvatar *self)
+{
+ g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
+
+ return self->text;
+}
+
+/**
+ * hdy_avatar_set_text:
+ * @self: a #HdyAvatar
+ * @text: (nullable): the text used to get the initials and color
+ *
+ * Set the text used to generate the fallback initials color
+ */
+void
+hdy_avatar_set_text (HdyAvatar *self,
+ const gchar *text)
+{
+ g_return_if_fail (HDY_IS_AVATAR (self));
+
+ if (g_strcmp0 (self->text, text) == 0)
+ return;
+
+ g_clear_pointer (&self->text, g_free);
+ self->text = g_strdup (text);
+
+ clear_pango_layout (self);
+ set_class_color (self);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
+}
+
+/**
+ * hdy_avatar_get_show_initials:
+ * @self: a #HdyAvatar
+ *
+ * Returns whether initials are used for the fallback or the icon.
+ *
+ * Returns: %TRUE if the initials are used for the fallback.
+ */
+gboolean
+hdy_avatar_get_show_initials (HdyAvatar *self)
+{
+ g_return_val_if_fail (HDY_IS_AVATAR (self), FALSE);
+
+ return self->show_initials;
+}
+
+/**
+ * hdy_avatar_set_show_initials:
+ * @self: a #HdyAvatar
+ * @show_initials: whether the initials should be shown on the fallback avatar
+ * or the icon.
+ *
+ * Sets whether the initials should be shown on the fallback avatar or the icon.
+ */
+void
+hdy_avatar_set_show_initials (HdyAvatar *self,
+ gboolean show_initials)
+{
+ g_return_if_fail (HDY_IS_AVATAR (self));
+
+ if (self->show_initials == show_initials)
+ return;
+
+ self->show_initials = show_initials;
+
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_INITIALS]);
+}
+
+/**
+ * hdy_avatar_set_image_load_func:
+ * @self: a #HdyAvatar
+ * @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
+ *
+ * A callback which is called when the custom image need to be reloaded for some
+ * reason (e.g. scale-factor changes).
+ */
+void
+hdy_avatar_set_image_load_func (HdyAvatar *self,
+ HdyAvatarImageLoadFunc load_image,
+ gpointer user_data,
+ GDestroyNotify destroy)
+{
+ 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);
+
+ self->load_image_func = load_image;
+ self->load_image_func_target = user_data;
+ self->load_image_func_target_destroy_notify = destroy;
+
+ update_custom_image (self);
+}
+
+/**
+ * hdy_avatar_get_size:
+ * @self: a #HdyAvatar
+ *
+ * Returns the size of the avatar.
+ *
+ * Returns: the size of the avatar.
+ */
+gint
+hdy_avatar_get_size (HdyAvatar *self)
+{
+ g_return_val_if_fail (HDY_IS_AVATAR (self), 0);
+
+ return self->size;
+}
+
+/**
+ * hdy_avatar_set_size:
+ * @self: a #HdyAvatar
+ * @size: The size to be used for the avatar
+ *
+ * Sets the size of the avatar.
+ */
+void
+hdy_avatar_set_size (HdyAvatar *self,
+ gint size)
+{
+ g_return_if_fail (HDY_IS_AVATAR (self));
+ g_return_if_fail (size >= -1);
+
+ if (self->size == size)
+ return;
+
+ self->size = size;
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+ g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIZE]);
+}