adw-avatar.c 20 KB
Newer Older
Julian Sparber's avatar
Julian Sparber committed
1
2
3
4
5
6
7
8
/*
 * Copyright (C) 2020 Purism SPC
 * Copyright (C) 2020 Felipe Borges
 *
 * Authors:
 * Felipe Borges <felipeborges@gnome.org>
 * Julian Sparber <julian@sparber.net>
 *
9
 * SPDX-License-Identifier: LGPL-2.1-or-later
Julian Sparber's avatar
Julian Sparber committed
10
11
12
13
14
15
 *
 */

#include "config.h"
#include <math.h>

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
16
17
#include "adw-avatar.h"
#include "adw-gizmo-private.h"
18
#include "adw-macros-private.h"
Julian Sparber's avatar
Julian Sparber committed
19

Adrien Plazas's avatar
Adrien Plazas committed
20
#define NUMBER_OF_COLORS 14
21

Julian Sparber's avatar
Julian Sparber committed
22
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
23
24
25
26
 * AdwAvatar:
 *
 * A widget displaying an image, with a generated fallback.
 *
27
28
29
30
31
 * <picture>
 *   <source srcset="avatar-dark.png" media="(prefers-color-scheme: dark)">
 *   <img src="avatar.png" alt="avatar">
 * </picture>
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
32
33
34
 * `AdwAvatar` is a widget that shows a round avatar.
 *
 * `AdwAvatar` generates an avatar with the initials of  the
35
 * [property@Avatar:text] on top of a colored background.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
36
 *
37
 * The color is picked based on the hash of the [property@Avatar:text].
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
38
 *
39
40
41
 * If [property@Avatar:show-initials] is set to `FALSE`,
 * [property@Avatar:icon-name] or `avatar-default-symbolic` is shown instead of
 * the initials.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
42
 *
43
 * Use [property@Avatar:custom-image] to set a custom image.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
44
45
46
47
 *
 * ## CSS nodes
 *
 * `AdwAvatar` has a single CSS node with name `avatar`.
Julian Sparber's avatar
Julian Sparber committed
48
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
49
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
50
51
 */

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
52
struct _AdwAvatar
Julian Sparber's avatar
Julian Sparber committed
53
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
54
55
56
57
58
59
  GtkWidget parent_instance;

  GtkWidget *gizmo;
  GtkLabel *label;
  GtkImage *icon;
  GtkImage *custom_image;
60
  GdkPaintable *custom_image_source;
Adrien Plazas's avatar
Adrien Plazas committed
61

Christopher Davis's avatar
Christopher Davis committed
62
63
  char *icon_name;
  char *text;
Julian Sparber's avatar
Julian Sparber committed
64
65
  gboolean show_initials;
  guint color_class;
Christopher Davis's avatar
Christopher Davis committed
66
  int size;
Adrien Plazas's avatar
Adrien Plazas committed
67
};
Julian Sparber's avatar
Julian Sparber committed
68

69
G_DEFINE_FINAL_TYPE (AdwAvatar, adw_avatar, GTK_TYPE_WIDGET);
Julian Sparber's avatar
Julian Sparber committed
70
71
72

enum {
  PROP_0,
73
  PROP_ICON_NAME,
74
  PROP_TEXT,
Julian Sparber's avatar
Julian Sparber committed
75
  PROP_SHOW_INITIALS,
76
  PROP_CUSTOM_IMAGE,
Julian Sparber's avatar
Julian Sparber committed
77
78
79
80
81
  PROP_SIZE,
  PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];

Christopher Davis's avatar
Christopher Davis committed
82
83
static char *
extract_initials_from_text (const char *text)
Julian Sparber's avatar
Julian Sparber committed
84
85
{
  GString *initials;
Chun-wei Fan's avatar
Chun-wei Fan committed
86
87
  char *p = g_utf8_strup (text, -1);
  char *normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
Julian Sparber's avatar
Julian Sparber committed
88
  gunichar unichar;
Christopher Davis's avatar
Christopher Davis committed
89
  char *q = NULL;
Julian Sparber's avatar
Julian Sparber committed
90

Chun-wei Fan's avatar
Chun-wei Fan committed
91
92
  g_clear_pointer (&p, g_free);

Julian Sparber's avatar
Julian Sparber committed
93
94
95
96
97
98
99
100
101
  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, ' ');
