diff --git a/src/transcript/transcript-drawing-area.vala b/src/transcript/transcript-drawing-area.vala index 39e9048..dd29b9c 100644 --- a/src/transcript/transcript-drawing-area.vala +++ b/src/transcript/transcript-drawing-area.vala @@ -4,12 +4,25 @@ using Gee; private class TranscriptDrawingArea : Widget { public bool show_sender = true; + public Adjustment? viewport { + get { + return _viewport; + } + + set { + _viewport = value; + queue_draw(); + } + } private ArrayList _messages = new ArrayList(); private ArrayList _chat_items = new ArrayList(); + private Adjustment? _viewport = null; private const float bubble_margin = 18.0f; + private const bool debug_viewport = false; + public TranscriptDrawingArea() { add_css_class("transcript-drawing-area"); } @@ -49,35 +62,65 @@ private class TranscriptDrawingArea : Widget base.size_allocate(width, height, baseline); recompute_message_layouts(); } - + public override void snapshot(Snapshot snapshot) { - float y_offset = 0; - var container_width = get_width(); + const int viewport_y_padding = 50; + var viewport_rect = Graphene.Rect() { + origin = Graphene.Point() { x = 0, y = ((int)viewport.value - viewport_y_padding) }, + size = Graphene.Size() { width = get_width(), height = (int)viewport.page_size + (viewport_y_padding * 2) } + }; + + if (debug_viewport) { + // Draw viewport outline for debugging + var color = Gdk.RGBA() { red = 1.0f, green = 0, blue = 0, alpha = 1.0f }; + snapshot.append_border( + Gsk.RoundedRect().init_from_rect(viewport_rect, 0), + { 1, 1, 1, 1 }, + { color, color, color, color } + ); + } // Draw each item in reverse order, since the messages are in reverse order + float y_offset = 0; + int container_width = get_width(); for (int i = _chat_items.size - 1; i >= 0; i--) { var chat_item = _chat_items[i]; var item_width = chat_item.get_width(); var item_height = chat_item.get_height(); - snapshot.save(); - - // Flip the y-axis, since our parent is upside down (so newest messages are at the bottom) - snapshot.scale(1.0f, -1.0f); - - // Translate to the correct position - snapshot.translate(Graphene.Point() { + var origin = Graphene.Point() { x = (chat_item.from_me ? (container_width - item_width - bubble_margin) : bubble_margin), y = y_offset - }); + }; - // Undo the y-axis flip, origin is top left - snapshot.translate(Graphene.Point() { x = 0, y = -item_height }); + var size = Graphene.Size() { + width = item_width, + height = item_height + }; - chat_item.draw(snapshot); - snapshot.restore(); + var rect = Graphene.Rect() { + origin = origin, + size = size + }; - y_offset -= item_height + chat_item.vertical_padding; + // Skip drawing if this item is not in the viewport + if (viewport_rect.intersection(rect, null)) { + snapshot.save(); + + // Translate to the correct position + snapshot.translate(origin); + + // Flip the y-axis, since our parent is upside down (so newest messages are at the bottom) + snapshot.scale(1.0f, -1.0f); + + // Undo the y-axis flip, origin is top left + snapshot.translate(Graphene.Point() { x = 0, y = -item_height }); + + chat_item.draw(snapshot); + snapshot.restore(); + } + + y_offset += item_height + chat_item.vertical_padding; } } diff --git a/src/transcript/transcript-view.vala b/src/transcript/transcript-view.vala index 1c0d9bb..a081bb2 100644 --- a/src/transcript/transcript-view.vala +++ b/src/transcript/transcript-view.vala @@ -17,8 +17,10 @@ public class TranscriptView : Adw.Bin _model = value; if (value != null) { - // Reset scroll position - scrolled_window.vadjustment = new Gtk.Adjustment(0, 0, 0, 0, 0, 0); + // Reset scroll position by updating the existing adjustment + scrolled_window.vadjustment.value = 0; + scrolled_window.vadjustment.upper = 0; + scrolled_window.vadjustment.page_size = 0; weak TranscriptView self = this; messages_changed_handler_id = value.messages_changed.connect(() => { @@ -56,8 +58,14 @@ public class TranscriptView : Adw.Bin scrolled_window.set_child(transcript_drawing_area); scrolled_window.add_css_class("message-list-scroller"); + transcript_drawing_area.viewport = scrolled_window.vadjustment; container.set_content(scrolled_window); + // Connect to the adjustment's value_changed signal + scrolled_window.vadjustment.value_changed.connect(() => { + transcript_drawing_area.viewport = scrolled_window.vadjustment; + }); + var header_bar = new Adw.HeaderBar(); title_label.single_line_mode = true; title_label.ellipsize = Pango.EllipsizeMode.END;