From ca331e85efe2a74a6b9b5a5ff0fbcd10a36758ce Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 16 Jan 2018 16:17:42 +0100 Subject: Render avatar on demand --- main/src/ui/avatar_image.vala | 302 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 main/src/ui/avatar_image.vala (limited to 'main/src/ui/avatar_image.vala') diff --git a/main/src/ui/avatar_image.vala b/main/src/ui/avatar_image.vala new file mode 100644 index 00000000..98460a57 --- /dev/null +++ b/main/src/ui/avatar_image.vala @@ -0,0 +1,302 @@ +using Gtk; +using Dino.Entities; +using Xmpp; + +namespace Dino.Ui { + +public class AvatarImage : Misc { + public int height { get; set; default = 32; } + public int width { get; set; default = 32; } + public bool allow_gray { get; set; default = true; } + public Account account { get; private set; } + public StreamInteractor stream_interactor { get; set; } + public AvatarManager avatar_manager { owned get { return stream_interactor.get_module(AvatarManager.IDENTITY); } } + public MucManager muc_manager { owned get { return stream_interactor.get_module(MucManager.IDENTITY); } } + private Jid jid; + private string? text_only; + private bool with_plus; + private bool gray; + private Jid[] current_jids; + private Gdk.Pixbuf[] current_avatars; + + public AvatarImage() { + can_focus = false; + get_style_context().add_class("avatar"); + } + + public override void get_preferred_width(out int minimum_width, out int natural_width) { + minimum_width = width; + natural_width = width; + } + + public override void get_preferred_height(out int minimum_height, out int natural_height) { + minimum_height = height; + natural_height = height; + } + + private Cairo.Surface sub_surface(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + if (idx == -1 || current_avatars[idx] == null) { + set_source_hex_color(bufctx, gray || idx == -1 ? "555753" : Util.get_avatar_hex_color(stream_interactor, account, current_jids[idx])); + bufctx.rectangle(0, 0, width, height); + bufctx.fill(); + + string text = text_only ?? (idx == -1 ? "…" : Util.get_display_name(stream_interactor, current_jids[idx], account).get_char(0).toupper().to_string()); + bufctx.select_font_face(get_pango_context().get_font_description().get_family(), Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL); + bufctx.set_font_size(width / font_factor < 40 ? font_factor * 17 : font_factor * 25); + Cairo.TextExtents extents; + bufctx.text_extents(text, out extents); + double x_pos = width/2 - (extents.width/2 + extents.x_bearing); + double y_pos = height/2 - (extents.height/2 + extents.y_bearing); + bufctx.move_to(x_pos, y_pos); + bufctx.set_source_rgba(1, 1, 1, 1); + bufctx.show_text(text); + } else { + double w_scale = (double) width / current_avatars[idx].width; + double h_scale = (double) height / current_avatars[idx].height; + double scale = double.max(w_scale, h_scale); + bufctx.scale(scale, scale); + + double x_off = 0, y_off = 0; + if (scale == h_scale) { + x_off = (width / scale - current_avatars[idx].width) / 2.0; + } else { + y_off = (height / scale - current_avatars[idx].height) / 2.0; + } + Gdk.cairo_set_source_pixbuf(bufctx, current_avatars[idx], x_off, y_off); + bufctx.get_source().set_filter(Cairo.Filter.BEST); + bufctx.paint(); + } + return buffer; + } + + private static void set_source_hex_color(Cairo.Context ctx, string hex_color) { + ctx.set_source_rgba((double) hex_color.substring(0, 2).to_long(null, 16) / 255, + (double) hex_color.substring(2, 2).to_long(null, 16) / 255, + (double) hex_color.substring(4, 2).to_long(null, 16) / 255, + hex_color.length > 6 ? (double) hex_color.substring(6, 2).to_long(null, 16) / 255 : 1); + } + + public override bool draw(Cairo.Context ctx) { + if (text_only == null && (current_jids == null || current_avatars == null || current_jids.length == 0)) return false; + double radius = 3; + double degrees = Math.PI / 180.0; + ctx.new_sub_path(); + ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees); + ctx.arc(width - radius, height - radius, radius, 0 * degrees, 90 * degrees); + ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees); + ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees); + ctx.close_path(); + ctx.clip(); + + if (text_only != null) { + ctx.set_source_surface(sub_surface(ctx, -1, width, height), 0, 0); + ctx.paint(); + } else if (current_jids.length == 4 || with_plus) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height - 1, 2), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height - 1, 2), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 2, width - 1, height - 1, 2), 0, height + 1); + bufctx.paint(); + if (with_plus) { + bufctx.set_source_surface(sub_surface(ctx, -1, width - 1, height - 1, 2), width + 1, height + 1); + bufctx.paint(); + } else { + bufctx.set_source_surface(sub_surface(ctx, 3, width - 1, height - 1, 2), width + 1, height + 1); + bufctx.paint(); + } + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (current_jids.length == 3) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height - 1, 2), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2), width + 1, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 2, width - 1 , height - 1, 2), 0, height + 1); + bufctx.paint(); + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (current_jids.length == 2) { + Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width, height); + Cairo.Context bufctx = new Cairo.Context(buffer); + bufctx.scale(0.5, 0.5); + bufctx.set_source_surface(sub_surface(ctx, 0, width - 1, height * 2, 2), 0, 0); + bufctx.paint(); + bufctx.set_source_surface(sub_surface(ctx, 1, width - 1, height * 2, 2), width + 1, 0); + bufctx.paint(); + + ctx.set_source_surface(buffer, 0, 0); + ctx.paint(); + } else if (current_jids.length == 1) { + ctx.set_source_surface(sub_surface(ctx, 0, width, height), 0, 0); + ctx.paint(); + } else { + assert_not_reached(); + } + + if (gray) { + // convert to greyscale + ctx.set_operator(Cairo.Operator.HSL_COLOR); + ctx.set_source_rgb(1, 1, 1); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + // make the visible part more light + ctx.set_operator(Cairo.Operator.ATOP); + ctx.set_source_rgba(1, 1, 1, 0.7); + ctx.rectangle(0, 0, width, height); + ctx.fill(); + } + + return true; + } + + public override void destroy() { + if (stream_interactor != null) { + stream_interactor.get_module(PresenceManager.IDENTITY).show_received.disconnect(on_show_received); + stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.disconnect(on_received_avatar); + stream_interactor.connection_manager.connection_state_changed.disconnect(on_connection_changed); + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.disconnect(on_roster_updated); + } + } + + public void set_jid(StreamInteractor stream_interactor, Jid jid_, Account account, bool force_update = false) { + this.account = account; + if (this.stream_interactor == null) { + this.stream_interactor = stream_interactor; + stream_interactor.get_module(PresenceManager.IDENTITY).show_received.connect(on_show_received); + stream_interactor.get_module(AvatarManager.IDENTITY).received_avatar.connect(on_received_avatar); + stream_interactor.connection_manager.connection_state_changed.connect(on_connection_changed); + stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated); + } + if (muc_manager.is_groupchat(jid_, account)) { + // Groupchat + Gee.List? occupants = muc_manager.get_other_occupants(jid_, account); + jid = jid_; + if (occupants == null || occupants.size == 0) { + if (force_update || current_jids.length != 1 || !current_jids[0].equals(jid_) || gray != (allow_gray && (occupants == null || !is_self_online()))) { + set_jids(new Jid[] {jid_}, false, occupants == null || !is_self_online()); + } + } else if (occupants.size > 4) { + bool requires_update = force_update; + if (!with_plus) requires_update = true; + foreach (Jid jid in current_jids) { + if (!occupants.contains(jid)) { + requires_update = true; + } + } + if (requires_update) { + set_jids(occupants.slice(0, 3).to_array(), true); + } + } else { // 1 <= occupants.size <= 4 + bool requires_update = force_update; + if (with_plus) requires_update = true; + if (current_jids.length != occupants.size) requires_update = true; + foreach (Jid jid in current_jids) { + if (!occupants.contains(jid)) { + requires_update = true; + } + } + if (requires_update) { + set_jids(occupants.to_array(), false); + } + } + } else { + // Single user + this.jid = jid_; + if (force_update || current_jids.length != 1 || !current_jids[0].equals(jid) || gray != (allow_gray && (!is_counterpart_online(jid) || !is_self_online()))) { + set_jids(new Jid[] { jid }, false, !is_counterpart_online(jid) || !is_self_online()); + } + } + } + + private void on_show_received(Show show, Jid jid, Account account) { + if (!account.equals(this.account)) return; + if (jid.equals_bare(this.jid)) { + set_jid(stream_interactor, this.jid, account, true); + return; + } + foreach (Jid jid_ in current_jids) { + if (jid.equals_bare(jid_)) { + set_jid(stream_interactor, this.jid, account, true); + return; + } + } + } + + private void on_received_avatar(Gdk.Pixbuf avatar, Jid jid, Account account) { + if (!account.equals(this.account)) return; + if (jid.equals_bare(this.jid)) { + set_jid(stream_interactor, this.jid, account, true); + return; + } + foreach (Jid jid_ in current_jids) { + if (jid.equals_bare(jid_)) { + set_jid(stream_interactor, this.jid, account, true); + return; + } + } + } + + private void on_connection_changed(Account account, ConnectionManager.ConnectionState state) { + if (!account.equals(this.account)) return; + set_jid(stream_interactor, this.jid, account, true); + } + + private void on_roster_updated(Account account, Jid jid, Roster.Item roster_item) { + if (!account.equals(this.account)) return; + if (!jid.equals_bare(this.jid)) return; + set_jid(stream_interactor, this.jid, account, true); + } + + private bool is_self_online() { + return stream_interactor.connection_manager.get_state(account) == ConnectionManager.ConnectionState.CONNECTED; + } + + private bool is_counterpart_online(Jid counterpart) { + return stream_interactor.get_module(PresenceManager.IDENTITY).get_full_jids(counterpart, account) != null; + } + + public void set_jids(Jid[] jids, bool with_plus = false, bool gray = false) { + assert(jids.length > 0); + assert(jids.length < 5); + assert(!with_plus || jids.length == 3); + this.text_only = null; + this.gray = gray && allow_gray; + this.with_plus = with_plus; + this.current_jids = jids; + this.current_avatars = new Gdk.Pixbuf[jids.length]; + for (int i = 0; i < current_jids.length; ++i) { + Jid? real_jid = muc_manager.get_real_jid(current_jids[i], account); + if (real_jid != null) { + current_avatars[i] = avatar_manager.get_avatar(account, real_jid); + if (current_avatars[i] != null) { + current_jids[i] = real_jid; + continue; + } + } + current_avatars[i] = avatar_manager.get_avatar(account, current_jids[i]); + } + queue_draw(); + } + + public void set_text(string text, bool gray = true) { + this.text_only = text; + this.gray = gray; + this.with_plus = false; + this.current_jids = null; + this.current_avatars = null; + queue_draw(); + } +} + +} -- cgit v1.2.3-70-g09d2