Commit cc0f37a6 authored by Niels De Graef's avatar Niels De Graef

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.
parent de836feb
......@@ -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;
}
......@@ -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);
}
}
......@@ -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);
......
/* -*- 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 () {
......
......@@ -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) => {
......
......@@ -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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment