1 /*
2 * Copyright (C) 2020 Purism SPC
3 * Copyright (C) 2020 Felipe Borges
4 *
5 * Authors:
6 * Felipe Borges <felipeborges@gnome.org>
7 * Julian Sparber <julian@sparber.net>
8 *
9 * SPDX-License-Identifier: LGPL-2.1+
10 *
11 */
12
13 #include "config.h"
14 #include <math.h>
15
16 #include "hdy-avatar.h"
17 #include "hdy-avatar-icon-private.h"
18 #include "hdy-cairo-private.h"
19 #include "hdy-css-private.h"
20
21 #define NUMBER_OF_COLORS 14
22 #define LOAD_BUFFER_SIZE 65536
23 /**
24 * SECTION:hdy-avatar
25 * @short_description: A widget displaying an image, with a generated fallback.
26 * @Title: HdyAvatar
27 *
28 * #HdyAvatar is a widget to display a round avatar.
29 * A provided image is made round before displaying, if no image is given this
30 * widget generates a round fallback with the initials of the #HdyAvatar:text
31 * on top of a colord background.
32 * The color is picked based on the hash of the #HdyAvatar:text.
33 * If #HdyAvatar:show-initials is set to %FALSE, `avatar-default-symbolic` is
34 * shown in place of the initials.
35 * Use hdy_avatar_set_loadable_icon() or #HdyAvatar:loadable-icon to set a
36 * custom image.
37 *
38 * # CSS nodes
39 *
40 * #HdyAvatar has a single CSS node with name avatar.
41 *
42 */
43
44 struct _HdyAvatar
45 {
46 GtkDrawingArea parent_instance;
47
48 gchar *icon_name;
49 gchar *text;
50 PangoLayout *layout;
51 gboolean show_initials;
52 guint color_class;
53 gint size;
54 GdkPixbuf *round_image;
55
56 HdyAvatarIcon *load_func_icon;
57 GLoadableIcon *icon;
58 GCancellable *cancellable;
59 guint currently_loading_size;
60 gboolean loading_error;
61 };
62
63 G_DEFINE_TYPE (HdyAvatar, hdy_avatar, GTK_TYPE_DRAWING_AREA);
64
65 enum {
66 PROP_0,
67 PROP_ICON_NAME,
68 PROP_TEXT,
69 PROP_SHOW_INITIALS,
70 PROP_SIZE,
71 PROP_LOADABLE_ICON,
72 PROP_LAST_PROP,
73 };
74 static GParamSpec *props[PROP_LAST_PROP];
75
76 typedef struct {
77 gint size;
78 gint scale_factor;
79 } SizeData;
80
81 static void
size_data_free(SizeData * data)82 size_data_free (SizeData *data)
83 {
84 g_slice_free (SizeData, data);
85 }
86
87 static void
88 load_icon_async (HdyAvatar *self,
89 gint size,
90 GCancellable *cancellable,
91 GAsyncReadyCallback callback,
92 gpointer user_data);
93
94 static inline GLoadableIcon *
get_icon(HdyAvatar * self)95 get_icon (HdyAvatar *self)
96 {
97 if (self->icon)
98 return self->icon;
99
100 return G_LOADABLE_ICON (self->load_func_icon);
101 }
102
103 static inline gboolean
is_scaled(GdkPixbuf * pixbuf)104 is_scaled (GdkPixbuf *pixbuf)
105 {
106 return (pixbuf && g_object_get_data (G_OBJECT (pixbuf), "scaled") != NULL);
107 }
108
109 static GdkPixbuf *
make_round_image(GdkPixbuf * pixbuf,gdouble size)110 make_round_image (GdkPixbuf *pixbuf,
111 gdouble size)
112 {
113 g_autoptr (cairo_surface_t) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
114 g_autoptr (cairo_t) cr = cairo_create (surface);
115 gint width = gdk_pixbuf_get_width (pixbuf);
116 gint height = gdk_pixbuf_get_height (pixbuf);
117
118 /* Clip a circle */
119 cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0, 2 * G_PI);
120 cairo_clip (cr);
121 cairo_new_path (cr);
122
123 gdk_cairo_set_source_pixbuf (cr, pixbuf, (size - width) / 2, (size - height) / 2);
124 cairo_paint (cr);
125
126 return gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
127 }
128
129 static gchar *
extract_initials_from_text(const gchar * text)130 extract_initials_from_text (const gchar *text)
131 {
132 GString *initials;
133 g_autofree gchar *p = g_utf8_strup (text, -1);
134 g_autofree gchar *normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
135 gunichar unichar;
136 gchar *q = NULL;
137
138 if (normalized == NULL)
139 return NULL;
140
141 initials = g_string_new ("");
142
143 unichar = g_utf8_get_char (normalized);
144 g_string_append_unichar (initials, unichar);
145
146 q = g_utf8_strrchr (normalized, -1, ' ');
147 if (q != NULL && g_utf8_next_char (q) != NULL) {
148 q = g_utf8_next_char (q);
149
150 unichar = g_utf8_get_char (q);
151 g_string_append_unichar (initials, unichar);
152 }
153
154 return g_string_free (initials, FALSE);
155 }
156
157 static GdkPixbuf *
update_custom_image(GdkPixbuf * pixbuf_from_icon,GdkPixbuf * round_image,gint new_size)158 update_custom_image (GdkPixbuf *pixbuf_from_icon,
159 GdkPixbuf *round_image,
160 gint new_size)
161 {
162 if (round_image &&
163 gdk_pixbuf_get_width (round_image) == new_size &&
164 !is_scaled (round_image))
165 return g_object_ref (round_image);
166
167 if (pixbuf_from_icon) {
168 gint pixbuf_from_icon_size = MIN (gdk_pixbuf_get_width (pixbuf_from_icon),
169 gdk_pixbuf_get_height (pixbuf_from_icon));
170 if (pixbuf_from_icon_size == new_size)
171 return make_round_image (pixbuf_from_icon, new_size);
172 }
173
174 if (round_image) {
175 /* Use a scaled image till we get the new image from async loading */
176 GdkPixbuf *pixbuf = gdk_pixbuf_scale_simple (round_image,
177 new_size,
178 new_size,
179 GDK_INTERP_BILINEAR);
180 g_object_set_data (G_OBJECT (pixbuf), "scaled", GINT_TO_POINTER (TRUE));
181
182 return pixbuf;
183 }
184
185 return NULL;
186 }
187
188 static void
size_prepared_cb(GdkPixbufLoader * loader,gint width,gint height,gpointer user_data)189 size_prepared_cb (GdkPixbufLoader *loader,
190 gint width,
191 gint height,
192 gpointer user_data)
193 {
194 gint size = GPOINTER_TO_INT (user_data);
195 gdouble ratio = (gdouble) width / (gdouble) height;
196
197 if (width < height) {
198 width = size;
199 height = size / ratio;
200 } else {
201 width = size * ratio;
202 height = size;
203 }
204
205 gdk_pixbuf_loader_set_size (loader, width, height);
206 }
207
208 /* This function is copied from the gdk-pixbuf project,
209 * from the file gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-io.c.
210 * It was modified to fit libhandy's code style.
211 */
212 static void
load_from_stream_async_cb(GObject * stream,GAsyncResult * res,gpointer data)213 load_from_stream_async_cb (GObject *stream,
214 GAsyncResult *res,
215 gpointer data)
216 {
217 g_autoptr (GTask) task = data;
218 GdkPixbufLoader *loader = g_task_get_task_data (task);
219 g_autoptr (GBytes) bytes = NULL;
220 GError *error = NULL;
221
222 bytes = g_input_stream_read_bytes_finish (G_INPUT_STREAM (stream), res, &error);
223 if (bytes == NULL) {
224 gdk_pixbuf_loader_close (loader, NULL);
225 g_task_return_error (task, error);
226
227 return;
228 }
229
230 if (g_bytes_get_size (bytes) == 0) {
231 if (!gdk_pixbuf_loader_close (loader, &error)) {
232 g_task_return_error (task, error);
233
234 return;
235 }
236
237 g_task_return_pointer (task,
238 g_object_ref (gdk_pixbuf_loader_get_pixbuf (loader)),
239 g_object_unref);
240
241 return;
242 }
243
244 if (!gdk_pixbuf_loader_write (loader,
245 g_bytes_get_data (bytes, NULL),
246 g_bytes_get_size (bytes),
247 &error)) {
248 gdk_pixbuf_loader_close (loader, NULL);
249 g_task_return_error (task, error);
250
251 return;
252 }
253
254 g_input_stream_read_bytes_async (G_INPUT_STREAM (stream),
255 LOAD_BUFFER_SIZE,
256 G_PRIORITY_DEFAULT,
257 g_task_get_cancellable (task),
258 load_from_stream_async_cb,
259 g_object_ref (task));
260 }
261
262 static void
icon_load_async_cb(GLoadableIcon * icon,GAsyncResult * res,GTask * task)263 icon_load_async_cb (GLoadableIcon *icon,
264 GAsyncResult *res,
265 GTask *task)
266 {
267 GdkPixbufLoader *loader = g_task_get_task_data (task);
268 g_autoptr (GInputStream) stream = NULL;
269 g_autoptr (GError) error = NULL;
270
271 stream = g_loadable_icon_load_finish (icon, res, NULL, &error);
272 if (stream == NULL) {
273 gdk_pixbuf_loader_close (loader, NULL);
274 g_task_return_error (task, g_steal_pointer (&error));
275 g_object_unref (task);
276
277 return;
278 }
279
280 g_input_stream_read_bytes_async (stream,
281 LOAD_BUFFER_SIZE,
282 G_PRIORITY_DEFAULT,
283 g_task_get_cancellable (task),
284 load_from_stream_async_cb,
285 task);
286 }
287
288 static GdkPixbuf *
load_from_gicon_async_finish(GAsyncResult * async_result,GError ** error)289 load_from_gicon_async_finish (GAsyncResult *async_result,
290 GError **error)
291 {
292 GTask *task = G_TASK (async_result);
293
294 return g_task_propagate_pointer (task, error);
295 }
296
297 static void
load_from_gicon_async_for_display_cb(HdyAvatar * self,GAsyncResult * res,gpointer * user_data)298 load_from_gicon_async_for_display_cb (HdyAvatar *self,
299 GAsyncResult *res,
300 gpointer *user_data)
301 {
302 g_autoptr (GError) error = NULL;
303 g_autoptr (GdkPixbuf) pixbuf = NULL;
304
305 pixbuf = load_from_gicon_async_finish (res, &error);
306
307 if (error != NULL) {
308 if (g_error_matches (error, HDY_AVATAR_ICON_ERROR, HDY_AVATAR_ICON_ERROR_EMPTY)) {
309 self->loading_error = TRUE;
310 } else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
311 g_warning ("Failed to load icon: %s", error->message);
312 self->loading_error = TRUE;
313 }
314 }
315
316 self->currently_loading_size = -1;
317
318 if (pixbuf) {
319 g_autoptr (GdkPixbuf) custom_image = NULL;
320 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
321 gint width = gtk_widget_get_allocated_width (GTK_WIDGET (self));
322 gint height = gtk_widget_get_allocated_height (GTK_WIDGET (self));
323 gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
324 gint new_size = MIN (width, height) * scale_factor;
325
326 if (get_icon (self)) {
327 custom_image = update_custom_image (pixbuf,
328 NULL,
329 new_size);
330
331 if (!self->round_image && custom_image)
332 gtk_style_context_add_class (context, "image");
333 }
334
335 g_set_object (&self->round_image, custom_image);
336 gtk_widget_queue_draw (GTK_WIDGET (self));
337 }
338 }
339
340 static void
load_from_gicon_async_for_export_cb(HdyAvatar * self,GAsyncResult * res,gpointer * user_data)341 load_from_gicon_async_for_export_cb (HdyAvatar *self,
342 GAsyncResult *res,
343 gpointer *user_data)
344 {
345 GTask *task = G_TASK (user_data);
346 g_autoptr (GError) error = NULL;
347 g_autoptr (GdkPixbuf) pixbuf = NULL;
348
349 pixbuf = load_from_gicon_async_finish (res, &error);
350
351 if (error &&
352 !g_error_matches (error, HDY_AVATAR_ICON_ERROR, HDY_AVATAR_ICON_ERROR_EMPTY) &&
353 !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
354 g_warning ("Failed to load icon: %s", error->message);
355 }
356
357 g_task_return_pointer (task,
358 g_steal_pointer (&pixbuf),
359 g_object_unref);
360 g_object_unref (task);
361 }
362
363 static void
load_icon_async(HdyAvatar * self,gint size,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)364 load_icon_async (HdyAvatar *self,
365 gint size,
366 GCancellable *cancellable,
367 GAsyncReadyCallback callback,
368 gpointer user_data)
369 {
370 GTask *task = g_task_new (self, cancellable, callback, user_data);
371 GdkPixbufLoader *loader = gdk_pixbuf_loader_new ();
372
373 g_signal_connect (loader, "size-prepared",
374 G_CALLBACK (size_prepared_cb),
375 GINT_TO_POINTER (size));
376
377 g_task_set_task_data (task, loader, g_object_unref);
378
379 g_loadable_icon_load_async (get_icon (self),
380 size,
381 cancellable,
382 (GAsyncReadyCallback) icon_load_async_cb,
383 task);
384 }
385
386 /* This function is copied from the gdk-pixbuf project,
387 * from the file gdk-pixbuf/gdk-pixbuf/gdk-pixbuf-io.c.
388 * It was modified to fit libhandy's code style.
389 */
390 static GdkPixbuf *
load_from_stream(GdkPixbufLoader * loader,GInputStream * stream,GCancellable * cancellable,GError ** error)391 load_from_stream (GdkPixbufLoader *loader,
392 GInputStream *stream,
393 GCancellable *cancellable,
394 GError **error)
395 {
396 GdkPixbuf *pixbuf;
397 guchar buffer[LOAD_BUFFER_SIZE];
398
399 while (TRUE) {
400 gssize n_read = g_input_stream_read (stream, buffer, sizeof (buffer),
401 cancellable, error);
402
403 if (n_read < 0) {
404 gdk_pixbuf_loader_close (loader, NULL);
405
406 return NULL;
407 }
408
409 if (n_read == 0)
410 break;
411
412 if (!gdk_pixbuf_loader_write (loader, buffer, n_read, error)) {
413 gdk_pixbuf_loader_close (loader, NULL);
414
415 return NULL;
416 }
417 }
418
419 if (!gdk_pixbuf_loader_close (loader, error))
420 return NULL;
421
422 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
423 if (pixbuf == NULL)
424 return NULL;
425
426 return g_object_ref (pixbuf);
427 }
428
429 static GdkPixbuf *
load_icon_sync(GLoadableIcon * icon,gint size)430 load_icon_sync (GLoadableIcon *icon,
431 gint size)
432 {
433 g_autoptr (GError) error = NULL;
434 g_autoptr (GInputStream) stream = g_loadable_icon_load (icon, size, NULL, NULL, &error);
435 g_autoptr (GdkPixbufLoader) loader = gdk_pixbuf_loader_new ();
436 g_autoptr (GdkPixbuf) pixbuf = NULL;
437
438 if (error) {
439 g_warning ("Failed to load icon: %s", error->message);
440 return NULL;
441 }
442
443 g_signal_connect (loader, "size-prepared",
444 G_CALLBACK (size_prepared_cb),
445 GINT_TO_POINTER (size));
446
447 pixbuf = load_from_stream (loader, stream, NULL, &error);
448
449 if (error) {
450 g_warning ("Failed to load pixbuf from GLoadableIcon: %s", error->message);
451 return NULL;
452 }
453
454 return g_steal_pointer (&pixbuf);
455 }
456
457 static void
set_class_color(HdyAvatar * self)458 set_class_color (HdyAvatar *self)
459 {
460 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
461 g_autofree GRand *rand = NULL;
462 g_autofree gchar *new_class = NULL;
463 g_autofree gchar *old_class = g_strdup_printf ("color%d", self->color_class);
464
465 gtk_style_context_remove_class (context, old_class);
466
467 if (self->text == NULL || strlen (self->text) == 0) {
468 /* Use a random color if we don't have a text */
469 rand = g_rand_new ();
470 self->color_class = g_rand_int_range (rand, 1, NUMBER_OF_COLORS);
471 } else {
472 self->color_class = (g_str_hash (self->text) % NUMBER_OF_COLORS) + 1;
473 }
474
475 new_class = g_strdup_printf ("color%d", self->color_class);
476 gtk_style_context_add_class (context, new_class);
477 }
478
479 static void
set_class_contrasted(HdyAvatar * self,gint size)480 set_class_contrasted (HdyAvatar *self,
481 gint size)
482 {
483 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
484
485 if (size < 25)
486 gtk_style_context_add_class (context, "contrasted");
487 else
488 gtk_style_context_remove_class (context, "contrasted");
489 }
490
491 static void
clear_pango_layout(HdyAvatar * self)492 clear_pango_layout (HdyAvatar *self)
493 {
494 g_clear_object (&self->layout);
495 }
496
497 static void
ensure_pango_layout(HdyAvatar * self)498 ensure_pango_layout (HdyAvatar *self)
499 {
500 g_autofree gchar *initials = NULL;
501
502 if (self->layout != NULL || self->text == NULL || strlen (self->text) == 0)
503 return;
504
505 initials = extract_initials_from_text (self->text);
506 self->layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), initials);
507 }
508
509 static void
set_font_size(HdyAvatar * self,gint size)510 set_font_size (HdyAvatar *self,
511 gint size)
512 {
513 GtkStyleContext *context;
514 PangoFontDescription *font_desc;
515 gint width, height;
516 gdouble padding;
517 gdouble sqr_size;
518 gdouble max_size;
519 gdouble new_font_size;
520
521 if (self->round_image != NULL || self->layout == NULL)
522 return;
523
524 context = gtk_widget_get_style_context (GTK_WIDGET (self));
525 gtk_style_context_get (context, gtk_style_context_get_state (context),
526 "font", &font_desc, NULL);
527
528 pango_layout_set_font_description (self->layout, font_desc);
529 pango_layout_get_pixel_size (self->layout, &width, &height);
530
531 /* This is the size of the biggest square fitting inside the circle */
532 sqr_size = (gdouble)size / 1.4142;
533 /* The padding has to be a function of the overall size.
534 * The 0.4 is how steep the linear function grows and the -5 is just
535 * an adjustment for smaller sizes which doesn't have a big impact on bigger sizes.
536 * Make also sure we don't have a negative padding */
537 padding = MAX (size * 0.4 - 5, 0);
538 max_size = sqr_size - padding;
539 new_font_size = (gdouble)height * (max_size / (gdouble)width);
540
541 font_desc = pango_font_description_copy (font_desc);
542 pango_font_description_set_absolute_size (font_desc,
543 CLAMP (new_font_size, 0, max_size) * PANGO_SCALE);
544 pango_layout_set_font_description (self->layout, font_desc);
545 pango_font_description_free (font_desc);
546 }
547
548 static void
hdy_avatar_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)549 hdy_avatar_get_property (GObject *object,
550 guint property_id,
551 GValue *value,
552 GParamSpec *pspec)
553 {
554 HdyAvatar *self = HDY_AVATAR (object);
555
556 switch (property_id) {
557 case PROP_ICON_NAME:
558 g_value_set_string (value, hdy_avatar_get_icon_name (self));
559 break;
560
561 case PROP_TEXT:
562 g_value_set_string (value, hdy_avatar_get_text (self));
563 break;
564
565 case PROP_SHOW_INITIALS:
566 g_value_set_boolean (value, hdy_avatar_get_show_initials (self));
567 break;
568
569 case PROP_SIZE:
570 g_value_set_int (value, hdy_avatar_get_size (self));
571 break;
572
573 case PROP_LOADABLE_ICON:
574 g_value_set_object (value, hdy_avatar_get_loadable_icon (self));
575 break;
576
577 default:
578 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
579 break;
580 }
581 }
582
583 static void
hdy_avatar_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)584 hdy_avatar_set_property (GObject *object,
585 guint property_id,
586 const GValue *value,
587 GParamSpec *pspec)
588 {
589 HdyAvatar *self = HDY_AVATAR (object);
590
591 switch (property_id) {
592 case PROP_ICON_NAME:
593 hdy_avatar_set_icon_name (self, g_value_get_string (value));
594 break;
595
596 case PROP_TEXT:
597 hdy_avatar_set_text (self, g_value_get_string (value));
598 break;
599
600 case PROP_SHOW_INITIALS:
601 hdy_avatar_set_show_initials (self, g_value_get_boolean (value));
602 break;
603
604 case PROP_SIZE:
605 hdy_avatar_set_size (self, g_value_get_int (value));
606 break;
607
608 case PROP_LOADABLE_ICON:
609 hdy_avatar_set_loadable_icon (self, g_value_get_object (value));
610 break;
611
612 default:
613 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
614 break;
615 }
616 }
617
618 static void
hdy_avatar_dispose(GObject * object)619 hdy_avatar_dispose (GObject *object)
620 {
621 HdyAvatar *self = HDY_AVATAR (object);
622
623 g_cancellable_cancel (self->cancellable);
624 g_clear_object (&self->icon);
625 g_clear_object (&self->load_func_icon);
626
627 G_OBJECT_CLASS (hdy_avatar_parent_class)->dispose (object);
628 }
629
630 static void
hdy_avatar_finalize(GObject * object)631 hdy_avatar_finalize (GObject *object)
632 {
633 HdyAvatar *self = HDY_AVATAR (object);
634
635 g_clear_pointer (&self->icon_name, g_free);
636 g_clear_pointer (&self->text, g_free);
637 g_clear_object (&self->round_image);
638 g_clear_object (&self->layout);
639 g_clear_object (&self->cancellable);
640
641 G_OBJECT_CLASS (hdy_avatar_parent_class)->finalize (object);
642 }
643
644 static void
draw_for_size(HdyAvatar * self,cairo_t * cr,GdkPixbuf * custom_image,gint width,gint height,gint scale_factor)645 draw_for_size (HdyAvatar *self,
646 cairo_t *cr,
647 GdkPixbuf *custom_image,
648 gint width,
649 gint height,
650 gint scale_factor)
651 {
652 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
653 gint size = MIN (width, height);
654 gdouble x = (gdouble)(width - size) / 2.0;
655 gdouble y = (gdouble)(height - size) / 2.0;
656 const gchar *icon_name;
657 GdkRGBA color;
658 g_autoptr (GtkIconInfo) icon = NULL;
659 g_autoptr (GdkPixbuf) pixbuf = NULL;
660 g_autoptr (GError) error = NULL;
661 g_autoptr (cairo_surface_t) surface = NULL;
662
663 set_class_contrasted (self, size);
664
665 if (custom_image) {
666 surface = gdk_cairo_surface_create_from_pixbuf (custom_image, scale_factor,
667 gtk_widget_get_window (GTK_WIDGET (self)));
668 gtk_render_icon_surface (context, cr, surface, x, y);
669 gtk_render_background (context, cr, x, y, size, size);
670 gtk_render_frame (context, cr, x, y, size, size);
671 return;
672 }
673
674 gtk_render_background (context, cr, x, y, size, size);
675 gtk_render_frame (context, cr, x, y, size, size);
676
677 ensure_pango_layout (self);
678
679 if (self->show_initials && self->layout != NULL) {
680 set_font_size (self, size);
681 pango_layout_get_pixel_size (self->layout, &width, &height);
682
683 gtk_render_layout (context, cr,
684 ((gdouble) (size - width) / 2.0) + x,
685 ((gdouble) (size - height) / 2.0) + y,
686 self->layout);
687 return;
688 }
689
690 icon_name = self->icon_name && *self->icon_name != '\0' ?
691 self->icon_name : "avatar-default-symbolic";
692 icon = gtk_icon_theme_lookup_icon_for_scale (gtk_icon_theme_get_default (),
693 icon_name,
694 size / 2, scale_factor,
695 GTK_ICON_LOOKUP_FORCE_SYMBOLIC);
696 if (icon == NULL) {
697 g_critical ("Failed to load icon `%s'", icon_name);
698 return;
699 }
700
701 gtk_style_context_get_color (context, gtk_style_context_get_state (context), &color);
702 pixbuf = gtk_icon_info_load_symbolic (icon, &color, NULL, NULL, NULL, NULL, &error);
703 if (error != NULL) {
704 g_critical ("Failed to load icon `%s': %s", icon_name, error->message);
705 return;
706 }
707
708 surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale_factor,
709 gtk_widget_get_window (GTK_WIDGET (self)));
710
711 width = cairo_image_surface_get_width (surface);
712 height = cairo_image_surface_get_height (surface);
713 gtk_render_icon_surface (context, cr, surface,
714 (((gdouble) size - ((gdouble) width / (gdouble) scale_factor)) / 2.0) + x,
715 (((gdouble) size - ((gdouble) height / (gdouble) scale_factor)) / 2.0) + y);
716 }
717
718 static gboolean
hdy_avatar_draw(GtkWidget * widget,cairo_t * cr)719 hdy_avatar_draw (GtkWidget *widget,
720 cairo_t *cr)
721 {
722 HdyAvatar *self = HDY_AVATAR (widget);
723 GdkPixbuf *custom_image = NULL;
724 GtkStyleContext *context = gtk_widget_get_style_context (widget);
725 gint width = gtk_widget_get_allocated_width (widget);
726 gint height = gtk_widget_get_allocated_height (widget);
727 gint scale_factor = gtk_widget_get_scale_factor (widget);
728 gint new_size = MIN (width, height) * scale_factor;
729
730 if (get_icon (self)) {
731 custom_image = update_custom_image (NULL, self->round_image, new_size);
732
733 if ((!custom_image &&
734 !self->loading_error) ||
735 (self->currently_loading_size != new_size &&
736 is_scaled (custom_image))) {
737 self->currently_loading_size = new_size;
738 g_cancellable_cancel (self->cancellable);
739 g_set_object (&self->cancellable, g_cancellable_new ());
740 load_icon_async (self,
741 new_size,
742 self->cancellable,
743 (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
744 NULL);
745 }
746
747 /* We don't want to draw a broken custom image, because it may be scaled
748 and we prefer to use the generated one in this case */
749 if (self->loading_error)
750 g_clear_object (&custom_image);
751 }
752
753 if (self->round_image && !custom_image)
754 gtk_style_context_remove_class (context, "image");
755
756 if (!self->round_image && custom_image)
757 gtk_style_context_add_class (context, "image");
758
759 g_set_object (&self->round_image, custom_image);
760 draw_for_size (self, cr, self->round_image, width, height, scale_factor);
761
762 return FALSE;
763 }
764
765 /* This private method is prefixed by the class name because it will be a
766 * virtual method in GTK 4.
767 */
768 static void
hdy_avatar_measure(GtkWidget * widget,GtkOrientation orientation,gint for_size,gint * minimum,gint * natural,gint * minimum_baseline,gint * natural_baseline)769 hdy_avatar_measure (GtkWidget *widget,
770 GtkOrientation orientation,
771 gint for_size,
772 gint *minimum,
773 gint *natural,
774 gint *minimum_baseline,
775 gint *natural_baseline)
776 {
777 HdyAvatar *self = HDY_AVATAR (widget);
778
779 if (minimum)
780 *minimum = self->size;
781 if (natural)
782 *natural = self->size;
783
784 hdy_css_measure (widget, orientation, minimum, natural);
785 }
786
787 static void
hdy_avatar_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)788 hdy_avatar_get_preferred_width (GtkWidget *widget,
789 gint *minimum,
790 gint *natural)
791 {
792 hdy_avatar_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
793 minimum, natural, NULL, NULL);
794 }
795
796 static void
hdy_avatar_get_preferred_width_for_height(GtkWidget * widget,gint height,gint * minimum,gint * natural)797 hdy_avatar_get_preferred_width_for_height (GtkWidget *widget,
798 gint height,
799 gint *minimum,
800 gint *natural)
801 {
802 hdy_avatar_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
803 minimum, natural, NULL, NULL);
804 }
805
806 static void
hdy_avatar_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)807 hdy_avatar_get_preferred_height (GtkWidget *widget,
808 gint *minimum,
809 gint *natural)
810 {
811 hdy_avatar_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
812 minimum, natural, NULL, NULL);
813 }
814
815 static void
hdy_avatar_get_preferred_height_for_width(GtkWidget * widget,gint width,gint * minimum,gint * natural)816 hdy_avatar_get_preferred_height_for_width (GtkWidget *widget,
817 gint width,
818 gint *minimum,
819 gint *natural)
820 {
821 hdy_avatar_measure (widget, GTK_ORIENTATION_VERTICAL, width,
822 minimum, natural, NULL, NULL);
823 }
824
825 static GtkSizeRequestMode
hdy_avatar_get_request_mode(GtkWidget * widget)826 hdy_avatar_get_request_mode (GtkWidget *widget)
827 {
828 return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
829 }
830
831 static void
hdy_avatar_size_allocate(GtkWidget * widget,GtkAllocation * allocation)832 hdy_avatar_size_allocate (GtkWidget *widget,
833 GtkAllocation *allocation)
834 {
835 GtkAllocation clip;
836
837 hdy_css_size_allocate_self (widget, allocation);
838 gtk_widget_set_allocation (widget, allocation);
839
840 gtk_render_background_get_clip (gtk_widget_get_style_context (widget),
841 allocation->x,
842 allocation->y,
843 allocation->width,
844 allocation->height,
845 &clip);
846
847 GTK_WIDGET_CLASS (hdy_avatar_parent_class)->size_allocate (widget, allocation);
848 gtk_widget_set_clip (widget, &clip);
849 }
850
851 static void
hdy_avatar_class_init(HdyAvatarClass * klass)852 hdy_avatar_class_init (HdyAvatarClass *klass)
853 {
854 GObjectClass *object_class = G_OBJECT_CLASS (klass);
855 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
856
857 object_class->dispose = hdy_avatar_dispose;
858 object_class->finalize = hdy_avatar_finalize;
859 object_class->set_property = hdy_avatar_set_property;
860 object_class->get_property = hdy_avatar_get_property;
861
862 widget_class->draw = hdy_avatar_draw;
863 widget_class->get_request_mode = hdy_avatar_get_request_mode;
864 widget_class->get_preferred_width = hdy_avatar_get_preferred_width;
865 widget_class->get_preferred_height = hdy_avatar_get_preferred_height;
866 widget_class->get_preferred_width_for_height = hdy_avatar_get_preferred_width_for_height;
867 widget_class->get_preferred_height_for_width = hdy_avatar_get_preferred_height_for_width;
868 widget_class->size_allocate = hdy_avatar_size_allocate;
869
870 /**
871 * HdyAvatar:size:
872 *
873 * The avatar size of the avatar.
874 */
875 props[PROP_SIZE] =
876 g_param_spec_int ("size",
877 "Size",
878 "The size of the avatar",
879 -1, INT_MAX, -1,
880 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
881
882 /**
883 * HdyAvatar:icon-name:
884 *
885 * The name of the icon in the icon theme to use when the icon should be
886 * displayed.
887 * If no name is set, the avatar-default-symbolic icon will be used.
888 * If the name doesn't match a valid icon, it is an error and no icon will be
889 * displayed.
890 * If the icon theme is changed, the image will be updated automatically.
891 *
892 * Since: 1.0
893 */
894 props[PROP_ICON_NAME] =
895 g_param_spec_string ("icon-name",
896 "Icon name",
897 "The name of the icon from the icon theme",
898 NULL,
899 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
900
901 /**
902 * HdyAvatar:text:
903 *
904 * The text used for the initials and for generating the color.
905 * If #HdyAvatar:show-initials is %FALSE it's only used to generate the color.
906 */
907 props[PROP_TEXT] =
908 g_param_spec_string ("text",
909 "Text",
910 "The text used to generate the color and the initials",
911 NULL,
912 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
913
914 /**
915 * HdyAvatar:show_initials:
916 *
917 * Whether to show the initials or the fallback icon on the generated avatar.
918 */
919 props[PROP_SHOW_INITIALS] =
920 g_param_spec_boolean ("show-initials",
921 "Show initials",
922 "Whether to show the initials",
923 FALSE,
924 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
925
926 /**
927 * HdyAvatar:loadable-icon:
928 *
929 * A #GLoadableIcon used to load the avatar.
930 *
931 * Since: 1.2
932 */
933 props[PROP_LOADABLE_ICON] =
934 g_param_spec_object ("loadable-icon",
935 "Loadable Icon",
936 "The loadable icon used to load the avatar",
937 G_TYPE_LOADABLE_ICON,
938 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
939
940 g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
941
942 gtk_widget_class_set_css_name (widget_class, "avatar");
943 }
944
945 static void
hdy_avatar_init(HdyAvatar * self)946 hdy_avatar_init (HdyAvatar *self)
947 {
948 set_class_color (self);
949 g_signal_connect (self, "screen-changed", G_CALLBACK (clear_pango_layout), NULL);
950 }
951
952 /**
953 * hdy_avatar_new:
954 * @size: The size of the avatar
955 * @text: (nullable): The text used to generate the color and initials if
956 * @show_initials is %TRUE. The color is selected at random if @text is empty.
957 * @show_initials: whether to show the initials or the fallback icon on
958 * top of the color generated based on @text.
959 *
960 * Creates a new #HdyAvatar.
961 *
962 * Returns: the newly created #HdyAvatar
963 */
964 GtkWidget *
hdy_avatar_new(gint size,const gchar * text,gboolean show_initials)965 hdy_avatar_new (gint size,
966 const gchar *text,
967 gboolean show_initials)
968 {
969 return g_object_new (HDY_TYPE_AVATAR,
970 "size", size,
971 "text", text,
972 "show-initials", show_initials,
973 NULL);
974 }
975
976 /**
977 * hdy_avatar_get_icon_name:
978 * @self: a #HdyAvatar
979 *
980 * Gets the name of the icon in the icon theme to use when the icon should be
981 * displayed.
982 *
983 * Returns: (nullable) (transfer none): the name of the icon from the icon theme.
984 *
985 * Since: 1.0
986 */
987 const gchar *
hdy_avatar_get_icon_name(HdyAvatar * self)988 hdy_avatar_get_icon_name (HdyAvatar *self)
989 {
990 g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
991
992 return self->icon_name;
993 }
994
995 /**
996 * hdy_avatar_set_icon_name:
997 * @self: a #HdyAvatar
998 * @icon_name: (nullable): the name of the icon from the icon theme
999 *
1000 * Sets the name of the icon in the icon theme to use when the icon should be
1001 * displayed.
1002 * If no name is set, the avatar-default-symbolic icon will be used.
1003 * If the name doesn't match a valid icon, it is an error and no icon will be
1004 * displayed.
1005 * If the icon theme is changed, the image will be updated automatically.
1006 *
1007 * Since: 1.0
1008 */
1009 void
hdy_avatar_set_icon_name(HdyAvatar * self,const gchar * icon_name)1010 hdy_avatar_set_icon_name (HdyAvatar *self,
1011 const gchar *icon_name)
1012 {
1013 g_return_if_fail (HDY_IS_AVATAR (self));
1014
1015 if (g_strcmp0 (self->icon_name, icon_name) == 0)
1016 return;
1017
1018 g_clear_pointer (&self->icon_name, g_free);
1019 self->icon_name = g_strdup (icon_name);
1020
1021 if (!self->round_image &&
1022 (!self->show_initials || self->layout == NULL))
1023 gtk_widget_queue_draw (GTK_WIDGET (self));
1024
1025 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
1026 }
1027
1028 /**
1029 * hdy_avatar_get_text:
1030 * @self: a #HdyAvatar
1031 *
1032 * Get the text used to generate the fallback initials and color
1033 *
1034 * Returns: (nullable) (transfer none): returns the text used to generate
1035 * the fallback initials. This is the internal string used by
1036 * the #HdyAvatar, and must not be modified.
1037 */
1038 const gchar *
hdy_avatar_get_text(HdyAvatar * self)1039 hdy_avatar_get_text (HdyAvatar *self)
1040 {
1041 g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
1042
1043 return self->text;
1044 }
1045
1046 /**
1047 * hdy_avatar_set_text:
1048 * @self: a #HdyAvatar
1049 * @text: (nullable): the text used to get the initials and color
1050 *
1051 * Set the text used to generate the fallback initials color
1052 */
1053 void
hdy_avatar_set_text(HdyAvatar * self,const gchar * text)1054 hdy_avatar_set_text (HdyAvatar *self,
1055 const gchar *text)
1056 {
1057 g_return_if_fail (HDY_IS_AVATAR (self));
1058
1059 if (g_strcmp0 (self->text, text) == 0)
1060 return;
1061
1062 g_clear_pointer (&self->text, g_free);
1063 self->text = g_strdup (text);
1064
1065 clear_pango_layout (self);
1066 set_class_color (self);
1067 gtk_widget_queue_draw (GTK_WIDGET (self));
1068
1069 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
1070 }
1071
1072 /**
1073 * hdy_avatar_get_show_initials:
1074 * @self: a #HdyAvatar
1075 *
1076 * Returns whether initials are used for the fallback or the icon.
1077 *
1078 * Returns: %TRUE if the initials are used for the fallback.
1079 */
1080 gboolean
hdy_avatar_get_show_initials(HdyAvatar * self)1081 hdy_avatar_get_show_initials (HdyAvatar *self)
1082 {
1083 g_return_val_if_fail (HDY_IS_AVATAR (self), FALSE);
1084
1085 return self->show_initials;
1086 }
1087
1088 /**
1089 * hdy_avatar_set_show_initials:
1090 * @self: a #HdyAvatar
1091 * @show_initials: whether the initials should be shown on the fallback avatar
1092 * or the icon.
1093 *
1094 * Sets whether the initials should be shown on the fallback avatar or the icon.
1095 */
1096 void
hdy_avatar_set_show_initials(HdyAvatar * self,gboolean show_initials)1097 hdy_avatar_set_show_initials (HdyAvatar *self,
1098 gboolean show_initials)
1099 {
1100 g_return_if_fail (HDY_IS_AVATAR (self));
1101
1102 if (self->show_initials == show_initials)
1103 return;
1104
1105 self->show_initials = show_initials;
1106
1107 gtk_widget_queue_draw (GTK_WIDGET (self));
1108 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_INITIALS]);
1109 }
1110
1111 /**
1112 * hdy_avatar_set_image_load_func:
1113 * @self: a #HdyAvatar
1114 * @load_image: (closure user_data) (nullable): callback to set a custom image
1115 * @user_data: (nullable): user data passed to @load_image
1116 * @destroy: (nullable): destroy notifier for @user_data
1117 *
1118 * A callback which is called when the custom image need to be reloaded for some
1119 * reason (e.g. scale-factor changes).
1120 *
1121 * Deprecated: 1.2: use hdy_avatar_set_loadable_icon() instead.
1122 */
1123 void
hdy_avatar_set_image_load_func(HdyAvatar * self,HdyAvatarImageLoadFunc load_image,gpointer user_data,GDestroyNotify destroy)1124 hdy_avatar_set_image_load_func (HdyAvatar *self,
1125 HdyAvatarImageLoadFunc load_image,
1126 gpointer user_data,
1127 GDestroyNotify destroy)
1128 {
1129 g_autoptr (HdyAvatarIcon) icon = NULL;
1130
1131 g_return_if_fail (HDY_IS_AVATAR (self));
1132 g_return_if_fail (user_data != NULL || (user_data == NULL && destroy == NULL));
1133
1134 if (load_image != NULL)
1135 icon = hdy_avatar_icon_new (load_image, user_data, destroy);
1136
1137 if (self->load_func_icon && !self->icon) {
1138 g_cancellable_cancel (self->cancellable);
1139 g_clear_object (&self->cancellable);
1140 self->currently_loading_size = -1;
1141 self->loading_error = FALSE;
1142 }
1143
1144 g_set_object (&self->load_func_icon, icon);
1145
1146 /* Don't update the custom avatar when we have a user set GLoadableIcon */
1147 if (self->icon)
1148 return;
1149
1150 if (self->load_func_icon) {
1151 gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
1152
1153 self->cancellable = g_cancellable_new ();
1154 self->currently_loading_size = self->size * scale_factor;
1155 load_icon_async (self,
1156 self->currently_loading_size,
1157 self->cancellable,
1158 (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
1159 NULL);
1160 } else {
1161 gtk_widget_queue_draw (GTK_WIDGET (self));
1162 }
1163 }
1164
1165 /**
1166 * hdy_avatar_get_size:
1167 * @self: a #HdyAvatar
1168 *
1169 * Returns the size of the avatar.
1170 *
1171 * Returns: the size of the avatar.
1172 */
1173 gint
hdy_avatar_get_size(HdyAvatar * self)1174 hdy_avatar_get_size (HdyAvatar *self)
1175 {
1176 g_return_val_if_fail (HDY_IS_AVATAR (self), 0);
1177
1178 return self->size;
1179 }
1180
1181 /**
1182 * hdy_avatar_set_size:
1183 * @self: a #HdyAvatar
1184 * @size: The size to be used for the avatar
1185 *
1186 * Sets the size of the avatar.
1187 */
1188 void
hdy_avatar_set_size(HdyAvatar * self,gint size)1189 hdy_avatar_set_size (HdyAvatar *self,
1190 gint size)
1191 {
1192 g_return_if_fail (HDY_IS_AVATAR (self));
1193 g_return_if_fail (size >= -1);
1194
1195 if (self->size == size)
1196 return;
1197
1198 self->size = size;
1199
1200 gtk_widget_queue_resize (GTK_WIDGET (self));
1201 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIZE]);
1202 }
1203
1204 /**
1205 * hdy_avatar_draw_to_pixbuf:
1206 * @self: a #HdyAvatar
1207 * @size: The size of the pixbuf
1208 * @scale_factor: The scale factor
1209 *
1210 * Renders @self into a pixbuf at @size and @scale_factor. This can be used to export the fallback avatar.
1211 *
1212 * Returns: (transfer full): the pixbuf.
1213 *
1214 * Since: 1.2
1215 */
1216 GdkPixbuf *
hdy_avatar_draw_to_pixbuf(HdyAvatar * self,gint size,gint scale_factor)1217 hdy_avatar_draw_to_pixbuf (HdyAvatar *self,
1218 gint size,
1219 gint scale_factor)
1220 {
1221 g_autoptr (cairo_surface_t) surface = NULL;
1222 g_autoptr (cairo_t) cr = NULL;
1223 g_autoptr (GdkPixbuf) custom_image = NULL;
1224 g_autoptr (GdkPixbuf) pixbuf_from_icon = NULL;
1225 gint scaled_size = size * scale_factor;
1226 GtkStyleContext *context;
1227 GtkAllocation bounds;
1228
1229 g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
1230 g_return_val_if_fail (size > 0, NULL);
1231 g_return_val_if_fail (scale_factor > 0, NULL);
1232
1233 context = gtk_widget_get_style_context (GTK_WIDGET (self));
1234 gtk_render_background_get_clip (context, 0, 0, size, size, &bounds);
1235
1236 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1237 bounds.width * scale_factor,
1238 bounds.height * scale_factor);
1239 cairo_surface_set_device_scale (surface, scale_factor, scale_factor);
1240 cr = cairo_create (surface);
1241
1242 cairo_translate (cr, -bounds.x, -bounds.y);
1243
1244 if (get_icon (self)) {
1245 /* Only used the cached round_image if it fits the size and it isn't scaled*/
1246 if (!self->round_image ||
1247 is_scaled (self->round_image) ||
1248 gdk_pixbuf_get_width (self->round_image) != scaled_size) {
1249 pixbuf_from_icon = load_icon_sync (get_icon (self), scaled_size);
1250 custom_image = update_custom_image (pixbuf_from_icon, NULL, scaled_size);
1251 gtk_style_context_add_class (context, "image");
1252 } else {
1253 custom_image = update_custom_image (NULL, self->round_image, scaled_size);
1254 }
1255 }
1256
1257 draw_for_size (self, cr, custom_image, size, size, scale_factor);
1258
1259 return gdk_pixbuf_get_from_surface (surface, 0, 0,
1260 bounds.width * scale_factor,
1261 bounds.height * scale_factor);
1262 }
1263
1264 /**
1265 * hdy_avatar_draw_to_pixbuf_async:
1266 * @self: a #HdyAvatar
1267 * @size: The size of the pixbuf
1268 * @scale_factor: The scale factor
1269 * @cancellable: (nullable): optional #GCancellable object, %NULL to ignore
1270 * @callback: (scope async): a #GAsyncReadyCallback to call when the avatar is generated
1271 * @user_data: (closure): the data to pass to callback function
1272
1273 * Renders asynchronously @self into a pixbuf at @size and @scale_factor.
1274 * This can be used to export the fallback avatar.
1275 *
1276 * Since: 1.2
1277 */
1278 void
hdy_avatar_draw_to_pixbuf_async(HdyAvatar * self,gint size,gint scale_factor,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1279 hdy_avatar_draw_to_pixbuf_async (HdyAvatar *self,
1280 gint size,
1281 gint scale_factor,
1282 GCancellable *cancellable,
1283 GAsyncReadyCallback callback,
1284 gpointer user_data)
1285 {
1286 g_autoptr (GTask) task = NULL;
1287 gint scaled_size = size * scale_factor;
1288 SizeData *data;
1289
1290 g_return_if_fail (HDY_IS_AVATAR (self));
1291 g_return_if_fail (size > 0);
1292 g_return_if_fail (scale_factor > 0);
1293
1294 data = g_slice_new (SizeData);
1295 data->size = size;
1296 data->scale_factor = scale_factor;
1297
1298 task = g_task_new (self, cancellable, callback, user_data);
1299 g_task_set_source_tag (task, hdy_avatar_draw_to_pixbuf_async);
1300 g_task_set_task_data (task, data, (GDestroyNotify) size_data_free);
1301
1302 if (get_icon (self) &&
1303 (!self->round_image ||
1304 gdk_pixbuf_get_width (self->round_image) != scaled_size ||
1305 is_scaled (self->round_image)))
1306 load_icon_async (self,
1307 scaled_size,
1308 cancellable,
1309 (GAsyncReadyCallback) load_from_gicon_async_for_export_cb,
1310 g_steal_pointer (&task));
1311 else
1312 g_task_return_pointer (task, NULL, NULL);
1313 }
1314
1315 /**
1316 * hdy_avatar_draw_to_pixbuf_finish:
1317 * @self: a #HdyAvatar
1318 * @async_result: a #GAsyncResult
1319 *
1320 * Finishes an asynchronous draw of an avatar to a pixbuf.
1321 *
1322 * Returns: (transfer full): a #GdkPixbuf
1323 *
1324 * Since: 1.2
1325 */
1326 GdkPixbuf *
hdy_avatar_draw_to_pixbuf_finish(HdyAvatar * self,GAsyncResult * async_result)1327 hdy_avatar_draw_to_pixbuf_finish (HdyAvatar *self,
1328 GAsyncResult *async_result)
1329 {
1330 GTask *task;
1331 g_autoptr (GdkPixbuf) pixbuf_from_icon = NULL;
1332 g_autoptr (GdkPixbuf) custom_image = NULL;
1333 g_autoptr (cairo_surface_t) surface = NULL;
1334 g_autoptr (cairo_t) cr = NULL;
1335 SizeData *data;
1336 GtkStyleContext *context;
1337 GtkAllocation bounds;
1338
1339 g_return_val_if_fail (G_IS_TASK (async_result), NULL);
1340
1341 task = G_TASK (async_result);
1342
1343 g_warn_if_fail (g_task_get_source_tag (task) == hdy_avatar_draw_to_pixbuf_async);
1344
1345 data = g_task_get_task_data (task);
1346
1347 context = gtk_widget_get_style_context (GTK_WIDGET (self));
1348 gtk_render_background_get_clip (context, 0, 0, data->size, data->size, &bounds);
1349
1350 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1351 bounds.width * data->scale_factor,
1352 bounds.height * data->scale_factor);
1353 cairo_surface_set_device_scale (surface, data->scale_factor, data->scale_factor);
1354 cr = cairo_create (surface);
1355
1356 cairo_translate (cr, -bounds.x, -bounds.y);
1357
1358 pixbuf_from_icon = g_task_propagate_pointer (task, NULL);
1359 custom_image = update_custom_image (pixbuf_from_icon,
1360 NULL,
1361 data->size * data->scale_factor);
1362 draw_for_size (self, cr, custom_image, data->size, data->size, data->scale_factor);
1363
1364 return gdk_pixbuf_get_from_surface (surface, 0, 0,
1365 bounds.width * data->scale_factor,
1366 bounds.height * data->scale_factor);
1367 }
1368
1369 /**
1370 * hdy_avatar_get_loadable_icon:
1371 * @self: a #HdyAvatar
1372 *
1373 * Gets the #GLoadableIcon set via hdy_avatar_set_loadable_icon().
1374 *
1375 * Returns: (nullable) (transfer none): the #GLoadableIcon
1376 *
1377 * Since: 1.2
1378 */
1379 GLoadableIcon *
hdy_avatar_get_loadable_icon(HdyAvatar * self)1380 hdy_avatar_get_loadable_icon (HdyAvatar *self)
1381 {
1382 g_return_val_if_fail (HDY_IS_AVATAR (self), NULL);
1383
1384 return self->icon;
1385 }
1386
1387 /**
1388 * hdy_avatar_set_loadable_icon:
1389 * @self: a #HdyAvatar
1390 * @icon: (nullable): a #GLoadableIcon
1391 *
1392 * Sets the #GLoadableIcon to use as an avatar.
1393 * The previous avatar is displayed till the new avatar is loaded,
1394 * to immediately remove the custom avatar set the loadable-icon to %NULL.
1395 *
1396 * The #GLoadableIcon set via this function is prefered over a set #HdyAvatarImageLoadFunc.
1397 *
1398 * Since: 1.2
1399 */
1400 void
hdy_avatar_set_loadable_icon(HdyAvatar * self,GLoadableIcon * icon)1401 hdy_avatar_set_loadable_icon (HdyAvatar *self,
1402 GLoadableIcon *icon)
1403 {
1404 g_return_if_fail (HDY_IS_AVATAR (self));
1405 g_return_if_fail (icon == NULL || G_IS_LOADABLE_ICON (icon));
1406
1407 if (icon == self->icon)
1408 return;
1409
1410 if (self->icon) {
1411 g_cancellable_cancel (self->cancellable);
1412 g_clear_object (&self->cancellable);
1413 self->currently_loading_size = -1;
1414 self->loading_error = FALSE;
1415 }
1416
1417 g_set_object (&self->icon, icon);
1418
1419 if (self->icon) {
1420 gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (self));
1421 self->currently_loading_size = self->size * scale_factor;
1422 load_icon_async (self,
1423 self->currently_loading_size,
1424 self->cancellable,
1425 (GAsyncReadyCallback) load_from_gicon_async_for_display_cb,
1426 NULL);
1427 } else {
1428 gtk_widget_queue_draw (GTK_WIDGET (self));
1429 }
1430
1431 g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOADABLE_ICON]);
1432 }
1433