aboutsummaryrefslogtreecommitdiff
path: root/main/src/ui/widgets/avatar_picture.vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2023-01-24 18:59:46 +0100
committerMarvin W <git@larma.de>2023-03-05 16:47:46 +0100
commitdb3b0d5f233ee3587ae54f8f035222cb098b11dd (patch)
tree587e5b21a92c8482b25338556d3ef98c764c8ebb /main/src/ui/widgets/avatar_picture.vala
parentd81829652057d63b9971b9217996438ee41788ca (diff)
downloaddino-db3b0d5f233ee3587ae54f8f035222cb098b11dd.tar.gz
dino-db3b0d5f233ee3587ae54f8f035222cb098b11dd.zip
New Avatar UI
Diffstat (limited to 'main/src/ui/widgets/avatar_picture.vala')
-rw-r--r--main/src/ui/widgets/avatar_picture.vala519
1 files changed, 519 insertions, 0 deletions
diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala
new file mode 100644
index 00000000..e632413c
--- /dev/null
+++ b/main/src/ui/widgets/avatar_picture.vala
@@ -0,0 +1,519 @@
+using Dino.Entities;
+using Gtk;
+using Xmpp;
+
+public class Dino.Ui.ViewModel.AvatarPictureTileModel : Object {
+ public string display_text { get; set; }
+ public Gdk.RGBA background_color { get; set; }
+ public File? image_file { get; set; }
+}
+
+public class Dino.Ui.ViewModel.AvatarPictureModel : Object {
+ public ListModel tiles { get; set; }
+}
+
+public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : AvatarPictureTileModel {
+ private StreamInteractor stream_interactor;
+ private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
+ private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
+ private Conversation? conversation;
+ private Jid? primary_avatar_jid;
+ private Jid? secondary_avatar_jid;
+ private Jid? display_name_jid;
+
+ public ConversationParticipantAvatarPictureTileModel(StreamInteractor stream_interactor, Conversation conversation, Jid jid) {
+ this.stream_interactor = stream_interactor;
+ this.conversation = conversation;
+ this.primary_avatar_jid = jid;
+ this.display_name_jid = jid;
+
+ string color_id = jid.to_string();
+ if (conversation.type_ != Conversation.Type.CHAT && primary_avatar_jid.equals_bare(conversation.counterpart)) {
+ Jid? real_jid = muc_manager.get_real_jid(primary_avatar_jid, conversation.account);
+ if (real_jid != null && muc_manager.is_private_room(conversation.account, conversation.counterpart.bare_jid)) {
+ secondary_avatar_jid = primary_avatar_jid;
+ primary_avatar_jid = real_jid.bare_jid;
+ color_id = primary_avatar_jid.to_string();
+ } else {
+ color_id = jid.resourcepart.to_string();
+ }
+ } else if (conversation.type_ == Conversation.Type.CHAT) {
+ primary_avatar_jid = jid.bare_jid;
+ color_id = primary_avatar_jid.to_string();
+ }
+ string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
+ display_text = display.get_char(0).toupper().to_string();
+ stream_interactor.get_module(RosterManager.IDENTITY).updated_roster_item.connect(on_roster_updated);
+
+ float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
+ background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f};
+
+ update_image_file();
+ avatar_manager.received_avatar.connect(on_received_avatar);
+ avatar_manager.fetched_avatar.connect(on_received_avatar);
+ }
+
+ private void update_image_file() {
+ File image_file = avatar_manager.get_avatar_file(conversation.account, primary_avatar_jid);
+ if (image_file == null && secondary_avatar_jid != null) {
+ image_file = avatar_manager.get_avatar_file(conversation.account, secondary_avatar_jid);
+ }
+ this.image_file = image_file;
+ }
+
+ private void on_received_avatar(Jid jid, Account account) {
+ if (account.equals(conversation.account) && (jid.equals(primary_avatar_jid) || jid.equals(secondary_avatar_jid))) {
+ update_image_file();
+ }
+ }
+
+ private void on_roster_updated(Account account, Jid jid) {
+ if (account.equals(conversation.account) && jid.equals(display_name_jid)) {
+ string display = Util.get_participant_display_name(stream_interactor, conversation, display_name_jid);
+ display_text = display.get_char(0).toupper().to_string();
+ }
+ }
+}
+
+public class Dino.Ui.ViewModel.CompatAvatarPictureModel : AvatarPictureModel {
+ private StreamInteractor stream_interactor;
+ private AvatarManager? avatar_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(AvatarManager.IDENTITY); } }
+ private MucManager? muc_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(MucManager.IDENTITY); } }
+ private PresenceManager? presence_manager { owned get { return stream_interactor == null ? null : stream_interactor.get_module(PresenceManager.IDENTITY); } }
+ private ConnectionManager? connection_manager { owned get { return stream_interactor == null ? null : stream_interactor.connection_manager; } }
+ private Conversation? conversation;
+
+ construct {
+ tiles = new GLib.ListStore(typeof(ViewModel.AvatarPictureTileModel));
+ }
+
+ public CompatAvatarPictureModel(StreamInteractor? stream_interactor) {
+ this.stream_interactor = stream_interactor;
+ if (stream_interactor != null) {
+ connect_signals_weak(this);
+ }
+ }
+
+ private static void connect_signals_weak(CompatAvatarPictureModel model_) {
+ WeakRef model_weak = WeakRef(model_);
+ ulong muc_manager_private_room_occupant_updated_handler_id = 0;
+ ulong muc_manager_proom_info_updated_handler_id = 0;
+ ulong avatar_manager_received_avatar_handler_id = 0;
+ ulong avatar_manager_fetched_avatar_handler_id = 0;
+ muc_manager_private_room_occupant_updated_handler_id = model_.muc_manager.private_room_occupant_updated.connect((muc_manager, account, room, jid) => {
+ CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
+ if (model != null) {
+ model.on_room_updated(account, room);
+ } else if (muc_manager_private_room_occupant_updated_handler_id != 0) {
+ muc_manager.disconnect(muc_manager_private_room_occupant_updated_handler_id);
+ muc_manager_private_room_occupant_updated_handler_id = 0;
+ }
+ });
+ muc_manager_proom_info_updated_handler_id = model_.muc_manager.room_info_updated.connect((muc_manager, account, room) => {
+ CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
+ if (model != null) {
+ model.on_room_updated(account, room);
+ } else if (muc_manager_proom_info_updated_handler_id != 0) {
+ muc_manager.disconnect(muc_manager_proom_info_updated_handler_id);
+ muc_manager_proom_info_updated_handler_id = 0;
+ }
+ });
+ avatar_manager_received_avatar_handler_id = model_.avatar_manager.received_avatar.connect((avatar_manager, jid, account) => {
+ CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
+ if (model != null) {
+ model.on_received_avatar(jid, account);
+ } else if (avatar_manager_received_avatar_handler_id != 0) {
+ avatar_manager.disconnect(avatar_manager_received_avatar_handler_id);
+ avatar_manager_received_avatar_handler_id = 0;
+ }
+ });
+ avatar_manager_fetched_avatar_handler_id = model_.avatar_manager.fetched_avatar.connect((avatar_manager, jid, account) => {
+ CompatAvatarPictureModel? model = (CompatAvatarPictureModel) model_weak.get();
+ if (model != null) {
+ model.on_received_avatar(jid, account);
+ } else if (avatar_manager_fetched_avatar_handler_id != 0) {
+ avatar_manager.disconnect(avatar_manager_fetched_avatar_handler_id);
+ avatar_manager_fetched_avatar_handler_id = 0;
+ }
+ });
+ }
+
+ private void on_room_updated(Account account, Jid room) {
+ if (conversation != null && account.equals(conversation.account) && conversation.counterpart.equals_bare(room)) {
+ reset();
+ set_conversation(conversation);
+ }
+ }
+
+ private void on_received_avatar(Jid jid, Account account) {
+ on_room_updated(account, jid);
+ }
+
+ public void reset() {
+ (tiles as GLib.ListStore).remove_all();
+ }
+
+ public CompatAvatarPictureModel set_conversation(Conversation conversation) {
+ if (stream_interactor == null) {
+ critical("set_conversation() used on CompatAvatarPictureModel without stream_interactor");
+ return this;
+ }
+ this.conversation = conversation;
+ if (conversation.type_ == Conversation.Type.GROUPCHAT) {
+ if (avatar_manager.has_avatar(conversation.account, conversation.counterpart)) {
+ add_internal("#", conversation.counterpart.to_string(), avatar_manager.get_avatar_file(conversation.account, conversation.counterpart));
+ } else {
+ Gee.List<Jid>? occupants = muc_manager.get_other_offline_members(conversation.counterpart, conversation.account);
+ if (occupants != null && !occupants.is_empty && muc_manager.is_private_room(conversation.account, conversation.counterpart)) {
+ int count = occupants.size > 4 ? 3 : occupants.size;
+ for (int i = 0; i < count; i++) {
+ add_participant(conversation, occupants[i]);
+ }
+ if (occupants.size > 4) {
+ add_internal("+");
+ }
+ } else {
+ add_internal("#", conversation.counterpart.to_string());
+ }
+ }
+ } else {
+ add_participant(conversation, conversation.counterpart);
+ }
+ return this;
+ }
+
+ public CompatAvatarPictureModel add_participant(Conversation conversation, Jid jid) {
+ if (stream_interactor == null) {
+ critical("add_participant() used on CompatAvatarPictureModel without stream_interactor");
+ return this;
+ }
+ (tiles as GLib.ListStore).append(new ConversationParticipantAvatarPictureTileModel(stream_interactor, conversation, jid));
+ return this;
+ }
+
+ public CompatAvatarPictureModel add(string display, string? color_id = null, File? image_file = null) {
+ add_internal(display, color_id, image_file);
+ return this;
+ }
+
+ private AvatarPictureTileModel add_internal(string display, string? color_id = null, File? image_file = null) {
+ GLib.ListStore store = tiles as GLib.ListStore;
+ float[] rgbf = color_id != null ? Xep.ConsistentColor.string_to_rgbf(color_id) : new float[] {0.5f, 0.5f, 0.5f};
+ var model = new ViewModel.AvatarPictureTileModel() {
+ display_text = display.get_char(0).toupper().to_string(),
+ background_color = Gdk.RGBA() { red = rgbf[0], green = rgbf[1], blue = rgbf[2], alpha = 1.0f},
+ image_file = image_file
+ };
+ store.append(model);
+ return model;
+ }
+}
+
+
+public class Dino.Ui.CompatAvatarDrawer {
+ public float radius_percent { get; set; default = 0.2f; }
+ public ViewModel.AvatarPictureModel? model { get; set; }
+ public int height_request { get; set; default = 35; }
+ public int width_request { get; set; default = 35; }
+ public string font_family { get; set; default = "Sans"; }
+
+ public Cairo.ImageSurface draw_image_surface() {
+ Cairo.ImageSurface surface = new Cairo.ImageSurface(Cairo.Format.ARGB32, width_request, height_request);
+ draw_on_context(new Cairo.Context(surface));
+ return surface;
+ }
+
+ public void draw_on_context(Cairo.Context ctx) {
+ double radius = (width_request + height_request) * 0.25f * radius_percent;
+ double degrees = Math.PI / 180.0;
+ ctx.new_sub_path();
+ ctx.arc(width_request - radius, radius, radius, -90 * degrees, 0 * degrees);
+ ctx.arc(width_request - radius, height_request - radius, radius, 0 * degrees, 90 * degrees);
+ ctx.arc(radius, height_request - radius, radius, 90 * degrees, 180 * degrees);
+ ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees);
+ ctx.close_path();
+ ctx.clip();
+
+ if (this.model.tiles.get_n_items() == 4) {
+ Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
+ Cairo.Context bufctx = new Cairo.Context(buffer);
+ bufctx.scale(0.5, 0.5);
+ bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request - 1, 2), width_request + 1, 0);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, height_request + 1);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 3, width_request - 1, height_request - 1, 2), width_request + 1, height_request + 1);
+ bufctx.paint();
+
+ ctx.set_source_surface(buffer, 0, 0);
+ ctx.paint();
+ } else if (this.model.tiles.get_n_items() == 3) {
+ Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
+ Cairo.Context bufctx = new Cairo.Context(buffer);
+ bufctx.scale(0.5, 0.5);
+ bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request - 1, 2), 0, 0);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 2, width_request - 1, height_request - 1, 2), 0, width_request + 1);
+ bufctx.paint();
+
+ ctx.set_source_surface(buffer, 0, 0);
+ ctx.paint();
+ } else if (this.model.tiles.get_n_items() == 2) {
+ Cairo.Surface buffer = new Cairo.Surface.similar(ctx.get_target(), Cairo.Content.COLOR_ALPHA, width_request, height_request);
+ Cairo.Context bufctx = new Cairo.Context(buffer);
+ bufctx.scale(0.5, 0.5);
+ bufctx.set_source_surface(sub_surface_idx(ctx, 0, width_request - 1, height_request * 2, 2), 0, 0);
+ bufctx.paint();
+ bufctx.set_source_surface(sub_surface_idx(ctx, 1, width_request - 1, height_request * 2, 2), width_request + 1, 0);
+ bufctx.paint();
+
+ ctx.set_source_surface(buffer, 0, 0);
+ ctx.paint();
+ } else if (this.model.tiles.get_n_items() == 1) {
+ ctx.set_source_surface(sub_surface_idx(ctx, 0, width_request, height_request, 1), 0, 0);
+ ctx.paint();
+ } else if (this.model.tiles.get_n_items() == 0) {
+ ctx.set_source_surface(sub_surface_idx(ctx, -1, width_request, height_request, 1), 0, 0);
+ ctx.paint();
+ }
+ ctx.set_source_rgb(0, 0, 0);
+ }
+
+ private Cairo.Surface sub_surface_idx(Cairo.Context ctx, int idx, int width, int height, int font_factor = 1) {
+ ViewModel.AvatarPictureTileModel tile = (ViewModel.AvatarPictureTileModel) this.model.tiles.get_item(idx);
+ Gdk.Pixbuf? avatar = new Gdk.Pixbuf.from_file(tile.image_file.get_path());
+ string? name = idx >= 0 ? tile.display_text : "";
+ Gdk.RGBA hex_color = tile.background_color;
+ return sub_surface(ctx, font_family, avatar, name, hex_color, width, height, font_factor);
+ }
+
+ private static Cairo.Surface sub_surface(Cairo.Context ctx, string font_family, Gdk.Pixbuf? avatar, string? name, Gdk.RGBA background_color, 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 (avatar == null) {
+ Gdk.cairo_set_source_rgba(bufctx, background_color);
+ bufctx.rectangle(0, 0, width, height);
+ bufctx.fill();
+
+ string text = name == null ? "…" : name.get_char(0).toupper().to_string();
+ bufctx.select_font_face(font_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 / avatar.width;
+ double h_scale = (double) height / avatar.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 - avatar.width) / 2.0;
+ } else {
+ y_off = (height / scale - avatar.height) / 2.0;
+ }
+
+ Gdk.cairo_set_source_pixbuf(bufctx, avatar, x_off, y_off);
+ bufctx.get_source().set_filter(Cairo.Filter.BEST);
+ bufctx.paint();
+ }
+ return buffer;
+ }
+}
+
+public class Dino.Ui.AvatarPicture : Gtk.Widget {
+ public float radius_percent { get; set; default = 0.2f; }
+ public ViewModel.AvatarPictureModel? model { get; set; }
+ private Gee.List<Tile> tiles = new Gee.ArrayList<Tile>();
+
+ private ViewModel.AvatarPictureModel? old_model;
+ private ulong model_tiles_items_changed_handler;
+
+ construct {
+ height_request = 35;
+ width_request = 35;
+ set_css_name("picture");
+ add_css_class("avatar");
+ notify["radius-percent"].connect(queue_draw);
+ notify["model"].connect(on_model_changed);
+ }
+
+ private void on_model_changed() {
+ if (old_model != null) {
+ old_model.tiles.disconnect(model_tiles_items_changed_handler);
+ }
+ foreach (Tile tile in tiles) {
+ tile.unparent();
+ tile.dispose();
+ }
+ tiles.clear();
+ old_model = model;
+ if (model != null) {
+ model_tiles_items_changed_handler = model.tiles.items_changed.connect(on_model_items_changed);
+ for(int i = 0; i < model.tiles.get_n_items(); i++) {
+ Tile tile = new Tile();
+ tile.model = model.tiles.get_item(i) as ViewModel.AvatarPictureTileModel;
+ tile.insert_after(this, tiles.is_empty ? null : tiles.last());
+ tiles.add(tile);
+ }
+ }
+ }
+
+ private void on_model_items_changed(uint position, uint removed, uint added) {
+ while (removed > 0) {
+ Tile old = tiles.remove_at((int) position);
+ old.unparent();
+ old.dispose();
+ removed--;
+ }
+ while (added > 0) {
+ Tile tile = new Tile();
+ tile.model = model.tiles.get_item(position) as ViewModel.AvatarPictureTileModel;
+ tile.insert_after(this, position == 0 ? null : tiles[(int) position - 1]);
+ tiles.insert((int) position, tile);
+ position++;
+ added--;
+ }
+ queue_allocate();
+ }
+
+ public override void measure(Orientation orientation, int for_size, out int minimum, out int natural, out int minimum_baseline, out int natural_baseline) {
+ minimum_baseline = natural_baseline = -1;
+ if (orientation == Orientation.HORIZONTAL) {
+ minimum = natural = width_request;
+ } else {
+ minimum = natural = height_request;
+ }
+ }
+
+ public override void size_allocate(int width, int height, int baseline) {
+ int half_width_size = width / 2;
+ int half_height_size = height / 2;
+ int half_width_offset = (width % 2 == 0) ? half_width_size : half_width_size + 1;
+ int half_height_offset = (height % 2 == 0) ? half_height_size : half_height_size + 1;
+ if (tiles.size == 1) {
+ tiles[0].allocate(width, height, baseline, null);
+ } else if (tiles.size == 2) {
+ tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
+ tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = height }, baseline);
+ } else if (tiles.size == 3) {
+ tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = height }, baseline);
+ tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
+ tiles[2].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
+ } else if (tiles.size == 4) {
+ tiles[0].allocate_size(Allocation() { x = 0, y = 0, width = half_width_size, height = half_height_size }, baseline);
+ tiles[1].allocate_size(Allocation() { x = half_width_offset, y = 0, width = half_width_size, height = half_height_size }, baseline);
+ tiles[2].allocate_size(Allocation() { x = 0, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
+ tiles[3].allocate_size(Allocation() { x = half_width_offset, y = half_height_offset, width = half_width_size, height = half_height_size }, baseline);
+ }
+ }
+
+ public override SizeRequestMode get_request_mode() {
+ return SizeRequestMode.CONSTANT_SIZE;
+ }
+
+ public override void snapshot(Gtk.Snapshot snapshot) {
+ Graphene.Rect bounds = Graphene.Rect();
+ bounds.init(0, 0, get_width(), get_height());
+ Gsk.RoundedRect rounded_rect = Gsk.RoundedRect();
+ rounded_rect.init_from_rect(bounds, (get_width() + get_height()) * 0.25f * radius_percent);
+ snapshot.push_rounded_clip(rounded_rect);
+ base.snapshot(snapshot);
+ snapshot.pop();
+ }
+
+ public override void dispose() {
+ model = null;
+ on_model_changed();
+ base.dispose();
+ }
+
+ private class Tile : Gtk.Widget {
+ public ViewModel.AvatarPictureTileModel? model { get; set; }
+ public Gdk.RGBA background_color { get; set; default = Gdk.RGBA(){ red = 1.0f, green = 1.0f, blue = 1.0f, alpha = 0.0f }; }
+ public string display_text { get { return label.get_text(); } set { label.set_text(value); } }
+ public File? image_file { get { return picture.file; } set { picture.file = value; } }
+
+ private Binding? background_color_binding;
+ private Binding? display_text_binding;
+ private Binding? image_file_binding;
+
+ private Label label = new Label("");
+ private Picture picture = new Picture();
+
+ construct {
+ label.insert_after(this, null);
+ label.attributes = new Pango.AttrList();
+ label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
+#if GTK_4_8 && VALA_0_58
+ picture.content_fit = Gtk.ContentFit.COVER;
+#elif GTK_4_8
+ picture.@set("content-fit", 2);
+#endif
+ picture.insert_after(this, label);
+ this.notify["model"].connect(on_model_changed);
+ }
+
+ private void on_model_changed() {
+ if (background_color_binding != null) background_color_binding.unbind();
+ if (display_text_binding != null) display_text_binding.unbind();
+ if (image_file_binding != null) image_file_binding.unbind();
+ if (model != null) {
+ background_color_binding = model.bind_property("background-color", this, "background-color", BindingFlags.SYNC_CREATE);
+ display_text_binding = model.bind_property("display-text", this, "display-text", BindingFlags.SYNC_CREATE);
+ image_file_binding = model.bind_property("image-file", this, "image-file", BindingFlags.SYNC_CREATE);
+ } else {
+ background_color_binding = null;
+ display_text_binding = null;
+ image_file_binding = null;
+ }
+ }
+
+ public override void dispose() {
+ if (background_color_binding != null) background_color_binding.unbind();
+ if (display_text_binding != null) display_text_binding.unbind();
+ if (image_file_binding != null) image_file_binding.unbind();
+ background_color_binding = null;
+ display_text_binding = null;
+ image_file_binding = null;
+ label.unparent();
+ picture.unparent();
+ base.dispose();
+ }
+
+ public override void size_allocate(int width, int height, int baseline) {
+ int min, nat, bl_min, bl_nat;
+ picture.measure(Orientation.HORIZONTAL, -1, out min, out nat, out bl_min, out bl_nat);
+ if (nat > 0) {
+ picture.allocate(width, height, baseline, null);
+ label.visible = false;
+ } else {
+ picture.allocate(0, 0, 0, null);
+ label.attributes = new Pango.AttrList();
+ label.attributes.insert(Pango.attr_foreground_new(uint16.MAX, uint16.MAX, uint16.MAX));
+ label.attributes.insert(Pango.attr_scale_new(double.min((double)width, (double)height) * 0.05));
+ label.margin_bottom = height/40;
+ label.visible = true;
+ label.allocate(width, height, baseline, null);
+ }
+ }
+
+ public override void snapshot(Gtk.Snapshot snapshot) {
+ if (label.visible) {
+ Graphene.Rect bounds = Graphene.Rect();
+ bounds.init(0, 0, get_width(), get_height());
+ snapshot.append_node(new Gsk.ColorNode(background_color, bounds));
+ }
+ base.snapshot(snapshot);
+ }
+ }
+} \ No newline at end of file