From 1a2dad08a5b9e11e32bfe3805698e2b275917c80 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Fri, 6 Jun 2025 14:33:40 -0700 Subject: [PATCH] adds image bubble layout for attachments --- .../conversation-list-model.vala | 1 - src/meson.build | 1 + .../layouts/image-bubble-layout.vala | 98 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/transcript/layouts/image-bubble-layout.vala diff --git a/src/conversation-list/conversation-list-model.vala b/src/conversation-list/conversation-list-model.vala index e762a75..d50f983 100644 --- a/src/conversation-list/conversation-list-model.vala +++ b/src/conversation-list/conversation-list-model.vala @@ -89,7 +89,6 @@ public class ConversationListModel : Object, ListModel } // Update changed conversations - GLib.message("Changed conversations: %d", changed_conversations.size); foreach (var conv in changed_conversations) { // Find position of old conversation uint old_pos = 0; diff --git a/src/meson.build b/src/meson.build index 06bff60..9dcebb7 100644 --- a/src/meson.build +++ b/src/meson.build @@ -36,6 +36,7 @@ sources = [ 'transcript/layouts/bubble-layout.vala', 'transcript/layouts/chat-item-layout.vala', 'transcript/layouts/date-item-layout.vala', + 'transcript/layouts/image-bubble-layout.vala', 'transcript/layouts/sender-annotation-layout.vala', 'transcript/layouts/text-bubble-layout.vala', diff --git a/src/transcript/layouts/image-bubble-layout.vala b/src/transcript/layouts/image-bubble-layout.vala new file mode 100644 index 0000000..53cab58 --- /dev/null +++ b/src/transcript/layouts/image-bubble-layout.vala @@ -0,0 +1,98 @@ +using Gtk; + +private class ImageBubbleLayout : BubbleLayout +{ + public string image_path; + public bool is_downloaded; + + private Graphene.Size image_size; + private Gdk.Texture? cached_texture = null; + private const float max_image_width = 300.0f; + private const float max_image_height = 400.0f; + + 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; + } + + // Try to load the image to get its dimensions + try { + var texture = Gdk.Texture.from_filename(image_path); + var original_width = (float)texture.get_width(); + var original_height = (float)texture.get_height(); + + // Calculate scaled dimensions while maintaining aspect ratio + var scale_factor = float.min( + max_image_width / original_width, + max_image_height / original_height + ); + scale_factor = float.min(scale_factor, 1.0f); // Don't scale up + + this.image_size = Graphene.Size() { width = original_width * scale_factor, height = original_height * scale_factor }; + } 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); + } + } + + public override float get_height() { + float aspect_ratio = image_size.width / image_size.height; + if (image_size.width > max_width) { + return max_width / aspect_ratio; + } + + return image_size.height; + } + + public override float get_width() { + return float.min(image_size.width, max_width); + } + + 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 = get_width(), height = get_height() } + }; + + 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(); + } +} \ No newline at end of file