[gnome-contacts] Avatar: cleaner UI.



commit cc0f37a64a331beb7de71cdf1448a1708cf9eb13
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Fri Jan 19 16:05:07 2018 +0100

    Avatar: cleaner UI.
    
    * Always clip to a circle.
    * In case there is no avatar, use a specific color for each contact and
    draw a default icon on top if it.

 data/ui/style.css                        |    2 +-
 src/contacts-avatar.vala                 |  111 +++++++++++++++++++++--------
 src/contacts-contact-list.vala           |    4 +-
 src/contacts-contact.vala                |   95 -------------------------
 src/contacts-link-suggestion-grid.vala   |    3 +-
 src/contacts-linked-accounts-dialog.vala |    4 +-
 6 files changed, 89 insertions(+), 130 deletions(-)
---
diff --git a/data/ui/style.css b/data/ui/style.css
index ea89ca8..067c6f8 100644
--- a/data/ui/style.css
+++ b/data/ui/style.css
@@ -91,6 +91,6 @@ ContactsWindow .primary-toolbar.toolbar {
   text-shadow: none;
 }
 
-.contacts-avatar-dialog .contact-display-name {
+.contacts-avatar-popover .contact-display-name {
   font-size: 20px;
 }
diff --git a/src/contacts-avatar.vala b/src/contacts-avatar.vala
index 5b0d1d6..286ff95 100644
--- a/src/contacts-avatar.vala
+++ b/src/contacts-avatar.vala
@@ -26,7 +26,12 @@ using Gee;
 public class Contacts.Avatar : DrawingArea {
   private int size;
   private Gdk.Pixbuf? pixbuf;
-  private Pango.Layout? layout;
+  private Contact? contact;
+
+  // The background color used in case of a fallback avatar
+  private Gdk.RGBA? bg_color = null;
+  // The color used for an initial or the fallback icon
+  private const Gdk.RGBA fg_color = { 0, 0, 0, 0.25 };
 
   public Avatar (int size) {
     this.size = size;
@@ -37,25 +42,27 @@ public class Contacts.Avatar : DrawingArea {
     show ();
   }
 
-  public void set_pixbuf (Gdk.Pixbuf a_pixbuf) {
-    pixbuf = Contact.frame_icon (a_pixbuf);
+  public void set_pixbuf (Gdk.Pixbuf? a_pixbuf) {
+    this.pixbuf = a_pixbuf;
     queue_draw ();
   }
 
   public void set_image (AvatarDetails? details, Contact? contact = null) {
+    this.contact = contact;
+
+    // FIXME listen for changes in the Individual's avatar
+
     Gdk.Pixbuf? a_pixbuf = null;
-    if (details != null &&
-       details.avatar != null) {
+    if (details != null && details.avatar != null) {
       try {
-       var stream = details.avatar.load (size, null);
-       a_pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
-      }
-      catch {
+        var stream = details.avatar.load (size, null);
+        a_pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
+      } catch {
       }
     }
 
     if (a_pixbuf == null) {
-      a_pixbuf = Contact.draw_fallback_avatar (size, contact);
+      a_pixbuf = null;
     }
     set_pixbuf (a_pixbuf);
   }
@@ -63,30 +70,72 @@ public class Contacts.Avatar : DrawingArea {
   public override bool draw (Cairo.Context cr) {
     cr.save ();
 
-    if (pixbuf != null) {
-      Gdk.cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
-      cr.paint();
-    }
+    if (this.pixbuf != null)
+      draw_contact_avatar (cr);
+    else // No avatar available, draw a fallback
+      draw_fallback (cr);
 
-    if (layout != null) {
-      Utils.cairo_rounded_box (cr, 0, 0, size, size, 4);
-      cr.clip ();
-
-      cr.set_source_rgba (0, 0, 0, 0.5);
-      cr.rectangle (0, size, size, 0);
-      cr.fill ();
-
-      cr.set_source_rgb (1.0, 1.0, 1.0);
-      Pango.Rectangle rect;
-      layout.get_extents (null, out rect);
-      double label_width = rect.width/(double)Pango.SCALE;
-      double label_height = rect.height / (double)Pango.SCALE;
-      cr.move_to (Math.round ((size - label_width) / 2.0),
-                 size + Math.floor ((-label_height) / 2.0));
-      Pango.cairo_show_layout (cr, layout);
-    }
     cr.restore ();
 
     return true;
   }
+
+  private void draw_contact_avatar (Cairo.Context cr) {
+    Gdk.cairo_set_source_pixbuf (cr, this.pixbuf, 0, 0);
+    // Clip with a circle
+    create_circle (cr);
+    cr.clip_preserve ();
+    cr.paint ();
+  }
+
+  private void draw_fallback (Cairo.Context cr) {
+    // The background color
+    if (this.bg_color == null)
+      calculate_color ();
+
+    // Fill the background circle
+    cr.set_source_rgb (this.bg_color.red, this.bg_color.green, this.bg_color.blue);
+    cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI);
+    create_circle (cr);
+    cr.fill_preserve ();
+
+    // Draw the icon
+    try {
+      // FIXME we can probably cache this
+      var theme = IconTheme.get_default ();
+      var fallback_avatar = theme.lookup_icon ("avatar-default",
+                                               this.size * 4 / 5,
+                                               IconLookupFlags.FORCE_SYMBOLIC);
+      var icon_pixbuf = fallback_avatar.load_symbolic (fg_color);
+      create_circle (cr);
+      cr.clip_preserve ();
+      Gdk.cairo_set_source_pixbuf (cr, icon_pixbuf, 1 + this.size / 10, 1 + this.size / 5);
+      cr.paint ();
+    } catch (Error e) {
+      warning ("Couldn't get default avatar icon: %s", e.message);
+    }
+  }
+
+  private void calculate_color () {
+    //XXX find something if this.contact == nulll or id == ""
+
+    // We use the hash of the id so we get the same color for a contact
+    var hash = str_hash (this.contact.individual.id);
+
+    var r = ((hash & 0xFF0000) >> 16) / 255.0;
+    var g = ((hash & 0x00FF00) >> 8) / 255.0;
+    var b = (hash & 0x0000FF) / 255.0;
+
+    // Make it a bit lighter by default (and since the foreground will be darker)
+    this.bg_color = Gdk.RGBA () {
+      red = (r + 2) / 3.0,
+      green = (g + 2) / 3.0,
+      blue = (b + 2) / 3.0,
+      alpha = 0
+    };
+  }
+
+  private void create_circle (Cairo.Context cr) {
+    cr.arc (this.size / 2, this.size / 2, this.size / 2, 0, 2*Math.PI);
+  }
 }
diff --git a/src/contacts-contact-list.vala b/src/contacts-contact-list.vala
index bc8868e..6bc2a25 100644
--- a/src/contacts-contact-list.vala
+++ b/src/contacts-contact-list.vala
@@ -26,6 +26,8 @@ using Gee;
  */
 public class Contacts.ContactList : ListBox {
   private class ContactDataRow : ListBoxRow {
+    private const int LIST_AVATAR_SIZE = 48;
+
     public Contact contact;
     public Label label;
     private Avatar avatar;
@@ -41,7 +43,7 @@ public class Contacts.ContactList : ListBox {
       grid.margin = 3;
       grid.margin_start = 9;
       grid.set_column_spacing (10);
-      this.avatar = new Avatar (Contact.LIST_AVATAR_SIZE);
+      this.avatar = new Avatar (LIST_AVATAR_SIZE);
 
       label = new Label ("");
       label.set_ellipsize (Pango.EllipsizeMode.END);
diff --git a/src/contacts-contact.vala b/src/contacts-contact.vala
index 08dcc12..82d2f6e 100644
--- a/src/contacts-contact.vala
+++ b/src/contacts-contact.vala
@@ -1,4 +1,3 @@
-/* -*- Mode: vala; indent-tabs-mode: t; c-basic-offset: 2; tab-width: 8 -*- */
 /*
  * Copyright (C) 2011 Alexander Larsson <alexl redhat com>
  *
@@ -26,9 +25,6 @@ public errordomain ContactError {
 }
 
 public class Contacts.Contact : GLib.Object  {
-  public const int LIST_AVATAR_SIZE = 48;
-  public const int SMALL_AVATAR_SIZE = 54;
-
   public weak Store store;
   public bool is_main;
 
@@ -38,19 +34,6 @@ public class Contacts.Contact : GLib.Object  {
 
   public Persona? fake_persona = null;
 
-  private Gdk.Pixbuf? _small_avatar;
-  public Gdk.Pixbuf small_avatar {
-    get {
-      if (_small_avatar == null) {
-       var pixbuf = load_icon (individual.avatar, SMALL_AVATAR_SIZE);
-       if (pixbuf == null)
-         pixbuf = draw_fallback_avatar (SMALL_AVATAR_SIZE, this);
-       _small_avatar = frame_icon (pixbuf);
-      }
-      return _small_avatar;
-    }
-  }
-
   public string display_name {
     get { return this.individual.display_name; }
   }
@@ -171,7 +154,6 @@ public class Contacts.Contact : GLib.Object  {
     individual.notify.disconnect(notify_cb);
     individual = new_individual;
     individual.set_data ("contact", this);
-    _small_avatar = null;
     individual.notify.connect(notify_cb);
     queue_changed (true);
   }
@@ -379,9 +361,6 @@ public class Contacts.Contact : GLib.Object  {
   }
 
   private void notify_cb (ParamSpec pspec) {
-    if (pspec.get_name () == "avatar") {
-      _small_avatar = null;
-    }
     queue_changed (false);
   }
 
@@ -430,80 +409,6 @@ public class Contacts.Contact : GLib.Object  {
     update_filter_data ();
   }
 
-  // TODO: This should be async, but the vala bindings are broken (bug #649875)
-  private Gdk.Pixbuf load_icon (LoadableIcon ?file, int size) {
-    Gdk.Pixbuf? res = null;
-    if (file != null) {
-      try {
-       Cancellable c = new Cancellable ();
-       var stream = file.load (size, null, c);
-       res = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true, c);
-      } catch (GLib.Error e) {
-       warning ("error loading avatar %s\n", e.message);
-      }
-    }
-
-    return res;
-  }
-
-  public static Gdk.Pixbuf frame_icon (Gdk.Pixbuf icon) {
-    int w = icon.get_width ();
-    int h = icon.get_height ();
-    var cst = new Cairo.ImageSurface (Cairo.Format.ARGB32, w, h);
-    var cr = new Cairo.Context (cst);
-
-    cr.set_source_rgba (0, 0, 0, 0);
-    cr.rectangle (0, 0, w, h);
-    cr.fill ();
-
-    Gdk.cairo_set_source_pixbuf (cr, icon, 0, 0);
-    Utils.cairo_rounded_box (cr,
-                            0, 0,
-                            w, h, 4);
-    cr.fill ();
-
-    return Gdk.pixbuf_get_from_surface (cst, 0, 0, w, h);
-  }
-
-  private static Gdk.Pixbuf? fallback_pixbuf_default;
-  public static Gdk.Pixbuf draw_fallback_avatar (int size, Contact? contact) {
-    if (size == SMALL_AVATAR_SIZE && fallback_pixbuf_default != null)
-      return fallback_pixbuf_default;
-
-    Gdk.Pixbuf pixbuf = null;
-    try {
-      var cst = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
-      var cr = new Cairo.Context (cst);
-
-      var pat = new Cairo.Pattern.linear (0, 0, 0, size);
-      pat.add_color_stop_rgb (0, 0.937, 0.937, 0.937);
-      pat.add_color_stop_rgb (1, 0.969, 0.969, 0.969);
-
-      cr.set_source (pat);
-      cr.paint ();
-
-      int avatar_size = (int) (size * 0.3);
-      var icon_info = IconTheme.get_default ().lookup_icon ("avatar-default-symbolic", avatar_size,
-                                                           IconLookupFlags.GENERIC_FALLBACK);
-      if (icon_info != null) {
-       Gdk.cairo_set_source_pixbuf (cr, icon_info.load_icon (), (size - avatar_size) / 2, (size - 
avatar_size) / 2);
-       cr.rectangle ((size - avatar_size) / 2, (size - avatar_size) / 2, avatar_size, avatar_size);
-       cr.fill ();
-      }
-      pixbuf = Gdk.pixbuf_get_from_surface (cst, 0, 0, size, size);
-    } catch {
-    }
-
-    if (size == SMALL_AVATAR_SIZE)
-      fallback_pixbuf_default = pixbuf;
-
-    if (pixbuf != null)
-      return pixbuf;
-
-    var cst = new Cairo.ImageSurface (Cairo.Format.ARGB32, size, size);
-    return Gdk.pixbuf_get_from_surface (cst, 0, 0, size, size);
-  }
-
   /* We claim something is "removable" if at least one persona is removable,
      that will typically unlink the rest. */
   public bool can_remove_personas () {
diff --git a/src/contacts-link-suggestion-grid.vala b/src/contacts-link-suggestion-grid.vala
index 03994ae..88cc551 100644
--- a/src/contacts-link-suggestion-grid.vala
+++ b/src/contacts-link-suggestion-grid.vala
@@ -26,6 +26,7 @@ using Gee;
  */
 [GtkTemplate (ui = "/org/gnome/Contacts/ui/contacts-link-suggestion-grid.ui")]
 public class Contacts.LinkSuggestionGrid : Grid {
+  private const int AVATAR_SIZE = 54;
 
   [GtkChild]
   private Gtk.Label description_label;
@@ -42,7 +43,7 @@ public class Contacts.LinkSuggestionGrid : Grid {
   public LinkSuggestionGrid (Contact contact) {
     get_style_context ().add_class ("contacts-suggestion");
 
-    var image_frame = new Avatar (Contact.SMALL_AVATAR_SIZE);
+    var image_frame = new Avatar (AVATAR_SIZE);
     image_frame.hexpand = false;
     image_frame.margin = 12;
     contact.keep_widget_uptodate (image_frame,  (w) => {
diff --git a/src/contacts-linked-accounts-dialog.vala b/src/contacts-linked-accounts-dialog.vala
index 95a69b4..b6b377f 100644
--- a/src/contacts-linked-accounts-dialog.vala
+++ b/src/contacts-linked-accounts-dialog.vala
@@ -20,6 +20,8 @@ using Gtk;
 using Folks;
 
 public class Contacts.LinkedAccountsDialog : Dialog {
+  private const int AVATAR_SIZE = 54;
+
   Contact contact;
   ListBox linked_accounts_view;
 
@@ -78,7 +80,7 @@ public class Contacts.LinkedAccountsDialog : Dialog {
 
       var row_grid = new Grid ();
 
-      var image_frame = new Avatar (Contact.SMALL_AVATAR_SIZE);
+      var image_frame = new Avatar (AVATAR_SIZE);
       image_frame.set_hexpand (false);
       image_frame.margin = 6;
       image_frame.margin_end = 12;


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