102
103
  if (q != NULL) {
    unichar = g_utf8_get_char (g_utf8_next_char (q));
Julian Sparber's avatar
Julian Sparber committed
104

105
106
    if (unichar != 0)
      g_string_append_unichar (initials, unichar);
Julian Sparber's avatar
Julian Sparber committed
107
108
  }

Chun-wei Fan's avatar
Chun-wei Fan committed
109
110
  g_free (normalized);

Julian Sparber's avatar
Julian Sparber committed
111
112
113
  return g_string_free (initials, FALSE);
}

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
114
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
115
update_visibility (AdwAvatar *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
116
{
117
  gboolean has_custom_image = gtk_image_get_paintable (self->custom_image) != NULL;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
118
119
120
121
122
123
124
  gboolean has_initials = self->show_initials && self->text && strlen (self->text);

  gtk_widget_set_visible (GTK_WIDGET (self->label), !has_custom_image && has_initials);
  gtk_widget_set_visible (GTK_WIDGET (self->icon), !has_custom_image && !has_initials);
  gtk_widget_set_visible (GTK_WIDGET (self->custom_image), has_custom_image);
}

Julian Sparber's avatar
Julian Sparber committed
125
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
126
set_class_color (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
127
{
Chun-wei Fan's avatar
Chun-wei Fan committed
128
  char *old_class, *new_class;
Julian Sparber's avatar
Julian Sparber committed
129

Chun-wei Fan's avatar
Chun-wei Fan committed
130
  old_class = g_strdup_printf ("color%d", self->color_class);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
131
  gtk_widget_remove_css_class (self->gizmo, old_class);
Julian Sparber's avatar
Julian Sparber committed
132

Adrien Plazas's avatar
Adrien Plazas committed
133
  if (self->text == NULL || strlen (self->text) == 0) {
Julian Sparber's avatar
Julian Sparber committed
134
    /* Use a random color if we don't have a text */
Chun-wei Fan's avatar
Chun-wei Fan committed
135
136
    GRand *rand = g_rand_new ();

Adrien Plazas's avatar
Adrien Plazas committed
137
    self->color_class = g_rand_int_range (rand, 1, NUMBER_OF_COLORS);
Chun-wei Fan's avatar
Chun-wei Fan committed
138
139

    g_rand_free (rand);
Julian Sparber's avatar
Julian Sparber committed
140
  } else {
Adrien Plazas's avatar
Adrien Plazas committed
141
    self->color_class = (g_str_hash (self->text) % NUMBER_OF_COLORS) + 1;
Julian Sparber's avatar
Julian Sparber committed
142
143
  }

Adrien Plazas's avatar
Adrien Plazas committed
144
  new_class = g_strdup_printf ("color%d", self->color_class);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
145
  gtk_widget_add_css_class (self->gizmo, new_class);
Chun-wei Fan's avatar
Chun-wei Fan committed
146
147
148

  g_free (old_class);
  g_free (new_class);
Julian Sparber's avatar
Julian Sparber committed
149
150
151
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
152
update_initials (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
153
{
Chun-wei Fan's avatar
Chun-wei Fan committed
154
  char *initials;
Julian Sparber's avatar
Julian Sparber committed
155

156
  if (gtk_image_get_paintable (self->custom_image) != NULL ||
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
157
158
159
      !self->show_initials ||
      self->text == NULL ||
      strlen (self->text) == 0)
Julian Sparber's avatar
Julian Sparber committed
160
161
    return;

Adrien Plazas's avatar
Adrien Plazas committed
162
  initials = extract_initials_from_text (self->text);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
163
164

  gtk_label_set_label (self->label, initials);
Chun-wei Fan's avatar
Chun-wei Fan committed
165
166

  g_free (initials);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
167
168
169
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
170
update_icon (AdwAvatar *self)
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
171
172
173
174
175
{
  if (self->icon_name)
    gtk_image_set_from_icon_name (self->icon, self->icon_name);
  else
    gtk_image_set_from_icon_name (self->icon, "avatar-default-symbolic");
Julian Sparber's avatar
Julian Sparber committed
176
177
178
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
179
update_font_size (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
180
{
Christopher Davis's avatar
Christopher Davis committed
181
  int width, height;
Christopher Davis's avatar
Christopher Davis committed
182
183
184
185
  double padding;
  double sqr_size;
  double max_size;
  double new_font_size;
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
186
  PangoAttrList *attributes;
Julian Sparber's avatar
Julian Sparber committed
187

188
  if (gtk_image_get_paintable (self->custom_image) != NULL ||
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
189
190
191
      !self->show_initials ||
      self->text == NULL ||
      strlen (self->text) == 0)
Julian Sparber's avatar
Julian Sparber committed
192
193
    return;

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
194
195
196
  /* Reset font size first to avoid rounding errors */
  attributes = pango_attr_list_new ();
  gtk_label_set_attributes (self->label, attributes);
Julian Sparber's avatar
Julian Sparber committed
197

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
198
  pango_layout_get_pixel_size (gtk_label_get_layout (self->label), &width, &height);
Julian Sparber's avatar
Julian Sparber committed
199
200

  /* This is the size of the biggest square fitting inside the circle */
Christopher Davis's avatar
Christopher Davis committed
201
  sqr_size = (double) self->size / 1.4142;
Julian Sparber's avatar
Julian Sparber committed
202
  /* The padding has to be a function of the overall size.
203
204
205
   * 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 */
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
206
  padding = MAX (self->size * 0.4 - 5, 0);
Julian Sparber's avatar
Julian Sparber committed
207
  max_size = sqr_size - padding;
Christopher Davis's avatar
Christopher Davis committed
208
  new_font_size = (double) height * (max_size / (double) width);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
209
210
211

  pango_attr_list_change (attributes, pango_attr_size_new_absolute (CLAMP (new_font_size, 0, max_size) * PANGO_SCALE));
  gtk_label_set_attributes (self->label, attributes);
Julian Sparber's avatar
Julian Sparber committed
212

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
213
  pango_attr_list_unref (attributes);
Julian Sparber's avatar
Julian Sparber committed
214
215
216
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
217
adw_avatar_get_property (GObject    *object,
Julian Sparber's avatar
Julian Sparber committed
218
219
220
221
                         guint       property_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
222
  AdwAvatar *self = ADW_AVATAR (object);
Julian Sparber's avatar
Julian Sparber committed
223
224

  switch (property_id) {
225
  case PROP_ICON_NAME:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
226
    g_value_set_string (value, adw_avatar_get_icon_name (self));
227
228
    break;

229
  case PROP_TEXT:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
230
    g_value_set_string (value, adw_avatar_get_text (self));
Julian Sparber's avatar
Julian Sparber committed
231
232
233
    break;

  case PROP_SHOW_INITIALS:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
234
    g_value_set_boolean (value, adw_avatar_get_show_initials (self));
Julian Sparber's avatar
Julian Sparber committed
235
236
    break;

237
238
239
240
  case PROP_CUSTOM_IMAGE:
    g_value_set_object (value, adw_avatar_get_custom_image (self));
    break;

Julian Sparber's avatar
Julian Sparber committed
241
  case PROP_SIZE:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
242
    g_value_set_int (value, adw_avatar_get_size (self));
Julian Sparber's avatar
Julian Sparber committed
243
244
245
246
247
248
249
250
251
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
252
adw_avatar_set_property (GObject      *object,
Julian Sparber's avatar
Julian Sparber committed
253
254
255
256
                         guint         property_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
257
  AdwAvatar *self = ADW_AVATAR (object);
Julian Sparber's avatar
Julian Sparber committed
258
259

  switch (property_id) {
260
  case PROP_ICON_NAME:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
261
    adw_avatar_set_icon_name (self, g_value_get_string (value));
262
263
    break;

264
  case PROP_TEXT:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
265
    adw_avatar_set_text (self, g_value_get_string (value));
Julian Sparber's avatar
Julian Sparber committed
266
267
268
    break;

  case PROP_SHOW_INITIALS:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
269
    adw_avatar_set_show_initials (self, g_value_get_boolean (value));
Julian Sparber's avatar
Julian Sparber committed
270
271
    break;

272
273
274
275
  case PROP_CUSTOM_IMAGE:
    adw_avatar_set_custom_image (self, g_value_get_object (value));
    break;

Julian Sparber's avatar
Julian Sparber committed
276
  case PROP_SIZE:
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
277
    adw_avatar_set_size (self, g_value_get_int (value));
Julian Sparber's avatar
Julian Sparber committed
278
279
280
281
282
283
284
285
286
    break;

  default:
    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
    break;
  }
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
287
adw_avatar_dispose (GObject *object)
Julian Sparber's avatar
Julian Sparber committed
288
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
289
  AdwAvatar *self = ADW_AVATAR (object);
Julian Sparber's avatar
Julian Sparber committed
290

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
291
  g_clear_pointer (&self->gizmo, gtk_widget_unparent);
Julian Sparber's avatar
Julian Sparber committed
292

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
293
294
295
  self->label = NULL;
  self->icon = NULL;
  self->custom_image = NULL;
Julian Sparber's avatar
Julian Sparber committed
296

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
297
  G_OBJECT_CLASS (adw_avatar_parent_class)->dispose (object);
Julian Sparber's avatar
Julian Sparber committed
298
299
}

300
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
301
adw_avatar_finalize (GObject *object)
302
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
303
  AdwAvatar *self = ADW_AVATAR (object);
304

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
305
306
  g_clear_pointer (&self->icon_name, g_free);
  g_clear_pointer (&self->text, g_free);
307
  g_clear_object (&self->custom_image_source);
308

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
309
  G_OBJECT_CLASS (adw_avatar_parent_class)->finalize (object);
310
311
}

Julian Sparber's avatar
Julian Sparber committed
312
static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
313
adw_avatar_class_init (AdwAvatarClass *klass)
Julian Sparber's avatar
Julian Sparber committed
314
315
316
317
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
318
319
320
321
  object_class->dispose = adw_avatar_dispose;
  object_class->finalize = adw_avatar_finalize;
  object_class->set_property = adw_avatar_set_property;
  object_class->get_property = adw_avatar_get_property;
Julian Sparber's avatar
Julian Sparber committed
322

323
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
324
   * AdwAvatar:icon-name: (attributes org.gtk.Property.get=adw_avatar_get_icon_name org.gtk.Property.set=adw_avatar_set_icon_name)
325
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
326
327
328
   * The name of an icon to use as a fallback.
   *
   * If no name is set, `avatar-default-symbolic` will be used.
329
330
331
332
   *
   * Since: 1.0
   */
  props[PROP_ICON_NAME] =
333
    g_param_spec_string ("icon-name", NULL, NULL,
334
                         NULL,
335
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
336

Julian Sparber's avatar
Julian Sparber committed
337
  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
338
339
340
   * AdwAvatar:text: (attributes org.gtk.Property.get=adw_avatar_get_text org.gtk.Property.set=adw_avatar_set_text)
   *
   * Sets the text used to generate the fallback initials and color.
Julian Sparber's avatar
Julian Sparber committed
341
   *
342
343
   * It's only used to generate the color if [property@Avatar:show-initials] is
   * `FALSE`.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
344
345
   *
   * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
346
   */
347
  props[PROP_TEXT] =
348
    g_param_spec_string ("text", NULL, NULL,
349
                         "",
350
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
Julian Sparber's avatar
Julian Sparber committed
351
352

  /**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
353
   * AdwAvatar:show-initials: (attributes org.gtk.Property.get=adw_avatar_get_show_initials org.gtk.Property.set=adw_avatar_set_show_initials)
Julian Sparber's avatar
Julian Sparber committed
354
   *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
355
356
   * Whether initials are used instead of an icon on the fallback avatar.
   *
357
   * See [property@Avatar:icon-name] for how to change the fallback icon.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
358
359
   *
   * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
360
361
   */
  props[PROP_SHOW_INITIALS] =
362
    g_param_spec_boolean ("show-initials", NULL, NULL,
Julian Sparber's avatar
Julian Sparber committed
363
                          FALSE,
364
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
Julian Sparber's avatar
Julian Sparber committed
365

366
367
368
  /**
   * AdwAvatar:custom-image: (attributes org.gtk.Property.get=adw_avatar_get_custom_image org.gtk.Property.set=adw_avatar_set_custom_image)
   *
369
370
371
   * A custom image paintable.
   *
   * Custom image is displayed instead of initials or icon.
372
373
374
375
   *
   * Since: 1.0
   */
  props[PROP_CUSTOM_IMAGE] =
376
    g_param_spec_object ("custom-image", NULL, NULL,
377
                         GDK_TYPE_PAINTABLE,
378
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
379

380
381
382
383
384
385
386
387
  /**
   * AdwAvatar:size: (attributes org.gtk.Property.get=adw_avatar_get_size org.gtk.Property.set=adw_avatar_set_size)
   *
   * The size of the avatar.
   *
   * Since: 1.0
   */
  props[PROP_SIZE] =
388
    g_param_spec_int ("size", NULL, NULL,
389
                      -1, INT_MAX, -1,
390
                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
391

Julian Sparber's avatar
Julian Sparber committed
392
393
  g_object_class_install_properties (object_class, PROP_LAST_PROP, props);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
394
  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
Julian Sparber's avatar
Julian Sparber committed
395
396
397
}

static void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
398
adw_avatar_init (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
399
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
400
  self->gizmo = adw_gizmo_new ("avatar", NULL, NULL, NULL, NULL, NULL, NULL);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
  gtk_widget_set_overflow (self->gizmo, GTK_OVERFLOW_HIDDEN);
  gtk_widget_set_halign (self->gizmo, GTK_ALIGN_CENTER);
  gtk_widget_set_valign (self->gizmo, GTK_ALIGN_CENTER);
  gtk_widget_set_layout_manager (self->gizmo, gtk_bin_layout_new ());
  gtk_widget_set_parent (self->gizmo, GTK_WIDGET (self));

  self->label = GTK_LABEL (gtk_label_new (NULL));
  gtk_widget_set_parent (GTK_WIDGET (self->label), self->gizmo);

  self->icon = GTK_IMAGE (gtk_image_new ());
  gtk_widget_set_parent (GTK_WIDGET (self->icon), self->gizmo);

  self->custom_image = GTK_IMAGE (gtk_image_new ());
  gtk_widget_set_parent (GTK_WIDGET (self->custom_image), self->gizmo);

416
417
  self->text = g_strdup ("");

Julian Sparber's avatar
Julian Sparber committed
418
  set_class_color (self);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
419
420
421
422
423
424
  update_initials (self);
  update_font_size (self);
  update_icon (self);
  update_visibility (self);

  g_signal_connect (self, "notify::root", G_CALLBACK (update_font_size), NULL);
Julian Sparber's avatar
Julian Sparber committed
425
426
427
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
428
 * adw_avatar_new:
Julian Sparber's avatar
Julian Sparber committed
429
 * @size: The size of the avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
430
431
 * @text: (nullable): the text used to get the initials and color
 * @show_initials: whether to use initials instead of an icon as fallback
Julian Sparber's avatar
Julian Sparber committed
432
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
433
 * Creates a new `AdwAvatar`.
Julian Sparber's avatar
Julian Sparber committed
434
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
435
 * Returns: the newly created `AdwAvatar`
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
436
437
 *
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
438
439
 */
GtkWidget *
Christopher Davis's avatar
Christopher Davis committed
440
441
442
adw_avatar_new (int         size,
                const char *text,
                gboolean    show_initials)
Julian Sparber's avatar
Julian Sparber committed
443
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
444
  return g_object_new (ADW_TYPE_AVATAR,
Julian Sparber's avatar
Julian Sparber committed
445
446
447
448
449
450
                       "size", size,
                       "text", text,
                       "show-initials", show_initials,
                       NULL);
}

451
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
452
 * adw_avatar_get_icon_name: (attributes org.gtk.Method.get_property=icon-name)
Maximiliano's avatar
Maximiliano committed
453
 * @self: an avatar
454
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
455
 * Gets the name of an icon to use as a fallback.
456
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
457
 * Returns: (nullable): the icon name
458
459
460
 *
 * Since: 1.0
 */
Christopher Davis's avatar
Christopher Davis committed
461
const char *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
462
adw_avatar_get_icon_name (AdwAvatar *self)
463
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
464
  g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
465
466
467
468
469

  return self->icon_name;
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
470
 * adw_avatar_set_icon_name: (attributes org.gtk.Method.set_property=icon-name)
Maximiliano's avatar
Maximiliano committed
471
 * @self: an avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
472
 * @icon_name: (nullable): the icon name
473
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
474
475
476
 * Sets the name of an icon to use as a fallback.
 *
 * If no name is set, `avatar-default-symbolic` will be used.
477
478
479
480
 *
 * Since: 1.0
 */
void
Christopher Davis's avatar
Christopher Davis committed
481
482
adw_avatar_set_icon_name (AdwAvatar  *self,
                          const char *icon_name)
483
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
484
  g_return_if_fail (ADW_IS_AVATAR (self));
485
486
487
488
489
490
491

  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);

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
492
  update_icon (self);
493
494
495
496

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_NAME]);
}

Julian Sparber's avatar
Julian Sparber committed
497
/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
498
 * adw_avatar_get_text: (attributes org.gtk.Method.get_property=text)
Maximiliano's avatar
Maximiliano committed
499
 * @self: an avatar
Julian Sparber's avatar
Julian Sparber committed
500
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
501
 * Gets the text used to generate the fallback initials and color.
Julian Sparber's avatar
Julian Sparber committed
502
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
503
504
 * Returns: (nullable): the text used to generate the fallback initials and
 *   color
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
505
506
 *
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
507
 */
Christopher Davis's avatar
Christopher Davis committed
508
const char *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
509
adw_avatar_get_text (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
510
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
511
  g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
Julian Sparber's avatar
Julian Sparber committed
512

Adrien Plazas's avatar
Adrien Plazas committed
513
  return self->text;
Julian Sparber's avatar
Julian Sparber committed
514
515
516
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
517
 * adw_avatar_set_text: (attributes org.gtk.Method.set_property=text)
Maximiliano's avatar
Maximiliano committed
518
 * @self: an avatar
519
 * @text: (nullable): the text used to get the initials and color
Julian Sparber's avatar
Julian Sparber committed
520
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
521
 * Sets the text used to generate the fallback initials and color.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
522
 *
523
524
525
 * It's only used to generate the color if [property@Avatar:show-initials] is
 * `FALSE`.
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
526
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
527
528
 */
void
Christopher Davis's avatar
Christopher Davis committed
529
530
adw_avatar_set_text (AdwAvatar  *self,
                     const char *text)
Julian Sparber's avatar
Julian Sparber committed
531
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
532
  g_return_if_fail (ADW_IS_AVATAR (self));
Julian Sparber's avatar
Julian Sparber committed
533

Adrien Plazas's avatar
Adrien Plazas committed
534
  if (g_strcmp0 (self->text, text) == 0)
Julian Sparber's avatar
Julian Sparber committed
535
536
    return;

Adrien Plazas's avatar
Adrien Plazas committed
537
  g_clear_pointer (&self->text, g_free);
538
  self->text = g_strdup (text ? text : "");
Julian Sparber's avatar
Julian Sparber committed
539
540

  set_class_color (self);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
541
542
543
544

  update_initials (self);
  update_font_size (self);
  update_visibility (self);
Julian Sparber's avatar
Julian Sparber committed
545

546
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TEXT]);
Julian Sparber's avatar
Julian Sparber committed
547
548
549
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
550
 * adw_avatar_get_show_initials: (attributes org.gtk.Method.get_property=show-initials)
Maximiliano's avatar
Maximiliano committed
551
 * @self: an avatar
Julian Sparber's avatar
Julian Sparber committed
552
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
553
 * Gets whether initials are used instead of an icon on the fallback avatar.
Julian Sparber's avatar
Julian Sparber committed
554
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
555
 * Returns: whether initials are used instead of an icon as fallback
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
556
557
 *
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
558
559
 */
gboolean
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
560
adw_avatar_get_show_initials (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
561
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
562
  g_return_val_if_fail (ADW_IS_AVATAR (self), FALSE);
Julian Sparber's avatar
Julian Sparber committed
563

Adrien Plazas's avatar
Adrien Plazas committed
564
  return self->show_initials;
Julian Sparber's avatar
Julian Sparber committed
565
566
567
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
568
 * adw_avatar_set_show_initials: (attributes org.gtk.Method.set_property=show-initials)
Maximiliano's avatar
Maximiliano committed
569
 * @self: an avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
570
 * @show_initials: whether to use initials instead of an icon as fallback
Julian Sparber's avatar
Julian Sparber committed
571
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
572
 * Sets whether to use initials instead of an icon on the fallback avatar.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
573
 *
574
575
 * See [property@Avatar:icon-name] for how to change the fallback icon.
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
576
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
577
578
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
579
adw_avatar_set_show_initials (AdwAvatar *self,
Julian Sparber's avatar
Julian Sparber committed
580
581
                              gboolean   show_initials)
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
582
  g_return_if_fail (ADW_IS_AVATAR (self));
Julian Sparber's avatar
Julian Sparber committed
583

Adrien Plazas's avatar
Adrien Plazas committed
584
  if (self->show_initials == show_initials)
Julian Sparber's avatar
Julian Sparber committed
585
586
    return;

Adrien Plazas's avatar
Adrien Plazas committed
587
  self->show_initials = show_initials;
Julian Sparber's avatar
Julian Sparber committed
588

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
589
590
591
592
  update_initials (self);
  update_font_size (self);
  update_visibility (self);

Julian Sparber's avatar
Julian Sparber committed
593
594
595
596
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_INITIALS]);
}

/**
597
 * adw_avatar_get_custom_image: (attributes org.gtk.Method.get_property=custom-image)
Maximiliano's avatar
Maximiliano committed
598
 * @self: an avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
599
 *
600
 * Gets the custom image paintable.
Julian Sparber's avatar
Julian Sparber committed
601
 *
602
603
604
605
606
607
608
609
610
 * Returns: (nullable) (transfer none): the custom image
 *
 * Since: 1.0
 */
GdkPaintable *
adw_avatar_get_custom_image (AdwAvatar *self)
{
  g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);

611
  return self->custom_image_source;
612
613
614
615
}

/**
 * adw_avatar_set_custom_image: (attributes org.gtk.Method.set_property=custom-image)
Maximiliano's avatar
Maximiliano committed
616
 * @self: an avatar
617
618
619
 * @custom_image: (nullable) (transfer none): a custom image
 *
 * Sets the custom image paintable.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
620
 *
621
622
 * Custom image is displayed instead of initials or icon.
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
623
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
624
625
 */
void
626
627
adw_avatar_set_custom_image (AdwAvatar    *self,
                             GdkPaintable *custom_image)
Julian Sparber's avatar
Julian Sparber committed
628
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
629
  g_return_if_fail (ADW_IS_AVATAR (self));
630
  g_return_if_fail (custom_image == NULL || GDK_IS_PAINTABLE (custom_image));
631

632
  if (self->custom_image_source == custom_image)
633
    return;
Julian Sparber's avatar
Julian Sparber committed
634

635
636
637
638
639
640
641
642
643
644
  g_set_object (&self->custom_image_source, custom_image);

  if (custom_image) {
    int height = gdk_paintable_get_intrinsic_height (custom_image);
    int width = gdk_paintable_get_intrinsic_width (custom_image);

    if (height == width) {
      gtk_image_set_from_paintable (self->custom_image, custom_image);
    } else {
      GtkSnapshot *snapshot = gtk_snapshot_new ();
Chun-wei Fan's avatar
Chun-wei Fan committed
645
      GdkPaintable *square_image;
646
647
648
649
650
651
652
      int size = MIN (width, height);

      gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT ((size - width) / 2.f, (size - height) / 2.f));
      gdk_paintable_snapshot (custom_image, snapshot, width, height);

      square_image = gtk_snapshot_free_to_paintable (snapshot, &GRAPHENE_SIZE_INIT (size, size));
      gtk_image_set_from_paintable (self->custom_image, square_image);
Chun-wei Fan's avatar
Chun-wei Fan committed
653
654

      g_object_unref (square_image);
655
    }
Julian Sparber's avatar
Julian Sparber committed
656

657
    gtk_widget_add_css_class (self->gizmo, "image");
658
659
  } else {
    gtk_image_set_from_paintable (self->custom_image, NULL);
660
    gtk_widget_remove_css_class (self->gizmo, "image");
661
  }
Julian Sparber's avatar
Julian Sparber committed
662

663
  update_initials (self);
664
665
666
  update_visibility (self);

  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_CUSTOM_IMAGE]);
Julian Sparber's avatar
Julian Sparber committed
667
668
669
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
670
 * adw_avatar_get_size: (attributes org.gtk.Method.get_property=size)
Maximiliano's avatar
Maximiliano committed
671
 * @self: an avatar
Julian Sparber's avatar
Julian Sparber committed
672
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
673
 * Gets the size of the avatar.
Julian Sparber's avatar
Julian Sparber committed
674
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
675
 * Returns: the size of the avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
676
677
 *
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
678
 */
Christopher Davis's avatar
Christopher Davis committed
679
int
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
680
adw_avatar_get_size (AdwAvatar *self)
Julian Sparber's avatar
Julian Sparber committed
681
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
682
  g_return_val_if_fail (ADW_IS_AVATAR (self), 0);
Julian Sparber's avatar
Julian Sparber committed
683

Adrien Plazas's avatar
Adrien Plazas committed
684
  return self->size;
Julian Sparber's avatar
Julian Sparber committed
685
686
687
}

/**
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
688
 * adw_avatar_set_size: (attributes org.gtk.Method.set_property=size)
Maximiliano's avatar
Maximiliano committed
689
 * @self: an avatar
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
690
 * @size: The size of the avatar
Julian Sparber's avatar
Julian Sparber committed
691
692
 *
 * Sets the size of the avatar.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
693
694
 *
 * Since: 1.0
Julian Sparber's avatar
Julian Sparber committed
695
696
 */
void
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
697
adw_avatar_set_size (AdwAvatar *self,
Christopher Davis's avatar
Christopher Davis committed
698
                     int        size)
Julian Sparber's avatar
Julian Sparber committed
699
{
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
700
  g_return_if_fail (ADW_IS_AVATAR (self));
Julian Sparber's avatar
Julian Sparber committed
701
702
  g_return_if_fail (size >= -1);

Adrien Plazas's avatar
Adrien Plazas committed
703
  if (self->size == size)
Julian Sparber's avatar
Julian Sparber committed
704
705
    return;

Adrien Plazas's avatar
Adrien Plazas committed
706
  self->size = size;
Julian Sparber's avatar
Julian Sparber committed
707

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
708
709
710
711
712
713
714
715
716
717
  gtk_widget_set_size_request (self->gizmo, size, size);
  gtk_image_set_pixel_size (self->icon, size / 2);

  if (size < 25)
    gtk_widget_add_css_class (self->gizmo, "contrasted");
  else
    gtk_widget_remove_css_class (self->gizmo, "contrasted");

  update_font_size (self);

Julian Sparber's avatar
Julian Sparber committed
718
719
720
  gtk_widget_queue_resize (GTK_WIDGET (self));
  g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SIZE]);
}
721
722

