From 21c926456d3832c510a454d975551b4f30bcfcac Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 3 May 2025 22:12:26 -0700 Subject: [PATCH] reorg: message layout becomes interface for other types of chat items (like date) --- src/meson.build | 5 +- .../bubble-layout.vala} | 101 +++--------------- .../layouts/chat-item-layout.vala | 10 ++ .../layouts/text-bubble-layout.vala | 96 +++++++++++++++++ src/message-list/message-drawing-area.vala | 33 +++--- 5 files changed, 142 insertions(+), 103 deletions(-) rename src/message-list/{message-layout.vala => layouts/bubble-layout.vala} (69%) create mode 100644 src/message-list/layouts/chat-item-layout.vala create mode 100644 src/message-list/layouts/text-bubble-layout.vala diff --git a/src/meson.build b/src/meson.build index 73c3bdb..882c1e5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -31,7 +31,10 @@ sources = [ 'message-list/message-list-view.vala', 'message-list/message-list-model.vala', 'message-list/message-drawing-area.vala', - 'message-list/message-layout.vala', + + 'message-list/layouts/bubble-layout.vala', + 'message-list/layouts/chat-item-layout.vala', + 'message-list/layouts/text-bubble-layout.vala', 'models/conversation.vala', 'models/message.vala', diff --git a/src/message-list/message-layout.vala b/src/message-list/layouts/bubble-layout.vala similarity index 69% rename from src/message-list/message-layout.vala rename to src/message-list/layouts/bubble-layout.vala index 266534b..e4d123d 100644 --- a/src/message-list/message-layout.vala +++ b/src/message-list/layouts/bubble-layout.vala @@ -1,7 +1,6 @@ using Gtk; -using Gee; -private struct MessageLayoutConstants { +private struct BubbleLayoutConstants { public float tail_width; public float tail_curve_offset; public float tail_side_offset; @@ -9,7 +8,7 @@ private struct MessageLayoutConstants { public float corner_radius; public float text_padding; - public MessageLayoutConstants(float scale_factor) { + public BubbleLayoutConstants(float scale_factor) { tail_width = 15.0f / scale_factor; tail_curve_offset = 2.5f / scale_factor; tail_side_offset = 0.0f / scale_factor; @@ -19,61 +18,18 @@ private struct MessageLayoutConstants { } } -private class MessageLayout : Object +private abstract class BubbleLayout : Object, ChatItemLayout { - public Message message; + public bool from_me { get; set; } - private float max_width; - private Pango.Layout layout; - private Widget parent; - private MessageLayoutConstants constants; + protected float max_width; + protected Widget parent; + protected BubbleLayoutConstants constants; - public MessageLayout(Message message, Widget parent, float max_width) { - this.message = message; + protected BubbleLayout(Widget parent, float max_width) { this.max_width = max_width; this.parent = parent; - this.constants = MessageLayoutConstants(parent.get_scale_factor()); - - layout = parent.create_pango_layout(message.text); - - // Get the system font settings - var settings = Gtk.Settings.get_default(); - var font_name = settings.gtk_font_name; - - // Create font description from system font - var font_desc = Pango.FontDescription.from_string(font_name); - - var size = font_desc.get_size(); - font_desc.set_size((int)(size)); - layout.set_font_description(font_desc); - - // Set max width - layout.set_width((int)text_available_width * Pango.SCALE); - } - - public bool from_me { - get { - return message.from_me; - } - } - - private float text_available_width { - get { - return max_width - text_x_offset - constants.text_padding; - } - } - - private float text_x_offset { - get { - return from_me ? constants.text_padding : constants.tail_width + constants.text_padding; - } - } - - private float text_x_padding { - get { - // Opposite of text_x_offset - return from_me ? constants.tail_width + constants.text_padding : constants.text_padding; - } + this.constants = BubbleLayoutConstants(parent.get_scale_factor()); } private Gdk.RGBA background_color { @@ -87,47 +43,18 @@ private class MessageLayout : Object } } - public float get_height() { - Pango.Rectangle ink_rect, logical_rect; - layout.get_pixel_extents(out ink_rect, out logical_rect); + public abstract float get_height(); - return logical_rect.height + constants.corner_radius + constants.tail_bottom_padding; - } - - public float get_width() { - Pango.Rectangle ink_rect, logical_rect; - layout.get_pixel_extents(out ink_rect, out logical_rect); - - return logical_rect.width + text_x_offset + text_x_padding; - } + public abstract float get_width(); public void draw(Snapshot snapshot) { with_bubble_clip(snapshot, (snapshot) => { draw_background(snapshot); - draw_text(snapshot); + draw_content(snapshot); }); } - private void draw_text(Snapshot snapshot) - { - snapshot.save(); - - Pango.Rectangle ink_rect, logical_rect; - layout.get_pixel_extents(out ink_rect, out logical_rect); - snapshot.translate(Graphene.Point() { - x = text_x_offset, - y = ((get_height() - constants.tail_bottom_padding) - logical_rect.height) / 2 - }); - - snapshot.append_layout(layout, Gdk.RGBA() { - red = 1.0f, - green = 1.0f, - blue = 1.0f, - alpha = 1.0f - }); - - snapshot.restore(); - } + public abstract void draw_content(Snapshot snapshot); private void draw_background(Snapshot snapshot) { @@ -152,7 +79,7 @@ private class MessageLayout : Object snapshot.append_linear_gradient(bounds, start_point, end_point, stops); } - private void with_bubble_clip(Snapshot snapshot, Func func) { + protected void with_bubble_clip(Snapshot snapshot, Func func) { var width = get_width(); var height = get_height(); diff --git a/src/message-list/layouts/chat-item-layout.vala b/src/message-list/layouts/chat-item-layout.vala new file mode 100644 index 0000000..4a3c18e --- /dev/null +++ b/src/message-list/layouts/chat-item-layout.vala @@ -0,0 +1,10 @@ +using Gtk; + +interface ChatItemLayout : Object +{ + public abstract bool from_me { get; set; } + + public abstract float get_height(); + public abstract float get_width(); + public abstract void draw(Snapshot snapshot); +} diff --git a/src/message-list/layouts/text-bubble-layout.vala b/src/message-list/layouts/text-bubble-layout.vala new file mode 100644 index 0000000..30de42c --- /dev/null +++ b/src/message-list/layouts/text-bubble-layout.vala @@ -0,0 +1,96 @@ +using Gtk; + +private class TextBubbleLayout : BubbleLayout +{ + public Message message; + private Pango.Layout layout; + + public TextBubbleLayout(Message message, Widget parent, float max_width) { + base(parent, max_width); + + this.from_me = message.from_me; + this.message = message; + + layout = parent.create_pango_layout(message.text); + + // Get the system font settings + var settings = Gtk.Settings.get_default(); + var font_name = settings.gtk_font_name; + + // Create font description from system font + var font_desc = Pango.FontDescription.from_string(font_name); + + var size = font_desc.get_size(); + font_desc.set_size((int)(size)); + layout.set_font_description(font_desc); + + // Set max width + layout.set_width((int)text_available_width * Pango.SCALE); + } + + private float text_available_width { + get { + return max_width - text_x_offset - constants.text_padding; + } + } + + private float text_x_offset { + get { + return from_me ? constants.text_padding : constants.tail_width + constants.text_padding; + } + } + + private float text_x_padding { + get { + // Opposite of text_x_offset + return from_me ? constants.tail_width + constants.text_padding : constants.text_padding; + } + } + + private Gdk.RGBA background_color { + get { + return from_me ? parent.get_color() : Gdk.RGBA() { + red = 1.0f, + green = 1.0f, + blue = 1.0f, + alpha = 0.08f + }; + } + } + + public override float get_height() { + Pango.Rectangle ink_rect, logical_rect; + layout.get_pixel_extents(out ink_rect, out logical_rect); + + return logical_rect.height + constants.corner_radius + constants.tail_bottom_padding; + } + + public override float get_width() { + Pango.Rectangle ink_rect, logical_rect; + layout.get_pixel_extents(out ink_rect, out logical_rect); + + return logical_rect.width + text_x_offset + text_x_padding; + } + + public override void draw_content(Snapshot snapshot) { + snapshot.save(); + + Pango.Rectangle ink_rect, logical_rect; + layout.get_pixel_extents(out ink_rect, out logical_rect); + snapshot.translate(Graphene.Point() { + x = text_x_offset, + y = ((get_height() - constants.tail_bottom_padding) - logical_rect.height) / 2 + }); + + snapshot.append_layout(layout, Gdk.RGBA() { + red = 1.0f, + green = 1.0f, + blue = 1.0f, + alpha = 1.0f + }); + + snapshot.restore(); + } +} + + diff --git a/src/message-list/message-drawing-area.vala b/src/message-list/message-drawing-area.vala index 8791059..7d0fce4 100644 --- a/src/message-list/message-drawing-area.vala +++ b/src/message-list/message-drawing-area.vala @@ -4,7 +4,7 @@ using Gee; private class MessageDrawingArea : Widget { private SortedSet _messages = new TreeSet(); - private ArrayList _message_layouts = new ArrayList(); + private ArrayList _chat_items = new ArrayList(); private const float bubble_padding = 10.0f; private const float bubble_margin = 18.0f; @@ -31,8 +31,8 @@ private class MessageDrawingArea : Widget } else { // compute total message layout height float total_height = 0.0f; - _message_layouts.foreach((message_layout) => { - total_height += message_layout.get_height() + bubble_padding; + _chat_items.foreach((chat_item) => { + total_height += chat_item.get_height() + bubble_padding; return true; }); @@ -52,9 +52,9 @@ private class MessageDrawingArea : Widget public override void snapshot(Snapshot snapshot) { var container_width = get_width(); float y_offset = 0; - _message_layouts.foreach((message_layout) => { - var message_width = message_layout.get_width(); - var message_height = message_layout.get_height(); + _chat_items.foreach((chat_item) => { + var message_width = chat_item.get_width(); + var message_height = chat_item.get_height(); snapshot.save(); @@ -63,14 +63,14 @@ private class MessageDrawingArea : Widget // Translate to the correct position snapshot.translate(Graphene.Point() { - x = (message_layout.from_me ? (container_width - message_width - bubble_margin) : bubble_margin), + x = (chat_item.from_me ? (container_width - message_width - bubble_margin) : bubble_margin), y = y_offset }); // Undo the y-axis flip, origin is top left snapshot.translate(Graphene.Point() { x = 0, y = -message_height }); - message_layout.draw(snapshot); + chat_item.draw(snapshot); snapshot.restore(); y_offset -= message_height + bubble_padding; @@ -83,14 +83,17 @@ private class MessageDrawingArea : Widget var container_width = get_width(); float max_width = container_width * 0.90f; - _message_layouts.clear(); - _messages - .order_by((a, b) => (int)b.date - (int)a.date) // reverse order - .foreach((message) => { - _message_layouts.add(new MessageLayout(message, this, max_width)); - return true; - }); + _chat_items.clear(); + var sorted_messages = _messages + .order_by((a, b) => (int)b.date - (int)a.date); // reverse order + + + sorted_messages.foreach((message) => { + _chat_items.add(new TextBubbleLayout(message, this, max_width)); + return true; + }); + queue_draw(); queue_resize(); }