using Gee; using Gtk; private class SizeCache { private static SizeCache instance = null; private HashMap size_cache = new HashMap(); public static SizeCache get_instance() { if (instance == null) { instance = new SizeCache(); } return instance; } public Graphene.Size? get_size(string image_path) { return size_cache.get(image_path); } public void set_size(string image_path, Graphene.Size size) { size_cache.set(image_path, size); } } private class ImageBubbleLayout : BubbleLayout { public string image_path; public bool is_downloaded; public string? attachment_guid; private Graphene.Size image_size; private Gdk.Texture? cached_texture = null; public ImageBubbleLayout(string image_path, bool from_me, Widget parent, float max_width, Graphene.Size? image_size = null) { base(parent, max_width); this.from_me = from_me; this.image_path = image_path; this.is_downloaded = false; // Calculate image dimensions for layout calculate_image_dimensions(image_size); } private void calculate_image_dimensions(Graphene.Size? image_size) { if (image_size != null) { this.image_size = image_size; return; } var cached_size = SizeCache.get_instance().get_size(image_path); if (cached_size != null) { this.image_size = cached_size; return; } // Try to load the image to get its dimensions try { warning("No image size provided, loading image to get dimensions"); var texture = Gdk.Texture.from_filename(image_path); var original_width = (float)texture.get_width(); var original_height = (float)texture.get_height(); this.image_size = Graphene.Size() { width = original_width, height = original_height }; SizeCache.get_instance().set_size(image_path, this.image_size); } catch (Error e) { // Fallback dimensions if image can't be loaded warning("Failed to load image %s: %s", image_path, e.message); this.image_size = Graphene.Size() { width = 200.0f, height = 150.0f }; } } private void load_image_if_needed() { if (cached_texture != null) { return; } if (!is_downloaded) { return; } try { cached_texture = Gdk.Texture.from_filename(image_path); } catch (Error e) { warning("Failed to load image %s: %s", image_path, e.message); } } private float intrinsic_height { get { var scale_factor = float.min(max_width / image_size.width, 1.0f); return image_size.height * scale_factor; } } private float intrinsic_width { get { var scale_factor = float.min(max_width / image_size.width, 1.0f); return image_size.width * scale_factor; } } public override float get_height() { return float.max(intrinsic_height, 100.0f); } public override float get_width() { return float.max(intrinsic_width, 200.0f); } public override void draw_content(Snapshot snapshot) { load_image_if_needed(); snapshot.save(); var image_rect = Graphene.Rect () { origin = Graphene.Point() { x = 0, y = 0 }, size = Graphene.Size() { width = intrinsic_width, height = intrinsic_height } }; // Center image in the bubble (if it's smaller than the bubble) snapshot.translate(Graphene.Point() { x = (get_width() - intrinsic_width) / 2, y = (get_height() - intrinsic_height) / 2 }); if (cached_texture != null) { snapshot.append_texture(cached_texture, image_rect); } else { snapshot.append_color(Gdk.RGBA() { red = 0.6f, green = 0.6f, blue = 0.6f, alpha = 0.5f }, image_rect); } snapshot.restore(); } public override void copy(Gdk.Clipboard clipboard) { clipboard.set_texture(cached_texture); } }