/**
723
 * adw_avatar_draw_to_texture:
Maximiliano's avatar
Maximiliano committed
724
 * @self: an avatar
725
726
 * @scale_factor: The scale factor
 *
727
 * Renders @self into a [class@Gdk.Texture] at @scale_factor.
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
728
729
 *
 * This can be used to export the fallback avatar.
730
 *
731
 * Returns: (transfer full): the texture
732
 *
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
733
 * Since: 1.0
734
 */
735
736
737
GdkTexture *
adw_avatar_draw_to_texture (AdwAvatar *self,
                            int        scale_factor)
738
{
Chun-wei Fan's avatar
Chun-wei Fan committed
739
740
  GdkTexture *result;
  GskRenderNode *node;
741
742
743
744
  GtkSnapshot *snapshot;
  GtkNative *native;
  GskRenderer *renderer;
  int size;
745
  graphene_matrix_t transform;
746

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
747
  g_return_val_if_fail (ADW_IS_AVATAR (self), NULL);
748
749
  g_return_val_if_fail (scale_factor > 0, NULL);

750
751
  size = self->size * scale_factor;

752
753
754
755
  g_assert (gtk_widget_compute_transform (GTK_WIDGET (self),
                                          self->gizmo,
                                          &transform));

Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
756
  snapshot = gtk_snapshot_new ();
757
  gtk_snapshot_scale (snapshot, scale_factor, scale_factor);
758
  gtk_snapshot_transform_matrix (snapshot, &transform);
Alexander Mikhaylenko's avatar
Alexander Mikhaylenko committed
759
760
761
762
  GTK_WIDGET_GET_CLASS (self)->snapshot (GTK_WIDGET (self), snapshot);

  node = gtk_snapshot_free_to_node (snapshot);

763
764
  native = gtk_widget_get_native (GTK_WIDGET (self));
  renderer = gtk_native_get_renderer (native);
765

Chun-wei Fan's avatar
Chun-wei Fan committed
766
767
768
769
770
  result = gsk_renderer_render_texture (renderer, node, &GRAPHENE_RECT_INIT (0, 0, size, size));

  gsk_render_node_unref (node);

  return result;
771
}